From 68a91a254395e85496b82659881ece09a5d24f00 Mon Sep 17 00:00:00 2001 From: Rolando Bosch Date: Tue, 7 Jul 2026 16:09:21 -0400 Subject: [PATCH 1/2] fix(openai): handle None arguments in legacy function_call streaming merge --- langfuse/openai.py | 4 ++- tests/unit/test_openai.py | 69 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 72 insertions(+), 1 deletion(-) diff --git a/langfuse/openai.py b/langfuse/openai.py index ea9dcd68c..6dcdb3e67 100644 --- a/langfuse/openai.py +++ b/langfuse/openai.py @@ -811,7 +811,9 @@ def _extract_streamed_openai_response(resource: Any, chunks: Any) -> Any: curr["name"] = curr["name"] or getattr( tool_call_chunk, "name", None ) - curr["arguments"] += getattr(tool_call_chunk, "arguments", "") + curr["arguments"] = (curr.get("arguments") or "") + ( + getattr(tool_call_chunk, "arguments", None) or "" + ) if ( delta.get("tool_calls", None) is not None diff --git a/tests/unit/test_openai.py b/tests/unit/test_openai.py index 681be4fbf..af9f43e9b 100644 --- a/tests/unit/test_openai.py +++ b/tests/unit/test_openai.py @@ -401,6 +401,75 @@ def test_streaming_chat_completion_preserves_tool_calls_after_content(): assert metadata == {"finish_reason": "tool_calls"} +def _make_chat_stream_chunks_with_legacy_function_call_none_arguments(): + """Mirrors a real OpenAI-compatible-proxy payload shape (see PR #1339, + which fixed the identical crash for the newer `tool_calls` array but + left this deprecated `function_call` merge branch unpatched): the first + streamed delta carries a `function_call` with `arguments=None`, and a + later delta appends string arguments to it. + """ + return [ + SimpleNamespace( + model="gpt-4o-mini", + choices=[ + SimpleNamespace( + delta=SimpleNamespace( + role="assistant", + content=None, + function_call=SimpleNamespace( + name="get_weather", arguments=None + ), + tool_calls=None, + ), + finish_reason=None, + ) + ], + usage=None, + ), + SimpleNamespace( + model="gpt-4o-mini", + choices=[ + SimpleNamespace( + delta=SimpleNamespace( + role=None, + content=None, + function_call=SimpleNamespace( + name=None, arguments='{"city": "Berlin"}' + ), + tool_calls=None, + ), + finish_reason="function_call", + ) + ], + usage=None, + ), + ] + + +def test_streaming_chat_completion_handles_legacy_function_call_none_arguments(): + """Regression test: the deprecated `function_call` streaming-merge branch + must not crash when the first delta's `arguments` is None (a real + OpenAI-compatible-proxy behaviour, analogous to the `tool_calls` bug + fixed in PR #1339 but never patched for this legacy branch). + """ + model, completion, usage, metadata = ( + lf_openai_module._extract_streamed_openai_response( + SimpleNamespace(type="chat"), + _make_chat_stream_chunks_with_legacy_function_call_none_arguments(), + ) + ) + + assert model == "gpt-4o-mini" + assert completion == { + "role": "assistant", + "function_call": { + "name": "get_weather", + "arguments": '{"city": "Berlin"}', + }, + } + assert metadata == {"finish_reason": "function_call"} + + def test_response_api_output_serializes_openai_parsed_response_objects(): class ParsedOutput(BaseModel): name: str From 94b33dda2753918cf03b7a1ae5a785d86221cc08 Mon Sep 17 00:00:00 2001 From: Rolando Bosch Date: Fri, 10 Jul 2026 21:22:13 -0400 Subject: [PATCH 2/2] test(openai): rebase legacy function_call regression test onto #1751 arity _extract_streamed_openai_response now returns a 5-tuple (adds service_tier, upstream #1751) instead of 4. Update this test's unpack to match; the bug assertions (model/completion/metadata content) are unchanged. --- tests/unit/test_openai.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/test_openai.py b/tests/unit/test_openai.py index af9f43e9b..dff020424 100644 --- a/tests/unit/test_openai.py +++ b/tests/unit/test_openai.py @@ -452,7 +452,7 @@ def test_streaming_chat_completion_handles_legacy_function_call_none_arguments() OpenAI-compatible-proxy behaviour, analogous to the `tool_calls` bug fixed in PR #1339 but never patched for this legacy branch). """ - model, completion, usage, metadata = ( + model, completion, usage, metadata, _service_tier = ( lf_openai_module._extract_streamed_openai_response( SimpleNamespace(type="chat"), _make_chat_stream_chunks_with_legacy_function_call_none_arguments(),