feat(api): 添加万川平台模型配置获取和同步功能 - 新增 getWanchuanModelConfig 函数,按模型编码获取平台模型配置 - 新增 syncWanchuanModelToSettings 函数,从万川平台拉取模型配置并写入后端 AI 设置 - 支持按用途分多个模型编码(generic/vision/voice)分别同步配置 - 配置失败时跳过对应字段,不影响其他模型同步 feat(settings): 重构AI模型配置界面支持多模块分组 - 将AI配置按话题分析、报告生成、视觉、语音四个模块分组展示 - 每个模块独立配置接口地址、密钥和模型名称 - 添加从万川平台获取配置的按钮和同步功能 - 优化配置状态指示和错误提示信息 refactor(config): 扩展AI配置支持独立的语音视觉报告网关 - 新增 voice_base_url/voice_api_key 配置项 - 新增 vision_base_url/vision_api_key 配置项 - 新增 summary_base_url/summary_api_key 配置项 - 留空时回退到 ai_base_url/ai_api_key 兼容单网关场景 refactor(http): 统一使用共享HTTP客户端减少连接开销 - 替换各处 httpx.AsyncClient 为 shared_client - 在 lifespan 中正确关闭共享客户端资源 - 优化 get_current_wxid 和 health 检查中的HTTP请求 refactor(ai): 按用途缓存AI客户端支持不同网关配置 - 重构 get_openai_client 支持按(base_url, api_key)缓存 - 新增 get_client_for 函数按用途获取对应客户端 - 支持语音、视觉、报告等不同用途使用独立网关和密钥 ```
47 lines
1.9 KiB
Python
47 lines
1.9 KiB
Python
import httpx
|
||
from openai import AsyncOpenAI
|
||
|
||
from services.runtime_settings import get_ai_settings
|
||
|
||
# 按 (base_url, api_key) 缓存客户端:聊天/视觉/语音可能指向不同网关与密钥,
|
||
# 各自一个 pair,最多累积 3 个,有界。配置变更(settings PUT 会 invalidate
|
||
# runtime_settings 缓存)后,新的 pair 会自然生成新客户端;旧的留存无副作用。
|
||
_client_cache: dict[tuple[str, str], AsyncOpenAI] = {}
|
||
_http_client_cache: dict[tuple[str, str], httpx.AsyncClient] = {}
|
||
|
||
|
||
def _get_client(base_url: str, api_key: str) -> AsyncOpenAI:
|
||
cache_key = (base_url or "", api_key or "")
|
||
if cache_key not in _client_cache:
|
||
http_client = httpx.AsyncClient(timeout=httpx.Timeout(600.0, connect=30.0))
|
||
_http_client_cache[cache_key] = http_client
|
||
_client_cache[cache_key] = AsyncOpenAI(
|
||
api_key=api_key or "missing",
|
||
base_url=base_url or None,
|
||
http_client=http_client,
|
||
)
|
||
return _client_cache[cache_key]
|
||
|
||
|
||
async def get_openai_client() -> tuple[AsyncOpenAI, dict]:
|
||
"""聊天调用方(话题/报告/总结/对话)复用:用全局 ai_base_url / ai_api_key。"""
|
||
settings = await get_ai_settings()
|
||
client = _get_client(
|
||
settings.get("ai_base_url") or "",
|
||
settings.get("ai_api_key") or "",
|
||
)
|
||
return client, settings
|
||
|
||
|
||
async def get_client_for(purpose: str) -> tuple[AsyncOpenAI, dict]:
|
||
"""按用途取客户端:purpose 为 'voice' / 'vision'。
|
||
|
||
优先用 {purpose}_base_url / {purpose}_api_key,为空则回退到全局
|
||
ai_base_url / ai_api_key(单网关场景无需重复配置)。
|
||
"""
|
||
settings = await get_ai_settings()
|
||
base_url = settings.get(f"{purpose}_base_url") or settings.get("ai_base_url") or ""
|
||
api_key = settings.get(f"{purpose}_api_key") or settings.get("ai_api_key") or ""
|
||
client = _get_client(base_url, api_key)
|
||
return client, settings
|