Skip to content

tool_choice="auto" returns tool calls as text with empty tool_calls (only "required" yields structured tool_calls) #874

Description

@justinchuby

Summary

When calling the OpenAI-compatible /v1/chat/completions endpoint with tool_choice="auto" (the OpenAI default, and what most agent frameworks send), Foundry Local returns the tool call as raw text inside message.content with an empty tool_calls array and finish_reason="stop". The structured tool_calls are only populated when the caller explicitly sets tool_choice="required".

This breaks standard OpenAI clients and agent frameworks (e.g. anything built on the OpenAI SDK, LangChain, custom agents), because they read message.tool_calls and — per the OpenAI spec — expect tool_choice="auto" to let the model decide and still return structured tool calls when it chooses to call a tool. With Foundry Local they instead receive prose containing an XML/JSON blob that they cannot dispatch.

Reproduction

Model: qwen2.5-coder-7b-instruct-generic-cpu (also reproduces on other tool-capable models), Foundry Local 0.10.0.

Request (only tool_choice differs):

curl -s http://127.0.0.1:<port>/v1/chat/completions -H "Content-Type: application/json" -d '{
  "model": "qwen2.5-coder-7b-instruct-generic-cpu",
  "messages": [{"role": "user", "content": "What is the weather in Paris? Use the tool."}],
  "tools": [{"type": "function", "function": {
      "name": "get_weather",
      "description": "Get weather for a city",
      "parameters": {"type": "object", "properties": {"city": {"type": "string"}}, "required": ["city"]}}}],
  "tool_choice": "auto",
  "temperature": 0
}'

Actual behavior

tool_choice="auto" — tool call leaks into content as text, tool_calls empty:

finish_reason: stop
tool_calls:    []
content:       "```xml\n<tools>\n    {\"name\": \"get_weather\", \"arguments\": {\"city\": \"Paris\"}}\n</tools>\n```"

tool_choice="required" — structured tool call is returned as expected:

finish_reason: tool_calls
tool_calls:    [{"index": 0, "id": "09xikmhSQ", "type": "function",
                "function": {"name": "get_weather", "arguments": "{\n  \"city\": \"Paris\"\n}"}}]

Expected behavior

With tool_choice="auto", when the model decides to call a tool, Foundry Local should parse the model's tool-call output and populate message.tool_calls with finish_reason="tool_calls" — the same as tool_choice="required" — instead of returning the call as raw text. auto should differ from required only in whether a tool must be called, not in how a chosen call is surfaced.

Impact

  • Any OpenAI-compatible agent/framework that sends the default tool_choice="auto" cannot use Foundry Local tool calling without a custom text-scraping workaround.
  • Forcing tool_choice="required" is not a general fix, because it prevents the model from answering directly when no tool is needed.

Environment

  • Foundry Local: 0.10.0
  • Endpoint: OpenAI-compatible /v1/chat/completions
  • Models: qwen2.5-coder-7b-instruct-generic-cpu (reproduces broadly across tool-capable models)
  • OS: Linux

Question

Is the auto → text vs required → structured behavior intentional, or a gap in the server-side tool-call parsing? Ideally auto would run the same tool-call extraction that required already does and emit structured tool_calls.

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