Replies: 2 comments
|
Confirmed against b150a55 (the exact commit you quote) — your diagnosis is precise, and the proposed != null fix is correct. Two additions from a sweep of the rest of the tree: 1. The bug is real exactly as described packages/llm/llm-deepseek/src/translate.ts lines 159-160 use the guard 2. A second, worse instance of the same conflation lives in llm-pi-ai packages/llm/llm-pi-ai/src/stream.ts:
So the same root (explicit null treated as a present value) has two manifestations in the llm layer: silent overwrite in llm-deepseek (your report) and a hard crash in llm-pi-ai. Worth fixing both, and the llm-pi-ai side should normalize at the source (coalesce to empty string where the value is copied) rather than patching the consumer. 3. Same class as the family this repo already tracks The "explicit null is absence, not a value" conflation has now bitten in: #1713 / #2997 (null-delta in the serializer/assembler — null treated as a present delta overwrites real data), and now this pair in the llm stream adapters. Same root shape: a guard written for the happy-path wire format (fields omitted) breaks when a peer serializes absence explicitly. A repo-wide sweep for !== undefined guarding nullable fields is worthwhile — the pattern is now confirmed live at 4+ sites. 4. Regression shape A fake stream that emits chunk 1 with id and name set and chunk 2 with explicit null for both, asserting the emitted tool-call-delta keeps the chunk-1 values. Cheap, and it pins the exact relay behavior the report describes. 中文摘要:已对照 b150a55 确认——translate.ts 第 159-160 行的 undefined 守卫正是问题所在,!= null 修复正确(官方 API 省略字段为 undefined,relay 显式 null 则穿透守卫覆盖真实值)。sweep 发现第二处更严重的同形:llm-pi-ai/stream.ts 的 toolcall_start 三元分支让 null 穿透,delta 处理里 null.length 直接 TypeError 崩溃(不是静默覆盖)。家族关联:与 #1713/#2997(null-delta 被当 present 值覆盖真实数据)同根——「显式 null 是缺失不是值」的守卫漏洞在 llm 流适配器再次出现。回归测试形态:chunk1 带 id/name + chunk2 显式 null,断言 delta 保留首值。 |
|
Follow-up correction to my own previous comment, and a family convergence that makes this report more significant than a one-line fix: 1. The This is the 4th independent report of the exact same mechanism (explicit-null continuation deltas wiping captured tool id/name through the
And the family's hard-won refinement: 2. The canonical fix is three layers, not one line
Family total now: 6 poison paths / 11 threads (#1449/#1519/#1337/#1593/#2169 + identity-loss #1713/#2090/#2997/#4265, with #2343/#2540/#2855 confirming variants and layers). One regression test with three wire samples (null, empty-string, omitted) pins the whole family. 3. The llm-pi-ai crash site I flagged stands on its own — 中文摘要(更正+收敛):我上一条评论说的 |
Uh oh!
There was an error while loading. Please reload this page.
Summary
When using a third-party OpenAI-compatible relay (e.g. opencode.ai/zen/go/v1) that sends explicit
nullfortool_calls[].function.name/tool_calls[].idin later stream chunks of one tool call, thellm-deepseekadapter treatsnullas a present value and overwrites the realname/idfrom the first chunk. Result: the harness receivesunknown tool ""errors, the model retries repeatedly, and the run fails.Environment
@deepseek-ai/llm-deepseek(packages/llm/llm-deepseek)Repro
{"tool_calls":[{"index":0,"id":"call_1","function":{"name":"bash","arguments":"echo hello"}}]}and later a chunk{"tool_calls":[{"index":0,"id":null,"function":{"name":null,"arguments":"..."}}]}(explicit null instead of omitted field).Error: unknown tool ""repeated; the real tool name never surfaces.Root cause
In
packages/llm/llm-deepseek/src/translate.ts(and compiled lib/), the streaming tool_calls parser guards with!== undefined:null !== undefinedis true → guard passes → overwrites real name with null → downstream maps to "".Proposed fix (1-line each)
Change
!== undefinedto!= nullin the streaming tool_calls parsing path:!= nullcovers both undefined (omitted) and null (explicit) as "absent", matching OpenAI-compatible relays that explicitly emit null.Notes
!== undefinedon streamed nullable fields.All reactions