openclaw - 💡(How to fix) Fix MLX LM Server tool calls fail: finish_reason 'tool_call' (singular) not recognized [1 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
openclaw/openclaw#61499Fetched 2026-04-08 02:57:57
View on GitHub
Comments
0
Participants
1
Timeline
0
Reactions
0

Error Message

The agent stalls because mlx_lm server returns finish_reason: "tool_call" (singular), but the mapStopReason() function in @mariozechner/pi-ai only matches "tool_calls" (plural) and "function_call". The singular form falls through to the default case and is treated as an error. return { stopReason: "error", errorMessage: Provider finish_reason: ${reason} };

Gateway Error Logs

[agent/embedded] embedded run agent end: isError=true error=Provider finish_reason: tool_call [agent/embedded] embedded run agent end: isError=true error=Provider finish_reason: tool_call [agent/embedded] embedded run agent end: isError=true error=Provider finish_reason: tool_call

Root Cause

In @mariozechner/pi-ai/dist/providers/openai-completions.js, the mapStopReason() function (line ~644):

function mapStopReason(reason) {
    switch (reason) {
        case "function_call":
        case "tool_calls":           // ← only plural
            return { stopReason: "toolUse" };
        // ...
        default:
            return { stopReason: "error", errorMessage: `Provider finish_reason: ${reason}` };
    }
}

mlx_lm server (Apple's MLX framework) returns "tool_call" (singular) as the finish_reason. This is a common variation — not all OpenAI-compatible servers follow the exact plural form from the OpenAI spec.

Code Example

Provider finish_reason: tool_call

---

"providers": {
     "custom-127-0-0-1-8080": {
       "baseUrl": "http://127.0.0.1:8080/v1",
       "api": "openai-completions",
       "models": [{ "id": "minimax-m2.5-8bit", "contextWindow": 16000 }]
     }
   }

---

function mapStopReason(reason) {
    switch (reason) {
        case "function_call":
        case "tool_calls":           // ← only plural
            return { stopReason: "toolUse" };
        // ...
        default:
            return { stopReason: "error", errorMessage: `Provider finish_reason: ${reason}` };
    }
}

---

case "function_call":
case "tool_call":        // ← add singular form
case "tool_calls":
    return { stopReason: "toolUse" };

---

[agent/embedded] embedded run agent end: isError=true error=Provider finish_reason: tool_call
[agent/embedded] embedded run agent end: isError=true error=Provider finish_reason: tool_call
[agent/embedded] embedded run agent end: isError=true error=Provider finish_reason: tool_call
RAW_BUFFERClick to expand / collapse

Bug Summary

When using a local MLX LM Server (mlx_lm server) as a custom OpenAI-compatible provider, every tool call fails with:

Provider finish_reason: tool_call

The agent stalls because mlx_lm server returns finish_reason: "tool_call" (singular), but the mapStopReason() function in @mariozechner/pi-ai only matches "tool_calls" (plural) and "function_call". The singular form falls through to the default case and is treated as an error.

Environment

  • OpenClaw: 2026.4.2
  • Provider: Custom OpenAI-compatible (mlx_lm server v0.30.4)
  • Model: MiniMax M2.5 8-bit (local)
  • Platform: macOS (Apple Silicon)

Steps to Reproduce

  1. Configure a custom provider pointing to mlx_lm server:
    "providers": {
      "custom-127-0-0-1-8080": {
        "baseUrl": "http://127.0.0.1:8080/v1",
        "api": "openai-completions",
        "models": [{ "id": "minimax-m2.5-8bit", "contextWindow": 16000 }]
      }
    }
  2. Send any message that triggers a tool call
  3. The agent errors with Provider finish_reason: tool_call

Root Cause

In @mariozechner/pi-ai/dist/providers/openai-completions.js, the mapStopReason() function (line ~644):

function mapStopReason(reason) {
    switch (reason) {
        case "function_call":
        case "tool_calls":           // ← only plural
            return { stopReason: "toolUse" };
        // ...
        default:
            return { stopReason: "error", errorMessage: `Provider finish_reason: ${reason}` };
    }
}

mlx_lm server (Apple's MLX framework) returns "tool_call" (singular) as the finish_reason. This is a common variation — not all OpenAI-compatible servers follow the exact plural form from the OpenAI spec.

Suggested Fix

Add case "tool_call": to the switch:

case "function_call":
case "tool_call":        // ← add singular form
case "tool_calls":
    return { stopReason: "toolUse" };

This one-line change needs to be applied in two places:

  1. node_modules/@mariozechner/pi-ai/dist/providers/openai-completions.js (server-side)
  2. dist/control-ui/assets/openai-completions-*.js (control UI)

Gateway Error Logs

[agent/embedded] embedded run agent end: isError=true error=Provider finish_reason: tool_call
[agent/embedded] embedded run agent end: isError=true error=Provider finish_reason: tool_call
[agent/embedded] embedded run agent end: isError=true error=Provider finish_reason: tool_call

(Repeated on every message that triggers tool use — the model's tool call payload is actually well-formed, only the finish_reason string differs.)

extent analysis

TL;DR

Add a case "tool_call": to the mapStopReason() function in @mariozechner/pi-ai to handle the singular form of the finish_reason returned by mlx_lm server.

Guidance

  • Apply the suggested fix by adding case "tool_call": to the mapStopReason() function in two places: node_modules/@mariozechner/pi-ai/dist/providers/openai-completions.js and dist/control-ui/assets/openai-completions-*.js.
  • Verify the fix by sending a message that triggers a tool call and checking if the agent no longer errors with Provider finish_reason: tool_call.
  • If modifying the node_modules directory is not desirable, consider creating a custom provider that wraps the mlx_lm server and handles the finish_reason mapping.
  • Be aware that this fix may need to be reapplied if the @mariozechner/pi-ai package is updated.

Example

function mapStopReason(reason) {
    switch (reason) {
        case "function_call":
        case "tool_call":        // ← add singular form
        case "tool_calls":
            return { stopReason: "toolUse" };
        // ...
        default:
            return { stopReason: "error", errorMessage: `Provider finish_reason: ${reason}` };
    }
}

Notes

This fix assumes that the mlx_lm server will always return either "tool_call" or "tool_calls" as the finish_reason. If other variations are possible, additional cases may need to be added to the mapStopReason() function.

Recommendation

Apply the workaround by adding the case "tool_call": to the mapStopReason() function, as this is a targeted fix that addresses the specific issue with the mlx_lm server returning a singular finish_reason.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING