Skip to main content

OpenAI Agents SDK Tool Calling

Tool calling is the mechanism that transforms an AI agent from a passive knowledge engine into an action‑taking system. In the OpenAI Agents SDK, agents can invoke external functions—search the web, query databases, call APIs, execute business logic—to accomplish real‑world tasks. This article is your implementation‑focused guide: you’ll learn how tools are defined, how the model selects and invokes them, how the runner manages the entire lifecycle, and how to build robust, production‑ready tool workflows.


What Is Tool Calling in the OpenAI Agents SDK

Tool calling is the SDK’s native support for function execution by an agent. Instead of returning a plain text answer, the agent can emit a structured request to call a tool, the SDK executes the tool, and the result is fed back into the conversation so the agent can continue reasoning.

The flow is:

Reasoning → Tool Selection → Tool Execution → Result Processing

Tools can be anything from a simple Python function to a secure HTTP API wrapper. The model decides when and which tool to use based on the tool’s description and the current conversation context.

Example:

from agents import Agent, Runner, function_tool

@function_tool
def get_current_time() -> str:
"""Return the current server time."""
from datetime import datetime
return datetime.now().isoformat()

agent = Agent(
name="Timekeeper",
instructions="You can tell the current time using the get_current_time tool.",
model="gpt-4o",
tools=[get_current_time]
)

result = await Runner.run(agent, "What time is it?")
print(result.final_output) # "The current time is 2026-06-11T14:22:01"

Behind the scenes, the model requested a tool call; the SDK executed get_current_time; the agent then produced a natural‑language answer using the result.


Why Tool Calling Matters

Without tools, an agent is limited to the knowledge stored in its model parameters. Tool calling enables:

  • Real‑world actions – send emails, create tickets, write to databases.
  • Access to live data – current stock prices, weather, breaking news.
  • Workflow automation – trigger downstream processes automatically.
  • Grounded responses – base answers on real, verifiable data.
  • Extended capabilities – perform calculations, run code, transform data.

In production, agents that cannot take action are rarely useful. Tool calling is the bridge between conversational AI and business automation.


How Tool Calling Works

The SDK’s Runner handles the entire tool‑calling loop transparently:

  1. The user sends a request.
  2. The Runner provides the agent with the conversation history and the list of available tool schemas.
  3. The model returns a response. If it contains tool_calls, the Runner pauses LLM reasoning and invokes the specified tools.
  4. Tool outputs are injected as new messages (tool role) into the conversation.
  5. The model continues, possibly making more tool calls, until it produces a final text answer.
  6. The Runner returns the final output, updates the session, and records a trace.

You never write a loop; the Runner manages this cycle automatically.


Core Tool Calling Concepts

ConceptPurpose
ToolA callable function exposed to the agent.
Tool SchemaThe definition of the tool’s name, description, and input parameters (JSON Schema).
ArgumentsThe structured JSON that the model generates to invoke a tool.
Tool ResultThe output returned by the tool function, serialized as a string.
Tool Response ProcessingHow the SDK integrates the tool result into the conversation (append message).

These concepts work together to form a robust, model‑agnostic execution pipeline.


Tool Definition

A tool is any Python function (sync or async) decorated with @function_tool. The decorator automatically extracts the function’s name, docstring, and parameter annotations to build the tool schema.

What Is a Tool

A tool must:

  • Have a descriptive name (by default, the function name).
  • Include a meaningful docstring – this is the primary signal the model uses to decide when to call it.
  • Accept typed parameters (Pydantic models or simple types) that define the input schema.
  • Return a string (the result is passed back to the model as text).

Example of a well‑defined tool:

from agents import function_tool
from pydantic import BaseModel, Field

class SearchParams(BaseModel):
query: str = Field(description="Search keywords")
max_results: int = Field(default=5, description="Maximum number of results")

@function_tool
def web_search(query: str, max_results: int = 5) -> str:
"""Search the web for relevant pages. Returns a list of snippets."""
# Connect to a search API
results = fake_search_api(query, max_results)
return "\n".join(results)

The description in the Field and the function docstring are crucial. The model uses them to understand what the tool does and how to fill its parameters.

Built‑in Hosted Tools

The SDK also provides pre‑built host tools that run in a secure environment:

  • WebSearchTool – search the web.
  • FileSearchTool – search a vector store for documents.
  • CodeInterpreterTool – execute Python code in a sandbox.

These require additional configuration (e.g., vector store IDs) and may incur separate costs. They are defined as tool objects that you attach to the agent just like function tools.

from agents import WebSearchTool

agent = Agent(
name="Researcher",
instructions="Search the web for information.",
tools=[WebSearchTool()]
)

Tool Schemas

Why Schemas Matter

The tool schema is the contract between the model and the tool. It describes:

  • The tool’s name – a unique identifier.
  • A natural language description – what the tool does.
  • The input parameters – their names, types, descriptions, and which are required.

The SDK automatically generates the schema from the function signature and the Pydantic model (if provided). The schema is included in the system message or as a tool definition when calling the model. Without a well‑crafted schema, the model may call the tool incorrectly or not at all.

Key practices:

  • Use Pydantic BaseModel for complex parameter validation.
  • Add Field(description=...) to every parameter.
  • The docstring should state what the tool returns, not just what it does.

Tool Selection Process

When the agent receives a new message, the model evaluates the entire conversation and the list of available tool schemas to decide:

  • Whether a tool is needed – If the answer can be generated from internal knowledge, the model may not call any tool.
  • Which tool to use – It matches the user’s intent against the tool descriptions. Clear, distinct descriptions reduce ambiguity.
  • When multiple tools are appropriate – The model can call a single tool, multiple tools in parallel (if parallel_tool_calls is enabled), or call tools sequentially (one after another, using the result of the first to decide the next).

Example: A user says “Find the capital of France and then tell me the weather there.” The model might call get_capital and get_weather in parallel (if both are stateless), or call get_capital first and then get_weather with the capital as an argument.

The Runner handles both scenarios automatically.


Argument Generation

Once the model selects a tool, it generates a JSON object that matches the tool’s parameter schema. This JSON includes the argument names and values. For example, for web_search(query="AI agents", max_results=3), the generated arguments would be:

{"query": "AI agents", "max_results": 3}

The SDK then parses this JSON and calls the tool with the corresponding keyword arguments.

If the generated arguments are invalid (e.g., missing a required field, wrong type), the SDK will raise an error. You can catch these errors and feed them back as a failure message, allowing the model to retry with corrected arguments. This self‑correction loop is a natural extension of the tool‑calling pattern.


Tool Execution Lifecycle

Let’s walk through a complete cycle step by step.

  1. User request – The user sends a message.
  2. Agent reasoning – The model receives the system instructions, conversation history, and tool schemas.
  3. Tool selection – The model decides to call a tool (or not).
  4. Argument creation – It generates the input parameters for that tool.
  5. Tool execution – The Runner invokes the tool function with those arguments.
  6. Result retrieval – The tool’s return value is captured.
  7. Result injection – The result is appended as a message with role tool and the tool call ID.
  8. Continued reasoning – The model sees the new tool message and may make another tool call or produce a final answer.
  9. Final response generation – The agent returns the user‑facing text.

This lifecycle repeats (with a limit) until the agent decides the task is complete or max_turns is reached.


Tool Result Processing

The tool result is returned as a string. If the tool returns a non‑string object, the SDK calls str() on it. To pass structured data, return a JSON‑formatted string or use json.dumps.

Inside the agent, the result becomes a ToolMessage (internally). The agent can use the result to:

  • Directly answer the user.
  • Call another tool with derived data.
  • Perform validation or summarization.

If the tool raises an exception, the Runner catches it and converts it into an error message that is injected into the conversation. The model can then try a different approach or ask the user for help.

Example of handling a tool error in the tool itself:

@function_tool
def safe_api_call(url: str) -> str:
try:
response = requests.get(url, timeout=5)
response.raise_for_status()
return response.text[:1000]
except Exception as e:
return f"Error calling {url}: {str(e)}"

This approach lets the model self‑correct without crashing the entire run.


Single Tool vs Multiple Tool Workflows

Workflow TypeDescriptionWhen to use
Single Tool FlowThe agent calls one tool and answers the user.Simple lookups, weather, time.
Sequential Tool ChainThe agent calls Tool A, uses its output to call Tool B, and then answers.Multi‑step data retrieval (e.g., find capital → get weather).
Parallel Tool CallsThe agent calls multiple independent tools simultaneously.Fetching data from multiple unrelated sources.

The SDK supports all three patterns. For parallel calls, the model generates multiple tool_calls in a single response; the Runner executes them concurrently and merges the results before the next LLM call.


Tool Calling Patterns

Common reusable patterns:

PatternDescriptionExample
LookupRetrieve a single value by key.get_order_status(order_id)
RetrievalSearch and return multiple items.search_knowledge_base(query)
ActionPerform a side effect (create, send, delete).send_email(to, subject, body)
ValidationCheck a condition and return a boolean/status.is_valid_promo(code)
TransformationConvert data from one format to another.convert_currency(amount, from, to)

Each tool should be atomic and idempotent where possible (especially for actions).


MCP and Tool Calling

The Model Context Protocol (MCP) is an open standard for exposing tools. The OpenAI Agents SDK can integrate with MCP servers via custom tool adapters. An MCP server exposes a set of tools with standardised schemas; you can load these tools as native SDK tools.

FeatureNative SDK ToolsMCP Tools
DefinitionPython @function_toolDefined on remote server (JSON schema)
ExecutionLocal Python processRemote process (via MCP client)
ReusabilityWithin the same codebaseAcross any agent/framework
DependenciesSame environment as agentIsolated environment
SetupMinimalRequires running an MCP server

Using MCP tools, you can give agents access to a vast ecosystem of pre‑built, language‑agnostic tools without writing any Python wrappers. For details, see the MCP Guide.


Error Handling

Robust tool calling requires anticipating failures.

  • Invalid arguments – The model might produce arguments that don’t match the schema. The SDK will raise a ModelBehaviorError. Implement a retry or feed the error back to the model.
  • Tool failure – The tool function itself might crash. Always wrap the tool’s body in try/except and return a descriptive error string.
  • Timeout handling – Use asyncio.wait_for or set timeouts on HTTP calls inside the tool. Return a timeout message.
  • Retry strategies – For transient errors, implement retries inside the tool function using tenacity. The Runner does not automatically retry failed tool calls, but you can configure the model to re‑call a tool by feeding it the error message.
  • Fallback tools – If a primary tool fails repeatedly, design the agent to call a simpler fallback tool or provide a cached response.

Example of a retry‑enabled tool:

from tenacity import retry, stop_after_attempt, wait_exponential

@function_tool
@retry(stop=stop_after_attempt(3), wait=wait_exponential(multiplier=1, min=2, max=10))
def fetch_stock_price(ticker: str) -> str:
resp = requests.get(f"https://api.stocks.example/{ticker}", timeout=5)
resp.raise_for_status()
return resp.text

Security Considerations

Tools can perform harmful actions. Always apply these practices:

  • Input validation – Use Pydantic models with strict constraints (max_length, regex). Reject suspicious inputs.
  • Output validation – Sanitize tool outputs before sending to the user or logging. Remove PII.
  • Access control – Attach only necessary tools to each agent. For sensitive operations, use a separate agent that is only accessible after guardrails.
  • Secrets protection – Use environment variables or a secrets manager for API keys. Never hard‑code credentials.
  • Safe tool execution – If a tool runs code or shell commands, sandbox it (use CodeInterpreterTool which runs in a secure environment, or your own Docker sandbox). Avoid exposing os.system or equivalent.

Performance Optimization

  • Tool latency – Keep tool functions fast. Use async functions for I/O. Cache results when appropriate.
  • Caching – For deterministic tools (e.g., get_weather(city)), cache results in Redis or an in‑memory cache with a TTL.
  • Rate limits – Respect external API rate limits. Implement queuing or back‑off inside tools.
  • Batching – If the model calls multiple tools in parallel, the Runner executes them concurrently, reducing total wait time.
  • Reducing unnecessary calls – Write clear instructions that encourage the model to use tools only when needed. Fine‑tune the max_turns limit to prevent excessive loops.

Common Beginner Mistakes

  1. Overusing tools – adding dozens of tools with overlapping descriptions confuses the model. Start with a minimal set.
  2. Poor tool descriptions – vague or absent docstrings lead to incorrect tool selection.
  3. Weak schemas – not using Pydantic models or Field descriptions results in malformed arguments.
  4. Missing validation – accepting any input without validation exposes vulnerabilities and causes runtime errors.
  5. Large tool sets – without clear differentiation, the model might always call the wrong tool.
  6. Ignoring tool errors – letting exceptions propagate uncaught crashes the agent run.
  7. Not handling tool output – expecting the model to perfectly parse unstructured tool output; use structured formats (JSON) if possible.

Best Practices

  • Keep tools atomic – one tool, one action. Combine them via multi‑turn reasoning.
  • Define clear schemas – use args_schema (via Pydantic model or parameter annotations) and detailed docstrings.
  • Validate inputs – reject invalid input inside the tool and return a helpful error message.
  • Handle failures gracefully – never let a tool raise an unhandled exception.
  • Monitor tool performance – log every invocation: name, arguments (sanitised), duration, result length. Use tracing.
  • Use structured outputs – return JSON strings or consistent formats so the model can parse them easily.
  • Test tools independently – before integrating, verify the tool’s behaviour and error handling.

Practical Example: Research Agent with Tool Calling

We’ll build an agent that researches a topic using a web search tool, retrieves full documents from URLs, and then summarises the findings.

1. Define the tools

from agents import function_tool
import requests, json

@function_tool
def web_search(query: str) -> str:
"""Search the web and return a JSON list of {title, url, snippet}."""
# In production, use a real search API (Tavily, SerpAPI).
# Simulated result:
results = [
{"title": "AI Agents in 2026", "url": "https://example.com/ai", "snippet": "..."},
{"title": "Future of Agents", "url": "https://example.com/future", "snippet": "..."}
]
return json.dumps(results)

@function_tool
def fetch_page(url: str) -> str:
"""Fetch the text content of a web page. Returns plain text."""
try:
resp = requests.get(url, timeout=10, headers={"User-Agent": "ResearchBot"})
resp.raise_for_status()
# Basic text extraction (simulated)
text = resp.text[:3000]
return text
except Exception as e:
return f"Error fetching {url}: {str(e)}"

@function_tool
def summarize_text(text: str, max_length: int = 200) -> str:
"""Summarize a long text into a concise paragraph. max_length is word count."""
# This is a placeholder; in a real agent you'd use another model call or a dedicated tool.
# The agent can also do summarization by reasoning, but we provide a tool for completeness.
words = text.split()
if len(words) <= max_length:
return text
return " ".join(words[:max_length]) + "..."

2. Create the research agent

from agents import Agent, Runner

researcher = Agent(
name="ResearchAssistant",
instructions=(
"You are a helpful research assistant. "
"1. When asked to research a topic, first use web_search to find relevant pages. "
"2. Then use fetch_page to retrieve the full content of the most promising URLs. "
"3. Finally, use summarize_text (or your own reasoning) to provide a concise summary. "
"Always cite your sources."
),
model="gpt-4o",
tools=[web_search, fetch_page, summarize_text]
)

3. Run the agent

import asyncio

async def main():
config = {"max_turns": 10} # prevent runaway loops
result = await Runner.run(researcher, "Tell me about the latest trends in AI agents.", run_config=config)
print(result.final_output)

asyncio.run(main())

Execution flow:

  1. The user asks about “latest trends in AI agents”.
  2. The model decides to call web_search("latest trends in AI agents").
  3. The Runner executes the search tool; the result (a JSON string of snippets) is injected into the conversation.
  4. The agent reads the search results and decides to fetch one or more pages by calling fetch_page(url=...). It may call multiple URLs in parallel if the model generates multiple tool calls.
  5. Once pages are fetched, the agent either calls summarize_text on the combined content or directly produces a summary using its own LLM reasoning.
  6. The final answer includes a concise summary with citations.

This pipeline demonstrates a sequential tool chain pattern where the output of one tool influences the next. The entire process is traceable via the SDK’s tracing feature.


Tool Calling and Other SDK Concepts

Tool calling is the core action mechanism; it integrates deeply with other SDK features:

  • Core Concepts – Agents, instructions, and runners form the context in which tools operate.
  • Handoffs – A handoff can be triggered by a tool result; for example, a triage agent calls a classification tool and then hands off to a specialist based on the result.
  • Guardrails – Use input guardrails to validate tool‑call arguments before execution; output guardrails to verify tool outputs.
  • Observability – Every tool call is traced automatically, including arguments, result, and timing.

For cross‑framework tool usage, see the LangGraph Tool Calling guide, CrewAI Tools & Delegation, and the framework comparison. For external tool ecosystems, explore the MCP Guide.


FAQ

1. What is Tool Calling in the OpenAI Agents SDK?

It’s the mechanism that allows an agent to invoke external functions (APIs, databases, custom code) during a conversation. The model decides when to call a tool, and the SDK executes it and integrates the result.

2. How does Tool Calling work?

The SDK includes the tool schemas in the LLM request. When the LLM returns a tool call, the Runner invokes the tool, inserts the output as a message, and continues the conversation.

3. How are tools selected?

The model uses the tool’s name and description (from the docstring and parameter descriptions) to match the user’s intent with the appropriate tool.

4. Can multiple tools be used in a single turn?

Yes, the model can request multiple tool calls in a single response. The Runner executes them in parallel and merges results.

5. What are tool schemas?

The JSON‑Schema representation of a tool’s parameters (name, type, description). They are generated automatically from the function signature and any Pydantic model.

6. How do MCP tools differ from native tools?

MCP tools are defined on a remote server and accessed via a standard protocol. Native tools are Python functions defined locally. Both can be used in the agent interchangeably.

7. Is Tool Calling production‑ready?

Yes. The SDK includes tracing, error handling, parallel execution, and guardrails that make tool workflows robust for production.

8. How do I handle tool errors?

Wrap the tool body in try/except and return an error message. The model will receive that message and can attempt to correct the problem.

9. What is the maximum number of tools an agent can have?

There’s no hard limit, but for reliability, keep the number under 10–15 per agent. More tools increase the risk of the model calling the wrong one.

10. How do I prevent the agent from calling a tool endlessly?

Set max_turns in the RunConfig. The Runner will stop the loop after that many turns.

11. Can tools be async?

Yes, define the tool function with async def. The Runner will await it correctly.

12. How do I pass complex data to a tool?

Use a Pydantic model as the function’s input type. The model will generate JSON conforming to that model.

13. Can I use tools without the OpenAI model?

The SDK is designed for OpenAI models. For other providers, use frameworks like LangGraph or AutoGen.

14. How do I debug tool calls?

Enable tracing (set set_tracing_export_api_key). The trace will show every tool invocation with inputs and outputs. Add logging within the tool functions.

15. Is there built‑in support for MCP tools?

The SDK does not have a native MCP client, but you can create a custom tool adapter that calls an MCP server. The MCP Guide explains how.

16. How do I limit the size of tool outputs?

Return truncated strings (e.g., first 2000 characters) or a summary. The model handles large contexts up to its limit, but smaller outputs improve latency.


Conclusion

The OpenAI Agents SDK’s tool‑calling system gives you a straightforward yet powerful way to build agents that can interact with the real world. By wrapping functions with @function_tool and attaching them to agents, you let the model decide when and how to use them, while the Runner manages the execution lifecycle—including parallel calls, error injection, and tracing.

Key takeaways:

  • Tools are Python functions with clear descriptions and typed inputs.
  • The Runner loop automatically handles tool selection, invocation, and result integration.
  • Schemas are crucial—the quality of the tool description directly affects model accuracy.
  • Error handling, validation, and security must be implemented in the tool itself.
  • MCP offers a standardised, scalable way to provide tools across agents.

Continue your journey with these deep dives:

For tool ecosystems and cross‑framework patterns, see the MCP Guide, the LangGraph Tool Calling guide, and the framework comparison. Now, equip your agents with the right tools—and let them work.