Files
yuanzhipeng 646efa132e ```
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 函数按用途获取对应客户端
- 支持语音、视觉、报告等不同用途使用独立网关和密钥
```
2026-06-24 20:34:10 +08:00

47 lines
1.9 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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