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.7 KiB
Python
47 lines
1.7 KiB
Python
"""共享的 httpx.AsyncClient。
|
||
|
||
历史问题:后端每次访问 chatlog.exe(127.0.0.1:5030)都新建一个 AsyncClient,
|
||
用完即关,没有 keep-alive。打开一个群聊会瞬间产生几十条短连接(图片代理、
|
||
头像查询等),在 Windows 上会堆积 TIME_WAIT / 耗尽临时端口,导致“用一会儿就卡”。
|
||
|
||
这里改为全后端共享一个带连接池的 client,复用 keep-alive 连接,连接建立开销
|
||
和端口占用都大幅下降。在 lifespan 关闭时统一释放。
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import httpx
|
||
|
||
_client: httpx.AsyncClient | None = None
|
||
|
||
|
||
def shared_client() -> httpx.AsyncClient:
|
||
"""返回进程级共享的 AsyncClient(惰性创建)。
|
||
|
||
- trust_env=False:与原各处调用保持一致,不读系统代理,避免本地回环被代理拦截。
|
||
- follow_redirects=True:媒体/文件接口需要;普通 api 调用无重定向,无副作用。
|
||
- limits:保持 keep-alive 连接,避免每请求新建连接。
|
||
单次请求可通过 client.get(..., timeout=...) 覆盖超时。
|
||
"""
|
||
global _client
|
||
if _client is None or _client.is_closed:
|
||
_client = httpx.AsyncClient(
|
||
trust_env=False,
|
||
follow_redirects=True,
|
||
timeout=httpx.Timeout(30.0, connect=5.0),
|
||
limits=httpx.Limits(
|
||
max_keepalive_connections=32,
|
||
max_connections=128,
|
||
keepalive_expiry=30.0,
|
||
),
|
||
)
|
||
return _client
|
||
|
||
|
||
async def close_shared_client() -> None:
|
||
"""在应用关闭时释放共享 client。"""
|
||
global _client
|
||
if _client is not None and not _client.is_closed:
|
||
await _client.aclose()
|
||
_client = None
|