Files
diary-news/backend/app/services/llm/providers.py

103 lines
3.6 KiB
Python
Raw Normal View History

"""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 客户端 — 与 LlmClient 单例行为完全一致。"""
return LlmClient(
chat_model=setting.chat_model,
image_model=setting.image_model,
interval_sec=setting.interval_sec,
)
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,
},
}