Skip to main content

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

  1. Open your agent in the dashboard.
  2. Go to Brain → Reasoning Language Model (LLM).
  3. Switch to the Bring your Own LLM (BYOL) tab.
  4. Paste your WebSocket URL (e.g. wss://your-server.example.com/chat/stream).
  5. (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.
  6. Click Test Connection to verify reachability, then Save URL.
  7. Choose a Fallback LLM — either No fallback or Use platform LLM.
The URL is stored under 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 by session_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:
These frames are consumed by the Vaani agent and discarded — they are only used to confirm the connection is live. The content strings may be anything.

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:
The system prompt built from your agent configuration is always the first entry with "role": "system". Your server must apply it as the LLM’s instruction/system message for each turn. If you create an ADK LlmAgent or a LangChain chain, pass this text as the agent instruction or system message so the configured persona is honoured.
req_body.modify_agent.persona.metadata contains the per-call template variables (e.g. customer_name, account_type) passed by the caller. If your system prompt uses {{ variable }} placeholders, populate your LLM session state from this object so the placeholders resolve correctly.

Your server → Agent (streaming response)

Stream back one or more chunks, each as a JSON frame:
Send a final frame with "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 standard Authorization HTTP header during the WebSocket upgrade handshake:
Your server can validate this header before accepting the connection (e.g. using FastAPI’s websocket dependency or any standard WebSocket middleware).

How it works


X-Agent-Id header forwarding

If the X-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:
This lets your BYOL server identify which logical agent is initiating the connection without having to parse the request body. The value is also available inside every turn payload as 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.