Replies: 3 comments
|
The handbook has two relevant operator paths for this failure: streamed tool identity corruption (trace empty callId/name before policy) and runaway-loop containment (cancel first, preserve durable events, and do not retry malformed calls).\n\n- https://github.com/sandbaseai/deepseek-harness-handbook/blob/main/docs/en/troubleshooting/streamed-tool-call-empty-identity.md\n- https://github.com/sandbaseai/deepseek-harness-handbook/blob/main/docs/en/troubleshooting/runaway-agent-loop.md |
|
这个死循环是已被系统测量过的家族,补跨适配器数据(含你要的会话不可读角度——同族 #2674/#4611): 我们把这一族畸形 delta(空串/null 的 id、name 续块,真身晚到,全程无 id 的并行交错,无 index 按 id 续块)逐一注入真 pi-ai openai-completions 累加器(client/SSE 解析/组装全部未修改,只有线上字节是手造的):全部正确组装。免疫的机理:块按 这条量出来的规则(index 优先 + 首个非空胜出)可以直接当 |
|
复测 + 最小补丁(对应验收条件 a) 复测(master / 该仓库 与 @weijiafu14 在 #2 量出的免疫规则对齐:本路径缺的正是「按 最小补丁(finalize 前对空 name 直接 for (const call of delta?.tool_calls ?? []) {
let block = toolBlocks.get(call.index)
if (!block) {
block = open('tool-call')
toolBlocks.set(call.index, block)
yield { type: 'block-start', index: block.index, blockType: 'tool-call' }
}
- if (call.id !== undefined) block.callId = call.id
- if (call.function?.name !== undefined) block.name = call.function.name
+ // immunity: never write/overwrite with an empty id or name (first non-empty wins; later "" can't clobber)
+ if (call.id !== undefined && call.id.length > 0) block.callId = call.id
+ if (call.function?.name !== undefined && call.function.name.length > 0) block.name = call.function.name if (payload === DONE) {
+ // reject any tool-call that finished without a name BEFORE emitting block-ends,
+ // so no empty-name tool/call event is ever persisted (kills the unknown-tool "" death loop)
+ for (const block of order) {
+ if (block.kind === 'tool-call' && (block.name === undefined || block.name.length === 0)) {
+ throw new LlmError(
+ `tool-call at index ${block.index} finished without a tool name`
+ + ` (callId=${block.callId ?? '<empty>'}); aborting as MALFORMED_RESPONSE`,
+ 'MALFORMED_RESPONSE',
+ )
+ }
+ }
for (const block of order) {
yield { type: 'block-end', index: block.index, block: closeBlock(block) }
}可选第二层(验收条件 b,defense-in-depth): 临时止血(落地前):自定义 OpenAI 兼容网关改走 |
Uh oh!
There was an error while loading. Please reload this page.
dsh 工具调用落库时 name/callId 为空,执行端报 unknown tool "" 并无限重试。
复现、预期与验收
deepseek-official,baseURL 指向 OpenAI 兼容网关(如https://ai-hub.ebanma.com/v1),模型deepseek-v4-flashassistant/chunk的tool-call-delta带id/name(如run_code),但落库的tool/call事件callId与name均为空字符串Error: unknown tool ""(ToolNotFoundError/UNKNOWN_TOOL),模型拿不到有效结果继续重试 → 单回合出现 161 次空名调用死循环tool/call事件callId=""name="",全部被UNKNOWN_TOOL拒绝name正常路由执行@deepseek-ai/cordis@4.0.1、@deepseek-ai/dsh-llm-deepseek@0.1.1-rc.2(npmnext)、loongsuite-pilot agentshell 1.1.27translate.ts对空 name 的 tool-call block 改为拒绝(MALFORMED_RESPONSE)或发出明确错误,并把现有 unknown-tool snapshot gate 扩展覆盖空 name 变体补充技术定位:
packages/llm/llm-deepseek/src/translate.ts只有!== undefined才赋值 name,空字符串会被原样接受,收尾name: block.name ?? '':packages/core/agent-loop/src/tool-calls.ts的appendToolCall原样持久化block.name:已确认该逻辑在发布版
@deepseek-ai/dsh-llm-deepseek@0.1.1-rc.2与当前 master(dsh-0.1.2-alpha.1,2026-08-28)中相同,尚未修复。All reactions