mirror of
https://github.com/zhayujie/chatgpt-on-wechat.git
synced 2026-07-21 14:17:11 +08:00
fix: tool call match
This commit is contained in:
@@ -196,6 +196,11 @@ class AgentStreamExecutor:
|
|||||||
# are never stripped mid-execution (which would cause LLM loops).
|
# are never stripped mid-execution (which would cause LLM loops).
|
||||||
self._trim_messages()
|
self._trim_messages()
|
||||||
|
|
||||||
|
# Validate after trimming: trimming may leave orphaned tool_use at the
|
||||||
|
# boundary (e.g. the last kept turn ends with an assistant tool_use whose
|
||||||
|
# tool_result was in a discarded turn).
|
||||||
|
self._validate_and_fix_messages()
|
||||||
|
|
||||||
self._emit_event("agent_start")
|
self._emit_event("agent_start")
|
||||||
|
|
||||||
final_response = ""
|
final_response = ""
|
||||||
|
|||||||
@@ -70,68 +70,72 @@ def sanitize_claude_messages(messages: List[Dict]) -> int:
|
|||||||
else:
|
else:
|
||||||
break
|
break
|
||||||
|
|
||||||
# 3. Full scan: ensure every tool_result references a known tool_use id
|
# 3. Iteratively remove unmatched tool_use / tool_result until stable.
|
||||||
known_ids: Set[str] = set()
|
# Removing one broken message can orphan others (e.g. an assistant msg
|
||||||
|
# with both matched and unmatched tool_use — deleting it orphans the
|
||||||
|
# previously-matched tool_result). Loop until clean.
|
||||||
|
for _ in range(5):
|
||||||
|
use_ids: Set[str] = set()
|
||||||
|
result_ids: Set[str] = set()
|
||||||
|
for msg in messages:
|
||||||
|
for block in (msg.get("content") or []):
|
||||||
|
if not isinstance(block, dict):
|
||||||
|
continue
|
||||||
|
if block.get("type") == "tool_use" and block.get("id"):
|
||||||
|
use_ids.add(block["id"])
|
||||||
|
elif block.get("type") == "tool_result" and block.get("tool_use_id"):
|
||||||
|
result_ids.add(block["tool_use_id"])
|
||||||
|
|
||||||
|
bad_use = use_ids - result_ids
|
||||||
|
bad_result = result_ids - use_ids
|
||||||
|
if not bad_use and not bad_result:
|
||||||
|
break
|
||||||
|
|
||||||
|
pass_removed = 0
|
||||||
i = 0
|
i = 0
|
||||||
while i < len(messages):
|
while i < len(messages):
|
||||||
msg = messages[i]
|
msg = messages[i]
|
||||||
role = msg.get("role")
|
role = msg.get("role")
|
||||||
content = msg.get("content", [])
|
content = msg.get("content", [])
|
||||||
|
if not isinstance(content, list):
|
||||||
if role == "assistant" and isinstance(content, list):
|
|
||||||
for block in content:
|
|
||||||
if isinstance(block, dict) and block.get("type") == "tool_use":
|
|
||||||
tid = block.get("id", "")
|
|
||||||
if tid:
|
|
||||||
known_ids.add(tid)
|
|
||||||
|
|
||||||
elif role == "user" and isinstance(content, list):
|
|
||||||
if not _has_block_type(content, "tool_result"):
|
|
||||||
i += 1
|
i += 1
|
||||||
continue
|
continue
|
||||||
|
|
||||||
orphaned = [
|
if role == "assistant" and bad_use and any(
|
||||||
b.get("tool_use_id", "")
|
isinstance(b, dict) and b.get("type") == "tool_use"
|
||||||
for b in content
|
and b.get("id") in bad_use for b in content
|
||||||
if isinstance(b, dict)
|
):
|
||||||
and b.get("type") == "tool_result"
|
logger.warning(f"⚠️ Removing assistant msg with unmatched tool_use")
|
||||||
and b.get("tool_use_id", "")
|
|
||||||
and b.get("tool_use_id", "") not in known_ids
|
|
||||||
]
|
|
||||||
if orphaned:
|
|
||||||
orphaned_set = set(orphaned)
|
|
||||||
if not _has_block_type(content, "text"):
|
|
||||||
logger.warning(
|
|
||||||
f"⚠️ Removing orphaned tool_result message (tool_ids: {orphaned})"
|
|
||||||
)
|
|
||||||
messages.pop(i)
|
messages.pop(i)
|
||||||
removed += 1
|
pass_removed += 1
|
||||||
# Also remove a preceding broken assistant tool_use message
|
continue
|
||||||
if i > 0 and messages[i - 1].get("role") == "assistant":
|
|
||||||
prev = messages[i - 1].get("content", [])
|
if role == "user" and bad_result and _has_block_type(content, "tool_result"):
|
||||||
if isinstance(prev, list) and _has_block_type(prev, "tool_use"):
|
has_bad = any(
|
||||||
messages.pop(i - 1)
|
isinstance(b, dict) and b.get("type") == "tool_result"
|
||||||
removed += 1
|
and b.get("tool_use_id") in bad_result for b in content
|
||||||
i -= 1
|
)
|
||||||
|
if has_bad:
|
||||||
|
if not _has_block_type(content, "text"):
|
||||||
|
logger.warning(f"⚠️ Removing user msg with unmatched tool_result")
|
||||||
|
messages.pop(i)
|
||||||
|
pass_removed += 1
|
||||||
continue
|
continue
|
||||||
else:
|
else:
|
||||||
new_content = [
|
before = len(content)
|
||||||
|
msg["content"] = [
|
||||||
b for b in content
|
b for b in content
|
||||||
if not (
|
if not (isinstance(b, dict) and b.get("type") == "tool_result"
|
||||||
isinstance(b, dict)
|
and b.get("tool_use_id") in bad_result)
|
||||||
and b.get("type") == "tool_result"
|
|
||||||
and b.get("tool_use_id", "") in orphaned_set
|
|
||||||
)
|
|
||||||
]
|
]
|
||||||
delta = len(content) - len(new_content)
|
pass_removed += before - len(msg["content"])
|
||||||
if delta:
|
|
||||||
logger.warning(
|
|
||||||
f"⚠️ Stripped {delta} orphaned tool_result block(s) from mixed message"
|
|
||||||
)
|
|
||||||
msg["content"] = new_content
|
|
||||||
removed += delta
|
|
||||||
i += 1
|
i += 1
|
||||||
|
|
||||||
|
removed += pass_removed
|
||||||
|
if pass_removed == 0:
|
||||||
|
break
|
||||||
|
|
||||||
if removed:
|
if removed:
|
||||||
logger.info(f"🔧 Message validation: removed {removed} broken message(s)")
|
logger.info(f"🔧 Message validation: removed {removed} broken message(s)")
|
||||||
return removed
|
return removed
|
||||||
|
|||||||
Reference in New Issue
Block a user