b
hi
t
Thanks for reaching out, Yashpratap. I'm an AI assistant and I'm here to help. Give me a moment while I research this topic.
Hi — welcome to LiveKit! What can I help you with today? If you’re not sure where to start, you can tell me: • whether you’re integrating LiveKit (SDK, REST, or server-side), • if you’re seeing an error (paste the error and where it appears), • which platform (web, iOS, Android, or server) you’re using, • or share your goal (join rooms, record sessions, scale participants, etc.). Pick one and I’ll guide you next.
b
how does livekit works in a kubernetes arhictecture? Like how does the livekit server which is serving as a load balancer know to which pod it needs to dispatch the room request. Because for a load balancer all the pods will be corresponding to a single public ip. Assume I'm self hosting the livekit.
t
You're asking how LiveKit behaves when self-hosted on Kubernetes and how room requests get routed when all pods sit behind a single public IP. Here’s how it works and what to configure. Short answer • An external Layer‑4 (TCP) load balancer (e.g., cloud NLB or Kubernetes Service type=LoadBalancer) distributes each client’s initial WebSocket/TCP connection to one LiveKit pod. • That pod acts as a signaling bridge. LiveKit uses Redis to coordinate cluster state and pick which node will host the room. • One node hosts the room; other pods proxy signaling to that host. Room hosting is chosen by load/region criteria, not by the load balancer’s IP routing. Key flow 1. Client connects to wss://livekit.example.com → external TCP load balancer → a backend pod. 2. The receiving pod checks Redis to see if the requested room exists. 3. If room exists, Redis tells which node is the host and the pod proxies signaling to that host. 4. If room does not exist, the pod selects a host node (node selector uses load and optional region settings), marks it in Redis, and that node becomes the room host. 5. Media and SFU responsibilities are handled by the chosen host node; other pods only proxy signaling for participants that connected to them. Why single public IP is okay • The load balancer distributes connections to different pod backends despite a single public IP. • Internal coordination (Redis) ensures consistent per-room ownership regardless of which pod accepted the initial connection. What you need for a Kubernetes self-hosted deployment • External Layer‑4 load balancer (NLB / Service type=LoadBalancer or equivalent) to accept client TCP/WebSocket connections. • Redis for cluster coordination and state (required for multi-node deployments). • Configure LiveKit node settings (e.g.,
sysload_limit
, optional region tags) so the node selector can pick appropriate hosts. • Ensure networking allows pods to proxy to each other (pod-to-pod connectivity) and that Redis is reachable by all nodes. If you want, I can: • Show an example Kubernetes Service and Deployment snippet for LiveKit + Redis. • Explain node selector options (sysload, region) and where to configure them. • Walk through how a client join request looks (signaling + Redis checks). Which of these would you like next? Sources: Deploy to Kubernetes | LiveKit docs | Distributed Setup | LiveKit docs
b
give me the diagram flow in a production self hosted livekit in a kubernetes architecture.
t
Understood — below is a clear diagram flow (ASCII + short labels) you can use for a production self‑hosted LiveKit deployment on Kubernetes, followed by brief notes on each component and where decisions (room host selection, proxying) happen. Diagram (flow left → right) Client(s) ↓ DNS ↓ External Layer‑4 Load Balancer (NLB / Service type=LoadBalancer) ├─ HTTPS/WSS → Ingress Controller / TLS Termination └─ TCP/UDP → forwards raw WebRTC ports to nodes ↓ LiveKit Server Pods (hostNetwork: true) ←→ Redis (coordination + pub/sub) ├─ Pod A (accepts client conn) ←─── reports stats / reads room state ├─ Pod B (possible room host) └─ Pod C (possible room host) │ ├─ If room exists: Pod A proxies signaling to room host (Pod B) ├─ If room missing: Pod A selects host via node selector (sysload/region), writes to Redis, Pod B becomes host └─ Media (RTP/RTCP over UDP) flows between clients and room host pod ↓ Optional components: • Ingress Service (RTMP/WHIP ingestion) ← TCP LB → Ingress workers → Redis → LiveKit rooms • Egress Service (recording/composites) ← jobs from API → Egress workers (Chrome) → storage/RTMP • TURN server (for NAT traversal) — reachable by clients & LiveKit • Monitoring (Prometheus) exposes metrics for autoscaling (livekit_ingress_available, livekit_egress_available) Notes — key behaviors • External LB distributes initial TCP/WebSocket connections across LiveKit pods despite a single public IP. • Redis is required for multi‑node coordination: room existence, node stats, and pub/sub for signaling. • Only one node hosts a room at a time; other pods proxy signaling for participants that connected to them. • Node selection considers
sysload_limit
and optional region tags to choose the room host. • Use hostNetwork to allow direct UDP media handling and avoid port conflicts (typically one LiveKit pod per Kubernetes node). • TLS termination can occur at the LB/Ingress; TURN/TLS certs must be configured and reachable. • Autoscale Ingress/Egress using provided Prometheus metrics and Kubernetes HPA. If you’d like, I can: • Produce this as a PNG/SVG diagram (which nodes/regions you want shown), • Provide a Kubernetes manifest / Helm values snippet illustrating Service/Deployment with hostNetwork and Redis config, • Or expand the flow showing exact ports and WebSocket/API endpoints. Which would you prefer? Sources: Deploy to Kubernetes | LiveKit docs | Self-hosting the Ingress Service | LiveKit docs | Self-hosting the Egress Service | LiveKit docs | LiveKit Cloud | LiveKit Docs
b
what is the production ready way to handle error events in Livekit agent STT-LLM-TTS pipeline
t
You're asking for a production‑ready strategy to handle error events in a LiveKit Agent STT→LLM→TTS pipeline. Below are concise, practical recommendations you can implement immediately. 1) Use provider fallbacks • Wrap STT, LLM, and TTS with
FallbackAdapter
so a secondary provider is used automatically when the primary fails. • Mark providers as unhealthy on failure and periodically probe them for recovery. 2) Subscribe to ErrorEvent and act by severity • Listen for
error
events from AgentSession. • If
event.error.recoverable
is True: log, metric, and allow automatic retries. • If
recoverable
is False: trigger a graceful user response (e.g., session.say with a canned message), mark session state as degraded, and escalate to manual/automated recovery flows. 3) Graceful degradation modes • On partial failures, reduce pipeline complexity: e.g., use simpler LLM model, lower audio quality, or skip TTS and play a prerecorded prompt. • Provide user-facing fallback messages when LLM/TTS outputs are unavailable. 4) Retry and backoff policy • Implement exponential backoff with capped retries for transient STT/LLM/TTS errors. • Use idempotency where possible so repeated attempts don’t cause duplicated actions. 5) Multi-worker redundancy • Run 3+ agent workers per
agent_name
to ensure automatic failover of active sessions and jobs. 6) Observability & alerts • Emit metrics for error rates, latencies, and provider health (STT errors/sec, LLM latency, TTS failures). • Alert on thresholds (e.g., provider error rate spike, long LLM TTFT) and create dashboards for trending. 7) Session recording & post‑mortem • Enable
record=True
for sessions to capture audio and transcripts for debugging and RCA. • Store error context (request IDs, provider responses, timestamps) alongside recordings. 8) Circuit breaker & health marking • Use a circuit breaker per provider: short‑circuit calls when error rate exceeds threshold, then periodically probe to restore. • Expose provider health to the node selector so hosts avoid overloaded/unhealthy nodes. 9) User notifications & recovery UX • When unrecoverable: inform users with a friendly message and offer retry or alternative flow. • For recoverable/transient issues: show "retrying…" state or short hold music. 10) Automation & runbooks • Automate common recoveries (restart worker, rotate API keys, failover provider) where safe. • Maintain runbooks for unrecoverable scenarios (how to swap provider credentials, escalate, or restore Redis). Example minimal handler pattern • On
error
event: ◦ Log + metric ◦ If recoverable: queue retry ◦ If unrecoverable: session.say("We're having trouble—please try again later") and mark session degraded Which would you like next? • A small code snippet for an
error
event handler in Python/Node.js • Example
FallbackAdapter
configuration for STT/LLM/TTS • Recommended Prometheus metrics and alert thresholds Pick one and I’ll produce it. Sources: Events and error handling | LiveKit docs | Agents Framework | livekit/livekit_composite | DeepWiki | Agents (Node.js) | livekit/livekit_composite | DeepWiki | Agents (Python) | livekit/livekit_composite | DeepWiki | examples/drive-thru | livekit/livekit_composite | DeepWiki
b
A small code snippet for an
error
event handler in Python
t
Below is a concise, production-ready Python snippet for handling
error
events in an AgentSession. It logs, emits metrics, uses backoff for recoverable errors, and sends a user-facing message for unrecoverable errors. Adjust logging/metrics/backoff to your observability and retry policies.
Copy code
import asyncio
import logging
from datetime import datetime, timedelta
from backoff import expo       # pip install backoff
from prometheus_client import Counter, Gauge  # optional

# Prometheus metrics (optional)
ERROR_COUNTER = Counter("agent_errors_total", "Total agent errors", ["source", "recoverable"])
LAST_ERROR = Gauge("agent_last_error_timestamp", "Last error time", ["source"])

logger = logging.getLogger("agent")
logger.setLevel(<http://logging.INFO|logging.INFO>)

# Simple in-memory circuit state per provider/source
_circuit_breakers = {}

def mark_error(source: str, recoverable: bool):
    ERROR_COUNTER.labels(source=source, recoverable=str(recoverable)).inc()
    LAST_ERROR.labels(source=source).set_to_current_time()
    logger.error("Error from %s recoverable=%s", source, recoverable)

def circuit_allow(source: str, cooldown_secs: int = 30) -> bool:
    state = _circuit_breakers.get(source)
    if not state:
        return True
    last_failed = state.get("last_failed")
    if last_failed and datetime.utcnow() < last_failed + timedelta(seconds=cooldown_secs):
        return False
    return True

def circuit_register_failure(source: str):
    _circuit_breakers[source] = {"last_failed": datetime.utcnow()}

# Example exponential backoff retry decorator for recoverable ops
def retry_on_recoverable(max_tries=3):
    def decorator(fn):
        async def wrapper(*args, **kwargs):
            tries = 0
            delay = 1
            while tries < max_tries:
                try:
                    return await fn(*args, **kwargs)
                except Exception as e:
                    tries += 1
                    if tries >= max_tries:
                        raise
                    await asyncio.sleep(delay)
                    delay *= 2
        return wrapper
    return decorator

# Attach to your AgentSession
@session.on("error")
async def on_error(event):
    """
    event.source -> "stt" | "llm" | "tts" | ...
    event.error.message -> human message
    event.error.recoverable -> bool
    event.context -> optional extra info (request id, provider)
    """
    src = getattr(event, "source", "unknown")
    err = getattr(event.error, "message", str(event.error))
    rec = getattr(event.error, "recoverable", False)

    # Record metrics & logs
    mark_error(src, rec)

    # If recoverable: attempt retries or let framework retry automatically.
    if rec:
        <http://logger.info|logger.info>("Recoverable error from %s: %s — will retry if allowed", src, err)

        # Circuit breaker: skip retries if provider is in cooldown
        if not circuit_allow(src):
            logger.warning("Provider %s in cooldown; skipping retry", src)
            return

        # Optionally perform a limited retry (example: re-run the failed action)
        try:
            # If you have access to the failed action, call it with backoff
            # Example placeholder: await retry_failed_operation(...)
            pass
        except Exception as retry_err:
            logger.exception("Retry failed for %s: %s", src, retry_err)
            circuit_register_failure(src)
        return

    # Unrecoverable: inform the user and mark degraded state
    logger.error("Unrecoverable error from %s: %s — notifying user and marking degraded", src, err)
    try:
        # Friendly user-facing message
        await session.say("I'm sorry — an error occurred and I can't continue right now. Please try again shortly.")
    except Exception:
        logger.exception("Failed to send user-facing error message via session.say()")

    # Optionally stop or mark session degraded for downstream logic
    try:
        await session.end()  # if you want to terminate the session
    except Exception:
        logger.debug("session.end() failed or not desired; leaving session for manual cleanup")
Would you like a trimmed version without metrics/circuit logic or an example that retries a specific STT/LLM/TTS call? Sources: Events and error handling | LiveKit docs
b
I want the trimmed version
t
Below is a trimmed, minimal Python snippet for handling
error
events on an
AgentSession
. It logs the event, treats recoverable errors with a simple log/metrics hook, and sends a friendly user message for unrecoverable errors.
Copy code
import logging

logger = logging.getLogger("agent")
logger.setLevel(<http://logging.INFO|logging.INFO>)

@session.on("error")
async def on_error(event):
    # event.source -> "stt" | "llm" | "tts" | ...
    # event.error.message, event.error.recoverable
    src = getattr(event, "source", "unknown")
    err_msg = getattr(event.error, "message", str(event.error))
    recoverable = getattr(event.error, "recoverable", False)

    <http://logger.info|logger.info>("Agent error from %s: %s (recoverable=%s)", src, err_msg, recoverable)

    if recoverable:
        # lightweight hook: increment metric or simple retry logic
        # e.g., METRIC_ERRORS.labels(source=src).inc()
        return

    # Unrecoverable: notify user and optionally end session
    try:
        await session.say("Sorry, I'm having trouble responding right now. Please try again later.")
    except Exception:
        logger.exception("Failed to send error message to user")

    try:
        await session.end()
    except Exception:
        logger.debug("session.end() failed or was skipped")
Would you like this adapted to include a basic retry wrapper for recoverable errors or to include a Prometheus metric increment?
b
session.end() v/s session.shutdown() v/s session.aclose() What's the difference?
t
You're asking for the difference between
session.end()
,
session.shutdown()
, and
session.aclose()
for LiveKit Agent sessions. Here's a concise, production-ready summary. •
session.end()
◦ Not a valid AgentSession method. Do not use. •
session.shutdown()
◦ Graceful shutdown. Waits for pending work (e.g., draining queued speech, committing transcripts, closing I/O). ◦ Use when you want the agent to finish in-flight tasks before stopping. (Python API) •
session.aclose()
◦ Immediate close. Cleans up and force-closes the session without waiting for pending operations. ◦ Use for forceful termination or final cleanup (often called after
shutdown()
if needed). (Python API) Node.js note: the equivalent graceful shutdown is available on the job context (e.g.,
ctx.shutdown()
), not
session.shutdown()
. If you want, I can show a brief Python example that calls
shutdown(drain=True)
then
await session.aclose()
for a proper graceful+force cleanup. Sources: Job lifecycle | LiveKit docs
b
If I need to add an async method of publishToAKafkaTopic inside the entrypoint method. What would be the best practice to do this in production If the payload need to have the room.sid and some metadata from the room in it.
t
Good question — here’s a concise production-ready pattern to asynchronously publish room events (including
room.sid
and metadata) from an entrypoint, with best practices you can adopt immediately. Short checklist (what to do) • Capture room context after joining: use
ctx.connect()
then read
ctx.room.name
(room id) and
ctx.room.metadata
. • Fire-and-forget: use
asyncio.create_task()
to avoid blocking the entrypoint. • Robust publish function: retry with exponential backoff, log failures, and surface metrics. • Track background tasks if you need guaranteed delivery on shutdown. • Keep entrypoint fast and non-blocking; Kafka publish should be best-effort unless you require strong delivery guarantees. • Use structured payloads (event_type, timestamp, room_sid, metadata, job_id, agent_identity). • Use schema (Avro/Protobuf) in prod for consumer compatibility. Minimal pattern (trimmed)
Copy code
import asyncio
import json
import logging
from datetime import datetime

logger = logging.getLogger("agent")

async def publish_to_kafka(payload):
    try:
        # implement aio-kafka producer logic with retries/backoff here
        await kafka_send_and_wait("topic-name", json.dumps(payload).encode())
        <http://logger.info|logger.info>("Published event for %s", payload["room_sid"])
    except Exception:
        logger.exception("Kafka publish failed for %s", payload.get("room_sid"))

@server.rtc_session()
async def entrypoint(ctx: JobContext):
    await ctx.connect(auto_subscribe=AutoSubscribe.AUDIO_ONLY)

    payload = {
        "event_type": "agent_joined_room",
        "timestamp": datetime.utcnow().isoformat() + "Z",
        "room_sid": ctx.room.name,
        "room_metadata": ctx.room.metadata,
        "job_id": ctx.job.id,
        "agent_identity": ctx.agent.identity,
    }

    # fire-and-forget publish
    asyncio.create_task(publish_to_kafka(payload))

    # continue with agent logic...
Graceful shutdown (optional, if you need delivery guarantees) • Keep a set of tasks and await them during shutdown:
Copy code
_active_tasks = set()

task = asyncio.create_task(publish_to_kafka(payload))
_active_tasks.add(task)
task.add_done_callback(lambda t: _active_tasks.discard(t))

# on shutdown:
await asyncio.gather(*_active_tasks, return_exceptions=True)
Reliability & observability recommendations • Implement retries with exponential backoff (tenacity or custom). • Use a circuit breaker for persistent broker failures to avoid flooding. • Emit metrics (publish attempts, failures, latency) and logs with room_sid/job_id. • For audit/critical events, prefer synchronous ack or a separate durable pipeline (webhook → backend → Kafka) instead of blind fire-and-forget. • Use schemas (Avro/Protobuf) and include a request/job id for tracing. If you want, I can: • Provide a fuller example with aiokafka + tenacity retry logic, or • Show how to add Prometheus metrics and task-tracking for graceful shutdown. Which would you like? Sources: Job lifecycle | LiveKit docs | Camera &amp; microphone | LiveKit docs | examples/survey | livekit/livekit_composite | DeepWiki | Sending files &amp; bytes | LiveKit docs | Agents (Python) | livekit/livekit_composite | DeepWiki
b
How does the prewarm function works in the WorkerOptions.
t
You’re asking how the
prewarm
function works in Worker/agent setup. Here’s a concise explanation and actionable notes for production. What prewarm does • Preloads expensive resources (models, native libs, large files) into worker processes before jobs run. • Runs once per warm process and stores loaded resources on the process-level userdata (
proc.userData
/
proc.userdata
) so jobs dispatched to that process can reuse them. • Reduces job startup latency because the job doesn’t have to load heavy resources on demand. Where it’s defined • Node.js agents: defined on the agent via
defineAgent({ prewarm: async (proc) => { ... } })
. • Python agents: assigned as the server setup function (e.g.,
server.setup_fnc = prewarm
). Key behaviors and best practices • Process isolation: each warm process has its own copy of prewarmed resources; nothing is shared across processes. • Run timing: prewarm runs when the worker process is initialized/kept warm by the agent server (before job assignment). • Use-case: load STT/LLM/TTS models, VAD, or other heavy assets. • Storage: save to `proc.userData`/`proc.userdata` and read from the job `ctx.proc.userData`/`ctx.proc.userdata` inside entrypoint. • Memory sizing: size nodes so you can host the prewarmed models; host‑networked and resource limits still apply. • Number of warm processes: control via your agent server/worker pool configuration so you have enough prewarmed processes for concurrency without OOM. • Graceful reloads: ensure you handle reloads so processes re-prewarm cleanly (avoid leaking GPU memory or native handles). • Health & observability: track prewarm success/failure (logs + metrics) and fail fast if prewarm can’t load a required resource. Minimal Node.js example
Copy code
export default defineAgent({
  prewarm: async (proc) => {
    proc.userData.model = await loadLargeModel();
  },
  entry: async (ctx) => {
    const model = ctx.proc.userData.model;
    // use model
  }
});
Minimal Python example
Copy code
def prewarm(proc):
    proc.userdata["model"] = load_large_model()

server.setup_fnc = prewarm

@server.rtc_session()
async def entrypoint(ctx):
    model = ctx.proc.userdata["model"]
    # use model
When not to prewarm • Small/lightweight resources — prewarming adds memory overhead. • If processes are highly ephemeral or you cannot provision enough memory for multiple copies. If you tell me which SDK (Python or Node) and which resources you plan to prewarm (STT model, TTS voices, VAD, GPU models), I can give a tailored example including memory sizing recommendations and how many warm processes to keep. Sources: Agent server options | LiveKit docs | Silero VAD plugin | LiveKit docs
b
If for a python livekit agent class, I want to initialize some other service classes like userServiceProxy Class or the kafka_service class, where should I initialize it for the production ready code
t
Initialize long‑lived service clients (userServiceProxy, kafka_service, DB pools, HTTP clients) once per worker process—not per job. This keeps startup fast, avoids reconnect storms, and is production‑ready. Recommended places to initialize (Python) • Prewarm / setup function (preferred): run when worker processes are spawned so each warm process gets a single shared instance. • AgentServer startup: if you have a central singleton used by all processes in the same process (less common with process isolation). • Avoid creating in the entrypoint for every job (unless the client must be job‑scoped). How to store and access them • Put instances on the process userdata (proc.userdata) in prewarm so entrypoint can access via ctx.proc.userdata. • Track background tasks and close clients on graceful shutdown. Minimal pattern (production-ready) 1. Prewarm/setup_fnc: create clients and save to proc.userdata. 2. Entrypoint: read from ctx.proc.userdata and use. 3. Shutdown/cleanup: ensure clients are closed when the process exits. Example (trimmed):
Copy code
# server setup
def prewarm(proc):
    proc.userdata["kafka"] = KafkaService.connect(bootstrap="kafka:9092")
    proc.userdata["user_proxy"] = UserServiceProxy(base_url="<https://users.internal>")
    # optionally run health checks here

server.setup_fnc = prewarm

@server.rtc_session()
async def entrypoint(ctx: JobContext):
    kafka = ctx.proc.userdata["kafka"]
    user_proxy = ctx.proc.userdata["user_proxy"]

    # use kafka/user_proxy (non-blocking calls preferred)
    asyncio.create_task(kafka.publish({...}))
    user = await user_proxy.get_user(ctx.job.user_id)
    ...
Shutdown handling • If you need guaranteed flush on shutdown, track tasks and on
shutdown()
or process exit await outstanding publishes and call client.close()/stop(). Other production notes • Use connection pooling for HTTP and Kafka clients (aiokafka, httpx.AsyncClient). • Add retries/backoff and circuit breakers inside service wrappers. • Log initialization errors and expose health checks so orchestration can restart unhealthy workers. • Size number of warm processes according to memory (each process holds its own client state). If you tell me which Kafka and HTTP libraries you plan to use (e.g., aiokafka, confluent-kafka, httpx), I can give a concrete prewarm + shutdown code sample.
b
I've added some kafka init and some other userserviceproxy init in the prewarm. After that my entrypoint method is not getting called whenever the client connects. There are no errors in the logs. livekit version = 1.0.20
t
Thanks — that sounds frustrating. I’ll help troubleshoot. A few common causes and quick checks first, then please share the minimal code/logs I request so I can pinpoint the issue. Quick checks (do these now) • Ensure prewarm completes quickly and does not block indefinitely (no long sync waits). If you perform blocking I/O, wrap in async or spawn background tasks. • Confirm prewarm does not raise an exception (exceptions can silently stop process warm pool). Add try/except and explicit logging at start/end. • Verify you store instances on
proc.userdata
(Python) and access them via
ctx.proc.userdata
in entrypoint. • Confirm your agent process is actually running and warm processes are alive (ps/kubectl logs). Check agent server startup logs for prewarm success messages. • Ensure entrypoint decorator/signature is correct (
@server.rtc_session()
and
async def entrypoint(ctx: JobContext):
). What I suspect • Prewarm is blocking or hanging (e.g., synchronous Kafka connect, DNS, auth) preventing job dispatch. • Prewarm raised an exception that was not logged, causing worker to not become ready. • Resource exhaustion (OOM) or process crash during prewarm but logs are not captured. Please share these (minimal snippets) 1. Your
prewarm
function (exact code). 2. Your
entrypoint
function signature and decorator. 3. How you assign
server.setup_fnc = prewarm
and start the server (snippet). 4. Any relevant startup logs around prewarm (first 50-100 lines) — especially any print/log before/after prewarm. 5. LiveKit Agents version note you already gave (1.0.20) and whether you run in Kubernetes or locally. Immediate small fixes you can try • Add logging and a timeout around prewarm:
Copy code
def prewarm(proc):
    <http://logger.info|logger.info>("prewarm start")
    try:
        # wrap long init in background task or async with timeout
        proc.userdata["kafka"] = KafkaService.connect(...)
    except Exception:
        logger.exception("prewarm failed")
    <http://logger.info|logger.info>("prewarm done")
• If Kafka init is blocking, create a lightweight placeholder in prewarm and connect lazily in background:
Copy code
proc.userdata["kafka"] = LazyKafka(bootstrap=..., connect_background=True)
• Temporarily comment out prewarm contents to verify entrypoint is called; reintroduce pieces one-by-one. If you share the snippets/logs I requested, I’ll review and point out the exact cause and fix.
b
I'm using WorkerOptions to initialize the prewarm
t
Thanks — here's a focused answer for using WorkerOptions prewarm in Python and why your entrypoint may not get called, plus exact checks and a minimal safe pattern to fix it. Why entrypoint can be skipped after prewarm • Blocking or long‑running sync work in
setup_fnc
prevents worker from finishing init. • Uncaught exception in prewarm causes the worker to fail warmup silently. • Prewarm creates network connections or heavy resources that exhaust or deadlock resources. •
setup_fnc
not wired correctly into WorkerOptions (must be
setup_fnc=prewarm
). Immediate checks (do these now) 1. Confirm WorkerOptions usage: ◦ worker_options = WorkerOptions(entrypoint_fnc=entrypoint, setup_fnc=prewarm) ◦ cli.run_app(worker_options) 1. Add explicit logging at start/end and catch exceptions:
Copy code
def prewarm(proc):
       <http://logger.info|logger.info>("prewarm start")
       try:
           # your init
       except Exception:
           logger.exception("prewarm failed")
           raise
       <http://logger.info|logger.info>("prewarm done")
1. Ensure prewarm is non‑blocking: ◦ Do not call blocking I/O or long sync connects. Use async or defer connect. ◦ If a library is sync-only, initialize lazily in entrypoint or spawn a background thread/task. 1. Test quickly by commenting out prewarm body: ◦ If entrypoint runs, reintroduce pieces one at a time to isolate the culprit. Safe production pattern (minimal, non‑blocking) • In prewarm: create configs/clients placeholders and load only CPU‑heavy models. • In entrypoint: establish network connections (Kafka, HTTP) or call connect_async. Example:
Copy code
def prewarm(proc):
    <http://logger.info|logger.info>("prewarm start")
    # load heavy local models only
    proc.userdata["model"] = load_large_model()
    # store configs but don't connect
    proc.userdata["kafka_config"] = {"brokers": "kafka:9092"}
    <http://logger.info|logger.info>("prewarm done")

@server.rtc_session()
async def entrypoint(ctx: JobContext):
    # connect network clients per-job or lazily
    kafka = KafkaService(config=ctx.proc.userdata["kafka_config"])
    await kafka.connect()  # async connect inside entrypoint
    model = ctx.proc.userdata["model"]
    ...
If you want, share these minimal items and I’ll inspect them: • Your
prewarm
code • Your WorkerOptions creation and run snippet • The
entrypoint
signature • Any startup logs around prewarm (first 50 lines) I’ll pinpoint the exact line causing the hang or silent failure. Sources: Agent server options | LiveKit docs | Silero VAD plugin | LiveKit docs | Agent deployment CLI reference | LiveKit docs
b
If I'm self hosting the Livekit, and I want to use both capabilities in respect of voice pipeline - 1. STT-LLM-TTS pipeline agent and 2. Realtime api agent. How do I achieve this in the production in python
t
You can run both agent capabilities in production by running them as separate Python agent processes (or containers), each with its own agent_name and lifecycle, then dispatching the appropriate agent to rooms. Concise plan and best practices: 1) Run two distinct agent services • STT→LLM→TTS agent: built around AgentSession with STT/LLM/TTS adapters, prewarm heavy models. • Realtime API agent: built around AgentSession using the Realtime provider (OpenAI Realtime or similar). • Give each service a unique agent_name (e.g., "stt-llm-tts-agent", "realtime-agent"). 2) Deployment model • Package each agent as its own container (or process). Deploy both to your cluster (Kubernetes/Compose) and run multiple replicas per agent_name for HA. • Use WorkerOptions / AgentServer setup_fnc / prewarm to initialize per‑process resources. ◦ Prewarm only CPU-heavy models. Defer network connects (Kafka, DB, broker clients) to entrypoint or connect asynchronously to avoid blocking warm-up. 3) Registration & routing • Each agent process registers with your LiveKit server under its agent_name; LiveKit will dispatch jobs to matching agents. • Use explicit dispatch (agent dispatch API or token room config) to decide which agent should join a room. ◦ Example flows: automated dispatch from your backend based on room metadata, or client-requested dispatch via server API. 4) Resource isolation & sizing • Separate resource needs: STT/LLM/TTS agents may need more CPU/RAM (or GPU) and longer prewarm; Realtime agents may need lower latency and different scaling. • Run node selectors/taints or dedicated node pools for heavier agents. • Keep one LiveKit agent process per container process (process isolation), and control concurrency by number of warm processes. 5) Shared concerns • Coordination: both agent types can share Redis / LiveKit server; they just have different agent_names. • Observability: expose metrics per agent_name (TTFT, error rates, job queue depth) and logs containing job_id/room_sid. • Graceful shutdown: call
shutdown(drain=True)
then
aclose()
in Python to drain speech/IO before exit. • Prewarm & userdata: put reusable models in
proc.userdata
and access from
ctx.proc.userdata
inside entrypoint. • Credentials: keep separate env config/secrets for each agent (API keys, model configs). 6) Dispatch examples • Backend explicit dispatch (recommended for control): ◦ Use the agent dispatch API to create a dispatch for a room with the chosen agent_name. • Token-based dispatch: ◦ Include agent dispatch config in the token/room config for client-driven flows. 7) Reliability patterns • Use FallbackAdapters for STT/LLM/TTS providers. • Implement retries/backoff, circuit breakers, and per-provider health checks. • Prewarm enough processes to meet expected concurrency; size memory accordingly. 8) Scaling & autoscaling • Scale each agent type independently. Use metrics (job queue depth,
livekit_agent_jobs_pending
) to drive autoscaling. • For heavy models, prefer fewer larger instances; for many small realtime jobs, prefer more smaller instances. If you want, I can: • Provide two minimal runnable Python agent skeletons (STT-LLM-TTS and Realtime) showing prewarm, entrypoint, and WorkerOptions. • Show example Kubernetes Deployment manifests and HPA settings for each agent type. Which would you like next? Sources: Agent server | LiveKit docs | Agents Framework | livekit/livekit_composite | DeepWiki | Agent dispatch | LiveKit docs | Deploying to custom environments | LiveKit docs | Agent server options | LiveKit docs
b
Can I run both in the same container ?
t
Short answer: technically yes, but not recommended for production. Why not (concise pros/cons) • Isolation & stability: separate containers isolate crashes, memory/GPU pressure, and OOMs. One agent leaking memory can take down both. • Scaling: STT‑LLM‑TTS and Realtime have different sizing and concurrency needs; separate services let you scale them independently. • Resource placement: heavy models may need GPU/large nodes, realtime needs low‑latency smaller nodes — separate containers + node selectors/taints is cleaner. • Deployment & rollout: independent deployments let you roll out, drain, and prewarm per agent without impacting the other. • Observability & ops: metrics, autoscaling, and failure domains are simpler per agent_name. When same‑container is acceptable • Low traffic / POC or dev environment. • Very small models and identical resource profiles. • You accept single failure domain and combined scaling. If you must run both in one container (how-to, minimal safe pattern) 1. Run separate processes (one per agent) — do not multiplex in a single process. 2. Give each process its own AgentServer/WorkerOptions and unique agent_name. 3. Use a process supervisor (systemd, supervisord, or a small script that starts both and restarts on failure). 4. Avoid heavy prewarm contention: prewarm only what the process needs; use
proc.userdata
per process. 5. Separate env config and logs for each process (distinct prefixes, ports where applicable). 6. Ensure limits: set container CPU/memory limits big enough for both, and use cgroups/node selectors accordingly. 7. Health checks: expose independent health endpoints for each process and wire Kubernetes liveness/readiness probes to them. 8. Graceful shutdown: trap signals and call each process’s graceful shutdown (
shutdown(drain=True)
then
aclose()
), then exit supervisor only after both are closed. 9. Monitoring & alerts: tag metrics with agent_name so you can still distinguish behavior. Example process supervisor sketch (conceptual) • start-stt-agent.sh -> runs stt agent process (agent_name=stt-llm-tts-agent) • start-realtime-agent.sh -> runs realtime agent (agent_name=realtime-agent) • entrypoint.sh launches both, waits, and forwards signals for graceful shutdown. Recommendation • For production: run as separate containers/pods per agent_name. Use dedicated node pools for heavy agents, independent autoscaling, and per-agent observability. • For dev/POC: same container is fine if you follow the safe pattern above. If you want, I can: • Provide a small example Dockerfile + supervisord config to run both processes in one container, or • Provide Kubernetes manifests for running them as separate Deployments with HPA and node selectors. Which would you like?
b
what are the advantages and disadvantages of separate container in an application. And how do I direct the dispatch of the job from the livekit server based on the token metadata.
t
Below are concise pros/cons of using separate containers, then a direct how‑to for dispatching jobs from LiveKit using token/room metadata and explicit dispatch. I’ll keep examples short — tell me if you want full code snippets or k8s manifests. Part A — Separate containers: advantages vs disadvantages Advantages • Isolation: crashes, memory/GPU leaks, or high CPU in one agent don’t take down the other. • Independent scaling: scale STT‑LLM‑TTS and Realtime agents separately to match different CPU/memory/latency needs. • Resource placement: schedule heavy agents (GPU/large nodes) separately using node selectors/taints. • Safer rollouts: deploy/roll back one agent without impacting the other; drain/upgrade independently. • Clear observability: per-agent metrics, logs, alerts, and SLOs are simpler to manage. • Security boundaries: separate secrets/permissions per agent. Disadvantages • Operational overhead: more images, deployments, CI pipelines, and configs to maintain. • Higher baseline resource usage: multiple containers mean duplicated runtime overhead. • More networking complexity: service discovery, internal auth, cross-service calls. • Slightly higher deployment latency for small-scale/POC setups. When to use each • Separate containers: production, mixed resource profiles, HA requirements. • Same container: dev/POC or tiny, identical workloads where ops cost outweighs isolation needs. Part B — Directing dispatch from LiveKit using token metadata Two ways to control which agent handles a room/job: 1) Token/room metadata dispatch (recommended for client-driven flows) • Include agent dispatch config in the token/room configuration you issue to clients. • The token can carry room configuration that instructs LiveKit to dispatch a particular agent_name when a room is joined. High-level steps: • Your backend builds an AccessToken for the client. • Add room configuration specifying agent dispatch entries (agent_name + optional metadata). • Client uses that token to join; LiveKit will dispatch the named agent to the room. Benefits: client-driven, simple, no extra API call at runtime. 2) Explicit API dispatch from your backend (recommended for server-driven control) • After creating or on room join, call the LiveKit Agent Dispatch API to create a dispatch for a room with a chosen agent_name. • This gives full server-side control (choose agent based on user profile, A/B testing, or business rules). High-level example (pseudo): • Decide agent: if room_metadata['use_realtime'] -> agent_name="realtime-agent" else "stt-llm-tts-agent" • Call LiveKit agent dispatch create endpoint with room and agent_name. Best practices combining both • Use token metadata when the client or session request should determine agent (e.g., user-selected “Realtalk” mode). • Use backend explicit dispatch for policy decisions (billing tier, routing by region, experiments). • Put decision logic in a single backend service; store dispatch decisions in DB/audit log for debugging. What metadata to include • agent_name • region or preferred_node • job/request id for tracing • any feature flags (e.g., quick_mode, high_fidelity) • room_sid, user identity (for downstream consumers) Observability & safety • Log every dispatch (agent_name, room, job_id, reason). • Emit metrics and alerts for failed dispatch attempts and agent availability. • Fallback: if the chosen agent is unavailable, have a backend rule to pick a secondary agent or retry. Quick example outline (token approach) • Backend: create AccessToken containing room config with agents = [{ "agent_name": "realtime-agent", "metadata": {...}}] • Client: join using token → LiveKit server dispatches "realtime-agent" to that room automatically. Quick example outline (explicit API) • Backend: on room create/join decide agent_name • Backend: call LiveKit Agent Dispatch API: CreateAgentDispatchRequest(room=roomName, agent_name=agent_name) If you want, I can: • Provide a compact Python example that embeds agent dispatch into the AccessToken payload, or • Provide an explicit API call example (Python) that creates a dispatch and logs the result. Which example would help you most?
b
I'm only providing the token to the client from a different microservice's api. Can I include the agent name in the Token creation? Will the livekit server only dispatches the job to that particular agent-name only?
t
Yes — you can embed the agent name in the token you issue from another microservice, and LiveKit will use that token room configuration to dispatch the specified agent to the room. How it works (concise) • When you include a RoomAgentDispatch (agent dispatch) entry in the AccessToken/room configuration, LiveKit will attempt to dispatch a job to an agent with that exact
agent_name
. • LiveKit dispatches to agents registered under that name only. If no matching agent is available, the dispatch will fail or remain pending depending on agent availability and your server configuration. What to include in the token • agent_name (the exact name the agent processes register with) • optional metadata you want the agent to receive (job_id, feature flags, region) • room name/id and standard grants (room join, etc.) Minimal Python example (creating token with agent dispatch)
Copy code
from livekit import AccessToken, VideoGrant
from livekit.rooms import RoomAgentDispatch, RoomConfiguration  # pseudo-names; adjust to your SDK

token = (
    AccessToken(api_key, api_secret)
    .with_identity("client-123")
    .with_grants(VideoGrant(room_join=True))
    .with_room_config(RoomConfiguration(agents=[
        RoomAgentDispatch(agent_name="realtime-agent", metadata={"mode":"realtime"})
    ]))
    .to_jwt()
)
Caveats & best practices • Exact agent_name: the agent_name in the token must match the agent process registration exactly. • Agent availability: if the named agent has no healthy workers, dispatch will not be fulfilled — implement fallback logic (backend can create an explicit dispatch to a fallback agent). • Use server-side decisioning for critical routes: if you need firm guarantees or complex routing (region, billing tiers), your backend can instead call the Agent Dispatch API after creating the room. • Logging & observability: log the token issuance and dispatch decisions (room, agent_name, job_id) so you can debug failed dispatches. • Security: only trusted backend services should issue tokens with agent dispatch entries. If you want, I can: • Give a concrete code sample matching the specific LiveKit Python SDK you use, or • Show how to implement a fallback dispatch when the primary agent is unavailable. Which would you prefer?
b
give me the java code example to create the accesstoken for this scenario
t
Below is a concise, production-ready Java example that creates a LiveKit AccessToken embedding a room config with an agent dispatch (agent_name + metadata). Adjust imports and dependency versions to match your server SDK.
Copy code
import io.livekit.server.AccessToken;
import io.livekit.server.VideoGrant;
import io.livekit.server.RoomConfiguration;
import io.livekit.server.RoomAgentDispatch;

public class TokenWithAgentDispatch {

    public static String createToken(
            String apiKey,
            String apiSecret,
            String identity,
            String roomName,
            String agentName,
            String agentMetadataJson, // e.g. "{\"user_id\":\"123\",\"mode\":\"realtime\"}"
            int ttlSeconds
    ) {
        AccessToken token = new AccessToken(apiKey, apiSecret);
        token.setIdentity(identity);
        token.setTtl(ttlSeconds);

        // grant room join permission
        token.addGrants(new VideoGrant().setRoom(roomName));

        // create agent dispatch entry
        RoomAgentDispatch dispatch = new RoomAgentDispatch()
            .setAgentName(agentName)
            .setMetadata(agentMetadataJson);

        RoomConfiguration roomConfig = new RoomConfiguration()
            .setAgents(new RoomAgentDispatch[] { dispatch });

        // attach room config
        token.roomConfig(roomConfig);

        return token.toJwt();
    }

    public static void main(String[] args) {
        String apiKey = System.getenv("LIVEKIT_API_KEY");
        String apiSecret = System.getenv("LIVEKIT_API_SECRET");

        String jwt = createToken(
            apiKey,
            apiSecret,
            "user-123",
            "support-room-1",
            "realtime-agent",
            "{\"user_id\":\"12345\",\"tier\":\"gold\"}",
            3600
        );

        System.out.println("Generated token: " + jwt);
    }
}
Notes and best practices • agentName must exactly match the agent processes' registered
agent_name
. • agentMetadataJson should be a JSON string; the agent receives it on JobContext for routing/metadata. • If the named agent has no healthy workers, dispatch will not be fulfilled — implement backend fallback logic if needed. • Store API secrets securely (env/secret manager) and keep token TTL appropriate for your flow. • Log token issuance (room, agent_name, job_id) for observability and debugging. Sources: Agent dispatch | LiveKit docs | Authentication | LiveKit docs | SIP dispatch rule | LiveKit docs | server-sdk-kotlin | livekit/livekit_composite | DeepWiki | Agents telephony integration | LiveKit docs
b
Also tell me where does the agent-name is set in python agent worker
t
Set the agent name when you register the session handler—pass
agent_name
to the
@server.rtc_session()
decorator (or to `WorkerOptions`/AgentServer registration). That name is what your agent process registers with LiveKit and what the server matches when dispatching. Minimal examples 1) Using the decorator on the session handler (common pattern)
Copy code
from livekit.agents import AgentServer, JobContext

server = AgentServer()

@server.rtc_session(agent_name="stt-llm-tts-agent")
async def entrypoint(ctx: JobContext):
    # entrypoint logic
    ...
2) With WorkerOptions (explicit entrypoint binding)
Copy code
from livekit.agents import WorkerOptions, AgentServer

server = AgentServer()

@server.rtc_session()                    # no agent_name here
async def entrypoint(ctx):
    ...

worker_opts = WorkerOptions(entrypoint_fnc=entrypoint, agent_name="realtime-agent")
# start the worker (cli.run_app or equivalent)
3) If you construct AgentServer/registration directly, ensure agent_name is provided where the server registers (framework wiring uses the same name). Where to check • The agent process logs on startup will show registration with that agent_name. • Use
lk agent status
or your orchestration logs to confirm the agent_name is registered. Recommendation • Pick a unique, stable string (e.g., "stt-llm-tts-agent" and "realtime-agent"). • Keep it consistent across deployments and use that exact string in token RoomAgentDispatch or explicit dispatch API. Want a quick check of your current agent code? Paste the decorator/WorkerOptions snippet and I’ll point out where to add or correct the agent_name.
b
If self hosted livekit and deploying in a kubernetes environment, how does the python agent worker application handles multiple connections from the client. On which parameter does the number of concurrent connections being handled in a livekit architecture?
t
Short answer: a Python agent handles concurrency by running one job (agent session) per client connection and scaling across worker processes/pods — concurrency is governed by pod count, per‑pod resources, and the agent’s load policy (not a single "max connections" parameter). Key points • One job per connection: each client/room dispatch becomes a job. Jobs run in isolated subprocesses within the agent worker process pool. • Concurrency drivers: ◦ Number of agent worker pods (Kubernetes replicas) — horizontal capacity. ◦ CPU / memory per pod — determines how many jobs a pod can host. ◦ Worker process pool / warm processes (prewarm) — determines how many concurrent job processes are ready. ◦ load function and load_threshold (AgentServer settings) — stop accepting jobs when load too high. ◦ Kubernetes autoscaling (HPA) using CPU / custom metrics (job queue depth, custom load) to scale pods. • Sizing guidance (rules of thumb): ◦ Start with a well-provisioned pod (e.g., 4 CPU / 8 GB) and measure — a single pod can often handle ~10–30 concurrent voice jobs depending on models/features. ◦ Tune by benchmarking your STT/LLM/TTS pipeline (VAD, noise reduction, model sizes) and observing TTFT / CPU/RAM. • Practical controls you can use: ◦ Implement custom
load_fnc
to reflect active job count or CPU and return a 0..1 load value. ◦ Set
load_threshold
(default ~0.7) so workers stop accepting new jobs above threshold. ◦ Control number of warm processes / prewarm count so you have ready processes but avoid OOM. ◦ Use HPA to add/remove pods based on CPU or custom Prometheus metrics (job queue, pending dispatch). ◦ Graceful shutdown (
shutdown(drain=True)
then
aclose()
) so in-flight jobs finish during deploys. • Observability & reliability: ◦ Emit metrics per agent_name: active_jobs, job_queue_depth, TTFT, error_rate. ◦ Log room_sid/job_id on job start/finish for traceability. ◦ Use prewarm for heavy models but avoid blocking network connects in prewarm. • Recommendation: ◦ Benchmark with realistic workloads to find jobs-per-pod for your pipeline. ◦ Tune
load_fnc
to match those measurements and drive HPA with custom metrics. If you want, I can: • Suggest a sample
load_fnc
implementation (Python) that uses active job count, • Show a sample K8s HPA manifest wired to a custom metric for agent job queue, • Or help estimate pod sizing if you share expected concurrent calls and which STT/LLM/TTS providers/models you’ll run. Which would you like? Sources: Agent server | LiveKit docs | Agent server options | LiveKit docs | Deploying to custom environments | LiveKit docs | examples/drive-thru | livekit/livekit_composite | DeepWiki | Deploy to Kubernetes | LiveKit docs
b
Does livekit maintain a session context of the conversation? Like if I've asked something in a voice interaction a few mins earlier, would livekit be able to reply accordingly to the same question?
t
LiveKit itself does not hold conversational memory. It provides media, signaling, and agent job dispatch — but maintaining conversational context (what was said minutes ago, prior intents, etc.) is the responsibility of your application or the agent running on top of LiveKit. How to get persistent conversation context (recommended patterns) • Agent-managed session state: keep conversation history inside your AgentSession (in memory) for the active job so the agent can reference earlier turns while that job runs. • Persist across jobs: store transcripts/turns in an external store (DB, Redis, or object storage) keyed by room_sid or user_id. When a user reconnects or a new job starts, load recent history into the agent as context. • Use LLM context windows: prepend recent transcript turns or a summarized memory to the prompt you send to the LLM so replies reflect prior interactions. • Short-term in-process caching: use `proc.userdata`/job context for lightweight state during a job; use external storage for minutes/hours persistence. • Event-driven enrichment: publish room events (join/leave/transcript) to Kafka/webhooks so downstream services can maintain a timeline and surface relevant context to agents. • Session timeout & summarization: for long gaps, summarize past conversation to keep within model token limits and maintain coherence. Practical example flow 1. During call: record transcripts (enable
record=True
or capture STT) and append turns to a Redis list keyed by
room:{room_sid}:history
. 2. On new agent job or reconnection: fetch last N turns or a summary and pass into the LLM prompt (or load into AgentSession memory). 3. Optionally store condensed summary back to Redis to keep history size small. LiveKit features that help • AgentSession events and recording support let you capture audio/transcripts for storage. • Token/room metadata and JobContext contain
room_sid
so you have a stable key to fetch stored context. If you want, I can: • Show a minimal code example (Python) that saves transcripts to Redis and rehydrates them into the LLM prompt at job start, or • Suggest a summarization strategy and prompt template to keep context useful across minutes/hours. Which would you like?
b
I'm using a customLLM api as a tool call to retrieve the information about my company. And I'm using the STT-LLM-TTS pipeline, what's the best practice for this architecture in the production?
t
Great — here’s a concise, production-ready checklist and architecture guidance for using a custom LLM API as a tool in your STT→LLM→TTS LiveKit pipeline (Python). I’ll focus on reliability, latency, security, and operational concerns so you can deploy safely. 1) Tool design • Expose your custom LLM as a function tool via
@function_tool()
so the agent can call it as a structured tool. • Make the function’s signature and docstring explicit so the LLM chooses it reliably. • Mark irreversible actions (writes/billing) as non-interruptible with
context.disallow_interruptions()
. 2) Isolation & service layer • Encapsulate network calls in a dedicated service class (e.g.,
CompanyInfoService
) with: ◦ timeouts, retries (exponential backoff), and circuit breaker. ◦ connection pooling (aiohttp AsyncClient or similar). ◦ observability hooks (logs, traces, metrics). • Call the service from the tool function; keep business logic outside the agent core. 3) Latency & UX • Set strict request timeouts (e.g., 5–10s) and return early user-facing progress messages (e.g., “Checking that now…”) to avoid long silent waits. • For long-running queries, respond with interim TTS feedback, then update with the result. • Use background tasks for non-critical publishes (analytics, logging) so the agent isn’t blocked. 4) Error handling • In the tool, raise
ToolError
for expected failures so the LLM can reply gracefully. • Distinguish recoverable vs unrecoverable: ◦ Recoverable: retry with backoff; log and let session continue. ◦ Unrecoverable: return a user-friendly message and mark session degraded. • Track and emit metrics: tool calls total, failures, latency, timeouts. 5) Caching & consistency • Cache frequent lookups (Redis or in-process LRU) with TTL to reduce API calls and latency. • If data updates frequently, use short TTL and include cache invalidation hooks. 6) Context management & memory • Maintain short-term in-memory context for the active job (AgentSession memory). • Persist long-term conversational state to a DB/Redis keyed by room_sid so context survives reconnections. • For LLM prompts, prepend recent turns or a summarized memory to stay inside token limits. 7) Security & secrets • Keep API keys in secrets manager / K8s secrets; never in code. • Validate and sanitize inputs/outputs to avoid injection into prompts. • Enforce least privilege on the custom LLM API and use mTLS or private network if possible. 8) Prewarm & connections • Prewarm CPU-heavy models in
server.setup_fnc
(proc.userdata). Do not perform blocking network connects in prewarm. • Initialize connection configs in prewarm, but connect to external services asynchronously in entrypoint or lazily on first use. 9) Observability & tracing • Log request/response IDs, room_sid, job_id, user identity for each tool call. • Add distributed tracing (trace id propagated to your LLM API). • Export Prometheus metrics: tool_calls_total, tool_errors_total, tool_latency_seconds. 10) Scaling & reliability • Run agents as separate services and scale based on job queue depth and CPU/RAM. • Use FallbackAdapter for STT/LLM/TTS providers (automatic provider failover). • Use circuit breakers to avoid cascading failures to the custom API. • Provide a fallback reply (canned message or simpler LLM) when the custom API is unavailable. 11) Testing & safety • Test tool selection with representative prompts so the LLM reliably invokes the tool. • Simulate failures (timeouts, 5xx, malformed responses) to validate graceful degradation. • Add content filters or verification of tool output before TTS playback if needed. 12) Example minimal flow (conceptual) • User speaks → STT → LLM decides to call
get_company_info(tool_arg)
→ tool calls CompanyInfoService.query(tool_arg) with timeout/retries → returns structured result → LLM generates response → TTS plays answer → record transcript + event to Kafka. If you want, I can: • Provide a compact Python example showing
@function_tool
+
CompanyInfoService
with aiohttp + tenacity retries and raising
ToolError
. • Show a recommended Prometheus metrics set and sample alert thresholds. Which example would help you next? Sources: Tool definition and use | LiveKit docs | External data and RAG | LiveKit Docs | Voice AI quickstart | LiveKit docs | SIP Lifecycle Management Agent | LiveKit docs | examples/voice_agents | livekit/livekit_composite | DeepWiki
b
I want a good prompt for my Agent
t
Below is a production‑ready system prompt you can use for your STT→LLM→TTS Agent. It’s designed to make the agent reliable, concise, safe, and to use your custom tool (
get_company_info
) when needed. After the prompt I include quick guidelines for tuning, context injection, and safety. System prompt (use as the Agent’s base prompt) You are a concise, helpful, and safety‑aware voice assistant for Acme Corp. Your job is to answer user questions about Acme’s products, services, policies, and account information clearly and politely. Use the following rules: 1. Prioritize facts and data from the official company tool: ◦ If the user asks for company‑specific information (product specs, pricing, account data, status), call the tool
get_company_info(query)
with a clear, structured query. Do not guess company facts; always use the tool when the answer depends on internal data. ◦ If the tool returns no result, respond with a helpful fallback: apologize, offer next steps (ask clarifying question, provide contact method, or offer to open a support ticket). 1. Be brief and structured: ◦ Keep spoken replies under 20 seconds where possible. ◦ Use short sentences. When listing steps, number them: “1)… 2)…” ◦ For multi‑part answers, start with a 1–2 sentence summary, then a short bulleted detail list. 1. Confirm intent and ask clarifying questions: ◦ If the user’s request is ambiguous, ask one concise clarifying question before calling tools or taking action. ◦ Example: “Do you mean billing for product X, or your last invoice?” 1. Handle sensitive data safely: ◦ Never read or expose full account credentials, secrets, or PII unless explicitly authorized and the tool indicates permitted fields. ◦ Mask or summarize sensitive fields in spoken output (e.g., “ending in 1234” rather than full card numbers). 1. Error & uncertainty handling: ◦ If the tool or external API fails, say: “I’m sorry — I couldn’t retrieve that information right now. Would you like me to try again or connect you to support?” ◦ Avoid fabricating details. Use phrases like “I don’t have that info” or “I’m unable to access that right now.” 1. Use tool output responsibly: ◦ When
get_company_info
returns structured data, cite the relevant field(s) in the response and offer an action: “Would you like me to…” ◦ If the tool returns multiple possible matches, summarize top 2 and ask which one the user means. 1. Conversation memory & summarization: ◦ Keep only the last N = 8 user/assistant turns in working memory for immediate replies. ◦ If conversation exceeds context limits, summarize earlier turns to preserve key facts (user name, account id, ongoing issue) and use the summary in prompts. 1. Interruption policy: ◦ If the user interrupts during a tool call, immediately stop the tool call and ask whether to continue, cancel, or switch topics. ◦ For irreversible actions, do not allow interruptions (tool should be called with interruptions disabled). 1. Tone & compliance: ◦ Use a polite, professional tone; match user formality level. ◦ Do not give legal, medical, or financial advice beyond reading official company policy; if asked, redirect to the appropriate team. 1. Actionables & next steps: ◦ End answers with a clear next step when appropriate (e.g., “Would you like me to open a ticket?” or “Shall I send this to your email?”). Guidelines for using this prompt in your agent • Prepend a short runtime context before user prompt: room_sid, job_id, user_identity, and last 3 turns (or the summarized version). • Use
get_company_info(query)
only for company/internal data; for general knowledge use the LLM alone. • Enforce token/window limits by summarizing older context server‑side before sending to the model. Tuning tips • If replies are too verbose → add “Keep responses shorter: max 50 words.” • If agent under‑uses the tool → emphasize: “Always call
get_company_info
for internal company questions.” • If agent over‑calls the tool for trivial answers → add: “Do not call the tool for publicly available general knowledge (dates, widely known facts).” Safety & monitoring • Log every tool call (query, job_id, room_sid) and the returned data for audits. • Track metrics: tool_calls, tool_failures, average tool latency, user satisfaction rating. • Add a fallback flow for sustained tool downtime: route users to human support or cached FAQs. If you want, I can convert this into: • A compact JSON system + behavior spec for your agent runtime, • A prompt template that auto‑injects
room_sid
,
user_id
, and last‑turn summary, • Or provide example conversational traces showing how the agent should behave. Which would you prefer?
b
is it mandatory or better to add a description to the function call method. Like does the Livekit uses that description present in the function tool?
t
I don't have the answer you're looking for. You could also try asking your question: • in one of the other Slack channels or • to https://deepwiki.com/livekit/livekit_composite which is trained on all LiveKit source code If you find the answer, please post it here to help others!