Files
planet/docs/plans/earth-news-cruise-summary-plan.md
2026-04-29 17:27:44 +08:00

12 KiB
Raw Blame History

Earth 新闻巡航摘要增强计划

背景

Earth 的新闻巡航模式目前直接消费 /api/v1/news/earth-feed 返回的 items[].summary。这个字段主要来自 RSS/Atom 的 descriptionsummarycontent,再经过 HTML 清理与长度截断。

这个实现足够轻量,但在巡航展示里有三个问题:

  • 不是所有新闻源都会提供摘要,部分源只返回标题和链接。
  • 聚合源的摘要质量不稳定,可能只是重复标题、来源署名或片段文本。
  • 巡航模式需要更稳定的“态势说明”,否则新闻卡片的 SUMMARY 区域会显得空或信息密度不足。

目标不是把所有新闻都交给大模型,而是建立一个分层摘要管线:能用新闻源自带内容时零成本处理,需要增强时优先用本地模型,云端 LLM 只作为可控兜底。

当前相关实现

文件 作用
backend/app/services/earth_news.py 拉取 RSS/Atom 新闻源、解析标题/摘要、按区域聚合并返回 Earth 新闻 payload
backend/app/api/v1/news.py 暴露 /api/v1/news/earth-feed
frontend/public/earth/js/news.js 拉取新闻 payload 并渲染媒体面板新闻列表
frontend/public/earth/js/news-cruise-adapter.js 将新闻条目映射为巡航事件,并把 summary 传给信息卡
frontend/public/earth/js/info-card.js 展示新闻巡航卡片中的 SUMMARY

当前摘要生成逻辑集中在 earth_news.py

summary = _extract_item_text(node, "description", "content")
clean_summary = _truncate(_strip_html(summary), 180)

这意味着后端还没有区分“摘要来自哪里”“质量是否足够”“是否需要异步增强”。

总体方案

采用四级摘要来源:

优先级 来源 成本 适用情况 风险
1 新闻源自带 summary / description RSS/Atom 已提供可读摘要 字段可能为空或重复标题
2 本地规则提取 有正文片段但没有可靠摘要 只能抽取,不能真正概括
3 本地 Gemma/Ollama 摘要 巡航会展示且前两级质量不足 本地模型质量与机器性能相关
4 云端 LLM 兜底 用户手动增强、重点新闻、失败补偿 成本与网络依赖

推荐默认策略:

provider summary -> extractive summary -> cached local model summary -> async local model summary -> optional cloud LLM

巡航 UI 永远先展示已有摘要,不等待模型调用。模型摘要在后台补齐,写入缓存后下一轮巡航或刷新时使用。

数据结构

后端应把原来的 summary: str 升级为可追踪的摘要元信息,同时为了兼容前端保留顶层 summary 字段。

建议新增结构:

{
  "summary": "短摘要文本",
  "summary_meta": {
    "source": "provider",
    "quality": "good",
    "generated_at": "2026-04-29T00:00:00Z",
    "content_hash": "sha256:...",
    "model": null,
    "language": "zh-CN"
  }
}

字段说明:

字段 可选值 说明
source provider / extractive / local_llm / cloud_llm / fallback 摘要来源
quality good / partial / poor 后端对摘要可用性的判断
generated_at ISO 时间 模型或规则生成时间
content_hash SHA-256 用于缓存命中和判断内容变化
model 字符串或 null 例如 gemma3:4b
language 语言代码 默认 zh-CN,也可跟随新闻语言

前端第一阶段不需要显示 summary_meta,但可以用于后续调试面板或质量标记。

后端设计

NewsSummaryService

新增 backend/app/services/news_summary.py,提供统一入口:

async def resolve_news_summary(item: ParsedNewsItem, *, mode: str) -> NewsSummaryResult:
    ...

核心职责:

  • 标准化新闻输入标题、URL、来源、发布时间、摘要片段、正文片段。
  • 判断 provider summary 是否可用。
  • 生成本地规则摘要。
  • 查询模型摘要缓存。
  • 在允许时调用本地 Ollama/Gemma。
  • 在增强模式或手动触发时调用云端 LLM。
  • 返回摘要文本与 summary_meta

摘要质量判断

第一版可以用轻量规则:

  • 少于 30 个字符:poor
  • 与标题高度重复:partial
  • 包含明显来源署名或聚合噪声:partial
  • 60-180 个字符且不重复标题:good

伪代码:

def score_summary(title: str, summary: str) -> SummaryQuality:
    if len(summary.strip()) < 30:
        return "poor"
    if normalized_overlap(title, summary) > 0.75:
        return "partial"
    if looks_like_source_attribution(summary):
        return "partial"
    return "good"

本地规则摘要

如果新闻源没有摘要,但有 contentdescriptionsnippet 或正文片段:

  • 清理 HTML。
  • 去掉标题重复内容。
  • 去掉来源署名、发布时间、图片说明。
  • 优先取前 1-2 个完整句子。
  • 控制在 80-140 个中文字符或 40-80 个英文词。

本地 Gemma/Ollama Provider

不要在业务里写死 Gemma抽象为 LocalLLMSummaryProvider,默认可以指向 Ollama

NEWS_SUMMARY_PROVIDER=ollama
OLLAMA_BASE_URL=http://localhost:11434
NEWS_SUMMARY_MODEL=gemma3:4b
NEWS_SUMMARY_TIMEOUT_SECONDS=20
NEWS_SUMMARY_MAX_INPUT_CHARS=5000
NEWS_SUMMARY_MAX_PER_HOUR=60

Ollama 请求示例:

POST /api/generate
Content-Type: application/json

{
  "model": "gemma3:4b",
  "prompt": "...",
  "stream": false,
  "options": {
    "temperature": 0.2,
    "num_predict": 180
  }
}

摘要 prompt 要强调“只基于原文”,避免模型补事实:

你是新闻摘要器。只根据输入新闻内容生成摘要,不要添加原文没有的信息。
输出中文1-2 句话80-140 字。
如果原文信息不足,只概括已知事实,不要推测。

标题:{title}
来源:{source}
发布时间:{published_at}
正文或片段:
{content}

缓存

需要缓存模型摘要,避免重复花时间和费用。

缓存 key

sha256(url + title + published_at + normalized_content)

建议新增表或复用系统设置缓存。若要可查询与清理,推荐独立表:

news_summary_cache
- id
- cache_key
- url
- title
- content_hash
- summary
- source
- quality
- provider
- model
- generated_at
- expires_at
- failure_count
- last_error

缓存策略:

  • 同一 cache_key 命中后直接返回。
  • provider / extractive 可以短期缓存。
  • local_llm / cloud_llm 可以长缓存,内容 hash 变化才重算。
  • LLM 失败后记录 failure_count,短时间内不重复调用。

前端与巡航行为

前端第一阶段只需要继续使用 item.summary,不阻塞现有逻辑。

后续可选增强:

  • news.js 在渲染新闻列表时,如果 summary_meta.quality === "poor",可以用更紧凑的标题卡样式。
  • news-cruise-adapter.js 选择巡航项时,可以优先选择 summary_meta.quality !== "poor" 的新闻。
  • info-card.js 不显示“AI 生成中”这类文案,避免把系统内部状态暴露给用户。

如果后端异步生成完成,可以通过下一次 /api/v1/news/earth-feed 刷新自然更新。第一版不需要 WebSocket。

调用策略

默认使用“省钱模式”:

  • 只处理本次 payload 中即将进入巡航队列的前 N 条。
  • providerextractive 达到 good 时不调用模型。
  • 本地模型失败时不影响新闻 payload。
  • 云端 LLM 默认关闭,只允许手动增强或后台配置开启。

推荐限制:

配置 默认值 说明
NEWS_SUMMARY_MODE economy off / economy / enhanced / manual
NEWS_SUMMARY_CRUISE_PREFETCH_LIMIT 10 每次新闻 payload 预热多少条巡航摘要
NEWS_SUMMARY_MAX_PER_HOUR 60 本地模型每小时最多处理数量
NEWS_SUMMARY_CLOUD_MAX_PER_DAY 20 云端 LLM 每天最多处理数量
NEWS_SUMMARY_TIMEOUT_SECONDS 20 单条模型摘要超时
NEWS_SUMMARY_MAX_INPUT_CHARS 5000 输入截断上限

Gemma 本地部署建议

Gemma 适合作为“本地省钱层”,但不应成为强绑定依赖。建议通过 Ollama 接入,未来可切换 Qwen、Llama 或其他本地模型。

开发环境:

ollama pull gemma3:4b
ollama serve

集成原则:

  • 后端只依赖 Ollama HTTP API不直接依赖 Gemma SDK。
  • 模型名称来自配置,不写死在代码里。
  • 健康检查访问 /api/tags 或执行一条极短测试 prompt。
  • 如果 Ollama 不可用,摘要管线自动退回 provider / extractive

中文新闻较多时,需要单独评估 Gemma 与 Qwen 系本地模型的中文摘要质量。不要只看单条效果,至少抽样 50 条新闻比较:

  • 事实准确性
  • 中文自然度
  • 长度稳定性
  • 延迟
  • 是否会补充原文没有的信息

云端 LLM 兜底

云端 LLM 不作为默认路径,只用于:

  • 用户点击“增强摘要”。
  • 管理员开启增强模式。
  • 本地模型连续失败且新闻进入重点巡航队列。

云端结果同样写入 news_summary_cache,并受每日限额控制。

分阶段实施

第一阶段:零成本摘要质量增强

  • earth_news.py 中引入 summary_meta
  • 增加 provider summary 质量判断。
  • 增加本地规则摘要兜底。
  • /api/v1/news/earth-feed 保持兼容,继续返回顶层 summary
  • 前端无需大改。

验收标准:

  • 没有摘要的新闻也能尽量得到短摘要。
  • summary_meta.sourcesummary_meta.quality 可用于调试。
  • 现有新闻面板和巡航模式不破坏。

第二阶段:本地 Gemma/Ollama 摘要

  • 新增 LocalLLMSummaryProvider
  • 接入 Ollama /api/generate
  • 添加超时、输入截断、错误退避。
  • 增加模型摘要缓存。
  • 巡航 payload 后台预热前 N 条摘要。

验收标准:

  • Ollama 可用时,低质量摘要能被本地模型增强。
  • Ollama 不可用时,新闻接口仍然正常返回。
  • 同一新闻不会重复调用模型。

第三阶段:设置与可观测性

  • 在设置中增加新闻摘要模式:
    • 关闭
    • 省钱模式
    • 增强模式
    • 仅手动
  • 增加本地模型连通性检查。
  • 暴露缓存命中率、模型调用次数、失败次数。
  • 日志记录摘要来源和失败原因。

验收标准:

  • 用户可以不改环境变量就知道本地摘要服务是否可用。
  • 管理员能看出成本和失败情况。

第四阶段:云端 LLM 兜底

  • 接入现有 AI Provider 或新增 cloud summary provider。
  • 增加每日限额与手动增强入口。
  • 对云端生成结果落缓存。

验收标准:

  • 云端调用可控、可关闭、可限流。
  • 云端失败不影响巡航。

风险与防护

风险 防护
本地模型生成不存在的事实 prompt 明确禁止扩写;摘要只作为原文概括;保留来源链接
本地模型慢导致新闻接口卡住 模型摘要异步化;接口先返回已有摘要
成本失控 默认不启用云端;按小时/天限流;缓存命中优先
摘要语言不一致 配置目标语言,默认 zh-CN
新闻源正文不足 只概括标题和片段,不强行扩写
模型服务不可用 自动回退,不影响巡航主流程

推荐优先级

先做第一阶段和第二阶段的最小闭环:

  1. summary_meta + 质量判断。
  2. 本地规则摘要。
  3. Ollama provider。
  4. 缓存。
  5. 巡航前 N 条异步预热。

云端 LLM 和设置页可以后置。这样能先验证“摘要缺失比例、本地模型质量、实际延迟”三个关键问题,再决定是否投入更重的 UI 与云端增强。