Skip to content

fix(llm): dedupe colliding tool_calls[].id before serialization#6506

Merged
bug-ops merged 1 commit into
mainfrom
fix/6501-dup-tool-call-id
Jul 19, 2026
Merged

fix(llm): dedupe colliding tool_calls[].id before serialization#6506
bug-ops merged 1 commit into
mainfrom
fix/6501-dup-tool-call-id

Conversation

@bug-ops

@bug-ops bug-ops commented Jul 19, 2026

Copy link
Copy Markdown
Owner

Summary

  • OpenAI-compatible backends (observed: gonkagate/Kimi-K2) can derive their tool_call.id counter relative to their perceived context window. After Zeph's Focus-based context compaction shrinks what is sent, the backend's counter can reset and re-mint an id that collides with one still present earlier in the still-serialized history Zeph continues to send, which the backend rejects outright (tool_calls[].id must be unique within the request), stalling the turn.
  • Adds a dedupe_tool_call_ids pass at the end of convert_messages_structured (crates/zeph-llm/src/openai/mod.rs) that rewrites colliding tool_calls[].id values to fresh unique ids before serialization and keeps each paired tool-role message's tool_call_id in sync (FIFO per original id, so ordering across repeated collisions is preserved). Ids that never collide are left untouched.
  • Applies to both type = "openai" and type = "compatible" providers, since CompatibleProvider delegates message conversion to OpenAiProvider.

Testing

  • cargo nextest -p zeph-llm: 1104/1104 PASS, including 7 new regression tests covering: the post-Focus-compaction-shaped non-adjacent collision from the issue, unique-ids-pass-through-unchanged, triple-collision FIFO depth, multi-call-per-message independence, an orphaned tool call with no response message, a tool message with no pending entry, and an empty tool_calls array.
  • Full local check suite (fmt, clippy --profile ci with the CI feature set, nextest, rustdoc gate) clean.
  • LLM serialization gate: cloud OpenAI/Claude test accounts are currently exhausted (known, ongoing infra issue), so live-verified against local Ollama's OpenAI-compatible endpoint (type = "compatible", qwen2.5:7b) — the identical CompatibleProvider -> OpenAiProvider::convert_messages_structured code path. A real tool-calling round trip completed end-to-end with zero 400/422 errors; the raw debug dump confirms a well-formed request with correctly paired tool_calls[].id/tool_call_id. The exact duplicate-id collision itself is not reproducible live without a backend whose id counter is context-relative — that scenario is covered directly by the added regression tests.

Closes #6501

@github-actions github-actions Bot added bug Something isn't working size/L Large PR (201-500 lines) documentation Improvements or additions to documentation llm zeph-llm crate (Ollama, Claude) rust Rust code changes labels Jul 19, 2026
An OpenAI-compatible backend can derive its tool-call-id counter
relative to its perceived context window; after Focus-based context
compaction shrinks what Zeph sends, the counter can reset and re-mint
an id colliding with one still present earlier in the still-serialized
history, which the backend rejects outright with a duplicate-id error,
stalling the turn.

Add a dedup pass at the end of convert_messages_structured that
rewrites colliding tool_calls[].id values to fresh unique ids and
keeps each paired tool-role message's tool_call_id in sync. Applies to
both the openai and compatible provider types, since CompatibleProvider
delegates message conversion to OpenAiProvider.

Closes #6501
@bug-ops
bug-ops enabled auto-merge (squash) July 19, 2026 19:25
@bug-ops
bug-ops force-pushed the fix/6501-dup-tool-call-id branch from bfa65e6 to a489cbb Compare July 19, 2026 19:25
@bug-ops
bug-ops merged commit ebfed99 into main Jul 19, 2026
43 checks passed
@bug-ops
bug-ops deleted the fix/6501-dup-tool-call-id branch July 19, 2026 19:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working documentation Improvements or additions to documentation llm zeph-llm crate (Ollama, Claude) rust Rust code changes size/L Large PR (201-500 lines)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

openai-compatible provider: duplicate tool_call.id after Focus compaction crashes the turn

1 participant