Skip to content

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.

  • Python with the OpenAI SDK: pip install openai
  • An API key exported as MIRAPI_API_KEY (see Authentication)
  • A model from GET /v1/models whose supported_parameters list tool calling — check the model page in the console catalogue or the Models & pricing page
import json
import os
from 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 tool
response = client.chat.completions.create(
model="deepseek-chat",
messages=messages,
tools=TOOLS,
)
msg = response.choices[0].message
messages.append(msg)
# 2. Execute every requested call
for 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 context
final = client.chat.completions.create(
model="deepseek-chat",
messages=messages,
tools=TOOLS,
)
print(final.choices[0].message.content)
  1. Define a tool schema in tools and implement the matching function locally.
  2. Read message.tool_calls — each entry has id, function.name, and function.arguments (a JSON string).
  3. Execute the local function and append a role: "tool" message whose tool_call_id matches the request.
  4. Repeat until the model returns a message with no tool_calls; that content is 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.

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.

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.

  • 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 tools is missing from a request. Verify the model’s supported_parameters and send tools on every turn.
  • Broken JSON in function.arguments: parse it inside try/except and, 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-After header 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.