From 967762950c33f2dba509079152d3169fe55f0ef9 Mon Sep 17 00:00:00 2001 From: Yahya Date: Mon, 8 Jun 2026 09:58:27 +0200 Subject: [PATCH] fix(chatcompletions): repair truncated tool-call args before emission A streaming tool call cut off after the opening brace would leave state.function_calls[i].arguments = "{". CAI persisted that into conversation history verbatim, and on the next turn the upstream litellm proxy strict-parsed it as JSON and rejected the whole request with HTTP 400 ("unexpected end of data: line 1 column 2"), wedging the session. - openai_chatcompletions.py: at end-of-stream, validate every accumulated function_call.arguments string. If empty or not valid JSON, normalize to "{}" before emitting events. - message_builder.py: same guard when replaying assistant tool_calls out of memory, so a poisoned history loaded from disk also recovers. --- .../models/chatcompletions/message_builder.py | 8 ++++++++ src/cai/sdk/agents/models/openai_chatcompletions.py | 13 +++++++++++++ 2 files changed, 21 insertions(+) diff --git a/src/cai/sdk/agents/models/chatcompletions/message_builder.py b/src/cai/sdk/agents/models/chatcompletions/message_builder.py index e75f32ef..a1f8968f 100644 --- a/src/cai/sdk/agents/models/chatcompletions/message_builder.py +++ b/src/cai/sdk/agents/models/chatcompletions/message_builder.py @@ -424,6 +424,14 @@ class Converter: arguments = "{}" elif isinstance(arguments, dict): arguments = json.dumps(arguments) + else: + # Truncated/streamed-then-cut function call args (e.g. "{") + # are not valid JSON and the upstream proxy rejects the whole + # request with HTTP 400, wedging the conversation. + try: + json.loads(arguments) + except (TypeError, ValueError): + arguments = "{}" tool_calls_param.append( ChatCompletionMessageToolCallParam( id=tc.get("id", "")[:40], diff --git a/src/cai/sdk/agents/models/openai_chatcompletions.py b/src/cai/sdk/agents/models/openai_chatcompletions.py index 47d871b0..7156de4d 100644 --- a/src/cai/sdk/agents/models/openai_chatcompletions.py +++ b/src/cai/sdk/agents/models/openai_chatcompletions.py @@ -2676,6 +2676,19 @@ class OpenAIChatCompletionsModel(Model): type="response.content_part.done", ) + # Repair partially-streamed function calls before emission so a + # truncated args string like "{" cannot poison conversation history + # and trigger HTTP 400 on the next request. + for _fc in state.function_calls.values(): + _args = _fc.arguments or "" + if not _args.strip(): + _fc.arguments = "{}" + else: + try: + json.loads(_args) + except (TypeError, ValueError): + _fc.arguments = "{}" + # Actually send events for the function calls for function_call in state.function_calls.values(): # First, a ResponseOutputItemAdded for the function call