cuddly-vr-85751
12/28/2025, 4:46 PMtall-airline-16756
12/28/2025, 4:46 PMtall-airline-16756
12/28/2025, 4:46 PMcuddly-vr-85751
12/28/2025, 4:46 PMtall-airline-16756
12/28/2025, 4:47 PMcuddly-vr-85751
12/28/2025, 4:47 PMtall-airline-16756
12/28/2025, 4:48 PMcuddly-vr-85751
12/28/2025, 4:48 PMtall-airline-16756
12/28/2025, 4:48 PMcuddly-vr-85751
12/28/2025, 4:49 PMtall-airline-16756
12/28/2025, 4:50 PMlk 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 Docscuddly-vr-85751
12/28/2025, 4:55 PMtall-airline-16756
12/28/2025, 4:56 PMtools 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):
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 docscuddly-vr-85751
12/28/2025, 5:03 PMtall-airline-16756
12/28/2025, 5:04 PMasync 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)
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
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)
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)
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)?cuddly-vr-85751
12/28/2025, 5:06 PMtall-airline-16756
12/28/2025, 5:06 PMtools= 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)
# 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?cuddly-vr-85751
12/28/2025, 5:07 PMtall-airline-16756
12/28/2025, 5:08 PM# 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)
# 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)
# 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?cuddly-vr-85751
12/28/2025, 5:12 PMtall-airline-16756
12/28/2025, 5:13 PMtools: 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):
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:
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):
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:
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 docscuddly-vr-85751
12/28/2025, 5:17 PMtall-airline-16756
12/28/2025, 5:17 PMawait 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:
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 docscuddly-vr-85751
12/28/2025, 5:18 PMtall-airline-16756
12/28/2025, 5:18 PMawait 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:
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):
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?cuddly-vr-85751
12/28/2025, 5:21 PMtall-airline-16756
12/28/2025, 5:21 PMfrom 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)
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.cuddly-vr-85751
12/28/2025, 5:37 PMcuddly-vr-85751
12/28/2025, 5:37 PMtall-airline-16756
12/28/2025, 5:37 PMcuddly-vr-85751
12/28/2025, 5:37 PMtall-airline-16756
12/28/2025, 5:38 PMcuddly-vr-85751
12/28/2025, 5:39 PMtall-airline-16756
12/28/2025, 5:39 PMcuddly-vr-85751
12/28/2025, 6:27 PMtall-airline-16756
12/28/2025, 6:27 PM# 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 docscuddly-vr-85751
12/28/2025, 6:29 PMtall-airline-16756
12/28/2025, 6:29 PM# 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?