@@ -696,6 +696,16 @@ def test_session_content_parts_are_preserved_by_input_and_output_normalizers() -
696696 ]
697697
698698
699+ def test_session_output_serializes_unsupported_sequences_for_core_model () -> None :
700+ output = GalileoRecordNormalizer .session_output ([1 , 2 ])
701+
702+ assert isinstance (output , str )
703+ assert output == json .dumps ([1 , 2 ])
704+ assert json .loads (output ) == [1 , 2 ]
705+ session = Session (input = [], output = output , traces = [])
706+ assert session .output == output
707+
708+
699709def test_record_normalizer_serializes_nested_models_uuids_and_timestamps () -> None :
700710 identifier = uuid4 ()
701711 timestamp = datetime (2025 , 1 , 2 , 3 , 4 , 5 , tzinfo = UTC )
@@ -735,6 +745,9 @@ class NestedModel(BaseModel):
735745 value : int
736746 optional : str | None = None
737747
748+ class NestedValuesModel (BaseModel ):
749+ payload : object
750+
738751 @dataclass
739752 class NestedDataclass :
740753 identifier : object
@@ -756,21 +769,32 @@ def __init__(self) -> None:
756769 naive_timestamp = datetime (2025 , 1 , 2 )
757770 offset_timestamp = datetime (2025 , 1 , 2 , tzinfo = timezone (timedelta (hours = 2 )))
758771
772+ class StructuredState (Enum ):
773+ VALUE = {"identifier" : identifier , "timestamp" : utc_timestamp }
774+
759775 assert encoder .default (NestedModel (value = 3 )) == {"value" : 3 }
760776 assert encoder .default (utc_timestamp ) == "2025-01-02T00:00:00Z"
761- assert isinstance ( encoder .default (naive_timestamp ), str )
777+ assert encoder .default (naive_timestamp ) == naive_timestamp . astimezone (). isoformat ( )
762778 assert encoder .default (offset_timestamp ) == "2025-01-02T00:00:00+02:00"
763779 assert encoder .default (date (2025 , 1 , 2 )) == "2025-01-02"
764780 assert encoder .default (identifier ) == str (identifier )
765781 assert encoder .default (Path ("folder/file.txt" )) == "folder/file.txt"
766782 assert encoder .default (State .READY ) == "ready"
783+ assert json .dumps (StructuredState .VALUE , cls = _GalileoJSONEncoder ) == json .dumps (
784+ {"identifier" : str (identifier ), "timestamp" : "2025-01-02T00:00:00Z" }
785+ )
767786 assert encoder .default (b"text" ) == "text"
768787 assert encoder .default (b"\xff " ) == "<not serializable bytes>"
769788 assert encoder .default (NestedDataclass (identifier )) == {"identifier" : identifier }
770- assert encoder .default (2 ** 53 ) == str (2 ** 53 )
771789 assert encoder .default ({"item" }) == ["item" ]
772790 assert encoder .default (object ()) == "<object>"
773791
792+ assert GalileoRecordNormalizer .json_text (42 ) == "42"
793+ assert GalileoRecordNormalizer .json_text (2 ** 53 ) == json .dumps (str (2 ** 53 ))
794+ assert GalileoRecordNormalizer .json_text ({"large_integer" : 2 ** 53 }) == json .dumps (
795+ {"large_integer" : str (2 ** 53 )}
796+ )
797+
774798 recursive : list [object ] = []
775799 recursive .append (recursive )
776800 normalized = _sdk_json_value (
@@ -805,29 +829,52 @@ def __init__(self) -> None:
805829 assert _json_compatible ({"values" : [State .READY , date (2025 , 1 , 2 )]}) == {
806830 "values" : ["ready" , "2025-01-02" ]
807831 }
832+ nested_values = _json_compatible (
833+ {
834+ "nested" : {
835+ "set" : {b"bytes" },
836+ "frozenset" : frozenset ({Path ("nested/path" )}),
837+ "invalid_bytes" : {b"\xff " },
838+ },
839+ "model" : NestedValuesModel (
840+ payload = {"set" : {b"model bytes" }, "path" : Path ("model/path" )}
841+ ),
842+ }
843+ )
844+ assert nested_values == {
845+ "nested" : {
846+ "set" : ["bytes" ],
847+ "frozenset" : ["nested/path" ],
848+ "invalid_bytes" : ["<not serializable bytes>" ],
849+ },
850+ "model" : {"payload" : {"set" : ["model bytes" ], "path" : "model/path" }},
851+ }
852+ assert json .loads (json .dumps (nested_values )) == nested_values
808853
809854
810855def test_normalizer_preserves_message_content_and_reports_invalid_trace_inputs () -> None :
811856 from agent_control_evaluator_galileo .records .normalization import (
812857 _flatten_message_sequence ,
858+ _message_content ,
813859 _message_sequence ,
814860 _normalize_content_items ,
815861 )
816862
817863 messages = [
818864 {"role" : "user" , "content" : "" },
819865 {"role" : "assistant" , "content" : ["plain" , {"unsupported" : True }]},
820- {"content" : 7 },
821- {},
822866 ]
823867 flattened = _flatten_message_sequence (messages )
824868
825869 assert [part ["text" ] for part in flattened ] == [
826870 "plain" ,
827871 '{"unsupported": true}' ,
828- "7" ,
829872 ]
830873 assert _message_sequence ([{"role" : "user" , "content" : "question" }])
874+ assert not _message_sequence ([{"content" : "question" }])
875+ retriever_document = {"content" : "retrieved text" , "metadata" : {"source" : "kb" }}
876+ assert not _message_sequence ([retriever_document ])
877+ assert _message_content (retriever_document ) is None
831878 assert not _message_sequence ([{"other" : "field" }])
832879 assert _normalize_content_items ("not a sequence" ) is None
833880 assert _normalize_content_items ([{"invalid" : True }]) is None
0 commit comments