Skip to content

Qwen3-coder Tool Calling Fails with Many Tools via Ollama #6883

@kuccello

Description

@kuccello

Describe the bug

As described in other reports #6655 and #5986, goose and ollama model interactions struggle to do anything useful in developer mode.

Summary

When using Qwen3-coder model through Ollama with Goose's default developer extensions (11 tools), the model fails to execute tool calls. Instead of returning structured JSON tool calls, the model outputs XML-style tool invocations embedded in the text content, which Goose previously could not parse.

Root Cause

Qwen3-coder supports native JSON tool calling when provided with a small number of tools (approximately 5 or fewer). However, when the tool count exceeds this threshold, the model switches behavior and outputs XML-formatted tool calls within the content field of the response, even when the tools array is properly provided in the API request.

Example of XML Output Format

When Qwen3-coder receives too many tools, it outputs tool calls like this:

I'll create the file for you.

<function=developer__text_editor>
<parameter=command>write</parameter>
<parameter=path>/tmp/hello.txt</parameter>
<parameter=file_text>hello world</parameter>
</function>
</tool_call>

Instead of the expected JSON format in the tool_calls field:

{
  "tool_calls": [{
    "id": "call_123",
    "function": {
      "name": "developer__text_editor",
      "arguments": "{\"command\": \"write\", \"path\": \"/tmp/hello.txt\", \"file_text\": \"hello world\"}"
    }
  }]
}

Reproduction Steps

  1. Install Goose and configure it to use Ollama with Qwen3-coder:

    # ~/.config/goose/config.yaml
    GOOSE_PROVIDER: ollama
    GOOSE_MODEL: qwen3-coder:latest
  2. Run Goose with the developer extension enabled (default)

  3. Ask Goose to perform any file operation, e.g., "Create a file at /tmp/test.txt with the content 'hello'"

  4. Observe that Goose responds with text containing XML tool calls but does not actually execute the tool

Technical Details

  • Affected Provider: Ollama (using OpenAI-compatible API format)
  • Affected Model: qwen3-coder (tested with qwen3-coder:latest)
  • Tool Count Threshold: Approximately 5-6 tools triggers the behavior change
  • Goose Default Tool Count: 11 tools (with developer extension)

Verification via curl

With few tools (works correctly - returns JSON tool_calls):

curl http://localhost:11434/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{
    "model": "qwen3-coder:latest",
    "messages": [{"role": "user", "content": "List files in current directory"}],
    "tools": [{"type": "function", "function": {"name": "shell", "parameters": {"type": "object", "properties": {"command": {"type": "string"}}}}}]
  }'

With many tools (fails - returns XML in content):

I'll create the file for you.

<function=developer__text_editor>
<parameter=command>write</parameter>
<parameter=path>/tmp/hello.txt</parameter>
<parameter=file_text>hello world</parameter>
</function>
</tool_call>

Solution Implemented - see #6882

Added XML tool call parsing as a fallback in crates/goose/src/providers/formats/openai.rs:

  1. parse_xml_tool_calls function - Parses XML-style tool calls using regex:

    • Matches <function=name>...</function> blocks
    • Extracts <parameter=key>value</parameter> pairs
    • Returns parsed tool calls as MessageContent::ToolRequest
  2. Modified response_to_message - Added fallback logic:

    • If no JSON tool_calls found in response
    • Check if content contains <function= pattern
    • Parse XML and convert to tool requests
  3. Modified response_to_streaming_message - Added streaming support:

    • Accumulate text content during streaming
    • On stream completion, check for XML tool calls
    • Parse and yield tool requests if found

Testing

Unit tests added for:

  • Single XML tool call parsing
  • Multiple XML tool calls
  • XML with prefix text
  • Exact Qwen3-coder output format
  • JSON tool_calls taking precedence over XML (no false positives)
  • Integration with response_to_message

Files to be changed

  • crates/goose/src/providers/formats/openai.rs - Add XML parsing logic and tests

Related Information

  • This appears to be a limitation/behavior of Qwen3-coder when handling large tool schemas
  • Other models using the Ollama OpenAI-compatible endpoint may exhibit similar behavior
  • The fix is backward-compatible and only activates when JSON tool_calls are absent

Potential Future Improvements

  1. Consider adding a configuration option to limit the number of tools sent to models known to have this limitation
  2. Add logging/telemetry when XML fallback is triggered to help identify affected models
  3. Investigate if this behavior can be controlled via Ollama configuration or model parameters

Please provide the following information

  • OS & Arch: Mac OS Tahoe 26.2 (M1 Ultra 128GB ram), ARM 64
  • Interface: UI / CLI
  • Version: v1.22.1
  • Extensions enabled: apps, developer
  • Provider & Model: qwen3-coder:latest - 30b

Additional context
I have a fix for this here #6882

Metadata

Metadata

Assignees

Labels

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions