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 会丢弃 script、style、nav、header、footer、aside、隐藏区域等非正文内容;保留 H1–H6 层级、段落、列表、代码、表格、链接和图片引用。
每个 Block 记录 char_start、char_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 | 面向查询的子图 | CALLS、IMPORTS、READS、WRITES、ROUTES_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.py与multimodal/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:Productionrequire_tree_sitter=true、Code Error Graph 与 CAS Reuse;src/mt_rag/chunking/semantic.py:Document/Code Structure-aware Chunk。
参考资料
- Tree-sitter 官方文档;
- Python
ast官方文档; - Yamaguchi 等人的 Modeling and Discovering Vulnerabilities with Code Property Graphs;
- Joern Code Property Graph 文档;
- Docling 官方项目;
- Azure AI Search 文档分块指南。
Typed IR 增加了 Parser 和 Schema 成本,但它让「文本相关」之外的结构、位置和关系也能被验证。统一字符串是方便的中间结果,不能成为工程证据的最终表示。
相关文章
RAG 不是一条必经流水线,而是一种按需取证的能力
先理解目标、先看当前源码,只有跨来源信息真的能改善判断时,才调用 Local RAG 去找证据候选。
一次代码命中为什么还不算答案
工程检索必须把结果绑到仓库、commit 和 symbol 上;缺失、过期或来自错误仓库的命中,只能当定位线索。