Custom LLM & API
Custom LLM & API
Beyond the built‑in providers in the catalog, Telenow can drive an agent with your own model in two ways:
- Custom LLM (
customllm) — point at any OpenAI‑compatible chat endpoint (self‑hosted Llama via Ollama/vLLM, a private gateway, etc.). Telenow speaks the standard OpenAI wire format; everything else (tools, RAG, transcripts) works normally. - Custom API (
customapi) — plug in your own agentic backend. Telenow sends the caller's turn to your HTTP endpoint and streams back whatever your service returns. Your service owns the "brain" (prompting, tools, RAG, memory); Telenow provides the real‑time voice (STT, TTS, telephony).
Both are selected by setting the agent's llmProvider and llmConfig when creating/updating an agent.
Custom LLM (OpenAI-compatible)
Use this when you have a model behind an OpenAI‑compatible /chat/completions API.
{
"name": "Self-hosted agent",
"llmProvider": "customllm",
"llmModel": "llama-3.1-8b-instruct",
"llmConfig": {
"baseUrl": "https://llm.yourcompany.com/v1",
"apiKey": "optional-key"
},
"sttProvider": "deepgram",
"ttsProvider": "elevenlabs",
"ttsVoice": "rachel",
"sessionConfig": {}
}
llmConfig field | Notes |
|---|---|
baseUrl | Required. Your OpenAI‑compatible base URL (Telenow calls {baseUrl}/chat/completions) |
apiKey | Optional bearer key for your endpoint |
llmModel is the model name your endpoint expects. Because it's the OpenAI wire format, tools and knowledge bases work the same as with hosted models.
Custom API (your agentic endpoint)
Use this when you already have an assistant/orchestration service and just want Telenow for voice. Telenow becomes a thin streaming relay to your endpoint.
{
"name": "Bring-your-own-brain",
"llmProvider": "customapi",
"llmModel": "customapi",
"llmConfig": { "apiEndpoint": "https://api.yourcompany.com/assistant" },
"sttProvider": "deepgram",
"ttsProvider": "elevenlabs",
"ttsVoice": "rachel",
"sessionConfig": {}
}
What Telenow sends
On each user turn, Telenow makes a streaming POST to your endpoint. The query string ?calling=true&stream=true is always appended to your apiEndpoint:
POST https://api.yourcompany.com/assistant?calling=true&stream=true
Authorization: Bearer <bearer> # only if a bearer was provided at session start
Content-Type: application/json
{ "query": "the caller's latest utterance", "userId": "…", /* …your payload fields, merged at top level… */ }
| Body field | Where it comes from |
|---|---|
query | The caller's latest utterance (whitespace‑collapsed). Your service keeps its own conversation state/memory — Telenow does not send the full history |
userId | The session's user id, when available |
(your payload keys) | Every key of the payload object you passed at session start is merged into the top level of this body — so put whatever context your backend needs (customer id, locale, the sessionId, …) there |
Provide bearer and payload at session start via init-web-call. The bearer becomes the Authorization: Bearer … header on every turn; payload becomes the merged body fields above — handy for passing the signed‑in customer's identity/context to your backend.
What your endpoint returns
A Server‑Sent Events (SSE) stream. Each event is a data: line whose value is a JSON object with a type. Telenow reads two things: token deltas it speaks in real time, and control events it reacts to.
data: {"type":"assistant_token","delta":"Hi there, "}
data: {"type":"assistant_token","delta":"how can I help?"}
data: {"type":"call_end","msg":"Thanks for calling — goodbye!"}
data: [DONE]
| Event | Telenow's behavior |
|---|---|
{"type":"assistant_token","delta":"…"} | Appends delta to what the agent speaks. Empty deltas are ignored. Send these as your model generates them for the lowest latency |
{"type":"call_end","msg":"…"} | Speaks the optional msg, then ends the call (see below) |
{"type":"<anything else>", …} | Passed through to orchestration as a control event — reserved for future hooks; unknown types are otherwise harmless |
data: [DONE] | End‑of‑turn sentinel (OpenAI‑style). Optional but recommended |
Notes that match the parser exactly:
- Lines must start with
data:(with the space). The JSON must be on a singledata:line. - A
data:line (or even a multibyte UTF‑8 character) may be split across TCP chunks — Telenow buffers and reassembles, so you don't have to align your writes to event boundaries. - A
typeofassistant_tokenwith a missing/emptydeltais dropped; an event with notypeis ignored.
Tools, RAG and memory live on your server
With Custom API you already own the agentic workflow — tool calls, RAG, memory, and the system prompt all run inside your endpoint. Telenow just attaches STT + TTS and speaks your streamed tokens. So you don't define tools in Telenow for a customapi agent (the Tools step is skipped — Telenow never sends a tool list and never runs its tool loop). Implement tools inside your endpoint and emit assistant_token deltas back.
Want Telenow to dispatch tools for you (HTTP tools + native transfer/end‑call)? Switch the agent's Brain to OpenAI, Groq, Anthropic, or Custom LLM — those run Telenow's tool loop.
customapidoes not.
What still works. Because STT and TTS run normally, you still get a full transcript, call recording, post‑call analysis (it's computed from the transcript by Telenow's analysis model, independent of your brain), latency metrics, and webhooks. Billing: Telenow does not meter LLM tokens for customapi — that's your own model's cost on your side; you're billed for STT, TTS, and telephony as usual (see Billing & usage).
Two things still act on the live Telenow call, so your server drives them explicitly:
Hang up the call
Emit a call_end event in your SSE stream:
data: { "type": "call_end", "msg": "Thanks for calling — goodbye!" }
Telenow speaks the optional msg, then ends the call.
Transfer to a human
When your workflow decides to escalate, trigger a warm transfer from your server with the REST endpoint — pass the E.164 number to dial:
curl -X POST https://api.telenow.ai/api/sessions/{sessionId}/transfer \
-H "x-api-key: vai_live_…" \
-H "Content-Type: application/json" \
-d '{ "to": "+15551234567" }'
Telenow bridges the live call to that number (respecting your Do‑Not‑Call list). Works on phone calls on every carrier (Plivo, Twilio, Vobiz, Exotel, Vonage, SIP trunks) and web (browser) calls — a web call dials the human on PSTN from the agent's bound number and bridges the browser audio to it (assign the agent a number on the Numbers page). Tata Tele Smartflo is the exception: a transfer on a live phone call is refused, because Tata offers no way to redirect or bridge a call already on a voice-streaming leg. A transfer from a web call whose agent is bound to a Smartflo number does work — there Telenow places a fresh outbound call to the human rather than redirecting an existing one.
Keep the
sessionIdhandy: you get it back frominit-web-call/initiate-callwhen the call starts. Pass it into your endpoint'spayloadso your server has it when it's time to transfer.
Set it up from the dashboard
You don't need the API to use either mode:
- Open Agents in the sidebar → open (or create) an agent → the Brain step in the agent builder.
- Pick Custom LLM or Custom API as the provider.
- Custom LLM: enter your Base URL (the part before
/chat/completions), an optional API key, and the Model name your endpoint expects. Custom API: enter your API endpoint URL. - Configure STT, TTS, and a voice as normal — those always run on Telenow.
- Save, then use the Test panel (or the Publish tab) to try it.
For Custom API, pass per‑session context (bearer, payload, identifier) when you start the call via init-web-call / initiate-call.
Which one?
Custom LLM (customllm) | Custom API (customapi) | |
|---|---|---|
| You provide | An OpenAI‑compatible model endpoint | A full agentic service |
| Wire format | OpenAI /chat/completions | Your query → SSE stream |
| Built‑in tools / RAG | ✅ Work normally | ❌ You implement them inside your endpoint |
| Transcript, recording, post‑call analysis | ✅ | ✅ (built from STT/TTS, not your brain) |
| LLM token billing | Billed (your model via Telenow) | Not metered by Telenow — your model, your cost |
| Best when | You just want a different/own model | You already have your own assistant |
Troubleshooting
| Symptom | Likely cause |
|---|---|
Custom LLM requires baseUrl in llmConfig | Set the Base URL (Custom LLM). It must be the root before /chat/completions (e.g. https://llm.yourco.com/v1) |
customapi requires extra.apiEndpoint | The agent's llmConfig.apiEndpoint is missing — set the API endpoint in the Brain step |
| Agent says nothing on a Custom API call | Your stream isn't emitting data: {"type":"assistant_token","delta":"…"} lines, or the data: prefix/space is missing. Check that you flush tokens as SSE |
| Call won't hang up on its own | Emit data: {"type":"call_end","msg":"…"} from your stream — there's no LLM‑callable end‑call tool in customapi mode |
| Transfer does nothing | Call POST /api/sessions/{sessionId}/transfer from your server with the sessionId and an E.164 to; for web calls the agent must have a phone number assigned (the human is dialed over PSTN) |
| Tools you defined in Telenow never fire | Expected — the Tools step is skipped for customapi. Run tools inside your endpoint, or switch the Brain to OpenAI/Groq/Anthropic/Custom LLM for Telenow's tool loop |