Docs

LiveKit voice agent routing

Point a LiveKit AgentSession at Speko: one base URL and one key across all three slots, in Python and TypeScript.

Slots

SlotClassRouteRoutable
sttopenai.STT/v1/audio/transcriptions15 of 16
llmopenai.LLM/v1/chat/completions16 of 17
ttsopenai.TTS/v1/audio/speech15 of 17

Setup

mkdir speko-livekit && cd speko-livekit
uv init --bare --python 3.11
uv add "livekit-agents[openai]~=1.6" "python-dotenv>=1.0,<2"
export SPEKO_API_KEY=sk_live_...

Catalog

curl -s https://api.speko.ai/v1/models

Response

{"data": [{"id": "deepgram:nova-3", "object": "model", "provider": "Deepgram",
           "model": "Nova-3", "api": "stt", "routable": true}]}

Agent

"""Minimal LiveKit Agents voice worker using Speko's OpenAI-compatible API."""

from __future__ import annotations

import os

from dotenv import load_dotenv
from livekit import agents
from livekit.agents import Agent, AgentServer, AgentSession, JobContext
from livekit.plugins import openai
from openai import AsyncOpenAI


load_dotenv()

SPEKO_API_KEY = os.getenv("SPEKO_API_KEY")
SPEKO_BASE_URL = "https://api.speko.ai/v1"
SPEKO_OBJECTIVE = os.getenv("SPEKO_OBJECTIVE", "balanced")
SPEKO_LANGUAGE = os.getenv("SPEKO_LANGUAGE", "en")

if not SPEKO_API_KEY:
    raise RuntimeError("SPEKO_API_KEY is required")


def speko_client() -> AsyncOpenAI:
    """Build one OpenAI-compatible client shared by all three stages."""

    return AsyncOpenAI(
        api_key=SPEKO_API_KEY,
        base_url=SPEKO_BASE_URL,
        default_headers={
            "X-Speko-Objective": SPEKO_OBJECTIVE,
            "X-Speko-Language": SPEKO_LANGUAGE,
        },
    )


server = AgentServer()


@server.rtc_session()
async def entrypoint(ctx: JobContext) -> None:
    client = speko_client()
    session = AgentSession(
        # Segmented STT is used here so the example works with Speko's
        # transcription route instead of opening a speech-to-speech socket.
        stt=openai.STT(
            client=client,
            model="auto",
            use_realtime=False,
            language=SPEKO_LANGUAGE,
        ),
        llm=openai.LLM(
            client=client,
            model="auto",
        ),
        # The Python LiveKit OpenAI TTS transport expects the raw PCM route.
        # "tts-1" is routed, not pinned: the router still picks the provider.
        # Pin one with a catalog id instead, e.g. model="cartesia:sonic-3.5".
        tts=openai.TTS(
            client=client,
            model="tts-1",
            voice="alloy",
            response_format="pcm",
        ),
    )

    await session.start(
        Agent(
            instructions=(
                "You are a helpful voice assistant. "
                "Keep replies concise and conversational."
            )
        ),
        room=ctx.room,
    )
    await ctx.connect()
    await session.generate_reply(
        instructions="Greet the caller in one short sentence and offer help."
    )


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

Run

LanguageCommandNeeds
Pythonuv run python agent.py consoleSave the Python example as agent.py. Talks in the terminal; no LiveKit credentials.
TypeScriptbunx tsx agent.ts devNeeds LIVEKIT_URL, LIVEKIT_API_KEY, LIVEKIT_API_SECRET.

Override per request

import os

from livekit.plugins import openai
from openai import AsyncOpenAI

client = AsyncOpenAI(
    api_key=os.environ["SPEKO_API_KEY"],
    base_url="https://api.speko.ai/v1",
    default_headers={"X-Speko-Objective": "latency", "X-Speko-Language": "es"},
)

stt = openai.STT(client=client, model="auto", language="es")
llm = openai.LLM(client=client, model="auto")
tts = openai.TTS(client=client, model="tts-1", voice="alloy", response_format="pcm")

Limits

model="auto" on openai.TTS, Python onlyThe plugin parses SSE and hears silence. Use model="tts-1"; the router still picks the provider.
detect_language=TrueSends an empty language field, which the router rejects with invalid_routing_header.
vad=NoneRe-arms the RuntimeError a segmented speech-to-text raises on the first user turn. Omit the argument.
use_realtime / useRealtimeOpens the speech-to-speech socket instead of the transcription route this page configures.

Reference

QuickstartRouting headers, response headers, errors
PipecatThe same wiring in a Pipecat pipeline
ModelsThe measured rows the ranking reads
LiveKit AgentsFramework documentation
Node API referenceClass signatures