# Summary Loop 工作说明 ## 1. 目的 本文档说明 `scripts/run_summary_loop.py` 如何把以下流程串成一个最小闭环: `提取 JSON -> LLM 生成摘要 JSON -> validator 校验 -> 失败则修复重试` 这个脚本的目标不是单次调用 LLM,而是让 LLM 输出进入一个“生成后验收”的自动反馈回路。 --- ## 2. 核心思路 这个闭环由三部分组成: - 提取结果 - 来自 `outputs/*.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/read-flow-2026.extracted.json` - 摘要 prompt:`outputs/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. 生成的中间文件 每次执行脚本都会在输出目录下留下调试痕迹: - `result.loop.attempt-1.raw.txt` - 模型原始输出 - `result.loop.attempt-1.json` - 提取出的 JSON 结果 - `result.loop.attempt-1.validation.json` - 这一轮的校验报告 - `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 验收,失败就拿着错误清单继续修”,直到结果通过或达到重试上限。