【免费下载链接】daily-paper-reader
开源的每日 arXiv / OpenReview 论文推荐与 AI 阅读平台,支持 GitHub Actions 自动更新、GitHub Pages 部署与论文问答。
📚 开源项目Daily Paper Reader是一个每日自动推荐 arXiv / OpenReview 论文并生成 AI 阅读笔记的平台,它的整个「论文精读、日报生成」链路都依赖 LLM 返回严格结构化的 JSON。这篇文章带你拆解它在 src/llm.py 中实现的结构化输出三级回退与截断 JSON 自动修复机制——即使模型「不听话」,流水线也不会翻车。
一、为什么让 LLM 输出 JSON 总是容易翻车?
Daily Paper Reader 的前后端整体链路:LLM Refine 环节要求模型稳定输出 JSON
在批量论文筛选场景下,模型输出 JSON 时常见的「翻车」姿势有 4 种:
| 翻车场景 | 表现 | 直接后果 |
|---|---|---|
API 不支持response_format | 返回 400 报错 | 整个批次任务失败 |
| 模型套了 Markdown 代码块 | json {...} | json.loads直接炸掉 |
输出被max_tokens截断 | JSON 缺右括号、字符串没闭合 | 解析失败,且重试仍会截断 |
| 多吐/漏字段 | 多出extra字段或漏了required字段 | 下游取键报 KeyError |
Daily Paper Reader 的做法是:不信任任何单一手段,而是层层设防。核心入口是 src/llm.py 中的chat_structured()方法,配合 src/4.llm_refine_papers.py 的业务层恢复逻辑,形成了「格式回退 → 解析修复 → 业务重试/拆批」三道防线。
二、第一道防线:三级格式回退链(json_schema → json_object → prompt_only)
chat_structured()会先构造一条「格式尝试队列」,由 _structured_response_format_names() 决定:
# 默认回退链(DeepSeek 稳定入口是 json_object) ["json_object", "prompt_only"] # 环境变量 DPR_LLM_STRUCTURED_FORMAT=json_schema 时 ["json_schema", "json_object", "prompt_only"]json_schema:最严格的受控输出,直接传 schema;json_object:只保证「输出是合法 JSON 对象」,DeepSeek 官方稳定的 JSON 模式入口;prompt_only:兜底方案——不发response_format,改在 src/llm.py 的_ensure_json_instruction()中自动注入一条 system 提示:"Output valid JSON only. Do not output Markdown, code fences, or explanatory text."
关键在回退的触发判断:_is_structured_output_unsupported_error() 会检查 400/404/415/422 错误中是否包含response_format、not supported、must be one of等信号。命中就打印[INFO] Structured Outputs 不受支持,回退到 ...然后换下一级格式重试,而不是让整个任务报错退出。
💡 想强制某种格式?设置环境变量
DPR_LLM_STRUCTURED_FORMAT(或LLM_STRUCTURED_FORMAT)为json_schema/json_object/prompt_only即可,见 tests/test_llm_structured_output.py 中的回退用例。
三、第二道防线:脏 JSON 解析 + 截断自动修复
就算模型「声称」只输出 JSON,实际返回仍可能带代码块、多余文字甚至半截 JSON。parse_json_content() 按以下顺序抢救:
- 剥壳:_strip_json_wrappers() 用正则去掉
```json/```围栏; - 多候选切片:分别尝试「从第一个
{到结尾」「从第一个{到最后一个}」、数组版本以及原文,逐个用JSONDecoder.raw_decode试解析; - 截断修复:某个候选解析失败时,调用 _repair_json_suffix()——这是整个机制里最巧的一招。
_repair_json_suffix()的原理其实像一个「迷你括号配对器」:逐字符扫描文本,跟踪当前是否在字符串内(含转义符),用栈记录未闭合的{和[,扫描结束后:
- 若字符串没闭合 → 补一个
"; - 若栈里还有未闭合括号 → 按逆序补上
}/]; - 最后用正则
re.sub(r",(\s*[}\]])", r"\1", ...)清掉},],这类尾逗号(这是截断 JSON 最常见的致命伤)。
也就是说,哪怕模型输出被max_tokens拦腰截断成{"papers": [{"id": "1706.03762", "title": "Attention...,它也能被自动补全成可解析的合法 JSON。🛠️
解析成功后还有一层本地 schema 校验:_validate_json_schema_subset() 轻量检查type、enum、required缺失、additionalProperties: false时的多余字段,递归覆盖对象和数组。校验不通过且还有回退档位可用时,会打印[INFO] json_object 返回内容未通过 JSON 校验,回退到 prompt_only再试一次。最终返回值里带着parsed、parse_error、response_format_used三个字段,业务层可以清晰知道「数据是否可信、用的哪级格式」。
四、第三道防线:业务层重试 + 截断批次二分拆分
格式和解析都救不回来时,src/4.llm_refine_papers.py 的批量论文筛选环节还有一层「业务级容灾」:
- 截断识别:响应
finish_reason == "length"会抛出专门的 FilterOutputTruncatedError(src/4.llm_refine_papers.py); - 带上下文重试:recover_filter_results() 最多重试 3 次(
MAX_FILTER_RETRIES = 3),每次通过 build_filter_retry_note() 把上次的具体错误(如missing ids=p-2)写进 prompt 让模型「纠错重答」; - 二分拆批:若是截断错误且批次内论文数 > 1,立即中断重试,把批次对半拆分递归处理(
split recovery)——因为输出越长越容易截断,拆小批次是从根上消除问题; - 现场留证:失败批次的原始输出会存到 debug 目录的
filter_raw_*.txt,方便排查(src/4.llm_refine_papers.py)。
这套重试/拆批逻辑有完整的单元测试覆盖:tests/test_llm_refine_recovery.py 验证了「缺 id 时带错误说明重试」和「重试耗尽后自动二分批次」两种恢复路径。
五、最终效果:结构化摘要直接写进你的笔记
这套机制的下游收益非常直观——LLM Refine 环节返回的tldr_cn、motivation_cn、method_cn、result_cn、conclusion_cn等结构化字段,最终会渲染成论文页摘要和 Zotero 笔记:
结构化 JSON 字段(TLDR、motivation/method/result/conclusion)稳定落到 Zotero 笔记
在前端面板中,你也可以看到这些由 LLM 结构化输出支撑的标签、评分与中文 evidence:
Daily Paper Reader 后台面板:论文评分、标签与中文摘要均来自稳定解析的 JSON 字段
六、如果你想在自己的项目里复用这套思路
不用照抄代码,记住这 4 条「稳定 JSON 输出」的黄金法则即可:
- 格式请求要做降级链:
json_schema → json_object → prompt 约束,识别「不支持」类错误自动换档,而不是直接失败; - 解析永远多备几个切片候选:剥 Markdown 围栏、截取首个
{到末个},比裸调json.loads健壮得多; - 补括号 + 去尾逗号是截断 JSON 性价比最高的修复手段,二十行代码就能实现;
- 截断要专门识别(
finish_reason: length),并优先用「拆小批次」而非盲目重试。
🔍 想深入阅读源码,可以从这几个文件入手:
- 核心实现:src/llm.py(
chat_structured、parse_json_content、_repair_json_suffix) - 业务重试与拆批:src/4.llm_refine_papers.py
- 格式回退测试:tests/test_llm_structured_output.py
- 截断恢复测试:tests/test_llm_refine_recovery.py
- 文档生成调用示例:src/6.generate_docs.py
掌握这套「三级回退 + 截断自修复」机制,你调 LLM 输出 JSON 时,翻车的概率会大幅下降。🚀
【免费下载链接】daily-paper-reader
开源的每日 arXiv / OpenReview 论文推荐与 AI 阅读平台,支持 GitHub Actions 自动更新、GitHub Pages 部署与论文问答。
相关推荐
its-trip 提示词工程拆解:JSON Schema 硬约束 + 自动修复,如何逼 LLM 稳定输出结构化结果?
its trip 提示词工程拆解:JSON Schema 硬约束 + 自动修复,如何逼 LLM 稳定输出结构化结果? its trip 是 CANN 交通大模型
人工智能大模型AI 应用AI Agent多智能体本地部署智慧城市CANNAscendopenEuler如何让LLM稳定输出JSON?ocrbase的JSON修复与候选解析策略源码全解
如何让LLM稳定输出JSON?ocrbase的JSON修复与候选解析策略源码全解 在调用 LLM 做结构化数据提取时,"输出不稳定、JSON 解析失败"几乎是每
如何用 Web-Dev-For-Beginners 的 LangChain 结构输出让 LLM 返回可解析的 JSON
如何用 Web Dev For Beginners 的 LangChain 结构输出让 LLM 返回可解析的 JSON 当你用 Web Dev For Begi
文档教程教育
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考