Desarrollo IATutorial

Armá un Agente de Voz en 2026: Pipeline con LiveKit, Tool Calls y Presupuesto de Latencia

14 de octubre de 2026
13 min de lectura
Péndulo de Newton de esferas de acero pulido en pleno movimiento sobre un fondo gris suave
Compartir:

Un chatbot puede tardar dos segundos en responder y a nadie le molesta. Un agente de voz que tarda dos segundos se siente roto: el que llama repite “¿hola?”, le habla encima a la respuesta y todo el turno se desarma. La voz es primero un problema de latencia y de turnos, y recién después de redacción de prompts.

Esta es la construcción práctica de la ruta STT → LLM → TTS. Uso LiveKit Agents (livekit-agents 1.8.7, publicado el 14 de octubre de 2026) para el ejemplo, cubro VAD, detección de turnos, barge-in, tool calls en plena llamada y traspaso telefónico, y lo comparo con Pipecat 1.12.0 usando solo hechos que verifiqué en la documentación oficial. No corrí el agente ni medí latencia: cada número acá lleva su fuente y su fecha.

1. El Problema: la Voz es un Pipeline con Reloj

Un turno de voz cruza al menos cinco etapas: la red, la detección de actividad de voz, el speech-to-text, el LLM y el text-to-speech. El usuario escucha silencio durante la suma de todo lo que no superpusiste. La documentación de LiveKit dice que una conversación se siente natural cuando la latencia de punta a punta queda por debajo de un segundo, y la introducción de Pipecat dice que sus pipelines suelen caer entre 500 y 800 ms. Ambas son afirmaciones de proveedor, no garantías, y ninguna conoce tu STT, tu modelo ni tu región.

El Error Más Común

Construirlo como una app de chat: esperar la transcripción completa, esperar la respuesta completa y recién ahí sintetizar todo. Cada etapa espera a la anterior, caés en los 1.000 a 2.000+ ms que el blog de LiveKit da para un pipeline bloqueante ingenuo, y la primera interrupción lo empeora porque nada sabe cortarse a mitad de frase.

La solución es estructural, no un modelo más rápido: streaming en cada etapa, decidir los límites del turno con algo más listo que un temporizador de silencio, y tratar la interrupción como un evento de primera clase. Eso es lo que te dan los frameworks de abajo, y por eso no armaría este loop a mano.

Para la alternativa speech-to-speech y su precio por minuto, mirá mi post de Grok Voice 3.0. Este es el camino del pipeline, armado a mano.

2. Conceptos Mínimos Antes de Tocar Código

Seis ideas explican casi todas las perillas que vas a tocar.

🎚️

VAD

el detector de voz

Un modelo local chico marca voz versus silencio cuadro a cuadro. LiveKit y Pipecat usan Silero en CPU. Alimenta la detección de turnos y de interrupciones; no entiende palabras.

🧠

Detección de turnos

cuándo terminó el usuario

Un temporizador de silencio corta a la gente a mitad de idea. El turn detector de LiveKit y el Smart Turn de Pipecat son modelos que pesan qué se dijo y cómo antes de cerrar el turno.

✋

Barge-in

el usuario habla encima del agente

La reproducción se corta, el trabajo en curso del LLM y del TTS se cancela y el historial conserva solo lo que el usuario realmente escuchó. Un backchannel como “aja” no debe contar como interrupción.

🌊

Streaming en todas partes

superponer las etapas

Transcripciones parciales, tokens en streaming, TTS que habla con el primer texto que recibe. Según el blog de LiveKit, el streaming acerca la latencia total a la de la etapa más lenta, no a la suma.

⚡

Generación preventiva

adivinar antes

LiveKit arranca el LLM apenas llega una transcripción final, antes de confirmar el turno, y descarta el borrador si el usuario sigue hablando. Gasta tokens para ganar tiempo y viene activada por defecto.

📞

Transporte

de dónde viene el audio

Una sala WebRTC para navegadores y apps, un trunk SIP o un stream de medios por WebSocket para teléfonos. Mismo código de agente, otra puerta de entrada.

Presupuesto de latencia: una herramienta de diseño, no un resultado

Valores típicos tal como los declaran los proveedores. Son referencias a reemplazar por tu propio p50 y p95, no mediciones mías.

EtapaTípico según el proveedorFuenteMétrica a registrar
Red y transporteSin cifra en las fuentes que leín/atimestamps del cliente
VAD10 a 50 msBlog de LiveKit, 2026-03-23VADMetrics
Espera de fin de turno0,3 s mínimo con el turn detector, 0,5 s sin élDocs de LiveKit, leídas el 2026-10-14end_of_turn_delay
Transcripción STTUnos 200 ms por enunciado; parciales en menos de 100 msBlog de LiveKit, 2026-03-23transcription_delay
Primer token del LLM300 a 800 msBlog de LiveKit, 2026-03-23llm_node_ttft
Primer audio del TTS100 a 200 ms con streamingBlog de LiveKit, 2026-03-23tts_node_ttfb
Turno completo500 a 800 ms “en la mayoría de los casos” (Pipecat); 300 a 600 ms en cascada con streaming (LiveKit)Docs de Pipecat y blog de LiveKit, leídos el 2026-10-14e2e_latency

Mi aritmética, no una medición: sumá las etapas de una sola cifra sin superposición y da 610 a 1.250 ms, más la espera de 300 a 500 ms del fin de turno, o sea entre 0,9 y 1,75 s. La mitad alta de ese rango queda por encima del “menos de un segundo” de la propia LiveKit, y justamente por eso existen la superposición por streaming y la generación preventiva. Los totales de 300 a 600 ms y de 400 a 800 ms del blog están en tablas distintas y no se concilian entre sí.

El modelo mental a conservar

Un agente de voz es un pipeline con reloj. Cada decisión de diseño o quita una espera (streaming, generación preventiva, mejor señal de fin de turno) o quita una falla (limpieza de ruido, reglas de interrupción). Si un cambio no hace ninguna de las dos, no es un cambio de agente de voz.

3. LiveKit Agents vs Pipecat: Qué Verifiqué

Ambos son frameworks Python open source para exactamente este pipeline, y ambos son buenos. La tabla lista solo hechos que leí en la documentación oficial, en tags del código fuente o en índices de paquetes el 14 de octubre de 2026.

Uso LiveKit para el ejemplo por una sola razón: su quickstart es un archivo único y completo, así que pude chequear cada llamada contra el tag 1.8.7. El quickstart de los docs de Pipecat muestra fragmentos, y sus docs y su repositorio de quickstart hoy usan nombres de clase distintos (ver la nota debajo de la tabla).

Hechos verificados, lado a lado

Leído el 2026-10-14 en docs oficiales, tags de GitHub, changelogs y PyPI.

LiveKit AgentsPipecat
Última en PyPIlivekit-agents 1.8.7 (2026-10-14); también existe un pre-release 2.0.0b1pipecat-ai 1.12.0 (2026-09-26)
Licencia y PythonApache-2.0, Python 3.10 a 3.14BSD-2-Clause, Python 3.11+
TransporteEl agente entra a una sala WebRTC en un servidor LiveKit (Cloud o propio)Intercambiable: Daily, LiveKit, SmallWebRTC peer to peer, WebSocket, WhatsApp
VADPlugin Silero, corre en CPUSilero en CPU, o Krisp VIVA VAD
Detección de turnosModelo de audio turn detector (v1 en LiveKit Inference, v1-mini en CPU local); opciones: solo VAD, endpointing del STT o manualModelo Smart Turn por defecto (LocalSmartTurnAnalyzerV3); una estrategia de speech timeout como opción más simple
Barge-inActivo por defecto; el modo adaptive en LiveKit Cloud filtra backchannel; el historial conserva solo lo que el usuario escuchó; las falsas interrupciones silenciosas se reanudanActivo por defecto; un InterruptionFrame cancela LLM, TTS y audio en cola; solo se conserva el texto hablado; MinWords y estrategias de mute
Tools en plena llamada@function_tool; tools async con ctx.update y filler; flag CANCELLABLEFunciones directas; @tool_options(cancel_on_interruption, timeout_secs)
TelefoníaTrunks SIP y dispatch rules, transferencias frías (REFER) y cálidas, DTMF, números de EE.UU. en CloudPSTN y SIP vía Daily; serializers WebSocket para Twilio, Telnyx, Plivo y Exotel (sin transferencias por esa vía, según los docs)
Hosting y preciosServidor open source propio o LiveKit Cloud; su página de precios lista sesiones de agente a $0.01 por minuto sobre la cuota del plan (sin fecha de vigencia)Framework propio, o Pipecat Cloud (hosteado por Daily, por uso, calculadora en su página de precios); regiones propias vía Enterprise
Quickstartagent.py completo; lk agent devpipecat init quickstart; uv run bot.py; cliente en localhost:7860

Nombres en Pipecat: los docs arman el pipeline con PipelineWorker y WorkerRunner, mientras que el bot.py del repositorio pipecat-quickstart que leí todavía importa PipelineTask y PipelineRunner. El changelog registra el cambio de nombre y dice que los nombres viejos emiten un DeprecationWarning. Tomá los nombres de los docs y esperá que los tutoriales viejos usen los anteriores.

Elegí por restricciones, no por benchmarks

¿Ya estás en Daily o necesitás un transporte poco común? Los transportes intercambiables de Pipecat son el atractivo. ¿Querés un solo SDK para salas, agentes, SIP y una opción hosteada? LiveKit. No hice benchmarks de ninguno, y no conozco una comparación pareja de los proveedores en la que confiaría.

4. Paso 1: Armá el Agente y Corré la Base

Todo en este paso sale del quickstart de Voice AI de LiveKit. No lo corrí contra un proyecto real: lo copié de la documentación y revisé imports y firmas contra el tag livekit-agents 1.8.7 en GitHub.

Instalá la CLI, generá el proyecto, corré

Comandos del quickstart. El modo console corre el agente en tu terminal; dev lo conecta a LiveKit. Ambos necesitan tus credenciales de LiveKit porque los modelos corren en LiveKit Inference.

# 1) LiveKit CLI (pick your OS), then link your LiveKit Cloud project
winget install LiveKit.LiveKitCLI             # Windows
brew install livekit-cli                      # macOS
curl -sSL https://get.livekit.io/cli | bash   # Linux
lk cloud auth

# 2) Scaffold the official Python starter and run it
lk agent init my-agent --template agent-starter-python
cd my-agent
uv sync
lk agent console   # talk to it in your terminal (add --text for text only)
lk agent dev       # connect it to LiveKit and use the Agent Console

agent.py: la base STT-LLM-TTS

El archivo de pipeline del quickstart. El agent_name es el nombre que ingresás en el Agent Console; el plugin de cancelación de ruido necesita LiveKit Cloud, y el quickstart indica quitarlo y usar plugins de proveedores si hosteás vos.

from dotenv import load_dotenv

from livekit import agents
from livekit.agents import AgentServer, AgentSession, Agent, inference, room_io, TurnHandlingOptions
from livekit.plugins import ai_coustics

load_dotenv(".env.local")


class Assistant(Agent):
    def __init__(self) -> None:
        super().__init__(
            instructions="""You are a helpful voice AI assistant.
            You eagerly assist users with their questions by providing information from your extensive knowledge.
            Your responses are concise, to the point, and without any complex formatting or punctuation including emojis, asterisks, or other symbols.
            You are curious, friendly, and have a sense of humor.""",
        )

server = AgentServer()

@server.rtc_session(agent_name="my-agent")
async def my_agent(ctx: agents.JobContext):
    session = AgentSession(
        stt=inference.STT(model="assemblyai/universal-3-6-pro", language="en"),
        llm=inference.LLM(model="google/gemma-4-31b-it"),
        tts=inference.TTS(
            model="fishaudio/s2.1-pro",
            voice="fa4c9eb3dccc4806b382b40d61c6b10a",
        ),
        turn_handling=TurnHandlingOptions(
            turn_detection=inference.TurnDetector(),
        ),
    )

    await session.start(
        room=ctx.room,
        agent=Assistant(),
        room_options=room_io.RoomOptions(
            audio_input=room_io.AudioInputOptions(
                noise_cancellation=ai_coustics.audio_enhancement(model=ai_coustics.EnhancerModel.QUAIL_VF_S),
            ),
        ),
    )

    await session.generate_reply(
        instructions="Greet the user and offer your assistance."
    )


if __name__ == "__main__":
    agents.cli.run_app(server)

Leé el archivo como un pipeline. inference.STT, inference.LLM e inference.TTS son las tres etapas en streaming, servidas por LiveKit Inference: no necesitás keys de proveedores pero sí un proyecto de LiveKit Cloud. inference.TurnDetector() es el modelo de fin de turno, y la sesión aporta el VAD que necesita. Las instrucciones le piden al modelo respuestas breves sin emojis ni asteriscos, porque la salida se habla; además la sesión quita markdown y emojis por defecto en 1.8.7.

Probá en la terminal antes de probar en un teléfono

lk agent console te da una sesión de voz o de texto sin sala, y --record guarda la sesión en disco. Usalo para iterar prompt y tools; reservá el modo dev y los dispositivos reales para chequear latencia y turnos.

5. Paso 2: Turnos, VAD y Barge-In

El manejo de turnos es donde un agente de voz se siente humano o robótico, y en 1.8.x vive en un solo objeto: TurnHandlingOptions. Esta es la forma de la configuración inicial recomendada por LiveKit, recortada a lo que importa.

Fin de turno, interrupciones, generación preventiva

Los valores son los defaults y el punto de partida documentados por LiveKit, no algo que haya afinado yo. Cambiá de a uno y mirá las métricas del paso 4.

from livekit.agents import AgentSession, TurnHandlingOptions, inference

session = AgentSession(
    stt=inference.STT(model="assemblyai/universal-3-6-pro", language="en"),
    llm=inference.LLM(model="google/gemma-4-31b-it"),
    tts=inference.TTS(model="fishaudio/s2.1-pro", voice="fa4c9eb3dccc4806b382b40d61c6b10a"),
    turn_handling=TurnHandlingOptions(
        # audio model that predicts end of turn on top of VAD
        turn_detection=inference.TurnDetector(),
        # wait after detected silence before closing the turn
        # (0.3 / 2.5 are the documented defaults with the audio turn detector)
        endpointing={"mode": "fixed", "min_delay": 0.3, "max_delay": 2.5},
        interruption={
            "mode": "adaptive",   # default on LiveKit Cloud; use "vad" when self-hosting
            "min_duration": 0.5,  # seconds of speech that count as a barge-in
            "min_words": 0,       # raise it (needs STT) to ignore "okay", "uh-huh"
            "resume_false_interruption": True,
            "false_interruption_timeout": 2.0,
        },
        # on by default; preemptive TTS trades wasted compute for lower latency
        preemptive_generation={"preemptive_tts": False},
    ),
)

Hosting propio o tus keys: cambiá a plugins

Uso de plugins según la página de docs de cada proveedor (los ids de modelo son los ejemplos de los docs; usá los que corrés). El turn detector v1-mini corre en CPU local sin costo; el modelo v1 completo lo sirve LiveKit Inference.

# uv add "livekit-agents[silero,deepgram,openai,cartesia]~=1.8"
# env: DEEPGRAM_API_KEY, OPENAI_API_KEY, CARTESIA_API_KEY
from livekit.agents import AgentSession, TurnHandlingOptions, inference
from livekit.plugins import cartesia, deepgram, openai, silero

session = AgentSession(
    vad=silero.VAD.load(),
    stt=deepgram.STT(model="nova-3", language="en"),
    llm=openai.responses.LLM(model="gpt-4.1"),
    tts=cartesia.TTS(model="sonic-3", voice="f786b574-daa5-4673-aa0c-cbe3e8534c02"),
    turn_handling=TurnHandlingOptions(
        turn_detection=inference.TurnDetector(version="v1-mini"),  # local CPU
    ),
)

Tres defaults que conviene conocer, todos de la documentación de LiveKit: con el audio turn detector la sesión cierra un turno tras 0,3 s mínimo y 2,5 s máximo, contra 0,5 s y 3,0 s sin él; la generación preventiva está activa y la de TTS desactivada; y el manejo adaptativo de interrupciones, que filtra backchannel con un modelo de audio, es una función de LiveKit Cloud.

Español y la lista de 14 idiomas

El audio turn detector soporta 14 idiomas, español incluido. Fijá el idioma del STT de forma explícita (por ejemplo inference.STT(language="es")) para que el detector aplique el umbral correcto por idioma; sin STT cae a umbrales de inglés.

6. Paso 3: Function Calling en Plena Llamada sin Silencios Muertos

Un tool call en un agente de voz es un silencio que el usuario escucha. LiveKit te da tres comportamientos, y elegir el correcto por tool es casi todo el trabajo: una consulta corta de solo lectura que cancelás si el usuario interrumpe, una escritura que nunca debe quedar a medias y un trabajo lento que mantiene al agente hablando.

Tres tools, tres políticas de interrupción

Patrones de los docs de function tools y async tools de LiveKit. Los helpers _lookup_order, _cancel_in_backend y _build_statement son stubs de tu propio backend, con timeouts explícitos.

import asyncio

from livekit.agents import Agent, RunContext, function_tool
from livekit.agents.llm import ToolError


class SupportAgent(Agent):
    def __init__(self) -> None:
        super().__init__(instructions="You are a phone support agent. Short sentences, no formatting.")

    # 1) Short read-only lookup: stop the work if the user talks over the agent
    @function_tool
    async def get_order_status(self, order_id: str, run_ctx: RunContext) -> str | None:
        """Look up the status of an order when the user asks about it.

        Args:
            order_id: The ID of the order to look up.
        """
        lookup = asyncio.ensure_future(self._lookup_order(order_id))  # your backend call
        await run_ctx.speech_handle.wait_if_not_interrupted([lookup])
        if run_ctx.speech_handle.interrupted:
            lookup.cancel()  # Python does not cancel it for you
            return None      # empty result, no reply
        return lookup.result()

    # 2) Write: a barge-in must not leave it half done
    @function_tool
    async def cancel_order(self, order_id: str, run_ctx: RunContext) -> str:
        """Cancel an order. Call only after the user confirmed out loud.

        Args:
            order_id: The ID of the order to cancel.
        """
        run_ctx.disallow_interruptions()
        ok = await self._cancel_in_backend(order_id)  # your backend call, with a timeout
        if not ok:
            raise ToolError("The order could not be cancelled. Offer to transfer the caller to a person.")
        return f"Order {order_id} is cancelled."

    # 3) Slow job: acknowledge now, fill the silence, answer when done
    @function_tool()
    async def export_statement(self, ctx: RunContext, month: str) -> str:
        """Generate a statement and email it. Takes up to a minute.

        Args:
            month: Month to export, YYYY-MM.
        """
        await ctx.update(f"Generating the statement for {month}. This can take a minute.")
        async with ctx.with_filler("Still working on it, hang on a sec.", delay=5):
            await self._build_statement(month)  # your slow backend call
        return f"The statement for {month} was sent to the email on file."

Qué le hace una interrupción a un tool

Por defecto un tool interrumpido sigue corriendo en segundo plano: los docs de LiveKit dicen que interrumpir al agente no cancela el trabajo. En Python lo cancelás vos, como hace el primer tool. Mantené cada agente en unos 5 a 10 tools (guía de LiveKit), devolvé strings listos para hablar en vez de JSON crudo, y recordá que max_tool_steps limita las llamadas consecutivas por turno a 3 por defecto.

7. Paso 4: Medí Cada Turno Antes de Afinar

LiveKit adjunta un reporte de métricas a cada mensaje del chat. Los campos mapean uno a uno con la tabla de presupuesto, así ves qué etapa se comió el turno.

Latencia por turno desde conversation_item_added

Nombres de campo de los docs de métricas de LiveKit. Mandalos a tus logs o trazas; acá solo los imprimo.

from livekit.agents import ConversationItemAddedEvent
from livekit.agents.llm import ChatMessage


@session.on("conversation_item_added")
def on_item(ev: ConversationItemAddedEvent):
    if not isinstance(ev.item, ChatMessage):
        return
    m = ev.item.metrics  # per-turn MetricsReport
    if ev.item.role == "user":
        print("stt:", m.get("transcription_delay"), "eot:", m.get("end_of_turn_delay"))
    elif ev.item.role == "assistant":
        print(
            "llm ttft:", m.get("llm_node_ttft"),
            "tts ttfb:", m.get("tts_node_ttfb"),
            "e2e:", m.get("e2e_latency"),
        )

Registrá esto por turno y mirá p50 y p95 por etapa en unas pocas decenas de llamadas reales desde tu región objetivo. llm_node_ttft y tts_node_ttfb solo se completan en modo pipeline. Las notas de la versión 1.8.7 sumaron la misma latencia de punta a punta para agentes realtime cuando hay VAD local configurado. Cambiá una perilla por vez: la propia página de tuning de LiveKit avisa que la generación preventiva no siempre reduce la latencia.

8. Llamadas Telefónicas: el Traspaso a Nivel Conceptual

El código del agente no cambia cuando quien llama está en un teléfono: un llamador SIP es un participante más en una sala. Lo que cambia es la puerta de entrada y qué hacés cuando el agente debe dejar de atender.

El camino de una llamada

  • 1. Un número o trunk recibe la llamada: LiveKit Phone Numbers (locales y toll-free de EE.UU., en LiveKit Cloud) o el trunk de tu proveedor SIP (LiveKit lista Twilio, Telnyx, Exotel, Plivo, Wavix, Sinch y didlogic como probados).
  • 2. Un inbound trunk la autentica y una dispatch rule elige la sala. Entra un participante SIP y tu servidor de agentes es despachado a esa sala.
  • 3. Usá el modelo de ruido para telefonía con los llamadores SIP y tareas DTMF para el teclado. Se aplican las mismas opciones de turnos.
  • 4. Transferencia fría: TransferSIPParticipant envía un SIP REFER y quien llama sale de tu sala. Un timeout de timbre (30 s por defecto) lo mantiene con el agente si nadie atiende.
  • 5. Transferencia cálida: WarmTransferTask (Python, beta) pone en espera al llamador, informa a la persona en una sala privada, los conecta y se retira. Un pipeline guarda texto en cada etapa, así que el resumen sale fácil.

El lado de Pipecat

Los docs de Pipecat listan Daily PSTN y SIP para el control de llamadas, y serializers WebSocket (Twilio, Telnyx, Plivo, Exotel) para flujos simples. Dicen que la vía WebSocket carece de funciones avanzadas como transferencias y reconexiones.

9. Modos de Falla: Eco, Salas Ruidosas, Tool Calls Largos

Las demos esconden estos tres. Las llamadas reales los encuentran en la primera semana.

Eco: el agente se interrumpe a sí mismo

Con altavoz el micrófono escucha al agente, el VAD se dispara y el agente se interrumpe solo. Mantené activo el echoCancellation de WebRTC del cliente (los docs de LiveKit dicen que corre solo en el cliente y puede quedar habilitado junto a la cancelación de ruido del agente), exigí min_words o min_duration para que los ruiditos cortos no cuenten, y dejá activa la reanudación tras falsa interrupción. Lo más cercano en Pipecat son sus estrategias de mute de usuario, que descartan el audio del usuario mientras el bot habla. No encontré una página de proveedor sobre eco específico de trunks SIP, así que probá con un teléfono en altavoz.

Salas ruidosas: el VAD oye voz, el STT oye cualquier cosa

Limpiá el audio antes del VAD. LiveKit ofrece aislamiento de voz para un hablante y supresión de ruido de fondo para varios, con modelos de Krisp y ai-coustics en LiveKit Cloud y una variante de telefonía para SIP. Subir el activation_threshold del VAD (0.5 por defecto) es la opción tosca y puede perder voces suaves; los docs de Pipecat dicen lo mismo de confidence y min_volume. Nunca apliques la misma limpieza en el frontend y en el agente.

Tool calls largos: silencio y acciones dobles

Un tool bloqueante es silencio muerto. Usá ctx.update y un filler para todo lo que pase de unos segundos, fijá timeouts HTTP explícitos y configurá on_duplicate para que un “¿hola?” impaciente no dispare una segunda reserva. En Pipecat los equivalentes son cancel_on_interruption=False y timeout_secs en @tool_options.

10. Errores Comunes (y los Arreglos Aburridos)

Casi todos los bugs de agentes de voz sobre los que leí caen en estos baldes. La columna izquierda es lo que aguanta; la derecha es lo que te despierta.

✅ Hacé esto

  • • Configurá los turnos con turn_handling=TurnHandlingOptions(...) y pineá livekit-agents~=1.8
  • • Arrancá con los defaults documentados y movés min_delay de a un paso
  • • Llamá a run_ctx.disallow_interruptions() al inicio de todo tool que escribe
  • • Devolvé strings listos para hablar y lanzá ToolError con un mensaje que el agente pueda decir
  • • Pedile al modelo frases cortas sin markdown ni emojis
  • • Registrá end_of_turn_delay, llm_node_ttft, tts_node_ttfb y e2e_latency por turno

❌ No esto

  • • Pasar min_endpointing_delay, allow_interruptions o turn_detection directo a AgentSession: deprecados en 1.8.7, remoción prevista para v2.0
  • • Shippear un tool de 20 segundos sin update ni filler y llamar “pensando” al silencio
  • • Activar cancelación de ruido en el frontend y en el agente a la vez
  • • Bajar el min_silence_duration de Silero a menos de 0,25 s con el audio turn detector (los docs dicen que lanza un ValueError al iniciar)
  • • Asumir que una interrupción cancela tu llamada al backend
  • • Copiar una cifra de latencia de un proveedor a tu SLA sin medirla

Regla de oro

Primero el presupuesto, después el ajuste. Poné tu p95 por etapa al lado de la tabla de diseño antes de tocar una perilla, y que una sola métrica justifique cada cambio.

Conclusión

Ya tenés el loop completo: un pipeline STT → LLM → TTS en streaming sobre LiveKit Agents, manejo de turnos sobre el que podés razonar, tools que se portan bien bajo interrupción, un camino telefónico en borrador y una métrica por etapa. Pipecat te daría la misma forma con otros nombres y otra historia de transporte, y la tabla comparativa está para que elijas sin guerra de benchmarks.

Esta semana: generá el quickstart, sumá un tool de solo lectura con manejo de interrupción, registrá las cuatro métricas por turno en 20 llamadas y completá tu propia tabla de presupuesto. Después decidí si el pipeline o el modelo speech-to-speech de mi post anterior gana con tu tráfico.

Machete del Tutorial

Turnos

  • • TurnHandlingOptions(...)
  • • inference.TurnDetector()
  • • interrupciones adaptive o vad

Tools

  • • @function_tool + RunContext
  • • disallow_interruptions() al escribir
  • • ctx.update + with_filler

Medición

  • • end_of_turn_delay
  • • llm_node_ttft + tts_node_ttfb
  • • e2e_latency p50 / p95

Fuentes

Diego Rodriguez

Diego Rodriguez

Ingeniero Senior Full-Stack & AI

Diego tiene mas de 9 anos de experiencia construyendo aplicaciones potenciadas por IA de produccion, desde orquestacion de LLMs y pipelines RAG hasta deteccion de riesgos con ML y sistemas de trading algoritmico.

Conoce mas sobre Diego →