Overview
BYOL lets you replace the default platform LLM with any inference engine you control — an on-premise model, a fine-tuned model, a Google ADK agent, a LangGraph workflow, or anything else that can speak the Vaani WebSocket protocol. When BYOL is enabled for an agent, every response turn is routed to your WebSocket server instead of the built-in provider (OpenAI, Google, Groq, etc.). The rest of the pipeline — STT, TTS, telephony, transcripts, Langfuse tracing — stays exactly the same.How to enable BYOL
- Open your agent in the dashboard.
- Go to Brain → Reasoning Language Model (LLM).
- Switch to the Bring your Own LLM (BYOL) tab.
- Paste your WebSocket URL (e.g.
wss://your-server.example.com/chat/stream). - (Optional) Enter an Auth Token in the Auth Token field. When set, Vaani sends
Authorization: Bearer <token>as an HTTP header during the WebSocket upgrade handshake. The token is stored Fernet-encrypted at rest and only decrypted at call time — the UI always displays a masked value (****<last4>). Leave blank for unauthenticated connections. - Click Test Connection to verify reachability, then Save URL.
- Choose a Fallback LLM — either No fallback or Use platform LLM.
agent_config.persona.senses_capabilities.brain.llm.extra_params.llm_websocket_url
and takes effect immediately on the next call.
WebSocket protocol
Your server must implement the following JSON message exchange over a persistent WebSocket connection. Vaani opens one connection per call (identified bysession_id / room name) and
sends one request per agent turn.
Connection handshake
Immediately after the WebSocket is accepted, your server must send two JSON frames in order:Agent → Your server (request)
For every agent turn (after the user finishes speaking) Vaani sends a JSON frame over the WebSocket. Here is a real example captured from a live call:Your server → Agent (streaming response)
Stream back one or more chunks, each as a JSON frame:"content_complete": true and optionally empty content to signal end of
turn:
You may optionally add
"end_call": true on the final frame to signal that the agent should
hang up the call after speaking the response.
Keep-alive (ping/pong)
If your server sends a keep-alive frame Vaani will echo it back immediately:WebSocket connection authentication
Vaani supports an optional Bearer token for authenticating the WebSocket connection to your BYOL server. When configured, the token is sent as the standardAuthorization HTTP header during the WebSocket upgrade handshake:
websocket dependency or any standard WebSocket middleware).
How it works
X-Agent-Id header forwarding
If theX-Agent-Id HTTP header is present on the /trigger-call/ request, Vaani forwards its value as an X-Agent-Id header on the WebSocket upgrade handshake:
req_body.x_agent_id.
Example server-side usage (FastAPI)
Fallback behaviour
You can configure what happens when your server is unreachable or returns an error:
The fallback mode is stored as
extra_params.fallback in the agent config ("none" or
"platform").
Session management
Each call opens a new connection at<ws_url>/<call_id> (the call_id / room name is appended
automatically). Your server should use the session_id or the path component to isolate
per-call state (e.g. conversation memory, tool state).
When the call ends Vaani closes the WebSocket cleanly. You can also expose a
DELETE /session/{session_id} endpoint on your server so Vaani can explicitly clean up state
(see the example ADK server).
Reference implementation
The vaani-adk-byol-example repository contains a complete FastAPI server (adk_server.py) that:
- Implements the full WebSocket protocol above
- Runs a Google ADK agent (
LlmAgent) with the system prompt injected per session - Exposes REST (
/chat), SSE (/chat/sse), and WebSocket (/chat/stream/{session_id}) endpoints - Can be deployed locally and exposed with ngrok in under 5 minutes.

