# How to Create and Use FunctionTool with Custom Python Functions in openai-agents-python

> Learn to create and use FunctionTool with custom Python functions in openai-agents-python. Expose any Python function to an LLM using the @function_tool decorator.

- Repository: [OpenAI/openai-agents-python](https://github.com/openai/openai-agents-python)
- Tags: how-to-guide
- Published: 2026-04-17

---

**You can expose any Python function to an LLM as a tool by applying the `@function_tool` decorator from [`src/agents/tool.py`](https://github.com/openai/openai-agents-python/blob/main/src/agents/tool.py), which automatically generates JSON schemas and wraps your callable for asynchronous runtime execution.**

The `openai-agents-python` SDK provides a first-class mechanism for converting standard Python functions into LLM-callable tools through the `FunctionTool` abstraction. By using the built-in decorator or factory methods, the library handles schema generation, type validation, and runtime invocation automatically according to the OpenAI function calling specification.

## Understanding the FunctionTool Architecture

The transformation from user function to LLM tool follows a specific pipeline implemented across three core modules:

1. **Decoration Phase**: The `@function_tool` decorator in [`src/agents/tool.py`](https://github.com/openai/openai-agents-python/blob/main/src/agents/tool.py) (lines 1650–1690) inspects your callable's signature, docstring, and type hints
2. **Schema Generation**: The `function_schema()` utility in [`src/agents/function_schema.py`](https://github.com/openai/openai-agents-python/blob/main/src/agents/function_schema.py) (lines 44–78) constructs a Pydantic model and JSON schema for parameter validation
3. **Runtime Execution**: The tool execution engine in [`src/agents/run_internal/tool_execution.py`](https://github.com/openai/openai-agents-python/blob/main/src/agents/run_internal/tool_execution.py) matches LLM function calls to your `FunctionTool` instance and invokes `on_invoke_tool`

When decorated, your function becomes a `FunctionTool` instance (defined in [`src/agents/tool.py`](https://github.com/openai/openai-agents-python/blob/main/src/agents/tool.py) lines 81–120) containing fields for `name`, `description`, `params_json_schema`, `strict_json_schema`, and the async `on_invoke_tool` method that serves as the runtime entry point.

## Creating Basic Function Tools

### Simple Synchronous Function

The minimal setup requires only the `@function_tool` decorator and a descriptive docstring:

```python
from agents import function_tool, Agent

@function_tool
def greet() -> str:
    """Return a friendly greeting to the user."""
    return "👋 Hello from the function tool!"

# The decorator returns a FunctionTool instance, not a raw function

print(greet.name)  # "greet"

print(greet.params_json_schema)  # {"properties": {}, "type": "object"}

# Attach to an Agent

agent = Agent(tools=[greet])

```

The decorator implementation in [`src/agents/tool.py`](https://github.com/openai/openai-agents-python/blob/main/src/agents/tool.py) automatically derives the tool name from the function name and the description from the docstring using `generate_func_documentation` in [`src/agents/function_schema.py`](https://github.com/openai/openai-agents-python/blob/main/src/agents/function_schema.py).

### Tools with Typed Arguments

Add parameters with type hints to generate structured JSON schemas:

```python
import json
from agents import function_tool, ToolContext

@function_tool
def calculate_bmi(weight_kg: float, height_m: float) -> dict:
    """Calculate BMI given weight in kilograms and height in meters.
    
    Args:
        weight_kg: Weight in kilograms
        height_m: Height in meters
    """
    bmi = weight_kg / (height_m ** 2)
    return {"bmi": round(bmi, 2), "category": "normal" if 18.5 <= bmi < 25 else "other"}

# Simulate runtime invocation (as the agent would do)

ctx = ToolContext(context=None, tool_name="calculate_bmi", 
                  tool_call_id="call_123", tool_arguments="")
result = await calculate_bmi.on_invoke_tool(
    ctx, 
    json.dumps({"weight_kg": 70, "height_m": 1.75})
)

```

The `_on_invoke_tool_impl` method (lines 1802–1840 in [`src/agents/tool.py`](https://github.com/openai/openai-agents-python/blob/main/src/agents/tool.py)) handles parsing the JSON input, validating against the Pydantic model generated by `function_schema()`, and calling your function.

## Advanced FunctionTool Configuration

### Accessing ToolContext in Async Functions

When your tool needs access to runtime context or dependencies, accept `ToolContext` as the first parameter:

```python
import asyncio
from agents import function_tool, ToolContext

@function_tool
async def fetch_user_profile(ctx: ToolContext, user_id: str) -> dict:
    """Fetch user profile from database using the request-scoped context.
    
    The ToolContext provides access to per-run data and dependencies.
    """
    # Access context data if available

    db_client = ctx.context.get("db") if ctx.context else None
    
    await asyncio.sleep(0.1)  # Simulate async I/O

    return {"id": user_id, "name": "Alice", "cached": False}

# The decorator detects ToolContext automatically via takes_context property

assert fetch_user_profile.takes_context is True

```

The decorator inspects the first parameter type to set `FunctionTool.takes_context`, ensuring the runtime injects the context object before other arguments.

### Custom Names, Descriptions, and Error Handling

Override defaults and configure error handling for production resilience:

```python
from agents import function_tool, RunContextWrapper

def handle_tool_error(ctx: RunContextWrapper, exc: Exception) -> str:
    """Return formatted error instead of raising to prevent agent failure."""
    return f"TOOL_ERROR: {exc.__class__.__name__}: {str(exc)}"

@function_tool(
    name_override="weather_lookup",
    description_override="Get current weather conditions for a location.",
    strict_mode=False,  # Allows partial JSON for optional fields

    failure_error_function=handle_tool_error,
)
def get_weather(city: str, units: str = "metric") -> str:
    """Fetch weather data.
    
    Args:
        city: City name to look up
        units: Temperature units (metric or imperial)
    """
    return f"Weather in {city}: 22°C" if units == "metric" else "72°F"

# Non-strict mode excludes optional fields from required schema

assert "units" not in get_weather.params_json_schema.get("required", [])

```

When `strict_mode=False`, the generated schema in [`src/agents/function_schema.py`](https://github.com/openai/openai-agents-python/blob/main/src/agents/function_schema.py) omits default arguments from the required fields array, giving the LLM flexibility in parameter provision.

### Deferred Loading for Large Tool Sets

For applications with hundreds of tools, use deferred loading to reduce context window usage:

```python
@function_tool(defer_loading=True)
def expensive_analysis(query: str) -> dict:
    """Perform expensive computation only when explicitly selected.
    
    This tool description is hidden from the model until 
    a tool search is performed via the Responses API.
    """
    return {"result": f"Analysis of {query} complete", "tokens_used": 1500}

assert expensive_analysis.defer_loading is True

```

Setting `defer_loading=True` (defined in `FunctionTool` fields) prevents the tool description from being included in the initial system prompt, loading it only when the model performs a specific tool search.

## Runtime Execution Flow

When an Agent runs with your custom tools, the execution flow follows this path according to [`src/agents/run_internal/tool_execution.py`](https://github.com/openai/openai-agents-python/blob/main/src/agents/run_internal/tool_execution.py):

1. The LLM emits a function call with name and JSON arguments
2. `execute_function_tool_calls` matches the name to your `FunctionTool` instance
3. The runtime calls `on_invoke_tool(ctx, json_string)`, which internally:
   - Parses JSON via `_parse_function_tool_json_input`
   - Validates against `schema.params_pydantic_model`
   - Invokes your original function (sync or async)
   - Formats the return value as a string or JSON
4. Results wrap into `RunItem` objects and return to the LLM conversation

Error handling occurs at the `failure_error_function` level if configured, otherwise exceptions propagate to the Agent's output.

## Summary

- **Apply `@function_tool`** from [`src/agents/tool.py`](https://github.com/openai/openai-agents-python/blob/main/src/agents/tool.py) to any Python function to create a `FunctionTool` instance automatically
- **Use type hints and docstrings** to generate accurate JSON schemas via [`src/agents/function_schema.py`](https://github.com/openai/openai-agents-python/blob/main/src/agents/function_schema.py)
- **Accept `ToolContext`** as the first argument when you need runtime context, request-scoped data, or dependency injection
- **Configure `strict_mode=False`** for optional parameters and `defer_loading=True` for performance optimization with large tool sets
- **Implement `failure_error_function`** handlers to gracefully manage runtime exceptions without breaking agent execution
- **Reference runtime execution** in [`src/agents/run_internal/tool_execution.py`](https://github.com/openai/openai-agents-python/blob/main/src/agents/run_internal/tool_execution.py) to understand how LLM calls map to your Python functions

## Frequently Asked Questions

### What is the difference between `@function_tool` and creating a `FunctionTool` manually?

The `@function_tool` decorator is the recommended approach that handles inspection, schema generation, and wrapper creation automatically in [`src/agents/tool.py`](https://github.com/openai/openai-agents-python/blob/main/src/agents/tool.py). While you could instantiate `FunctionTool` directly with the dataclass constructor (lines 81–120), you would need to manually construct the JSON schema, provide the async `on_invoke_tool` implementation, and handle context injection yourself.

### How does the library handle synchronous vs asynchronous functions?

The decorator detects whether your function is a coroutine and wraps it appropriately in the `_on_invoke_tool_impl` method (lines 1802–1840 in [`src/agents/tool.py`](https://github.com/openai/openai-agents-python/blob/main/src/agents/tool.py)). Synchronous functions are called directly within the async wrapper, while async functions are awaited. Both return values are serialized to strings or JSON for the LLM context.

### Can I use classes or instance methods as function tools?

Currently, the `@function_tool` decorator in [`src/agents/tool.py`](https://github.com/openai/openai-agents-python/blob/main/src/agents/tool.py) expects a standalone function or static method. For class-based tools, define the method as a static method or move the logic to a module-level function. The schema generation logic relies on inspectable function signatures that don't include `self` references, which would complicate JSON schema derivation.

### What happens if my function raises an exception during execution?

If you provide a `failure_error_function` callback in the decorator, that function receives the `RunContextWrapper` and exception, returning a string that the LLM sees as the tool result. Without this handler, exceptions propagate up to the Agent runner. According to the implementation in [`tool.py`](https://github.com/openai/openai-agents-python/blob/main/tool.py), this allows you to return structured error messages or fallback values rather than crashing the agent loop.