fix: tool_calls 流中断自愈——400 配对错误时移除不完整段重试一次;加消息结构诊断日志
This commit is contained in:
@@ -132,6 +132,22 @@ class Agent:
|
|||||||
pass
|
pass
|
||||||
return text_result, image_b64
|
return text_result, image_b64
|
||||||
|
|
||||||
|
def _repair_tool_messages(self):
|
||||||
|
"""修复 tool_calls 配对不完整:从尾部移除「assistant 带 tool_calls 但其后
|
||||||
|
tool 回应不足」的消息段(流中断可能丢失分片,400 重试前自愈)。"""
|
||||||
|
for i in range(len(self.messages) - 1, -1, -1):
|
||||||
|
m = self.messages[i]
|
||||||
|
if m.get("role") == "assistant" and m.get("tool_calls"):
|
||||||
|
# 统计其后 tool 消息数是否匹配
|
||||||
|
need = len(m["tool_calls"])
|
||||||
|
have = sum(1 for x in self.messages[i + 1:]
|
||||||
|
if x.get("role") == "tool")
|
||||||
|
if have < need:
|
||||||
|
_log.warning("修复不完整 tool_calls 段(need=%d have=%d),回退 %d 条消息",
|
||||||
|
need, have, len(self.messages) - i)
|
||||||
|
self.messages = self.messages[:i]
|
||||||
|
return
|
||||||
|
|
||||||
# ---------- 主循环(流式) ----------
|
# ---------- 主循环(流式) ----------
|
||||||
async def run_stream(self, prompt: str, serial: str = "",
|
async def run_stream(self, prompt: str, serial: str = "",
|
||||||
history=None, on_delta=None, on_tool=None,
|
history=None, on_delta=None, on_tool=None,
|
||||||
@@ -166,6 +182,9 @@ class Agent:
|
|||||||
content_parts = []
|
content_parts = []
|
||||||
tool_acc = {} # index -> {id, name, args}
|
tool_acc = {} # index -> {id, name, args}
|
||||||
has_tool = False
|
has_tool = False
|
||||||
|
retried = False
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
async for chunk in self._chat_stream():
|
async for chunk in self._chat_stream():
|
||||||
choice = (chunk.get("choices") or [{}])[0]
|
choice = (chunk.get("choices") or [{}])[0]
|
||||||
delta = choice.get("delta") or {}
|
delta = choice.get("delta") or {}
|
||||||
@@ -189,6 +208,15 @@ class Agent:
|
|||||||
acc["name"] += fn["name"]
|
acc["name"] += fn["name"]
|
||||||
if fn.get("arguments"):
|
if fn.get("arguments"):
|
||||||
acc["args"] += fn["arguments"]
|
acc["args"] += fn["arguments"]
|
||||||
|
break
|
||||||
|
except RuntimeError as e:
|
||||||
|
# 流中断导致 tool_calls 分片丢失:修复后重试一次
|
||||||
|
if ("tool_calls" in str(e) or "must be followed" in str(e)) and not retried:
|
||||||
|
_log.warning("tool_calls 消息不完整,自愈重试")
|
||||||
|
self._repair_tool_messages()
|
||||||
|
retried = True
|
||||||
|
continue
|
||||||
|
raise
|
||||||
|
|
||||||
full_content = "".join(content_parts)
|
full_content = "".join(content_parts)
|
||||||
|
|
||||||
|
|||||||
@@ -367,6 +367,16 @@ def _agent_thread(run_id, prompt, serial, cfg):
|
|||||||
_log.warning(f"经验保存异常: {e}")
|
_log.warning(f"经验保存异常: {e}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
_log.warning(f"Agent 运行异常: {e}")
|
_log.warning(f"Agent 运行异常: {e}")
|
||||||
|
# 诊断:打印消息结构(tool_calls 与 tool 消息配对检查)
|
||||||
|
try:
|
||||||
|
roles = [m.get("role", "?") for m in agent.messages]
|
||||||
|
tcs = sum(1 for m in agent.messages
|
||||||
|
if m.get("tool_calls") and isinstance(m.get("tool_calls"), list))
|
||||||
|
tools_msg = sum(1 for m in agent.messages if m.get("role") == "tool")
|
||||||
|
_log.warning(f"诊断 messages: {len(agent.messages)} 条 roles={roles[-8:]} "
|
||||||
|
f"tool_calls消息={tcs} tool回应={tools_msg}")
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
with _lock:
|
with _lock:
|
||||||
_run["state"] = "error"
|
_run["state"] = "error"
|
||||||
_run["error"] = f"{type(e).__name__}: {str(e)[:200]}"
|
_run["error"] = f"{type(e).__name__}: {str(e)[:200]}"
|
||||||
|
|||||||
Reference in New Issue
Block a user