Agent workflows · 2026-10-17
An Arabic agent that calls tools with Jais 3: the OpenAI tools loop
Build an agent that speaks Arabic and calls tools with ifm/jais-3-mova-36b-a4b: tool_choice auto, a two-turn loop and the error cases, with Python.
Short answer: give the tools with tool_choice auto
LLMTR is a gateway that gives access to language models from different providers through one OpenAI-compatible API. You call Jais 3 with the id ifm/jais-3-mova-36b-a4b on the /v1/chat/completions endpoint at https://llmtr.com/v1 and define the tools in the tools field in the OpenAI format. When the model decides to call a tool, the response carries tool_calls; you run the tool and send the result back in a tool message.
IFM's model card says Jais 3 is designed for Arabic and English assistant and agentic applications. On LLMTR only tool_choice auto works on this row. required, a named function and none with tools attached are refused with 400 before the request reaches the model, and the error message says which form to use.
How do you build an Arabic agent that calls tools with Jais 3?
The Python example below defines an order status tool. The user asks about their order in Arabic; the model calls get_order_status with an order_id, the example runs the tool and adds the result to the history as a tool message. On the second request the model reads the result and answers in Arabic. The loop runs at most four turns, so a conversation that goes wrong does not run forever.
Add the assistant message that carries tool_calls to the history as it is, then send one tool message per call carrying its tool_call_id. You do not need to send reasoning_content back in earlier assistant messages. The arguments the model produces are a JSON string; the example catches the error that parsing them can raise and reports that error to the model as the tool result too.
By default the model reasons before answering on every turn, and reasoning tokens count toward max_tokens. That is why the example gives 4,096 tokens. If a turn ends with finish_reason length, raise the limit.
An OpenAI Python SDK agent loop that calls the get_order_status tool through ifm/jais-3-mova-36b-a4b with tool_choice auto, sends the tool result back in a tool message and runs at most four turns.
import json
import os
from openai import OpenAI
client = OpenAI(base_url="https://llmtr.com/v1", api_key=os.environ["LLMTR_API_KEY"])
MODEL = "ifm/jais-3-mova-36b-a4b"
TOOLS = [
{
"type": "function",
"function": {
"name": "get_order_status",
"description": "Return the shipping status of an order by its number.",
"parameters": {
"type": "object",
"properties": {"order_id": {"type": "string"}},
"required": ["order_id"],
},
},
}
]
def get_order_status(order_id: str) -> dict:
# Replace with your own order system.
return {"order_id": order_id, "status": "shipped", "eta": "2026-10-21"}
messages = [
{"role": "system", "content": "أنت مساعد خدمة عملاء. أجب بالعربية."},
{"role": "user", "content": "أين طلبي رقم 1042؟"},
]
for _ in range(4):
response = client.chat.completions.create(
model=MODEL, messages=messages, tools=TOOLS, tool_choice="auto", max_tokens=4096
)
message = response.choices[0].message
if not message.tool_calls:
print(message.content)
break
messages.append(
{
"role": "assistant",
"content": message.content,
"tool_calls": [call.model_dump() for call in message.tool_calls],
}
)
for call in message.tool_calls:
try:
result = get_order_status(**json.loads(call.function.arguments))
except (json.JSONDecodeError, TypeError) as err:
result = {"error": str(err)}
messages.append({"role": "tool", "tool_call_id": call.id, "content": json.dumps(result)})
Which tool_choice forms work on Jais 3?
The table shows how LLMTR answers each tool_choice value on this row. If you want a particular tool to be called, use tool_choice auto and say so in the system prompt; check in your code whether the response carries tool_calls. To turn tool use off, do not send the tools field at all.
| tool_choice | Result |
|---|---|
| auto (or no field) | Supported; the model decides whether to call the tool |
| required | 400 invalid_request; use auto |
| A named function | 400 invalid_request; use auto |
| none (with tools attached) | 400 unsupported_input; do not send the tools field |
Keeping the loop robust
Limit the number of turns: if the model calls a tool again and again, the loop stops at the limit you set. Give tool results in short JSON; that text is all the model reads. If a tool fails, give the error as the result too, so the model can tell the user what happened.
If some step requires a tool call, for example a database lookup before every answer, run that step in your code and add the result to the prompt instead of expecting the model to do it. For jobs that need a forced tool call, the same maker's ifm/k2-horizon-375b-a23b model supports required and a named function.
Tool definitions are part of the prompt as well and are sent again on every turn. Prefer short, clear parameter names to long descriptions.
Testing the agent and handling errors
Test the tool functions apart from the model: check with unit tests that a function such as get_order_status returns the right result for fixed inputs. Then run the loop with a few typical Arabic questions and record which questions make the model call the tool and which it answers directly. If the model made up an answer to a question where the tool was not called, state more clearly in the system prompt when the tool should be used.
If you get a 429 inside the loop, tell the two cases apart. If Retry-After is a few seconds, you hit the short-term request limit; wait that long and repeat the same request. If Retry-After is measured in hours, the daily allowance is used up; stop the loop and tell the user. A 400 error is about the shape of the request; log the message and do not repeat the request without fixing it.
Recording each turn's usage values and finish_reason lets you see later how many tokens each turn spent and whether an answer was cut at the limit.
Free period, daily allowance and the data note
The row is free until 5 November 2026, 23:59 (TRT), and every account has an allowance of 200,000 tokens a day for this model, and new accounts start with a smaller allowance on their first day; input and output are added up. In agent loops every turn sends the whole history again, so the allowance runs out faster than with single questions; add up usage.total_tokens from each response to follow your spending. When the allowance is used up, the API returns 429, and the Retry-After header tells you when you can try again; the allowance renews every day at 00:00 (TRT). From 6 November 2026, 00:00 the model returns 410 model_retired.
According to the data note on the LLMTR model page, prompts and completions may be logged by the provider and used to improve the model. Tool results are part of the prompt too: do not put data such as customer names, addresses or card details in a tool result.
Build an Arabic agent that calls tools with Jais 3
Define the tool in the OpenAI format, send the request with tool_choice auto and return the tool result in a tool message.
- Define the tool. In the tools field, write the tool's name, description and JSON Schema parameters in the OpenAI format.
- First request. Send the Arabic question to ifm/jais-3-mova-36b-a4b with tool_choice auto and check whether the response carries tool_calls.
- Return the result. Run the tool, add the result as a tool message with its tool_call_id and repeat the request to get the model's Arabic answer.
Frequently asked questions
Does Jais 3 support tool calling?
Yes. On LLMTR, ifm/jais-3-mova-36b-a4b calls tools in the OpenAI format. Use tool_choice auto; required, a named function and none with tools attached return 400.
Can I force a particular tool on Jais 3?
No. Forcing through tool_choice is refused on this model. Say in the system prompt that the tool is needed, or run the required step in your own code. If you need a forced tool call, ifm/k2-horizon-375b-a23b supports required and a named function.
Do I need to add reasoning_content to the history?
No. Adding earlier assistant messages with content and, where present, tool_calls is enough; you do not need to send reasoning_content back.