fix(translation): preserve multimodal tool results - #389
Conversation
Signed-off-by: Ting-Hong Shieh <32212900+ting-hong-shieh@users.noreply.github.com>
WalkthroughAnthropic document and tool-result blocks now retain normalized multimodal content. OpenAI Chat translation keeps text in ChangesMultimodal tool-result translation
Estimated code review effort: 3 (Moderate) | ~20 minutes Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@crates/switchyard-translation/src/codecs/anthropic/buffered.rs`:
- Around line 625-648: Update decode_anthropic_file_source so its final
unsupported-source fallback stores Value::Object(block.clone()) rather than only
source.clone(), preserving the document wrapper for encode_one_anthropic_block.
Add a regression test covering an unsupported document source and verifying the
re-encoded block retains the complete original document structure.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Enterprise
Run ID: 1f81d3d1-0110-497d-b9c4-ca5e5eb91786
📒 Files selected for processing (3)
crates/switchyard-translation/src/codecs/anthropic/buffered.rscrates/switchyard-translation/src/codecs/openai_chat/buffered.rscrates/switchyard-translation/tests/request_translation.rs
Signed-off-by: Ting-Hong Shieh <32212900+ting-hong-shieh@users.noreply.github.com>
|
@ting-hong-shieh Thanks for putting this PR. Can you please attach a snapshot of the output or trace before this fix and after the fix. Will be easy to understand and review with that. Thanks |
|
Thanks, @ayushag-nv. I ran the same buffered Anthropic → OpenAI Chat translation at the PR base ( Input [
{"type": "text", "text": "here it is:"},
{
"type": "image",
"source": {
"type": "base64",
"media_type": "image/png",
"data": "iVBORw0KGgo="
}
}
]Before ( {
"messages": [
{
"role": "tool",
"tool_call_id": "toolu_1",
"content": "here it is: {\"source\":{\"data\":\"iVBORw0KGgo=\",\"media_type\":\"image/png\",\"type\":\"base64\"},\"type\":\"image\"}"
}
],
"diagnostics": []
}The image block is serialized into ordinary tool-message text. After ( {
"messages": [
{
"role": "tool",
"tool_call_id": "toolu_1",
"content": "here it is:"
},
{
"role": "user",
"content": [
{
"type": "image_url",
"image_url": {
"url": "data:image/png;base64,iVBORw0KGgo="
}
}
]
}
],
"diagnostics": [
{
"code": "lossy_conversion",
"severity": "Warning",
"message": "OpenAI Chat tool messages only support text; non-text tool-result content was moved to a user message",
"source": "anthropic_messages",
"target": "openai_chat"
}
]
}Text now remains in the tool message, while the image is preserved as multimodal content in the following user message. The role lowering is also reported through the existing lossy-conversion diagnostic. |
| if source.get("type").and_then(Value::as_str) == Some("base64") | ||
| && let Some(data) = source.get("data").and_then(Value::as_str) | ||
| { | ||
| return FileSource::FileData { |
There was a problem hiding this comment.
This normalization loses source.media_type and moves the document’s top-level title into source.filename. With preservation disabled, a valid base64 Anthropic document therefore re-encodes without the required media_type and with title in the wrong place, which can make the upstream request invalid. Could we retain the Anthropic fields here (or preserve the raw block) and add a round-trip test for a known base64 document?
There was a problem hiding this comment.
Addressed in d7fcd6d. Anthropic image and document blocks now keep their complete provider wrappers in the IR, so preservation-disabled round trips retain source.media_type, top-level title, and the remaining document fields. OpenAI Chat conversion maps only portable base64 data plus title; Anthropic-managed file_id values use the existing lossy/reject path instead of being forwarded as OpenAI IDs.
Added round-trip and cross-provider regression coverage. cargo fmt --all --check, cargo clippy --workspace --all-targets -- -D warnings, and cargo test --workspace pass locally.
Signed-off-by: Elias Shieh <32212900+ting-hong-shieh@users.noreply.github.com>
d7fcd6d to
664626c
Compare
What changed
text,image, anddocumentblocks nested in Anthropictool_result.contentwhen decoding into the neutral IR.toolmessages and lower non-text output into a following multimodalusermessage.Why
The Anthropic decoder previously flattened every non-text block inside
tool_result.contentinto JSON text. By the time the OpenAI Chat encoder ran, the image or document structure had already been lost, so the downstream model received base64 or metadata as ordinary text.OpenAI Chat tool messages accept text content but not image or file parts. User messages accept those multimodal parts, so the translation now splits the content at that protocol boundary instead of silently stringifying it.
Closes #380.
Validation
cargo fmt --all --checkcargo clippy --workspace --all-targets -- -D warningscargo test --workspaceRegression coverage includes base64 images, base64 documents, parallel tool-result ordering, diagnostics, and strict rejection policy.
Summary by CodeRabbit
New Features
Bug Fixes