Files
intl_news/docs/llm-cache-optimization.md
T
simon c1ab751d95 feat: 日报改为每日一次 + 新增 LLM 使用/成本/缓存文档
- 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 增加上述三个文档索引
2026-09-04 10:38:33 +08:00

103 lines
5.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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)