# How to Use Function Tools with LiveKit Agents: Complete Implementation Guide

> Master LiveKit Agents and LLMs by learning how to implement function tools. Decorate methods, manage ToolContext, and handle FunctionToolsExecutedEvents for seamless Python function invocation.

- Repository: [LiveKit/agents](https://github.com/livekit/agents)
- Tags: how-to-guide
- Published: 2026-03-06

---

**LiveKit Agents enable large language models (LLMs) to invoke Python functions automatically by decorating methods with `@function_tool`, collecting them in the agent's `ToolContext`, and emitting `FunctionToolsExecutedEvent` results that are re-inserted into the conversation context.**

Function tools transform your voice agents from passive responders into active systems capable of querying databases, calling external APIs, and executing business logic. In the `livekit/agents` framework, tools are first-class citizens that bridge natural language understanding with programmatic action.

## Defining Function Tools with the @function_tool Decorator

The `@function_tool` decorator in [`livekit/agents/llm/tool_context.py`](https://github.com/livekit/agents/blob/main/livekit/agents/llm/tool_context.py) converts Python callables into `FunctionTool` objects that LLMs can discover and invoke. When applied to an async method, the decorator introspects type hints and docstrings to generate a JSON Schema automatically.

```python
from livekit.agents.llm import function_tool
from livekit.agents.voice import Agent

class WeatherAgent(Agent):
    @function_tool
    async def get_weather(self, latitude: str, longitude: str):
        """Return current temperature for the given coordinates."""
        # External API call implementation

        return {"temperature": 22, "unit": "C"}

```

*Source*: [[`examples/voice_agents/weather_agent.py`](https://github.com/livekit/agents/blob/main/examples/voice_agents/weather_agent.py)](https://github.com/livekit/agents/blob/main/examples/voice_agents/weather_agent.py#L24-L38)

The decorator creates a `FunctionTool` instance that stores the callable, its name, description, and parameter schema. This metadata allows the LLM provider (OpenAI, Anthropic, Google) to understand when and how to invoke the function during a conversation.

### Raw JSON Schema Tools

For complex schemas or OpenAI-compatible definitions, use `raw_schema=RawFunctionDescription` to bypass automatic introspection. This approach is implemented in the same [`tool_context.py`](https://github.com/livekit/agents/blob/main/tool_context.py) file and creates a `RawFunctionTool` instead.

```python
from livekit.agents.llm import function_tool, RawFunctionDescription

@function_tool(
    raw_schema=RawFunctionDescription(
        name="search_web",
        description="Search the web and return snippets.",
        parameters={
            "type": "object",
            "properties": {"query": {"type": "string"}},
            "required": ["query"]
        },
    )
)
async def search_web(query: str):
    # Web search implementation

    return {"results": ["snippet 1", "snippet 2"]}

```

*Source*: [[`examples/voice_agents/raw_function_description.py`](https://github.com/livekit/agents/blob/main/examples/voice_agents/raw_function_description.py)](https://github.com/livekit/agents/blob/main/examples/voice_agents/raw_function_description.py#L30-L38)

## Registering Tools with Your Agent

The base `Agent` class automatically discovers decorated methods through `ToolContext`. When you instantiate an agent, the constructor calls `self._collect_tools()` (defined in [`livekit/agents/voice/agent.py`](https://github.com/livekit/agents/blob/main/livekit/agents/voice/agent.py)), which scans the class for `@function_tool` decorators and populates the internal registry.

```python
class MyAgent(Agent):
    def __init__(self):
        super().__init__(
            instructions="You are a helpful assistant.",
            llm=openai.realtime.RealtimeModel(),
            # Tools are auto-collected from decorated methods

        )

```

The `ToolContext` class (in [`livekit/agents/llm/tool_context.py`](https://github.com/livekit/agents/blob/main/livekit/agents/llm/tool_context.py)) maintains a mapping of `name → FunctionTool`, enabling fast lookup when the LLM issues a call. You can also manually pass a `Toolset` or list of tools to the constructor if you need to share tools across multiple agent instances.

## LLM Integration and Tool Invocation

When the agent creates a `ChatContext` (in [`livekit/agents/voice/agent.py`](https://github.com/livekit/agents/blob/main/livekit/agents/voice/agent.py)), it copies the tool list into `ChatContext.tools`. This context propagates to the LLM provider via the `LLM.chat` method in [`livekit/agents/llm/llm.py`](https://github.com/livekit/agents/blob/main/livekit/agents/llm/llm.py), which streams the available functions to the underlying API.

The LLM returns a `FunctionToolCall` object containing the tool name and JSON-encoded arguments. The framework captures this in the streaming response and prepares for execution without blocking the conversation flow.

## Executing Tool Calls and Handling Results

Execution occurs in [`livekit/agents/llm/utils.py`](https://github.com/livekit/agents/blob/main/livekit/agents/llm/utils.py) through the `execute_function_call` function. This utility:

1. Resolves the tool name against the `ToolContext`
2. Deserializes JSON arguments into Python types
3. Injects the optional `RunContext` if the function signature requires it
4. Awaits async results or handles sync returns
5. Wraps outputs or exceptions into `FunctionCallOutput` objects

```python

# Pseudocode representing the internal execution flow

output = await execute_function_call(
    tool_context=self._tool_context,
    function_call=function_tool_call,
    run_context=run_context
)

```

After execution, the `FunctionCallOutput` is automatically inserted back into the conversation history, allowing the LLM to generate a contextual response based on the tool's data.

## Event-Driven Tool Execution Flow

Once tools execute, the agent emits a `FunctionToolsExecutedEvent` (defined in [`livekit/agents/voice/events.py`](https://github.com/livekit/agents/blob/main/livekit/agents/voice/events.py)). This event carries both the original `FunctionToolCall` and the resulting `FunctionCallOutput`, enabling client-side reactions such as displaying tool results or modifying UI state.

```python
@session.on("function_tools_executed")
def on_tool_executed(ev: FunctionToolsExecutedEvent):
    for call, out in ev.zipped():
        print(f"Tool {call.name} returned: {out.output}")
        # Optional: Prevent the agent from speaking the result

        # ev.cancel_tool_reply()

```

The `RemoteSession` class in [`livekit/agents/voice/client_events.py`](https://github.com/livekit/agents/blob/main/livekit/agents/voice/client_events.py) bridges these server-side events to the frontend via RTC data channels, ensuring real-time synchronization between voice agents and client applications.

## Summary

- **Define tools** using `@function_tool` for automatic schema generation or `raw_schema=RawFunctionDescription` for manual control, both located in [`livekit/agents/llm/tool_context.py`](https://github.com/livekit/agents/blob/main/livekit/agents/llm/tool_context.py).
- **Auto-registration** occurs via `Agent.__init__` calling `_collect_tools()`, which populates a `ToolContext` mapping tool names to callables.
- **LLM propagation** happens through `ChatContext.tools` in [`livekit/agents/voice/agent.py`](https://github.com/livekit/agents/blob/main/livekit/agents/voice/agent.py), sending schemas to providers like OpenAI or Anthropic.
- **Execution** is handled by `execute_function_call` in [`livekit/agents/llm/utils.py`](https://github.com/livekit/agents/blob/main/livekit/agents/llm/utils.py), which handles argument parsing, async invocation, and result wrapping.
- **Event emission** via `FunctionToolsExecutedEvent` allows client-side handling through the `function_tools_executed` event, with optional reply cancellation via `cancel_tool_reply()`.

## Frequently Asked Questions

### What is the difference between @function_tool and raw_schema tools?

**`@function_tool` without arguments** automatically generates JSON Schema from Python type hints and docstrings, creating a standard `FunctionTool` object. **`@function_tool(raw_schema=RawFunctionDescription(...))`** creates a `RawFunctionTool` that uses your exact schema definition, bypassing introspection. Use raw schemas when you need provider-specific parameter formats or complex nested structures that type hints cannot express.

### How does the Agent automatically discover function tools?

The base `Agent` class constructor calls `self._collect_tools()` (in [`livekit/agents/voice/agent.py`](https://github.com/livekit/agents/blob/main/livekit/agents/voice/agent.py)), which scans the instance for methods decorated with `@function_tool`. These are registered in a `ToolContext` object that maps function names to their implementations. You do not need to manually list tools unless you are using external functions or sharing a `Toolset` across agents.

### Can I cancel a tool reply after execution?

Yes. The `FunctionToolsExecutedEvent` emitted after tool execution includes a `cancel_tool_reply()` method. Call this inside your event handler to prevent the agent from verbally acknowledging the tool result, useful when you want to handle the output silently or trigger a hand-off to another system instead.

### How are tool outputs returned to the LLM?

After `execute_function_call` runs the function (in [`livekit/agents/llm/utils.py`](https://github.com/livekit/agents/blob/main/livekit/agents/llm/utils.py)), it wraps the return value in a `FunctionCallOutput` object. The agent inserts this output into the `ChatContext` as a function result message, then sends the updated context back to the LLM. The model receives the JSON output and generates its next response based on that data, creating a seamless tool-use conversation loop.