Tool-calling agent
Tool calling lets a model ask your code to run a function and return the result, instead of answering from training data alone. This tutorial builds a minimal agent loop on MirAPI’s OpenAI-compatible endpoint at https://api.mirapi.ai/v1: you define a tool, the model requests it, your code executes it, and the result goes back to the model for a final answer.
Tool calling on MirAPI uses the standard OpenAI tools and tool_choice format, so the example below runs with the OpenAI SDK or any OpenAI-compatible client.
Prerequisites
Section titled “Prerequisites”- Python with the OpenAI SDK:
pip install openai - An API key exported as
MIRAPI_API_KEY(see Authentication) - A model from
GET /v1/modelswhosesupported_parameterslist tool calling — check the model page in the console catalogue or the Models & pricing page
The tool-calling loop
Section titled “The tool-calling loop”import jsonimport osfrom openai import OpenAI
client = OpenAI( base_url="https://api.mirapi.ai/v1", api_key=os.environ["MIRAPI_API_KEY"],)
TOOLS = [ { "type": "function", "function": { "name": "get_weather", "description": "Get the current weather for a city", "parameters": { "type": "object", "properties": { "city": {"type": "string", "description": "City name"} }, "required": ["city"], }, }, }]
def get_weather(city: str) -> str: # Replace with a real weather API in production. return json.dumps({"city": city, "temp_c": 24, "condition": "sunny"})
messages = [ {"role": "user", "content": "What is the weather in Shanghai? Use the tool."}]
# 1. Model decides to call the toolresponse = client.chat.completions.create( model="deepseek-chat", messages=messages, tools=TOOLS,)msg = response.choices[0].messagemessages.append(msg)
# 2. Execute every requested callfor call in msg.tool_calls or []: result = get_weather(json.loads(call.function.arguments)["city"]) messages.append( { "role": "tool", "tool_call_id": call.id, "content": result, } )
# 3. Model produces the final answer with the tool output in contextfinal = client.chat.completions.create( model="deepseek-chat", messages=messages, tools=TOOLS,)print(final.choices[0].message.content)How tool calls work
Section titled “How tool calls work”- Define a tool schema in
toolsand implement the matching function locally. - Read
message.tool_calls— each entry hasid,function.name, andfunction.arguments(a JSON string). - Execute the local function and append a
role: "tool"message whosetool_call_idmatches the request. - Repeat until the model returns a message with no
tool_calls; thatcontentis the final answer.
If the model requests several tools in one turn, execute all of them before calling the API again. The model does not run your function itself — it only returns the function name and arguments for your code to dispatch.
Tool schema
Section titled “Tool schema”The tools array uses the OpenAI function-calling shape:
| Field | Type | Purpose |
|---|---|---|
tools[].type |
string | Always "function" for function calling. |
tools[].function.name |
string | The identifier the model uses when it requests the tool. |
tools[].function.description |
string | When and how to use the tool; a clearer description improves call accuracy. |
tools[].function.parameters |
object | A JSON Schema describing the arguments the model may pass. |
Control tool choice
Section titled “Control tool choice”tool_choice overrides the default behaviour:
| Value | Effect |
|---|---|
"auto" (default) |
The model decides whether to call a tool. |
"none" |
Disables tool calls. |
"required" |
Forces the model to call a tool. |
{"type": "function", "function": {"name": "..."}} |
Forces a specific function. |
Include tools in every request in the loop, including the final call, so the model can keep calling tools when it needs to.
Error handling
Section titled “Error handling”- 403 with a valid key: the model is not on your key’s whitelist, or the balance is empty. Top up and retry — the same key works again immediately after a top-up.
- Model answers without calling the tool: the model may not support tool calling, or
toolsis missing from a request. Verify the model’ssupported_parametersand sendtoolson every turn. - Broken JSON in
function.arguments: parse it insidetry/exceptand, on failure, return the error text as the tool result so the model can correct itself. - 429 rate limit: retry with jittered exponential backoff (1s → 2s → 4s, capped around 30s); MirAPI does not send a
Retry-Afterheader and does not retry for you.
Every error message ends with a request ID — include it when you open a support ticket.
A tool-calling loop resends the tool schema and the full message history on every turn, so each extra turn adds input tokens. A short loop of a few turns is typically a few thousand tokens — well under a cent for most models, but check the pricing field in GET /v1/models for exact per-model rates.
Repeated prefixes in the history are eligible for prompt caching, which bills at a lower cached-read rate than normal input. Reasoning tokens produced between tool calls are billed as output.
Related links
Section titled “Related links”- Models & pricing — find models that support tool calling
- OpenAI compatibility — endpoint and request format
- Errors — status codes and retry guidance
- Prompt caching — lower cost for repeated context