Agent workflows · 2026-10-22
Building a tool-calling agent loop with Agents-A1
A multi-turn tool loop on Agents-A1: tools, tool_choice, returning tool results, a step limit and reasoning; free until 31 October 2026.
Short answer: tools, tool_choice auto, a role tool message and a step limit
LLMTR is a gateway that gives access to language models from different providers through one OpenAI-compatible API. Agents-A1 (internlm/agents-a1-free) and Sage-A1.5 (internlm/sage-a1.5-free), which runs the same loop, can be called free of charge until 31 October 2026, 23:59 (TRT) on the /v1/chat/completions endpoint at https://llmtr.com/v1 with the API key from your llmtr.com account. The loop has four steps: define the tools in tools, send auto as tool_choice, run every tool_calls item of the response in your own code and send the result back in a role: "tool" message, and repeat until the model stops calling tools or the step limit is reached. After 31 October 2026, keep the same loop on the paid qwen/qwen3.6-35b-a3b or the free nvidia/nemotron-3.5-super-vl-free.
Agents-A1's model card describes it as a 35B MoE agent model scaled for long-horizon search, engineering, scientific research, instruction following and tool calling, and says it supports function calling natively. On LLMTR it offers a 262,144-token context, text and image input, and json_object and json_schema output.
How do you build a tool-calling agent loop with Agents-A1?
Look at the model's response on every turn. If message.tool_calls is set, first append the model's assistant message with its tool_calls field to the history, then run the tool for each call and append the result as a role: "tool" message with the same tool_call_id. function.arguments is a JSON string; parse it before you use it. If tool_calls is empty, message.content is the final answer and the loop ends. Branch on whether the tool_calls field is present, not on the finish_reason label.
The model can return more than one tool call in a single turn. Answer each call with its own role: "tool" message; if one is missing, the next request returns an error. Put an upper bound on the loop: MAX_STEPS is 5 in the example, and a loop that reaches the limit stops without waiting for a final answer, so an unexpected tool result cannot turn into a loop that never ends. If you hit the limit often, make the tool definitions and the prompt clearer.
Turn the tool result into text with json.dumps and put it in the content field. The shorter the result, the fewer input tokens the following turns carry, because the whole history is sent again on every turn.
Full example: at most five steps with a local function
The Python example below defines a local function named get_weather as a tool and sends it to Agents-A1 with tool_choice auto. It runs every returned call, puts the result into a role: "tool" message with json.dumps, and stops after at most five steps. If the model returns two parallel calls for the two cities, each gets its own answer. For Sage-A1.5, change only the id in the MODEL variable; the ids to use are written in the comment at the top of the code.
OpenAI Python SDK loop on Agents-A1 that runs the get_weather tool through https://llmtr.com/v1/chat/completions, returns tool results in role: "tool" messages and stops after at most five steps.
import json
import os
from openai import OpenAI
client = OpenAI(base_url="https://llmtr.com/v1", api_key=os.environ["LLMTR_API_KEY"])
# No charge until 31 October 2026 23:59 (TRT). Sage-A1.5 runs the same loop: "internlm/sage-a1.5-free".
# From 1 November 2026 both ids return 410; move to "qwen/qwen3.6-35b-a3b" (paid, keep tool_choice "auto")
# or "nvidia/nemotron-3.5-super-vl-free".
MODEL = "internlm/agents-a1-free"
MAX_STEPS = 5
def get_weather(city: str) -> dict:
# Local stand-in for a real weather service.
return {"city": city, "temp_c": 21, "condition": "clear"}
LOCAL_TOOLS = {"get_weather": get_weather}
TOOLS = [{
"type": "function",
"function": {
"name": "get_weather",
"description": "Return the current temperature and condition for a city.",
"parameters": {
"type": "object",
"properties": {"city": {"type": "string"}},
"required": ["city"],
},
},
}]
messages = [{"role": "user", "content": "Compare today's weather in Istanbul and Ankara."}]
for step in range(1, MAX_STEPS + 1):
response = client.chat.completions.create(
model=MODEL,
messages=messages,
tools=TOOLS,
tool_choice="auto",
max_tokens=8192,
)
choice = response.choices[0]
message = choice.message
print("step", step, choice.finish_reason, response.usage.total_tokens)
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],
})
# Answer every call of the turn, parallel calls included, each with its own tool_call_id.
for call in message.tool_calls:
args = json.loads(call.function.arguments)
result = LOCAL_TOOLS[call.function.name](**args)
messages.append({"role": "tool", "tool_call_id": call.id, "content": json.dumps(result)})
else:
print("Stopped after", MAX_STEPS, "steps without a final answer.")
tool_choice, reasoning, max_tokens and streaming
The LLMTR model card tells you to use auto or a named function for tool_choice on Agents-A1 and Sage-A1.5; do not send required. Send auto so the model decides which tool to call and when. If a specific tool must be called at a given step, use {"type": "function", "function": {"name": "get_weather"}} on that request and go back to auto on the next turn.
Both models reason before answering by default; the reasoning text arrives in reasoning_content and reasoning tokens count toward max_tokens. A short max_tokens can cut the answer off before reasoning ends and return finish_reason length, which is why max_tokens is 8192 in the example. On intermediate steps that only pass a tool result to the next call, if you do not need reasoning, add :fast to the model id or send "reasoning": false in the request; in Python that is extra_body={"reasoning": False}. :think or "reasoning": true keeps reasoning on; reasoning_effort returns 400 on these rows.
Streaming is supported: with stream: true the response arrives in chunks and tool call arguments stream inside delta.tool_calls. If you send include_usage: true in stream_options, the last chunk carries usage. Join all argument chunks of a call before you run it.
| Property | Agents-A1 | What you do in the loop |
|---|---|---|
| tool_choice | auto or a named function | start with auto; force a specific tool with a named function on that request |
| Context window | 262,144 tokens | the history is sent again every turn; shorten long tool results |
| Input | text and images (URL or base64) | attach a screenshot as a base64 data URL |
| JSON | json_object and json_schema | bind the final answer to a schema with response_format |
| Reasoning | on by default; off with "reasoning": false or :fast | give max_tokens generous room; you can use :fast on intermediate steps |
| Usage | free until 31 October 2026, 23:59 (TRT); 500,000 tokens a day on each model separately, new accounts start with a smaller allowance on their first day | when the allowance runs out you get 429; retry according to the Retry-After header |
Which one when?
The choices below follow from the model cards and from the tool_choice forms on LLMTR; compare output quality with your own tools.
- A general agent loop in which the model picks the tool itself: Agents-A1, with tool_choice auto.
- A step at which a specific tool must be called: Agents-A1 or Sage-A1.5, with a named function on that request.
- A long-horizon scientific task that hands work to subagents: Sage-A1.5, with the same loop and the spawn_subagent tool; its model card says subagent orchestration is enabled by defining this tool.
- Tool results that carry sensitive, personal or customer data: do not use these two rows, which are free until 31 October 2026; prompts and completions may be logged by the provider and used to improve the model.
Daily allowance and after 1 November 2026
Agents-A1 and Sage-A1.5 are free until 31 October 2026, 23:59 (TRT) and take nothing from your balance. Each account can use 500,000 tokens a day on each model separately; input and output are counted as one total, reasoning tokens included, the allowance renews at 00:00 (TRT), and new accounts start with a smaller allowance on their first day. In an agent loop the input tokens grow step by step. When the allowance runs out the request returns 429 rate_limit_error and the Retry-After header tells you when to try again; if the prompt is larger than what is left, the error tells you to send a shorter request.
From 1 November 2026, 00:00 (TRT), both ids return 410 model_retired and requests are not redirected to another model; you have to change the model id. To continue on a paid model, use Qwen3.6 35B-A3B (qwen/qwen3.6-35b-a3b): it is in the same size class and costs $0.07 input and $0.70 output per 1M tokens. Its card on LLMTR says the tool_choice forms that force a tool call (required or a named function) are not supported; send auto there and say in the prompt when the tool should be used. To continue free after 31 October 2026, use the daily-quota row of Nemotron 3.5 Super VL (nvidia/nemotron-3.5-super-vl-free); send images to it as a base64 data URL.
Building a tool loop on Agents-A1
Define a tool for Agents-A1, run the returned calls and send the results back, and stop the loop with a step limit.
- Tool definition. Add the function with its JSON Schema parameters to the tools list and send auto as tool_choice.
- Run and return. Run every tool_calls item and put the result, via json.dumps, into a role: "tool" message with the same tool_call_id.
- Limit. Cap the loop at five steps; if finish_reason comes back as length, raise max_tokens.
Frequently asked questions
Can tool_choice required be used on Agents-A1?
The LLMTR model card tells you to use auto or a named function; do not send required. To force a specific tool, use {"type": "function", "function": {"name": "..."}}.
What should I do with parallel tool calls?
Run every tool_calls item in the response and append a separate role: "tool" message for each with the same tool_call_id. If one is missing, the next request returns an error.
How does reasoning affect the agent loop?
Reasoning tokens count toward max_tokens and toward the daily allowance. Give max_tokens generous room; on intermediate steps that do not need reasoning, add :fast to the model id or send "reasoning": false.