ROCm / ROCm/FastFlowLM

Bug: tool_choice is not enforced and malformed tool calls are returned as successful responses

Open
#649 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
C++
Stars
1.9k
Forks
152
Avg merge
4h 14m
Merged PRs (30d)
11

Description

Summary

POST /v1/chat/completions accepts OpenAI-style tools, tool_choice, and parallel_tool_calls, but FastFlowLM does not enforce the requested tool-selection policy or validate the parsed tool call before returning it.

Across Gemma 4 and two Qwen families, deliberately truncating a required tool call produces HTTP 200 with finish_reason: "tool_calls" even though the function name and/or arguments are invalid. A client will attempt to dispatch these fabricated calls and fail downstream.

This is distinct from model quality: the server owns the API contract and should never label an unusable parser result as a completed tool call.

Environment

  • FastFlowLM: stock Linux release 0.9.46
  • Hardware: AMD Ryzen AI NPU / XDNA2
  • Endpoint: /v1/chat/completions, non-streaming
  • Context length: 8192 for the reproduction matrix
  • Temperature: 0.1
  • parallel_tool_calls: false
  • Test date: 2026-08-03

Minimal reproduction

Start any tested model, for example:

flm serve qwen3-it:4b --ctx-len 8192 --port 52625

Then send a required tool call with an intentionally small output budget:

curl -sS http://127.0.0.1:52625/v1/chat/completions \
  -H 'Content-Type: application/json' \
  -d '{
    "model": "qwen3-it:4b",
    "messages": [
      {"role": "user", "content": "Get the weather for Miami using the function."}
    ],
    "tools": [{
      "type": "function",
      "function": {
        "name": "get_weather",
        "description": "Get the current weather for a location",
        "parameters": {
          "type": "object",
          "properties": {"location": {"type": "string"}},
          "required": ["location"]
        }
      }
    }],
    "tool_choice": "required",
    "parallel_tool_calls": false,
    "temperature": 0.1,
    "max_tokens": 1
  }'

Actual results

All three tool-capable model families returned HTTP 200 and finish_reason: "tool_calls" for an invalid, truncated call:

Model Returned function Returned arguments
gemma4-it:e2b call (not declared) "{}"
qwen3-it:4b empty string empty string
qwen3.5:2b empty string "{}"

Representative Qwen response:

{
  "choices": [{
    "message": {
      "role": "assistant",
      "tool_calls": [{
        "type": "function",
        "function": {"name": "", "arguments": ""}
      }]
    },
    "finish_reason": "tool_calls"
  }]
}

There is a second policy-enforcement symptom on gemma3:1b: with tool_choice: "required" and a normal token budget, FastFlowLM returned plain assistant text with finish_reason: "stop". If that model does not support tools, the server should reject the request rather than silently ignore the required policy.

A longer Gemma 4 workflow also leaked raw template syntax as ordinary content instead of a structured call:

list directory{path:<|"|>.<|"|>}<tool_call|>

Expected behavior

  • tool_choice: "none" must prevent tool calls.
  • tool_choice: "required" must not return a successful response without at least one valid declared tool call.
  • A named function choice must call exactly that declared function.
  • parallel_tool_calls: false must allow at most one call in the response.
  • Every returned call must name a declared function and contain a JSON-encoded object in function.arguments.
  • If generation is truncated or parsing fails, return a non-dispatchable result such as finish_reason: "length", or a clear model/server error. Do not return finish_reason: "tool_calls" with an invalid call.
  • If a model lacks tool support, reject requests that require tool use with a clear error.

Cross-check

The same test corpus includes a normal required call and a three-turn chain (read_dataset -> calculate_total -> final answer). Those succeed on the tested Gemma 4 and Qwen models, demonstrating that the models and parsers can produce valid calls when generation completes. The defect is that invalid results are accepted without enforcing the request contract.

A local response-validation prototype was also tested with gemma4-it:e4b; it passed the normal required call, fail-closed truncation, multi-turn chain, and a longer skill workflow. That suggests this can be fixed centrally in the OpenAI REST boundary instead of with per-model argument repair.

Prototype commit: https://github.com/waw2637/FastFlowLM/commit/4978c1a18fddd235e438e8ffd063083617395adb

Related issues

  • #537 covers argument normalization/serialization.
  • #559 and #641 cover model workflow/output quality.
  • #634 covers updated Gemma 4 templates.

Those may improve how often a model emits valid calls, but they do not ensure that the REST API enforces tool_choice or fails closed when parsing produces an invalid call.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start at the OpenAI REST boundary handling POST /v1/chat/completions and review prototype commit 4978c1a18fddd235e438e8ffd063083617395adb. Reproduce the curl case and the stated tool-call corpus, then verify that tool_choice, parallel_tool_calls, declared function names, and JSON arguments are enforced and invalid or truncated results fail closed rather than returning finish_reason "tool_calls".

Written by the indexing model from the issue text.

Assessment

Tech stack
cpp
Domain
api, backend
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.