Skip to content

PDF 少于 10 页时 LLMExtract 静默产出空 paper.md 却返回 200 成功(pdftoppm 页码补零宽度不匹配) #93

Description

@bruceding

现象

少于 10 页的 PDF 调用 POST /api/documents/:id/llm-extract,会返回

{"message":"PDF extracted with LLM successfully","pages":1,"total_pages":1}

但产物 paper.md0 字节,且 LLM 一次都没有被调用。没有任何错误日志。

实测(1 页 PDF,走真实 handler,用假 claude 二进制记录是否被 spawn):

HTTP 状态码    : 200
响应体         : {"message":"PDF extracted with LLM successfully","pages":1,"total_pages":1}
LLM 被调用过吗 : false
paper.md 大小  : 0 字节

根因

两个环节对页码文件名的约定不一致。

backend/api/documents.go:417pdftoppm 输出到 <tempDir>/page,而 pdftoppm 的补零宽度是按 PDF 总页数决定的;documents.go:459 却硬编码了两位补零:

pageImg := filepath.Join(tempDir, fmt.Sprintf("page-%02d.png", i))
if _, err := os.Stat(pageImg); os.IsNotExist(err) {
    continue
}

实测阈值精确是 10 页:

总页数 pdftoppm 实际产出 handler 找的 结果
1 page-1.png page-01.png 落空
2 page-1.png page-01.png 落空
9 page-1.png page-01.png 落空
10 page-01.png page-01.png 命中
11 page-01.png page-01.png 命中

为什么是静默失败

os.Stat 未命中就 continue,而且发生在写入任何内容之前(页眉 ## Page N 也在其后),所以循环空转完直接走到成功返回。用户看到"成功提取 N 页",拿到空文件。

为什么一直没被发现

该功能主要用于提取 arXiv 论文,而论文几乎都 ≥10 页,刚好落在能工作的一侧。受影响的是所有短 PDF:单页文档、幻灯片、票据、几页的报告。

建议修法

不要猜补零宽度。pdftoppm 跑完后 glob page-*.png,按数字后缀建 map[int]string,再按页号取。比现在的 Sprintf 更短也更稳。

并补一条回归测试:1 页 PDF 必须真的调用 LLM 且产出非空 paper.md。现有测试用 pdfunite 把单页样本拼成 10 页来绕开这个 bug(见 backend/api/documents_llmextract_test.go 的注释),修好后那段拼接可以删掉。

发现经过

feature/pi-backend-switch 分支的 Plan 2 Task 11 中,为 PDF 逐页转换路径补测试时发现。不属于该计划的改动范围,且修它会改变 claude 路径的可观测行为(与该计划"claude 行为逐条等价"的验收项冲突),故单独开 issue。

另一个相邻问题(本 issue 不处理)

documents.go:410tempDir/tmp/pdf_pages_<userId>_<docID> 这种固定路径,同一用户对同一文档并发请求会互相覆盖对方的 PNG。建议一并评估,但属独立问题。

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions