fix(proxy): add safe_json_loads to prevent 500 errors on streaming tool call decoding
This commit is contained in:
+35
-19
@@ -277,6 +277,30 @@ def _ollama_tools_to_openai(tools):
|
|||||||
})
|
})
|
||||||
return out
|
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):
|
def _openai_tool_calls_to_ollama(tool_calls):
|
||||||
"""OpenAI tool_calls -> ollama message.tool_calls."""
|
"""OpenAI tool_calls -> ollama message.tool_calls."""
|
||||||
if not tool_calls:
|
if not tool_calls:
|
||||||
@@ -284,10 +308,7 @@ def _openai_tool_calls_to_ollama(tool_calls):
|
|||||||
out = []
|
out = []
|
||||||
for tc in tool_calls:
|
for tc in tool_calls:
|
||||||
fn = tc.get("function", {})
|
fn = tc.get("function", {})
|
||||||
try:
|
args = safe_json_loads(fn.get("arguments", "{}"))
|
||||||
args = json.loads(fn.get("arguments", "{}"))
|
|
||||||
except json.JSONDecodeError:
|
|
||||||
args = {}
|
|
||||||
out.append({
|
out.append({
|
||||||
"function": {
|
"function": {
|
||||||
"name": fn.get("name", ""),
|
"name": fn.get("name", ""),
|
||||||
@@ -674,15 +695,16 @@ def ollama_chat(payload):
|
|||||||
|
|
||||||
om = {"role": "assistant", "content": ""}
|
om = {"role": "assistant", "content": ""}
|
||||||
if tc_parts:
|
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": {
|
"function": {
|
||||||
"name": e["name"],
|
"name": e.get("name", ""),
|
||||||
"arguments": json.loads(e["args"]) if e["args"] else {},
|
"arguments": args,
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
for e in tc_parts.values()
|
om["tool_calls"] = tc_list
|
||||||
]
|
|
||||||
|
|
||||||
yield json.dumps({
|
yield json.dumps({
|
||||||
"model": MODEL_NAME,
|
"model": MODEL_NAME,
|
||||||
@@ -953,10 +975,7 @@ def _openai_tool_calls_to_anthropic(tool_calls):
|
|||||||
blocks = []
|
blocks = []
|
||||||
for tc in tool_calls:
|
for tc in tool_calls:
|
||||||
fn = tc.get("function", {})
|
fn = tc.get("function", {})
|
||||||
try:
|
args = safe_json_loads(fn.get("arguments", "{}"))
|
||||||
args = json.loads(fn.get("arguments", "{}"))
|
|
||||||
except json.JSONDecodeError:
|
|
||||||
args = {}
|
|
||||||
blocks.append({
|
blocks.append({
|
||||||
"type": "tool_use",
|
"type": "tool_use",
|
||||||
"id": tc.get("id") or "toolu_" + uuid.uuid4().hex[:24],
|
"id": tc.get("id") or "toolu_" + uuid.uuid4().hex[:24],
|
||||||
@@ -1115,10 +1134,7 @@ def anthropic_messages(payload):
|
|||||||
if finish:
|
if finish:
|
||||||
# close any open tool_use block(s) with accumulated input
|
# close any open tool_use block(s) with accumulated input
|
||||||
for tid, entry in tc_parts.items():
|
for tid, entry in tc_parts.items():
|
||||||
try:
|
args = safe_json_loads(entry.get("args"))
|
||||||
args = json.loads(entry["args"]) if entry["args"] else {}
|
|
||||||
except json.JSONDecodeError:
|
|
||||||
args = {}
|
|
||||||
yield sse("content_block_delta", {
|
yield sse("content_block_delta", {
|
||||||
"type": "content_block_delta",
|
"type": "content_block_delta",
|
||||||
"index": entry["block_index"],
|
"index": entry["block_index"],
|
||||||
|
|||||||
Reference in New Issue
Block a user