Data Flow
Understanding how data flows through STELLA helps when debugging issues or extending the system. This document traces the path of a user's voice input from the browser to the AI response.
Voice Conversation Flowβ
ββββββββββββ βββββββββββ βββββββββββ βββββββββ βββββββ
β User ββββββΆβ Browser ββββββΆβ LiveKit ββββββΆβ Agent ββββββΆβ LLM β
β (Voice) β β (WebRTC)β β Server β β (STT) β β β
ββββββββββββ βββββββββββ βββββββββββ βββββββββ βββββββ
β β
β βΌ
ββββββββββββ βββββββββββ βββββββββββ βββββββββ βββββββ
β User βββββββ Browser βββββββ LiveKit βββββββ Agent βββββββ LLM β
β (Hears) β β (Audio) β β Server β β (TTS) β β β
ββββββββββββ βββββββββββ βββββββββββ βββββββββ βββββββ
Step-by-Step Breakdownβ
1. User Speaksβ
The user speaks into their microphone. The browser captures the audio using the Web Audio API.
// Browser captures audio
navigator.mediaDevices.getUserMedia({ audio: true })
.then(stream => {
// Stream is connected to LiveKit
room.localParticipant.publishTrack(stream.getAudioTracks()[0]);
});
2. WebRTC Transportβ
The browser encodes the audio using Opus codec and sends it via WebRTC to the LiveKit server.
Key characteristics:
- Low latency (50-150ms typical)
- Adaptive bitrate
- Encrypted transport (DTLS-SRTP)
3. LiveKit Routingβ
LiveKit receives the audio and routes it to all participants in the room, including the agent pod.
User Audio βββΆ LiveKit βββΆ Agent Pod
β
ββββΆ Other Participants (if any)
4. Speech-to-Text (STT)β
The agent receives the audio stream and processes it through the STT engine.
# Agent receives audio from LiveKit
async def on_audio_frame(self, frame: AudioFrame):
# Process through STT pipeline
text = await self.pipeline.transcribe(frame)
if text.is_final:
await self.on_transcript(text.text, is_final=True)
STT Options:
- Sherpa-ONNX: Local, low-latency, runs on CPU
- Whisper: Higher accuracy, can run locally or via API
- Cloud STT: Google, Azure, AWS speech services
5. LLM Processingβ
The transcribed text is sent to the LLM along with conversation history and system context.
async def generate_response(self, user_input: str) -> str:
messages = [
{"role": "system", "content": self.system_prompt},
*self.conversation_history,
{"role": "user", "content": user_input}
]
response = await self.openai.chat.completions.create(
model="gpt-4o",
messages=messages,
tools=self.get_tool_definitions()
)
return response.choices[0].message.content
6. Tool Execution (Optional)β
If the LLM wants to use a tool, the agent executes it and feeds results back to the LLM.
# LLM requests a tool call
tool_call = response.choices[0].message.tool_calls[0]
# Agent executes the tool
result = await self.execute_tool(
tool_call.function.name,
json.loads(tool_call.function.arguments)
)
# Feed result back to LLM for final response
7. Text-to-Speech (TTS)β
The LLM's response is converted to audio through the TTS engine.
async def speak(self, text: str):
# Stream TTS output
async for audio_chunk in self.pipeline.text_to_speech_stream(text):
# Publish to LiveKit room
await self.publish_audio(audio_chunk)
TTS Options:
- Kokoro: Local, fast, good quality
- Piper: Local, multiple voices
- ElevenLabs: Cloud, very natural voices
- OpenAI TTS: Cloud, simple integration
8. Audio Deliveryβ
The TTS audio is published to LiveKit and routed back to the user's browser.
Agent TTS Audio βββΆ LiveKit βββΆ User's Browser βββΆ Speakers
Data Channel Messagesβ
In addition to audio, STELLA uses LiveKit data channels for text-based communication.
Message Typesβ
// Transcript message (interim and final)
{
type: "transcript",
speaker: "user" | "assistant",
text: "Hello, how can I help?",
isFinal: true,
timestamp: 1699876543210
}
// Status update
{
type: "status",
status: "thinking" | "speaking" | "listening",
message: "Searching the database..."
}
// Progress update
{
type: "progress",
todos: [
{ id: "1", description: "Search database", status: "completed" },
{ id: "2", description: "Generate response", status: "in_progress" }
]
}
Flow Diagramβ
ββββββββββββ ββββββββββββ
β Frontend β β Agent β
ββββββ¬ββββββ ββββββ¬ββββββ
β β
βββββ User types message βββββββββΆβ
β β
βββββ status: "thinking" ββββββββββ
β β
βββββ transcript (interim) ββββββββ
β β
βββββ transcript (final) ββββββββββ
β β
βββββ status: "listening" βββββββββ
β β
Database Persistenceβ
All messages are persisted to PostgreSQL for history and analytics.
-- Messages table structure
CREATE TABLE messages (
id UUID PRIMARY KEY,
session_id UUID REFERENCES sessions(id),
speaker VARCHAR(20), -- 'user' or 'assistant'
content TEXT,
timestamp TIMESTAMP WITH TIME ZONE,
metadata JSONB
);
Write Pathβ
- Agent generates response
- Backend receives message via WebSocket
- Message saved to PostgreSQL
- Message broadcast to all connected clients
Read Pathβ
- Client requests session history
- Backend queries PostgreSQL
- Messages returned in chronological order
Latency Considerationsβ
| Stage | Typical Latency | Notes |
|---|---|---|
| Audio Capture | ~10ms | Browser processing |
| WebRTC Transport | 50-150ms | Network dependent |
| STT Processing | 100-500ms | Model and hardware dependent |
| LLM Generation | 500-3000ms | Model and prompt dependent |
| TTS Generation | 100-500ms | Streaming reduces perceived latency |
| Audio Playback | ~10ms | Browser processing |
Total typical latency: 800ms - 4000ms
Latency Optimizationβ
- Streaming TTS: Start speaking before full response is generated
- Local STT: Use Sherpa-ONNX instead of cloud APIs
- Edge deployment: Run agents closer to users
- Response caching: Cache common responses
Next Stepsβ
- Session Lifecycle - Session states
- Kubernetes Orchestration - Pod management