Files
reader/docs/design/summary-loop-explained.md

230 lines
4.7 KiB
Markdown
Raw Permalink 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.
# Summary Loop 工作说明
## 1. 目的
本文档说明 `scripts/run_summary_loop.py` 如何把以下流程串成一个最小闭环:
`提取 JSON -> LLM 生成摘要 JSON -> validator 校验 -> 失败则修复重试`
这个脚本的目标不是单次调用 LLM,而是让 LLM 输出进入一个“生成后验收”的自动反馈回路。
---
## 2. 核心思路
这个闭环由三部分组成:
- 提取结果
- 来自 `outputs/reference/extracted/*.json` 或 `outputs/freshrss/extracted/*.json`
- 提供标题、链接、正文、质量标记
- LLM 摘要
- 基于提取结果和 prompt 生成 `result.json`
- validator
- 检查 `result.json` 是否符合结构和业务规则
- 不通过则返回错误列表
因此,LLM 不是“自己知道哪里错了”,而是脚本在每轮生成后用 validator 明确指出错误,再要求它修复。
---
## 3. 运行顺序
脚本入口:
- `scripts/run_summary_loop.py`
执行顺序如下:
1. 读取提取结果 JSON
2. 读取摘要 prompt 模板
3. 从提取结果中裁剪出摘要真正需要的输入字段
4. 调用 LLM 生成摘要 JSON
5. 解析模型输出中的 JSON
6. 将结果写入输出文件
7. 调用 validator 校验结果
8. 如果通过,结束
9. 如果失败,构造修复 prompt,再次调用 LLM
---
## 4. 输入来源
脚本主要使用两个输入文件:
- 提取结果:`outputs/reference/extracted/read-flow-2026.extracted.json`
- 摘要 prompt:`outputs/prompts/llm-summary-prompt.txt`
提取结果不会整包无差别塞给 LLM,而是先裁剪成更小的摘要输入:
- `article.title`
- `article.url`
- `article.plain_text`
- `article.quality_flags`
- `warnings`
这样做是为了减少 payload,降低模型超时概率,也让 prompt 更聚焦。
---
## 5. 首次生成
第一次生成时,脚本会把:
- prompt 模板
- 结构化文章输入
拼成一个完整请求,发给 LLM。
对应函数:
- `build_initial_prompt(...)`
- `call_llm(...)`
模型返回后,脚本会尝试提取一个 JSON 对象。
如果模型输出不是合法 JSON:
- 这一轮直接视为失败
- 会生成一份失败校验结果
- 然后进入修复重试
---
## 6. validator 如何加入流程
validator 不是附加步骤,而是主流程里的硬门槛。
对应函数:
- `validate_llm_result(...)`
校验内容包括两层:
### 6.1 结构校验
- 必填字段是否存在
- 字段类型是否正确
- `category` 是否属于允许枚举
- `summary` 长度是否合规
- `highlights` / `keywords` / `topics` 数量是否合规
### 6.2 业务校验
- `keywords` 和 `topics` 不能重复
- 列表内部不能重复
- `title` 和 `url` 必须与提取结果一致
只有 validator 返回 `valid: true`,这一轮结果才会被接受。
---
## 7. LLM 如何知道自己生成错了
LLM 本身不会主动知道哪些字段不合规。
脚本会在校验失败后,把以下信息重新发给 LLM:
- validator 错误列表
- 原始提取结果
- 当前错误的摘要 JSON
然后要求它:
- 只修复错误字段
- 保留已经正确的字段
- 继续只输出合法 JSON
对应函数:
- `build_repair_prompt(...)`
也就是说:
- prompt 负责提出目标
- validator 负责判错
- repair prompt 负责把错误喂回模型
这三者一起形成自动修复回路。
---
## 8. 时序图
```text
Extracted JSON
|
v
run_summary_loop.py
|
|-- 读取 prompt 模板
|-- 构造摘要输入
|
v
LLM
|
v
摘要 JSON
|
v
validator
|
|-- valid = true ------> 结束
|
|-- valid = false
| |
| |-- errors
| v
| repair prompt
| |
+------> LLM 重试
```
---
## 9. 生成的中间文件
每次执行脚本都会在输出目录下留下调试痕迹:
- `outputs/reference/summary/result.loop.attempt-1.raw.txt`
- 模型原始输出
- `outputs/reference/summary/result.loop.attempt-1.json`
- 提取出的 JSON 结果
- `outputs/reference/summary/result.loop.attempt-1.validation.json`
- 这一轮的校验报告
- `outputs/reference/summary/result.loop.json`
- 当前最终结果
这些文件的价值是:
- 可以回看模型原始输出
- 可以定位是 JSON 解析失败还是 schema 校验失败
- 可以看每轮修复到底修了什么
---
## 10. 当前脚本参数
当前脚本支持:
- `--extracted`
- `--prompt`
- `--output`
- `--max-retries`
- `--timeout`
- `--api-key`
- `--model`
- `--api-url`
这意味着它既可以走环境变量,也可以直接用命令行传入模型配置。
---
## 11. 一句话结论
`run_summary_loop.py` 的本质不是“调一次 LLM”,而是“让 LLM 生成结果后必须经过 validator 验收,失败就拿着错误清单继续修”,直到结果通过或达到重试上限。