fix(proxy): add safe_json_loads to prevent 500 errors on streaming tool call decoding

This commit is contained in:
wmantly
2026-09-13 02:18:52 +00:00
parent cd989b3e70
commit 05720793ad
+35 -19
View File
@@ -277,6 +277,30 @@ def _ollama_tools_to_openai(tools):
})
return out
def safe_json_loads(val, default=None):
"""Safely decode JSON string, handling extra data or syntax errors gracefully."""
if default is None:
default = {}
if not val:
return default
if isinstance(val, dict):
return val
s = str(val).strip()
if not s:
return default
try:
return json.loads(s)
except Exception:
pass
try:
decoder = json.JSONDecoder()
obj, _ = decoder.raw_decode(s)
if isinstance(obj, dict):
return obj
except Exception:
pass
return {"raw": s}
def _openai_tool_calls_to_ollama(tool_calls):
"""OpenAI tool_calls -> ollama message.tool_calls."""
if not tool_calls:
@@ -284,10 +308,7 @@ def _openai_tool_calls_to_ollama(tool_calls):
out = []
for tc in tool_calls:
fn = tc.get("function", {})
try:
args = json.loads(fn.get("arguments", "{}"))
except json.JSONDecodeError:
args = {}
args = safe_json_loads(fn.get("arguments", "{}"))
out.append({
"function": {
"name": fn.get("name", ""),
@@ -674,15 +695,16 @@ def ollama_chat(payload):
om = {"role": "assistant", "content": ""}
if tc_parts:
om["tool_calls"] = [
{
tc_list = []
for e in tc_parts.values():
args = safe_json_loads(e.get("args"))
tc_list.append({
"function": {
"name": e["name"],
"arguments": json.loads(e["args"]) if e["args"] else {},
"name": e.get("name", ""),
"arguments": args,
}
}
for e in tc_parts.values()
]
})
om["tool_calls"] = tc_list
yield json.dumps({
"model": MODEL_NAME,
@@ -953,10 +975,7 @@ def _openai_tool_calls_to_anthropic(tool_calls):
blocks = []
for tc in tool_calls:
fn = tc.get("function", {})
try:
args = json.loads(fn.get("arguments", "{}"))
except json.JSONDecodeError:
args = {}
args = safe_json_loads(fn.get("arguments", "{}"))
blocks.append({
"type": "tool_use",
"id": tc.get("id") or "toolu_" + uuid.uuid4().hex[:24],
@@ -1115,10 +1134,7 @@ def anthropic_messages(payload):
if finish:
# close any open tool_use block(s) with accumulated input
for tid, entry in tc_parts.items():
try:
args = json.loads(entry["args"]) if entry["args"] else {}
except json.JSONDecodeError:
args = {}
args = safe_json_loads(entry.get("args"))
yield sse("content_block_delta", {
"type": "content_block_delta",
"index": entry["block_index"],