Skip to content

fix(evaluators): normalize records with galileo-core - #277

Merged
namrataghadi-galileo merged 5 commits into
mainfrom
feature/SAO-17516-galileo-record-normalization
Oct 1, 2026
Merged

namrataghadi-galileo merged 5 commits into
mainfrom
feature/SAO-17516-galileo-record-normalization

Conversation

@namrataghadi-galileo

@namrataghadi-galileo namrataghadi-galileo commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • Add Galileo Core based normalization to the standalone Galileo record factory.
  • Replace the splunk-ao dependency with galileo-core.
  • Verify factory outputs are concrete Galileo Core BaseStep subclasses and round-trip through their typed JSON schemas, including nested traces and sessions.
  • Verify the factory outputs can be converted by Galileo Core's RecordTypeAdapter after adding execution IDs and parent/session/trace relationships, matching the conversion input used by Orbit's execute_evaluator.

The Luna evaluator is intentionally unchanged and does not call this factory. The factory output is the concrete step model shape a future scorer-invocation path can hydrate and pass to execute_evaluator. That path must still assign unique step IDs and provide the authenticated execution context; this PR does not add scorer execution or authorization.

Validation

  • make -C evaluators/contrib/galileo check — 166 passed; lint and typecheck passed.

@codecov

codecov Bot commented Sep 30, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 95.89041% with 15 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
...control_evaluator_galileo/records/normalization.py 95.67% 15 Missing ⚠️

📢 Thoughts on this report? Let us know!

@namrataghadi-galileo namrataghadi-galileo changed the title refactor(galileo): normalize records with galileo-core fix(evaluators): normalize records with galileo-core Sep 30, 2026
@josjeon
josjeon self-requested a review October 1, 2026 03:42

@wrisa wrisa left a comment •

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some others flagged MEDIUM by agent,

  1. Unreachable int branch in _GalileoJSONEncoder.default — normalization.py:54-55
    if isinstance(value, int) and not isinstance(value, bool):
    return value if -_MAX_SAFE_INTEGER <= value <= _MAX_SAFE_INTEGER else str(value)
    json.JSONEncoder.default() is only called for types the base encoder can't handle natively. Python int is handled natively — this branch is never reached via json.dumps(). The test at test_records_factory.py:770 calls
    encoder.default(2**53) directly, bypassing normal dispatch, so it passes misleadingly.

  1. Enum.value not recursively normalized in _GalileoJSONEncoder.default — normalization.py:45-46
    if isinstance(value, Enum):
    return value.value
    If an enum's .value is itself non-serializable, json.dumps will fail or recurse unexpectedly. Compare with _sdk_json_value at normalization.py:73-74, which correctly recurses: return _sdk_json_value(value.value, active=active).

  1. _json_compatible silently passes set/bytes/Path through — normalization.py:131-141
    set and frozenset are not Sequence, so they fall through to return value at line 141, unchanged. This is inconsistent with _sdk_json_value which explicitly handles set | frozenset at normalization.py:106-107. Downstream Pydantic
    validation may reject the raw set.

  1. Dead duplicate fallback in session_output — normalization.py:454-456
    if all(isinstance(item, Mapping) for item in value):
    return [_json_compatible(item) for item in value]
    return [_json_compatible(item) for item in value] # identical
    Both branches are identical. One was presumably meant to call cls.json_text(value) for non-mapping sequences, but was never finished.

  1. GalileoRecordNormalizer exported in all without clear intent — records/init.py:16
    "GalileoRecordNormalizer",
    This is an internal normalization class with no public documentation. Exporting it makes its method signatures part of the semver contract. If it's intended for internal use only, it should be removed from all (or prefixed with _).

@namrataghadi-galileo

Copy link
Copy Markdown
Contributor Author

_

GalileoRecordNormalizer exported in all without clear intent — records/init.py:16
"GalileoRecordNormalizer",
This is an internal normalization class with no public documentation. Exporting it makes its method signatures part of the semver contract. If it's intended for internal use only, it should be removed from all (or prefixed with _).

_

This export is intentional. GalileoRecordNormalizer is the shared normalization utility for Galileo evaluator integrations, including future LLMAJ and registered-code evaluators. It remains inside the Galileo evaluator package and does not become part of generic Agent Control. We will keep the export and maintain its canonical Galileo model behavior.

@namrataghadi-galileo

Copy link
Copy Markdown
Contributor Author

Unreachable int branch in _GalileoJSONEncoder.default — normalization.py:54-55
if isinstance(value, int) and not isinstance(value, bool):
return value if -_MAX_SAFE_INTEGER <= value <= _MAX_SAFE_INTEGER else str(value)
json.JSONEncoder.default() is only called for types the base encoder can't handle natively. Python int is handled natively — this branch is never reached via json.dumps(). The test at test_records_factory.py:770 calls
encoder.default(2**53) directly, bypassing normal dispatch, so it passes misleadingly.

Agreed. json.JSONEncoder.default() is not called for native Python integers, so this branch is unreachable through json.dumps(). We will remove it and keep safe-integer conversion in _sdk_json_value(). We will also update _json_text() to use that path for top-level integers and replace the direct encoder.default() test with public serializer tests. This preserves Galileo core model types while validating the actual wire-format behavior.

@namrataghadi-galileo

Copy link
Copy Markdown
Contributor Author

Enum.value not recursively normalized in _GalileoJSONEncoder.default — normalization.py:45-46
if isinstance(value, Enum):
return value.value
If an enum's .value is itself non-serializable, json.dumps will fail or recurse unexpectedly. Compare with _sdk_json_value at normalization.py:73-74, which correctly recurses: return _sdk_json_value(value.value, active=active).

Valid comment. Galileo core already serializes its standard enum fields through Pydantic. This custom normalizer also handles enums nested in metadata and other arbitrary values, so returning value.value directly can leave non-serializable values. We will recursively normalize enum values through _sdk_json_value() and add focused coverage. This does not change Galileo core model types.

@namrataghadi-galileo

Copy link
Copy Markdown
Contributor Author

json_compatible silently passes set/bytes/Path through — normalization.py:131-141
set and frozenset are not Sequence, so they fall through to return value at line 141, unchanged. This is inconsistent with _sdk_json_value which explicitly handles set | frozenset at normalization.py:106-107. Downstream Pydantic
validation may reject the raw set.

Valid comment. I will normalize sets, bytes, paths, and nested model values using the same JSON-safe behavior as _sdk_json_value(). Will add coverage for nested values and verify that the normalized output passes json.dumps() and can be reconstructed with json.loads() in Orbit. This changes only the serialized representation and does not alter Galileo core model types.

@namrataghadi-galileo

Copy link
Copy Markdown
Contributor Author

Dead duplicate fallback in session_output — normalization.py:454-456
if all(isinstance(item, Mapping) for item in value):
return [_json_compatible(item) for item in value]
return [_json_compatible(item) for item in value] # identical
Both branches are identical. One was presumably meant to call cls.json_text(value) for non-mapping sequences, but was never finished.
Valid comment. The two fallback branches are identical. We will keep the earlier handling for valid Galileo messages, Documents, and content parts, then serialize any remaining unsupported sequence as JSON text. This avoids returning arbitrary lists that Galileo core may reject and keeps the result safely JSON-loadable by Orbit. We will add a focused Session model validation test.

@namrataghadi-galileo
namrataghadi-galileo enabled auto-merge (squash) October 1, 2026 17:30
@namrataghadi-galileo
namrataghadi-galileo merged commit a0d2660 into main Oct 1, 2026
6 checks passed
@namrataghadi-galileo
namrataghadi-galileo deleted the feature/SAO-17516-galileo-record-normalization branch October 1, 2026 17:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants