tool_use in the catalog — for this guide we’ll use Llama-3.3-70B-Instruct-FP8.
Tool-calling reliability scales with model size. The smallest models — for example
Meta-Llama-3.1-8B-Instruct-FP8 — handle a single, simple tool call well, but they get less dependable as the schema tightens or the conversation runs over several turns. Two things to watch for: arguments can come back loosely typed (a number as the string "5", or an array collapsed into one comma-separated string), so validate and coerce on your side rather than trusting the raw JSON; and in a multi-step loop the model may keep emitting tool_calls instead of settling on a final answer, so cap the number of tool round trips your code will follow. For multi-step agents, or whenever you depend on exact argument types, prefer a larger tool-capable model such as the Llama-3.3-70B-Instruct-FP8 we use below.Full round trip
Forcing a specific tool
Settool_choice to force the model’s hand:
"tool_choice": "required" to force it to call some tool, or "none" to forbid tool calls entirely.
For GLM-5.2, MiniMax-M2.7 and NVIDIA-Nemotron-3.5-Lightning-30B-A3B,
"required" is bounded to exactly one tool call when every tool you send is a
function tool. Without the bound these models repeat the same call until the
token limit. The gateway forces parallel_tool_calls: false (if you sent
true, the response carries x-flexai-dropped-params: parallel_tool_calls)
and reserves one of the four stop entries for the model’s closing tool tag, so a "required"
request that already sends four stop entries, none of them the model’s
closing tool tag, is a 400 naming stop. A reply that is not exactly one
finished call is rejected with tool_choice_not_honored; when the reply stopped
at the token limit, the message says so and names the budget to raise. Streamed, the gateway holds the tool-call deltas
until the end and replaces them with the error event; that hold is best-effort
above the hold cap described below, so treat the streamed check as a backstop
and the stop bound as the fix. Thinking may be on or off on this path. "auto"
and named tool choices are unchanged.
A forced choice is enforced whether or not you stream. Not every serving
engine can make a model call a tool, so when you pin tool_choice to
"required" or to a named function and the model answers in prose instead, the
gateway rejects the answer rather than hand your agent text it would mistake for
a result. Non-streamed that is a 400 with code: "tool_choice_not_honored".
The same 400 covers a non-streamed forced reply that stops at the token limit
(finish_reason: "length") even when it carries tool calls: a call list cut off
by the budget is duplicate or partial execution waiting to happen, so raise
max_tokens or use "auto". On every other model that rule is non-streamed
only: a stream releases tool-call deltas as they arrive and cannot take them
back, so check finish_reason before dispatching calls from a stream.
Streamed, the status is already 200 when the answer is known, so the rejection
is the stream’s last event: reasoning deltas stream as usual, any text the model
wrote before deciding to call a tool is released together with the first
tool_calls delta, and a stream that ends with no call at all ends with one
error event (same type, param and code as the 400) instead of the prose.
The gateway holds back at most a few thousand characters of that prose; a model
that keeps writing past that is streamed from there on, and the same error
event still ends the stream. A named function is satisfied only by calls to
that function: a reply that calls a different tool, instead of it or as well as
it, is rejected the same way (on a named stream the call is delivered at the
end, once the whole answer is known). Either way
the tokens the model generated are billed, since the answer was produced before
it could be judged; on a chat stream the usage chunk arrives ahead of the
error event so you can see that spend.
Either way, retry with tool_choice: "auto" or pick a model that supports
forced tool calls; see errors for the exact
shapes.
Agent loops on DeepSeek-V4-Flash-0731
DeepSeek-V4-Flash-0731 is the model most of our coding-agent traffic runs on, and a few of its defaults are worth knowing before you put it in a multi-step loop.
Reasoning is off by default. A plain request gets a direct answer with no reasoning_content. To turn reasoning on, send reasoning_effort: only minimal keeps it off; low, medium and high all select the model’s high effort level (the model has two depths, so low and medium are rounded up and the response says so in x-flexai-adjusted-params: reasoning_effort=high), and max selects its maximum. thinking: {"type": "enabled" | "disabled"} is the on/off switch and wins over reasoning_effort: disabled turns reasoning off even if a reasoning_effort is also sent (on a plain request it is redundant), and enabled on its own turns reasoning on at the model’s default depth — pair it with a reasoning_effort to pick the depth. Reasoning tokens are returned in reasoning_content (and streamed as delta.reasoning_content) and are billed as output tokens, so raise max_tokens accordingly.
Give tool calls output headroom. The model writes its tool calls in a verbose native markup that the serving engine parses into tool_calls. If max_tokens cuts that markup off, the response ends with finish_reason: "length" and no tool_calls — from your agent’s point of view the model narrated an action and then stopped. Set max_tokens to 4096 or more on tool turns (an explicit value is honored as-is; only non-streaming requests that set none default to 2048, and only on non-reasoning models — a reasoning model gets no default, since it would spend it on the reasoning), and treat finish_reason: "length" without tool_calls as a truncation to retry with a larger budget, not as a final answer. The gateway marks that case for you: a non-streamed response cut off with nothing visible carries x-flexai-empty-at-length: true, and one that ran under the default carries x-flexai-max-tokens-defaulted: 2048. Non-streaming requests are also bounded by a ~95 s generation budget, so stream long agent turns.
Read the window from the API, don’t hard-code it. The model’s context_length on GET /v1/models is the prompt limit (see model discovery); prompts over it return a 400. For streaming requests, output is bounded only by the remaining window.
If you are building the loop with a framework, rather than by hand, see LangChain and LangGraph on FlexAI. It covers the same ground with the framework’s own abstractions, and carries a table of which models were measured to complete a real two-tool agent loop — which is a stricter bar than tool calling working at all.
Parallel tool calls
Most tool-capable models we host can emit multiple tool calls per turn (message.tool_calls will have length > 1). Handle each one, then add one role: "tool" message per call — each with the matching tool_call_id — before the next completion call.