Menu

#105 OpenAI-compatible Qwen tagged tool calls are treated as assistant text

open
nobody
None
2026-08-11
2026-08-11
Anonymous
No

Originally created by: yablokolabs

Summary

Some Qwen deployments behind OpenAI-compatible endpoints return tool calls as tagged text instead of native message.tool_calls, for example:

<tool_call>
<function=os.fs.read>
<parameter=path>/work/README.md</parameter>
</function>
</tool_call>

The tagged call may appear in message.content or message.reasoning_content. Atomic currently treats it as assistant text because the OpenAI response adapters only consume native message.tool_calls.

A second compatibility mismatch is that Atomic advertises flattened wire names such as os__fs__read, while the model can emit the human-facing dotted name os.fs.read.

This blocked a Tinker-hosted Qwen run on Atomic Agent v0.1.72 and remains reproducible by inspection on v0.1.73/current main (9d525ef4a004a9106608bea7a01ecd70ab279c2d).

Current behavior

openai-normalise-response.ts (github.com) reads native message.tool_calls, plain content, and reasoning_content, but does not recognize tagged tool syntax.

openai-tool-call-adapter.ts (github.com) advertises escaped tool names and only normalizes names after a native tool call has already been reconstructed.

Without translation, the tagged call is interpreted as ordinary assistant content or routed to reply instead of executing the requested tool.

Expected behavior

When no native message.tool_calls are present, a provider adapter should optionally recognize valid tagged calls from content or reasoning_content, convert them to native calls, and canonicalize names against the tools actually offered in the request.

Both of these should resolve to the advertised os__fs__read tool:

os__fs__read
os.fs.read

Suggested implementation

Add a bounded compatibility parser at the OpenAI provider-adapter layer that:

  • activates only when native tool calls are absent;
  • parses the known <tool_call><function=...><parameter=...> format;
  • accepts only names corresponding to tools supplied in the request;
  • maps dotted canonical names to their escaped wire names;
  • coerces parameter values using the supplied JSON schema;
  • passes the result through Atomic's normal tool validation and execution path.

A localhost adapter implementing this behavior enabled a meaningful 50-step Tinker/Qwen coding run.

Acceptance criteria

  • Tagged calls are parsed from both content and reasoning_content.
  • Dotted and escaped names map to the same offered tool.
  • String, integer, number, boolean, array, object, and null parameters are schema-coerced correctly.
  • Multiple tagged calls preserve order and receive distinct indices in streamed output.
  • Unknown tool names and malformed tagged text are not executed.
  • Native OpenAI message.tool_calls remain the preferred path and behave unchanged.
  • Ordinary prose containing angle brackets is not misclassified as a tool call.

Related

Tickets: #120

Discussion


Log in to post a comment.