Repository navigation
Guard: inspect and restore every choice of an n > 1 answer - #343
Merged
Merged
Conversation
Chat Completions answers with several choices and Gemini answers with several candidates were only partly covered by the tool call firewall and by the streamed placeholder restorer: both looked at the first element of `choices` / `candidates` only. Tool call firewall (tools/wall.rs): - every element of `choices` / `candidates` in a stream frame is inspected, and every Gemini candidate of a whole body; - Chat argument fragments are assembled per choice (by the choice `index`, a missing one counting as 0) and per tool call index, and also per position in the frame, the way a client that reads only the first element of each frame puts them together; a call is checked the way either kind of client would assemble it; - verdict paths name the choice or candidate the call is in. Placeholder restorer (redact/sse.rs): - Chat and Gemini text and argument lanes are kept per choice; a finish reason closes only the lanes of that choice, and synthesized frames carry its `index`. Public signatures are unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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 subscribe to this conversation on GitHub.
Already have an account?
Sign in.
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.
Problem
An answer can carry several choices: Chat Completions
choices(for example withn > 1) and Geminicandidates(for example withcandidateCount > 1), and one stream frame can carry more than one of them. Two parts oftw-guardonly looked at the first element:tools/wall.rs): in a stream, onlychoices[0]/candidates[0]of each frame was inspected, and Chat argument fragments were keyed by the tool callindexalone, so fragments of different choices were assembled together. In a whole Gemini body only the first candidate was inspected. A tool call in another choice could therefore reach a client without being checked against the dangerous-command rules.redact/sse.rs): all choices shared one text lane and Chat tool arguments were keyed by callindexalone, any choice's finish reason closed every lane, and synthesized frames always saidindex: 0. Half a placeholder held back for one choice could be emitted into another, leaving placeholders unrestored or tool arguments broken.Fix
Tool call firewall:
choices/candidatesin a stream frame is inspected, and every candidate of a whole Gemini body (Chat whole bodies already were);index; a missing one counts as 0, like a duplicate it is merged into that choice) and per tool call index;Placeholder restorer:
indexin the lane key), a finish reason closes only that choice's lanes, and synthesized frames carry the right choiceindex(and the tool callindexfor Chat arguments).Public signatures are unchanged.
Tests
New tests (all but one fail on
main):tools::wall:a_call_in_any_element_of_a_chat_frame_is_checked,interleaved_choices_are_assembled_per_choice,repeated_or_missing_choice_indexes_are_merged_like_the_client_does,a_function_call_in_any_gemini_candidate_is_checked, andwhat_a_first_element_reader_assembles_is_checked_too(passes onmain; it keeps the first-element reading covered after the change).redact::sse:chat_choices_hold_back_and_restore_apart,a_chat_choice_that_finishes_closes_only_itself,gemini_candidates_hold_back_and_restore_apart.cargo fmt --all -- --check,cargo clippy --workspace --all-targets -- -D warningsandcargo test --workspacepass locally.🤖 Generated with Claude Code