ThunderPhone 2.0 is live.Self-serve, from 2¢/min.Read the announcement

Connect tools & data

Use ThunderPhone from LiveKit Agents

Run a ThunderPhone voice agent as the realtime (speech-to-speech) model of a LiveKit Agents session, with LiveKit rooms, SIP and telephony around it.

LiveKit Agents is an open-source framework for realtime voice agents on top of LiveKit rooms. ThunderPhone plugs in as the realtime model: the framework sends the participant's audio, ThunderPhone returns the agent's voice, transcripts and function calls, and LiveKit's room, SIP trunks and telephony carry the audio. Speech recognition, the language model, the voice, turn-taking, 47 languages and tools all run on ThunderPhone.

Calls made this way appear in call history and are billed at your product's per-minute rate like any other realtime call. There is no subscription and no ThunderPhone phone number involved.

Install

pip install livekit-plugins-thunderphone
export THUNDERPHONE_API_KEY=sk_live_...   # a secret API key

The plugin wraps LiveKit's OpenAI Realtime model, because ThunderPhone's Realtime WebSocket speaks the same protocol. It needs livekit-agents 1.8 or newer.

Run a saved agent

Everything the agent does (prompt, voice, engine, languages, tools, greeting, silence check-ins) is configured on ThunderPhone. The session only moves audio.

from livekit.agents import Agent, AgentSession, JobContext, WorkerOptions, cli
from livekit.plugins import thunderphone
 
 
async def entrypoint(ctx: JobContext):
    session = AgentSession(llm=thunderphone.RealtimeModel(agent_id=12))
    await session.start(agent=Agent(instructions=""), room=ctx.room)
 
 
if __name__ == "__main__":
    cli.run_app(WorkerOptions(entrypoint_fnc=entrypoint))

A saved agent greets and runs its own tools on ThunderPhone; the LiveKit Agent's instructions and tools are not sent. The call ends when the ThunderPhone agent hangs up, and the session closes with it.

Configure the session inline

Without agent_id, instructions and tools come from the LiveKit Agent, the same way they do for OpenAI. product picks the engine and voice picks the ThunderPhone voice.

from livekit.agents import Agent, AgentSession, RunContext, function_tool
from livekit.plugins import thunderphone
 
 
class Receptionist(Agent):
    def __init__(self):
        super().__init__(instructions="You are Acme Dental's receptionist. Be brief.")
 
    @function_tool
    async def check_availability(self, context: RunContext, date: str) -> dict:
        """Free appointment slots on a date."""
        return {"slots": await calendar.free_slots(date)}
 
 
async def entrypoint(ctx: JobContext):
    session = AgentSession(
        llm=thunderphone.RealtimeModel(product="bolt", voice="olivia", language="es"),
    )
    await session.start(agent=Receptionist(), room=ctx.room)
    await session.generate_reply()   # the agent speaks first

ThunderPhone freezes instructions and tools when the call starts, so update_instructions, update_tools and agent hand-offs that change them are not supported mid-call. Inline sessions are client-steered: the agent speaks when you call generate_reply() or when the participant finishes a turn.

Options

ArgumentMeaning
api_keySecret key (sk_live_...). Defaults to THUNDERPHONE_API_KEY.
agent_idRun a saved agent. Mutually exclusive with product and voice.
productEngine for inline sessions: spark, bolt or storm.
voiceThunderPhone voice name for inline sessions.
languagePrimary language hint for inline sessions, e.g. es.
from_number, to_numberNumbers to record on the call when the room fronts a phone line.
live_transcriptsStream caller transcript fragments mid-utterance (billed extra).
base_urlEndpoint override, default wss://api.thunderphone.com/v1/realtime.

Events

ThunderPhone adds platform events on top of the realtime protocol. The session emits them:

@session.llm.session.on("thunderphone_call_ended")  # or on the RealtimeSession you hold
def on_call_ended(event):
    print("call ended:", event["reason"])

The RealtimeSession exposes call_id once the session is live. Use it with GET /v1/calls/{call_id} to fetch the recording, transcript and grade after the call. Every other call.* event (transfer, keypad, speech ignored) arrives as thunderphone_call_event.

Limits

  • Turn detection is server-side and always on; framework-side turn detection (turn_detection on the AgentSession) is ignored.
  • Instructions and tools cannot change once the call starts.
  • Audio is 16-bit mono PCM at 24 kHz in both directions.
  • Video frames are ignored.