Skip to content

Commit 38e3d95

Browse files
test(galileo): align record contract assertions
1 parent c14bcc1 commit 38e3d95

3 files changed

Lines changed: 45 additions & 31 deletions

File tree

‎evaluators/contrib/galileo/src/agent_control_evaluator_galileo/records/factory.py‎

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,21 @@
77

88
from agent_control_models import Step
99
from galileo_core.schemas.logging.session import Session
10-
from galileo_core.schemas.logging.span import LlmSpan, RetrieverSpan, ToolSpan
10+
from galileo_core.schemas.logging.span import (
11+
AgentSpan,
12+
ControlSpan,
13+
LlmSpan,
14+
RetrieverSpan,
15+
ToolSpan,
16+
WorkflowSpan,
17+
)
1118
from galileo_core.schemas.logging.trace import Trace
1219

1320
from .normalization import GalileoRecordNormalizer
1421

1522
type GalileoRecord = LlmSpan | ToolSpan | RetrieverSpan | Trace | Session
1623
type GalileoSpan = LlmSpan | ToolSpan | RetrieverSpan
24+
type GalileoCoreSpan = AgentSpan | WorkflowSpan | LlmSpan | RetrieverSpan | ToolSpan | ControlSpan
1725
_MISSING = object()
1826
_SPAN_TYPES = {"llm", "tool", "retriever"}
1927
_RECORD_TYPES = _SPAN_TYPES | {"trace", "session"}
@@ -45,8 +53,8 @@ def _selected_values(
4553
return selected_data, None
4654

4755

48-
def _span_children(children: list[GalileoRecord], *, parent_type: str) -> list[GalileoSpan]:
49-
spans: list[GalileoSpan] = []
56+
def _span_children(children: list[GalileoRecord], *, parent_type: str) -> list[GalileoCoreSpan]:
57+
spans: list[GalileoCoreSpan] = []
5058
for child in children:
5159
if not isinstance(child, (LlmSpan, ToolSpan, RetrieverSpan)):
5260
raise RecordFactoryError(f"Galileo {parent_type} children must be span steps.")

‎evaluators/contrib/galileo/tests/test_luna_evaluator.py‎

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -849,7 +849,16 @@ def handler(request: httpx.Request) -> httpx.Response:
849849
).model_dump(mode="json", exclude_none=True),
850850
"config": {"request_timeout_seconds": 8.0},
851851
}
852-
assert captured["body"] == expected_body
852+
actual_body = captured["body"]
853+
assert isinstance(actual_body, dict)
854+
actual_record = actual_body["record"]
855+
expected_record = expected_body["record"]
856+
assert isinstance(actual_record, dict)
857+
assert isinstance(expected_record, dict)
858+
# Each factory call assigns a fresh created_at timestamp.
859+
actual_record.pop("created_at", None)
860+
expected_record.pop("created_at", None)
861+
assert actual_body == expected_body
853862

854863
# The emitted body also satisfies the exact additive Orbit #1720 shape.
855864
orbit_request = _Orbit1720Request.model_validate(expected_body)
@@ -859,7 +868,7 @@ def handler(request: httpx.Request) -> httpx.Response:
859868
assert "selected question" in json.dumps(orbit_request.record.input)
860869
assert "selected answer" in json.dumps(orbit_request.record.output)
861870
assert orbit_request.inputs.tools == orbit_request.record.tools
862-
assert orbit_request.record.dataset_output == {"text": "expected"}
871+
assert json.loads(orbit_request.record.dataset_output) == {"text": "expected"}
863872
assert orbit_request.inputs.ground_truth == {"text": "expected"}
864873
assert orbit_request.record.user_metadata == {"source": "runtime"}
865874
assert "context" not in expected_body["record"]

‎evaluators/contrib/galileo/tests/test_records_factory.py‎

Lines changed: 23 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -880,12 +880,10 @@ class DocumentModel(BaseModel):
880880
def test_llm_tool_definitions_normalize_nested_pydantic_uuid_and_datetime_values() -> None:
881881
identifier = uuid4()
882882
timestamp = datetime(2025, 1, 2, tzinfo=UTC)
883-
source = LlmSpan(
884-
input="question",
885-
tools=[{"id": identifier, "created_at": timestamp}],
883+
normalized_tools = GalileoRecordNormalizer.llm_tools(
884+
[{"id": identifier, "created_at": timestamp}]
886885
)
887-
wire_json = source.model_dump(mode="json", exclude_none=True)
888-
record = LlmSpan.model_validate(wire_json)
886+
record = LlmSpan.model_validate({"input": "question", "tools": normalized_tools})
889887

890888
assert isinstance(record, LlmSpan)
891889
assert record.tools == [
@@ -1260,6 +1258,23 @@ def test_luna_wire_hierarchy_uses_top_level_fields_not_context() -> None:
12601258
assert rebuilt_trace.spans[0].name == "wire tool"
12611259
assert all(span.name != "ignored span" for span in rebuilt_trace.spans)
12621260

1261+
# Galileo Core supplies empty hierarchy defaults when top-level fields are absent.
1262+
# Context-only hierarchy must not be treated as canonical spans or traces.
1263+
context_only_trace = ScorerInvokeRecord.model_validate(
1264+
{
1265+
"type": "trace",
1266+
"name": "context only trace",
1267+
"context": {
1268+
"children": [{"type": "tool", "name": "ignored child"}],
1269+
"spans": [{"type": "tool", "name": "ignored span"}],
1270+
},
1271+
}
1272+
)
1273+
rebuilt_context_only_trace = Trace.model_validate(
1274+
context_only_trace.model_dump(mode="json", exclude_none=True)
1275+
)
1276+
assert rebuilt_context_only_trace.spans == []
1277+
12631278
context_only_session = ScorerInvokeRecord.model_validate(
12641279
{
12651280
"type": "session",
@@ -1268,24 +1283,8 @@ def test_luna_wire_hierarchy_uses_top_level_fields_not_context() -> None:
12681283
}
12691284
)
12701285
context_only_json = context_only_session.model_dump(mode="json", exclude_none=True)
1271-
with pytest.raises(ValidationError) as missing_spans:
1272-
Trace.model_validate(
1273-
ScorerInvokeRecord.model_validate(
1274-
{
1275-
"type": "trace",
1276-
"name": "context only trace",
1277-
"context": {
1278-
"children": [{"type": "tool", "name": "ignored child"}],
1279-
"spans": [{"type": "tool", "name": "ignored span"}],
1280-
},
1281-
}
1282-
).model_dump(mode="json", exclude_none=True)
1283-
)
1284-
assert any(error["loc"] == ("spans",) for error in missing_spans.value.errors())
1285-
1286-
with pytest.raises(ValidationError) as missing_traces:
1287-
Session.model_validate(context_only_json)
1288-
assert any(error["loc"] == ("traces",) for error in missing_traces.value.errors())
1286+
rebuilt_context_only_session = Session.model_validate(context_only_json)
1287+
assert rebuilt_context_only_session.traces == []
12891288

12901289

12911290
def test_luna_wire_round_trip_preserves_all_trace_span_types_and_nested_tool_span() -> None:
@@ -1337,8 +1336,6 @@ def test_luna_wire_round_trip_preserves_all_trace_span_types_and_nested_tool_spa
13371336
@pytest.mark.parametrize(
13381337
("payload", "core_model"),
13391338
[
1340-
({"type": "trace", "name": "trace", "input": "question"}, Trace),
1341-
({"type": "session", "name": "session"}, Session),
13421339
(
13431340
{
13441341
"type": "session",
@@ -1357,7 +1354,7 @@ def test_luna_wire_round_trip_preserves_all_trace_span_types_and_nested_tool_spa
13571354
),
13581355
],
13591356
)
1360-
def test_luna_wire_records_reject_missing_or_invalid_hierarchy(
1357+
def test_luna_wire_records_reject_invalid_hierarchy(
13611358
payload: dict[str, object],
13621359
core_model: type[BaseModel],
13631360
) -> None:

0 commit comments

Comments
 (0)