Skip to content

anthropic-compat: mixed tool_result+text user turn reorders tool result after text (wrong OAI ordering) #151

Description

@marksverdhei

Summary

server_chat_convert_anthropic_to_oai (tools/server/server-chat.cpp:327) always emits a user turn's tool_result blocks after that turn's text/content message, regardless of their original position. When an Anthropic user message mixes tool_result and text blocks, this reorders them — separating the tool result from the assistant tool_calls it answers and producing an invalid/incorrect OAI message sequence.

Detail

For each message, the loop (:390-453) sorts blocks into converted_content (text/image), tool_calls, and tool_results. It then emits:

  1. the main message ({role, content: converted_content, ...}) at :468, then
  2. each tool_result as a separate {role:"tool", ...} message at :471.

So a user turn with content [{type:"tool_result",...}, {type:"text","..."}] (Anthropic requires tool_result blocks first) converts to:

... assistant(tool_calls) , user(text) , tool(result)

but correct OAI ordering is:

... assistant(tool_calls) , tool(result) , user(text)

The tool message must directly follow the assistant tool_calls; interposing the user text breaks role adjacency and feeds the model the follow-up text before the tool result.

The pure case (user turn = only tool_result, the common Claude-Code flow) is unaffected: converted_content is empty so no main message is emitted (:455), and the tool messages land correctly. The bug is specific to mixed content in one user turn.

Repro

Anthropic request with:

{"messages":[
  {"role":"user","content":"weather?"},
  {"role":"assistant","content":[{"type":"tool_use","id":"t1","name":"w","input":{}}]},
  {"role":"user","content":[
     {"type":"tool_result","tool_use_id":"t1","content":"sunny"},
     {"type":"text","text":"thanks, and tomorrow?"}
  ]}
]}

→ converts to ... assistant(tool_calls), user("thanks..."), tool("sunny") — tool result after the user text.

Suggested fix

Preserve block order: instead of collecting tool_results into a trailing list, interleave them into oai_messages at their original position (emit any pending tool messages before the text/content message when the tool_result blocks precede text), or split the turn so tool messages are emitted first. A unit test in test_compat_anthropic.py with a mixed tool_result+text user turn would lock it.

Severity

Low / edge — non-crash, and the common single-tool_result flow is fine; only mixed tool_result+text in one user turn is affected. Filing for the record (found via idle bug-hunt, verified against origin/ht 90ae01b7a).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions