Skip to content

Commit 459a60a

Browse files
committed
refac
1 parent 9a269ec commit 459a60a

1 file changed

Lines changed: 54 additions & 1 deletion

File tree

backend/open_webui/utils/middleware.py

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -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+
163212
def 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

Comments
 (0)