Evalgent
Back to Blog
Voice AI Evaluation

Can You Use LiveKit and Pipecat Together? The Hybrid Voice Agent Stack

Deepesh Jayal
19 min read
Can You Use LiveKit and Pipecat Together? The Hybrid Voice Agent Stack

Most "LiveKit vs Pipecat" posts frame the two as rivals. That framing is half right. Both can run a full voice agent. But they sit at different layers, and one of Pipecat's official transports is LiveKit. So "which one?" has a third answer: both.

This guide is the hands-on version of that answer. You get the responsibility split, a call-flow walkthrough, and code checked against the current Pipecat LiveKit transport docs and the official example. You also get an outbound call sequence, a three-way comparison table and the failure modes that only the hybrid has. If you want the wider comparison first, start with our Pipecat vs LiveKit hub.

3
Connect attempts in the Pipecat LiveKit transport before it gives up
16 kHz / 24 kHz
Default Pipecat pipeline input / output sample rates
10 min
Minimum lifetime of LiveKit's auto-refreshed reconnect tokens
2
Systems whose logs you must join to debug one call

The first three figures come from the Pipecat source and the LiveKit docs, as cited below. The fourth is simply the number of systems in the hybrid. Figures later in the post marked "illustrative" are our own examples, not benchmarks.

Why LiveKit and Pipecat together is not a contradiction

The rivalry idea comes from LiveKit Agents. LiveKit ships its own agent framework, so it competes with Pipecat at the orchestration layer. But LiveKit is also a media platform. That part does not compete with Pipecat at all. Pipecat needs a transport, and LiveKit is one of the options.

LiveKit: an open-source WebRTC media platform built around rooms, participants and tracks. It also offers native SIP, phone numbers, recording through egress, client SDKs, a cloud service, and the separate LiveKit Agents framework.

Pipecat: an open-source Python framework that moves frames of audio, text and events through a pipeline of processors. It is transport-agnostic. Its transports include Daily, LiveKit, SmallWebRTC, WebSockets and telephony serializers.

Hybrid voice agent stack: a setup where LiveKit provides the media and telephony layer and a Pipecat pipeline provides the agent logic. The Pipecat bot is just another participant in a LiveKit room.

Why do teams do this? The most common pattern goes like this. A team picks Pipecat for orchestration. They like its frame model, its `ParallelPipeline` forks and its swap-anything services. Then they need phone calls from specific carriers. Pipecat does not terminate SIP itself. Its telephony overview lists Daily PSTN, Daily plus a SIP carrier, or carrier WebSocket media streams from Twilio, Telnyx, Plivo and Exotel. LiveKit, by contrast, has native SIP trunks, dispatch rules and a Phone Numbers product. So the team runs LiveKit as the transport. Calls arrive through LiveKit SIP, and Pipecat does the thinking.

The second pattern is simpler. A company already runs LiveKit rooms and wants an agent in them, but its voice team knows Pipecat. We cover the carrier side in depth in LiveKit vs Pipecat for telephony.

Who does what in a LiveKit plus Pipecat hybrid

Draw a hard line between layers. LiveKit gets audio in and out. Pipecat decides what to say and when.

Who does what in a LiveKit plus Pipecat hybrid: LiveKit handles media, rooms, SIP and recording, while Pipecat handles the pipeline, turn-taking, interruptions and tools
ResponsibilityOwner in the hybridNotes
WebRTC media, rooms, participants, tracksLiveKitPipecat subscribes and publishes like any client
SIP trunks, inbound and outbound callsLiveKitDispatch rules decide which room a caller lands in
Phone numbers and carriersLiveKit plus your carrierTwilio, Telnyx, Plivo, Wavix and others via SIP trunks
Recording and egressLiveKitRoom or track recording, independent of Pipecat
Browser and mobile client SDKsLiveKitPipecat also has a LiveKit transport in its JS client SDK
Tokens and room permissionsYour server, via LiveKit server SDKThe bot needs a join token like any participant
Launching the bot for each callYour serverLiveKit Agents dispatch does not launch Pipecat bots
Speech-to-text, LLM, text-to-speechPipecatAny service Pipecat supports, such as Deepgram or Cartesia
Turn detection and endpointingPipecatSilero VAD plus Smart Turn by default
Interruptions and barge-inPipecatFrames cancel TTS and flush the pipeline
Tool calls and parallel branchesPipecatFunction calling, `ParallelPipeline`
DTMFSharedLiveKit delivers SIP DTMF; Pipecat turns it into `InputDTMFFrame`
ObservabilitySplitMedia metrics in LiveKit; pipeline metrics and traces in Pipecat

Two rows deserve attention. First, bot launch. With LiveKit Agents, a dispatch rule can name an agent, and LiveKit sends the job to a registered worker. A Pipecat bot is not a LiveKit Agents worker. So you must detect the new room yourself, usually with a webhook, and start the bot. Second, observability. It is split by design, and that split is the root of most debugging pain later.

How a phone call flows through the hybrid

Here is the inbound path, end to end. Every hop is a place where latency adds up and calls can fail.

The hybrid stack: a phone call enters through a carrier and LiveKit SIP into a LiveKit room, and a Pipecat pipeline joins the room as the agent to run speech-to-text, the LLM, text-to-speech and turn-taking

The caller dials a number owned by your carrier or by LiveKit Phone Numbers. The carrier sends a SIP INVITE to your LiveKit SIP endpoint through an inbound trunk. A dispatch rule matches the call and places the caller in a room as a SIP participant. LiveKit fires a `participant_joined` webhook to your server. Your server mints a token and starts a Pipecat bot for that room. The bot connects, subscribes to the caller's audio track, and publishes its own `pipecat-audio` track. From then on, audio flows caller to LiveKit to Pipecat and back.

The dispatch rule below comes from the LiveKit dispatch rule docs. An individual rule creates a new room per caller, here prefixed with `call-`. Note what is missing: the `roomConfig.agents` block. That block dispatches LiveKit Agents workers, which you are not using.

{
  "dispatch_rule": {
    "rule": {
      "dispatchRuleIndividual": {
        "roomPrefix": "call-"
      }
    },
    "name": "Inbound calls to Pipecat"
  }
}

One privacy detail matters here. The individual rule names the room after the caller's phone number plus a random suffix. LiveKit's token docs warn that room names and identities appear in logs and are not removed by PII redaction. If that is a problem for you, LiveKit's callee rule can name rooms after an opaque ID in the SIP destination instead. Check the dispatch rule docs for that setup.

The Pipecat LiveKit transport, verified

Install the extra with `uv add "pipecat-ai[livekit]"`. The transport needs three things: the server URL, a JWT token, and the room name. All three are required constructor arguments. Parameters go in `LiveKitParams`, which inherits every field of Pipecat's `TransportParams`.

The code below is adapted from Pipecat's official LiveKit example on the main branch. Class names follow current Pipecat. `PipelineTask` was deprecated in 1.3.0 in favor of `PipelineWorker`, so older tutorials will look different. The Smart Turn wiring follows the Smart Turn docs. Treat it as simplified and check the docs for your version.

# bot.py - Pipecat pipeline on LiveKit transport
# Adapted from pipecat/examples/transports/transports-livekit.py (simplified - check the docs)
import os

from pipecat.audio.turn.smart_turn.local_smart_turn_v3 import LocalSmartTurnAnalyzerV3
from pipecat.audio.vad.silero import SileroVADAnalyzer
from pipecat.frames.frames import TTSSpeakFrame
from pipecat.pipeline.pipeline import Pipeline
from pipecat.pipeline.worker import PipelineParams, PipelineWorker
from pipecat.processors.aggregators.llm_context import LLMContext
from pipecat.processors.aggregators.llm_response_universal import (
    LLMContextAggregatorPair,
    LLMUserAggregatorParams,
)
from pipecat.services.cartesia.tts import CartesiaTTSService
from pipecat.services.deepgram.stt import DeepgramSTTService
from pipecat.services.openai.llm import OpenAILLMService
from pipecat.transports.livekit.transport import LiveKitParams, LiveKitTransport
from pipecat.turns.user_stop import TurnAnalyzerUserTurnStopStrategy
from pipecat.turns.user_turn_strategies import UserTurnStrategies
from pipecat.workers.runner import WorkerRunner


async def run_bot(url: str, token: str, room_name: str, greet_first: bool = True):
    transport = LiveKitTransport(
        url=url,
        token=token,
        room_name=room_name,
        params=LiveKitParams(audio_in_enabled=True, audio_out_enabled=True),
    )

    stt = DeepgramSTTService(api_key=os.environ["DEEPGRAM_API_KEY"])
    llm = OpenAILLMService(
        api_key=os.environ["OPENAI_API_KEY"],
        settings=OpenAILLMService.Settings(
            system_instruction="You are a phone agent. Keep answers short and speakable.",
        ),
    )
    tts = CartesiaTTSService(
        api_key=os.environ["CARTESIA_API_KEY"],
        settings=CartesiaTTSService.Settings(voice=os.environ["CARTESIA_VOICE_ID"]),
    )

    context = LLMContext()
    user_agg, assistant_agg = LLMContextAggregatorPair(
        context,
        user_params=LLMUserAggregatorParams(
            vad_analyzer=SileroVADAnalyzer(),
            user_turn_strategies=UserTurnStrategies(
                stop=[TurnAnalyzerUserTurnStopStrategy(turn_analyzer=LocalSmartTurnAnalyzerV3())]
            ),
        ),
    )

    pipeline = Pipeline([
        transport.input(),   # caller audio from the LiveKit room
        stt,
        user_agg,
        llm,
        tts,
        transport.output(),  # bot audio published as the "pipecat-audio" track
        assistant_agg,
    ])

    worker = PipelineWorker(pipeline, params=PipelineParams(enable_metrics=True))

    @transport.event_handler("on_first_participant_joined")
    async def on_first_participant_joined(transport, participant_id):
        # participant_id is the LiveKit identity, e.g. the SIP participant
        if greet_first:
            await worker.queue_frame(TTSSpeakFrame("Thanks for calling. How can I help?"))

    @transport.event_handler("on_participant_left")
    async def on_participant_left(transport, participant_id, reason):
        await worker.cancel()  # caller hung up: end the pipeline, free the process

    runner = WorkerRunner()
    await runner.add_workers(worker)
    await runner.run()

A few verified details shape how this behaves on real calls:

  • Identities, not session IDs. Participant IDs in the transport are LiveKit identities, the `identity` field set in the token. Events and methods such as `mute_participant()` use them.
  • Late joins are handled. If the caller is already in the room when the bot connects, the transport fires `on_first_participant_joined` right after connect. That is the normal case for inbound calls.
  • DTMF works. The `on_dtmf_event` handler fires for SIP DTMF. The transport also pushes an `InputDTMFFrame` downstream, so Pipecat's `DTMFAggregator` works on LiveKit calls.
  • Resampling is automatic. The transport resamples incoming LiveKit audio to the pipeline's input rate. Pipecat's `PipelineParams` defaults to 16 kHz in and 24 kHz out.

The official example waits one second before greeting. Audio sent before the other side is subscribed can be lost, so the first second matters.

Joining a room with a server-generated token

The bot needs its own token. Pipecat's runner helper shows the recommended grants. It builds an `AccessToken` with `room_join=True`, the room name, and `agent=True`. Pipecat's source notes that the agent flag lets LiveKit clients know an agent has joined. The Python SDK's default token lifetime is six hours if you set no TTL.

The launcher below receives LiveKit's webhook, filters for SIP participants, mints a token and starts the bot. Webhook validation uses LiveKit's receiver, which checks the signed JWT in the `Authorization` header, per the LiveKit webhooks docs. This is simplified. Check the server SDK reference before shipping.

# launcher.py - start one Pipecat bot per inbound SIP call (simplified - check the docs)
import asyncio
import os
from datetime import timedelta

from fastapi import FastAPI, Request
from livekit import api
from livekit.protocol.models import ParticipantInfo

from bot import run_bot

app = FastAPI()
receiver = api.WebhookReceiver(api.TokenVerifier())  # reads LIVEKIT_API_KEY / SECRET


def bot_token(room_name: str) -> str:
    return (
        api.AccessToken(os.environ["LIVEKIT_API_KEY"], os.environ["LIVEKIT_API_SECRET"])
        .with_identity(f"agent-{room_name}")
        .with_name("Pipecat Agent")
        .with_ttl(timedelta(minutes=5))  # only needs to be valid at connect time
        .with_grants(api.VideoGrants(room_join=True, room=room_name, agent=True))
        .to_jwt()
    )


@app.post("/livekit/webhook")
async def livekit_webhook(request: Request):
    body = (await request.body()).decode()
    event = receiver.receive(body, request.headers.get("Authorization", ""))

    if event.event == "participant_joined" and event.participant.kind == ParticipantInfo.Kind.SIP:
        room = event.room.name
        # In production, hand this to a warm worker pool instead of the web process
        asyncio.create_task(run_bot(os.environ["LIVEKIT_URL"], bot_token(room), room))
    return {"ok": True}

Three design choices in that snippet are deliberate.

  • Short TTL is fine. LiveKit token expiry only affects the initial connection, not reconnects. Once connected, the server issues refreshed tokens that last at least 10 minutes. So a five-minute token covers a 40-minute call. What it does not cover is a queue delay longer than five minutes before the bot connects.
  • A unique bot identity per room. Two participants with the same identity in one room collide. Deriving the identity from the room name keeps retries idempotent.
  • Webhooks are best effort. LiveKit retries failed deliveries but does not guarantee them. Make the handler idempotent and fast, and add a sweep for SIP rooms that have no agent after a few seconds.

Outbound calls with Pipecat and LiveKit SIP

Outbound flips the order. Your server creates the room, starts the bot, then asks LiveKit to dial. The dial uses `CreateSIPParticipant`, documented in LiveKit's outbound calls guide. With `wait_until_answered=True`, the call blocks until pickup and raises `SipCallError` on busy, no answer or trunk failure.

# dialer.py - outbound call into a room where the Pipecat bot is already waiting
# (simplified - check the docs)
import asyncio
import os
import uuid

from livekit import api
from livekit.protocol.sip import CreateSIPParticipantRequest

from bot import run_bot
from launcher import bot_token


async def place_call(phone_number: str):
    room = f"out-{uuid.uuid4().hex[:12]}"  # opaque name, no phone number in logs
    lkapi = api.LiveKitAPI()

    # 1. Bot joins first so it is warm before anyone answers
    bot = asyncio.create_task(
        run_bot(os.environ["LIVEKIT_URL"], bot_token(room), room, greet_first=False)
    )

    # 2. Dial. The SIP participant joins the room while the phone rings
    try:
        await lkapi.sip.create_sip_participant(
            CreateSIPParticipantRequest(
                sip_trunk_id=os.environ["LIVEKIT_SIP_OUTBOUND_TRUNK_ID"],
                sip_call_to=phone_number,
                room_name=room,
                participant_identity=f"callee-{uuid.uuid4().hex[:8]}",
                wait_until_answered=True,
            )
        )
    except api.SipCallError:
        bot.cancel()  # busy, declined, no answer, or trunk failure
        raise
    finally:
        await lkapi.aclose()

Why `greet_first=False`? LiveKit notes that SIP participants emit no audio while the call connects. The SIP participant can exist in the room before the callee picks up. If the bot greets on `on_first_participant_joined`, it may talk into a ringing line. Two fixes work. Let the callee speak first, since most people answer with "Hello?". Or signal the bot after `create_sip_participant` returns, then queue the greeting.

The outbound sequence, hop by hop:

StepActorWhat happensWhat can go wrong
1Your serverCreate room name, mint bot tokenToken TTL shorter than bot start time
2Pipecat botConnects, publishes `pipecat-audio`Cold start; connect retries add seconds
3Your server`CreateSIPParticipant` with trunk and numberWrong trunk, caller ID rejected by carrier
4LiveKit SIPINVITE to carrier; SIP participant joins, ringingBot greets a ringtone
5CalleeAnswers; `sip.callStatus` becomes `active`Voicemail also answers with 200 OK
6PipecatHears "Hello?", respondsFirst word clipped before subscription settles
7Either sideHang up; participant leavesBot process lingers and burns capacity

Step 5 hides a trap. Voicemail answers at the SIP layer with `200 OK`, so it is not an error. LiveKit documents answering machine detection, but check whether the feature you rely on is exposed outside LiveKit Agents. In the hybrid, you may need your own detection step in the Pipecat pipeline.

Hybrid vs pure LiveKit Agents vs pure Pipecat

Three architectures can take a phone call. The hybrid trades simplicity for a specific mix of capabilities.

DimensionHybrid (Pipecat on LiveKit)Pure LiveKit AgentsPure Pipecat (Daily or carrier WebSocket)
Media layerLiveKit rooms and WebRTCLiveKit rooms and WebRTCDaily WebRTC, or carrier media streams
SIP terminationNative LiveKit SIPNative LiveKit SIPDaily SIP/PSTN, or carrier WebSocket
Agent launch per callYour webhook and launcherBuilt-in dispatch to workersDaily dial-in webhook, or your server
Orchestration modelPipecat frames and processorsLiveKit Agents sessionsPipecat frames and processors
Turn detectionPipecat Smart Turn plus VADLiveKit turn detector modelPipecat Smart Turn plus VAD
Parallel branches`ParallelPipeline`Framework-specific patterns`ParallelPipeline`
Dependency treesTwo (Pipecat and LiveKit SDKs)OneOne, plus the transport SDK
ObservabilitySplit across two systemsMostly one systemMostly one system, plus carrier logs
Multi-party roomsStrong (LiveKit rooms)StrongGood on Daily; limited on WebSocket
Main riskSeams between the twoLock-in to one framework's modelCarrier-specific media handling

For the orchestration trade-offs alone, see how to choose between LiveKit and Pipecat. For hosted options, compare Pipecat Cloud vs LiveKit Cloud.

When the hybrid makes sense, and when it does not

The hybrid earns its complexity in a few clear cases.

  • You need LiveKit SIP and Pipecat's pipeline. You want calls from a carrier that LiveKit trunks well, and your team is invested in Pipecat processors. This is the most common reason.
  • You already run LiveKit. Your app, recording and client SDKs live on LiveKit. Adding a Pipecat participant is cheaper than moving media.
  • You need multi-party rooms. Warm transfers, supervisor listen-in and agent assist all fit LiveKit's room model. The Pipecat bot is one participant among several.
  • You want to swap orchestration later. Keeping media on LiveKit means you can move from Pipecat to LiveKit Agents, or back, without touching phone numbers.

It is the wrong choice in other cases.

  • You are starting fresh with no carrier constraint. Pure LiveKit Agents or pure Pipecat on Daily gives one framework, one set of docs and one support channel.
  • Your team is small. Two dependency trees means two upgrade cycles. A breaking change in either SDK can break calls.
  • You would pay for features you disable. LiveKit Agents includes its own turn detector, session model and dispatch. In the hybrid you use none of them. If those features appeal to you, use LiveKit Agents directly.

We break down the orchestration layer in the best orchestration for voice agents in 2026. The transport question is covered in SIP vs WebRTC for voice agents.

How to run Pipecat on LiveKit transport for phone calls

This is the full build order. Each step lists what to verify before moving on.

1. Create a LiveKit project and credentials. Use LiveKit Cloud or a self-hosted server. Set `LIVEKIT_URL`, `LIVEKIT_API_KEY` and `LIVEKIT_API_SECRET`. Verify with a test token that a browser client can join a room.

2. Install Pipecat with the LiveKit extra. Run `uv add "pipecat-ai[livekit]"` and add your STT, LLM and TTS services. Verify the official LiveKit example talks to you in a browser room.

3. Set up SIP. Create an inbound trunk for your number from Twilio, Telnyx, Plivo, Wavix or LiveKit Phone Numbers. Point the carrier's SIP URI at LiveKit. Verify a test call creates a SIP participant.

4. Create a dispatch rule without an agents block. Use an individual rule with a prefix, or a callee rule with opaque IDs. Verify each call lands in its own room.

5. Configure the webhook. Point LiveKit webhooks at your launcher. Filter `participant_joined` events for SIP participants. Verify signature validation rejects a forged request.

6. Mint a bot token per room. Grant `room_join`, the room name and `agent=True`. Use a unique identity per room. Verify two concurrent calls get two distinct bots.

7. Launch bots from a warm pool. Keep pre-started worker processes with models loaded. Hand each room to a free worker. Verify time from ring to first bot word on 20 test calls.

8. Handle hang-up and cleanup. End the pipeline on `on_participant_left`. Verify no bot process survives its call.

9. Add outbound dialing. Start the bot first, then call `CreateSIPParticipant` with `wait_until_answered=True`. Gate the greeting on answer. Verify busy and no-answer paths end cleanly.

10. Join the logs. Tag Pipecat metrics and traces with the room name and SIP call attributes. Verify you can pull one call's full timeline from both systems.

11. Test the whole path on real calls. Run scripted calls over the PSTN with noise, accents and interruptions. Measure latency, barge-in and task success per call.

For the Pipecat side of step 11, our Pipecat voice agent testing guide covers pipeline tests. The LiveKit voice agent testing guide covers the room and SIP side.

Hybrid-specific failure modes

The hybrid adds failure modes at the seam between the two frameworks. These are the common ones.

Failure modeSymptom on the callRoot causeFix
Late bot joinSilence for seconds after pickupWebhook delay plus process start plus model loadWarm pool; pre-join the bot for outbound
Connect retry stallLong silence, then the bot appearsTransport retries connect 3 times with 4 to 10 second waitsAlert on first connect failure; check URL and token
Token rejectedBot never joins; caller hears nothingTTL expired in a queue, wrong room in grantMint at dispatch time; log the token's room and expiry
Duplicate agentsTwo voices greet the callerOld dispatch rule still sends a LiveKit Agents workerRemove `roomConfig.agents` from SIP rules
Missed track subscriptionBot talks but never hearsCaller track not published or not subscribedLog `on_audio_track_subscribed`; fail the call if absent
Sample-rate mismatchChipmunk or muffled audio, poor transcriptsCustom processors assuming a rate the pipeline does not useKeep one declared rate; resample at the edges only
Clipped first wordGreeting starts mid-wordAudio sent before subscription settlesShort delay or wait for track subscription
Double turn detectionBot talks over pauses or waits too longSTT endpointing and Smart Turn both deciding turnsPick one owner for end-of-turn; tune the other off
Echo and false barge-inBot interrupts itself mid-sentenceCaller speakerphone feeds bot audio back on the PSTN legNoise and echo filtering; barge-in thresholds
Reconnect gapAudio drops for a few seconds mid-callNetwork change on the bot hostMonitor reconnect events; keep bots near LiveKit region
Orphaned botsCapacity drains over hoursHang-up not propagated to the pipelineCancel on participant left; add an idle timeout
Split observabilityNobody can explain a bad callMedia metrics in LiveKit, pipeline metrics in PipecatShared call ID across both systems

A few of these need more than a table row.

Cold start before the first word. Add up the inbound path. Webhook delivery, bot process start, model loading, room connect and track subscription all happen after the caller is already in the room. In one illustrative budget, that is 150 ms for the webhook, 800 ms to start a Python process, 400 ms to load VAD and Smart Turn, and 300 ms to connect. That is over 1.6 seconds of silence before the bot can speak. A warm pool removes most of it. Our LiveKit vs Pipecat latency guide breaks down the rest of the budget.

Double turn detection. In the hybrid, turn-taking lives in the Pipecat pipeline. You will not run LiveKit's turn detector, because it belongs to LiveKit Agents. But you can still get two deciders. Many STT services have their own endpointing, and Pipecat's aggregator has VAD plus Smart Turn. If both can end a turn, the bot answers early on some calls and late on others. Decide who owns end-of-turn. Our endpointing guide and VAD vs endpointing explainer cover the tuning.

Echo on phone calls. Browsers usually cancel echo. PSTN callers on speakerphone give no such guarantee. The bot's voice can return on the caller's track and trigger a barge-in. Test with speakerphone audio, and see our barge-in guide for thresholds.

Split observability. LiveKit knows about packet loss, SIP status and who was in the room. Pipecat knows about STT latency, LLM time to first byte and interruptions. Neither knows about the other. Put the room name and SIP call attributes on every Pipecat span. Our OpenTelemetry guide for voice agents shows the span layout.

Testing the seams of a hybrid voice agent

Here is the uncomfortable part. Neither framework tells you when call quality degrades. LiveKit will report a healthy room while the bot misses every other turn. Pipecat will report fast TTFB while the caller hears a clipped greeting. The hybrid adds a second seam, and seams are where calls break.

Pipeline unit tests miss late joins. Browser room tests miss PSTN echo and narrowband audio. Only a real call covers every seam: carrier, LiveKit SIP, the Pipecat bot, and back.

Measure what callers feel: pickup to first word, 95th percentile turn latency, barge-in success and task completion. Run it on many calls, with noise and accents, after every SDK upgrade. Our guides on stress testing voice AI and voice agent test automation show how to set that up.

Two frameworks, two seams — test both
Evalgent calls your LiveKit plus Pipecat agent over real phone lines and scores every seam, independently.
Book a demo

Evalgent sells no transport or framework. We call your agent like a customer would and score the call end to end. That matters most when both vendors' logs say everything is fine. See independent voice AI evaluation.

Frequently asked questions

Can you use LiveKit and Pipecat together?

Yes. Pipecat ships an official `LiveKitTransport`, installed with the `pipecat-ai[livekit]` extra. Your Pipecat bot joins a LiveKit room as a participant using a URL, a JWT token and a room name. LiveKit handles media, rooms and SIP. Pipecat handles speech-to-text, the LLM, text-to-speech, turn-taking and interruptions. Many teams run exactly this setup for phone agents.

How do you connect Pipecat to LiveKit?

Create a `LiveKitTransport` with your LiveKit server URL, a token and the room name, plus `LiveKitParams` for audio settings. Place `transport.input()` at the start of your pipeline and `transport.output()` near the end. Mint the token server-side with `room_join`, the room name and `agent=True`. Then run the pipeline with Pipecat's worker and runner classes.

Does Pipecat support LiveKit SIP?

Indirectly. Pipecat does not terminate SIP itself, but LiveKit does. A SIP caller becomes a participant in a LiveKit room, and the Pipecat bot joins that room. The transport also handles SIP DTMF, firing `on_dtmf_event` and pushing `InputDTMFFrame` downstream. Pipecat's own telephony docs cover Daily and carrier WebSockets, so the LiveKit SIP path is yours to wire.

How does a phone call reach a Pipecat bot on LiveKit?

The carrier sends the call to LiveKit through an inbound SIP trunk. A dispatch rule places the caller in a room as a SIP participant. LiveKit fires a `participant_joined` webhook to your server. Your server mints a token and starts a Pipecat bot for that room. The bot connects, subscribes to the caller's audio and starts the conversation.

Should I use LiveKit Agents or Pipecat on LiveKit?

Use LiveKit Agents if you want one framework with built-in dispatch and LiveKit's turn detector. Use Pipecat on LiveKit if your team is invested in Pipecat's pipeline, or if you want to keep orchestration portable. Both use the same rooms and SIP. The hybrid costs you a second dependency tree and your own bot launcher.

How do you make outbound calls with Pipecat and LiveKit?

Create an opaque room name, start the Pipecat bot in it, then call `CreateSIPParticipant` with your outbound trunk, the number and `wait_until_answered=True`. Handle `SipCallError` for busy, declined and no-answer outcomes. Do not greet on participant join, because the SIP participant can appear while the phone is still ringing. Greet after answer instead.

Why does my Pipecat bot join the LiveKit room late?

Usually because everything starts after the caller arrives. Webhook delivery, Python process start, VAD and Smart Turn loading, and room connect all add up. Connect failures add more, since the transport retries three times with waits of four to ten seconds. Use a warm pool of started bots, and pre-join the bot on outbound calls.

How do you test a LiveKit and Pipecat hybrid?

Test the whole path on real phone calls, not just the pipeline. Place scripted calls over a carrier, through LiveKit SIP, into the bot. Measure pickup to first word, 95th percentile turn latency, barge-in success and task completion. Include noise, accents and speakerphone. Rerun after every upgrade of either framework, since both can change behavior.

The bottom line

LiveKit and Pipecat work together as a hybrid voice agent stack, with LiveKit carrying media and SIP and Pipecat running the agent. That split adds seams at bot launch, tokens, audio and observability, so the hybrid is only as good as your end-to-end tests on real calls.

Want to see where your hybrid breaks before callers do? Book a demo with Evalgent.

Related Articles