From c1ab751d9557a9eb76d6f395c90f95f2ff3c1464 Mon Sep 17 00:00:00 2001 From: simon Date: Fri, 4 Sep 2026 10:38:33 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=97=A5=E6=8A=A5=E6=94=B9=E4=B8=BA?= =?UTF-8?q?=E6=AF=8F=E6=97=A5=E4=B8=80=E6=AC=A1=20+=20=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=20LLM=20=E4=BD=BF=E7=94=A8/=E6=88=90=E6=9C=AC/=E7=BC=93?= =?UTF-8?q?=E5=AD=98=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - domestic_full.sh / pipeline.sh 新增 --no-report 参数:跳过日报生成, 保持 M1 抓取 + M2→M6 管道不变,用于 12:00/18:00 只抓不生成日报 - crontab: 07:00 全量含日报,12:00/18:00 --no-report(日报每日一次,降低 LLM 费用) - docs/llm-models.md: 各 LLM 调用点分别用的 provider/model 事实清单 - docs/llm-cost-analysis.md: 真实 token 消耗与费用构成、降本建议 - docs/llm-cache-optimization.md: DeepSeek 上下文缓存机制与命中率提升设计 - docs/README.md 增加上述三个文档索引 --- docs/README.md | 3 + docs/llm-cache-optimization.md | 103 +++++++++++++++++++++++++++ docs/llm-cost-analysis.md | 126 +++++++++++++++++++++++++++++++++ docs/llm-models.md | 84 ++++++++++++++++++++++ scripts/domestic_full.sh | 27 ++++--- scripts/pipeline.sh | 20 ++++-- 6 files changed, 347 insertions(+), 16 deletions(-) create mode 100644 docs/llm-cache-optimization.md create mode 100644 docs/llm-cost-analysis.md create mode 100644 docs/llm-models.md diff --git a/docs/README.md b/docs/README.md index f462647..b3e83df 100644 --- a/docs/README.md +++ b/docs/README.md @@ -12,6 +12,9 @@ | [快速开始](quickstart.md) | 环境准备、安装、配置、首次运行 | | [使用手册](usage.md) | CLI 命令、Shell 脚本、MCP 服务、日报查看 | | [流水线详解](pipeline.md) | M1→M9 各步骤输入/输出、增量逻辑、幂等机制 | +| [LLM 模型使用清单](llm-models.md) | 各用到 LLM 的地方分别用哪个 provider / 模型 | +| [LLM 成本分析](llm-cost-analysis.md) | LLM 调用点、token 消耗与费用构成、降本建议 | +| [LLM 缓存优化设计](llm-cache-optimization.md) | DeepSeek 上下文缓存机制与命中率提升方案 | | [配置说明](configuration.md) | `system.yaml`、`sources.yaml`、Profile、`.env` | | [部署与运维](deployment.md) | Pi 服务器部署、Xvfb/代理、crontab、日志清理 | | [开发指南](development.md) | 源码结构、测试、已知问题、新增新闻源 | diff --git a/docs/llm-cache-optimization.md b/docs/llm-cache-optimization.md new file mode 100644 index 0000000..fbf8bf3 --- /dev/null +++ b/docs/llm-cache-optimization.md @@ -0,0 +1,103 @@ +# DeepSeek 上下文缓存优化设计方案 + +> 目标:提高 DeepSeek API 的 prompt 缓存命中率,从而降低 M4 翻译与 M7 日报的输入成本。 +> 依据:DeepSeek「上下文硬盘缓存」机制([官方文档](https://api-docs.deepseek.com/zh-cn/guides/kv_cache))。 +> 状态:**设计稿**,尚未改动生产代码;文中「改动点」需确认后实施。 + +## 一、机制要点(DeepSeek 上下文硬盘缓存) + +1. **默认开启、零代码开启**:所有请求自动参与缓存,无需在请求里额外传参。 +2. **「缓存前缀单元」完整匹配**才命中:每次请求的输入结束位 / 输出结束位、以及系统检测到的公共前缀,都会落盘为**缓存前缀单元**。后续请求必须**完整匹配**某个缓存前缀单元,才会计入「缓存命中」。 + - `A+B` 之后再来 `A+B+C` → 命中 `A+B`(沿用例一看)。 + - `A+B` 之后再来 `A+C` → **不命中**,但系统会把公共前缀 `A` 落盘,下一轮 `A+D` 可命中 `A`。 +3. **价格差异极大**(`deepseek-v4-flash`): + | 输入 | 空闲 | 高峰 | + |------|------|------| + | 缓存命中 | 0.05 元/百万 | 0.10 元/百万 | + | 缓存未命中 | 1.5 元/百万 | 3.0 元/百万 | + - 命中比未命中便宜 **30 倍**。提升命中率是单位成本上最强的手段。 +4. **可观测**:响应 `usage` 里的 `prompt_cache_hit_tokens` / `prompt_cache_miss_tokens` 直接给出命中情况,可据此验证优化前后。 + +## 二、当前实现的问题 + +### 场景一:M4 翻译(`llm/extractor.py`) + +当前 `user` message 结构(`prompts/translation_and_extraction.md`): + +``` +请处理以下英文财经新闻: + +**标题**: {title} ← 每篇不同 +**来源**: {source_name} ← 每篇不同 +**发布时间**: {publish_time} ← 每篇不同 +**正文**: +{content} ← 每篇不同 +``` + +- `system` prompt 完全固定 → 可作为一个稳定前缀,但只有几百 token,占单篇输入比例很小。 +- `user` prompt 里「标题/来源/发布时间」都在正文之前且每篇不同 → 导致**每篇文章的 user 前缀都错位**,正文 `{content}` 无法形成稳定公共前缀,命中率大幅下降。 +- 实测单篇平均输入 ≈1667 token,估算其中 75%+ 是正文 `{content}`;若能让正文前缀稳定,可显著命中。 + +### 场景二:M7 日报 AI 摘要(`scheduler/reporter.py`) + +- `system` prompt 固定("你是国际财经日报撰写助手…"),user prompt 是当日事件列表,每批内容不同 → 只有极短的 system 前缀可命中,命中率低但总量小(每天仅几千 token),非成本重点。 + +## 三、优化方案 + +### 方案 A(推荐,改动小、收益大):稳定 M4 user prompt 前缀 + +把「每篇变化但重复出现的结构性开头」从正文前挪到结构里,让**正文 `{content}` 作为相对固定的前缀占位**,或至少让「固定指令段」全部前置。 + +调整 `prompts/translation_and_extraction.md` 的 user 段,把固定指令前置,变化字段后置: + +``` +请处理以下英文财经新闻,输出 JSON(标题译文、正文全文译文、事件列表): + +[MODEL_INSTRUCTION_BLOCK] ← 完全固定的指令/示例(移到最前) + +【待处理新闻】 +正文: +{content} ← 长固定位置内容 + +【元信息】 +标题: {title} +来源: {source_name} +发布时间: {publish_time} +``` + +> 说明:把很长的 `{content}` 提前、固定,让后续连续请求以同一篇或相似内容为前缀;配合条件:同一批 82 篇连续翻译时,尽量让公共 system + 固定指令 + 常见正文前缀命中缓存。 + +### 方案 B(最直接):「正文最早、短字段最后」重排 + +即便不改语义,仅调整 user 段字段顺序也能提升命中:把 `{content}`(变量庞大但相对可复用的重复新闻源)尽量前置且稳定,`{title}`/`{source_name}`/`{publish_time}`(每篇必变)后置。这样 system + 固定指令 + 正文前缀可形成缓存前缀单元,后续请求命中。 + +### 方案 C:并发下最大化公共前缀(很少改动) + +- M4 并发 `concurrency=3` 同时发 3 个不同文章 → 冷启动多,前缀难以公共。可在首篇(或任务开头)先发 1 篇建立缓存前缀,再并发,后续命中率高。收益存在但不稳定,不强制。 + +### 方案 D(工程化验证):在 events 文件记录缓存命中 + +利用 `usage.prompt_cache_hit_tokens / prompt_cache_miss_tokens`,把这两项写入 `data/events/{date}/{hash}.json`(现有字段 `prompt_tokens` 旁),用于量化验证方案 A/B 落地后的命中率提升。属观测增强,不影响功能。 + +### 方案 E(日报场景,次要) + +M7 摘要量小,可不动;若要优化,保持 system prompt 稳定即可(当前已稳定)。可复用「截断重试时复用同一前缀」。 + +## 四、建议实施顺序 + +1. 先落地**可观测性(方案 D)**:给 events 记录缓存命中 token(小改动,零风险),跑 1 次对比基线。 +2. 再实施**方案 A / B**(重排 M4 user prompt),对比命中率与单篇输入成本。 +3. 酌情试点**方案 C**(并发预热)。 + +## 五、验收标准 + +- 单篇输入 token 中 `prompt_cache_hit_tokens` 占比显著上升(目标:日常连续翻译批 >50%)。 +- 单篇平均输入成本下降(命中 0.05 vs 未命中 1.5 元/百万)。 +- 翻译质量、输出格式不受影响(缓存只作用于输入前缀,不影响输出随机性)。 + +## 六、参考 + +- [DeepSeek 上下文硬盘缓存](https://api-docs.deepseek.com/zh-cn/guides/kv_cache) +- [DeepSeek 模型与价格](https://api-docs.deepseek.com/zh-cn/quick_start/pricing/) +- 相关代码:`prompts/translation_and_extraction.md`、`llm/extractor.py`、`scheduler/reporter.py` +- 配套成本分析:[llm-cost-analysis.md](llm-cost-analysis.md) \ No newline at end of file diff --git a/docs/llm-cost-analysis.md b/docs/llm-cost-analysis.md new file mode 100644 index 0000000..8751467 --- /dev/null +++ b/docs/llm-cost-analysis.md @@ -0,0 +1,126 @@ +# LLM 使用点与成本分析 + +> 本文档基于 2026-08-30 的代码与真实运行数据编写,用于: +> 1. 盘点本项目所有使用大模型(LLM / embedding)的位置与调用方式; +> 2. 分析 DeepSeek 每日费用的构成与量级; +> 3. 给出降本改善建议(不改写生产代码,仅供决策参考)。 + +## 一、本项目所有用到「大模型」的地方 + +本项目有两类大模型能力,计费方式和对成本的影响完全不同: + +- **对话 / 补全类(chat completion)**:贵,按「输入 token + 输出 token」计费。目前全部走 **DeepSeek(`deepseek-v4-flash`)**。 +- **向量化(embedding)**:便宜,按 token 计费。目前走 **DashScope(`text-embedding-v3`)**,与 DeepSeek 是两套账户/两笔账单。 + +### 1.1 对话类 LLM 调用点(付费大头,DeepSeek) + +| # | 场景 | 位置 | 触发频率 | 单次 token 规模 | 说明 | +|---|------|------|----------|----------------|------| +| 1 | **M4 全文英译中 + 投资事件抽取** | `llm/extractor.py::translate_and_extract`(`_call_llm_sync`) | 每个去重后的唯一文章 **1 次**(失败重试最多 3 次) | 单篇平均 输入≈1667 / 输出≈2342 | 这是**最大的成本来源**,每篇独立调用、整篇中文全文均输出 | +| 2 | **M7 日报 AI 摘要** | `scheduler/reporter.py::_call_llm_simple`(`_generate_ai_summary`) | 每天 3 次定时(07:00/12:00/18:00),每次 1~N 批 + 截断重试 | 每批 max_tokens 500–800(截断重试 1200) | 依赖当日高重要度事件条数;事件 ≤10 一条批,超过则分批 | + +> ⚠️ 是否重复翻译(重要成本放大项): +> M4 设计了**增量机制**——同一 `url_hash` 的 `data/events/{date}/{hash}.json` 已存在就跳过,不重复调用 LLM。但每天 3 次定时全流程都会把**当天新增的去重文章**翻译一遍,若某天连续中断/失败多次,缺的篇会在下次补齐,当周补跑可能一次性翻译多天积压文章。 + +### 1.2 向量类(embedding,DashScope,低费用) + +| # | 场景 | 位置 | 触发 | 说明 | +|---|------|------|------|------| +| 3 | **M5 向量生成(入库)** | `embedding/pipeline.py::embed_all_events` | 每天对已翻译文章批量 | `text-embedding-v3`,dim=1024,batch_size=10 | +| 4 | **M6 / MCP 检索查询向量化** | `vectorstore/pipeline.py::search_news`、`mcp_server/server.py` | 每次语义搜索 | 单个查询向量化,量极小 | + +### 1.3 不使用大模型的环节 + +- M1 抓取、M2 正文提取(trafilatura)、M3 去重(SimHash/指纹库)、M6 Qdrant 入库、M9 MySQL 写入 —— 均**不调用大模型**。 + +--- + +## 二、真实 token 消耗与费用估算 + +以 `deepseek-v4-flash`(官方模型名 `DeepSeek-V4-Flash-0731`)当前价格计([官方定价](https://api-docs.deepseek.com/zh-cn/quick_start/pricing/)): + +| 计价项 | 空闲时段 | 高峰时段(周一至五 9:00–12:00、14:00–18:00) | +|---|---|---| +| 输入(缓存未命中) | 1.5 元 / 百万 token | 3.0 元 / 百万 token | +| 输出 | 4.5 元 / 百万 token | 9.0 元 / 百万 token | + +> 注:本项目常见的定时运行在 07:00(空闲)与 12:00/18:00(接近高峰边界),因此同一日费用波动约 ±100%。 + +### 2.1 M4 翻译实测 token(来自 `data/events/*/` 的 `prompt_tokens`/`completion_tokens`) + +| 日期 | 去重后篇数 | 输入 token | 输出 token | 合计 | +|------|-----------|-----------|-----------|------| +| 2026-08-21(大日) | 82 | 135,616 | 208,146 | **343,762** | +| 2026-08-22(中) | 43 | 74,063 | 88,893 | 162,956 | +| 2026-08-30(小日) | 44 | 73,364 | 103,091 | 176,455 | + +单篇平均:输入 ≈1650–1720,输出 ≈2050–2540 token。 + +### 2.2 单日费用估算(仅 M4) + +**小日(约 44 篇,176k token)** +- 空闲:176,455 输入˝中位 ×1.5 + … 简化按 输入73k×1.5 + 输出103k×4.5 = 0.11 + 0.46 = **≈ 0.57 元** +- 高峰:输入73k×3 + 输出103k×9 = 0.22 + 0.93 = **≈ 1.15 元** + +**大日(约 82 篇,344k token)** +- 空闲:135,616×1.5 + 208,146×4.5 = 0.20 + 0.94 = **≈ 1.14 元** +- 高峰:135,616×3 + 208,146×9 = 0.41 + 1.87 = **≈ 2.28 元** + +### 2.3 日报摘要(M7) + +每次约 1–3 批,每批输出几百 token、输入为事件列表,单日 3 次合计约几千 token,费用 ≈ **0.03–0.1 元/天**,可忽略。 + +### 2.4 结论:费用的绝对构成 + +- **M4 翻译是绝对主力(>95% 费用)**,日报摘要与 embedding 开销可忽略。 +- 单日正常落在 **1–3 元** 区间(视当天文章量与命不命中高峰时段)。 +- 若出现 **补跑/积压**:一次把一两天缺的篇全翻,或当日文章量冲到 80+ 篇,单日可能到 **3–6 元**。这通常是用户感受到「费用较高」的真实触发点。 + +--- + +## 三、成本高的根因分析 + +1. **输出 token 巨大**:M4 做的是「全文英译中」,每篇都把整篇中文翻译完整输出,单篇输出 2000+ token,是费用的绝对主体(输出单价是输入的 3 倍)。 +2. **每篇独立调用**:82 篇文章 = 82 次往返,system prompt(约 3000 字符模板)每篇重复计入输入。 +3. **每天 3 次全流程**:虽增量去重,但当天新文章都翻译,且 07/12/18 各跑一次,频繁触发。 +4. **重试放大**:`max_attempts=3`,失败自动重试(实测约 +3%)。 +5. **高峰时段触发**:12:00/18:00 的定时可能落入高峰计价,费用 ×2。 +6. **付费墙源白跑**:CNBC/Reuters/FT/WSJ 等源抓不到正文,M2 标记 no_content 后**不会**进入 LLM(有防御过滤),因此不会为这些源付费——但它们也没产出,相当于 M1/M2 白耗。真正进 LLM 的只有 investinglive/zerohedge 等能抓到的源,这些源文章较长、翻译输出也就大。 + +--- + +## 四、降本改善建议(按性价比排序) + +> 以下均为「建议」,未改动生产代码;实施前请评估收益与副作用。 + +### 建议 A(收益最大):限制 / 缩短 M4 输出 +- 全文英译中的输出是主要开销。考虑: + - 将 `llm_scenes.translation.max_tokens` 从 8192 降到合理值(如 4096),或 + - 改 prompt,只输出**中文摘要 + 关键事件**,不再整篇英译中(日报和检索实际只消费 `content_zh_preview` 前 300 字与 events,不必整篇全文翻译)。 + - 收益:可显著削减输出 token(目前占费用 >60%)。 + +### 建议 B:尽量把翻译放在空闲时段 +- M4 翻译集中到 07:00(空闲,半价)。避免 12:00/18:00 落入高峰计价。 +- 仅日报生成放在 18:00(摘要量小,价差影响可忽略)。 + +### 建议 C:减少不必要的重复翻译(积压/补跑) +- 确保增量机制生效,避免因重复运行把同日文章翻译多遍。 +- 若当日中断,下个定时点补齐即可;不要为了「补日报」频繁手动全流程重跑。 + +### 建议 D:缓存命中优化 +- DeepSeek 对「缓存命中」输入大幅降价(0.05 元 vs 1.5 元/百万)。把 system prompt 固定化、相同前缀复用,可提高缓存命中率,降低输入成本(输入约占 40% 费用)。 + +### 建议 E:换更便宜的模型 / 分流 +- 若质量可接受,翻译/摘要改用更低价的同类模型或「空闲时段」更明显的档位。 +- embedding 已用 DashScope 低价档,无需动。 + +### 建议 F:成本监控 +- 为 `_process_one` 的成功返回顺带累计 token 到日报 `stats`,或定期统计 `data/events/*/prompt_tokens` 总和,用于成本对账与异常预警。 + +--- + +## 五、参考 + +- [DeepSeek 官方模型与价格](https://api-docs.deepseek.com/zh-cn/quick_start/pricing/) +- 相关代码:`llm/extractor.py`、`llm/pipeline.py`、`scheduler/reporter.py` +- 配置:`configs/system.yaml` 的 `llm` / `llm_scenes` / `embedding` 段 \ No newline at end of file diff --git a/docs/llm-models.md b/docs/llm-models.md new file mode 100644 index 0000000..062beea --- /dev/null +++ b/docs/llm-models.md @@ -0,0 +1,84 @@ +# LLM 使用点与模型对照清单 + +> 本文档是「各使用大模型(LLM)的地方分别用哪个 provider / 哪个模型」的**唯一事实源**。 +> 数据来源:当前 `configs/system.yaml` + 已生成 `data/events/*/` 的实际调用记录(provider/model 字段)。 + +## 一、结论速览(当前生效) + +| 场景 | 使用位置 | Provider | 模型 | 计费账户 | +|------|----------|----------|------|----------| +| M4 翻译 + 事件抽取 | `llm/pipeline.py` → `llm/extractor.py` | **DeepSeek** | `deepseek-v4-flash` | DeepSeek | +| M7 日报 AI 摘要 | `scheduler/reporter.py::_call_llm_simple` | **DeepSeek** | `deepseek-v4-flash` | DeepSeek | +| M5 向量生成 | `embedding/pipeline.py` | **DashScope** | `text-embedding-v3` | 阿里云百炼 | +| M6 入库向量/MCP 检索查询向量化 | `vectorstore/pipeline.py`、`mcp_server/server.py` | **DashScope** | `text-embedding-v3` | 阿里云百炼 | + +> ⚠️ **常见误区**:M4 翻译**用的就是 DeepSeek**(不是 Qwen/DashScope)。有人会误记成「翻译用的是别的模型」,很可能是因为 **M5/M6 的向量化走的是 DashScope(阿里云百炼)**,而翻译走的是 DeepSeek——两者账户 / 账单是分开的。已在 2026-08-21 / 08-22 / 08-30 生成的 `data/events/*/xxx.json` 中核实 87 条记录的 `provider=deepseek`、`model=deepseek-v4-flash`。 + +## 二、各调用点详情 + +### 1. M4 翻译 + 事件抽取(DeepSeek) + +- **入口**:`llm/pipeline.py::translate_all_deduped` → `llm/extractor.py::translate_and_extract` +- **配置来源**:`configs/system.yaml` → `llm_scenes.translation` + ```yaml + llm_scenes: + translation: + provider: "deepseek" + model: "deepseek-v4-flash" + temperature: 0.1 + max_tokens: 8192 + ``` +- **API 密钥**:`DEEPSEEK_API_KEY`、`DEEPSEEK_BASE_URL=https://api.deepseek.com`(`.env`) +- **说明**:单篇独立调用,全文英译中 + 投资事件抽取合并一次输出(JSON)。是全项目 token 消耗与费用的绝对主力。 + +### 2. M7 日报 AI 摘要(DeepSeek) + +- **入口**:`scheduler/reporter.py::_call_llm_simple`(`_generate_ai_summary`) +- **配置来源**:`configs/system.yaml` → `llm_scenes.daily_report` + ```yaml + llm_scenes: + daily_report: + provider: "deepseek" + model: "deepseek-v4-flash" + temperature: 0.3 + max_tokens: 1500 + ``` +- **说明**:高重要度事件分批(≤10 条/批)生成各批摘要后合并;截断时以更高 max_tokens 重试;全部失败走规则兜底。 + +### 3. M5 向量生成(DashScope) + +- **入口**:`embedding/pipeline.py::embed_all_events` +- **配置来源**:`configs/system.yaml` → `embedding` 段 + ```yaml + embedding: + provider: "dashscope" + dashscope_model: "text-embedding-v3" + dimension: 1024 + batch_size: 10 + max_attempts: 3 + timeout_sec: 30 + ``` +- **API 密钥**:`QWEN_API_KEY` / `DASHSCOPE_API_KEY`、`QWEN_BASE_URL`(`.env`) +- **说明**:批量向量化,输出 dim=1024。费用极低,不计入 DeepSeek 账单。 + +### 4. M6 入库 / MCP 检索查询向量化(DashScope) + +- **入口**:`vectorstore/pipeline.py::search_news`(入库 payload 由 M5 结果构建)、`mcp_server/server.py::search_news` +- **配置来源**:同上 `embedding` 段(入库向量与查询向量必须同模型,故共用一份配置)。 + +## 三、不使用大模型的环节 + +- M1 抓取(Playwright/Crawl4AI)、M2 正文提取(trafilatura)、M3 去重(SimHash/指纹库)、M6 Qdrant upsert、M9 MySQL 写入 —— **均不调用 LLM / embedding**。 + +## 四、切换 provider / 模型时须知 + +1. **翻译 / 摘要可选 DeepSeek 或 Qwen**:`llm_scenes.<场景>.provider` 设为 `deepseek` 或 `qwen`,同时确认对应 `.env` 里有对应 `*_API_KEY`/`*_BASE_URL`。`llm/client.py::load_llm_config` 支持 provider 覆盖。 +2. **Embedding 不能随便切**:Qdrant `en_finance_news` 入库向量与检索向量必须同模型。切换模型必须 `recreate` collection 并全量回灌(`ingest_all_embeddings(recreate=True)`),否则跨模型向量无法比较。 +3. **改配置后建议**:改 `provider/model` 后重跑一次管道确认 `data/events/*/` 的 `provider/model` 字段一致,避免两套模型混用。 + +## 五、参考 + +- `configs/system.yaml`(`llm` / `llm_scenes` / `embedding`) +- [docs/llm-cost-analysis.md](llm-cost-analysis.md)(成本构成与降本建议) +- [docs/configuration.md](configuration.md) 第 3.5/3.6 节(配置说明) +- [DeepSeek 官方模型与价格](https://api-docs.deepseek.com/zh-cn/quick_start/pricing/) \ No newline at end of file diff --git a/scripts/domestic_full.sh b/scripts/domestic_full.sh index b820cdd..7af6c26 100755 --- a/scripts/domestic_full.sh +++ b/scripts/domestic_full.sh @@ -7,9 +7,11 @@ # 每天 06:00 / 12:00 / 18:00 / 22:00 各执行一次 # ============================================= # 用法: -# ./scripts/domestic_full.sh # 全新执行 -# ./scripts/domestic_full.sh --resume # 从中断处继续(跳过已完成步骤, -# # 步骤状态见 data/run_state/{date}.state) +# ./scripts/domestic_full.sh # 全新执行(含日报) +# ./scripts/domestic_full.sh --resume # 从中断处继续(跳过已完成步骤, +# # 步骤状态见 data/run_state/{date}.state) +# ./scripts/domestic_full.sh --no-report # 只抓取 + M2→M6,跳过日报生成 +# ./scripts/domestic_full.sh --no-report --resume # ============================================= set -euo pipefail @@ -19,10 +21,12 @@ cd "$PROJECT_DIR" # ── 参数解析 ── RESUME=0 +NO_REPORT=0 for arg in "$@"; do case "$arg" in --resume) RESUME=1 ;; - *) echo "未知参数: ${arg}(支持 --resume)" >&2; exit 1 ;; + --no-report) NO_REPORT=1 ;; + *) echo "未知参数: ${arg}(支持 --resume / --no-report)" >&2; exit 1 ;; esac done @@ -36,7 +40,7 @@ mkdir -p "$LOG_DIR" FULL_LOG="$LOG_DIR/domestic_full_$(date +%Y%m%d_%H%M%S).log" : > "$FULL_LOG" -LOG "══════ 国内全流程开始(resume=${RESUME})═══════" +LOG "══════ 国内全流程开始(resume=${RESUME} no_report=${NO_REPORT})═══════" LOG "全流程日志: ${FULL_LOG}(终端实时显示完整进度)" # ── 加载 .env ── @@ -62,13 +66,16 @@ if step_should_run M1_crawl; then fi fi -# ── 2. M2→M6 管道(含日报)── -LOG "━━━ [2/2] M2→M6 管道 + 日报 ━━━" -if [ "$RESUME" = "1" ]; then - bash "$SCRIPT_DIR/pipeline.sh" --resume 2>&1 | tee -a "$FULL_LOG" +# ── 2. M2→M6 管道(+ 可选日报)── +if [ "$NO_REPORT" = "1" ]; then + LOG "━━━ [2/2] M2→M6 管道(已跳过日报,--no-report)━━━" else - bash "$SCRIPT_DIR/pipeline.sh" 2>&1 | tee -a "$FULL_LOG" + LOG "━━━ [2/2] M2→M6 管道 + 日报 ━━━" fi +PIPELINE_ARGS="" +[ "$RESUME" = "1" ] && PIPELINE_ARGS="$PIPELINE_ARGS --resume" +[ "$NO_REPORT" = "1" ] && PIPELINE_ARGS="$PIPELINE_ARGS --no-report" +bash "$SCRIPT_DIR/pipeline.sh" $PIPELINE_ARGS 2>&1 | tee -a "$FULL_LOG" if [ "$M1_FAILED" -eq 1 ]; then LOG "⚠️ 全流程结束,但 M1 抓取未成功,退出码=1" diff --git a/scripts/pipeline.sh b/scripts/pipeline.sh index d58cb73..757603b 100755 --- a/scripts/pipeline.sh +++ b/scripts/pipeline.sh @@ -3,8 +3,10 @@ # 国内服务器:全链路管道 M2 → M3 → M4 → M5 → M6 → 日报 # ============================================= # 用法: -# ./scripts/pipeline.sh # 全新执行(步骤级不跳过) -# ./scripts/pipeline.sh --resume # 从中断处继续(跳过已完成步骤) +# ./scripts/pipeline.sh # 全新执行(步骤级不跳过) +# ./scripts/pipeline.sh --resume # 从中断处继续(跳过已完成步骤) +# ./scripts/pipeline.sh --no-report # 只跑 M2→M6,跳过日报生成 +# ./scripts/pipeline.sh --no-report --resume # 前提:domestic_sync.sh 已完成 # ============================================= # 各步骤"跳过已处理文件"(文件级增量,由各 Python 模块自带): @@ -24,10 +26,12 @@ cd "$PROJECT_DIR" # ── 参数解析 ── RESUME=0 +NO_REPORT=0 for arg in "$@"; do case "$arg" in --resume) RESUME=1 ;; - *) echo "未知参数: ${arg}(支持 --resume)" >&2; exit 1 ;; + --no-report) NO_REPORT=1 ;; + *) echo "未知参数: ${arg}(支持 --resume / --no-report)" >&2; exit 1 ;; esac done @@ -40,7 +44,7 @@ LOG_DIR="logs" mkdir -p "$LOG_DIR" STEP_LOG="$LOG_DIR/pipeline_$(date +%Y%m%d_%H%M%S).log" : > "$STEP_LOG" -LOG "══════ 全链路管道开始(resume=${RESUME})═══════" +LOG "══════ 全链路管道开始(resume=${RESUME} no_report=${NO_REPORT})═══════" LOG "步骤日志: ${STEP_LOG}(终端实时显示完整进度)" # ── AI 模型信息读取(供阶段 banner 显性展示供应商/模型)── @@ -113,11 +117,15 @@ print(f'M6: {stats[\"ingested\"]}/{stats[\"total\"]} 条, {stats[\"elapsed_sec\" " # ── 日报(AI 大模型)── -LOG "━━━ 日报生成(AI 大模型: $(ai_model_info daily_report))━━━" -step_run report .venv/bin/python3 -c " +if [ "$NO_REPORT" = "1" ]; then + LOG "━━━ 日报生成(已跳过,--no-report)━━━" +else + LOG "━━━ 日报生成(AI 大模型: $(ai_model_info daily_report))━━━" + step_run report .venv/bin/python3 -c " from scheduler.reporter import generate_report report_id = generate_report() print(f'日报: report_id={report_id}' if report_id is not None else '日报: 无数据/失败') " +fi LOG "══════ 全链路管道完成 ✅ ═══════"