fix(translation): preserve tool IDs across Anthropic - #397
fix(translation): preserve tool IDs across Anthropic#397ting-hong-shieh wants to merge 4 commits into
Conversation
Signed-off-by: Ting-Hong Shieh <32212900+ting-hong-shieh@users.noreply.github.com>
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Enterprise Run ID: ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (7)
WalkthroughThe change replaces lossy tool ID sanitization with reversible URL-safe Base64 encoding. OpenAI Chat and Responses encoders restore original IDs before serialization. Request and streaming tests cover round trips, reserved prefixes, malformed values, Unicode, and empty IDs. ChangesTool ID round-trip
Estimated code review effort: 3 (Moderate) | ~20 minutes Mergeability Score: ⚪ Minimal · up to This change preserves tool-call IDs across translation boundaries and adds regression coverage; no actionable merge-blocking risk remains beyond normal checks and review. Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
Comment |
|
@ting-hong-shieh Thanks for putting this up. Can you always do the before and after the change snapshot of the results. It helps me to do better and fast reviews. |
|
Thanks, @ayushag-nv. I ran the same in-process buffered translation at the PR base ( Input upstream tool-call ID: Before ( {
"anthropic_tool_use_id": "functions_list_skills_0",
"replayed_openai_tool_call_id": "functions_list_skills_0",
"replayed_openai_tool_result_id": "functions_list_skills_0",
"upstream_emitted_id": "functions.list_skills:0"
}The unsupported characters are replaced with underscores, and both replayed OpenAI IDs differ from the ID emitted by the upstream model. After ( {
"anthropic_tool_use_id": "sy64_ZnVuY3Rpb25zLmxpc3Rfc2tpbGxzOjA",
"replayed_openai_tool_call_id": "functions.list_skills:0",
"replayed_openai_tool_result_id": "functions.list_skills:0",
"upstream_emitted_id": "functions.list_skills:0"
}The Anthropic-facing ID stays within the accepted character set, while the OpenAI tool call and tool result both recover the exact upstream ID on replay. |
Signed-off-by: Ting-Hong Shieh <32212900+ting-hong-shieh@users.noreply.github.com>
|
@nachiketb-nvidia @elyasmnvidian to review |
Signed-off-by: Elias Shieh <32212900+ting-hong-shieh@users.noreply.github.com>
|
Resolved the merge conflict and updated the branch. Thanks! |
nachiketb-nvidia
left a comment
There was a problem hiding this comment.
Requesting changes here: the buffered path restores reversible Anthropic-safe tool IDs, but the streaming path does not.
Concrete issue:
- Anthropic stream
tool_use.id = sy64_ZnVuY3Rpb25zLmxpc3Rfc2tpbGxzOjA - Expected OpenAI tool-call ID:
functions.list_skills:0 - Actual OpenAI stream output keeps the encoded
sy64_...ID
Suggested fix:
- Keep
sanitize_anthropic_tool_use_id(...)when encoding to Anthropic. - Decode IDs when reading from Anthropic, not when writing to OpenAI/Responses.
- Apply that in:
- Anthropic buffered
tool_use.id - Anthropic buffered
tool_result.tool_use_id - Anthropic stream
content_block_starttool IDs
- Anthropic buffered
- Remove
desanitize_anthropic_tool_use_id(...)calls/imports from OpenAI Chat / Responses buffered encoders. - After that cleanup, OpenAI Chat / Responses encoders should just emit the neutral IR IDs directly.
- Add one streaming regression test for Anthropic -> OpenAI Chat or Responses. one test should be enough, remove the rest
This keeps the Anthropic-specific escaping localized to the Anthropic codec and makes buffered + streaming behavior consistent.
Signed-off-by: Ting-Hong Shieh <32212900+ting-hong-shieh@users.noreply.github.com>
|
Thanks, @nachiketb-nvidia. Addressed this in e534e88:
Validation:
No live provider call was made. Could you please take another look? |
What
sy64_-prefixed, unpadded Base64URL encoding.tool_use.idandtool_result.tool_use_idvalues at Anthropic ingress, for both buffered and streaming paths.[A-Za-z0-9_-]IDs unchanged, escape the reserved prefix, and pass malformed encoded values through unchanged.Why
Some OpenAI-compatible models emit tool-call IDs containing characters that Anthropic does not accept. Kimi K2, for example, can emit IDs such as
functions.list_skills:0and expects the same ID on the next turn.The previous sanitizer replaced unsupported characters with underscores. That mapping could not be reversed, so Switchyard replayed a different ID to the upstream model and could break multi-turn tool calling. The new encoding keeps Anthropic-facing IDs within the accepted character set while Anthropic decoders restore the original ID before it enters the neutral IR.
Closes #178
Before / after
Input upstream tool-call ID:
functions.list_skills:0Before:
{ "anthropic_tool_use_id": "functions_list_skills_0", "replayed_openai_tool_call_id": "functions_list_skills_0", "replayed_openai_tool_result_id": "functions_list_skills_0" }After:
{ "anthropic_tool_use_id": "sy64_ZnVuY3Rpb25zLmxpc3Rfc2tpbGxzOjA", "replayed_openai_tool_call_id": "functions.list_skills:0", "replayed_openai_tool_result_id": "functions.list_skills:0" }How tested
cargo fmt --all --checkcargo clippy --workspace --all-targets -- -D warningscargo test -p switchyard-translationcargo test --workspaceuv run ruff check .uv run mypy switchyarduv run pytest tests/ -v -m "not integration"— 147 passed, 2 deselectedChecklist
--helpare unchanged because this does not add a configuration or command surface.Notes for reviewers
sy64_is reserved for encoded IDs. An upstream ID that already starts with this prefix is encoded again before it reaches an Anthropic client, which keeps decoding unambiguous. Invalid prefixed input is left unchanged.No live provider call was made.
Summary by CodeRabbit