Bring your own brain
2wai doesn’t require you to use any particular language model, or a language model at all. The API’s only job on the “what does the avatar say” side is: given text, speak it. Everything upstream of that (understanding the user, deciding on a reply, retrieving context, calling tools) is entirely yours.
The pattern
Section titled “The pattern”async function reply(sessionId, userMessage) { // 1. Decide what to say. Use whatever you want here: this example uses // the OpenAI SDK, but any LLM, retrieval pipeline, or static logic works. const completion = await openai.chat.completions.create({ model: 'gpt-4o-mini', messages: [{ role: 'user', content: userMessage }], }); const text = completion.choices[0].message.content;
// 2. Speak it. await fetch(`https://api.2wai.ai/v1/sessions/${sessionId}/speak`, { method: 'POST', headers: { Authorization: `Bearer ${process.env.TWAI_SECRET_KEY}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ text }), });}That’s the entire integration surface. Swap step 1 for whatever you already run in production (a RAG pipeline, a fine-tuned model, a support macro system) without changing anything about how you talk to 2wai.
Streaming for lower latency
Section titled “Streaming for lower latency”If your LLM call supports streaming tokens, don’t wait for the full reply before calling /speak. Flush at sentence boundaries instead, so the avatar starts talking while the rest of the reply is still being generated:
async function replyStreaming(sessionId, userMessage) { const stream = await openai.chat.completions.create({ model: 'gpt-4o-mini', messages: [{ role: 'user', content: userMessage }], stream: true, });
let buffer = ''; for await (const chunk of stream) { buffer += chunk.choices[0]?.delta?.content ?? '';
// Flush on sentence-ending punctuation so /speak calls stay small and // the avatar starts responding sooner. const match = buffer.match(/^(.*?[.!?])\s+/); if (match) { const sentence = match[1]; buffer = buffer.slice(match[0].length); await speak(sessionId, sentence); } }
if (buffer.trim()) { await speak(sessionId, buffer.trim()); }}
async function speak(sessionId, text) { await fetch(`https://api.2wai.ai/v1/sessions/${sessionId}/speak`, { method: 'POST', headers: { Authorization: `Bearer ${process.env.TWAI_SECRET_KEY}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ text }), });}Handling interruptions
Section titled “Handling interruptions”If your product lets users interrupt the avatar mid-reply (barge-in), call POST /v1/sessions/{id}/interrupt as soon as you detect new user input, before queuing the new reply:
async function interrupt(sessionId) { await fetch(`https://api.2wai.ai/v1/sessions/${sessionId}/interrupt`, { method: 'POST', headers: { Authorization: `Bearer ${process.env.TWAI_SECRET_KEY}` }, });}- Bring your own TTS if you also want to replace 2wai’s speech synthesis.
- Latency for tuning the end-to-end response time.