# How to Implement Dynamic Tool Creation in LiveKit Agents

> Learn to implement dynamic tool creation in LiveKit Agents using three patterns: construction time initialization, runtime updates with update_tools, or temporary injection inside llm_node.

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

---

**LiveKit Agents enables dynamic tool creation through three distinct patterns: initializing tools at construction time with external data, updating the toolset at runtime via `update_tools()`, or injecting temporal tools inside `llm_node()` for single-generation use.**

LiveKit Agents is an open-source framework for building voice and multimodal AI agents. While agents typically expose static functions to LLMs, **dynamic tool creation** allows you to modify the available toolset at runtime—loading capabilities from external databases, responding to user-driven requests, or creating temporary helpers for specific conversation turns.

## The Three Patterns for Dynamic Tool Creation

The framework supports dynamic tool injection at three distinct lifecycle moments, each serving different architectural needs.

### 1. Construction-Time Tool Loading

Initialize tools when instantiating the agent, ideal for loading external configurations or database-driven capabilities before the session begins.

In [`livekit-agents/livekit/agents/voice/agent.py`](https://github.com/livekit/agents/blob/main/livekit-agents/livekit/agents/voice/agent.py) (lines 55-64), the `Agent` constructor builds the initial `ToolContext` from the `tools` argument and any methods decorated with `@function_tool` on your subclass:

```python
self._tools = [*tools, *find_function_tools(self)]
self._chat_ctx = chat_ctx.copy(tools=self._tools) if chat_ctx else ChatContext.empty()

```

**Example:** Loading course listings from a database to generate enum-backed tools:

```python
from enum import Enum
from pydantic import BaseModel
from livekit.agents import Agent, function_tool

# Load external data

courses = await _get_course_list_from_db()
CourseType = Enum("CourseType", {c.replace(" ", "_"): c for c in courses})

class CourseInfo(BaseModel):
    course: CourseType
    location: Literal["online", "in-person"]

async def _get_course_info(info: CourseInfo) -> str:
    return f"Course details for {info.course} at {info.location}"

# Inject at construction

agent = MyAgent(
    instructions="You are a course assistant.",
    tools=[function_tool(_get_course_info, name="get_course_info")]
)

```

### 2. Runtime Tool Updates

Add or modify tools after the agent is already running using `Agent.update_tools()`. This method validates new tools, rebuilds the internal `ToolContext`, and propagates changes to active real-time sessions.

According to [`livekit-agents/livekit/agents/voice/agent.py`](https://github.com/livekit/agents/blob/main/livekit-agents/livekit/agents/voice/agent.py) (lines 49-78), `update_tools()` performs deduplication by tool ID and refreshes the chat context:

```python
await agent.update_tools(agent.tools + [new_tool])

```

**Use case:** Enabling a "random number generator" only after the user specifically requests it:

```python
async def _random_number() -> int:
    import random
    return random.randint(1, 100)

# Add capability mid-session

await agent.update_tools(
    agent.tools + [function_tool(_random_number, name="random_number")]
)

```

### 3. Temporal Tools for Single Calls

Inject tools that exist only for the current LLM generation by modifying the mutable `tools` list inside a custom `llm_node()` implementation. These temporal tools do not persist to subsequent turns unless explicitly added again.

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

class WeatherAgent(Agent):
    async def llm_node(
        self,
        chat_ctx: llm.ChatContext,
        tools: list[llm.Tool | llm.Toolset],
        model_settings: llm.ModelSettings,
    ):
        # Create a one-off weather lookup for this turn only

        async def _get_weather(location: str) -> str:
            return f"The weather in {location} is sunny."
        
        tools.append(function_tool(
            _get_weather, 
            name="get_weather", 
            description="Lookup current weather"
        ))
        
        # Continue with the extended tool list

        return Agent.default.llm_node(self, chat_ctx, tools, model_settings)

```

## How Dynamic Tool Creation Works

Understanding the underlying mechanism ensures you implement updates correctly without side effects.

### The function_tool Decorator

The `@function_tool` decorator (or direct function call) wraps Python callables into `FunctionTool` or `RawFunctionTool` instances that LLM providers can invoke. Defined in [`livekit-agents/livekit/agents/llm/tool_context.py`](https://github.com/livekit/agents/blob/main/livekit-agents/livekit/agents/llm/tool_context.py) (lines 49-80), the decorator attaches metadata including the function name, description, and parameter schemas via the `__livekit_tool_info` attribute.

```python
@function_tool
def my_tool(param: str) -> str:
    return f"Result: {param}"

```

### ToolContext Validation and Deduplication

The `ToolContext` class acts as a stateless container that indexes all available function tools. When you call `update_tools()`, the framework validates that each item is a `FunctionTool`, `RawFunctionTool`, or `Toolset`, then builds a deduplicated dictionary keyed by tool ID to prevent conflicts.

### Real-Time Session Propagation

If your agent has an active real-time session when `update_tools()` is called, the framework automatically forwards the new tool list to the LLM provider via `self._activity.update_tools`. This ensures the remote model immediately sees the updated capabilities without requiring session reinitialization.

## Complete Working Example

Below is a self-contained implementation demonstrating all three patterns in the repository's dynamic tool creation example ([`examples/voice_agents/dynamic_tool_creation.py`](https://github.com/livekit/agents/blob/main/examples/voice_agents/dynamic_tool_creation.py)):

```python
from livekit.agents import Agent, function_tool, llm
from typing import Literal
from pydantic import BaseModel
from enum import Enum

class FlexibleAgent(Agent):
    async def llm_node(
        self,
        chat_ctx: llm.ChatContext,
        tools: list[llm.Tool | llm.Toolset],
        model_settings: llm.ModelSettings,
    ):
        # Pattern 3: Temporal tool for this turn only

        async def _hello(name: str) -> str:
            return f"👋 Hello, {name}!"
        
        tools.append(function_tool(
            _hello, 
            name="say_hello", 
            description="Greet a user by name"
        ))
        
        return Agent.default.llm_node(self, chat_ctx, tools, model_settings)

# Pattern 1: Construction-time injection

agent = FlexibleAgent(instructions="You are a helpful assistant.")

# Pattern 2: Runtime update

async def _double(x: int) -> int:
    return x * 2

await agent.update_tools(
    agent.tools + [function_tool(_double, name="double", description="Double a number")]
)

```

## Summary

LiveKit Agents provides robust **dynamic tool creation** capabilities through these key mechanisms:

*   **Construction-time injection** passes tools to `Agent.__init__()` for static initialization with external data
*   **Runtime updates** via `await agent.update_tools()` persistently modify the toolset and propagate to active real-time sessions
*   **Temporal injection** inside `llm_node()` allows single-turn tool availability without persistent side effects
*   The framework handles validation, deduplication by tool ID, and automatic context rebuilding in [`livekit-agents/livekit/agents/voice/agent.py`](https://github.com/livekit/agents/blob/main/livekit-agents/livekit/agents/voice/agent.py)

## Frequently Asked Questions

### What is the difference between `update_tools()` and `llm_node()` tool injection?

`update_tools()` permanently modifies the agent's toolset for the remainder of the session, updating the internal `ToolContext` and propagating changes to real-time providers. Injecting tools inside `llm_node()` only affects the current LLM generation turn; the tool disappears on subsequent calls unless you append it again.

### Can I remove tools dynamically after adding them?

Yes. Call `update_tools()` with a filtered list containing only the tools you want to keep. The framework replaces the entire internal tool list with your new collection, effectively removing any tools you omit from the argument.

### Does dynamic tool creation work during active real-time sessions?

Yes. When `update_tools()` detects an active session (via `self._activity`), it automatically pushes the updated tool definitions to the LLM provider. This happens in the background without interrupting the conversation flow.

### How does the framework handle duplicate tool definitions?

The `update_tools()` implementation in [`agent.py`](https://github.com/livekit/agents/blob/main/agent.py) builds a dictionary keyed by tool ID to deduplicate entries. If you pass multiple tools with the same identifier, the last one in the list takes precedence, ensuring the LLM receives a clean, conflict-free tool schema.