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
This commit is contained in:
xiaji
2026-06-12 19:00:00 +08:00
parent 3ab6e4c7d0
commit bc36a1fc38
15 changed files with 2746 additions and 48 deletions

View File

@@ -34,8 +34,12 @@ class ArticleListItem(BaseModel):
fetched_at: datetime
image_url: str | None = None
# === 列表预览钩子:点击进详情前的"诱导点" ===
commentary: str | None = None # LLM 点评(列表里截断显示)
# 双 provider 评论:Angel(原字段) + 美团(meituan 字段),前端两条都展示
commentary: str | None = None # Angel 评论(列表里截断显示)
commentary_status: str | None = None # ok/failed/pending/n/a
commentary_meituan: str | None = None # 美团评论
commentary_meituan_status: str | None = None
commentary_engine: str | None = None # angel / meituan / "angel,meituan"
image_ai_url: str | None = None # AI 插图(列表里缩略图)
is_starred: bool = False
@@ -66,8 +70,14 @@ class ArticleDetail(BaseModel):
format_status: str | None = None # pending/ok/failed/n/a
classify_status: str | None = None
image_ai_status: str | None = None
# 双 provider 评论
commentary_status: str | None = None
commentary: str | None = None
commentary: str | None = None # Angel
commentary_engine: str | None = None
commentary_meituan_status: str | None = None
commentary_meituan: str | None = None
commentary_meituan_model: str | None = None
commentary_meituan_error: str | None = None
entities: dict | None = None
sentiment: float | None = None
duplicate_of: int | None = None

View File

@@ -20,8 +20,39 @@ class LlmSettingOut(BaseModel):
enabled: bool = True
# 全局屏蔽分类标签;与 sources.blocklist_tags 合并后注入 classify prompt
blocklist_tags: list[str] = []
# 美团大模型(LongCat,OpenAI 兼容)双 provider 评论
# 安全:不回传 api_key 真值,只回传 meituan_api_key_set 表示"是否已配置"
meituan_api_key_set: bool = False
meituan_base_url: str = "https://api.longcat.chat/openai/v1"
meituan_chat_model: str = "LongCat-2.0-Preview"
meituan_interval_sec: float = 2.0
meituan_enabled: bool = True
meituan_commentary_prompt: str | None = None
updated_at: datetime | None = None
@classmethod
def from_row(cls, row) -> "LlmSettingOut":
"""从 LlmSetting 构造,key 字段转 bool。"""
return cls(
format_prompt=row.format_prompt,
classify_prompt=row.classify_prompt,
commentary_prompt=row.commentary_prompt,
image_prompt_template=row.image_prompt_template,
image_size=row.image_size,
chat_model=row.chat_model,
image_model=row.image_model,
interval_sec=row.interval_sec,
enabled=row.enabled,
blocklist_tags=row.blocklist_tags or [],
meituan_api_key_set=bool(row.meituan_api_key),
meituan_base_url=row.meituan_base_url,
meituan_chat_model=row.meituan_chat_model,
meituan_interval_sec=row.meituan_interval_sec,
meituan_enabled=row.meituan_enabled,
meituan_commentary_prompt=row.meituan_commentary_prompt,
updated_at=row.updated_at,
)
class LlmSettingUpdate(BaseModel):
"""PATCH — 全部字段 optional,只更新传入的。"""
@@ -36,6 +67,13 @@ class LlmSettingUpdate(BaseModel):
interval_sec: float | None = Field(default=None, ge=0.0, le=60.0)
enabled: bool | None = None
blocklist_tags: list[str] | None = None
# 美团 provider 字段(api_key 可更新;None/空 = 不修改;显式传空字符串 = 清空)
meituan_api_key: str | None = Field(default=None, max_length=512)
meituan_base_url: str | None = Field(default=None, max_length=255)
meituan_chat_model: str | None = Field(default=None, max_length=64)
meituan_interval_sec: float | None = Field(default=None, ge=0.0, le=60.0)
meituan_enabled: bool | None = None
meituan_commentary_prompt: str | None = None
# === 默认提示词(模板,用户可改)===