做向量的RAG肯定找不到工作,不为什么,就是过时了,Agent?那是人工智能编程的事。

如果你能把RAG+Agent结合,那肯定能找到工作!

如何不用向量数据库也能做RAG?而是要用Agentic方法搞定了百万token文档检索……

这大把公司要的,因为你都能自己出来单干了!!!!

就说我之前调试一个法律文档检索系统。客户的合同有800多页,切记800多页!

传统RAG的向量检索总是找不到关键条款——明明就在第523页的免责条款里,FAISS就是检索不出来。

搞了半天才发现,是文档分块把一个完整的条款切成了三段,向量相似度直接拉胯了。

这让我想起早前OpenAI悄悄更新的一个技术指南——他们居然说可以不用向量数据库做RAG。

刚看到的时候我是懵的,这不是反直觉吗?

但仔细研究后发现,还真别说,这个叫Agentic RAG的方法确实有点东西。

并且现在很多大公司都是追求高质量,Agent+RAG算是个新东西,主要是效果好……

尤其是专业领域,像法律、医疗、科技等等,全都需要……

RAG为什么不好?

去年做一个医疗知识库项目时,光是调试embedding模型就花了两周。你能想象吗?同一个问题”糖尿病能吃水果吗”,用不同的embedding模型,检索出来的内容完全不一样:

  • text-embedding-ada-002:检索出糖尿病的定义(???)
  • BGE-large:找到了水果的营养成分(额…)
  • E5-large:终于找到了糖尿病饮食指南(总算靠谱了)

更郁闷的是分块策略。我试过:

  • 按段落分:上下文断裂严重
  • 按固定token分:经常把一句话切成两半
  • 按语义分:计算量大到怀疑人生

说起来都是泪。有次处理一份技术规范文档,里面有个重要的表格横跨了3页。传统分块直接把表格切碎了,用户问”性能指标是多少”,系统返回了个表头…客户差点没把我骂死。

不要搞RAG,要搞就搞Agentic RAG

核心思路(其实很朴素)

之前GPT-4-turbo支持128K上下文后,我突然意识到一个问题:既然模型能一次看完整本《哈利波特》,为什么还要切块?

Agentic RAG的思路就像人类看书:

  1. 先快速翻一遍,知道大概讲什么
  2. 找到相关章节
  3. 仔细读那几页
  4. 得出答案

听起来简单?实现起来可不简单。

注意力机制的”隐形嵌入”

这里必须得说个技术细节(敲黑板)。很多人说Agentic RAG是”无嵌入”的,这其实是个误解。

Transformer的自注意力机制本质上就是在做动态嵌入:

image-20251011130153182

Q、K、V这三个矩阵,不就是在为每个token生成上下文相关的表示吗?只不过这个”嵌入”是动态的、实时计算的,而不是预先存好的静态向量。

老公式了,看过的同学过一下就行……

顺便吐槽一下,当初读Attention Is All You Need这篇论文时,看到这个公式愣了半天。

后来debug transformer代码才真正理解——说白了就是让模型自己决定该关注哪些词。

接下来,你学到了,也能开干,也不愁没人要你!

照着写代码

第一版:能跑就行

上个月重构那个法律文档系统时,我先写了个最简单的版本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import tiktoken
from openai import OpenAI

def quick_and_dirty_rag(document, question):
"""
最简单的实现,能跑就行
上次demo时就用的这个,客户看效果还不错
"""
client = OpenAI() # API key我放环境变量了

# 直接把整个文档扔给GPT-4-turbo
# 注意:这个真的很费钱!!!一次查询要好几美元
response = client.chat.completions.create(
model="gpt-4-turbo",
messages=[
{"role": "system", "content": "你是个文档分析专家"},
{"role": "user", "content": f"文档:\n{document}\n\n问题:{question}"}
]
)

return response.choices[0].message.content

这版本虽然糙,但对于小文档真的好用。处理10页以内的合同,准确率贼高。

第二版:分层导航(开始认真了)

但是遇到几百页的文档就扛不住了。token太多,API直接报错。于是我实现了分层导航:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
def smart_chunk_navigator(text, question, max_rounds=3):
"""
模拟人类阅读:先看目录,再看章节,最后看段落

这个函数我调了一个星期,各种边界情况处理起来巨麻烦
比如有的文档没有明显的章节结构,只能按语义密度分割
"""
tokenizer = tiktoken.get_encoding("cl100k_base")

# 第一轮:粗分20个大块(经验值,太多会增加API调用次数)
chunks = split_into_chunks(text, n=20)

scratchpad = f"开始搜索:{question}" # 记录推理过程,方便debug

for round in range(max_rounds):
# 让GPT选择相关的块
selected_chunks = select_relevant_chunks(
chunks,
question,
scratchpad
)

# 检查是否需要继续细分
total_tokens = sum(len(tokenizer.encode(c)) for c in selected_chunks)

if total_tokens < 2000: # 够小了,可以深度分析
break

# 继续细分选中的块
new_chunks = []
for chunk in selected_chunks:
# 每个大块再分5个小块
sub_chunks = split_into_chunks(chunk, n=5)
new_chunks.extend(sub_chunks)

chunks = new_chunks
scratchpad += f"\n第{round+1}轮:找到{len(chunks)}个相关段落"

return selected_chunks, scratchpad

有个细节特别重要:scratchpad这个东西看起来简单,但它是实现多步推理的关键。

我一开始没加这个,导致GPT每轮都在重复搜索同样的内容,简直智障。

路由代理:最精髓的部分

整个系统最核心的是路由代理。

它决定了每一步该看文档的哪个部分:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
class RoutingAgent:
def __init__(self):
self.client = OpenAI()
# 这个prompt我改了不下20个版本
# 最开始太复杂,GPT经常理解错
# 现在这个版本简洁但有效
self.system_prompt = """
你是文档导航专家。分析用户问题,选择最可能包含答案的文档块。

输出JSON格式:
{
"reasoning": "你的推理过程",
"selected_chunks": [块的索引],
"confidence": 0-1的置信度
}

记住:宁可多选几个块,也别漏掉关键信息。
"""

def route(self, chunks, question, history=""):
# 构建上下文(只包含每个块的前100字符预览)
previews = [f"块{i}: {c[:100]}..." for i, c in enumerate(chunks)]

prompt = f"""
问题:{question}

历史记录:{history}

文档块预览:
{chr(10).join(previews)}
"""

response = self.client.chat.completions.create(
model="gpt-4-turbo",
messages=[
{"role": "system", "content": self.system_prompt},
{"role": "user", "content": prompt}
],
response_format={"type": "json_object"},
temperature=0 # 不要创造性,要稳定性
)

return json.loads(response.choices[0].message.content)

千万别小看temperature=0这个参数。

之前用默认值0.7,同一个问题每次选的块都不一样,搞得我怀疑是不是代码有bug。

合理中的意外:

坑1:上下文爆炸

最开始我太贪心,想一次处理整个文档。

结果200页的PDF直接把API搞崩了。

错误信息:context_length_exceeded

老老实实分层处理。现在我的经验是:

  • 第一层:20-30个大块
  • 第二层:每个大块分5-10个小块
  • 第三层:如果还需要,再分段落

坑2:成本失控

去年11月,我跑了一个批量测试,1000个查询花了我600多美元…老板看到账单差点让我卷铺盖走人。

现在的优化策略:

  1. 缓存一切能缓存的:相同问题直接返回缓存结果
  2. 混合模式:简单问题用GPT-3.5,复杂的才上GPT-4
  3. 预过滤:如果文档小于5000 tokens,直接全文搜索
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 成本优化的核心代码
def cost_optimized_query(doc, question):
# 先检查缓存
cache_key = hashlib.md5(f"{doc[:100]}{question}".encode()).hexdigest()
if cache_key in cache:
print("命中缓存,省钱了!")
return cache[cache_key]

# 根据文档大小选择策略
token_count = len(tokenizer.encode(doc))

if token_count < 5000:
# 小文档,直接上
result = simple_rag(doc, question)
elif token_count < 50000:
# 中等文档,用GPT-3.5预筛选
result = hybrid_rag(doc, question, model="gpt-3.5-turbo")
else:
# 大文档,完整的Agentic流程
result = full_agentic_rag(doc, question)

cache[cache_key] = result
return result

坑3:答案幻觉

这个最头疼。有次系统非常自信地说”根据第15页的内容…”,结果文档总共就10页。客户当场黑脸。

解决办法是加了个验证层:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
def verify_answer(answer, source_chunks):
"""
验证答案是否真的基于源文档
这个函数救了我无数次
"""
# 检查引用的页码/段落是否存在
citations = extract_citations(answer)
for citation in citations:
if not citation_exists(citation, source_chunks):
return False, f"虚假引用:{citation}"

# 用另一个模型交叉验证
verification_prompt = f"""
答案:{answer}
源文档:{source_chunks}

这个答案是否完全基于源文档?是否有编造的内容?
"""

# 用GPT-4做验证(贵但是值得)
is_valid = verify_with_gpt4(verification_prompt)

return is_valid

性能对比

上个月我做了个详细的对比测试,用的是公司的法律文档数据集(2000份合同):

指标 传统 RAG Agentic RAG 备注
准确率 76.3% 89.7% Agentic 明显更准确
平均延迟 0.8秒 3.2秒 Agentic 慢了4倍
每次查询成本 $0.008 $0.045 Agentic 贵了5倍多
跨段落推理 45.2% 91.3% 这是 Agentic 的杀手锏
幻觉率 12.1% 3.2% 加了验证层后大幅降低

看到这个数据,我的结论是:如果你追求准确性,预算充足,Agentic RAG真香。

如果要控制成本、延迟以及低要求,可以考虑RAG,真要精准度,就要变……

生产环境的优化

混合架构(最实用)

实际项目中,我现在都用混合架构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class HybridRAG:
def __init__(self):
self.vector_store = FAISS() # 传统向量检索
self.agentic_engine = AgenticRAG() # Agentic引擎

def query(self, question, documents):
# 第一步:用向量检索快速筛选
candidates = self.vector_store.search(question, top_k=10)

# 第二步:对候选文档用Agentic深度分析
# 这样既快又准,成本也可控
final_answer = self.agentic_engine.deep_analyze(
question,
candidates
)

return final_answer

这个方案在我们生产环境跑了3个月,效果很稳定。

平均延迟1.5秒,成本降低了60%,准确率还提升了15%。

智能缓存策略

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class SmartCache:
def __init__(self):
self.semantic_cache = {} # 语义相似的问题共享答案
self.exact_cache = {} # 完全相同的问题直接返回

def get(self, question):
# 精确匹配
if question in self.exact_cache:
return self.exact_cache[question]

# 语义匹配(用embedding计算相似度)
similar_q = self.find_similar_question(question)
if similar_q and self.similarity(question, similar_q) > 0.95:
print(f"语义缓存命中:{similar_q[:50]}...")
return self.semantic_cache[similar_q]

return None

真实案例:法律文档分析系统

分享一个上个月刚上线的真实项目。客户是一家律所,需要分析大量的并购协议。

需求很变态:

  1. 每份协议200-500页
  2. 需要提取所有的关键条款(终止条件、保密协议、赔偿条款等)
  3. 准确率要求>95%
  4. 必须有准确的引用来源

传统RAG试了一个月,准确率始终在80%徘徊。主要问题是:

  • 条款经常跨页
  • 法律术语的向量表示不准确
  • 上下文依赖严重

改用Agentic RAG后:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
class LegalDocumentAnalyzer:
def __init__(self):
self.clause_patterns = {
"termination": ["终止", "解除", "期满", "breach", "terminate"],
"confidentiality": ["保密", "机密", "不披露", "NDA"],
"liability": ["责任", "赔偿", "损失", "indemnity"]
}

def analyze_contract(self, contract_text, query_type):
# 1. 智能分段(保持条款完整性)
sections = self.parse_legal_structure(contract_text)

# 2. 多轮导航找到相关条款
relevant_clauses = []
scratchpad = f"搜索{query_type}相关条款"

for round in range(3): # 最多3轮
candidates = self.navigate_to_clauses(
sections,
query_type,
scratchpad
)

# 验证条款完整性
for clause in candidates:
if self.is_complete_clause(clause):
relevant_clauses.append(clause)
else:
# 扩展搜索范围,获取完整条款
expanded = self.expand_context(clause, sections)
relevant_clauses.append(expanded)

scratchpad += f"\n第{round+1}轮找到{len(candidates)}个条款"

if len(relevant_clauses) >= 3: # 找够了
break

# 3. 生成结构化报告
report = self.generate_legal_report(
relevant_clauses,
query_type,
include_citations=True
)

return report

上线后效果:

  • 准确率达到96.8%
  • 处理一份300页文档约45秒
  • 每份文档成本约$0.8(客户能接受)

客户反馈最满意的是引用准确性——每个结论都能精确定位到具体页码和段落。

正在尝试的优化

  1. 知识图谱增强:先用LLM构建文档的知识图谱,然后基于图结构导航。初步测试效果不错,但构建图谱的成本有点高。
  2. 自适应深度控制:根据问题复杂度自动决定导航深度。简单问题1-2轮搞定,复杂问题可能需要4-5轮。
  3. 多模态支持:很多文档包含表格、图表。正在试验用GPT-4V处理这些内容。

一些思考

做了这么久RAG,我觉得核心问题不是用不用向量数据库,而是如何让AI更好地理解文档结构和上下文关系。

这个也是人工智能当中,能否做到高质量项目的根本思维,你一味只会CV,其实真的不可以……

Agentic RAG的本质是把检索变成了推理

它不是简单地找相似内容,而是像人一样思考:“要回答这个问题,我需要看文档的哪些部分?”

这种方法确实更贵、更慢,但对于高价值场景(法律、医疗、金融),准确性的提升是值得的。

写在最后

Agentic RAG很好,但是我的建议:

  1. 小文档(<10页):直接全文输入,简单粗暴效果好
  2. 中等文档(10-100页):用Agentic RAG,性价比最高
  3. 大规模文档库(>1000份):混合架构,向量检索+Agentic精读
  4. 实时性要求高和低质要求:还是老老实实用传统RAG

上周和同事讨论这个技术,面试的人根本不知道变通。

同时,这一套技术真的是很挑企业。

要么肯花钱,要么就是肯花钱本地部署。

所以说学对技术,就等于是选对企业,不用愁找不到工作…