DocumentIR、Issue IR 与 CodeIR 为什么必须分流

文档、工单和代码拥有不同结构;先建立 Typed IR,才能正确分块、构图并保留可追溯位置。

本页目录 · 10 节

把 HTML、Jira JSON、PDF 和 TypeScript 全部转成一段 Markdown,确实可以很快接上 Embedding。但这种统一只发生在字符串层:表格的行列、工单的状态变化、代码的 Symbol、调用边和 Commit 都被压平了。后续模型能找到相似词,却无法可靠回答「哪一行调用了谁」「这句话来自评论还是需求正文」。

mt_rag 的选择是统一 Provenance Contract,同时为不同来源保留专门的 Typed IR。

本篇知识地图

flowchart TB
  R["Raw + CAS Ref"] --> D{"Parser Router"}
  D --> H["DocumentIR<br/>heading / paragraph / table / image"]
  D --> J["Issue IR<br/>description / comment / changelog / worklog"]
  D --> C["Code CPG<br/>AST / Symbol / Call / CFG / DFG"]
  H --> HC["Block-aware Chunks"]
  J --> JC["Issue-aware Chunks"]
  C --> CC["Symbol-aware Chunks + Semantic CPG"]
  HC --> P["Shared Chunk / Evidence Contract"]
  JC --> P
  CC --> P

知识分布可以概括为:Document Structure、Source-specific Semantics、Code Analysis、Structure-aware Chunking 和 Parser Failure Lineage。

统一的是 Block Contract,不是 Parser

DocumentIRV1 的基础单位是有序 DocumentBlockV1

block_id / kind / ordinal / text
locator / section_path / links / asset_ref / metadata

kind 可以是 Heading、Paragraph、List Item、Table、Code、Image、Page、Sheet、Slide、Note 或 Error。Block Ordinal 必须从 0 连续递增,Locator 不能为空。这样每个下游 Chunk 都能说明自己来自哪一个页面、字符范围、表格、Comment 或代码行,而不是只引用一段无法定位的文本。

HTML:去掉页面 Chrome,保留语义边界

当前 HTML Parser 会丢弃 scriptstylenavheaderfooteraside、隐藏区域等非正文内容;保留 H1–H6 层级、段落、列表、代码、表格、链接和图片引用。

每个 Block 记录 char_startchar_end 和原始 HTML Tag。Heading 更新 section_path;Table 按 Row/Cell 组织,而不是把所有 Cell 无边界连接;Image 保存 Asset Ref 与 Alt。页面完全有内容却解析不到 Semantic Block 时,IR 写入 no_semantic_blocks Warning,而不是返回一个看似成功的空文档。

这仍然不是浏览器级 DOM/布局还原。复杂宏、动态内容和 Confluence 特有组件可能需要专用 Adapter,但当前 Contract 已能把「解析不到」和「页面为空」区分开。

Jira:正文、评论和状态变化不能混成一个段落

Jira Parser 读取 Atlassian Document Format,并把 Summary、Metadata、Description、Comments、Changelog、Worklogs 和 Remote Links 放在不同 Section。Comment Block 带 Comment ID、Author、Created/Updated Timestamp;Changelog 带 Field、From/To 和 Changed At;Worklog 保留 Author 与 Started。

因此,检索到「字段从 A 改为 B」时,系统知道它来自 Changelog,而不是需求正文。回答可以说「工单状态曾变化」,不会把它误写成系统业务规则。

多模态:Native、OCR 与 Unavailable 都是可观察状态

附件由 Media Type 路由:

  • PDF 先尝试 Native Text/Layout;无文本的 Page 可单页 OCR;同时命中两者时标记 pdf-mixed
  • Table 和 Image 保留 Page、BBox、Table/Image Ordinal;
  • Image 可以分别运行 OCR 与 Caption,并保留各自 Observation;
  • XLSX、DOCX、PPTX 使用结构化 Block,保留 Sheet、Table、Slide、Note 等 Locator;
  • 本地 Parser 结果不足时可以走 Docling Shadow/Fallback;失败写 Audit Event 或 Error Block。

Fail-soft 的意思不是吞掉错误。它允许一个附件失败而不丢掉整个 Source Run,但必须把 Stage、Status 和截断后的 Error 记录下来;pdf-unavailable 不能伪装成成功提取的空文本。

代码:文本相似度之前,先建立可验证结构

当前生产 CPG 路径对 Python 使用标准库 AST;TypeScript、TSX、JavaScript、Java、Swift 和 Kotlin 使用 Tree-sitter Named AST。Production Batch 要求 Tree-sitter 可用,缺少 Language Pack 会阻断该解析路径,不静默退回正则并声称同等可信。

Full CPG 中包括:

作用 示例证据
AST 语法父子结构 Node Type、Line、End Line、Column
Symbol/Type Class、Interface、Function、Method Qualified Name、Signature、Base Type
Call Graph 调用关系 Raw Expression、Resolver Rule、Commit
CFG 顺序、分支和 Join Branch Kind、Source Line
DFG 读写与值传播 Assignment、Name Flow
Semantic CPG 面向查询的子图 CALLSIMPORTSREADSWRITESROUTES_TO

这里的「Full CPG」是相对于进入 Neo4j 的 Semantic Projection 而言:它保留这套 Extractor 能生成的完整 AST/CFG/DFG/Call Artifact,目标是检索和 Evidence,不是编译器级、语言完备的程序分析。当前 CFG 常以顺序、分支和 Join 为主,DFG 聚焦 Assignment/Name Flow;动态 Dispatch、反射和跨语言 Runtime Call 仍可能无法解析。

能精确解析的边标成 EXTRACTED;只有名称但无法解析 Target 的调用落到 unresolved_call,Confidence 为 AMBIGUOUS。Parser 遇到语法、编码或文件大小问题时会生成 Code Error Graph,至少保留 Repository → File、Commit 和 Parse Error,而不是让文件从 Corpus 中无声消失。

Chunking:先尊重结构,再考虑长度

Document Chunker 以 Block 为边界;Code Chunker 以 Symbol 与 Line Range 为边界。Long Block 超过上限时才在内部回退切分,并记录 boundary_reason

Parent/Child 结构承担两种相反需求:Child 足够小,便于精确命中一个 Method、表格 Row 或小节;Parent 保留完整 Section,供回答读取上下文。每个 Chunk 继续携带 Original CAS URI、Content Hash、Source Locator、Entity 和 Evidence Ref。

这比固定的几百字窗口复杂,但固定窗口会把 Function Signature 与 Body、Heading 与 Paragraph、Table Header 与 Row 随机切开。Embedding 能召回半句话,无法恢复丢失的结构语义。

失败模式与取舍

失败模式 当前表示 Trade-off
HTML 只有导航/脚本 Warning 或空 Blocks,不索引 Chrome 可能需要 Source-specific Macro Adapter
PDF 某页无文本 单页 OCR;失败写 Audit Event OCR 慢且有识别误差,不能覆盖原图
Office 复杂布局 保留结构化 Block 和 Locator 不保证像素级版面复原
Tree-sitter 不可用 Production Parser Fail Closed 部署依赖更重,但不把 Regex 结果冒充 AST
动态 Dispatch 无法静态解析 unresolved_call + AMBIGUOUS Graph Recall 下降,换取不伪造调用边
Block 过长 仅在 Block 内 Fallback Split 个别语义单元仍会被切开,但范围可审计

可复核的项目证据

  • src/mt_rag/documents/contracts.py:Block Kind、Locator 和连续 Ordinal Contract;
  • src/mt_rag/documents/html.py:DOM-aware Drop Tags、Heading/Table/Image 与 Source Offset;
  • src/mt_rag/documents/jira.py:ADF、Comments、Changelog、Worklogs、Remote Links;
  • src/mt_rag/multimodal/parser.pymultimodal/docling.py:PDF Native/OCR、Office、Image、Fallback 和 Audit;
  • src/mt_rag/code/cpg.py:Python AST、Tree-sitter CPG、CFG/DFG、Unresolved Call 和 Semantic Projection;
  • src/mt_rag/code/batch.py:Production require_tree_sitter=true、Code Error Graph 与 CAS Reuse;
  • src/mt_rag/chunking/semantic.py:Document/Code Structure-aware Chunk。

参考资料

Typed IR 增加了 Parser 和 Schema 成本,但它让「文本相关」之外的结构、位置和关系也能被验证。统一字符串是方便的中间结果,不能成为工程证据的最终表示。

相关文章

在做类似的事情?

很乐意就分布式系统、交付流程和应用 AI 交换意见。

[email protected]