Tool calling
Multi-turn OpenAI-shape tool loops on Qwen-family models. Cline drop-in.Malibu speaks OpenAI-compatible tool calling on
/v1/chat/completions. It supports first-turn tool_calls[], role: "tool" results sent back on later turns, assistant tool-call history, and streamed tool-call deltas. The agent loop runs on your machine. The provider Mac only turns the model’s native tool-call markup into OpenAI’s tool_calls[] shape. It never runs your tools. Anchor framework: Cline. Point its OpenAI-compatible provider at the Malibu base URL and pick a Qwen-family model.
The examples below use client from Using Malibu with the OpenAI SDK.
Supported models
The provider picks a tool-call parser from the model ID. There is no per-request setting for this.
Qwen-family catalog models:
mlx-community/Qwen3-Coder-30B-A3B-Instruct-4bit, mlx-community/Qwen2.5-Coder-32B-Instruct-4bit, mlx-community/Qwen3-8B-4bit, mlx-community/Qwen3-32B-4bit, mlx-community/Qwen3-30B-A3B-Instruct-2507-4bit, mlx-community/Qwen3.5-27B-4bit, mlx-community/Qwen3.5-35B-A3B-4bit, mlx-community/Qwen3.6-27B-4bit, mlx-community/Qwen3.6-35B-A3B-4bit, mlx-community/Qwen3.8-27B-4bit.
Being in the catalog does not mean a model is serving right now.
GET https://api.malibu.tech/v1/status lists the models that currently have ready providers.<function=name><parameter=key>…). Malibu parses that form as well as the JSON form. Tool and parameter names can use the OpenAI/MCP character set [A-Za-z0-9_-], so MCP-namespaced names such as my-server__shell work.
Single turn: emitting a tool call
finish_reasonis"tool_calls"andmessage.contentisnull. Any prose the model wrote around the call is dropped.- Each
tool_calls[]entry hasid(call_plus 32 lowercase hex characters),type: "function",function.name, andfunction.arguments. function.argumentsis a JSON-encoded string that holds a JSON object, as in OpenAI’s wire shape. Calljson.loadson it to get a dict.- You only get calls to functions you declared in
tools. If the model names any other function, the output comes back as plain text. On gpt-oss models, an undeclared function name returns502 malformed_tool_call_final_jsoninstead. - You never get a partial tool call. Malformed markup comes back as plain assistant text. If generation hits
max_tokenspartway through a call, the response ends withfinish_reason: "length".
tool_choice and parallel_tool_calls
If your agent requires a tool call, check
finish_reason and re-prompt whenever it isn’t "tool_calls".
On providers running the current stable CLI, one turn can return several tool calls, in the order the model wrote them. Newer provider builds, not yet in a stable release, treat an omitted or false parallel_tool_calls as one call per turn: generation stops after the first complete call. Send parallel_tool_calls: true if you want more than one call per turn. Agents that run one tool per turn don’t need to change anything.
Multi-turn: feeding the result back
Run the tool locally. Then send the whole conversation back: the assistant message with itstool_calls, plus one role: "tool" message per call with the matching tool_call_id. The provider keeps no state between turns. It validates the tool-call chain inside each request.
- Every
tool_call_idmust match an assistanttool_calls[].idearlier in the same request. A missing ID returns400 invalid_tool_call_id. An ID with no matching call returns400 tool_call_id_not_found. A second result for the same ID returns400 duplicate_tool_call_id. - Tool-call IDs minted by other OpenAI-compatible providers also work. The gateway rewrites any ID that doesn’t match Malibu’s
call_format to a deterministiccall_…ID before validation, and assistant/tool pairs stay matched. role: "tool"content must be a string. An empty string is fine.nullreturns400 invalid_request.- Providers on the stable release accept at most 256 messages per request. Longer histories return
400 messages_too_long, so trim or summarize long coding-agent sessions.
Streaming tool-call deltas
Setstream=True and rebuild the tool calls from the deltas the same way you would with OpenAI, grouping by tool_calls[].index. The first delta for an index carries id, type, and function.name. Later deltas append pieces of function.arguments. Joined in order, the pieces form one JSON object, identical to the non-streaming arguments string.
data: {"error": …} event (code malformed_tool_call or malformed_tool_call_final_json), then data: [DONE]. That error event never carries finish_reason: "tool_calls". Don’t execute tool calls from a stream that ended this way. Check the error’s retryable field: malformed_tool_call_final_json is retryable, malformed_tool_call is not.
Limits
Sizes are UTF-8 bytes of the unescaped string value. A value exactly at the cap is accepted. Request-side errors return
param: null; the error code identifies which cap was hit. Malibu never silently truncates a tool result.
Buyer-side validation
Emittedtool_calls[] reflect model output, not provider-verified intent. Validate every call before you run it. See Buyer-side validation.