chore: 集成 LLM 增强 — config/main/articles schema/workers + .env.example 加 Agnes 配置

This commit is contained in:
Mavis
2026-06-08 14:24:23 +08:00
parent ffd667f0dc
commit ba2298da0a
7 changed files with 48 additions and 7 deletions

View File

@@ -56,3 +56,13 @@ FETCH_MAX_RETRIES=2
DOMAIN= DOMAIN=
# 邮箱(Let's Encrypt 用) # 邮箱(Let's Encrypt 用)
ACME_EMAIL=you@example.com ACME_EMAIL=you@example.com
# ===== Agnes LLM(翻译后智能增强)=====
# 留空 = 不启用 LLM 增强(只走翻译)
# Agnes 控制台申请:https://platform.agnes-ai.com/
AGNES_API_KEY=your_agnes_api_key
AGNES_BASE_URL=https://apihub.agnes-ai.com/v1
AGNES_CHAT_MODEL=agnes-2.0-flash
AGNES_IMAGE_MODEL=agnes-image-2.1-flash
# LLM 调用间隔(秒,避免被限流;chat + image 各 1 个串行)
LLM_INTERVAL_SEC=2.0

View File

@@ -169,14 +169,20 @@ async def get_article(
title_zh=article.title_zh, title_zh=article.title_zh,
body_zh_html=article.body_zh_html, body_zh_html=article.body_zh_html,
body_zh_text=article.body_zh_text, body_zh_text=article.body_zh_text,
body_zh_formatted=article.body_zh_formatted,
summary_zh=article.summary_zh, summary_zh=article.summary_zh,
lang_src=article.lang_src, lang_src=article.lang_src,
author=article.author, author=article.author,
image_url=article.image_url, image_url=article.image_url,
image_ai_url=article.image_ai_url,
translation_status=article.translation_status, translation_status=article.translation_status,
translation_engine=article.translation_engine, translation_engine=article.translation_engine,
translated_at=article.translated_at, translated_at=article.translated_at,
category=article.category, category=article.category,
format_status=article.format_status,
classify_status=article.classify_status,
image_ai_status=article.image_ai_status,
commentary_status=article.commentary_status,
commentary=article.commentary, commentary=article.commentary,
entities=article.entities, entities=article.entities,
sentiment=article.sentiment, sentiment=article.sentiment,

View File

@@ -92,6 +92,15 @@ class Settings(BaseSettings):
domain: str = "" domain: str = ""
acme_email: 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
# ===== 内部路径(部署后可调) ===== # ===== 内部路径(部署后可调) =====
project_root: Path = Path(__file__).resolve().parents[2] project_root: Path = Path(__file__).resolve().parents[2]

View File

@@ -16,7 +16,7 @@ from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse from fastapi.responses import JSONResponse
from starlette.exceptions import HTTPException as StarletteHTTPException from starlette.exceptions import HTTPException as StarletteHTTPException
from app.api import admin, articles, auth, bookmarks, me, sources, subscriptions from app.api import admin, admin_llm, articles, auth, bookmarks, me, sources, subscriptions
from app.config import settings from app.config import settings
from app.database import engine from app.database import engine
from app.redis_client import close_redis, get_redis from app.redis_client import close_redis, get_redis
@@ -100,6 +100,7 @@ app.include_router(sources.router, prefix=API_PREFIX)
app.include_router(bookmarks.router, prefix=API_PREFIX) app.include_router(bookmarks.router, prefix=API_PREFIX)
app.include_router(subscriptions.router, prefix=API_PREFIX) app.include_router(subscriptions.router, prefix=API_PREFIX)
app.include_router(admin.router, prefix=API_PREFIX) app.include_router(admin.router, prefix=API_PREFIX)
app.include_router(admin_llm.router, prefix=API_PREFIX)
# === 健康检查 === # === 健康检查 ===

View File

@@ -5,6 +5,7 @@
from app.models.api_token import ApiToken # noqa: F401 from app.models.api_token import ApiToken # noqa: F401
from app.models.article import Article # noqa: F401 from app.models.article import Article # noqa: F401
from app.models.bookmark import Bookmark # noqa: F401 from app.models.bookmark import Bookmark # noqa: F401
from app.models.llm_setting import LlmSetting # noqa: F401
from app.models.source import Source, SourceKind # noqa: F401 from app.models.source import Source, SourceKind # noqa: F401
from app.models.subscription import Subscription # noqa: F401 from app.models.subscription import Subscription # noqa: F401
from app.models.user import User, UserRole # noqa: F401 from app.models.user import User, UserRole # noqa: F401
@@ -13,6 +14,7 @@ __all__ = [
"ApiToken", "ApiToken",
"Article", "Article",
"Bookmark", "Bookmark",
"LlmSetting",
"Source", "Source",
"SourceKind", "SourceKind",
"Subscription", "Subscription",

View File

@@ -46,14 +46,21 @@ class ArticleDetail(BaseModel):
title_zh: str | None = None title_zh: str | None = None
body_zh_html: str | None = None body_zh_html: str | None = None
body_zh_text: str | None = None body_zh_text: str | None = None
body_zh_formatted: str | None = None # LLM 排版后
summary_zh: str | None = None summary_zh: str | None = None
lang_src: str | None = None lang_src: str | None = None
author: str | None = None author: str | None = None
image_url: str | None = None image_url: str | None = None
image_ai_url: str | None = None # LLM 生成的插图
translation_status: str translation_status: str
translation_engine: str | None = None translation_engine: str | None = None
translated_at: datetime | None = None translated_at: datetime | None = None
# === LLM 增强状态 + 内容 ===
category: str | None = None category: str | None = None
format_status: str | None = None # pending/ok/failed/n/a
classify_status: str | None = None
image_ai_status: str | None = None
commentary_status: str | None = None
commentary: str | None = None commentary: str | None = None
entities: dict | None = None entities: dict | None = None
sentiment: float | None = None sentiment: float | None = None

View File

@@ -17,6 +17,7 @@ from sqlalchemy import select
from app.config import settings from app.config import settings
from app.database import AsyncSessionLocal from app.database import AsyncSessionLocal
from app.models.source import Source from app.models.source import Source
from app.services.llm.enrichment import enrichment_loop
from app.workers.pipeline import fetch_one_source, run_once, translation_loop from app.workers.pipeline import fetch_one_source, run_once, translation_loop
logger = logging.getLogger("news.worker") logger = logging.getLogger("news.worker")
@@ -93,6 +94,10 @@ async def main() -> None:
translation_task = asyncio.create_task(translation_loop(), name="translation_loop") translation_task = asyncio.create_task(translation_loop(), name="translation_loop")
logger.info("translation_loop task scheduled (1 article/sec)") logger.info("translation_loop task scheduled (1 article/sec)")
# 独立的 LLM 增强后台循环(翻译完成后,跑 4 项 LLM 任务)
enrichment_task = asyncio.create_task(enrichment_loop(), name="enrichment_loop")
logger.info("enrichment_loop task scheduled (scans translated articles)")
stop = asyncio.Event() stop = asyncio.Event()
def _signal_handler(): def _signal_handler():
@@ -108,12 +113,13 @@ async def main() -> None:
pass pass
await stop.wait() await stop.wait()
logger.info("stopping scheduler and translation loop") logger.info("stopping scheduler and background loops")
translation_task.cancel() for t in (translation_task, enrichment_task):
try: t.cancel()
await translation_task try:
except asyncio.CancelledError: await t
pass except asyncio.CancelledError:
pass
scheduler.shutdown(wait=False) scheduler.shutdown(wait=False)