| name | livekit-agent-tools |
| description | Comprehensive guide for building functional tools for LiveKit voice agents using the @function_tool decorator. Use when creating tools for LiveKit agents to enable capabilities like API calls, database queries, multi-agent coordination, or any external integrations. Covers tool design, RunContext handling, interruption patterns, parameter documentation, testing, and production best practices. |
LiveKit Agent Tools Development
Build robust, production-ready tools for LiveKit voice agents that enable LLMs to interact with external services, manage state, coordinate multi-agent workflows, and handle real-time voice interactions.
Quick Start
Basic Tool Pattern
from livekit.agents import Agent
from livekit.agents.llm import function_tool
from livekit.agents.voice import RunContext
class MyAgent(Agent):
def __init__(self):
super().__init__(
instructions="You are a helpful assistant...",
llm="openai/gpt-4o-mini",
)
@function_tool
async def get_weather(self, location: str) -> str:
"""Get current weather for a location.
Args:
location: City name or address to get weather for
"""
# Your implementation here
return f"Weather in {location}: Sunny, 72°F"
The @function_tool decorator automatically registers methods as callable tools for the LLM. The docstring is critical—it tells the LLM when and how to use the tool.
Tool Definition Best Practices
Critical for reliability: A good tool definition is key to reliable tool use from your LLM. Be specific about:
- What the tool does
- When it should or should not be used
- What the arguments are for
- What type of return value to expect
Core Concepts
1. Tool Naming and Descriptions
Use clear, action-oriented names that help the LLM discover the right tool:
@function_tool
async def search_flights(self, origin: str, destination: str, date: str) -> str:
"""Search for available flights between two cities on a specific date.
Use this when the user asks about flight availability, prices, or schedules.
Do NOT use this for booking—only for searching.
Args:
origin: Departure city or airport code
destination: Arrival city or airport code
date: Travel date in YYYY-MM-DD format
"""
2. RunContext for State and Control
The RunContext parameter provides access to session state, speech control, and user data:
@function_tool
async def save_preference(
self,
preference_name: str,
value: str,
context: RunContext
) -> str:
"""Save a user preference for later use.
Args:
preference_name: Name of the preference (e.g., "favorite_color")
value: The preference value
context: Runtime context (automatically provided)
"""
# Access shared state across tools
context.userdata[preference_name] = value
return f"Saved {preference_name} as {value}"
See Context & State Management for complete RunContext patterns.
3. Parameter Documentation
Use type hints and annotations for rich parameter documentation:
from typing import Annotated, Literal
from pydantic import Field
from enum import Enum
class Priority(str, Enum):
LOW = "low"
MEDIUM = "medium"
HIGH = "high"
@function_tool
async def create_task(
self,
title: str,
priority: Priority,
due_date: Annotated[str | None, Field(description="Due date in YYYY-MM-DD format")] = None
) -> str:
"""Create a new task with specified priority.
Args:
title: Brief description of the task
priority: Task priority level
due_date: Optional deadline for the task
"""
See Parameter Patterns for all documentation approaches.
When to Use Different Patterns
Simple Action Tools
Use for straightforward operations that execute quickly:
- Logging events
- Simple calculations
- Database queries that return quickly
API Integration Tools
Use for external service calls:
- Weather APIs
- Database queries
- Third-party integrations
See examples/api-integration-tool.py
Long-Running Tools
Use for operations that might take time and should handle interruptions:
- Web searches
- Complex calculations
- File processing
See Long-Running Functions and examples/long-running-tool.py
Stateful Tools
Use for maintaining context across interactions:
- Shopping cart management
- Multi-step workflows
- User preferences
See Context & State Management and examples/stateful-tool.py
Multi-Agent Coordination Tools
Use for agent handoffs and specialized routing:
- Transferring to specialized agents
- Escalation workflows
- Domain-specific routing
See Multi-Agent Patterns and examples/agent-handoff-tool.py
Dynamic Tool Creation
Tools can be created at runtime for maximum flexibility:
from livekit.agents.llm import function_tool
# Option 1: Pass tools at agent creation
agent = MyAgent(
instructions="...",
tools=[
function_tool(
get_user_data,
name="get_user_data",
description="Fetch user information from database"
)
]
)
# Option 2: Update tools after creation
await agent.update_tools(
agent.tools + [
function_tool(
new_capability,
name="new_capability",
description="Dynamically added tool"
)
]
)
See Dynamic Tool Creation for complete patterns.
Testing Your Tools
Testing is essential for reliable agents. LiveKit provides helpers that work with pytest:
import pytest
from livekit.agents.testing import VoiceAgentTestSession
@pytest.mark.asyncio
async def test_weather_tool():
async with VoiceAgentTestSession(agent=MyAgent()) as session:
response = await session.send_text("What's the weather in Tokyo?")
assert "Tokyo" in response.text
assert session.tool_calls[-1].name == "get_weather"
See Testing Guide for comprehensive testing strategies.
Production Considerations
Error Handling
Always handle errors gracefully and return meaningful messages:
@function_tool
async def fetch_data(self, user_id: str) -> str:
"""Fetch user data from the database."""
try:
data = await database.get_user(user_id)
return f"User data: {data}"
except UserNotFoundError:
return f"No user found with ID {user_id}. Please check the ID and try again."
except DatabaseError as e:
return "I'm having trouble accessing the database right now. Please try again in a moment."
Interruption Handling
Design tools that respect user interruptions for better UX:
@function_tool
async def search_database(self, query: str, context: RunContext) -> str | None:
"""Search the database for matching records."""
# Allow user to interrupt long searches
search_task = asyncio.ensure_future(perform_search(query))
await context.speech_handle.wait_if_not_interrupted([search_task])
if context.speech_handle.interrupted:
search_task.cancel()
return None # Skip the tool reply
return search_task.result()
Tool Organization
For complex agents with many tools:
- Group related tools by domain
- Share common tools across agents using functions defined outside classes
- Use clear naming conventions (e.g.,
order_create,order_update,order_cancel)
See Best Practices for production patterns.
Reference Documentation
Load these as needed for specific patterns:
- Context & State Management - RunContext, userdata, session control
- Parameter Patterns - Type hints, annotations, enums, validation
- Long-Running Functions - Async patterns, interruption handling
- Multi-Agent Patterns - Agent handoffs, coordination, state transfer
- Dynamic Tool Creation - Runtime tool generation and updates
- Testing Guide - Test strategies, mocking, evaluation
- Best Practices - Production patterns, security, performance
Examples
Complete, runnable examples demonstrating each pattern:
- examples/basic-tool.py - Simple tool implementation
- examples/api-integration-tool.py - External API calls
- examples/long-running-tool.py - Async operations with interruptions
- examples/stateful-tool.py - State management with RunContext
- examples/agent-handoff-tool.py - Multi-agent coordination
Environment Setup
Before running examples, create a .env file with required API keys:
# LiveKit Configuration
LIVEKIT_URL=wss://your-project.livekit.cloud
LIVEKIT_API_KEY=your_api_key
LIVEKIT_API_SECRET=your_api_secret
# LLM Provider (choose one)
OPENAI_API_KEY=your_openai_key
ANTHROPIC_API_KEY=your_anthropic_key
Install dependencies:
pip install livekit-agents livekit-plugins-openai livekit-plugins-silero python-dotenv aiohttp
Quick Reference
| Pattern | When to Use | Key Features | Example |
|---|---|---|---|
| Basic Tools | Simple, fast operations | Direct return values, no state | basic-tool.py |
| API Integration | External service calls | Async HTTP, error handling | api-integration-tool.py |
| Long-Running | Time-consuming ops | Interruption support, cancellation | long-running-tool.py |
| Stateful | Multi-step workflows | RunContext.userdata, persistence | stateful-tool.py |
| Multi-Agent | Specialized routing | Agent handoffs, context transfer | agent-handoff-tool.py |
| Dynamic | Runtime tool creation | Permission-based, conditional | See dynamic-tools.md |
Common Patterns Cheat Sheet
# Basic tool
@function_tool
async def tool_name(self, param: str) -> str:
"""Tool description with usage guidance."""
return result
# With state
@function_tool
async def stateful_tool(self, param: str, context: RunContext) -> str:
context.userdata["key"] = value
return result
# With interruption handling
@function_tool
async def long_tool(self, param: str, context: RunContext) -> str | None:
task = asyncio.ensure_future(operation())
await context.speech_handle.wait_if_not_interrupted([task])
if context.speech_handle.interrupted:
task.cancel()
return None
return task.result()
# Agent handoff
@function_tool
async def transfer_agent(self, context: RunContext):
new_agent = SpecialistAgent()
return new_agent, "Transferring to specialist"
Quick Troubleshooting
Tool not being called: Improve the description to be more specific about when to use it.
Type errors: Ensure all parameters have proper type hints.
Interruption issues: Use RunContext.speech_handle.wait_if_not_interrupted() for long operations.
State not persisting: Use context.userdata to share state across tool calls.
Agent transitions fail: Return a tuple (new_agent, message) from the tool.
Import errors: Verify all required packages are installed (pip install livekit-agents livekit-plugins-openai).
For detailed guidance, consult the reference documentation above.