Agent workflows · 2026-10-10
A tool-calling agent with Mistral Large 4: tool_choice and reasoning
An agent loop with mistral/mistral-large-4 on LLMTR: tool_choice options, a named function instead of required, and sending reasoning_content back.
Short answer: it calls tools, but tool_choice required is not available
LLMTR (llmtr.com) is a gateway that gives access to language models hosted in Türkiye and at global providers through one OpenAI-compatible API. In a tool-calling agent you use Mistral Large 4 with the model id mistral/mistral-large-4 on the /v1/chat/completions endpoint at https://llmtr.com/v1. Tools are defined in the OpenAI-style tools array, the model returns the call in tool_calls, and you send the result back in a role: "tool" message.
Mistral positions Large 4 for coding and agentic workflows. Three things matter when you build an agent with it: for tool_choice, auto, none and a named function work but required does not; the model can call more than one tool in a single response; and earlier assistant messages must go back with their reasoning_content field.
How to build a tool-calling agent with Mistral Large 4
The Python loop below defines an order lookup tool with the OpenAI SDK. On every turn the assistant message is appended to the history as returned, with model_dump(exclude_none=True), so the reasoning_content field is kept. If the model did not call a tool, the loop prints the answer and stops. If it did, the loop runs the tool for each tool_calls item and appends the result as a role: "tool" message with its tool_call_id.
Put a turn limit on the loop (5 in the example) and keep max_tokens at a few thousand tokens. Reasoning is on by default; reasoning tokens come out of the max_tokens budget and are billed at the output price. A response whose finish_reason is length is incomplete; raise the budget and repeat that turn.
An agent loop with the OpenAI Python SDK that uses the lookup_order tool and keeps assistant messages in the history together with reasoning_content.
import json
from openai import OpenAI
client = OpenAI(base_url="https://llmtr.com/v1", api_key="llmtr-your_key")
def lookup_order(order_id: str) -> dict:
return {"order_id": order_id, "status": "shipped"}
tools = [{
"type": "function",
"function": {
"name": "lookup_order",
"description": "Fetches an order by its order number",
"parameters": {
"type": "object",
"properties": {"order_id": {"type": "string"}},
"required": ["order_id"],
},
},
}]
messages = [{"role": "user", "content": "Where are orders 4417 and 4418?"}]
for _ in range(5):
response = client.chat.completions.create(
model="mistral/mistral-large-4",
messages=messages,
tools=tools,
tool_choice="auto",
max_tokens=4096,
)
message = response.choices[0].message
# Keep reasoning_content: the model reads its earlier reasoning from it.
messages.append(message.model_dump(exclude_none=True))
if not message.tool_calls:
print(message.content)
break
for call in message.tool_calls:
args = json.loads(call.function.arguments)
messages.append({
"role": "tool",
"tool_call_id": call.id,
"content": json.dumps(lookup_order(**args)),
})
tool_choice: auto, none, a named function and required
With auto the model decides whether to call a tool, and when you have several tools it is the right choice for most turns; say in the prompt which tool to use and when. none turns tool calls off. To make a tool mandatory, name the function. The curl request below forces the lookup_order function by name and turns reasoning off.
tool_choice: "required" is not available on this model: the request is refused with 400 invalid_request and error.details.reason carries tool_choice_required_unsupported. When you move an agent that sends required to Large 4, use the function name if there is one tool, or auto plus a prompt instruction if there are several.
| tool_choice | Result |
|---|---|
| auto | The model decides whether to call a tool |
| none | No tool is called; the model answers in text |
| {"type": "function", "function": {"name": "lookup_order"}} | The named function is called |
| required | 400 invalid_request, error.details.reason: tool_choice_required_unsupported |
A curl request that forces the lookup_order function by name and sends reasoning_effort none.
curl https://llmtr.com/v1/chat/completions \
-H "Authorization: Bearer $LLMTR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "mistral/mistral-large-4",
"reasoning_effort": "none",
"max_tokens": 512,
"messages": [{"role": "user", "content": "Fetch order 4417."}],
"tools": [{
"type": "function",
"function": {
"name": "lookup_order",
"description": "Fetches an order by its order number",
"parameters": {
"type": "object",
"properties": {"order_id": {"type": "string"}},
"required": ["order_id"]
}
}
}],
"tool_choice": {"type": "function", "function": {"name": "lookup_order"}}
}'
Parallel tool calls and tool results
The model can return more than one tool call in one response; the two-order question in the example can produce two lookup_order calls. Handle the whole tool_calls array and append each result as a separate role: "tool" message with its own tool_call_id; do not leave a call without a result. If you want calls one at a time, send parallel_tool_calls: false.
Keep tool results short and structured: every result in the history counts as input tokens on later turns. If the agent's final answer must follow a schema, you can send response_format with json_schema and strict: true.
Sending reasoning back and what turns cost
In a multi-turn conversation, send earlier assistant messages back as you received them, reasoning_content included. The model reads what it reasoned in the earlier turn from that field, and consistency across turns in a tool loop depends on it. Replayed reasoning is billed as input tokens. If you remove the field you spend fewer tokens, but the model no longer knows what it decided in the earlier turn.
An example: a 1,500-token reasoning trace sent back as input on the next turn, not read from cache, costs 1,500 x $0.68 / 1M = $0.00102 until 20 October 2026, 00:00 UTC, and $0.00204 at $1.36 from then on. The way to cut cost is not to drop the trace but to reason only where it helps: on simple routing turns where the tool is obvious use reasoning_effort: "none" or mistral/mistral-large-4:none, and on planning or result-merging turns use high. The level is chosen per request.
Limits and getting started on LLMTR
Output per response is at most 262,144 tokens; a larger max_tokens returns 400. The context window is 524,288 tokens, and long tool histories have to fit in it. temperature must be between 0 and 1.5; seed and user return 422. When streaming, reasoning arrives in delta.reasoning_content and the answer in delta.content.
To start, create an account at llmtr.com, top up prepaid credit and create a key under Dashboard > API Keys; use https://llmtr.com/v1 as the base_url in the loop above. Each request is deducted from your credit at the catalog price in effect when it is processed, and the usage page shows the agent's cost by key and by model.
Build a tool-calling agent with Mistral Large 4
Define a tool, run the loop with mistral/mistral-large-4 and keep assistant messages in the history together with reasoning_content.
- Tool definition. Define the function in the tools array with its name, description and JSON Schema parameters.
- tool_choice. Use auto with several tools and name the function for a single mandatory tool; do not send required.
- Loop. Append the assistant message with reasoning_content, append each tool_calls result as a role tool message and set a turn limit.
Frequently asked questions
Does Mistral Large 4 support tool_choice required?
No. A request with tool_choice: "required" returns 400 invalid_request with tool_choice_required_unsupported in error.details.reason. To make a tool mandatory, name the function.
Should I send reasoning_content back to Mistral Large 4?
Yes. In multi-turn conversations and tool loops, send earlier assistant messages back with reasoning_content; the model reads its earlier reasoning from it. The replayed trace is billed as input tokens.
Can Mistral Large 4 call several tools at once?
Yes, one response can carry more than one tool_calls item. Handle all of them and append each result as its own role: "tool" message, or send parallel_tool_calls: false to get calls one at a time.