Files
diary-news/backend/app/config.py
xiaji bc36a1fc38 feat(commentary): 双 provider 评论 — Angel(Agnes) + 美团大模型(LongCat)
- 新增 articles.commentary_meituan{_status,_model,_error} 4 列 + commentary_engine
- LlmSetting 加 meituan_api_key/base_url/chat_model/interval_sec/enabled/commentary_prompt
- 新 app/services/llm/providers.py 工厂,支持多 provider 客户端
- enrichment 流程改为 commentary_angel + commentary_meituan 并行(asyncio.gather),
  任一 provider 失败不影响另一个
- enrichment_loop 状态判定:任一 provider 状态不是 ok 都视为待 enrich
- alembic 0004_dual_commentary 迁移
- 前端 Feed 卡片 + ArticleDetail 详情页各加一条'美团评论'卡
- AdminLlmSettings 加美团 provider 配置卡(独立 api_key 编辑器,不回显明文)
- LlmSettingOut.meituan_api_key_set (bool) 替代直接回传 key
- 默认 URL https://api.longcat.chat/openai/v1 / 默认模型 LongCat-2.0-Preview
2026-06-12 19:00:00 +08:00

154 lines
5.2 KiB
Python

"""应用配置:从 .env / 环境变量读取,集中管理所有开关。"""
from __future__ import annotations
from functools import lru_cache
from pathlib import Path
from pydantic import Field, field_validator
from pydantic_settings import BaseSettings, SettingsConfigDict
class Settings(BaseSettings):
model_config = SettingsConfigDict(
env_file=".env",
env_file_encoding="utf-8",
case_sensitive=False,
extra="ignore",
)
# ===== 通用 =====
tz: str = "Asia/Hong_Kong"
log_level: str = "INFO"
# ===== 数据库 =====
postgres_user: str
postgres_password: str
postgres_db: str
postgres_host: str = "postgres"
postgres_port: int = 5432
@property
def database_url(self) -> str:
# asyncpg
return (
f"postgresql+asyncpg://{self.postgres_user}:{self.postgres_password}"
f"@{self.postgres_host}:{self.postgres_port}/{self.postgres_db}"
)
@property
def sync_database_url(self) -> str:
# alembic 用的同步 URL
return (
f"postgresql+psycopg2://{self.postgres_user}:{self.postgres_password}"
f"@{self.postgres_host}:{self.postgres_port}/{self.postgres_db}"
)
# ===== Redis =====
redis_host: str = "redis"
redis_port: int = 6379
redis_password: str
redis_db: int = 0
@property
def redis_url(self) -> str:
return (
f"redis://:{self.redis_password}@{self.redis_host}:{self.redis_port}/{self.redis_db}"
)
# ===== JWT =====
jwt_secret: str
jwt_algorithm: str = "HS256"
access_token_ttl_min: int = 60
refresh_token_ttl_day: int = 14
# ===== 腾讯云 TMT =====
tencentcloud_secret_id: str = ""
tencentcloud_secret_key: str = ""
tencentcloud_region: str = "ap-hongkong"
tencent_tmt_endpoint: str = "tmt.tencentcloudapi.com"
tencent_tmt_quota_month: int = 5_000_000
tencent_tmt_quota_buffer: float = 0.05
tencent_tmt_max_chars_per_req: int = 4500
# ===== 智谱 GLM(OpenAI 兼容,翻译主通道)=====
# 用法:智谱开放平台 GLM-4 系列,通过 OpenAI 协议调用
# 留空 api_key = 不启用该 provider
zhipu_api_key: str = ""
zhipu_base_url: str = "https://open.bigmodel.cn/api/paas/v4"
zhipu_chat_model: str = "glm-4-flash"
zhipu_model: str = "glm-4-flash" # 兼容旧字段名
# 2 秒/次(用户要求 6/11,降低频率避免触发限流)
zhipu_interval_sec: float = 2.0
# ===== 讯飞星火(WebSocket,翻译二级通道)=====
# 用法:讯飞星火 v1.1 Spark Lite,WebSocket 鉴权需要 APPID + APIKey + APISecret
# 留空任意一个 = 不启用该 provider
# (历史实现用的是 OpenAI 兼容 + APIPassword,已切换为 WebSocket 鉴权,字段名换)
spark_appid: str = ""
spark_api_key: str = "" # WebSocket 鉴权用的 APIKey
spark_api_secret: str = "" # WebSocket 鉴权用的 APISecret
spark_domain: str = "lite" # v1.1 Spark Lite
# 兼容旧字段名(留空,只在没填 WebSocket 字段时起提示作用)
spark_api_password: str = ""
# 2 秒/次(用户要求 6/11,降低频率避免触发限流)
spark_interval_sec: float = 2.0
@field_validator("tencent_tmt_quota_buffer")
@classmethod
def _check_buffer(cls, v: float) -> float:
if not 0.0 <= v <= 0.5:
raise ValueError("buffer 必须在 0~0.5")
return v
# ===== 本地翻译 =====
local_translate_enabled: bool = False
local_translate_model: str = "nllb-200-distilled-600M"
local_translate_device: str = "cpu"
# ===== 腾讯 MaaS(OpenAI 兼容翻译备用通道)=====
# 用法:腾讯云 MaaS 提供的翻译模型,通过 OpenAI 协议调用
# 留空 api_key = 不启用该 provider
tencent_maas_api_key: str = ""
tencent_maas_base_url: str = "https://maas-api.hivoice.cn/v1"
tencent_maas_model: str = "u2"
# 2 秒/次(与智谱/星火统一节流)
tencent_maas_interval_sec: float = 2.0
# ===== 抓取 =====
fetch_global_qps: int = 4
fetch_timeout: int = 20
fetch_fail_pause_threshold: int = 3
fetch_max_retries: int = 2
# ===== Caddy / 域名 =====
domain: str = ""
acme_email: str = ""
# ===== Agnes LLM(智能增强)=====
# 留空 = 不启用 LLM 增强(翻译后只走默认排版,提示词也不读)
agnes_api_key: str = ""
agnes_base_url: str = "https://apihub.agnes-ai.com/v1"
agnes_chat_model: str = "agnes-2.0-flash"
agnes_image_model: str = "agnes-image-2.1-flash"
# 全局 LLM 调用间隔(秒),避免被限流
llm_interval_sec: float = 2.0
# ===== 美团大模型 LongCat(双 provider 评论的第二个)=====
# OpenAI 兼容端点;与 Agnes 并列,各自跑各自的 prompt,结果存到 articles 各自列
# 留空 api_key = 不启用美团 provider(Angel 仍正常工作)
meituan_api_key: str = ""
meituan_base_url: str = "https://api.longcat.chat/openai/v1"
meituan_chat_model: str = "LongCat-2.0-Preview"
meituan_interval_sec: float = 2.0
# ===== 内部路径(部署后可调) =====
project_root: Path = Path(__file__).resolve().parents[2]
@lru_cache
def get_settings() -> Settings:
return Settings() # type: ignore[call-arg]
settings = get_settings()