Skip to main content

Toolsets

Group related tools and add or remove them as a unit.

Available inPython
|
Node.js

Overview

A Toolset bundles related tools under a single ID so you can add or remove them as a group.

Pass a list of tools to the Toolset constructor:

from livekit.agents import Agent, function_tool, RunContext
from livekit.agents.llm import Toolset
@function_tool()
async def lookup_user(context: RunContext, user_id: str) -> dict:
"""Look up a user by ID."""
return {"name": "Jane Doe", "email": "jane@example.com"}
@function_tool()
async def update_user(context: RunContext, user_id: str, email: str) -> str:
"""Update a user's email address."""
return f"Updated email for {user_id}."
class MyAgent(Agent):
def __init__(self):
super().__init__(
instructions="You are a helpful assistant.",
tools=[
Toolset(id="user-management", tools=[lookup_user, update_user])
],
)

Pass an array of tools to the Toolset constructor:

import { llm, voice } from '@livekit/agents';
import { z } from 'zod';
const lookupUser = llm.tool({
name: 'lookupUser',
description: 'Look up a user by ID.',
parameters: z.object({ userId: z.string() }),
execute: async ({ userId }) => {
return { name: 'Jane Doe', email: 'jane@example.com' };
},
});
const updateUser = llm.tool({
name: 'updateUser',
description: "Update a user's email address.",
parameters: z.object({ userId: z.string(), email: z.string() }),
execute: async ({ userId }) => {
return `Updated email for ${userId}.`;
},
});
const agent = voice.Agent.create({
instructions: 'You are a helpful assistant.',
tools: [new llm.Toolset({ id: 'user-management', tools: [lookupUser, updateUser] })],
});

Toolsets are flattened automatically when sent to the LLM. You can add or remove a toolset as a group using update_tools() in Python or updateTools() in Node.js. In Node.js, llm.isToolset(value) checks whether a value is a toolset.

Note

Tool names must be unique across all tools and toolsets. If a toolset and a standalone tool, or two toolsets, share a tool with the same name, the agent raises an error.

Custom toolsets

Use a custom toolset when you need custom initialization, teardown, or dynamic tool loading. Setup and close hooks are called automatically by AgentSession.

Subclass Toolset and override setup() and aclose():

from livekit.agents import Agent, RunContext, function_tool
from livekit.agents.llm import Toolset
from typing_extensions import Self
class WeatherToolset(Toolset):
def __init__(self):
super().__init__(id="weather_tools")
self._lookup = function_tool(
self._lookup_weather,
name="lookup_weather",
description="Look up current weather for a location.",
)
self._forecast = function_tool(
self._get_forecast,
name="get_forecast",
description="Get a multi-day weather forecast.",
)
self._tools = [self._lookup, self._forecast]
async def setup(self) -> Self:
await super().setup()
# initialize external connections, load config, etc.
return self
async def aclose(self) -> None:
await super().aclose()
# close connections, release resources
async def _lookup_weather(self, context: RunContext, location: str) -> str:
return f"The weather in {location} is sunny."
async def _get_forecast(
self, context: RunContext, location: str, days: int = 3
) -> str:
return f"{days}-day forecast for {location}: sunny."
class MyAgent(Agent):
def __init__(self):
super().__init__(
instructions="You are a helpful weather assistant.",
tools=[WeatherToolset()],
)

Use llm.Toolset.create() with setup and aclose hooks:

import { llm, voice } from '@livekit/agents';
import { z } from 'zod';
const lookupWeather = llm.tool({
name: 'lookupWeather',
description: 'Look up current weather for a location.',
parameters: z.object({ location: z.string() }),
execute: async ({ location }) => {
return `The weather in ${location} is sunny.`;
},
});
const getForecast = llm.tool({
name: 'getForecast',
description: 'Get a multi-day weather forecast.',
parameters: z.object({ location: z.string(), days: z.number().default(3) }),
execute: async ({ location, days }) => {
return `${days}-day forecast for ${location}: sunny.`;
},
});
const weatherToolset = llm.Toolset.create({
id: 'weather_tools',
tools: [],
setup: async (ctx) => {
// initialize external connections, load config, etc.
ctx.updateTools([lookupWeather, getForecast]);
},
aclose: async () => {
// close connections, release resources
},
});
const agent = voice.Agent.create({
instructions: 'You are a helpful weather assistant.',
tools: [weatherToolset],
});

The built-in MCP Toolset is Python-specific. It connects to an MCP server on setup() and disconnects on aclose().

Dynamic tool discovery

Available in
Beta
|
Python

Agents with many tools can suffer from degraded LLM accuracy and wasted tokens. Dynamic tool discovery solves this by loading tool definitions on demand instead of all at once. Two kinds of toolsets are available:

  • ToolSearchToolset exposes a single tool_search function. When the LLM calls it, matching tools are added to the LLM's native tool list for the next turn. May be simpler for the model to understand.
  • ToolProxyToolset exposes exactly two fixed tools, tool_search and call_tool. The tool list never changes, so providers like OpenAI and Anthropic can reuse their prompt cache across turns. May be better for many tools or cost-sensitive workloads.

Both accept toolsets (including MCPToolset), function tools, and standalone tools:

from livekit.agents import Agent, RunContext, function_tool
from livekit.agents.beta.toolsets import ToolProxyToolset
from livekit.agents.llm import Toolset
class WeatherToolset(Toolset):
def __init__(self):
super().__init__(id="weather")
@function_tool()
async def get_weather(self, context: RunContext, location: str) -> str:
"""Get current weather for a location."""
return f"Sunny, 72F in {location}"
class FlightToolset(Toolset):
def __init__(self):
super().__init__(id="flights")
@function_tool()
async def search_flights(self, context: RunContext, origin: str, destination: str) -> str:
"""Search for available flights."""
return f"Found 3 flights from {origin} to {destination}"
@function_tool()
async def convert_currency(context: RunContext, amount: float, code: str) -> str:
"""Convert an amount to the given currency code."""
return f"{amount} converted to {code}"
class TravelAgent(Agent):
def __init__(self):
super().__init__(
instructions="You are a travel planning assistant. Use tool_search to find the right tools.",
tools=[
ToolProxyToolset(
id="travel_tools",
tools=[
WeatherToolset(),
FlightToolset(),
convert_currency,
],
max_results=3,
)
],
)

Swap ToolProxyToolset for ToolSearchToolset to use native tool calls instead of the proxy pattern. Both classes share the same core arguments.

The default search strategy uses BM25 ranking, which returns tools ordered by relevance to the query rather than by literal pattern match. For simpler regex-based matching, pass a KeywordSearchStrategy:

from livekit.agents.beta.toolsets.tool_search import KeywordSearchStrategy
toolset = ToolProxyToolset(
id="my_tools",
tools=[...],
search_strategy=KeywordSearchStrategy(),
)

You can also implement your own strategy by conforming to the SearchStrategy protocol.

Additional resources

The following articles provide more information about the topics discussed in this guide: