Addis AI

Voice Interface (VUI)

Orchestrate STT, LLM, and TTS to build full voice conversational agents.

Building a Voice User Interface (VUI) requires chaining three distinct Addis AI capabilities into a single, cohesive loop. This guide demonstrates how to build a server-side orchestrator that takes user audio and returns an AI voice response.

The Voice Pipeline

A typical voice interaction follows a request-response cycle known as the "Voice Loop".

Live request pathPhase 01 / 09
User speaks
Audio
Transcribe
Text
Reasoning
Text
Synthesize
Audio
Play
Listening for user input...
  1. Transcribe (STT): Convert user audio (Amharic/Oromo) into text.
  2. Reason (LLM): Send that text to the Chat API to get an intelligent response.
  3. Speak (TTS): Convert the AI's text response back into audio.

Server-Side Orchestrator

To minimize latency and manage secrets, this pipeline should run on your server. The client sends one audio file, and the server returns the audio response (and text transcript).

This Express.js route handles the entire pipeline in one request.

import AddisAI from "addisai";
import express from "express";
import multer from "multer";

const app = express();
const upload = multer(); // Memory storage
const addis = new AddisAI();

app.post("/api/voice-chat", upload.single("audio"), async (req, res) => {
  try {
    if (!req.file) return res.status(400).json({ error: "Audio is required" });

    // 1. Transcribe
    const audio = new File(
      [req.file.buffer],
      req.file.originalname || "input.wav",
      { type: req.file.mimetype },
    );
    const transcript = await addis.speech.transcribe({ audio, language: "am" });

    // 2. Reason
    const completion = await addis.chat.completions.create({
      system: "Answer naturally and concisely in Amharic.",
      messages: [{ role: "user", content: transcript.text }],
    });
    const answer = completion.choices[0].message.content;

    // 3. Speak with Addis Voices 2
    const clip = await addis.voice.generate({
      text: answer,
      voiceId: "am-hamen",
      language: "am",
      outputFormat: "mp3_44100",
      clientRequestId: crypto.randomUUID(),
    });

    res.json({
      user_transcript: transcript.text,
      ai_text: answer,
      audio_url: clip.audioUrl,
    });
  } catch (error) {
    console.error(error);
    res.status(500).json({ error: "Voice pipeline failed" });
  }
});
from uuid import uuid4
from addisai import AddisAI

addis = AddisAI()

def run_voice_pipeline(audio_file_path: str):
    # 1. Transcribe
    with open(audio_file_path, "rb") as audio:
        transcript = addis.speech.transcribe(audio=audio, language="am")

    # 2. Reason
    completion = addis.chat.completions.create(
        system="Answer naturally and concisely in Amharic.",
        messages=[{"role": "user", "content": transcript["text"]}],
    )
    answer = completion["choices"][0]["message"]["content"]

    # 3. Speak with Addis Voices 2
    clip = addis.voice.generate(
        text=answer,
        voice_id="am-hamen",
        language="am",
        output_format="mp3_44100",
        client_request_id=str(uuid4()),
    )

    return {
        "user_transcript": transcript["text"],
        "ai_text": answer,
        "audio_url": clip.audio_url,
    }

Frontend Implementation

On the client side (React, Flutter, etc.), your job is to record audio, send it to your new orchestrator endpoint, and play the result.

Record Audio

Use a library like MediaRecorder (Web) or flutter_sound (Mobile) to capture user input.

  • Format: WAV or MP3.
  • Sample Rate: 16kHz is sufficient for speech.

Send to Server

Upload the blob to your /api/voice-chat endpoint.

const formData = new FormData();
formData.append('audio', audioBlob, 'input.wav');

const res = await fetch('/api/voice-chat', { method: 'POST', body: formData });
const data = await res.json();

Play Response

Play the completed Addis Voices 2 clip URL returned by your backend.

const audio = new Audio(data.audio_url);
audio.play();

Latency & Optimization

The "Request-Response" model adds up latency (STT time + LLM time + TTS time). To build a truly conversational experience, you should consider these optimizations.

Parallel Execution

Segment deliberately. For long responses, split complete sentences into separate Addis Voices 2 clips and queue them in order. Addis Voices 2 returns completed clips; it does not stream one generation.

Realtime API

The Ultimate Solution. If latency is critical (e.g., live customer support), switch to our Realtime API. It handles STT, Logic, and TTS on the server over a single WebSocket connection with sub-300ms latency.

VAD (Voice Activity)

Implement Voice Activity Detection on the client. Only stop recording when the user has been silent for 500ms-1000ms. Sending silence to the API wastes time and money.

Context

Store the relevant user_text and ai_text turns in your session, then send them back as messages on the next chat request. Trim or summarize older turns instead of growing the history indefinitely.

On this page