Voice AI
All recipes
PythonTypeScriptBeginner

Interruption Handling

Tune how the agent yields when the user barges in.

Recipe prompt

Paste into Cursor, Claude Code, v0, or your coding agent
Use with your coding agent
You are implementing the "Interruption Handling" recipe in this project.

Read the recipe markdown first:
https://raw.githubusercontent.com/AgoraIO-Conversational-AI/recipe-agent-interruptions/main/README.md

Use the source repository for cross-reference:
https://github.com/AgoraIO-Conversational-AI/recipe-agent-interruptions

Build this recipe into the user's app using the markdown as the implementation guide. Inspect related source files through the repository links when the recipe points to them. Ask before installing new dependencies.

Recipe

Rendered from the configured recipe markdown.

Raw

Agora Conversational AI — Interruptions Recipe (Python)

![License: MIT](./LICENSE) ![Python](https://www.python.org/) ![Bun](https://bun.sh/)

The interruptions recipe in the Agora Conversational AI recipes family. Control whether the agent can be interrupted mid-speech: choose from fully interruptible, completely uninterruptable, or keyword-triggered barge-in. The INTERRUPTION_MODE env var sets the Agora interruption config at agent-start time.

This repo ships a zero-key mock LLM endpoint mounted at /llm in the same backend process so you can run the full pipeline immediately, then replace the mock with your own model. STT (Deepgram) and TTS (MiniMax) stay Agora-managed.

Prerequisites

Run It

# 1. Install + create the server Python venv
bun run setup

# 2. Add Agora credentials (CLI), or edit server/.env.local by hand
agora login
agora project use <your-project>          # select which project to use (you may have several)
agora project env write server/.env.local # writes App ID/Certificate; keeps your CUSTOM_LLM_* lines

# 3. Expose the backend publicly (Agora cloud calls /llm/chat/completions)
ngrok http 8000

# 4. Add the tunnel URL to server/.env.local (use whatever domain ngrok prints —
#    today that is usually *.ngrok-free.dev)
#    CUSTOM_LLM_URL=https://<your-tunnel>.ngrok-free.dev/llm/chat/completions

# 5. (Optional) Set the interruption mode
#    echo 'INTERRUPTION_MODE=keywords' >> server/.env.local

# 6. Run the backend and web
bun run dev

Open http://localhost:3000Start Conversation → speak while the agent is talking to test barge-in.

Working from a clone

After cloning, run bun run setup to create the Python venv, then follow the steps above to add credentials and a tunnel URL.

Services:

ServiceURL
Frontendhttp://localhost:3000
Backendhttp://localhost:8000 (also serves /llm)
API docshttp://localhost:8000/docs

Deploy

Deploy web (Next.js) and server (a single publicly reachable FastAPI backend). The mock LLM endpoint is mounted at /llm in the same process, so Agora cloud reaches it at <public-url>/llm/chat/completions. Set AGENT_BACKEND_URL in the web deployment so the Next rewrites reach the backend.

A single-process Docker image is published to ghcr.io/AgoraIO-Conversational-AI/recipe-agent-interruptions on v* tags. It bundles the agent backend and the mock LLM endpoint in one process on port 8000. Point CUSTOM_LLM_URL at <public-url>/llm/chat/completions.

Co-public caveat: the server :8000 is now the public endpoint Agora calls (/llm), so the token endpoints are co-public; the App Certificate is only used in-memory to mint tokens (never on the wire); add auth/rate-limiting before a real deployment.

Environment Variables

Backend env file: `server/.env.example`.

VariableRequiredDefaultNotes
AGORA_APP_IDAgora Console → Project → App ID
AGORA_APP_CERTIFICATEAgora Console → Project → App Certificate (server only)
CUSTOM_LLM_URLPublic chat-completions URL of your mounted /llm endpoint (<tunnel>/llm/chat/completions). Agora cloud calls it; cannot be localhost.
CUSTOM_LLM_API_KEYany-key-hereForwarded by Agora cloud as Authorization: Bearer. Required by the CustomLLM vendor.
CUSTOM_LLM_MODELinterruptions-mockModel name passed to your endpoint
INTERRUPTION_MODEinterruptibleinterruptible \uninterruptable \keywords. Lives in server/.env.local.
AGENT_GREETINGbuilt-inOptional opening line override
PORT8000Agent backend port
AGENT_BACKEND_URL (web deploy)Required in a deployed web app when proxying to the backend

INTERRUPTION_MODE values

ValueAgora interruption configBehavior
interruptible (default){"enable": true}Agent stops speaking when the user speaks
uninterruptable{"enable": false, "disabled_config": {"strategy": "append"}}Agent finishes its turn; user speech is appended for next turn
keywords{"enable": true, "mode": "keywords", "keywords_config": {"keywords": [...]}}Agent stops only when a trigger word ("stop", "wait", "hold on") is detected

The mode mapping lives in server/src/interruption_config.py.

Commands

bun run setup            # install web deps + create server/ venv
bun run dev              # run backend (:8000, serves /llm) + web (:3000)

bun run doctor           # prerequisite check (no creds needed)
bun run doctor:local     # + .env.local + credentials + CUSTOM_LLM_URL checks

bun run verify           # web-only gate (no Agora creds needed)
bun run verify:local     # full local gate: backend compile + smoke tests + web build
bun run clean            # remove venvs and build artifacts

Tests run standalone (no Agora cloud needed): pytest in server/, plus bun run verify in web/. CI runs them on Linux/macOS/Windows × Python 3.10 & 3.13.

Architecture

Browser (localhost:3000)
  │  fetch /api/*
  ▼
Next.js  ──rewrite──▶  Agent backend  (server/, localhost:8000)
                          │  starts agent session (CustomLLM vendor + interruption config)
                          ▼
                       Agora ConvoAI Cloud
                          │  POST <CUSTOM_LLM_URL>   (Authorization: Bearer)
                          ▼
                       Mock LLM endpoint  (mounted at /llm in server/, localhost:8000)
                          ▲  public via ngrok tunnel

The browser only ever calls Next /api/*, which rewrites to the agent backend. The agent backend owns Agora tokens and agent lifecycle. The mock LLM endpoint is mounted at /llm in the same backend; because Agora cloud — not the browser — calls it, the backend must be publicly reachable (ngrok http 8000). See ARCHITECTURE.md.

What You Get

  • Next.js web client (React 19 / TypeScript) for RTC/RTM and agent lifecycle
  • FastAPI agent backend with token generation and agent session management
  • The /api/get_config, /api/startAgent, and /api/stopAgent contract, rewritten by Next to the backend
  • INTERRUPTION_MODE → Agora interruption config: interruptible (default), uninterruptable, or keywords (trigger words: "stop", "wait", "hold on")
  • The mock LLM endpoint returns a long monologue so you have time to practice interrupting
  • Zero-key mock: no external API key needed to run the demo

How It Works

  1. Browser loads; Next.js fetches /api/get_config → backend generates a Token007 RTC token and returns channel/UID config.
  2. User clicks Start Conversation; Next.js posts /api/startAgent → backend calls Agora ConvoAI to start an agent session using CustomLLM (pointing at CUSTOM_LLM_URL) and applies the interruption config from INTERRUPTION_MODE.
  3. Agora ConvoAI Cloud joins the RTC channel, receives audio, and runs STT (Deepgram nova-3).
  4. For each LLM turn, Agora POSTs the conversation to CUSTOM_LLM_URL/chat/completions; the mock endpoint streams back a long monologue as an OpenAI SSE response.
  5. Agora runs TTS (MiniMax speech26_turbo) and sends audio back to the browser. The interruption config governs whether the user's speech interrupts the agent mid-sentence.
  6. User clicks Stop; Next.js posts /api/stopAgent → backend ends the agent session.

Replacing the mock

The mock LLM always returns a long monologue so you can test interruption behavior. To use a real model, replace the body of get_long_reply() in `server/src/llm.py` with your own logic. The endpoint must keep speaking the OpenAI streaming /chat/completions contract. A production endpoint should also validate the Authorization: Bearer header.

Repo Map

PathDescription
web/Next.js frontend (:3000)
server/FastAPI agent backend (:8000) — tokens, agent lifecycle, interruption config, and the /llm endpoint at the same port
server/src/llm.pyOpenAI-compatible mock /chat/completions handler; no Agora deps
ARCHITECTURE.mdDetailed request flow, interruption config table, API reference
AGENTS.mdGuide for coding agents working in this repo

Troubleshooting

ProblemFix
Agent starts but never speaksCUSTOM_LLM_URL is not public or omits /llm/chat/completions. Use your ngrok URL.
doctor:local warns about localhostReplace the local URL with your public tunnel URL.
Local calls fail / hang under a global proxy (Clash, etc.)Your proxy is routing loopback through itself. Configure it to send 127.0.0.1, localhost, and RFC-1918 ranges DIRECT.
Missing server/venv during verifyRun bun run setup.
Interruption mode has no effectConfirm INTERRUPTION_MODE is set in server/.env.local.

More Docs

License

Released under the MIT License.