---
title: "Building Custom Function Tools with @function_tool Decorator"
description: "Master the @function_tool decorator in the OpenAI Agents SDK. Learn how to create sync and async tools, handle complex parameters, and wire multiple custom tools into your agents."
canonical: https://callsphere.ai/blog/building-custom-function-tools-function-tool-decorator-openai-agents
category: "Learn Agentic AI"
tags: ["OpenAI", "Function Tools", "Decorator", "Python", "Tutorial"]
author: "CallSphere Team"
published: 2026-03-14T00:00:00.000Z
updated: 2026-05-06T21:26:37.424Z
---

# Building Custom Function Tools with @function_tool Decorator

> Master the @function_tool decorator in the OpenAI Agents SDK. Learn how to create sync and async tools, handle complex parameters, and wire multiple custom tools into your agents.

## Why Custom Function Tools?

Hosted tools cover common capabilities like web search and code execution, but real-world agents need to interact with **your** systems — databases, APIs, business logic, and external services. The `@function_tool` decorator lets you turn any Python function into a tool that an agent can call.

The SDK automatically generates the JSON schema for the tool from your function's type hints and docstring. The agent sees the tool's name, description, and parameter schema, then decides when and how to call it.

## Your First Function Tool

The simplest function tool is a decorated Python function with type hints:

```mermaid
flowchart LR
    INPUT(["User input"])
    AGENT["Agent
name plus instructions"]
    HAND{"Handoff to
another agent?"}
    SUB["Sub-agent
specialist"]
    GUARD{"Guardrail
passed?"}
    TOOL["Tool call"]
    SDK[("Tracing
OpenAI dashboard")]
    OUT(["Final output"])
    INPUT --> AGENT --> HAND
    HAND -->|Yes| SUB --> GUARD
    HAND -->|No| GUARD
    GUARD -->|Yes| TOOL --> AGENT
    GUARD -->|Block| OUT
    AGENT --> OUT
    AGENT --> SDK
    style AGENT fill:#4f46e5,stroke:#4338ca,color:#fff
    style GUARD fill:#f59e0b,stroke:#d97706,color:#1f2937
    style SDK fill:#ede9fe,stroke:#7c3aed,color:#1e1b4b
    style OUT fill:#059669,stroke:#047857,color:#fff
```

```python
from agents import Agent, Runner, function_tool

@function_tool
def get_weather(city: str) -> str:
    """Get the current weather for a given city."""
    # In production, call a real weather API here
    return f"The weather in {city} is 72F and sunny."

agent = Agent(
    name="Weather Agent",
    instructions="You help users check the weather. Use the get_weather tool when asked about weather conditions.",
    tools=[get_weather],
)

result = Runner.run_sync(agent, "What's the weather like in Tokyo?")
print(result.final_output)
```

The decorator reads the function name (`get_weather`), the docstring (used as the tool description), and the parameter types to build the tool schema automatically.

## Async Function Tools

For tools that call external APIs or databases, use async functions to avoid blocking the event loop:

```python
import httpx
from agents import function_tool

@function_tool
async def fetch_stock_price(symbol: str) -> str:
    """Fetch the latest stock price for a given ticker symbol."""
    async with httpx.AsyncClient() as client:
        response = await client.get(
            f"https://api.example.com/stocks/{symbol}"
        )
        data = response.json()
        return f"{symbol}: ${data['price']:.2f}"
```

The SDK handles both sync and async tools seamlessly. Async tools are awaited during the agent loop, while sync tools are run in a thread pool so they do not block.

## Complex Parameters with Pydantic Models

For tools with structured inputs, use Pydantic models to define complex parameter schemas:

```python
from pydantic import BaseModel, Field
from agents import function_tool

class FlightSearch(BaseModel):
    origin: str = Field(description="Departure airport code (e.g., SFO)")
    destination: str = Field(description="Arrival airport code (e.g., NRT)")
    date: str = Field(description="Travel date in YYYY-MM-DD format")
    max_stops: int = Field(default=1, description="Maximum number of stops")

@function_tool
def search_flights(params: FlightSearch) -> str:
    """Search for available flights between two airports."""
    return f"Found 3 flights from {params.origin} to {params.destination} on {params.date} with up to {params.max_stops} stop(s)."
```

The Pydantic model's field descriptions become part of the JSON schema the agent sees, helping the model fill in parameters correctly.

## Customizing Tool Name and Description

You can override the auto-generated name and description:

```python
@function_tool(
    name_override="lookup_customer",
    description_override="Search for a customer by email address or customer ID. Returns the customer's name, plan, and account status.",
)
def find_customer(identifier: str) -> str:
    """Internal: look up customer record."""
    # The description_override is what the agent sees,
    # not this docstring
    return f"Customer {identifier}: Pro plan, active"
```

This is useful when your internal function name doesn't match what you want the agent to see, or when you need a more detailed description than the docstring provides.

## Wiring Multiple Tools Into an Agent

Agents become truly useful when they have access to several tools. The model chooses which tool to call based on the user's request:

```python
from agents import Agent, Runner, function_tool

@function_tool
def create_ticket(title: str, priority: str, description: str) -> str:
    """Create a support ticket in the ticketing system."""
    return f"Ticket created: '{title}' with {priority} priority."

@function_tool
def list_open_tickets(customer_id: str) -> str:
    """List all open support tickets for a customer."""
    return f"Customer {customer_id} has 3 open tickets."

@function_tool
def escalate_ticket(ticket_id: str, reason: str) -> str:
    """Escalate a support ticket to a senior agent."""
    return f"Ticket {ticket_id} escalated. Reason: {reason}"

agent = Agent(
    name="Support Agent",
    instructions="You are a customer support agent. Help users manage their support tickets. Use the appropriate tool for each request.",
    tools=[create_ticket, list_open_tickets, escalate_ticket],
)

result = Runner.run_sync(agent, "Create a high-priority ticket about a billing error on my last invoice.")
print(result.final_output)
```

## Accessing RunContext in Tools

Sometimes your tools need access to shared state — a database connection, the current user ID, or configuration. The SDK passes a `RunContextWrapper` as the first argument if your function accepts it:

```python
from dataclasses import dataclass
from agents import Agent, Runner, RunContextWrapper, function_tool

@dataclass
class AppContext:
    user_id: str
    db_connection: object  # your DB connection

@function_tool
async def get_user_orders(ctx: RunContextWrapper[AppContext]) -> str:
    """Retrieve the current user's recent orders."""
    user_id = ctx.context.user_id
    # Use ctx.context.db_connection to query the database
    return f"User {user_id} has 5 recent orders."

agent = Agent(
    name="Order Agent",
    instructions="You help users check their order history.",
    tools=[get_user_orders],
)

app_ctx = AppContext(user_id="user_123", db_connection=None)
result = Runner.run_sync(agent, "Show me my recent orders.", context=app_ctx)
print(result.final_output)
```

The `RunContextWrapper` is typed generically, so you get full IDE autocompletion and type checking on your context object. The context is passed once when you call `Runner.run_sync()` and is available to every tool call during that run.

## Key Takeaways

- Use `@function_tool` to turn any Python function into an agent tool
- Add type hints and docstrings — the SDK auto-generates the JSON schema
- Use Pydantic models for complex parameter structures
- Access shared state via `RunContextWrapper`
- Combine multiple tools to build capable, domain-specific agents

---

Source: https://callsphere.ai/blog/building-custom-function-tools-function-tool-decorator-openai-agents
