# 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`: ```python 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 兜底 | 高 | 用户手动增强、重点新闻、失败补偿 | 成本与网络依赖 | 推荐默认策略: ```text provider summary -> extractive summary -> cached local model summary -> async local model summary -> optional cloud LLM ``` 巡航 UI 永远先展示已有摘要,不等待模型调用。模型摘要在后台补齐,写入缓存后下一轮巡航或刷新时使用。 ## 数据结构 后端应把原来的 `summary: str` 升级为可追踪的摘要元信息,同时为了兼容前端保留顶层 `summary` 字段。 建议新增结构: ```json { "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`,提供统一入口: ```python 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` 伪代码: ```python 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: ```env 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 请求示例: ```http POST /api/generate Content-Type: application/json { "model": "gemma3:4b", "prompt": "...", "stream": false, "options": { "temperature": 0.2, "num_predict": 180 } } ``` 摘要 prompt 要强调“只基于原文”,避免模型补事实: ```text 你是新闻摘要器。只根据输入新闻内容生成摘要,不要添加原文没有的信息。 输出中文,1-2 句话,80-140 字。 如果原文信息不足,只概括已知事实,不要推测。 标题:{title} 来源:{source} 发布时间:{published_at} 正文或片段: {content} ``` ### 缓存 需要缓存模型摘要,避免重复花时间和费用。 缓存 key: ```text sha256(url + title + published_at + normalized_content) ``` 建议新增表或复用系统设置缓存。若要可查询与清理,推荐独立表: ```text 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 或其他本地模型。 开发环境: ```bash 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` | | 新闻源正文不足 | 只概括标题和片段,不强行扩写 | | 模型服务不可用 | 自动回退,不影响巡航主流程 | ## 推荐优先级 先做第一阶段和第二阶段的最小闭环: 1. `summary_meta` + 质量判断。 2. 本地规则摘要。 3. Ollama provider。 4. 缓存。 5. 巡航前 N 条异步预热。 云端 LLM 和设置页可以后置。这样能先验证“摘要缺失比例、本地模型质量、实际延迟”三个关键问题,再决定是否投入更重的 UI 与云端增强。