This article is the second of two parts on the upgraded pipeline in Part III of Enterprise Document Intelligence, a series that builds an enterprise RAG system from four bricks: document parsing, question parsing, retrieval, and generation. The first part, Upgrading a baseline RAG, brick by brick (link to come), upgraded each brick on its own. This part wires the four upgraded bricks into a single call and runs it end to end on real documents.本文是关于“企业文档智能”系列第三部分中升级版流水线的上下篇之二。该系列旨在通过四个“积木”(文档解析、问题解析、检索和生成)构建企业级 RAG 系统。第一部分(《逐步升级基准 RAG》,链接待补充)分别升级了每个积木。本部分则将这四个升级后的积木串联成一次单一调用,并在真实文档上进行端到端的运行测试。

The runnable notebook for this article is on GitHub: doc-intel/notebooks-vol1. It runs the single
pdf_qa call on all four documents, prints the typed answer and the per-brick audit trail for each, and lets you drop in your own PDF and watch the same pipeline handle it unchanged.本文的可运行 Notebook 位于 GitHub:doc-intel/notebooks-vol1。它会对所有四份文档运行单一的 pdf_qa 调用,打印出类型化答案以及每个积木的审计追踪,并允许你放入自己的 PDF,观察同一套流水线在不进行任何修改的情况下如何处理它。

Part A upgraded each brick on its own: parsing now returns a relational set with a TOC and a typed parsing_summary; question parsing turns the noisy user input into a structured brief; retrieval reads the document’s own TOC through a small LLM and merges those pages with the keyword hits; generation returns a typed answer with one citable span per item plus four context-quality indicators. Built in isolation, the four still have to run as one.A 部分分别升级了每个积木:解析现在返回包含目录和类型化 parsing_summary 的关系集;问题解析将嘈杂的用户输入转化为结构化的摘要;检索通过小型 LLM 读取文档自身的目录,并将这些页面与关键词匹配结果合并;生成返回一个类型化答案,每项包含一个可引用的片段以及四个上下文质量指标。虽然这四个积木是独立构建的,但它们必须作为一个整体运行。
This part wires them into a single function, pdf_qa, and reads the result. One call takes a PDF and a question and returns the typed answer plus the full audit trail from question to citation. Two side channels appear only once the bricks run together: parsing_summary feeding both LLM bricks, and a feedback signal from generation back to retrieval.本部分将它们串联成一个单一函数 pdf_qa,并读取结果。一次调用即可接收 PDF 和问题,并返回类型化答案以及从问题到引用的完整审计追踪。当积木串联运行时,会出现两个侧向通道:向两个 LLM 积木提供 parsing_summary,以及从生成阶段反馈回检索阶段的信号。
The running paper stays the same public 15-page arXiv submission, Attention Is All You Need, with the same two-typo question, “What are the options for positional encoding?”. Then the assembled pipeline is run on three more documents that stress different bricks: the NIST Cybersecurity Framework 2.0, the original Retrieval-Augmented Generation paper, and the World Bank Commodity Markets Outlook (April 2024), whose PDF ships a broken table of contents. (Sources and licenses are listed at the end.)所使用的论文仍然是那篇 15 页的公开 arXiv 投稿《Attention Is All You Need》,并沿用那个带有两个拼写错误的问题:“位置编码有哪些选项?”。随后,我们将组装好的流水线运行在另外三份文档上,以测试不同的积木:NIST 网络安全框架 2.0、原始的《检索增强生成》论文,以及世界银行的《大宗商品市场展望》(2024 年 4 月版,其 PDF 的目录已损坏)。(来源和许可列于文末。)
1. The pipeline end to end1. 流水线的端到端流程
Article 1 (minimal RAG)’s diagram showed four boxes in a row with one input pill per gap. Enough to introduce the bricks, not enough to capture what the upgraded pipeline actually carries. The version below adds the two side channels that the production code thr

pdf_qa call, with two side channels: parsing_summary and a feedback loop to retrieval – Image by author四个积木串联成一次 pdf_qa 调用,带有两个侧向通道:parsing_summary 和通往检索的反馈回路 —— 图片由作者提供Read the diagram in three passes.分三步阅读该图表。
The four bricks on the main row. Same names as Article 1 (minimal RAG). Each brick consumes the typed object the previous one emitted (line_df out of parsing, ParsedQuestion out of question parsing, anchor + context out of retrieval) and the contract is the Pydantic schema, not a plain dict. The four bricks stay independent: none of them imports any other.主行上的四个积木。名称与第 1 篇文章(最小化 RAG)相同。每个积木消耗前一个积木输出的类型化对象(解析输出 line_df,问题解析输出 ParsedQuestion,检索输出 anchor + context),且契约是 Pydantic 模式,而非普通字典。这四个积木保持独立:它们互不引用。
The first side channel: parsing_summary feeding question parsing. Question parsing in Article 1 (minimal RAG) saw only the raw text of the question. In the upgraded pipeline it also receives a compact projection of the doc-level synthesis the parsing brick computed (doc_type, n_pages, typical_fields, summary). The same question “what is the name?” parses very differently on a CV (doc_type=resume, typical_fields=[name, email, ...]) and on a 200-page annual report. The channel is dashed because it travels through PromptContext in the user content of the LLM call, not through a kwarg of the brick API. The brick API stays parse_question(question, *, context=PromptContext(...)) regardless of what doc_type is.第一个侧向通道:parsing_summary 馈送至问题解析。第 1 篇文章中的问题解析仅能看到问题的原始文本。在升级版流水线中,它还会接收解析积木计算出的文档级综合信息的紧凑投影(doc_type, n_pages, typical_fields, summary)。同一个问题“名字是什么?”在简历(doc_type=resume, typical_fields=[name, email, ...])和 200 页的年度报告中解析结果大不相同。该通道用虚线表示,因为它通过 LLM 调用用户内容中的 PromptContext 传输,而不是通过积木 API 的关键字参数。无论 doc_type 是什么,积木 API 始终保持 parse_question(question, *, context=PromptContext(...))。
The second side channel: the feedback loop. Generation does not always succeed in one shot. The LLM may flag “the retrieved context covered only part of the answer” (complete_answer_found=False) or “the document uses ‘excess’ where the question said ‘deductible’” (llm_discovered_keywords). The orchestrator reads those signals and routes back, typically to retrieval with an expanded keyword list, sometimes to question parsing for a query rewrite. Article 13 (the workflow pipeline) walks the loop in detail. This article runs the single-pass version, which is what the typical short paper or compliance document needs.第二个侧向通道:反馈回路。生成并不总是一次成功。LLM 可能会标记“检索到的上下文仅覆盖了部分答案”(complete_answer_found=False)或“文档使用了‘excess’而问题中说是‘deductible’”(llm_discovered_keywords)。编排器读取这些信号并进行路由,通常是路由回检索阶段以扩展关键词列表,有时路由回问题解析阶段以重写查询。第 13 篇文章(工作流流水线)详细介绍了该循环。本文运行的是单次通过版本,这正是典型短篇论文或合规文档所需要的。
Each of the four bricks composed here is developed, with its code in detail, in its own articles (this article shows only the wiring, not the per-brick code again):此处组合的四个积木中的每一个都在其各自的文章中进行了开发,并提供了详细代码(本文仅展示串联逻辑,不再重复展示各积木的代码):
- Document parsing: Article 5A (what to read in a PDF) and Article 5B (the relational data model).文档解析:第 5A 篇文章(PDF 中读什么)和第 5B 篇文章(关系数据模型)。
- Question parsing: Article 6A (the thesis), Article 6B (extraction), and Article 6C (dispatch).问题解析:第 6A 篇文章(论点)、第 6B 篇文章(提取)和第 6C 篇文章(分发)。
- Retrieval: Article 7A (retrieval as filtering), Article 7B (anchor detection), and Article 7C (the LLM arbiter).检索:第 7A 篇文章(作为过滤的检索)、第 7B 篇文章(锚点检测)和第 7C 篇文章(LLM 仲裁器)。
- Generation: the answer contract, prompt assembly, and validation (Articles 8A to 8C, links to come).生成:答案契约、提示词组装和验证(第 8A 至 8C 篇文章,链接待补充)。
Part A ran the bricks one at a time. Production code wraps them in a single function. The name says what the function does and what it does it on: pdf_qa does question-answering on a PDF. Its sibling excel_qa would do the same on an Excel workbook; pdf_translate would translate a PDF; pdf_compare would compare two. The four upgrades plug in at the four named steps; the rest is plumbing:A 部分一次运行一个积木。生产代码将它们包装在一个函数中。函数名说明了其功能和处理对象:pdf_qa 对 PDF 进行问答。其兄弟函数 excel_qa 对 Excel 工作簿执行相同操作;pdf_translate 翻译 PDF;pdf_compare 比较两个文件。四个升级点插入到四个命名步骤中;其余部分是管道工程:
def pdf_qa(pdf_path, question, *, client=None, expert_dict=None,
context: PromptContext | None = None):
# Brick 1: Parsing: line_df + page_df + toc_df + parsing_summary
parsed_pdf = parse_pdf(pdf_path, method="fitz")
line_df, page_df, toc_df = parsed_pdf["line_df"], parsed_pdf["page_df"], parsed_pdf["toc_df"]
parsing_summary = parsed_pdf["parsing_summary"]
# Project parsing_summary into the DocContext that both LLM bricks read
pipeline_ctx = (context or PromptContext()).with_doc_context(
DocContext.from_parsing_summary(parsing_summary)
)
# Brick 2: Question parsing: one LLM call corrects + extracts, then expand
parsed = parse_question(question, client=client, context=pipeline_ctx)
keywords = expand_with_expert(parsed.keywords, expert_dict or {})
shape = infer_answer_shape(question)
# Brick 3: Retrieval: keywords on page_df + LLM TOC router
kw_pages, _ = retrieve_pages(page_df, line_df, keywords, top_k=3)
selection = reason_on_toc(question, toc_df, client=client)
toc_pages = expand_sections_to_pages(toc_df, selection.section_ids)
pages = sorted(set(kw_pages['page_num']) | toc_pages)
filtered = line_df[line_df['page_num'].isin(pages)]
# Brick 4: Generation: schema picked from expected_answer_shape,
# pipeline_ctx threaded so the LLM sees doc_type / typical_fields
schema = ListAnswer if shape == 'listing' else AnswerWithEvidence
answer = llm_answer_with_evidence(question, filtered, client=client,
context=pipeline_ctx)
return answer, {'pages': pages, 'keywords': keywords,
'toc_selection': selection.model_dump()}
Production signature. The lib’s pdf_qa carries a few more kwargs the simplified form above hides:生产签名。该库的 pdf_qa 包含一些上述简化形式所隐藏的关键字参数:
store: the per-brick cache.store:各积木的缓存。method: the parsing engine.method:解析引擎。top_k: retrieval breadth.top_k:检索广度。use_toc: switches the LLM TOC router off when the document has no usable outline.use_toc:当文档没有可用大纲时,关闭 LLM 目录路由器。include_bbox: for layout-sensitive cases.include_bbox:用于布局敏感的情况。
They all default to behavior compatible with the short version. The retrieval body itself delegates to dispatch_page_retrieval from docintel.retrieval, the same shared helper the cross-document corpus_pdf_qa calls per-document, so single-doc and project-wide QA stay on the same TOC-routing logic. The body stays the same four-brick wiring.它们默认行为均与简化版本兼容。检索主体本身委托给 docintel.retrieval 中的 dispatch_page_retrieval,这是跨文档 corpus_pdf_qa 逐文档调用的共享辅助函数,因此单文档和全项目 QA 保持相同的目录路由逻辑。主体仍然是相同的四积木串联。
Here is what each brick took in and produced on this run:以下是本次运行中每个积木的输入和输出:
Brick 1, parsing. In: the Attention PDF path. Out: line_df, page_df, toc_df. The bootstrap chunk at the top of the article already showed each one; no separate figure here.积木 1,解析。输入:Attention PDF 路径。输出:line_df, page_df, toc_df。文章开头的引导块已经展示了每一个;此处不再单独列图。
Brick 2, question parsing. In: the noisy question. Out: the parsed brief.积木 2,问题解析。输入:嘈杂的问题。输出:解析后的简报。
{
"in": {
"raw_question": "What are the optoins for posiitional encoding?"
},
"out": {
"raw_keywords": ["posiitional encoding"],
"corrected_keywords": ["positional encoding"],
"expert_keywords_added": ["sinusoidal", "learned"],
"final_keywords": ["positional encoding", "sinusoidal", "learned"],
"expected_answer_shape": "listing"
}
}
Brick 3, retrieval. In: final_keywords + page_df + toc_df. Out: the retrieval state.积木 3,检索。输入:final_keywords + page_df + toc_df。输出:检索状态。
{
"in": {
"final_keywords": ["positional encoding", "sinusoidal", "learned"],
"scope": "page_df + toc_df"
},
"out": {
"keyword_candidates": [
{"page": 6, "matched_keywords": ["positional encoding", "sinusoidal", "learned"], "match_count": 3},
{"page": 9, "matched_keywords": ["positional encoding", "sinusoidal", "learned"], "match_count": 3},
{"page": 4, "matched_keywords": ["learned"], "match_count": 1}
],
"toc_routing": {
"section_ids": ["10"],
"reasoning": "Section 10, 'Positional Encoding', explicitly focuses on…",
"selected_sections": [
{"section_id": "10", "title": "Positional Encoding", "start_page": 6, "end_page": 6}
]
},
"merged_pages": [4, 6, 9]
}
}
Brick 3 merges the keyword pages and the TOC-picked pages into one set. The table below shows the reasoning: the paper’s full TOC as backbone, with for each section the keywords detected in its lines and three Y/. flags showing whether that section was picked by keyword retrieval, by TOC retrieval, and whether it ended up in merged_pages. The merger is a deterministic union; this table makes the decision auditable section by section.积木 3 将关键词页面和目录选定页面合并为一个集合。下表显示了推理过程:以论文的完整目录为骨干,针对每个部分显示其行中检测到的关键词,以及三个 Y/. 标志,表明该部分是通过关键词检索、目录检索选中的,以及它最终是否进入了 merged_pages。合并是确定性的并集;该表使决策在逐个部分上可审计。

Brick 4, generation. In: question + the lines on merged_pages. Out: a ListAnswer with one item per option, line spans, verbatim quotes, plus the four context-quality indicators.积木 4,生成。输入:问题 + merged_pages 上的行。输出:一个 ListAnswer,每个选项包含一项,以及行跨度、逐字引用和四个上下文质量指标。
{
"in": {
"question": "What are the optoins for posiitional encoding?",
"merged_pages": [4, 6, 9],
"filtered_line_df": "256 rows"
},
"out": {
"items": [
{"text": "Sinusoidal positional encodings", "start_page_num": 6, "start_line_num": 33, "end_page_num": 6, "end_line_num": 35, "quote": "PE(pos,2i) = sin(pos/100002i/dmodel)\nPE(pos,2i+1) = cos(po…"},
{"text": "Learned positional embeddings", "start_page_num": 6, "start_line_num": 41, "end_page_num": 6, "end_line_num": 41, "quote": "We also experimented with using learned positional embeddin…"},
{"text": "positional embedding instead of sinusoids", "start_page_num": 9, "start_line_num": 111, "end_page_num": 9, "end_line_num": 111, "quote": "positional embedding instead of sinusoids"}
],
"answer_found": true,
"complete_answer_found": true,
"context_completeness": 1.0,
"context_structured": true,
"confidence": 1.0,
"caveats": []
}
}
The line spans on each item are not abstract numbers. They map back to a real page in the source PDF, and the helper below draws a red rectangle around each item’s line range so the reader can see the answer on the page itself:每个条目上的行跨度不是抽象数字。它们映射回源 PDF 中的真实页面,下方的辅助工具在每个条目的行范围周围绘制一个红色矩形,以便读者可以在页面本身上看到答案:

Twenty lines of Python, four LLM calls (two in question parsing and generation, plus the bricks that do not call the LLM). The provenance dict makes every step replayable: log the call, an auditor can trace from question to citation without re-running the pipeline.二十行 Python 代码,四次 LLM 调用(问题解析和生成各两次,外加不调用 LLM 的积木)。来源字典(provenance dict)使每一步都可以重放:记录调用后,审计员无需重新运行流水线即可从问题追踪到引用。
The Attention paper above is a research paper. The same pipeline, with no code change, runs on documents shaped very differently. Three more tests: a compliance PDF, a research paper, and a stress case on a document with a broken TOC.上述 Attention 论文是一篇研究论文。同一条流水线在不更改任何代码的情况下,可以运行在形状迥异的文档上。另外三个测试:一份合规 PDF、一篇研究论文,以及一份目录损坏文档的压力测试。
2. When the answer is buried among many matches2. 当答案埋藏在大量匹配项中时
The NIST Cybersecurity Framework 2.0 is 32 pages, native TOC, single-column layout. The kind of document an enterprise team parses for retrieval. This time the question targets a single concept, not a list of options. The question parser’s shape inference flags it as single, and the pipeline dispatches to AnswerWithEvidence instead of ListAnswer. Same wrap function, different output schema. Listing questions are the subject of Article 12 (listing); here we exercise the other branch of the shape dispatch:NIST 网络安全框架 2.0 有 32 页,自带目录,单栏布局。这是企业团队进行检索解析的那类文档。这次问题针对的是单一概念,而不是选项列表。问题解析器的形状推断将其标记为 single,流水线分发给 AnswerWithEvidence 而不是 ListAnswer。相同的包装函数,不同的输出模式。列表问题是第 12 篇文章(列表)的主题;这里我们练习形状分发的另一个分支:
The four bricks walked on this run:本次运行中经过的四个积木:
Brick 1, parsing. In: test2_pdf (the NIST CSWP-29 PDF). Out: line_df, page_df, toc_df.积木 1,解析。输入:test2_pdf (NIST CSWP-29 PDF)。输出:line_df, page_df, toc_df。
Brick 2, question parsing. In: the noisy question. Out: the parsed brief.
{
"in": {
"raw_question": "How is a Profile defined in CSF 2.0?"
},
"out": {
"raw_keywords": ["Profile", "CSF 2.0"],
"corrected_keywords": ["Profile", "CSF 2.0"],
"expert_keywords_added": [],
"final_keywords": ["Profile", "CSF 2.0"],
"expected_answer_shape": "single"
}
}
Brick 3, retrieval. In: final_keywords + page_df + toc_df. Out: the retrieval state.
{
"in": {
"final_keywords": ["Profile", "CSF 2.0"],
"scope": "page_df + toc_df"
},
"out": {
"keyword_candidates": [
{"page": 2, "matched_keywords": ["Profile", "CSF 2.0"], "match_count": 2},
{"page": 5, "matched_keywords": ["Profile", "CSF 2.0"], "match_count": 2},
{"page": 4, "matched_keywords": ["Profile"], "match_count": 1}
],
"toc_routing": {
"section_ids": ["3", "2", "11"],
"reasoning": "Section 3.1 (CSF Profiles) directly addresses the definit…",
"selected_sections": [
{"section_id": "2", "title": "3. Introduction to CSF Profiles and Tiers", "start_page": 11, "end_page": 14},
{"section_id": "3", "title": "3.1. CSF Profiles", "start_page": 11, "end_page": 12},
{"section_id": "11", "title": "Appendix C. Glossary", "start_page": 31, "end_page": 32}
]
},
"merged_pages": [2, 4, 5, 11, 12, 13, 14, 31, 32]
}
}
The TOC-by-section audit on the same run:同一次运行中的按部分目录审计:

Brick 4, generation. In: question + the lines on merged_pages. Out: an AnswerWithEvidence with one answer string, one citable span, verbatim quotes, plus the quality indicators (complete_answer_found, context_structured, confidence).积木 4,生成。输入:问题 + merged_pages 上的行。输出:一个 AnswerWithEvidence,包含一个答案字符串、一个可引用跨度、逐字引用以及质量指标(complete_answer_found, context_structured, confidence)。
{
"in": {
"question": "How is a Profile defined in CSF 2.0?",
"merged_pages": [2, 4, 5, 11, 12, 13, 14, 31, 32],
"filtered_line_df": "280 rows"
},
"out": {
"answer": "A Profile in CSF 2.0, specifically a CSF Organizational Pro…",
"start_page_num": 11,
"start_line_num": 8,
"end_page_num": 12,
"end_line_num": 20,
"confidence": 1.0,
"justification": "Lines 11:8-12:20 provide a detailed definition of CSF P…",
"quotes": [
"A CSF Organizational Profile describes an organization's cu…",
"Every Organizational Profile includes one or both of the fo…",
"A Community Profile is a baseline of CSF outcomes that is c…"
],
"caveats": [],
"complete_answer_found": true,
"context_structured": true,
"llm_discovered_keywords": ["Organizational Profile", "Current Profile", "Target Profile", "Community Profile"]
}
}
The same span turned into a rectangle on the source page:同一个跨度在源页面上转化为矩形:

Because the question wording does not trigger LISTING_TRIGGERS (no options, all, which, what are), the shape inference returns single, and the pipeline dispatches the call to AnswerWithEvidence instead of ListAnswer. Same four bricks, same audit trail, different output schema.由于问题措辞未触发 LISTING_TRIGGERS(没有选项、所有、哪些、是什么),形状推断返回 single,流水线将调用分发给 AnswerWithEvidence 而不是 ListAnswer。相同的四个积木,相同的审计追踪,不同的输出模式。
The single span lands on the page that defines a CSF Profile, and the annotated page above shows the one rectangle highlighted in context. The quality indicators (complete_answer_found, context_structured, confidence) come back clean, so the downstream router would ship this answer as is.单个跨度落在定义 CSF Profile 的页面上,上方的标注页面显示了突出显示的矩形。质量指标(complete_answer_found, context_structured, confidence)返回结果良好,因此下游路由器将按原样发送此答案。
Listing questions get their own dedicated treatment in Article 12 (listing); here the takeaway is that the wrap function carries both shapes without a separate code path on the caller side.列表问题在第 12 篇文章(列表)中有专门的处理;此处的收获是包装函数在调用方无需单独代码路径的情况下承载了两种形状。
3. When the answer spans several sections3. 当答案跨越多个部分时
Retrieval-Augmented Generation for Knowledge-Intensive NLP Tasks (Lewis et al. 2020) is the paper that introduced the RAG architecture this series is about. 18 pages, native TOC, research-paper style with a results-table-heavy mid-section. Another single-answer question, this time about the paper’s central architectural pattern:《Retrieval-Augmented Generation for Knowledge-Intensive NLP Tasks》(Lewis 等人,2020 年)是引入本系列所讨论的 RAG 架构的论文。18 页,自带目录,研究论文风格,中间部分包含大量结果表。另一个单答案问题,这次是关于该论文的核心架构模式:
Same four-brick walk on this run:本次运行中相同的四积木流程:
Brick 1, parsing. In: test3_pdf (the original RAG paper, 18 pages). Out: line_df, page_df, toc_df.积木 1,解析。输入:test3_pdf (原始 RAG 论文,18 页)。输出:line_df, page_df, toc_df。
Brick 2, question parsing. In: the noisy question. Out: the parsed brief.
{
"in": {
"raw_question": "How does RAG combine retrieval and generation?"
},
"out": {
"raw_keywords": ["RAG", "retrieval", "generation"],
"corrected_keywords": ["RAG", "retrieval", "generation"],
"expert_keywords_added": [],
"final_keywords": ["RAG", "retrieval", "generation"],
"expected_answer_shape": "single"
}
}
Brick 3, retrieval. In: final_keywords + page_df + toc_df. Out: the retrieval state.
{
"in": {
"final_keywords": ["RAG", "retrieval", "generation"],
"scope": "page_df + toc_df"
},
"out": {
"keyword_candidates": [
{"page": 1, "matched_keywords": ["RAG", "retrieval", "generation"], "match_count": 3},
{"page": 2, "matched_keywords": ["RAG", "retrieval", "generation"], "match_count": 3},
{"page": 3, "matched_keywords": ["RAG", "retrieval", "generation"], "match_count": 3}
],
"toc_routing": {
"section_ids": ["2", "3", "4"],
"reasoning": "Section 2.1 Models likely explains the overall architectu…",
"selected_sections": [
{"section_id": "2", "title": "2.1 Models", "start_page": 3, "end_page": 3},
{"section_id": "3", "title": "2.2 Retriever: DPR", "start_page": 3, "end_page": 3},
{"section_id": "4", "title": "2.3 Generator: BART", "start_page": 3, "end_page": 3}
]
},
"merged_pages": [1, 2, 3]
}
}
The TOC-by-section audit on the same run:

Brick 4, generation. In: question + the lines on merged_pages. Out: an AnswerWithEvidence describing how RAG couples the retriever to the generator, with one cited span and the quality indicators.积木 4,生成。输入:问题 + merged_pages 上的行。输出:一个 AnswerWithEvidence,描述 RAG 如何将检索器与生成器耦合,包含一个引用跨度和质量指标。
{
"in": {
"question": "How does RAG combine retrieval and generation?",
"merged_pages": [1, 2, 3],
"filtered_line_df": "200 rows"
},
"out": {
"answer": "RAG combines retrieval and generation by using a pre-traine…",
"start_page_num": 2,
"start_line_num": 46,
"end_page_num": 3,
"end_line_num": 40,
"confidence": 0.98,
"justification": "Lines 2:46 to 3:40 describe the mechanism: the retrieve…",
"quotes": [
"Figure 1: Overview of our approach. We combine a pre-traine…",
"The retrieval component pη(z|x) is based on DPR ... Calcula…",
"The generator component pθ(yi|x, z, y1:i-1) could be modell…",
"RAG-Sequence Model ... the model uses the same document to…",
"In the RAG-Token model we can draw a different latent docum…"
],
"caveats": [
"The detailed algorithmic steps (e.g., training or more adva…"
],
"complete_answer_found": true,
"context_structured": true,
"llm_discovered_keywords": ["retriever", "generator", "marginalize", "latent document", "seq2seq", "DPR", "BART", "Maximum Inner Product Search", "RAG-Sequence", "RAG-Token"]
}
}
The same cited span drawn on the source page:在源页面上绘制相同的引用跨度:

The shape inference returns single again, and the pipeline dispatches to AnswerWithEvidence. The question is broader than NIST’s Profile definition because it asks about an architectural pattern that spans the retriever, the generator, and the way the two are jointly trained. Retrieval pulls a handful of pages across the body; the generator synthesises one paragraph that ties them together, with one citable span. As with the NIST run, the four indicators all stay clean on this well-structured PDF.形状推断再次返回 single,流水线分发给 AnswerWithEvidence。这个问题比 NIST 的 Profile 定义更广泛,因为它询问的是一种跨越检索器、生成器以及两者如何联合训练的架构模式。检索从正文中提取了少量页面;生成器合成了一个将它们联系在一起的段落,并带有一个可引用跨度。与 NIST 的运行一样,四个指标在这份结构良好的 PDF 上都保持良好。
4. When the table of contents is broken4. 当目录损坏时
The World Bank’s Commodity Markets Outlook (World Bank publication, April 2024 issue) has a built-in TOC, but the section titles all read Blank Page. The bookmarks were generated without titles. The LLM TOC router gets handed a TOC where every entry is empty of semantic content, picks nothing, and the pipeline falls back to keyword retrieval alone. A good stress test of what happens when one of the four bricks (parsing) returns a degenerate output. A single-answer question, on a forecasting report:世界银行的《大宗商品市场展望》(2024 年 4 月版)内置了目录,但章节标题全部显示为“空白页”。书签生成时没有标题。LLM 目录路由器拿到的目录中,每个条目都没有语义内容,因此什么也没选,流水线退回到仅使用关键词检索。这是一个很好的压力测试,用于观察当四个积木之一(解析)返回退化输出时会发生什么。一个关于预测报告的单答案问题:
Same walk, this time the TOC brick lands nothing useful:相同的流程,这次目录积木没能得到任何有用的结果:
Brick 1, parsing. In: test4_pdf (World Bank CMO April 2024). Out: line_df, page_df, toc_df. The toc_df is degenerate: every title reads Blank Page because the bookmarks were generated without titles.积木 1,解析。输入:test4_pdf (世界银行 CMO 2024 年 4 月版)。输出:line_df, page_df, toc_df。toc_df 是退化的:每个标题都显示为“空白页”,因为书签生成时没有标题。
Brick 2, question parsing. In: the noisy question. Out: the parsed brief.
{
"in": {
"raw_question": "What is the energy price outlook for 2024?"
},
"out": {
"raw_keywords": ["energy price", "2024"],
"corrected_keywords": ["energy price", "2024"],
"expert_keywords_added": [],
"final_keywords": ["energy price", "2024"],
"expected_answer_shape": "single"
}
}
Brick 3, retrieval. In: final_keywords + page_df + toc_df. Out: the retrieval state.
{
"in": {
"final_keywords": ["energy price outlook", "2024"],
"scope": "page_df + toc_df"
},
"out": {
"keyword_candidates": [
{"page": 1, "matched_keywords": ["2024"], "match_count": 1},
{"page": 3, "matched_keywords": ["2024"], "match_count": 1},
{"page": 4, "matched_keywords": ["2024"], "match_count": 1}
],
"toc_routing": {
"section_ids": [],
"reasoning": "None of the sections listed in the table of contents prov…",
"selected_sections": []
},
"merged_pages": [1, 3, 4]
}
}
The TOC-by-section audit makes the parsing failure visible at a glance:按部分目录审计使解析失败一目了然:

by_toc empty for every row; audit instantly exposes the degenerate TOC – Image by authorby_toc 在每一行都为空;审计立即暴露了退化的目录 —— 图片由作者提供Brick 4, generation. In: question + the lines on merged_pages. Out: an AnswerWithEvidence summarising the energy price outlook, with one citable span. The pipeline still produced a clean answer despite the TOC failing.积木 4,生成。输入:问题 + merged_pages 上的行。输出:一个 AnswerWithEvidence,总结能源价格展望,并带有一个可引用跨度。尽管目录失败,流水线仍然产生了清晰的答案。
{
"in": {
"question": "What is the energy price outlook for 2024?",
"merged_pages": [1, 3, 4],
"filtered_line_df": "47 rows"
},
"out": {
"answer": "NA",
"start_page_num": null,
"start_line_num": null,
"end_page_num": null,
"end_line_num": null,
"confidence": 0.0,
"justification": "None of the provided lines discuss the energy price out…",
"quotes": [],
"caveats": [
"No substantive content from the Commodity Markets Outlook r…"
],
"complete_answer_found": false,
"context_structured": true,
"llm_discovered_keywords": ["Commodity Markets Outlook", "data cutoff date", "license"]
}
}
There is no span to draw. The TOC router saw every title was Blank Page and picked nothing (toc_routing.selected_sections: []), so retrieval fell back to keywords. On this document the keyword 2024 matched only the front-matter pages, not the energy section, so generation read pages that hold no forecast, found nothing to cite, and returned answer: NA with complete_answer_found: false at confidence 0.0.没有跨度可以绘制。目录路由器看到每个标题都是“空白页”并选择了空集 (toc_routing.selected_sections: []),因此检索退回到关键词。在该文档上,关键词“2024”仅匹配了前言页面,而非能源部分,因此生成器读取的页面不包含预测,找不到可引用的内容,并返回 answer: NA,complete_answer_found: false,置信度 0.0。
That is the point, not a bug in the demo. Handed context that does not contain the answer, the pipeline declines instead of inventing a number. A naive RAG on the same broken retrieval would have taken the front-matter text and written a confident-sounding forecast from it. The fix is upstream, in parsing, not in generation: Article 10 (adaptive parsing)’s recover TOC from body path rebuilds a usable table of contents when the embedded one is degenerate, so the next run routes to the energy section and answers with a citable span.这就是重点,而不是演示中的 bug。当提供的上下文不包含答案时,流水线会拒绝回答,而不是编造一个数字。在相同的损坏检索上,朴素的 RAG 会提取前言文本,并从中写出一个听起来很自信的预测。修复方案在前端(解析),而不是在生成端:第 10 篇文章(自适应解析)的“从正文恢复目录”路径可以在嵌入式目录退化时重建可用的目录,这样下次运行就会路由到能源部分并以可引用跨度进行回答。
Four documents, both answer shapes exercised: the Attention paper as a listing question (one item per option), NIST and the RAG paper as single-answer questions (one paragraph, one span), and CMO as the single-answer question the pipeline correctly declined when retrieval came back without the forecast. Same wrap function, same four bricks, same audit trail; the only thing that changes is which Pydantic schema generation returns, and whether it can honestly fill it. The pipeline never had to know which document it was reading, and it never had to know which schema it was about to fill. Each test stands on its own as a black-box demo, and they all agree on what an audit trail looks like.四份文档,两种答案形状都得到了练习:Attention 论文作为列表问题(每个选项一项),NIST 和 RAG 论文作为单答案问题(一段话,一个跨度),CMO 作为流水线在检索不到预测时正确拒绝的单答案问题。相同的包装函数,相同的四个积木,相同的审计追踪;唯一改变的是生成返回的 Pydantic 模式,以及它是否能诚实地填充该模式。流水线从不需要知道它正在阅读哪份文档,也从不需要知道它即将填充哪种模式。每个测试都作为一个黑盒演示独立存在,并且它们对审计追踪的样子达成了一致。
Where the pipeline can still fail, each failure showing in a specific indicator:流水线可能仍然失败的地方,每个失败都显示在特定的指标中:
- a PDF without a usable TOC (in-text TOC the parser missed, or no TOC page at all): empty
toc_sections_matched.没有可用目录的 PDF(解析器错过的文中目录,或根本没有目录页):toc_sections_matched 为空。 - a scanned document where the parser delivered scrambled text in the first place:
context_structured=false.解析器一开始就交付了乱码文本的扫描文档:context_structured=false。 - a question whose vocabulary does not appear in the corpus at all:
complete_answer_found=falseor emptyitems.词汇表中根本不包含问题词汇的文档:complete_answer_found=false 或项目为空。
Article 10 (adaptive parsing) picks up from there: cheap parsing first, deeper parsing on demand, and what to do when retrieval comes back empty.第 10 篇文章(自适应解析)从这里开始:先进行廉价解析,按需进行更深层的解析,以及当检索返回为空时该怎么办。
5. What a naive pipeline does on the hard cases5. 朴素流水线在困难情况下的表现
The pipeline ran on every document above, answering three and honestly declining the fourth. The fair question: would a naive RAG (Article 1 (minimal RAG)’s baseline, keyword-match or embed pages, keep the top few, ask) have done as well? We ran pdf_qa_baseline against pdf_qa on the same questions, plus a few more standards to see where the two diverge.流水线在上述每份文档上运行,回答了三个并诚实地拒绝了第四个。一个公平的问题:朴素的 RAG(第 1 篇文章(最小化 RAG)的基准,关键词匹配或嵌入页面,保留前几个,询问)会做得一样好吗?我们在相同问题上运行了 pdf_qa_baseline 对比 pdf_qa,外加一些标准文档以观察两者的分歧。
Be honest about the result: on the two clean arXiv papers, the naive baseline answers correctly too. They are short and well-structured, and the answer keyword-matches. The gap is not on easy inputs. It opens on the standards. On NIST CSF 2.0, asked “How is a Profile defined?”, naive retrieves pages full of the word Profile but never the one that defines it, and returns “not defined in these lines” at 0.10. On the 400-plus-page NIST SP 800-53 catalog, asked for one control, it retrieves nothing usable and returns “NA” at 0.00. On NIST SP 800-207 it hands back three of the seven zero-trust tenets, confident at 0.95 that a partial list is the whole answer. On FIPS 199 it defines only the high impact level and admits low and moderate were never in its context. Our pipeline routes each question on the document’s own table of contents, anchors the right section, and returns the complete, cited answer every time.诚实地讲:在两篇清晰的 arXiv 论文上,朴素基准也能正确回答。它们简短且结构良好,答案与关键词匹配。差距不在于简单的输入。它出现在标准文档上。在 NIST CSF 2.0 上,问“Profile 是如何定义的?”,朴素检索会检索到充满“Profile”一词的页面,但永远找不到定义它的那个页面,并以 0.10 的置信度返回“这些行中未定义”。在 400 多页的 NIST SP 800-53 目录上,询问一个控制项,它检索不到任何有用的内容,并以 0.00 的置信度返回“NA”。在 NIST SP 800-207 上,它交回了七个零信任原则中的三个,并以 0.95 的高置信度认为部分列表就是全部答案。在 FIPS 199 上,它仅定义了高影响级别,并承认低和中影响从未出现在其上下文中。我们的流水线根据文档自身的目录路由每个问题,锚定正确的章节,并每次都返回完整、带引用的答案。
That pattern carries the argument. A naive RAG is a keyword-or-cosine bet, and it pays off until the document is long enough, or its vocabulary far enough from the question, that the answer sinks below the top-k cutoff. Then the model is handed context that does not contain the answer, and it does one of two things: it says “not found” (the honest 0.10 or NA case) or it invents a plausible answer from the wrong pages (the confident, partial tenets list). The second is what teams report as a hallucination. It is rarely the model inventing from nothing. Usually it is answering the wrong context faithfully, and often both at once: handed pages that do not contain the answer, it fills the gap with something plausible. Article 7quinquies (most RAG hallucinations are retrieval failures) makes that case at the retrieval level; here it shows up end to end.这种模式支撑了这一论点。朴素的 RAG 是一场基于关键词或余弦相似度的赌博,在文档足够短或词汇与问题足够接近时,它会奏效。一旦答案沉入 top-k 截断点之下,模型就会被赋予不包含答案的上下文,它会做两件事之一:说“未找到”(诚实的 0.10 或 NA 情况),或者从错误的页面编造一个合理的答案(自信的、部分的原则列表)。后者就是团队报告的幻觉。模型很少无中生有。通常它是忠实地回答了错误的上下文,而且往往两者兼有:给定不包含答案的页面,它用合理的内容填补了空白。第 7 篇文章(大多数 RAG 幻觉是检索失败)在检索层面论证了这一点;这里它在端到端层面表现出来。
This is why the four bricks are context engineering, not retrieval tuning. Each brick shapes what the model finally sees: parsing keeps the structure, question parsing widens the vocabulary, retrieval routes on the document’s own map, generation binds the answer to a citable span. Get the context right and a confident wrong answer has nowhere to come from. Prompt engineering isn’t enough: how four bricks of context engineering stop RAG hallucinations (Article 9bis, link to come) takes this contrast much further, one failure per brick, with a naive baseline built to break at exactly that brick. This section is the short version; that article is the full diagnosis.这就是为什么这四个积木是上下文工程,而不是检索调优。每个积木塑造了模型最终看到的内容:解析保持结构,问题解析拓宽词汇,检索根据文档自身的地图进行路由,生成将答案绑定到可引用跨度。把上下文搞对,自信的错误答案就无处产生。提示词工程是不够的:如何通过四个上下文工程积木阻止 RAG 幻觉(第 9 篇文章,链接待补充)将这种对比推向深入,每个积木一个失败案例,并构建了一个在特定积木上崩溃的朴素基准。本节是简短版本;那篇文章是完整的诊断。
6. Why the bricks stay independent6. 为什么积木保持独立
The three runs above worked without a single line of code change because of how the pipeline is decomposed. Each brick has one job and one typed output, so swapping the document changes what flows through the bricks, never the bricks themselves.上述三个运行无需更改一行代码即可工作,原因在于流水线的分解方式。每个积木只有一个工作和一个类型化输出,因此交换文档会改变流经积木的内容,但永远不会改变积木本身。
Each brick has a typed contract with its neighbours. Parsing returns a small relational set of DataFrames (line_df, page_df, toc_df) with known schemas. Question parsing returns a structured brief (corrected keywords, expert expansion, expected answer shape). Retrieval returns a merged page set and the filtered line_df rows on those pages. Generation returns a typed Pydantic object whose schema depends on the answer shape, carrying the items and the four quality indicators. Every intermediate output is a named, inspectable, dump-to-JSON-able object.每个积木与其邻居有类型化契约。解析返回一小组具有已知模式的关系型 DataFrame(line_df, page_df, toc_df)。问题解析返回结构化简报(更正后的关键词、专家扩展、预期答案形状)。检索返回合并后的页面集以及这些页面上过滤后的 line_df 行。生成返回一个类型化 Pydantic 对象,其模式取决于答案形状,携带项目和四个质量指标。每个中间输出都是一个命名、可检查、可转储为 JSON 的对象。
Because the contracts are explicit, each brick can be swapped without rewiring the rest. The four upgrades that built up this article are the proof:因为契约是明确的,每个积木都可以在不重新连接其余部分的情况下进行交换。构建本文的四个升级就是证明:
- Article 5B (the relational data model) added
toc_dfto parsing’s output. The other three bricks did not change; retrieval picked up the new DataFrame and ignored the change.第 5B 篇文章(关系数据模型)将 toc_df 添加到解析的输出中。其他三个积木没有改变;检索拾取了新的 DataFrame 并忽略了变化。 - Article 6 (question parsing) added typo correction and expert keywords to the brief. Question parsing’s signature grew, retrieval received richer keywords, nothing else moved.第 6 篇文章(问题解析)在简报中添加了拼写纠正和专家关键词。问题解析的签名增长了,检索收到了更丰富的关键词,其他部分没有移动。
- Article 7 (retrieval) added TOC matching to retrieval. Retrieval’s output kept the same shape, generation read the same
filtered_line_df.第 7 篇文章(检索)将目录匹配添加到检索中。检索的输出保持相同的形状,生成读取相同的 filtered_line_df。 - Article 8 (generation) added the four quality indicators to the answer schema. Generation’s output schema grew, the caller decided whether to read the new fields.第 8 篇文章(生成)将四个质量指标添加到答案模式中。生成的输出模式增长了,调用方决定是否读取新字段。
Four upgrades, four bricks, zero cross-brick rewrites.四次升级,四个积木,零次跨积木重写。
This generalizes beyond RAG. A complex task that resists composition is usually one where the intermediate types are implicit, named inconsistently, or not persistable. Decomposing into bricks with clean, typed contracts costs upfront thinking and pays back at every later upgrade. The pipeline you would ship is the one a colleague can rewrite one brick of without breaking the others.这超越了 RAG 的范畴。一个难以组合的复杂任务,通常是因为中间类型是隐式的、命名不一致或不可持久化的。分解为具有清晰、类型化契约的积木需要前期的思考,并在随后的每次升级中获得回报。你将交付的流水线是那种同事可以重写其中一个积木而不破坏其他部分的流水线。
7. Conclusion7. 结论
Same paper, same question as Article 1 (minimal RAG), four bricks at their Part II level. The structured contract between bricks makes the pipeline composable: DataFrames out of parsing and retrieval, Pydantic schemas out of question parsing and generation. A team can adopt the upgrades one brick at a time without rewiring the rest, and an auditor can replay every step from the provenance dict.与第 1 篇文章(最小化 RAG)相同的论文,相同的问题,四个积木处于第二部分水平。积木之间的结构化契约使流水线可组合:解析和检索输出 DataFrame,问题解析和生成输出 Pydantic 模式。团队可以一次一个积木地采用升级,而无需重新连接其余部分,审计员可以从来源字典重放每一步。

Article 10 (adaptive parsing) continues from where this one stops. The pipeline assumes parsing built a clean toc_df; when parsing fails (scans without OCR, broken bookmarks, layouts the heuristics miss), retrieval has to fall back to body-only signals. The next article walks the cheap-parsing-first, deeper-parsing-on-demand pattern.第 10 篇文章(自适应解析)从本文停止的地方继续。流水线假设解析构建了清晰的 toc_df;当解析失败(没有 OCR 的扫描件、损坏的书签、启发式错过的布局)时,检索必须退回到仅基于正文的信号。下一篇文章将探讨“先廉价解析,按需深入解析”的模式。
8. Sources and further reading8. 来源和进一步阅读
The article composes the four upgraded bricks (from Articles 5-8) end to end on three documents: the Attention Is All You Need paper, the NIST Cybersecurity Framework, and the original RAG paper. The responses.parse(text_format=Schema) pattern at the question-parsing and generation boundaries uses OpenAI’s Structured Outputs (Aug 2024). The closest published production-grade write-up of this kind of pipeline is Anthropic’s Contextual Retrieval (Sept 2024). The agentic upgrade path on top of the same four bricks is follow-up work; the per-brick provenance keeps the agent’s choices auditable.本文在三份文档上端到端地组合了四个升级后的积木(来自第 5-8 篇文章):《Attention Is All You Need》论文、NIST 网络安全框架和原始 RAG 论文。问题解析和生成边界处的 responses.parse(text_format=Schema) 模式使用了 OpenAI 的结构化输出(2024 年 8 月)。此类流水线最接近的已发布生产级文章是 Anthropic 的 Contextual Retrieval(2024 年 9 月)。在相同的四个积木之上的代理升级路径是后续工作;逐积木的来源记录使代理的选择可审计。
Earlier in the series:系列前期内容:
- Document Intelligence: series intro. What the series builds, brick by brick, and in what order.文档智能:系列介绍。该系列逐步构建的内容及其顺序。
What works, what breaks什么有效,什么会崩溃
- The minimal Enterprise RAG that never lies about its source: PDF in, highlighted answer out. The four-brick pipeline end to end: PDF in, highlighted answer out.从不谎报来源的最小化企业 RAG:输入 PDF,输出高亮答案。端到端的四积木流水线:输入 PDF,输出高亮答案。
- Embeddings Aren’t Magic: The Predictable Failure Modes of RAG Retrieval. Where embedding similarity wins (synonyms, typos, paraphrase), where it predictably breaks (unknown terms, negation, term-vs-answer relevance), and how to use it anyway.
- Rerankers Aren’t Magic Either: When the Cross-Encoder Layer Is Worth the Cost. What a cross-encoder adds over bi-encoder embeddings, measured, and when it is worth the latency.重排序器也不是魔法:交叉编码器层何时值得成本。交叉编码器相对于双编码器嵌入增加了什么,经过测量,以及何时值得延迟。
- RAG is not machine learning, and the ML toolkit solves the wrong problem. Why chunk-size sweeps and finetuning optimize the wrong thing; route by question type instead.RAG 不是机器学习,ML 工具包解决了错误的问题。为什么分块大小扫描和微调优化了错误的东西;按问题类型路由。
- From regex to vision models: which RAG technique fits which problem. Two axes, document complexity and question control, that pick the technique for each case.
- 10 common RAG mistakes we keep seeing in production. Ten production mistakes, organized brick by brick, with the fix for each.我们在生产中不断看到的 10 个常见 RAG 错误。十个生产错误,按积木组织,并附带修复方案。
Document parsing文档解析
- Beyond extract_text: the two layers of a PDF that drive RAG quality. The first half of the parsing brick: the document’s nature, signals, and summary.超越 extract_text:驱动 RAG 质量的 PDF 两层结构。解析积木的前半部分:文档的性质、信号和摘要。
- Stop returning flat text from a PDF: the relational tables RAG needs. The second half of the parsing brick: the relational tables every downstream brick reads.
- When PyMuPDF can’t see the table: parse PDFs for RAG with Azure Layout. The same tables from Azure Layout: native table cells, OCR, paragraph roles.当 PyMuPDF 看不到表格时:使用 Azure Layout 解析 PDF。来自 Azure Layout 的相同表格:原生表格单元格、OCR、段落角色。
- Parse PDFs for RAG locally with Docling: rich tables, no cloud upload. The same tables computed locally with Docling: TableFormer cells, nothing leaves the machine.使用 Docling 在本地解析 PDF:丰富的表格,无需云上传。使用 Docling 在本地计算的相同表格:TableFormer 单元格,无需离开机器。
- Vision LLMs are PDF parsers too: reading charts and diagrams for RAG. Vision as a parser: the pictures become searchable text.视觉 LLM 也是 PDF 解析器:为 RAG 读取图表和示意图。作为解析器的视觉:图片变成可搜索的文本。
- Parse scanned PDFs for RAG with EasyOCR: free OCR gives you words, not a document. Where traditional OCR stops: text recovered, structure lost.使用 EasyOCR 解析扫描的 PDF:免费 OCR 给你单词,而不是文档。传统 OCR 停止的地方:文本恢复,结构丢失。
- Making a PDF’s images searchable for RAG, without paying to read them all. The image cascade: filter cheap, classify, describe only what is worth reading.使 PDF 图片可搜索,无需付费读取全部。图片级联:廉价过滤、分类、仅描述值得阅读的内容。
- Reconstructing the table of contents a PDF forgot to ship, so RAG can scope by section. Rebuilding toc_df when the PDF prints a contents page but ships no outline.重建 PDF 遗漏的目录,以便 RAG 可以按章节限制范围。当 PDF 打印了内容页但没有提供大纲时重建 toc_df。
Question parsing问题解析
- RAG questions need parsing too: turn the user’s string into briefs for retrieval and generation. The thesis of question parsing: why a user string needs the same parsing as a document, and how it splits into a retrieval brief and a generation brief.RAG 问题也需要解析:将用户字符串转化为检索和生成的简报。问题解析的论点:为什么用户字符串需要与文档相同的解析,以及它如何拆分为检索简报和生成简报。
- What the question parser extracts from a user string: keywords, scope, shape, decomposition, clarification. The five families of columns the parser reads straight from the user’s question, with the code that fills each one.问题解析器从用户字符串中提取什么:关键词、范围、形状、分解、澄清。解析器直接从用户问题中读取的五类列,以及填充每一列的代码。
- Dispatching the parsed RAG question: chunk strategy, model tier, activations, audit. The decisions the parser makes on top of the user string, using the document’s profile: dispatch, activations, full schema, the audit trail (pipeline_trace.json), and a broker-corpus walkthrough.
- The Clarification Loop and Learned Defaults: When the Question Is Not Precise Enough. One focused clarification when the question is too vague, and the default learned from the answer.澄清循环和学习到的默认值:当问题不够精确时。当问题太模糊时进行一次集中的澄清,以及从答案中学习到的默认值。
Retrieval检索
- Retrieval is filtering, not search: a mental model for enterprise RAG. Retrieval reframed as filtering on line_df and toc_df: anchors small, context large.检索是过滤,而不是搜索:企业 RAG 的心理模型。检索被重构为对 line_df 和 toc_df 的过滤:锚点小,上下文大。
- Anchor detection for RAG: parallel detectors, then one LLM call at the end. Parallel anchor detectors: keyword always, embeddings alongside, one LLM call at the end.RAG 的锚点检测:并行检测器,最后一次 LLM 调用。并行锚点检测器:始终使用关键词,同时使用嵌入,最后进行一次 LLM 调用。
- Letting an LLM pick the right RAG page: the arbiter pattern at the end of retrieval. The LLM arbiter: candidates ranked with reasons, one typed JSON out.
- A structure for context engineering: the four typed pieces behind every RAG answer. Context engineering given a structure: the four typed pieces (fixed system prompt, retrieved lines, doc-context block, PromptContext wrapper) that fill one single-document RAG LLM call.上下文工程的结构:每个 RAG 答案背后的四个类型化片段。给定结构的上下文工程:填充单个文档 RAG LLM 调用的四个类型化片段(固定系统提示词、检索到的行、文档上下文块、PromptContext 包装器)。
Generation生成
- Make RAG generation return a typed contract: citations, typed values, and self-checks (link to come). The answer schema as the contract: typed values, items with evidence spans, self-assessment fields, and the completeness signal the pipeline computes itself.使 RAG 生成返回类型化契约:引用、类型化值和自检(链接待补充)。作为契约的答案模式:类型化值、带证据跨度的项目、自我评估字段以及流水线自身计算的完整性信号。
- Assemble each RAG generation prompt from a base prompt plus the rules each question needs. The dispatcher: a fixed BASE prompt plus the rules each question needs, the schema picked from the registry, and the full trace kept on every call.从基础提示词加上每个问题所需的规则组装每个 RAG 生成提示词。分发器:固定的 BASE 提示词加上每个问题所需的规则,从注册表中选择的模式,以及每次调用时保留的完整追踪。
- Validating the RAG answer before the user sees it: spans, quotes, and the feedback loop (link to come). The post-generation validator (spans, verbatim quotes, formats), not-found as a first-class answer, and the feedback loops that close the pipeline.在用户看到之前验证 RAG 答案:跨度、引用和反馈循环(链接待补充)。生成后验证器(跨度、逐字引用、格式)、作为一等答案的“未找到”,以及关闭流水线的反馈循环。
One-document pipelines单文档流水线
- A production RAG pipeline for PDFs: relational parsing, TOC retrieval, typed answers (link to come). Each of the four bricks upgraded one contract at a time: relational parsing, corpus-aware questions, TOC-routed retrieval, typed answers.用于 PDF 的生产级 RAG 流水线:关系解析、目录检索、类型化答案(链接待补充)。四个积木中的每一个都按契约一次升级一个:关系解析、语料库感知问题、目录路由检索、类型化答案。
Same direction as the article:与文章方向相同:
- Anthropic, Contextual Retrieval (Sept 2024 engineering post). The closest published “minimal but production-grade” upgrade write-up; lands on hybrid retrieval + reranking, complements the TOC-aware brick upgrade in this article.Anthropic, Contextual Retrieval (2024 年 9 月工程博客)。最接近的已发布“最小化但生产级”升级文章;落脚于混合检索 + 重排序,补充了本文中的目录感知积木升级。
- OpenAI, Structured Outputs. The
responses.parse(text_format=Schema)pattern used at the question-parsing and generation boundaries.OpenAI, Structured Outputs。在问题解析和生成边界使用的 responses.parse(text_format=Schema) 模式。 - Vaswani et al., Attention Is All You Need, NeurIPS 2017 (arXiv:1706.03762). The paper we ran the pipeline on; first test case in section 1.1. arXiv non-exclusive distribution license, declared on the arXiv abstract page.Vaswani 等人, Attention Is All You Need, NeurIPS 2017 (arXiv:1706.03762)。我们运行流水线的论文;第 1.1 节中的第一个测试用例。arXiv 非排他性分发许可,在 arXiv 摘要页面声明。
- NIST, The NIST Cybersecurity Framework (CSF) 2.0, NIST CSWP 29, February 2024 (DOI 10.6028/NIST.CSWP.29). The compliance document used in section 2. US Government work, public domain in the US, see the NIST copyright statement.NIST, The NIST Cybersecurity Framework (CSF) 2.0, NIST CSWP 29, 2024 年 2 月 (DOI 10.6028/NIST.CSWP.29)。第 2 节中使用的合规文档。美国政府工作,在美国属于公共领域,请参阅 NIST 版权声明。
- Lewis et al., Retrieval-Augmented Generation for Knowledge-Intensive NLP Tasks, NeurIPS 2020 (arXiv:2005.11401). The RAG paper itself, the third test in section 3. arXiv non-exclusive distribution license, declared on the arXiv abstract page.Lewis 等人, Retrieval-Augmented Generation for Knowledge-Intensive NLP Tasks, NeurIPS 2020 (arXiv:2005.11401)。RAG 论文本身,第 3 节中的第三个测试。arXiv 非排他性分发许可,在 arXiv 摘要页面声明。
- World Bank, Commodity Markets Outlook, April 2024 issue. The degenerate-TOC stress test (blank bookmark titles) in section 4. CC BY 3.0 IGO, as declared on the OKR publication page for April 2024.世界银行, Commodity Markets Outlook, 2024 年 4 月刊。第 4 节中的退化目录压力测试(空白书签标题)。CC BY 3.0 IGO,如 2024 年 4 月 OKR 出版页面所声明。
Runnable code paths call OpenAI services governed by OpenAI’s Terms of Use.可运行代码路径调用受 OpenAI 使用条款约束的 OpenAI 服务。
Different angle, different context:不同的角度,不同的上下文:
- Yao et al., ReAct: Synergizing Reasoning and Acting in Language Models, ICLR 2023 (arXiv:2210.03629). Founding paper of agentic RAG. The context is general-purpose tool-picking at runtime. Developing this line, where the four upgraded bricks become the agent’s audited toolkit, is follow-up work.Yao 等人, ReAct: Synergizing Reasoning and Acting in Language Models, ICLR 2023 (arXiv:2210.03629)。代理 RAG 的奠基论文。上下文是运行时的通用工具选择。发展这一方向,即四个升级后的积木成为代理的审计工具包,是后续工作。
- Lee et al., Can Long-Context Language Models Subsume Retrieval, RAG, SQL, and More?, 2024 (arXiv:2406.13121). The long-context-replaces-RAG upgrade path: skip parsing, skip retrieval, dump the whole document in. Empirical data on where this works and where it breaks.Lee 等人, Can Long-Context Language Models Subsume Retrieval, RAG, SQL, and More?, 2024 (arXiv:2406.13121)。长上下文取代 RAG 的升级路径:跳过解析,跳过检索,转储整个文档。关于它在何处有效以及何处崩溃的实证数据。







