TL;DR#
这篇文章介绍了一种由 OpenAI Cookbook 提出的新型 RAG(检索增强生成)架构,旨在利用 GPT-4.1 和 Gemini 等模型百万级 Token 上下文窗口的能力,绕过传统的向量嵌入(Embeddings)和向量数据库,直接通过 LLM Agent 进行智能检索。
🛠️ 核心机制:模仿人类阅读的三步流程#
该方法不再依赖语义相似度匹配,而是将检索视为一个多步骤的推理过程:
- 路由与导航 (Routing & Navigation):
- 系统将文档切分(例如按章节),但不转化为向量。
- 核心 Agent 使用“草稿纸 (Scratchpad)”机制进行推理,像人类查阅目录或略读一样,递归地判断哪些文本块包含相关信息,通过层级向下钻取。
- 生成与引用 (Generate Answer):
- 在定位到精确的段落后,LLM 生成答案并强制要求附带精确的引用来源(Citations)。
- 答案验证 (The Judge):
- 引入“LLM 即裁判 (LLM-as-Judge)”步骤(如使用 o4-mini),对比生成的答案与源文档,验证事实准确性和引用正确性,以消除幻觉。
✅ 主要优势 (Pros)#
- 零基础设施门槛:无需选择嵌入模型、维护向量数据库或处理复杂的索引更新。
- 零输入延迟 (Zero-ingest Latency):新文档上传后可立即查询,非常适合频繁更新的数据。
- 更高的推理质量:通过“草稿纸”推理和全文档上下文理解,比单纯的语义匹配更能处理复杂的跨段落查询。
- 可信度高:内置的验证机制确保了答案有据可查。
⚠️ 权衡与挑战 (Cons)#
- 高昂的成本:每个查询需要多次 LLM 调用(路由、生成、验证),比廉价的向量搜索贵得多。
- 高延迟:多步推理和递归搜索导致响应速度较慢。
- 扩展性瓶颈:面对成千上万份文档的海量数据集时,如果不配合预过滤步骤,单纯依靠 Agent 遍历是行不通的。
🎯 适用场景#
- 适合:高价值、高精度的任务。例如复杂的法律简报分析、技术手册查询,或者数据动态变化且需要精确引用的场景。
- 不适合:需要毫秒级响应的通用聊天机器人,或处理海量静态数据的场景(此时传统 RAG 仍是首选)。
一句话总结:这是一种“用计算换架构”的范式转变——牺牲速度和成本,换取了更简化的架构和更高的检索准确性,实际上是将“嵌入”内化到了大模型的注意力机制之中。
RAG Without Embeddings#
如果你正在基于大型语言模型 (LLMs) 构建应用,你大概率已经与检索增强生成 (RAG) 打过交道了。对于利用外部数据来让 LLM 获取事实依据,这是一种极好的方法,但其中的“R”部分——检索 (Retrieval)——可能会相当复杂。
我深知这种痛苦——花几个小时纠结分块 (chunk) 的大小、重叠策略,以及该选哪种嵌入模型 (embedding model)。而且还要管理向量数据库 (vector databases)?那完全是基础设施层面的挑战。
因此,当我偶然发现 OpenAI 在其 Cookbook 中关于一种不同的 RAG 方法的最新探索时——一种承诺绕过传统嵌入的方法——我的开发者大脑顿时兴奋了起来。我们真的可以在没有向量数据库那些繁琐操作的情况下获得出色的检索结果吗?这个想法感觉真正打破了常规。
其中的秘诀似乎在于利用像 GPT-4 或 Gemini Flash 这样较新模型的海量上下文窗口 (context windows)。能够一次性处理一百万个 token 的模型开启了全新的工作流。但是,上下文窗口的大小是唯一起作用的因素吗?
超越标准 RAG 流程#
传统的 RAG 流程非常直截了当:
- 输入 (Ingest):分割文档,对文本块进行嵌入,并存储在向量数据库中。
- 查询 (Query):对查询内容进行嵌入,在数据库中搜索相似的文本块。
- 增强与生成 (Augment & Generate):将文本块和查询一起投喂给 LLM 以获取答案。
这需要繁重的预处理工作,还得维护那个向量索引。这是一种稳健的方法,但它引入了延迟、存储开销以及对嵌入质量的依赖。此外,分块 (chunking) 过程本身可能导致上下文丢失,相关信息被分割到不同的块中,从而导致检索遗漏。
Agentic 方法:模仿你的大脑#
OpenAI 的 Agentic RAG 则截然不同。它是一个多步骤的过程,模仿了你处理长文档以寻找答案的方式。你不会通读每一个字;你会略读,找到有希望的章节,深入研读,然后提取相关内容。
这种方法使用不同的 LLM 调用,在每个阶段充当专门的“智能体 (Agents)”。这就提出了一个有趣的问题:这种方法真的是“无嵌入”的吗?还是 LLM 本身在其注意力机制 (attention mechanism) 中隐式地创建并使用了一种形式的“语义表征”?
窥探代码:一个多步骤的过程#
加载文档 (Load the Document)
import requests
# ... other imports ...
def load_document(url: str) -> str:
"""Load a document from a URL and return its text content."""
print(f"Downloading document from {url}...")
response = requests.get(url)
response.raise_for_status()
# ... (pdf reading and text extraction) ...
return full_text
# Load the document (e.g., a legal manual with 900k+ tokens)
document_text = load_document("https://www.uspto.gov/sites/default/files/docume
初始分割 (Initial Split)
# ... (tokenizer import) ...
def split_into_20_chunks(text: str, min_tokens: int = 500) -> List[Dict[str, An
"""Split text into up to 20 chunks, respecting sentence boundaries."""
sentences = sent_tokenize(text)
tokenizer = tiktoken.get_encoding(TOKENIZER_NAME)
# ... (logic to build chunks from sentences and handle chunk size) ...
return chunks
document_chunks = split_into_20_chunks(document_text, min_tokens=500)
# This results in ~20 chunks, each potentially holding tens of thousands of tok
路由与导航(核心智能体) (Routing & Navigation (The Core Agent))
# ... (OpenAI client setup) ...
def route_chunks(question: str, chunks: List[Dict[str, Any]],
depth: int, scratchpad: str = "") -> Dict[str, Any]:
"""Ask the model which chunks contain info relevant to the question."""
# ... (system prompt defining role as document navigator) ...
user_message = f"QUESTION: {question}\n\n"
if scratchpad:
user_message += f"CURRENT SCRATCHPAD:\n{scratchpad}\n\n"
user_message += "TEXT CHUNKS:\n\n"
# ... (add chunks text with IDs to user_message) ...
# Step 1: Ask model to record reasoning in scratchpad (required tool call)
# ... (model call with tools and tool_choice="required") ...
# ... (process tool call, update scratchpad) ...
# Step 2: Ask model to select relevant chunk IDs (structured output)
text_format = { "format": { "type": "json_schema", "name": "selected_chunks
# ... (model call with text_format) ...
# ... (extract selected_ids from JSON response) ...
return { "selected_ids": selected_ids, "scratchpad": new_scratchpad }
navigate_to_paragraphs 函数随后会递归调用 route_chunks,在文档层级中向下钻取,直到内容颗粒度足够细。
生成最终答案 (Generate Final Answer)
from pydantic import BaseModel, field_validator
from typing import List, Literal
class LegalAnswer(BaseModel):
answer: str
citations: List[str]
@field_validator('citations')
def validate_citations(cls, citations, info):
# ... (logic to check if citations match passed valid IDs) ...
return citations
def generate_answer(question: str, paragraphs: List[Dict[str, Any]],
scratchpad: str) -> LegalAnswer:
"""Generate an answer from the retrieved paragraphs."""
# ... (prepare context string from paragraphs with IDs) ...
# ... (system prompt focusing on answering ONLY from provided text) ...
response = client.responses.parse(
model="gpt-4.1",
input=[
{"role": "system", "content": system_prompt.format(...)},
{"role": "user", "content": f"QUESTION: {question}\n\nSCRATCHPAD: {
],
text_format=LegalAnswer,
temperature=0.3
)
return response.output_parsed
使用 Pydantic 和 field_validator 的结构化输出可确保引用的有效性,并提高可追溯性。
答案验证(裁判)#
class VerificationResult(BaseModel):
is_accurate: bool
explanation: str
confidence: Literal["high", "medium", "low"]
def verify_answer(question: str, answer: LegalAnswer,
cited_paragraphs: List[Dict[str, Any]]) -> VerificationResult
"""Verify if the answer is grounded in the cited paragraphs."""
# ... (prepare context string from cited paragraphs with IDs) ...
system_prompt = """You are a fact-checker for legal information.
Your job is to verify if the provided answer:
1. Is factually accurate according to the source paragraphs
2. Uses citations correctly
# ... (instructions for confidence scoring) ...
"""
response = client.responses.parse(
model="o4-mini",
input=[
{"role": "system", "content": system_prompt},
{"role": "user", "content": f"QUESTION: {question}\n\nANSWER TO VER
],
text_format=VerificationResult
)
return response.output_parsed
这个 “LLM即裁判 (LLM-as-Judge)” 步骤充当了关键的最后一道防线,提高了系统的整体可靠性。
抽丝剥茧:深入审视 OpenAI 的 Agentic 策略#
虽然 OpenAI 对 Agentic 检索的尝试绝对是在挑战极限,但我们绝对有必要先踩一脚刹车,真正深入挖掘它可能在哪些地方受阻,以及它如何与老派的传统 RAG 进行较量。
昂贵的代价与漫长的等待 (The Price Tag and the Wait Time):
你注意到的第一件事是什么?高昂的成本和显著的延迟。与简单、快速地跳转到向量数据库不同,这种方法需要 LLM 针对每个查询多次执行操作。这会累积成巨大的成本——无论是算力还是等待时间。因此,这里隐约可见的大问题是:随着数据量变大、问题变得更棘手,这种金钱和时间上的负担实际上如何扩展?我们能不能削减一些开销?或许缓存一些中间步骤,或者仅在初始路由调用中使用更小、更灵活的模型会是明智之举。
扩展的阵痛 (Scaling Pains):
好吧,当你只是筛选一份文档时,感觉相当不错。但如果要跨越成千上万份文档进行搜索呢?那完全是另一回事了。 在这种规模下,你可能必须在文档级别预先过滤或索引内容;这不再是可选项,而是变成了必选项。感觉真正有前途的前进方向是一种混合架构——你使用某种粗略但快速的方法来迅速缩小可能性范围,然后针对那少数精选的内容释放 Agentic 系统进行深度推理。
不可避免的上下文瓶颈 (The Unavoidable Context Crunch):
即使最新的模型吹嘘上下文窗口延伸到数百万个 token——哇,对吧?——它们仍然会碰壁。对于真正海量或极其复杂的文档,这个 Agentic 系统仍可能遇到限制。接下来的难题就变成了:它如何优雅地处理那些刚刚突破这个限制的文档?也许未来的迭代会变得更聪明,或许使用迭代摘要或智能上下文压缩技术来绕过这个障碍。
并非所有查询都生而平等 (Not All Queries Are Created Equal):
你知道,问题也分三六九等。简单的、基于事实的问题?是的,那可能会相对轻松过关。但是那些多部分查询,或者有点抽象的问题呢?它们注定会更棘手。那个内部的“思维草稿 (scratchpad)”机制真的能为复杂的思考提供足够的支持吗,还是我们需要内置额外的工具或推理辅助?
机器中的幽灵:幻觉 (The Ghost in the Machine: Hallucination):
看,任何依赖 LLM 的系统都有胡编乱造的风险——即幻觉。这个 Agentic 流程中的多步验证环节正是为了对抗这一点而设计的。但它有多万无一失呢?是否存在“裁判”模型可能错过细微不准确之处的棘手边缘情况?老实说,做一个对比研究,比较传统 RAG 和这种新的 Agentic 方法之间的幻觉率,将会是非常有见地的。
嵌入:消失了,还是仅仅被融合了? (Embeddings: Gone, or Just Integrated?):
最后,有一点更具哲学意味但值得细细咀嚼:这真的是“无嵌入”的吗?因为在其运作深处,特别是在那个注意力机制内,LLM 绝对正在构建一种对输入的内部表征——可以说,那就是一种动态嵌入。这微妙地改变了我们看待整个过程的方式。Agentic 方法似乎并没有完全废弃嵌入,而是将它们直接编织进核心检索和推理循环中,改变了它们被使用的位置和方式,而不仅仅是抛弃它们。
优点和缺点:权衡与选择#
这种 Agentic 风格的 RAG 虽然不可否认地聪明,但肯定也伴随着它自己的一套权衡。在好的一面,你获得了极棒的零输入延迟 (zero-ingest latency)——这意味着你可以立即查询全新的文档,无需枯燥地等待预处理。它在文档中移动的方式有一种真正的优雅,有点像模仿人类阅读的方式,在需要的地方深入钻研。这种动态的、分层的导航通常可以挖掘出更精准的答案来回答错综复杂的问题,特别是通过连接不同部分的观点的交叉推理。此外,对于核心检索部分,你跳过了整个索引基础设施的令人头痛的问题,使得设置变得简单了不少。而且至关重要的是,特别是当准确性至高无上时,那个明确的、结构化的验证过程增加了一层至关重要的信任,确保它给你的答案真正由源材料支持。
但是,一如既往,这些好处不是免费的。该系统自然会产生更高的单次查询成本,因为每个问题会触发多次 LLM 调用,而不是仅仅一次快速、廉价的向量数据库查找。这意味着,是的,事情可能会花更长一点的时间——你可能会注意到由于那递归的文档探索而增加的延迟。最重要的是,这种特定的、无索引的实现与经典 RAG 相比,当你面对真正庞大的文档堆时,在可扩展性方面会感到有点受限,除非你加入一些初始的预过滤步骤。
知道何时使用它#
那么,考虑到这种取舍,这种方法在什么场景下真正大显身手?它似乎特别适合处理冗长、专业文档中的复杂问题——想想详细的法律简报或密集的能技术手册——在这些地方,把事实弄得完全正确,并能够用精确的引用指向它们,是绝对不可妥协的。对于总是处于变化中、不断更新的数据,它感觉也是对的,仅仅因为没有静态索引需要你不断重建。反过来说,如果你需要的是一个快如闪电的通用聊天机器人,需要瞬间从巨大的静态数据集中提取信息,传统的基于嵌入的 RAG 可能仍然是你的首选。
探讨未来的概念#
OpenAI 的头脑们自然正在思考这可以如何演变。一个想法是混合系统:也许使用传统的嵌入搜索来快速缩小范围,然后让 Agentic 系统在那个更小、更易于管理的池子中漫游和推理。另一个非常酷的可能性?利用那些海量的上下文窗口,仅一次性地从文档构建一个详细的知识图谱 (knowledge graph),作为一个前期任务,然后让一个不同的、也许更轻量的模型在那个图谱中快速穿梭寻找答案。这有点像是结合了两个世界的长处,你不觉得吗?最后,调节“颗粒度”——让用户轻松指定他们希望系统深入到什么程度,是需要句子级的证据(对法律工作至关重要)还是也许只是一个段落(对于筛选新闻文章可能完全没问题)。这全在于找到精准度与运营成本之间的那个最佳平衡点。
总结#
深入研究这种 Agentic RAG 方法确实在我的脑海中巩固了几件关键的事情。那些百万 token 的上下文窗口?彻底改变了游戏规则,实现了这种流畅的、即时的文档探索。它阅读的方式,在层级中移动,感觉很自然,绝对有助于搞定更棘手问题的相关性。拥有那个思维草稿 (scratchpad)?它通过给模型空间来一步步解决问题,真正提升了推理能力。而且事实上,你可以在不需要整个数据库设置的情况下(至少对于核心检索部分)相当快地开始,这绝对是一个胜利,取决于你试图实现什么。但至关重要的是,那个内置的检查让你更信任答案——知道它们实际上是直接从源头提取并经过验证的。
总的来说?这个 Agentic RAG 的东西真的令人兴奋。它真正展示了 LLM,特别是那些拥有大上下文窗口和 Agentic 能力的 LLM,如何以新鲜、强大的方式破解复杂的信息问题——尤其是当“把事情做对”和“深入理解”比“快如闪电”或处理“暴力堆量”更重要时。