Skip to content
Get Started

Agent API

Function Calling

Built-in server tools + your own client function tools on the Agent API, with both handoff continuation styles.

The Agent API runs two kinds of tools:

  1. Built-in server tools — the platform’s health-data tools. They run server-side; you never execute them. Their trace is reported, not delegated.
  2. Client function tools — tools you declare on the request. When the model wants one, the response hands off with a function_call output item; you execute it and continue the run.

The agent always has its platform toolset over the Subject’s data (same catalog as the Answers API):

  • query_health_data — search and aggregate the Subject’s records
  • list_family_members — resolve care-circle members the Subject is allowed to query
  • search_medical_evidence — search literature, guidelines/consensus, and registered trials (feeds citations)
  • read_source — read one search result by ref; arbitrary URL fetching is not supported

The /v1 agent’s domain tools are read-only. It also runs internal planning and analysis tools. Every server-tool run lands in the response object’s top-level tool_steps extension — never as an output item, which official SDKs would mis-parse — and in streaming as the response.mirobody_tool_call side-channel event.

{
"model": "mirobody-flash",
"input": "Check my recent glucose and book a follow-up if it is trending up.",
"user": "alice",
"tools": [
{
"type": "function",
"name": "book_appointment",
"description": "Book a clinic appointment for the end user.",
"parameters": {
"type": "object",
"properties": { "date": { "type": "string", "description": "ISO date" } },
"required": ["date"]
}
}
]
}

Rules (violations are explicit 400s, never silently dropped):

RuleDetail
Type"type": "function" (client handoff) and "type": "mcp" (server-side remote tools) are supported. Both the flat Responses form and the completions-nested {"type":"function","function":{...}} form are accepted. {"type": "mcp"} attaches your remote MCP servers (server-side execution, no handoff) — see MCP servers.
CountMax 64 tools per request.
NamesMust match [a-zA-Z0-9_-]{1,64}; must be unique; must not shadow a built-in tool name (query_health_data, read_file, task, …).
parametersA JSON Schema object (defaults to {"type":"object","properties":{}}).
tool_choice"auto" (default), "none" (disables all tools for the turn, built-ins included — see backbone mode), or a named client tool. Anything else → 400 unsupported_parameter.

When the model calls your tool, the response completes with a function_call output item (status: "completed" — the response is done; the conversation is waiting on you):

{
"id": "resp_abc...",
"object": "response",
"status": "completed",
"output": [
{ "type": "function_call", "id": "fc_0", "call_id": "call_9f2...",
"status": "completed", "name": "book_appointment",
"arguments": "{\"date\": \"2026-07-14\"}" }
],
"output_text": "",
...
}

In streaming, the same handoff arrives as a response.output_item.addedresponse.function_call_arguments.delta / .doneresponse.output_item.done event group (Streaming).

Execute the tool, then continue the run in either of two ways:

Path 1 — stateful resume (previous_response_id)

Section titled “Path 1 — stateful resume (previous_response_id)”

Send only function_call_output items, referencing the handoff response. The server resumes the paused agent thread — no history resend:

Terminal window
curl https://api.mirobody.ai/v1/responses \
-H "Authorization: Bearer $MIROBODY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "mirobody-flash",
"previous_response_id": "resp_abc...",
"input": [
{ "type": "function_call_output", "call_id": "call_9f2...",
"output": "Booked: Mon 2026-07-14 09:30, Dr. Chen" }
],
"user": "alice"
}'

Requirements: outputs must cover exactly the pending call_ids (parallel calls → one function_call_output each); you may not mix message items into a resume; a handoff can be resumed once (a duplicate resume fails loudly). The handoff response must have been stored (store=true, the default).

What openai-agents does by default: resend the entire item transcript in input — including the function_call / function_call_output pairs — with no previous_response_id:

{
"model": "mirobody-flash",
"input": [
{ "role": "user", "content": "Check my recent glucose and book a follow-up if it is trending up." },
{ "type": "function_call", "call_id": "call_9f2...", "name": "book_appointment",
"arguments": "{\"date\": \"2026-07-14\"}" },
{ "type": "function_call_output", "call_id": "call_9f2...",
"output": "Booked: Mon 2026-07-14 09:30, Dr. Chen" }
],
"tools": [ ... ],
"user": "alice"
}

The pairs are reconstructed as conversation history and the run continues as a fresh turn. Works with store=false end to end.

The SDK handles the whole loop — declaration, handoff, execution, replay:

from agents import Agent, ModelSettings, Runner, function_tool, set_default_openai_client, set_tracing_disabled
from openai import AsyncOpenAI
set_default_openai_client(AsyncOpenAI(
base_url="https://api.mirobody.ai/v1", api_key="mb_live_..."))
set_tracing_disabled(True)
@function_tool
def book_appointment(date: str) -> str:
"""Book a clinic appointment for the end user (ISO date)."""
return f"Booked: {date} 09:30, Dr. Chen"
agent = Agent(
name="Health assistant",
model="mirobody-flash",
instructions="Check real health data before acting.",
tools=[book_appointment],
model_settings=ModelSettings(extra_body={"user": "alice"}), # tenant isolation — REQUIRED
)
result = Runner.run_sync(agent, "Check my recent glucose and book a follow-up if it's trending up.")
print(result.final_output)

The model reads the Subject’s real glucose data with built-in server tools, then hands off to your book_appointment — the SDK executes it locally and replays the transcript automatically.

HTTP 400 messageCause
previous response has no pending function callsResuming a response that wasn’t a handoff (or was already resumed).
cannot mix message items with function_call_output when resuming via previous_response_idA resume must contain only tool outputs.
unknown call_id(s): [...] / missing function_call_output for call_id(s): [...]Outputs must match the pending calls exactly.
previous response has pending function call(s) — provide function_call_output items for: ...Continuing a handoff conversation without supplying the outputs.
function_call_output without matching function_call items — replay the full transcript, or resume via previous_response_idA stateless replay must include the function_call items too.