@@ -160,6 +160,55 @@ def output_id(prefix: str) -> str:
160160 return f"{ prefix } _{ uuid4 ().hex [:24 ]} "
161161
162162
163+ def _split_tool_calls (
164+ tool_calls : list [dict ],
165+ ) -> list [dict ]:
166+ """Expand tool calls whose arguments contain multiple back-to-back JSON objects.
167+
168+ Some models (e.g. GPT-5.4) send multiple complete JSON argument objects
169+ under the same tool call index, producing concatenated invalid JSON like:
170+ '{"query":"A","count":5}{"query":"B","count":5}'
171+
172+ Each such tool call is split into separate entries so each gets executed
173+ independently. Single-object arguments pass through unchanged.
174+ """
175+
176+ def split_json_objects (raw : str ) -> list [str ]:
177+ decoder = json .JSONDecoder ()
178+ results = []
179+ position = 0
180+
181+ while position < len (raw ):
182+ while position < len (raw ) and raw [position ].isspace ():
183+ position += 1
184+ if position >= len (raw ):
185+ break
186+ try :
187+ _ , end = decoder .raw_decode (raw , position )
188+ results .append (raw [position :end ].strip ())
189+ position = end
190+ except json .JSONDecodeError :
191+ return [raw ]
192+
193+ return results or [raw ]
194+
195+ expanded = []
196+ for tool_call in tool_calls :
197+ arguments = tool_call .get ("function" , {}).get ("arguments" , "" )
198+ split_arguments = split_json_objects (arguments )
199+
200+ if len (split_arguments ) <= 1 :
201+ expanded .append (tool_call )
202+ else :
203+ for argument in split_arguments :
204+ cloned = copy .deepcopy (tool_call )
205+ cloned ["id" ] = f"call_{ uuid4 ().hex [:24 ]} "
206+ cloned ["function" ]["arguments" ] = argument
207+ expanded .append (cloned )
208+
209+ return expanded
210+
211+
163212def get_citation_source_from_tool_result (
164213 tool_name : str , tool_params : dict , tool_result : str , tool_id : str = ""
165214) -> list [dict ]:
@@ -4104,7 +4153,11 @@ async def flush_pending_delta_data(threshold: int = 0):
41044153 reasoning_item ["status" ] = "completed"
41054154
41064155 if response_tool_calls :
4107- tool_calls .append (response_tool_calls )
4156+ tool_calls .append (
4157+ _split_tool_calls (
4158+ response_tool_calls
4159+ )
4160+ )
41084161
41094162 if response .background :
41104163 await response .background ()
0 commit comments