c
hi
t
Thanks for reaching out, Arun. I'm an AI assistant and I'm here to help. Give me a moment while I research this topic.
Hi — welcome to LiveKit! How can I help you today? If you’re not sure where to start, you can tell me: • What you’re trying to build (web, iOS, Android, server-side) • Any error or unexpected behavior you’re seeing (copy/paste the message) • If you need setup help, share whether you’re using our hosted cloud or a self-hosted server Pick one and I’ll guide you through the next steps.
c
how do livkit deploye shtrere agemn 100s of agent will then dpely egent acode or one agen dybanmic enjetct
t
Thanks — I want to make sure I understand so I can give the right guidance. Do you mean "agents" as: • LiveKit server instances (rooms/servers) that host media, OR • Client-side participant apps/SDK instances (e.g., bots or automated participants), OR • Some custom worker/service that runs agent code and connects to LiveKit? Also tell me: • Are you using LiveKit Cloud (hosted) or a self-hosted LiveKit server? • Do you want to deploy hundreds of identical agents at once, or inject/modify code into running agents dynamically? • Are these agents ephemeral (spawn/terminate frequently) or long-lived? With those details I’ll explain deployment patterns and recommended approaches (e.g., container orchestration, autoscaling, or dynamic code injection alternatives).
c
how to liveikit agent s hosed on there cloud for ecplae if clreate 2 agents will the deyply to gent code or dynamic genrtions
t
Thanks — I want to make sure I’m answering the right question about LiveKit “agents.” Do you mean by “agent” one of these? • A server-side worker/service that programmatically connects to LiveKit (a bot/automation), or • A client participant (web/mobile SDK instance) that joins rooms, or • A LiveKit server instance/host (the LiveKit server process)? Also please confirm: • Are you using LiveKit Cloud (hosted) or self-hosted LiveKit? • Do you want to deploy a few fixed agent processes (e.g., 2) or hundreds that scale up/down automatically? • Do you expect to update agent code on deployed agents, or create agents dynamically from a template without redeploying code? Tell me which of the above matches your setup and goal and I’ll outline the recommended deployment pattern (how code is deployed, scaling options, and alternatives for dynamic behavior).
c
How does LiveKit deploy and scale agents? If you need hundreds of agents, do you deploy agent code many times, or run one agent and dynamically inject behavior?”
t
Thanks — I’ll answer your question about how LiveKit handles agents and scaling. Short answer: • You deploy agent code many times (multiple worker instances). LiveKit does not run one agent and dynamically inject different behaviors into it to serve many concurrent sessions. What that means and recommended approach: • Each agent runs as a separate worker process that registers with LiveKit and is assigned one job/session at a time. • To support hundreds of agents you run hundreds of worker instances (same code), typically managed by container orchestration (Kubernetes, autoscaling groups, etc.). • Agent dispatch routes jobs to available workers (round-robin / single-assignment) and considers availability, capacity, and proximity for routing. • For updates, use graceful draining so active sessions finish on existing workers while new sessions go to updated instances. If you want, I can: • Suggest a concrete autoscaling setup (Kubernetes HPA with CPU/job-queue metrics) or • Give a sample deployment layout (Dockerfile + k8s Deployment + HPA) or • Explain how to implement graceful draining for your agents Which option would you like next, and are you using LiveKit Cloud or self-hosted? Sources: Agents Framework | livekit/livekit_composite | DeepWiki | Server lifecycle | LiveKit docs | Custom agent deployments | LiveKit docs | Deploy and scale agents on LiveKit Cloud | Agents (Python) | livekit/livekit_composite | DeepWiki
c
so whenr ever i create agent on likcit cloud with the deply my agent code of yes how
t
Thanks — I’m responding about whether LiveKit Cloud deploys your agent code when you create/update an agent. Short answer: • Yes. Creating or deploying an agent on LiveKit Cloud packages your agent code, builds a container image from your Dockerfile, and deploys that image to LiveKit Cloud. Quick details: 1. Use
lk agent create
to package your current directory, upload code, build the container, and register the agent. 2. Use
lk agent deploy
after code changes — it uploads the new code, builds a new image, and performs a rolling deploy so new sessions go to the new version while existing sessions continue until finished (graceful shutdown up to ~1 hour). 3. You can roll back to a previous version (
lk agent rollback
) on paid plans. 4. To scale to many agents, run multiple worker instances (same image) via autoscaling (Kubernetes HPA or cloud autoscale). Each worker handles one session/job at a time. Would you like a sample
Dockerfile
+
lk
commands, or a recommended autoscaling setup for hundreds of agents? Sources: Deploying to production | LiveKit Docs
c
how can from livekit.agents import Agent, JobContext, JobProcess, WorkerOptions, cli from livekit.agents.voice import AgentSession from livekit.plugins import openai, cartesia, silero, deepgram, google from dotenv import load_dotenv from agents.utils import get_llm_from_config, get_stt_from_config, get_tts_from_config from agents.utils import WELCOME_MESSAGE, DEFAULT_SYSTEM_PROMPT from agents.utils.config import get_all_metadata load_dotenv() def prewarm(proc: JobProcess): proc.userdata.update({ "vad": silero.VAD.load(), }) class V1TestAgent(Agent): def __init__(self, meta_data): agent_info = meta_data['agent'] super().__init__( instructions=agent_info.get("system_prompt", DEFAULT_SYSTEM_PROMPT), stt=get_stt_from_config(meta_data['stt']), llm=get_llm_from_config(meta_data['llm']), tts=get_tts_from_config(meta_data['tts']) ) async def entrypoint(ctx: JobContext): meta_data = await get_all_metadata(ctx) agent = V1TestAgent(meta_data) session = AgentSession() await session.start(agent=agent, room=ctx.room) if True: await session.say(meta_data['agent'].get('greeting_message', WELCOME_MESSAGE)) async def on_session_end(): ctx.delete_room(ctx.room.name) ctx.add_shutdown_callback(on_session_end) def run(): cli.run_app( WorkerOptions( entrypoint_fnc=entrypoint, prewarm_fnc=prewarm, agent_name="v1_test_agent", port=8082, ) ) if name == "__main__": run() inject dynamically funciton tool calls
t
Thanks — I’m responding to your code snippet and your question: how to inject dynamic function/tool calls into an agent built with the livekit.agents framework. Short answer: • You register tools when constructing the Agent. Create tool callables programmatically from your metadata and pass them via the
tools
parameter to the Agent constructor. Each worker instance gets the configured tools when it starts a session. How to do it (concise steps + example): 1) Build a factory that converts metadata into tool callables (functions or objects wrapped as LiveKit tools). • Tools should be regular async functions that perform the action and return results. • Mark or wrap them according to the agent framework’s tool API (see your docs link for exact decorator/registration API). 2) Pass the generated tools into your Agent at init: • In your V1TestAgent init, call a helper like
create_dynamic_tools(meta_data['tools'])
and pass the returned list to
super().__init__(..., tools=...)
. 3) Use the agent’s LLM/tool invocation flow: • The agent’s LLM can call the registered tools during session runtime; no container redeploy is needed to change logic if you change metadata that the worker reads at startup or per-session. Minimal illustrative code sketch (adapt to framework decorators/API):
Copy code
def create_dynamic_tools(tool_configs):
    tools = []
    for cfg in tool_configs:
        async def tool_fn(ctx, input_text, cfg=cfg):
            # implement behavior from cfg (API calls, DB, etc.)
            return {"result": f"handled by {cfg['name']}"}
        # wrap/register tool_fn if framework requires a decorator or Tool class
        tools.append(tool_fn)
    return tools

class V1TestAgent(Agent):
    def __init__(self, meta_data):
        agent_info = meta_data['agent']
        dynamic_tools = create_dynamic_tools(meta_data.get('tools', []))
        super().__init__(
            instructions=agent_info.get("system_prompt", DEFAULT_SYSTEM_PROMPT),
            stt=get_stt_from_config(meta_data['stt']),
            llm=get_llm_from_config(meta_data['llm']),
            tts=get_tts_from_config(meta_data['tts']),
            tools=dynamic_tools,
        )
Deployment notes: • If metadata-driven tools change at runtime, have your entrypoint fetch fresh metadata per session (you already call
get_all_metadata(ctx)
in
entrypoint
) so each new session can receive updated tool configs without rebuilding the container. • If you change the agent code (tool implementation), you must redeploy the agent image (
lk agent deploy
). If you only change metadata (URLs, enable/disable tools, config), the agent can pick that up per-session if your code reads metadata at session start. Would you like: • A concrete example that uses the exact tool decorator/class from your LiveKit framework, or • A sample
create_dynamic_tools
that demonstrates calling an external API (OpenAI/Deepgram) based on metadata? Which one should I provide next? Sources: Deploy and scale agents on LiveKit Cloud | Deploying to production | LiveKit Docs | External data and RAG | LiveKit docs | Tool definition and use | LiveKit docs
c
give me few fruniton ttol cals
t
Below are a few concise example tool/function implementations you can adapt and register with your agent. Each example is an async callable suitable for wrapping as a tool in your Agent; they accept context and input, perform an action, and return a result dict/string. Adjust names and wrappers to match your framework’s tool decorator/API. 1) Simple echo tool
Copy code
async def echo_tool(ctx, input_text: str):
    """Returns the input back — useful for testing tool invocation."""
    return {"output": input_text}
2) External HTTP lookup (example: get user info)
Copy code
import aiohttp

async def fetch_user_tool(ctx, user_id: str):
    url = f"<https://api.example.com/users/{user_id}>"
    async with aiohttp.ClientSession() as s:
        async with s.get(url, timeout=10) as r:
            data = await r.json()
    return {"user": data}
3) Call OpenAI (or other LLM) for a short summary
Copy code
import openai

async def summarize_tool(ctx, text: str):
    resp = await openai.ChatCompletion.acreate(
        model="gpt-4o-mini",
        messages=[{"role": "user", "content": f"Summarize: {text}"}],
        max_tokens=150,
    )
    summary = resp.choices[0].message.content.strip()
    return {"summary": summary}
4) Transcribe audio via Deepgram (file URL)
Copy code
import aiohttp
import os

DEEPGRAM_KEY = os.getenv("DEEPGRAM_API_KEY")

async def transcribe_tool(ctx, audio_url: str):
    async with aiohttp.ClientSession() as s:
        headers = {"Authorization": f"Token {DEEPGRAM_KEY}"}
        async with s.post(
            "<https://api.deepgram.com/v1/listen>",
            params={"url": audio_url, "punctuate": True},
            headers=headers,
        ) as r:
            data = await r.json()
    transcript = data.get("results", {}).get("channels", [])[0].get("alternatives", [])[0].get("transcript", "")
    return {"transcript": transcript}
5) Trigger an action (e.g., start a call or create room) via LiveKit REST (example)
Copy code
import aiohttp
import os

LIVEKIT_KEY = os.getenv("LIVEKIT_API_KEY")
LIVEKIT_SECRET = os.getenv("LIVEKIT_API_SECRET")
LIVEKIT_URL = os.getenv("LIVEKIT_URL")  # e.g., <https://livekit.example.com>

async def create_room_tool(ctx, room_name: str):
    # Example: call your backend endpoint that creates a LiveKit room or uses server SDK
    async with aiohttp.ClientSession() as s:
        async with s.post(f"{LIVEKIT_URL}/rooms", json={"name": room_name}) as r:
            data = await r.json()
    return {"room": data}
Registering / wiring tips • Build these into a factory like
create_dynamic_tools
that returns a list of callables or Tool objects. • Pass them to your Agent via
super().__init__(..., tools=dynamic_tools)
. • Ensure tools are async and accept the parameters your LLM/tool-invocation layer will supply (often
ctx
and
input
). • Store secrets in environment variables and avoid blocking I/O (use aiohttp / async libraries). Would you like these wrapped in the exact tool decorator/class your framework requires (I can adapt to the LiveKit tool API if you paste the decorator or signature)?
c
how to call with the call autmaticlly or any inbuil fintioon tols
t
You're asking how tools get invoked automatically from an agent session — whether there’s a built‑in mechanism and how to wire it so your LLM triggers tools without manual calls. Short answer: the LLM/agent runtime invokes registered tools during the agent's reasoning loop. You register tools with the Agent, and when the model decides to call a tool (via function/call mechanism), the agent framework routes that call to your tool implementation and returns the result to the model. How to wire this up (concise steps) 1) Register tools on Agent init • Create async tool callables (or Tool objects / decorated functions if your SDK provides a decorator). • Pass them via the
tools=
parameter to
super().__init__(...)
so the agent knows available tools. 2) Ensure your LLM is configured to allow tool/function calling • Use an LLM wrapper that supports function/call style (OpenAI function-calling or equivalent). • Provide tool metadata (name, description, parameter schema) when you register the tool so the LLM can see what it can call. 3) At session start, supply per-session metadata if tools are dynamic • Your entrypoint already fetches metadata: call
create_dynamic_tools(meta['tools'])
and construct the Agent with those tools so each session has the right tool set. 4) Let the agent runtime handle invocation • During conversation, the LLM may return a function-call response. The agent runtime should: ◦ Detect the function-call intent from the LLM, ◦ Call the matching tool callable with parsed arguments, ◦ Capture the tool result and feed it back into the LLM as tool output (so the model continues reasoning). • You do not need to call tools manually unless you want to trigger them programmatically. Minimal code pattern (adapt to your framework’s decorator/API)
Copy code
# create tool factory
def create_dynamic_tools(tool_configs):
    tools = []
    for cfg in tool_configs:
        async def tool_fn(ctx, **params):
            # implement tool behavior based on cfg and params
            return {"result": f"done {cfg['name']}"}
        # attach metadata so LLM knows name/params/description if needed
        tool_fn._meta = {"name": cfg["name"], "description": cfg.get("desc",""), "schema": cfg.get("schema",{})}
        tools.append(tool_fn)
    return tools

class V1TestAgent(Agent):
    def __init__(self, meta_data):
        dynamic_tools = create_dynamic_tools(meta_data.get("tools", []))
        super().__init__(..., tools=dynamic_tools)
Notes & best practices • If using OpenAI-style function calling, include each tool’s parameter schema so the model can produce structured arguments. • Keep tools idempotent and safe — the agent runtime may retry or re-run tools during reasoning. • For dynamic updates to tools, change metadata (so new sessions pick up new tools). For code changes to tool implementations, redeploy the agent. • If you want to trigger a tool proactively from your code (not via model), call the tool callable directly with ctx and params. Would you like: • A concrete example using the exact LiveKit tool decorator/signature (paste the decorator or Tool class), or • A full working example that demonstrates a model function-call -> tool execution -> response loop using OpenAI function-calling?
c
i anr geyttin give hard code data
t
Thanks — I’ll give you concrete, hard‑coded tool examples, show how to register them on your Agent, and demonstrate the automatic function-call flow (LLM -> tool -> model). This adapts your existing code pattern. 1) Hard-coded tool functions (async)
Copy code
# tools.py
import aiohttp
import os

async def echo_tool(ctx, text: str):
    return {"output": text}

async def fetch_user_tool(ctx, user_id: str):
    async with aiohttp.ClientSession() as s:
        async with s.get(f"<https://api.example.com/users/{user_id}>", timeout=10) as r:
            return {"user": await r.json()}

DEEPGRAM_KEY = os.getenv("DEEPGRAM_API_KEY")
async def transcribe_tool(ctx, audio_url: str):
    async with aiohttp.ClientSession() as s:
        headers = {"Authorization": f"Token {DEEPGRAM_KEY}"}
        async with s.post(
            "<https://api.deepgram.com/v1/listen>",
            params={"url": audio_url, "punctuate": True},
            headers=headers,
        ) as r:
            data = await r.json()
    transcript = (data.get("results", {}).get("channels", [])[0]
                    .get("alternatives", [])[0].get("transcript", ""))
    return {"transcript": transcript}
2) Register tools on the Agent (pass tools list to super)
Copy code
# agent_module.py
from livekit.agents import Agent
from tools import echo_tool, fetch_user_tool, transcribe_tool

def create_hardcoded_tools():
    # Attach metadata the LLM needs (name, description, and param schema)
    # Adapt keys/structure if your Agent framework expects a Tool class/decorator.
    def wrap(fn, name, desc, schema):
        fn._meta = {"name": name, "description": desc, "schema": schema}
        return fn

    return [
        wrap(echo_tool, "echo", "Echo input text", {"type": "object", "properties": {"text": {"type":"string"}}}),
        wrap(fetch_user_tool, "fetch_user", "Get user info by ID", {"type":"object", "properties": {"user_id": {"type":"string"}}}),
        wrap(transcribe_tool, "transcribe", "Transcribe audio from URL", {"type":"object", "properties": {"audio_url": {"type":"string"}}}),
    ]

class V1TestAgent(Agent):
    def __init__(self, meta_data):
        tools = create_hardcoded_tools()
        agent_info = meta_data['agent']
        super().__init__(
            instructions=agent_info.get("system_prompt", "You are an assistant."),
            stt=None,
            llm=None,
            tts=None,
            tools=tools,
        )
3) How automatic invocation works (flow you don’t need to manual-call) • The Agent framework exposes the registered tool metadata to the LLM (name + param schema). • When the model decides to call a tool, it emits a function-call response (name + args). • The agent runtime detects the function-call, matches by name to your registered callable, invokes it with parsed args, then feeds the result back into the model so it can continue reasoning. You do not have to manually invoke tools in ordinary use; ensure: • Tools are registered on Agent init. • Tool metadata (name/schema) is present so the LLM can produce structured args. • Your LLM wrapper supports function-calling (OpenAI-style or equivalent). 4) Example: simulating a function-call cycle (conceptual)
Copy code
# simplified pseudo-flow inside agent runtime
llm_response = await llm.chat(messages, tools=tools_meta)  # model may return a function-call
if llm_response.type == "function_call":
    fname = llm_response.name
    args = llm_response.arguments  # parsed by schema
    tool_fn = find_tool_by_name(fname)  # your registry
    tool_result = await tool_fn(ctx, **args)
    # feed result back to the model as a tool output message
    messages.append({"role":"tool", "name": fname, "content": json.dumps(tool_result)})
    llm_response = await llm.chat(messages)  # model continues
5) If you want dynamic switching later • For metadata-driven dynamic tools: read metadata in
entrypoint
and construct tools per-session (you already call
get_all_metadata(ctx)
— use it to decide which hard-coded tools to enable or what configs/URLs to pass into tool closures). • For code changes (implementation), redeploy agent; for config changes (URLs, enabling tools), update metadata so new sessions pick them up. Would you like me to: • Wrap these with the exact LiveKit tool decorator/class (paste the decorator/signature), or • Produce a complete working example that demonstrates a real OpenAI function-call -> tool execution -> final answer using your framework?
c
tools: list[llm.FunctionTool | llm.RawFunctionTool | llm.ProviderTool] | None = None,explain all how to sue in agent
t
You're asking how to use the
tools: list[llm.FunctionTool | llm.RawFunctionTool | llm.ProviderTool] | None
parameter in an Agent. Below is a concise guide showing what each tool type is, how to define/register them, and how the agent/LLM invokes them — with minimal, copy‑pasteable snippets. 1) llm.FunctionTool — high‑level Python function tools • Purpose: Define async Python functions (or methods) with typed args. The framework exposes name/params/docs to the LLM so the model can produce structured function calls. • When to use: Custom logic, DB/API calls, stateful operations you run in your agent process. Example (method on Agent using decorator):
Copy code
from livekit.agents import function_tool, Agent

class MyAgent(Agent):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)

    @function_tool()
    async def get_weather(self, location: str) -> str:
        # implement external API call or logic
        return f"Weather in {location}: sunny, 75°F"
• The decorator registers metadata (name, params, description) automatically from signature/docstring. 2) llm.RawFunctionTool — raw schema + low‑level handler • Purpose: Provide a JSON schema for parameters and receive a raw dict of args. Use when signature mapping is inconvenient or dynamic schemas are required. • When to use: Dynamic tool sets, tools pulled from external configs, or non‑standard param handling. Example:
Copy code
from livekit.agents import function_tool

weather_schema = {
  "name": "get_weather",
  "description": "Get current weather for a city",
  "parameters": {
    "type": "object",
    "properties": {
      "location": {"type": "string"}
    },
    "required": ["location"]
  }
}

@function_tool(raw_schema=weather_schema)
async def raw_get_weather(raw_args: dict):
    location = raw_args["location"]
    return {"text": f"Weather in {location}: 70°F, partly cloudy"}
• The LLM will produce arguments matching the schema; your handler parses
raw_args
. 3) llm.ProviderTool — provider‑side capabilities • Purpose: Use tools implemented by the model provider (search, code execution, etc.). The provider executes the tool; the model can return/provider results. • When to use: Built‑in provider features like web search, specialized APIs offered by the LLM provider. Example (plugin style):
Copy code
from livekit.plugins import google
from livekit.agents import Agent

class MyAgent(Agent):
    def __init__(self):
        super().__init__(
            instructions="...",
            tools=[google.tools.GoogleSearch()]  # provider tool
        )
• Provider tools run outside your process; include them in
tools
so the model can invoke them. How to register tools on Agent • Pass tool callables or provider tool objects to Agent via
tools=
in
super().__init__
or decorate methods. Example:
Copy code
tools = [self.get_weather, raw_get_weather, google.tools.GoogleSearch()]
super().__init__(..., tools=tools)
How tools are invoked automatically (runtime flow) 1. Agent exposes tools' metadata (name, description, param schema) to the LLM when calling
llm.chat
. 2. The LLM may return a function‑call response (name + structured args). 3. Agent runtime matches name → tool callable (or provider tool). 4. Agent invokes the tool with parsed args (or provider executes it), captures result. 5. Agent injects tool output as a “tool” message back into the LLM and continues the reasoning loop. You do not need to manually call tools unless you want to trigger them from your code. Dynamic tools & updates • Per‑session dynamic tools: Build tools from metadata in your
entrypoint
(you already call
get_all_metadata(ctx)
) and pass them into Agent so each session gets the proper tool set. • Runtime updates: Use
await agent.update_tools(new_tools)
to add/remove tools on a running agent. • Code changes require redeploy; metadata/URL/config changes can be picked up per session without redeploy if your code reads metadata per session. Best practices • Provide clear names, descriptions, and schemas so the LLM reliably chooses the right tool. • Keep tools idempotent and safe; handle errors with
ToolError
where appropriate. • Limit number of tools to avoid confusion; prefer narrow, focused tools. • Use provider tools where heavy work should run on provider side (search, code exec). If you want, I can: • Convert one of your hard‑coded examples into a proper
@function_tool
or
raw_schema
version, or • Show a full flow: model function‑call → tool execution → feeding result back into model with your Agent code. Which do you prefer? Sources: Tool definition and use | LiveKit docs | Google Gemini LLM plugin guide | LiveKit docs | xAI Grok Voice Agent API plugin | LiveKit docs | Modifying LLM output before TTS | LiveKit docs
c
await agent.update_tools(new_tools) hwo to sue this in agen t giv reexmple
t
Thanks — I’ll show a concrete, minimal example of using
await agent.update_tools(new_tools)
in a LiveKit Agent (Python), plus a few quick notes and best practices. Python example — start with one tool, then add another at runtime:
Copy code
from livekit.agents import Agent, function_tool, AgentSession

# initial tool
@function_tool()
async def echo_tool(text: str) -> str:
    return f"echo: {text}"

# new tool to add later
@function_tool()
async def time_tool(zone: str) -> str:
    return f"time in {zone}: 12:00"  # replace with real lookup

class MyAgent(Agent):
    def __init__(self):
        super().__init__(instructions="Agent with dynamic tools", tools=[echo_tool])

    async def on_enter(self):
        # Called when a session starts — demonstrate updating tools here
        # Preserve existing tools and add the new one
        current = list(self.tools)  # existing registered tools
        new_tools = current + [time_tool]
        await self.update_tools(new_tools)

        # Optionally notify the user
        await self.session.generate_reply("Added a new tool: time_tool")

# run a session (entrypoint-like usage)
agent = MyAgent()
session = AgentSession(agent=agent)
await session.start(room="example_room")
Key points •
update_tools
replaces the agent’s tool list. To add, combine
agent.tools
with new tools; to remove, filter and call
update_tools(filtered_list)
. • Call it at session start (
on_enter
,
entrypoint
, or similar) for per-session tool changes, or call from any async handler during runtime to change capabilities mid-session. • Tool functions should be async and decorated/registered (e.g.,
@function_tool()
), or be provider/tool objects if using provider tools. • Code changes to tool implementations require redeploy;
update_tools
only changes which callables are available at runtime. • Ensure tools have clear names/descriptions/schemas so the LLM can call them reliably. Would you like a full working example showing the model function-call -> tool execution -> result loop, or a Node.js equivalent? Which one? Sources: Tool definition and use | LiveKit docs
c
can we add only ain on enter on enay where
t
Short answer: Yes — you can call
await agent.update_tools(new_tools)
in
on_enter
(or your entrypoint) to add tools once at session start, and you can also call it anywhere async during the agent lifecycle to change tools later. Both are supported. When to use each: • Use
on_enter
/ entrypoint to set per-session tools once (recommended when tools depend on session metadata or permissions). • Call
update_tools
later from any async handler (event, command, tool result) to add/remove tools mid‑session (useful for escalation, permissions changes, or progressive disclosure). • Avoid excessively frequent updates;
update_tools
replaces the full tool list, so preserve existing tools when adding (e.g.,
await agent.update_tools(agent.tools + [new_tool])
). Minimal example — add a tool once in on_enter:
Copy code
class MyAgent(Agent):
    def __init__(self):
        super().__init__(instructions="...")

    async def on_enter(self):
        # per-session metadata decided which tools to enable
        new_tools = list(self.tools) + [time_tool]
        await self.update_tools(new_tools)
        await self.session.generate_reply("Tools enabled for this session.")
Example — add a tool later (mid‑session):
Copy code
async def handle_command(self, cmd: str):
        if cmd == "enable-advanced":
            await self.update_tools(list(self.tools) + [advanced_tool])
            await self.session.generate_reply("Advanced tool enabled.")
Notes: •
update_tools
replaces the entire tool list; merge if you want to keep old tools. • Tool implementations (code) must be deployed in the agent process;
update_tools
only changes availability, not code. • Use clear names/schemas so the LLM reliably selects tools. Would you like a full entrypoint example that reads metadata and enables a specific tool only for that session?
c
from livekit.agents import Agent, JobContext, JobProcess, WorkerOptions, cli, function_tool from livekit.agents.voice import AgentSession from livekit.plugins import openai, cartesia, silero, deepgram, google from dotenv import load_dotenv from agents.utils import get_llm_from_config, get_stt_from_config, get_tts_from_config from agents.utils import WELCOME_MESSAGE, DEFAULT_SYSTEM_PROMPT from agents.utils.config import get_all_metadata, get_tools_from_metadata load_dotenv() def prewarm(proc: JobProcess): proc.userdata.update({ "vad": silero.VAD.load(), }) class V1TestAgent(Agent): def __init__(self, meta_data): agent_info = meta_data['agent'] super().__init__( instructions=agent_info.get("system_prompt", DEFAULT_SYSTEM_PROMPT), stt=get_stt_from_config(meta_data['stt']), llm=get_llm_from_config(meta_data['llm']), tts=get_tts_from_config(meta_data['tts']), tools=get_tools_from_metadata(meta_data['tool_calls'], []) ) @function_tool() async def get_weather(self) -> str: return f"Weather sunny, 75°F" async def entrypoint(ctx: JobContext): meta_data = await get_all_metadata(ctx) agent = V1TestAgent(meta_data) session = AgentSession() await session.start(agent=agent, room=ctx.room) if True: await session.say(meta_data['agent'].get('greeting_message', WELCOME_MESSAGE)) async def on_session_end(): ctx.delete_room(ctx.room.name) ctx.add_shutdown_callback(on_session_end) def run(): cli.run_app( WorkerOptions( entrypoint_fnc=entrypoint, prewarm_fnc=prewarm, agent_name="v1_test_agent", port=8082, ) ) if name == "__main__": run() how to add @function_tool() async def get_weather(self) -> str: return f"Weather sunny, 75°F" dubnaily inttotls
t
You're almost there — add your decorated method to the agent's tool list and/or merge it with metadata-derived tools. Two ways: register as an instance method tool, or add it at runtime. 1) Register the method on init (merge with metadata tools)
Copy code
from livekit.agents import function_tool

class V1TestAgent(Agent):
    def __init__(self, meta_data):
        agent_info = meta_data['agent']
        # metadata tools (returns list of callables or Tool objects)
        meta_tools = get_tools_from_metadata(meta_data.get('tool_calls', []), [])
        # include the bound method get_weather
        tools = meta_tools + [self.get_weather]
        super().__init__(
            instructions=agent_info.get("system_prompt", DEFAULT_SYSTEM_PROMPT),
            stt=get_stt_from_config(meta_data['stt']),
            llm=get_llm_from_config(meta_data['llm']),
            tts=get_tts_from_config(meta_data['tts']),
            tools=tools,
        )

    @function_tool()
    async def get_weather(self) -> str:
        return "Weather sunny, 75°F"
2) Add at session start (update_tools in on_enter or entrypoint)
Copy code
class V1TestAgent(Agent):
    # init without get_weather in tools, or with meta_tools only
    ...

    async def on_enter(self):
        # add get_weather to the current toolset
        await self.update_tools(list(self.tools) + [self.get_weather])
        await self.session.generate_reply("Weather tool enabled for this session.")
Notes: • Use
self.get_weather
(bound method) so the tool has access to agent state. •
get_tools_from_metadata
should return callable/tool objects compatible with the agent SDK. •
update_tools(...)
replaces the full list — merge
self.tools
to preserve existing tools. • If you want the tool available only for new sessions, construct tools from metadata in
entrypoint
before creating the agent instance (you already fetch metadata there). Which approach do you want: register on init, or add during on_enter/entrypoint? I can provide the exact placement in your current entrypoint code.
c
?
t
Thanks — I’m following up on your last message about the URL: https://cloud.livekit.io/projects/p_62vysos8yme/ingresses not being in the whitelist (I think you mean “white theme” or “whitelist”). Can you confirm which of these you mean? • Do you mean the site appearance (white theme / dark theme) in the LiveKit Cloud dashboard? • Or do you mean the URL is not in a CORS/ingress/allowed‑hosts whitelist for your project (so requests are blocked)? If it’s the whitelist (allowed origins/ingresses), tell me: • Which exact whitelist you need to update (CORS origins, OAuth redirect URIs, or ingress/allowed host list), and • Whether you’re trying to add that URL for API calls from a browser or for server/webhook routing. If it’s the theme/display issue, tell me: • Which browser and OS you’re using, and whether other pages in the dashboard show the correct theme. Give me one of those answers and I’ll provide the exact steps to fix it.
c
i see dahsboar din dark wthe can i see in white theme?
t
You can switch the LiveKit Cloud dashboard to a light (white) theme if your browser or OS theme is set to light — LiveKit follows the system/browser preference. Try one of these: • Toggle your OS or browser from dark → light (e.g., macOS System Settings > Appearance → Light; Windows Settings > Personalization > Colors → Light). • In Chrome/Edge, you can force a light theme by disabling dark mode extensions or using an extension that forces light themes. • If you’re using a browser-level dark theme or an accessibility setting, disable it and reload the dashboard. I don’t have a separate in-app theme toggle available to enable directly. After you try the above, let me know your OS and browser (and whether you use any dark-mode extensions) and I’ll give exact steps.
c
cant we udpate in dahsabord
t
Short answer: No — there isn’t an in‑dashboard toggle to change LiveKit Cloud’s theme. The dashboard follows your system/browser color preference. What you can do: • Switch your OS appearance to Light (macOS: System Settings → Appearance → Light; Windows: Settings → Personalization → Colors → Choose your mode → Light). • Or switch your browser to light mode / disable any dark‑theme extensions and reload the dashboard. • As a quick test, open the dashboard in a different browser/profile that uses a light theme. If you tell me your OS and browser (and whether you use a dark‑mode extension), I’ll give step‑by‑step instructions to get the dashboard showing the light theme.
c
from livekit.agents import Agent, JobContext, JobProcess, WorkerOptions, cli, function_tool from livekit.agents.voice import AgentSession from livekit.plugins import openai, cartesia, silero, deepgram, google from dotenv import load_dotenv from agents.utils import get_llm_from_config, get_stt_from_config, get_tts_from_config from agents.utils import WELCOME_MESSAGE, DEFAULT_SYSTEM_PROMPT from agents.utils.common_function_tool import get_weather from agents.utils.config import get_all_metadata, get_tools_from_metadata load_dotenv() def prewarm(proc: JobProcess): proc.userdata.update({ "vad": silero.VAD.load(), }) class V1TestAgent(Agent): def __init__(self, meta_data): agent_info = meta_data['agent'] super().__init__( instructions=agent_info.get("system_prompt", DEFAULT_SYSTEM_PROMPT), stt=get_stt_from_config(meta_data['stt']), llm=get_llm_from_config(meta_data['llm']), tts=get_tts_from_config(meta_data['tts']), tools=get_tools_from_metadata(meta_data['tool_calls'], []), ) @function_tool() async def get_weather(self, city: str) -> str: await get_weather(city) async def entrypoint(ctx: JobContext): meta_data = await get_all_metadata(ctx) agent = V1TestAgent(meta_data) session = AgentSession() await session.start(agent=agent, room=ctx.room) if True: await session.say(meta_data['agent'].get('greeting_message', WELCOME_MESSAGE)) async def on_session_end(): ctx.delete_room(ctx.room.name) ctx.add_shutdown_callback(on_session_end) def run(): cli.run_app( WorkerOptions( entrypoint_fnc=entrypoint, prewarm_fnc=prewarm, agent_name="v1_test_agent", port=8082, ) ) if name == "__main__": run() how to setup call recorin gor this agent
t
You're asking how to enable call recording for the agent code you pasted. Short answer: recording is configured at the room/ingress (infrastructure) level — typically via auto‑egress on your SIP/room configuration — not inside the agent code. Your agent does not need special recording logic; configure egress/recording for the rooms your agent uses. Concrete steps you can take 1) Enable auto‑egress on the dispatch rule or room creation • Configure the SIP dispatch rule or room config with an egress/autoEgress setting that points to your recording destination (S3/GCS pre-signed upload, webhook, or file server). • Example fields: file_prefix, output destination (S3/GCS URL or webhook endpoint). 2) Example (conceptual Python SDK snippet) • Create a dispatch rule / room config with AutoEgressConfiguration (adjust to your SDK shape):
Copy code
# pseudocode — adapt to your SDK
room_config = RoomConfiguration(
    egress=AutoEgressConfiguration(
        file_prefix="recordings/call-",
        file="<https://your-upload-endpoint.example.com/upload>"
    )
)
# attach this room_config when creating SIP dispatch rule or room
3) Verify destination permissions and storage • Ensure your S3/GCS bucket accepts uploads or that your webhook can receive file uploads. • If using S3/GCS, prefer pre-signed URLs or configured provider integration. 4) Confirm recordings in the dashboard/storage • After enabling egress, initiate a session routed to the configured room/dispatch rule and check your storage bucket or webhook logs for output files. • Dashboard: check SIP / Dispatch Rules and your storage to confirm files appear. 5) Notes relevant to your agent code • No code changes required in your agent to record audio; egress captures the room audio independent of AgentSession. • Keep VAD/STT/TTS as-is; recordings will include mixed audio unless you configure selective tracks. • If you need per-session programmatic control (start/stop egress), use the SDK/egress API to start egress for a room — but auto‑egress is recommended for automatic recording. If you want, I can: • Provide a full concrete code example matching the exact LiveKit SDK methods you use (paste which SDK/lang + whether you use LiveKit Cloud or self-hosted), or • Help you produce the exact SIP dispatch rule / room config JSON with your S3/GCS details to enable auto‑recording. Which would you like? Sources: sip | livekit/livekit_composite | DeepWiki | examples/bank-ivr | livekit/livekit_composite | DeepWiki | Simple Call Answering Agent | LiveKit docs
c
ful code
t
Thanks — I’ll give a full, concrete Python example that shows: • how to configure auto‑egress (recording) when creating a room, • how to create a SIP dispatch rule with egress (if you use SIP), • and how your existing agent entrypoint/session code stays the same. This example assumes you use LiveKit Cloud and the LiveKit Python admin API (adjust names if your SDK differs). Replace placeholders (API keys, project ID, S3 details, etc.) with your values. 1) Install prerequisites • Ensure you have the LiveKit admin SDK or HTTP client available and aiohttp installed. 2) Full example (create room with auto‑egress to an S3 pre-signed upload endpoint, then run your agent)
Copy code
# record_agent_example.py
import os
import asyncio
from livekit import LivekitSDKClient  # adjust import to your livekit admin SDK client
from livekit.models import Room, RoomConfiguration, AutoEgressConfiguration
from livekit.agents import Agent, function_tool, JobContext, JobProcess, WorkerOptions, cli
from livekit.agents.voice import AgentSession
from dotenv import load_dotenv
from agents.utils import get_llm_from_config, get_stt_from_config, get_tts_from_config
from agents.utils import WELCOME_MESSAGE, DEFAULT_SYSTEM_PROMPT
from agents.utils.config import get_all_metadata, get_tools_from_metadata

load_dotenv()

LIVEKIT_API_KEY = os.getenv("LIVEKIT_API_KEY")
LIVEKIT_API_SECRET = os.getenv("LIVEKIT_API_SECRET")
LIVEKIT_BASE_URL = os.getenv("LIVEKIT_BASE_URL")  # e.g. <https://cloud.livekit.io>

# --- ADMIN: functions to create room with auto-egress ---
async def create_room_with_egress(client: LivekitSDKClient, room_name: str, s3_presign_upload_url: str):
    """
    Create a room with auto egress recording configured to upload files to your endpoint.
    s3_presign_upload_url: an endpoint that accepts file uploads (pre-signed S3/GCS URL or your upload webhook).
    """
    room_config = RoomConfiguration(
        egress=AutoEgressConfiguration(
            file_prefix=f"recordings/{room_name}-",
            file=s3_presign_upload_url  # destination for recorded file
        )
    )

    room = Room(name=room_name, config=room_config)
    # Adjust to the admin client method your SDK uses
    created = await client.create_room(room)
    return created

# --- Your Agent code (adapted from your snippet) ---
def prewarm(proc: JobProcess):
    proc.userdata.update({"vad": None})  # load silero VAD if needed

class V1TestAgent(Agent):
    def __init__(self, meta_data):
        agent_info = meta_data["agent"]
        tools = get_tools_from_metadata(meta_data.get("tool_calls", []), [])
        # include any hard-coded tools by adding bound methods (if defined)
        super().__init__(
            instructions=agent_info.get("system_prompt", DEFAULT_SYSTEM_PROMPT),
            stt=get_stt_from_config(meta_data.get("stt")),
            llm=get_llm_from_config(meta_data.get("llm")),
            tts=get_tts_from_config(meta_data.get("tts")),
            tools=tools,
        )

    @function_tool()
    async def get_weather(self, city: str) -> str:
        return f"Weather sunny, 75°F in {city}"

async def entrypoint(ctx: JobContext):
    # 1) create room with egress so every session recorded
    # build admin client (adjust for your SDK; this shows async pattern)
    admin_client = LivekitSDKClient(base_url=LIVEKIT_BASE_URL, api_key=LIVEKIT_API_KEY, api_secret=LIVEKIT_API_SECRET)

    room_name = ctx.room.name
    # Obtain or construct an upload endpoint for recordings:
    # - Option A: pre-signed S3/GCS URL you generate server-side
    # - Option B: a webhook/upload endpoint that accepts POST file uploads
    # For this example, expect an environment variable with your upload URL/presigned endpoint:
    upload_endpoint = os.getenv("RECORDING_UPLOAD_URL")
    if not upload_endpoint:
        # fallback: create room without egress if no upload configured
        print("No RECORDING_UPLOAD_URL set; creating room without auto-egress.")
    else:
        # create or update room with auto-egress
        try:
            await create_room_with_egress(admin_client, room_name, upload_endpoint)
            print(f"Created room {room_name} with auto-egress -> {upload_endpoint}")
        except Exception as e:
            print("Failed to create room with egress:", e)

    # 2) start your agent session as before
    meta_data = await get_all_metadata(ctx)
    agent = V1TestAgent(meta_data)
    session = AgentSession()

    await session.start(agent=agent, room=ctx.room)

    await session.say(meta_data["agent"].get("greeting_message", WELCOME_MESSAGE))

    async def on_session_end():
        # Optionally ensure egress finished or cleanup
        try:
            # If your SDK supports checking egress/recording status, do it here
            pass
        finally:
            await ctx.delete_room(ctx.room.name)

    ctx.add_shutdown_callback(on_session_end)

def run():
    cli.run_app(
        WorkerOptions(
            entrypoint_fnc=entrypoint,
            prewarm_fnc=prewarm,
            agent_name="v1_test_agent",
            port=8082,
        )
    )

if __name__ == "__main__":
    run()
3) How to provide the recording destination • Preferred: generate an S3 pre-signed PUT URL for the object and set RECORDING_UPLOAD_URL to that URL (or a URL prefix scheme your backend supports). LiveKit will upload the recorded file to the given
file
destination. • Alternatively, expose a secure webhook endpoint that accepts multipart file uploads and set RECORDING_UPLOAD_URL to that endpoint. 4) SIP dispatch rule approach (if you use SIP trunking) • If you route calls via SIP dispatch rules, configure the dispatch rule with
room_config.egress
the same way so any room created by the rule is automatically recorded. • Use the admin SDK to create a SIP dispatch rule and include the same AutoEgressConfiguration. 5) Verify recordings • Start a session routed to the created room. • Check your upload endpoint or S3 bucket for the recorded file once the session ends. • Monitor LiveKit dashboard SIP / Rooms to confirm egress status. Notes & troubleshooting • If your SDK names differ, adapt the admin client calls to match. The critical fields are
room.config.egress.file
and
room.config.egress.file_prefix
. • Ensure your upload endpoint accepts the HTTP method and content LiveKit uses. If using S3 pre-signed URLs, ensure the URL is valid for PUT and the bucket CORS/policies allow uploads. • Auto-egress may have provider/plan limitations — verify your LiveKit Cloud plan supports automatic egress. If you want, I can: • Convert this to the exact admin SDK calls for your environment if you paste which LiveKit admin SDK package/version you’re using (or if you use raw HTTP, I’ll craft the exact REST payload), or • Provide an example showing how to generate an S3 pre-signed URL (boto3) and wire it into RECORDING_UPLOAD_URL. Which do you prefer?