fix: split concatenated JSON tool args in _parse_openai_sse to prevent _raw death spiral
This commit is contained in:
32
llmcore.py
32
llmcore.py
@@ -157,6 +157,22 @@ def _parse_claude_sse(resp_lines):
|
|||||||
content_blocks.append({"type": "text", "text": warn}); yield warn
|
content_blocks.append({"type": "text", "text": warn}); yield warn
|
||||||
return content_blocks
|
return content_blocks
|
||||||
|
|
||||||
|
|
||||||
|
def _try_parse_tool_args(raw):
|
||||||
|
"""Parse tool args string; split concatenated JSON objects like {..}{..} if needed.
|
||||||
|
Returns list of parsed dicts."""
|
||||||
|
if not raw: return [{}]
|
||||||
|
try: return [json.loads(raw)]
|
||||||
|
except: pass
|
||||||
|
parts = re.split(r'(?<=\})(?=\{)', raw)
|
||||||
|
if len(parts) > 1:
|
||||||
|
parsed = []
|
||||||
|
for p in parts:
|
||||||
|
try: parsed.append(json.loads(p))
|
||||||
|
except: return [{"_raw": raw}]
|
||||||
|
return parsed
|
||||||
|
return [{"_raw": raw}]
|
||||||
|
|
||||||
def _parse_openai_sse(resp_lines, api_mode="chat_completions"):
|
def _parse_openai_sse(resp_lines, api_mode="chat_completions"):
|
||||||
"""Parse OpenAI SSE stream (chat_completions or responses API).
|
"""Parse OpenAI SSE stream (chat_completions or responses API).
|
||||||
Yields text chunks, returns list[content_block].
|
Yields text chunks, returns list[content_block].
|
||||||
@@ -205,9 +221,11 @@ def _parse_openai_sse(resp_lines, api_mode="chat_completions"):
|
|||||||
if content_text: blocks.append({"type": "text", "text": content_text})
|
if content_text: blocks.append({"type": "text", "text": content_text})
|
||||||
for idx in sorted(fc_buf):
|
for idx in sorted(fc_buf):
|
||||||
fc = fc_buf[idx]
|
fc = fc_buf[idx]
|
||||||
try: inp = json.loads(fc["args"]) if fc["args"] else {}
|
inps = _try_parse_tool_args(fc["args"])
|
||||||
except: inp = {"_raw": fc["args"]}
|
for i, inp in enumerate(inps):
|
||||||
blocks.append({"type": "tool_use", "id": fc["id"] or '', "name": fc["name"], "input": inp})
|
bid = fc["id"] or ''
|
||||||
|
if len(inps) > 1: bid = f"{bid}_{i}" if bid else f"split_{i}"
|
||||||
|
blocks.append({"type": "tool_use", "id": bid, "name": fc["name"], "input": inp})
|
||||||
return blocks
|
return blocks
|
||||||
else:
|
else:
|
||||||
tc_buf = {} # index -> {id, name, args}
|
tc_buf = {} # index -> {id, name, args}
|
||||||
@@ -234,9 +252,11 @@ def _parse_openai_sse(resp_lines, api_mode="chat_completions"):
|
|||||||
if content_text: blocks.append({"type": "text", "text": content_text})
|
if content_text: blocks.append({"type": "text", "text": content_text})
|
||||||
for idx in sorted(tc_buf):
|
for idx in sorted(tc_buf):
|
||||||
tc = tc_buf[idx]
|
tc = tc_buf[idx]
|
||||||
try: inp = json.loads(tc["args"]) if tc["args"] else {}
|
inps = _try_parse_tool_args(tc["args"])
|
||||||
except: inp = {"_raw": tc["args"]}
|
for i, inp in enumerate(inps):
|
||||||
blocks.append({"type": "tool_use", "id": tc["id"] or '', "name": tc["name"], "input": inp})
|
bid = tc["id"] or ''
|
||||||
|
if len(inps) > 1: bid = f"{bid}_{i}" if bid else f"split_{i}"
|
||||||
|
blocks.append({"type": "tool_use", "id": bid, "name": tc["name"], "input": inp})
|
||||||
return blocks
|
return blocks
|
||||||
|
|
||||||
def _record_usage(usage, api_mode):
|
def _record_usage(usage, api_mode):
|
||||||
|
|||||||
Reference in New Issue
Block a user