12 KiB
Earth 新闻巡航摘要增强计划
背景
Earth 的新闻巡航模式目前直接消费 /api/v1/news/earth-feed 返回的 items[].summary。这个字段主要来自 RSS/Atom 的 description、summary 或 content,再经过 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"
本地规则摘要
如果新闻源没有摘要,但有 content、description、snippet 或正文片段:
- 清理 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 条。
provider或extractive达到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.source和summary_meta.quality可用于调试。- 现有新闻面板和巡航模式不破坏。
第二阶段:本地 Gemma/Ollama 摘要
- 新增
LocalLLMSummaryProvider。 - 接入 Ollama
/api/generate。 - 添加超时、输入截断、错误退避。
- 增加模型摘要缓存。
- 巡航 payload 后台预热前 N 条摘要。
验收标准:
- Ollama 可用时,低质量摘要能被本地模型增强。
- Ollama 不可用时,新闻接口仍然正常返回。
- 同一新闻不会重复调用模型。
第三阶段:设置与可观测性
- 在设置中增加新闻摘要模式:
关闭省钱模式增强模式仅手动
- 增加本地模型连通性检查。
- 暴露缓存命中率、模型调用次数、失败次数。
- 日志记录摘要来源和失败原因。
验收标准:
- 用户可以不改环境变量就知道本地摘要服务是否可用。
- 管理员能看出成本和失败情况。
第四阶段:云端 LLM 兜底
- 接入现有 AI Provider 或新增 cloud summary provider。
- 增加每日限额与手动增强入口。
- 对云端生成结果落缓存。
验收标准:
- 云端调用可控、可关闭、可限流。
- 云端失败不影响巡航。
风险与防护
| 风险 | 防护 |
|---|---|
| 本地模型生成不存在的事实 | prompt 明确禁止扩写;摘要只作为原文概括;保留来源链接 |
| 本地模型慢导致新闻接口卡住 | 模型摘要异步化;接口先返回已有摘要 |
| 成本失控 | 默认不启用云端;按小时/天限流;缓存命中优先 |
| 摘要语言不一致 | 配置目标语言,默认 zh-CN |
| 新闻源正文不足 | 只概括标题和片段,不强行扩写 |
| 模型服务不可用 | 自动回退,不影响巡航主流程 |
推荐优先级
先做第一阶段和第二阶段的最小闭环:
summary_meta+ 质量判断。- 本地规则摘要。
- Ollama provider。
- 缓存。
- 巡航前 N 条异步预热。
云端 LLM 和设置页可以后置。这样能先验证“摘要缺失比例、本地模型质量、实际延迟”三个关键问题,再决定是否投入更重的 UI 与云端增强。