Refactor backend to support interface-definition driven model resources
- Introduce a new model structure for managing interface definitions and model resources, enhancing the backend's capability to handle various service integrations. - Update the Makefile to reflect changes in database seeding and resource management commands. - Remove the deprecated credentials management routes and replace them with a unified model registry API. - Modify existing routes and schemas to align with the new model structure, ensuring seamless integration with the frontend. - Enhance database seeding scripts to populate new model resources and their configurations. - Update README documentation to reflect the new architecture and usage instructions for model resources and interface definitions.
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
"""创建 STT / LLM / TTS 服务。
|
||||
|
||||
对应 dograh 的 service_factory.py,但只留一套国产栈(OpenAI 兼容),
|
||||
按 provider 扩展时在这里加分支即可——这是未来接更多模型的唯一入口。
|
||||
按 interface_type 扩展时在这里加分支即可——这是未来接更多模型的唯一入口。
|
||||
"""
|
||||
|
||||
import config
|
||||
@@ -14,13 +14,7 @@ from pipecat.services.openai.tts import VALID_VOICES, OpenAITTSService
|
||||
from pipecat.transcriptions.language import Language
|
||||
|
||||
from services.pipecat.xfyun_asr import DEFAULT_XFYUN_ASR_URL, XfyunASRService
|
||||
from services.pipecat.xfyun_config import (
|
||||
is_super_tts,
|
||||
parse_xfyun_credential,
|
||||
websocket_url,
|
||||
xfyun_language,
|
||||
xfyun_speed,
|
||||
)
|
||||
from services.pipecat.xfyun_config import websocket_url, xfyun_language, xfyun_speed
|
||||
from services.pipecat.xfyun_super_tts import (
|
||||
DEFAULT_XFYUN_SUPER_TTS_URL,
|
||||
XfyunSuperTTSService,
|
||||
@@ -43,16 +37,21 @@ def create_stt(cfg: AssistantConfig):
|
||||
|
||||
连接信息优先用 cfg(由 config_resolver 从 DB 注入),为空回退 .env 默认。
|
||||
"""
|
||||
if cfg.stt_interface_type == "xfyun":
|
||||
credential = parse_xfyun_credential(cfg.stt_api_key)
|
||||
if cfg.stt_interface_type == "xfyun-asr":
|
||||
return XfyunASRService(
|
||||
app_id=credential.app_id,
|
||||
api_key=credential.api_key,
|
||||
api_secret=credential.api_secret,
|
||||
app_id=str(cfg.stt_secrets.get("appId") or ""),
|
||||
api_key=str(cfg.stt_secrets.get("apiKey") or ""),
|
||||
api_secret=str(cfg.stt_secrets.get("apiSecret") or ""),
|
||||
url=websocket_url(cfg.stt_base_url, DEFAULT_XFYUN_ASR_URL),
|
||||
language=xfyun_language(cfg.stt_language),
|
||||
sample_rate=16000,
|
||||
domain=str(cfg.stt_values.get("domain") or "iat"),
|
||||
accent=str(cfg.stt_values.get("accent") or "mandarin"),
|
||||
frame_size=int(cfg.stt_values.get("frameSize") or 1280),
|
||||
dynamic_correction=bool(cfg.stt_values.get("dynamicCorrection", False)),
|
||||
)
|
||||
if cfg.stt_interface_type not in {"openai-asr", "dashscope-asr"}:
|
||||
raise ValueError(f"不支持的 ASR 接口类型: {cfg.stt_interface_type}")
|
||||
|
||||
return OpenAISTTService(
|
||||
api_key=cfg.stt_api_key or config.STT_API_KEY,
|
||||
@@ -66,6 +65,8 @@ def create_stt(cfg: AssistantConfig):
|
||||
|
||||
def create_llm(cfg: AssistantConfig):
|
||||
"""DeepSeek 等,走 OpenAI 兼容的 /v1/chat/completions。"""
|
||||
if cfg.llm_interface_type not in {"openai-llm", "dashscope-llm"}:
|
||||
raise ValueError(f"不支持的 LLM 接口类型: {cfg.llm_interface_type}")
|
||||
return OpenAILLMService(
|
||||
api_key=cfg.llm_api_key or config.LLM_API_KEY,
|
||||
base_url=cfg.llm_base_url or config.LLM_BASE_URL,
|
||||
@@ -76,31 +77,39 @@ def create_llm(cfg: AssistantConfig):
|
||||
def create_tts(cfg: AssistantConfig):
|
||||
"""CosyVoice 等,走 OpenAI 兼容的 /v1/audio/speech。"""
|
||||
voice = cfg.voice or config.TTS_VOICE
|
||||
if cfg.tts_interface_type == "xfyun":
|
||||
credential = parse_xfyun_credential(cfg.tts_api_key)
|
||||
speed = xfyun_speed(cfg.tts_speed)
|
||||
if is_super_tts(cfg.tts_model, cfg.tts_base_url):
|
||||
return XfyunSuperTTSService(
|
||||
app_id=credential.app_id,
|
||||
api_key=credential.api_key,
|
||||
api_secret=credential.api_secret,
|
||||
voice=voice,
|
||||
url=websocket_url(cfg.tts_base_url, DEFAULT_XFYUN_SUPER_TTS_URL),
|
||||
sample_rate=16000,
|
||||
source_sample_rate=24000,
|
||||
speed=speed,
|
||||
)
|
||||
if cfg.tts_interface_type == "xfyun-super-tts":
|
||||
return XfyunSuperTTSService(
|
||||
app_id=str(cfg.tts_secrets.get("appId") or ""),
|
||||
api_key=str(cfg.tts_secrets.get("apiKey") or ""),
|
||||
api_secret=str(cfg.tts_secrets.get("apiSecret") or ""),
|
||||
voice=voice,
|
||||
url=websocket_url(cfg.tts_base_url, DEFAULT_XFYUN_SUPER_TTS_URL),
|
||||
sample_rate=16000,
|
||||
source_sample_rate=int(cfg.tts_values.get("sourceSampleRate") or 24000),
|
||||
speed=xfyun_speed(cfg.tts_speed),
|
||||
volume=int(cfg.tts_values.get("volume") or 50),
|
||||
pitch=int(cfg.tts_values.get("pitch") or 50),
|
||||
oral_level=str(cfg.tts_values.get("oralLevel") or "mid"),
|
||||
text_aggregation_mode=str(
|
||||
cfg.tts_values.get("textAggregationMode") or "token"
|
||||
),
|
||||
)
|
||||
if cfg.tts_interface_type == "xfyun-tts":
|
||||
return XfyunTTSService(
|
||||
app_id=credential.app_id,
|
||||
api_key=credential.api_key,
|
||||
api_secret=credential.api_secret,
|
||||
app_id=str(cfg.tts_secrets.get("appId") or ""),
|
||||
api_key=str(cfg.tts_secrets.get("apiKey") or ""),
|
||||
api_secret=str(cfg.tts_secrets.get("apiSecret") or ""),
|
||||
voice=voice,
|
||||
url=websocket_url(cfg.tts_base_url, DEFAULT_XFYUN_TTS_URL),
|
||||
sample_rate=16000,
|
||||
source_sample_rate=16000,
|
||||
speed=speed,
|
||||
source_sample_rate=int(cfg.tts_values.get("sourceSampleRate") or 16000),
|
||||
speed=xfyun_speed(cfg.tts_speed),
|
||||
volume=int(cfg.tts_values.get("volume") or 50),
|
||||
pitch=int(cfg.tts_values.get("pitch") or 50),
|
||||
push_stop_frames=True,
|
||||
)
|
||||
if cfg.tts_interface_type not in {"openai-tts", "dashscope-tts"}:
|
||||
raise ValueError(f"不支持的 TTS 接口类型: {cfg.tts_interface_type}")
|
||||
|
||||
# Pipecat 默认只接受 OpenAI 官方音色。OpenAI 兼容服务常使用自定义 voice id,
|
||||
# 注册为原样映射后仍由 OpenAI SDK 按字符串透传给供应商。
|
||||
|
||||
@@ -1,46 +1,7 @@
|
||||
"""Parse Xfyun's three-part credential from ProviderCredential.api_key."""
|
||||
"""Shared Xfyun service value normalization."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from dataclasses import dataclass
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class XfyunCredential:
|
||||
app_id: str
|
||||
api_key: str
|
||||
api_secret: str
|
||||
|
||||
|
||||
def parse_xfyun_credential(value: str) -> XfyunCredential:
|
||||
"""Accept JSON in the existing api_key column.
|
||||
|
||||
Example:
|
||||
{"appId":"...","apiKey":"...","apiSecret":"..."}
|
||||
"""
|
||||
try:
|
||||
payload = json.loads(value)
|
||||
except json.JSONDecodeError as exc:
|
||||
raise ValueError(
|
||||
'Xfyun API Key must be JSON: {"appId":"...","apiKey":"...","apiSecret":"..."}'
|
||||
) from exc
|
||||
|
||||
if not isinstance(payload, dict):
|
||||
raise ValueError("Xfyun API Key JSON must be an object")
|
||||
|
||||
credential = XfyunCredential(
|
||||
app_id=str(payload.get("appId") or payload.get("app_id") or "").strip(),
|
||||
api_key=str(payload.get("apiKey") or payload.get("api_key") or "").strip(),
|
||||
api_secret=str(
|
||||
payload.get("apiSecret") or payload.get("api_secret") or ""
|
||||
).strip(),
|
||||
)
|
||||
if not credential.app_id or not credential.api_key or not credential.api_secret:
|
||||
raise ValueError("Xfyun API Key JSON requires appId, apiKey, and apiSecret")
|
||||
return credential
|
||||
|
||||
|
||||
def websocket_url(value: str, default: str) -> str:
|
||||
url = (value or default).strip()
|
||||
if url.startswith("https://"):
|
||||
@@ -49,12 +10,6 @@ def websocket_url(value: str, default: str) -> str:
|
||||
return f"ws://{url.removeprefix('http://')}"
|
||||
return url
|
||||
|
||||
|
||||
def is_super_tts(model_id: str, api_url: str) -> bool:
|
||||
model = model_id.lower().replace("-", "_")
|
||||
return "super" in model or "/private/" in api_url.lower()
|
||||
|
||||
|
||||
def xfyun_language(value: str) -> str:
|
||||
normalized = (value or "zh_cn").lower().replace("-", "_")
|
||||
return {"zh": "zh_cn", "en": "en_us"}.get(normalized, normalized)
|
||||
|
||||
Reference in New Issue
Block a user