feat(docparser): 以 Go 进程内方式集成 anydoc 解析引擎,并收敛引擎注册表 - #58
Draft
lyingbug wants to merge 7 commits into
Draft
Conversation
vendored firecrawl/anydoc#30 的 Go 绑定(已 rebase 到 anydoc v0.1.8), 通过 cgo 链接 Rust 静态库,在 Go 侧直接把 docx/pptx/xlsx/odf/rtf/epub/csv/pdf 转成 Markdown,不再依赖 Python docreader,也不调用外部二进制。 默认构建不链接该库(无需 Rust 工具链),引擎在列表中显示为不可用; 使用 make build-anydoc / -tags anydoc 构建即可启用。 Co-authored-by: lyingbug <lyingbug@users.noreply.github.com>
同时为 app CI 增加一个 job:构建 anydoc 静态库并以 -tags anydoc 跑 docparser 测试, 避免 vendored 绑定与构建脚本在无人使用时腐化。 Co-authored-by: lyingbug <lyingbug@users.noreply.github.com>
这两条链路此前把引擎名原样透传给 Python docreader,Go 原生引擎(anydoc、 MinerU 等)实际不会生效。改为统一经 docparser.NewReader 解析,注册表构造不出 Reader 时仍回落到 docreader,保持原有的宽松行为。 Co-authored-by: lyingbug <lyingbug@users.noreply.github.com>
Co-authored-by: lyingbug <lyingbug@users.noreply.github.com>
审计 PR30 实现时发现两个问题: 1. 错误详情由 Rust 侧 thread-local 暂存、再由第二次 cgo 调用读取,而 Go 允许 goroutine 在两次 cgo 调用之间换到别的 OS 线程——并发下会丢失详情,甚至读到 同线程上另一份文档的错误。4000 次并发转换稳定复现(约 1/1500)。改为在整个 ABI 调用期间 LockOSThread,并补回归测试。 2. 解码器按缓冲区里的计数直接预分配(make([]Block, 0, n)),Rust 编码器与 Go 解码器版本错位时会先 OOM 掉进程再谈越界检查。改为按剩余字节数封顶。 同时给图片补上文档模型里的原始 alt 与所在章节,追加的图片引用不再只有文件名。 Co-authored-by: lyingbug <lyingbug@users.noreply.github.com>
Co-authored-by: lyingbug <lyingbug@users.noreply.github.com>
Co-authored-by: lyingbug <lyingbug@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
把 firecrawl/anydoc(Rust 文档转换库)以 Go 进程内库的方式集成为一个新的解析引擎
anydoc:docx/doc/pptx/ppt/xlsx/xls/odf/rtf/epub/csv/pdf 直接在 Go 侧转成 Markdown,不经过 Python docreader,也不 exec 任何二进制。集成对象选择的是 anydoc 整体而不是底层的
pdf-inspector:WeKnora 的短板是 office 格式(builtin甚至不支持 pptx),而 PDF 侧已有 pypdfium2 + OCR 路由,单接pdf-inspector只会重复现有能力并继承其风险。绑定方式采用 firecrawl/anydoc#30(cgo + Rust 静态库)。该 PR 尚未合入、也没有发布
go/vX.Y.Z或归档产物,因此把它 rebase 到上游最新v0.1.8后 vendored 到third_party/anydoc-go/,go.mod用replace指过去;上游发布后删目录、去replace即可,仅一个文件 import 它。关键设计
anydoc构建标签。未启用时backend_stub.go让引擎在引擎列表里显示为「未构建进本二进制」,其余引擎不受影响,go build ./...无需 Rust。Cargo.lock提交进仓库,构建脚本带--locked,CI 跑cargo audit。解析不可信上传的那层依赖必须可复现、有告警。catch_unwind(已实测有效,见下)。对 PR30 的审计与修复
审计发现两个实现问题,已在 vendored 副本中修复(值得回报上游):
anydoc_last_error读取;而 Go 允许 goroutine 在两次 cgo 调用之间迁移到别的 OS 线程。4000 次并发转换稳定复现(约 1/1500 丢失详情,原理上也会读到同线程上另一份文档的错误)。修复:整个 ABI 调用期间runtime.LockOSThread,并加回归测试TestErrorDetailSurvivesConcurrency(去掉修复即失败)。make([]Block, 0, n)的n直接取自缓冲区,Rust 编码器与 Go 解码器版本错位时会先 OOM 掉进程,再谈越界检查。修复:按剩余字节数封顶(capFor),并让need拒绝负长度。其余审计结论:编码器对模型是穷尽匹配(上游改模型会编译失败而非静默丢数据);资产长度前缀 u32 与上游 128 MiB 资产上限相容;40000 次转换 RSS 平稳无泄漏;
-race干净;20000 个变异 office/PDF 文件无崩溃。上游落后提交的影响
main 领先 PR30 六个提交(v0.1.7、v0.1.8 两次发版 + 三个解析器/渲染器修复)。逐个核对后,没有一个需要对绑定增删改:公共 API(
lib.rs导出、model/、ConvertError、Format)零改动,唯一的可见变化是detect::from_bytes降为pub(crate)并把 PDF 嗅探移到容器判定之后(内嵌 PDF 的 docx 现在识别为 docx),绑定通过Format::from_bytes调用不受影响。Node/Python 绑定在这些提交里也只改了版本号。顺带的解析器重构
EngineRegistration,目录集中在新的engines.go,knowledge_process.go里那段字符串switch收敛成一次docparser.NewReader调用。doc.go说明包内分层。Type of Change
Related Issue
无
Testing
单元 / 回归测试:
internal/infrastructure/docparser/anydoc(格式映射、可用性、扩展名与绑定的一致性、真实转换:CSV / 含内嵌图片的 docx / 内容嗅探 / 畸形输入 / PDF、并发错误详情、深度嵌套 PDF 存活)、anydoc_reader_test.go(图片标签与引用组装)、engine_registry_reader_test.go(引擎路由)。CI 新增 job:cargo audit→ 构建静态库 →-tags anydoc跑 docparser 全量测试。安全验证(对照实验):
端到端(真实文件走
docparser.NewReader→AnydocReader→ cgo → Rust):docx 的标题/表格/内嵌图片、pptx 的分页标题与要点、xlsx 表格、仓库内中文合并单元格 docx 均正确;37 KB docx 约 9 ms/篇,pptx 约 1 ms,xlsx 约 0.1 ms。完整日志:
/opt/cursor/artifacts/anydoc_audit.log、/opt/cursor/artifacts/anydoc_end_to_end.log、/opt/cursor/artifacts/anydoc_tests.log。其它已执行:
go vet+go test(全仓库,排除/docreader/)、go test -race -tags anydoc、golangci-lint run --new-from-rev=origin/main ./internal/...(0 issues)、前端npx tsx --test src/i18n/localeKeyAudit.test.ts(11 pass)。Checklist
git diff --check origin/main...HEADpasses已知边界(已写进文档)
builtin/mineru/paddleocr_vl。document_to_markdown是 crate 私有的,外部无法「改写成链接再渲染」。因此图片引用被追加在正文末尾,标签带上文档模型里的原始 alt 与所在章节(),但正文中的精确位置无法恢复。彻底解决需要上游导出渲染函数或提供资产链接选项。