feat: integrate OCI agent streaming and web voice client

This commit is contained in:
2026-10-13 19:49:27 +00:00
parent 088473a0c8
commit 19a415daae
19 changed files with 2034 additions and 23 deletions

View File

@@ -34,8 +34,13 @@ Architecture:
import json
import logging
from pathlib import Path
from fastapi.responses import FileResponse
from fastapi.staticfiles import StaticFiles
from fastapi import FastAPI, WebSocket, WebSocketDisconnect
from pydantic import BaseModel, ValidationError
from contextlib import aclosing
import asyncio
@@ -83,8 +88,16 @@ from voice_assistant.audio_gateway.app.protocol import (
STTPartial,
TurnEnded,
TurnStarted,
AgentDelta,
)
from voice_assistant.voice_pipeline import (
PipelineTextDelta,
PipelineAgentResponse,
PipelineAudioChunk,
)
from voice_assistant.audio_gateway.app.session import SessionState
# logger configuration
@@ -100,6 +113,18 @@ provider_catalog = load_provider_catalog()
MAX_AUDIO_CHUNK_BYTES = 64 * 1024 # 64 KB
WEB_DIR = Path(__file__).resolve().parents[1] / "web"
app.mount(
"/web",
StaticFiles(directory=WEB_DIR),
name="web",
)
@app.get("/ui", include_in_schema=False)
async def web_client() -> FileResponse:
return FileResponse(WEB_DIR / "index.html")
def create_turn_manager() -> TurnManager:
@@ -176,27 +201,63 @@ async def forward_stt_events(
),
)
result = await pipeline.process_transcript(
session_id=session_id,
turn_id=turn_id,
text=event.text,
)
if callable(getattr(pipeline.agent, "stream", None)):
async with aclosing(
pipeline.stream_turn_response(
session_id=session_id,
turn_id=turn_id,
text=event.text,
)
) as response_events:
async for response_event in response_events:
if not pipeline.is_active_turn(turn_id):
break
await send_json(
websocket,
AgentResponse(
if isinstance(response_event, PipelineTextDelta):
await send_json(
websocket,
AgentDelta(
session_id=session_id,
turn_id=turn_id,
text=response_event.text,
),
)
elif isinstance(response_event, PipelineAgentResponse):
await send_json(
websocket,
AgentResponse(
session_id=session_id,
turn_id=turn_id,
text=response_event.text,
),
)
elif isinstance(response_event, PipelineAudioChunk):
await websocket.send_bytes(response_event.audio)
else:
result = await pipeline.process_transcript(
session_id=session_id,
turn_id=turn_id,
text=result.response_text,
),
)
text=event.text,
)
async for audio_chunk in pipeline.synthesize_response(
result.response_text,
):
await websocket.send_bytes(audio_chunk)
await send_json(
websocket,
AgentResponse(
session_id=session_id,
turn_id=turn_id,
text=result.response_text,
),
)
pipeline.on_tts_complete(turn_id)
async for audio_chunk in pipeline.synthesize_response(
result.response_text,
):
await websocket.send_bytes(audio_chunk)
pipeline.on_tts_complete(turn_id)