- llm_settings.agnes_api_key TEXT (DB key 优先,.env 兜底) - llm_settings.agnes_base_url_override VARCHAR (留空 = 用 .env) - alembic 0005_agnes_key 迁移 - LlmSettingOut.agnes_api_key_set (bool) 替代直接回传 key - LlmSettingUpdate 加 agnes_api_key / agnes_base_url_override(可空可清空) - providers.get_angel_client 改用 DB key 优先 - enrichment.py 改为 get_angel_client() 工厂调用(热改 key 不需重启) - /admin/llm/settings/test 走 get_angel_client(测的是 DB 里的 key) - 前端 AdminLlmSettings 在'总开关 + 模型'卡里加 Angel api_key 输入框 + base_url 覆盖 + 已配置/未配置指示灯 + 清空按钮 - 顶部'测连接'按钮复用(测的就是 Angel)
121 lines
4.3 KiB
Python
121 lines
4.3 KiB
Python
"""LLM provider 工厂。
|
|
|
|
历史:全站只用一个 LlmClient(单例)指 Agnes。
|
|
现在:支持多个 provider,各自独立 base_url / api_key / model / 节流。
|
|
|
|
- `get_angel_client(setting)` — Agnes 客户端(原 LlmClient 等价)
|
|
- `get_meituan_client(setting)` — 美团大模型客户端(OpenAI 兼容,LongCat)
|
|
|
|
设计:
|
|
- 工厂每次返回新实例(无状态;节流靠 client 内部 Semaphore 自带)
|
|
- Provider 不可用(api_key 空)= 返回 None
|
|
- `get_provider_commentary_defaults()` 暴露 Angel / 美团 的 temperature / max_tokens / system 差异。
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
from typing import Any
|
|
|
|
from app.models.llm_setting import LlmSetting
|
|
from app.services.llm.client import LlmClient
|
|
|
|
logger = logging.getLogger("news.llm.providers")
|
|
|
|
# === Provider 名常量(供 enrichment/前端/日志统一引用)===
|
|
PROVIDER_ANGEL = "angel" # Agnes(原 LlmClient 默认端点)
|
|
PROVIDER_MEITUAN = "meituan" # 美团大模型(LongCat,OpenAI 兼容)
|
|
|
|
|
|
def get_angel_client(setting: LlmSetting) -> LlmClient:
|
|
"""Agnes(Angel)客户端。
|
|
|
|
凭据优先级(高 → 低):
|
|
1. llm_settings.agnes_api_key(DB 里存的 key,UI 可改)
|
|
2. .env AGNES_API_KEY
|
|
3. .env 任意一个都不配 = LlmClient.is_configured() = False
|
|
|
|
base_url 优先级:
|
|
1. llm_settings.agnes_base_url_override(DB 里存的)
|
|
2. .env AGNES_BASE_URL
|
|
"""
|
|
from app.config import settings as app_settings
|
|
|
|
api_key = (getattr(setting, "agnes_api_key", "") or app_settings.agnes_api_key) or ""
|
|
base_url = (
|
|
getattr(setting, "agnes_base_url_override", "") or app_settings.agnes_base_url
|
|
).rstrip("/")
|
|
return LlmClient(
|
|
base_url=base_url or "https://apihub.agnes-ai.com/v1",
|
|
api_key=api_key,
|
|
chat_model=setting.chat_model or "agnes-2.0-flash",
|
|
image_model=setting.image_model or "agnes-image-2.1-flash",
|
|
interval_sec=setting.interval_sec or 2.0,
|
|
)
|
|
|
|
|
|
def get_meituan_client(setting: LlmSetting) -> LlmClient | None:
|
|
"""美团大模型(LongCat)客户端。
|
|
配置来源:llm_settings 表里 meituan_* 字段(API key / base_url / model / interval / enabled)。
|
|
"""
|
|
from app.config import settings as app_settings # 延迟导入,避免循环
|
|
|
|
api_key = getattr(setting, "meituan_api_key", "") or app_settings.meituan_api_key
|
|
base_url = (
|
|
getattr(setting, "meituan_base_url", "") or app_settings.meituan_base_url
|
|
)
|
|
model = (
|
|
getattr(setting, "meituan_chat_model", "") or app_settings.meituan_chat_model
|
|
)
|
|
interval = (
|
|
getattr(setting, "meituan_interval_sec", None) or app_settings.meituan_interval_sec
|
|
)
|
|
if not api_key:
|
|
return None
|
|
return LlmClient(
|
|
base_url=base_url or "https://api.longcat.chat/openai/v1",
|
|
api_key=api_key,
|
|
chat_model=model or "LongCat-2.0-Preview",
|
|
interval_sec=float(interval or 2.0),
|
|
)
|
|
|
|
|
|
def get_provider_client(provider: str, setting: LlmSetting) -> LlmClient | None:
|
|
"""统一入口:按 provider 名取客户端。不可用时返回 None。"""
|
|
if provider == PROVIDER_ANGEL:
|
|
c = get_angel_client(setting)
|
|
return c if c.is_configured() else None
|
|
if provider == PROVIDER_MEITUAN:
|
|
return get_meituan_client(setting)
|
|
raise ValueError(f"unknown provider: {provider}")
|
|
|
|
|
|
def is_provider_enabled(provider: str, setting: LlmSetting) -> bool:
|
|
"""provider 是否启用 + 配置齐全。"""
|
|
if not setting.enabled:
|
|
return False
|
|
if provider == PROVIDER_ANGEL:
|
|
return get_provider_client(PROVIDER_ANGEL, setting) is not None
|
|
if provider == PROVIDER_MEITUAN:
|
|
if not bool(getattr(setting, "meituan_enabled", True)):
|
|
return False
|
|
return get_provider_client(PROVIDER_MEITUAN, setting) is not None
|
|
return False
|
|
|
|
|
|
# === Provider 评论差异(温度 / max_tokens / system)===
|
|
# Angel: temperature=0.6, max_tokens=600, system="你是资深新闻评论员。"
|
|
# 美团: temperature=0.7, max_tokens=1000, system=None(用户示例无 system 字段)
|
|
|
|
PROVIDER_COMMENTARY_DEFAULTS: dict[str, dict[str, Any]] = {
|
|
PROVIDER_ANGEL: {
|
|
"temperature": 0.6,
|
|
"max_tokens": 600,
|
|
"system": "你是资深新闻评论员。",
|
|
},
|
|
PROVIDER_MEITUAN: {
|
|
"temperature": 0.7,
|
|
"max_tokens": 1000,
|
|
"system": None,
|
|
},
|
|
}
|