You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
报错信息:
400 {"error":{"message":"messages.135.content.3: tool_use ids must be unique","type":"invalid_request_error","param":null,"code":"invalid_request_error"}}
Steps to reproduce / 复现步骤
配置deepseek官方的key
Expected behavior / 预期行为
能够正常调用
Actual behavior / 实际行为
一直返回:
400 {"error":{"message":"messages.135.content.3: tool_use ids must be unique","type":"invalid_request_error","param":null,"code":"invalid_request_error"}}
tool_use ids must be unique 不太可能来自存储层(磁盘上没有重复),大概率产生在发送前的消息组装/重放层:把同一调用的多个快照、或压缩边界两侧的重叠段一起放进了 messages。结合报错位置 messages.135.content.3(很靠后的长序列),三个最值得查的候选:
快照归并:重放时是否严格按消息 id 取“最后一条快照”?若按 block 收集而非按消息归并,同一 callId 会进两次。
根因:transcript 是 append-only 流,且容忍重试追加,因此同一个 tool call 可能以新的 id 再次进入下一次请求;宿主原有的 keep-last 去重按行 id 折叠,覆盖不到"新 id 的重复形态"。Anthropic 系端点(含 DeepSeek 官方)于是以 tool_use ids must be unique 拒绝整个回合,且该会话此后再也无法继续。
修复(PR #780 / commit 184e7c489):在最后一层视图、convertToLlm 之前对每个出站请求执行 dropDuplicateToolCalls —— 首次出现者保留,同一 id 的后续 call 与 result 一并丢弃,保证"一个 call 对应一个 result",同时把这次重建日志连同被丢弃的 id 打出来(最多 8 个),下次再出现时能直接指向写入方,而不是只看到 provider 的报错文案。子代理侧的重复 id 由 PR #782(3de2a6561)覆盖。代码注释中直接引用了本 issue 编号。
包含版本:v0.15.2 / v0.15.3(已用 git tag --contains 184e7c489 确认)。
Activity
vastsa commentedon Sep 20, 2026
可以让 ai 诊断一下日志,看看是怎么回事
muzimu217 commentedon Sep 20, 2026
按 @vastsa 的建议做了一轮诊断。没有 DeepSeek 官方 key 做端到端复现,但在本机 140 个真实会话 transcript(约 2.2 万条工具调用记录)上做了数据侧核查,可以把范围收窄很多:
数据侧核查(本机真实数据)
callId在磁盘上零重复:140 个会话文件里 5,786 个非空callId,同会话内无一重复。tool_call会以多条记录出现(pending 态无 id、complete 态带 id),本机 22,190 条tool_call块快照实际只对应 5,786 个真实调用,约 74% 是中间态快照。推论
tool_use ids must be unique不太可能来自存储层(磁盘上没有重复),大概率产生在发送前的消息组装/重放层:把同一调用的多个快照、或压缩边界两侧的重叠段一起放进了messages。结合报错位置messages.135.content.3(很靠后的长序列),三个最值得查的候选:callId会进两次。tool_use(保留摘要 + 原文未截断)。tool_use)重复追加。建议的定位手段(低成本)
在 agent-runtime 组装完请求、发送前加一个断言:扫一遍
messages里 assistant 的tool_use.id,发现重复即warn日志打出(会话 id + 重复 id + 前后各 2 条消息摘要)。这样下一个复现案例会直接给出重复产生的位置,而不是只拿到服务商的 400。也请报告者补充两点,能进一步收窄:出错会话是否很长/经历过上下文压缩;
logs/app/session.log里 400 前后的记录(有没有紧邻的 retry 或 compaction 事件)。vastsa commentedon Sep 21, 2026
复核结论:报告的 400 错误真实,但根因仍未闭环。当前 main 的持久化会话数据中 callId 可保持唯一,已有修复覆盖跨会话碰撞;现有证据更指向发送前的快照归并、compaction 或 retry 重放。请补充复现会话是否经历压缩/重试,以及 400 前后的 session/app 日志,issue 保持开放。
muzimu217 commentedon Sep 21, 2026
收到复核结论,方向一致——快照归并/压缩边界/重试重放正是此前数据侧排查收敛的三个候选(磁盘 callId 零重复、74% 为中间态快照,问题必然在组装层)。坐等报告者补充压缩/重试线索。另外之前提过的定位手段依然有效:在 agent-runtime 组装完 messages、发送前扫一遍 tool_use id,发现重复即打 warn(会话 id + 重复 id + 邻近消息摘要)——等方向确认后愿意直接做成 PR。
fix(agent-runtime): keep tool-call ids unique in every request
fix(agent-runtime): keep tool-call ids unique in every request
fix(agent-runtime): keep tool-call ids unique in every request
vastsa commentedon Sep 22, 2026
已在 main 修复并随 v0.15.2 发布,本 issue 可以关闭。
根因:transcript 是 append-only 流,且容忍重试追加,因此同一个 tool call 可能以新的 id 再次进入下一次请求;宿主原有的 keep-last 去重按行 id 折叠,覆盖不到"新 id 的重复形态"。Anthropic 系端点(含 DeepSeek 官方)于是以
tool_use ids must be unique拒绝整个回合,且该会话此后再也无法继续。修复(PR #780 / commit
184e7c489):在最后一层视图、convertToLlm之前对每个出站请求执行dropDuplicateToolCalls—— 首次出现者保留,同一 id 的后续 call 与 result 一并丢弃,保证"一个 call 对应一个 result",同时把这次重建日志连同被丢弃的 id 打出来(最多 8 个),下次再出现时能直接指向写入方,而不是只看到 provider 的报错文案。子代理侧的重复 id 由 PR #782(3de2a6561)覆盖。代码注释中直接引用了本 issue 编号。包含版本:v0.15.2 / v0.15.3(已用
git tag --contains 184e7c489确认)。请用 ≥ v0.15.2 复跑一次原来的长会话场景。若仍出现同样的 400,日志里应当能找到那一行 duplicate tool-call 记录(含 id),贴回来即可重新打开 —— 那时问题会明确指向写入方,而不是 provider。