diff --git a/src/agent.py b/src/agent.py index b146afae1..c8daf41da 100644 --- a/src/agent.py +++ b/src/agent.py @@ -175,8 +175,16 @@ async def async_response_stream( try: # Try to serialize the chunk object if hasattr(chunk, "model_dump"): - # Pydantic model - chunk_data = chunk.model_dump() + # Pydantic model. Exclude "delta" from serialization since Langflow's + # /response SSE wraps text deltas as {"content": "..."} for every provider but + # openai SDK's event models declare/expect delta to be str so model_dump() will + # emit noisy logs (PydanticSerializationUnexpectedValue) for every chunk. Basically + # just reattach the raw (unvalidated) value afterwards to preserve same shape. + # TODO: replace this with just chunk.model_dump() if langflow's /response SSE matches openai's + # SDK's delta: str schema + chunk_data = chunk.model_dump(exclude={"delta"}) + if hasattr(chunk, "delta"): + chunk_data["delta"] = chunk.delta elif hasattr(chunk, "__dict__"): chunk_data = chunk.__dict__ else: diff --git a/tests/unit/test_agent_stream_delta.py b/tests/unit/test_agent_stream_delta.py new file mode 100644 index 000000000..c6f59681f --- /dev/null +++ b/tests/unit/test_agent_stream_delta.py @@ -0,0 +1,38 @@ +import warnings + +from pydantic import BaseModel + + +class FakeTextDeltaEvent(BaseModel): + """Mirrors openai's ResponseTextDeltaEvent: `delta` is declared as `str`.""" + + delta: str + type: str + + +chunk = FakeTextDeltaEvent.model_construct( + delta={"content": "Hello"}, type="response.output_text.delta" +) + + +def test_model_dump_without_exclusion_raises_serialization_warning() -> None: + with warnings.catch_warnings(record=True) as recorded: + warnings.simplefilter("always") + chunk.model_dump() + + assert any( + issubclass(w.category, UserWarning) + and "PydanticSerializationUnexpectedValue" in str(w.message) + for w in recorded + ), "expected a PydanticSerializationUnexpectedValue warning for the delta type mismatch" + + +def test_model_dump_excluding_delta_avoids_warning_and_preserves_dict_shape() -> None: + with warnings.catch_warnings(): + warnings.simplefilter("error") + + chunk_data = chunk.model_dump(exclude={"delta"}) + chunk_data["delta"] = chunk.delta + + assert chunk_data["delta"] == {"content": "Hello"} + assert chunk_data["type"] == "response.output_text.delta"