Files
ai-video-fullstack/backend/services/realtime/launcher.py
Xin Wang 86639692ba feat: implement OpenAI-compatible Realtime API with authentication and management features
- Added support for public Realtime API, including new routes for managing API keys and handling WebRTC connections.
- Introduced RealtimeApiKey model and associated CRUD operations for admin management of API keys.
- Implemented authentication mechanisms for API keys and client secrets.
- Enhanced environment configuration with new secrets for Realtime API.
- Created OpenAIRealtime session management and event processing for real-time interactions.
- Updated schemas and settings to accommodate new features and ensure compatibility with existing systems.
2026-08-11 10:05:55 +08:00

196 lines
6.7 KiB
Python

"""Resolve and validate an assistant before creating a media connection."""
from __future__ import annotations
from typing import Any
from db.session import SessionLocal
from models import AssistantConfig
from services.config_resolver import resolve_runtime_config
from services.node_specs import graph_references
from services.runtime_variables import prepare_dynamic_config
from services.workflow_engine import WorkflowEngine
async def resolve_assistant_config(
assistant_id: str,
*,
dynamic_variables: dict[str, Any] | None = None,
) -> AssistantConfig:
async with SessionLocal() as session:
config = await resolve_runtime_config(session, assistant_id)
return prepare_dynamic_config(
config,
dynamic_variables or {},
assistant_id=assistant_id,
)
def validate_visual_runtime(config: AssistantConfig) -> bool:
"""Return the authoritative video-input permission or fail before connect."""
if config.type == "workflow":
return WorkflowEngine(config.graph).uses_vision()
vision_enabled = config.vision_enabled
if not vision_enabled:
return False
has_native_vision = (
not config.vision_model_resource_id and config.llm_support_image_input
)
has_aux_vision_model = (
bool(config.vision_model_resource_id)
and config.vision_llm_support_image_input
)
if not (has_native_vision or has_aux_vision_model):
raise ValueError(
"当前模型不支持图片输入,请在模型资源中选择支持图片输入的视觉模型"
)
return True
def _require_values(labels: list[tuple[str, Any]]) -> None:
missing = [label for label, value in labels if not value]
if missing:
raise ValueError(f"助手运行配置不完整: {', '.join(missing)}")
def _validate_voice_resource(
capability: str,
*,
interface_type: str,
values: dict[str, Any],
secrets: dict[str, Any],
) -> None:
labels: list[tuple[str, Any]] = []
if capability == "ASR":
if interface_type not in {"openai-asr", "dashscope-asr", "xfyun-asr"}:
raise ValueError(f"不支持的 ASR 接口类型: {interface_type}")
if interface_type == "xfyun-asr":
labels.extend(
(f"ASR {key}", secrets.get(key))
for key in ("appId", "apiKey", "apiSecret")
)
else:
labels.extend(
[
("ASR modelId", values.get("modelId")),
("ASR apiUrl", values.get("apiUrl")),
("ASR apiKey", secrets.get("apiKey")),
]
)
elif capability == "TTS":
if interface_type not in {
"openai-tts",
"dashscope-tts",
"xfyun-tts",
"xfyun-super-tts",
}:
raise ValueError(f"不支持的 TTS 接口类型: {interface_type}")
labels.append(("TTS voice", values.get("voice")))
if interface_type in {"xfyun-tts", "xfyun-super-tts"}:
labels.extend(
(f"TTS {key}", secrets.get(key))
for key in ("appId", "apiKey", "apiSecret")
)
else:
labels.extend(
[
("TTS modelId", values.get("modelId")),
("TTS apiUrl", values.get("apiUrl")),
("TTS apiKey", secrets.get("apiKey")),
]
)
elif capability == "LLM":
if interface_type not in {"openai-llm", "dashscope-llm"}:
raise ValueError(f"不支持的 LLM 接口类型: {interface_type}")
labels.extend(
[
("LLM modelId", values.get("modelId")),
("LLM apiUrl", values.get("apiUrl")),
("LLM apiKey", secrets.get("apiKey")),
]
)
_require_values(labels)
def validate_runtime_requirements(config: AssistantConfig) -> None:
"""Reject structurally incomplete assistants before media negotiation."""
if config.type not in {"prompt", "workflow", "dify", "fastgpt"}:
raise ValueError(f"当前助手类型不支持 Realtime API: {config.type}")
if config.runtimeMode == "realtime":
if config.type not in {"prompt", "workflow"}:
raise ValueError(f"助手类型 {config.type} 不支持 realtime 运行模式")
if config.realtime_interface_type not in {
"qwen-audio-realtime",
"stepfun-realtime",
}:
raise ValueError(
f"不支持的 Realtime 接口类型: {config.realtime_interface_type}"
)
_require_values(
[
("Realtime interfaceType", config.realtime_interface_type),
("Realtime modelId", config.realtimeModel),
("Realtime apiUrl", config.realtime_base_url),
("Realtime apiKey", config.realtime_api_key),
],
)
return
_validate_voice_resource(
"ASR",
interface_type=config.stt_interface_type,
values=config.stt_values,
secrets=config.stt_secrets,
)
_validate_voice_resource(
"TTS",
interface_type=config.tts_interface_type,
values=config.tts_values,
secrets=config.tts_secrets,
)
if config.type == "prompt":
_validate_voice_resource(
"LLM",
interface_type=config.llm_interface_type,
values=config.llm_values,
secrets=config.llm_secrets,
)
elif config.type == "dify":
_require_values(
[
("Dify apiUrl", config.dify_api_url),
("Dify apiKey", config.dify_api_key),
],
)
elif config.type == "fastgpt":
_require_values(
[
("FastGPT apiUrl", config.fastgpt_api_url),
("FastGPT apiKey", config.fastgpt_api_key),
],
)
if config.type != "workflow":
return
references = graph_references(config.graph)
missing_models = references["model_resources"] - set(
config.workflow_model_resources
)
missing_knowledge = references["knowledge_bases"] - set(
config.workflow_knowledge_bases
)
if missing_models or missing_knowledge:
missing = sorted([*missing_models, *missing_knowledge])
raise ValueError(f"Workflow 引用了不可用资源: {', '.join(missing)}")
for resource in config.workflow_model_resources.values():
if resource.capability in {"ASR", "TTS", "LLM"}:
_validate_voice_resource(
resource.capability,
interface_type=resource.interface_type,
values=resource.values,
secrets=resource.secrets,
)