fix(llm): dedupe colliding tool_calls[].id before serialization#6506
Merged
Conversation
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
enabled auto-merge (squash)
July 19, 2026 19:25
bug-ops
force-pushed
the
fix/6501-dup-tool-call-id
branch
from
July 19, 2026 19:25
bfa65e6 to
a489cbb
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
tool_call.idcounter 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.dedupe_tool_call_idspass at the end ofconvert_messages_structured(crates/zeph-llm/src/openai/mod.rs) that rewrites collidingtool_calls[].idvalues to fresh unique ids before serialization and keeps each pairedtool-role message'stool_call_idin sync (FIFO per original id, so ordering across repeated collisions is preserved). Ids that never collide are left untouched.type = "openai"andtype = "compatible"providers, sinceCompatibleProviderdelegates message conversion toOpenAiProvider.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 emptytool_callsarray.--profile ciwith the CI feature set, nextest, rustdoc gate) clean.type = "compatible",qwen2.5:7b) — the identicalCompatibleProvider -> OpenAiProvider::convert_messages_structuredcode 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 pairedtool_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