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).

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions