colossal-xylophone-68609
05/05/2026, 7:50 PMtall-airline-16756
05/05/2026, 7:50 PMtall-airline-16756
05/05/2026, 7:50 PM@function_tool or Node llm.tool() / Zod schemas). Each tool includes a name, description, and typed input schema.
• When the agent initializes a call to the LLM, the agent supplies a structured list of those tool schemas via the tools parameter. The LLM sees the tool names, descriptions, and parameter schemas so it can decide to invoke one.
• At runtime: user audio → STT → transcribed text sent to the LLM (with the tool schemas). The LLM can return either a direct text reply or a tool call (structured output).
• If the LLM issues a tool call, the framework intercepts it, extracts the parameters, executes the corresponding tool implementation, returns the tool result to the LLM, and the LLM then generates the final natural-language response using that result.
Additional notes
• Function-tool schemas are generated from the function signature/docstrings (Python) or Zod/JSON schemas (Node).
• Provider-side tools (e.g., WebSearch) can be added the same way and are exposed to the LLM as plugins in the tools list.
• Tools can be added/removed dynamically (update_tools) so the set of available tools the LLM sees can change per conversation.
If you want, tell me:
• which language (Python or Node) you’re using, and
• an example tool you’ve defined
and I’ll show the exact decorator/schema and the minimal payload that gets sent to the LLM.
Sources: Function tool definition | LiveKit Documentation | Tool definition and use | LiveKit Documentation | Sequential Pipeline Architecture for Voice Agents | LiveKit, with interactive elementscolossal-xylophone-68609
05/05/2026, 7:52 PMtall-airline-16756
05/05/2026, 7:52 PMtools array; each tool is an object with:
◦ `type`: "function"
◦ `function`: {
▪︎ `name`: string,
▪︎ `description`: string,
▪︎ `parameters`: {
• `type`: "object",
• `properties`: { param_name: { "type": type, "description": string }, ... },
• `required`: [ ... ],
• `additionalProperties`: false
} }
Example JSON entry for a lookup_weather tool
{
"type": "function",
"function": {
"name": "lookup_weather",
"description": "Look up weather information for a given location.",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "The location to look up weather information for."
},
"units": {
"type": "string",
"description": "Units for temperature (metric or imperial)."
}
},
"required": [
"location"
],
"additionalProperties": false
}
}
}
Runtime flow summary
1. Agent sends user text (after STT) to the LLM along with the tools array above.
2. The LLM may return a tool call (name + JSON args) instead of plain text.
3. The framework executes the matching tool with those args and returns the result to the LLM.
4. The LLM produces the final natural-language response using the tool output.
If you want, tell me which language (Python or Node) you're using and one example tool signature — I’ll show the exact decorator/schema and the minimal request payload sent to the LLM.
Sources: OpenAI LLM models | LiveKit Documentation | Tool definition and use | LiveKit Documentation | Function tool definition | LiveKit Documentation, with interactive elementscolossal-xylophone-68609
05/05/2026, 7:53 PMdef tool_name(self, params):
""" some description """
...tool bodytall-airline-16756
05/05/2026, 7:53 PMdef tool_name(self, params): ... will work and what the implications are. Short answer and guidance:
• That signature will run as regular code, but LiveKit will not automatically generate a structured schema for the LLM from a single untyped params argument. The LLM needs a JSON-schema/function schema to know what arguments to provide.
• To expose the tool to the LLM and get function-calling behavior, declare it with the function-tool mechanism so the schema is generated or provided:
◦ In Python: use @function_tool on a method or function and use typed parameters or provide a raw_schema. Example signatures generate proper schemas from type hints and docstrings.
◦ In Node: use llm.tool() with a Zod or JSON schema for parameters.
If you must accept a single params dict, provide an explicit schema (via raw_schema in Python or a JSON/Zod schema in Node) that describes the expected keys and types — otherwise the LLM won't know the structure and won't produce reliable tool calls.
Tell me which language (Python or Node) and I’ll show a minimal example converting your params style into a properly exposed tool schema.
, with interactive elementscolossal-xylophone-68609
05/05/2026, 7:54 PMtall-airline-16756
05/05/2026, 7:54 PMdef tool_name(self, params) style, then explain how to expose a JSON schema so the LLM can call it, and show the runtime payload and flow.
Scenario
• Goal: Agent provides user account details when asked (tool accepts a single params dict with user_id and fields).
• Problem: A plain params argument has no type info for the LLM. You must supply a schema so the LLM can generate structured tool calls.
1) Tool implementation (Python, method with params)
from livekit.agents import function_tool
class AccountAgent:
@function_tool(raw_schema={
"name": "get_account_info",
"description": "Return account fields for a given user id.",
"parameters": {
"type": "object",
"properties": {
"user_id": {"type": "string", "description": "ID of the user"},
"fields": {
"type": "array",
"items": {"type": "string"},
"description": "List of account fields to return, e.g. ['email','plan']"
}
},
"required": ["user_id"],
"additionalProperties": False
}
})
async def get_account_info(self, params):
# params is a dict {"user_id": "...", "fields": ["..."]}
user_id = params.get("user_id")
fields = params.get("fields", ["email"])
# ...fetch data from DB or service...
return {f: f"dummy-{f}-for-{user_id}" for f in fields}
Notes:
• @function_tool(raw_schema=...) explicitly supplies the function schema the LLM needs.
• The implementation still receives a single params dict, so your existing logic can stay the same.
2) Example tool entry LiveKit sends to the LLM (JSON function schema)
{
"type": "function",
"function": {
"name": "get_account_info",
"description": "Return account fields for a given user id.",
"parameters": {
"type": "object",
"properties": {
"user_id": {
"type": "string",
"description": "ID of the user"
},
"fields": {
"type": "array",
"items": {
"type": "string"
},
"description": "List of account fields to return"
}
},
"required": [
"user_id"
],
"additionalProperties": false
}
}
}
3) Runtime flow (end-to-end)
1. User: "What is the email and plan for user 123?"
2. Audio → STT → transcribed text sent to LLM along with the tools array containing the schema above.
3. LLM decides to call get_account_info and returns a function call like:
◦ name: "get_account_info"
◦ arguments: {"user_id":"123","fields":["email","plan"]}
1. LiveKit framework intercepts the tool call, invokes AccountAgent.get_account_info(params) with params = that dict.
2. Tool returns the data (e.g., {"email":"dummy-email-for-123","plan":"dummy-plan-for-123"}).
3. Framework returns the tool result to the LLM; the LLM generates the final natural-language reply, e.g., "User 123 has email ... and is on plan ...", which is sent back to the user (TTS if voice).
If you prefer Node or want an example where the schema is derived from typed parameters (no params dict), tell me which and I’ll show that minimal example.
, with interactive elementscolossal-xylophone-68609
05/05/2026, 8:22 PM@function_tool()
async def tool_name(self, context: Context):
""" use this tool ... """
<tool body>tall-airline-16756
05/05/2026, 8:23 PM@function_tool()
async def tool_name(self, context: RunContext):
""" use this tool ... """
<tool body>
Short answer
• Yes — this is valid. The framework accepts a context (use RunContext) and the tool runs normally.
• The LLM will see this tool as accepting no user arguments. LiveKit generates a function schema with empty properties and no required fields, so the LLM can call it without arguments.
What RunContext is for
• RunContext is passed to your tool at runtime (not part of the LLM-visible schema). It provides access to session state, userdata, methods like disallow_interruptions() and wait_for_playout(), and other runtime helpers the tool can use internally.
Example generated schema sent to the LLM
{
"type": "function",
"function": {
"name": "tool_name",
"description": "use this tool ...",
"parameters": {
"type": "object",
"properties": {},
"required": []
}
}
}
Runtime flow (brief)
1. User input → STT → text + tools array (including schema above) sent to LLM.
2. LLM may return a function call: name = "tool_name", with empty/absent arguments.
3. Framework invokes tool_name(self, context) passing a RunContext.
4. Tool returns a value (string or structured data); result is passed back to the LLM for final reply.
When to use this pattern
• Use it when the tool performs a fixed action or uses/contributes to session state and doesn’t need user-supplied parameters.
• If you need user-provided inputs, add typed parameters (or supply raw_schema) so the LLM knows what arguments to provide.
If you want, tell me whether you want:
• an example that returns structured data, or
• the variant that accepts user args (typed params or raw_schema)
and I’ll show the minimal code + request payload.
Sources: Function tool definition | LiveKit Documentation, with interactive elements