Files
ai-video-fullstack/backend/routes/voice_ws.py
Xin Wang deaf3d7730 Add dynamic variable support to Assistant model and related components
- Introduce dynamic variable definitions in AssistantConfig and Assistant models, allowing for flexible prompt customization.
- Implement validation for dynamic variable names and types in the schema.
- Update backend services and routes to handle dynamic variables in assistant configurations and runtime processing.
- Enhance frontend components to support dynamic variable definitions, including a new editor for managing variables.
- Add tests to ensure proper functionality and validation of dynamic variables in various scenarios.
2026-07-12 23:42:56 +08:00

70 lines
2.6 KiB
Python

"""WS 输出:裸 WebSocket 音频流(第二种输出方式)。
比 WebRTC 简单——没有 SDP/ICE/STUN/TURN,一条 WS 直接收发音频帧。
适合:服务端对接、话务网关、自定义客户端、调试。
约定:连接建立后,**第一条文本消息**发 JSON 启动参数:
{"assistant_id": "asst_xxx"} # 推荐:key 服务端解析
{"inline_config": {...AssistantConfig}} # 调试:内联
之后的二进制消息即音频帧(protobuf,与 transports.py serializer 对应)。
"""
import json
from db.session import SessionLocal
from fastapi import APIRouter, WebSocket
from loguru import logger
from models import AssistantConfig
from services.auth import require_admin_websocket
from services.config_resolver import resolve_runtime_config
from services.runtime_variables import prepare_dynamic_config
from starlette.websockets import WebSocketDisconnect
# pipecat 重依赖,惰性导入(见 voice_webrtc.py 说明)
router = APIRouter()
async def _resolve_start_config(raw: str) -> tuple[AssistantConfig, str | None]:
data = json.loads(raw)
if data.get("assistant_id"):
async with SessionLocal() as session:
cfg = await resolve_runtime_config(session, data["assistant_id"])
return (
prepare_dynamic_config(
cfg,
data.get("dynamic_variables"),
assistant_id=data["assistant_id"],
),
data["assistant_id"],
)
if data.get("inline_config"):
return (
prepare_dynamic_config(
AssistantConfig(**data["inline_config"]),
data.get("dynamic_variables"),
assistant_id=None,
),
None,
)
raise ValueError("启动参数缺少 assistant_id 或 inline_config")
@router.websocket("/ws/stream")
async def voice_stream(websocket: WebSocket):
from services.pipecat.pipeline import run_pipeline
from services.pipecat.transports import build_ws_transport
if not await require_admin_websocket(websocket):
return
await websocket.accept()
try:
cfg, assistant_id = await _resolve_start_config(await websocket.receive_text())
transport = build_ws_transport(websocket)
# 直接 await:管线持续读这条 WS 的音频帧,直到对端断开
await run_pipeline(transport, cfg, assistant_id=assistant_id, channel="websocket")
except WebSocketDisconnect:
logger.info("WS 音频流断开")
except Exception as e:
logger.error(f"WS 音频流出错: {e}")