RAG 基础:从数据类型出发理解RAG
写给第一次接触 RAG 的你。读完本文,你不仅能说出 RAG 是什么,更能在面对自己的真实数据时,知道该选哪条技术路线。
目录
- 从一个例子说起
- [为什么需要 RAG](#为什么需要 RAG "#2-%E4%B8%BA%E4%BB%80%E4%B9%88%E9%9C%80%E8%A6%81-rag")
- [RAG 的核心思想:先查资料,再回答](#RAG 的核心思想:先查资料,再回答 "#3-rag-%E7%9A%84%E6%A0%B8%E5%BF%83%E6%80%9D%E6%83%B3%E5%85%88%E6%9F%A5%E8%B5%84%E6%96%99%E5%86%8D%E5%9B%9E%E7%AD%94")
- [RAG 的通用流水线](#RAG 的通用流水线 "#4-rag-%E7%9A%84%E9%80%9A%E7%94%A8%E6%B5%81%E6%B0%B4%E7%BA%BF")
- [核心:按数据类型选择 RAG 策略](#核心:按数据类型选择 RAG 策略 "#5-%E6%A0%B8%E5%BF%83%E6%8C%89%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B%E9%80%89%E6%8B%A9-rag-%E7%AD%96%E7%95%A5")
- 5.1 非结构化文本
- 5.2 关系型数据库
- 5.3 [JSON / 文档型数据库](#JSON / 文档型数据库 "#53-json--%E6%96%87%E6%A1%A3%E5%9E%8B%E6%95%B0%E6%8D%AE%E5%BA%93")
- 5.4 图数据库
- 5.5 多模态数据:图片、音频、视频
- 5.6 其他值得了解的数据类型
- [数据混杂怎么办:路由、质检纠错与 Agentic RAG](#数据混杂怎么办:路由、质检纠错与 Agentic RAG "#6-%E6%95%B0%E6%8D%AE%E6%B7%B7%E6%9D%82%E6%80%8E%E4%B9%88%E5%8A%9E%E8%B7%AF%E7%94%B1%E8%B4%A8%E6%A3%80%E7%BA%A0%E9%94%99%E4%B8%8E-agentic-rag")
- 选型决策速查表
- [如何评估一个 RAG 系统](#如何评估一个 RAG 系统 "#8-%E5%A6%82%E4%BD%95%E8%AF%84%E4%BC%B0%E4%B8%80%E4%B8%AA-rag-%E7%B3%BB%E7%BB%9F")
- 五个常见误区
- 小结与学习路线
1. 从一个例子说起
假设你在一家公司负责搭建内部智能助手"小智"。员工会问它各种问题:
| 员工的问题 | 答案藏在哪 |
|---|---|
| "差旅报销的流程是什么?" | PDF 制度文档、Wiki 页面 |
| "上个季度华东区的销售额是多少?" | 关系型数据库里的订单表 |
| "帮我找找和'轻量化运动水壶'描述相似的商品" | 商品库(JSON 文档) |
| "支付网关这个项目,前后参与过哪些人、依赖哪些系统?" | 人员/项目关系网络(图数据) |
| "这张架构图里,网关上游接的是什么服务?" | 一张图片 |
| "上周客户投诉电话里,大家主要在抱怨什么?" | 客服录音(音频) |
你会发现:问题的本质都是"回答问题",但答案存放的"容器"完全不同。
这就是本文想传达的核心观点------很多人学 RAG 时一上来就背各种技术名词(向量、Embedding、GraphRAG......),但其实更好的入门方式是:
先看你的数据长什么样,再决定用什么方法去检索它。
数据类型决定了钥匙的形状。接下来我们就沿着这条主线,把 RAG 讲清楚。
2. 为什么需要 RAG
大语言模型(LLM)很聪明,但它有几个天生的局限:
- 知识有截止日期。模型的训练数据停留在某个时间点,之后发生的事情它一无所知。
- 会产生幻觉。当它不知道答案时,有时不会说"不知道",而是自信地编一个听起来很像真的答案。
- 看不到私有数据。你公司的制度文档、数据库、内部代码,从来没有出现在它的训练集中。
你可能会想:现在的模型上下文窗口不是已经能塞几十万 token 了吗?把所有资料都塞进去不就行了?
不太行,原因有三:
- 贵。每次提问都塞几十万字,token 费用很高,响应也慢。
- 注意力会稀释。上下文越长,模型对中间内容的关注度越低(业内常说的 "Lost in the Middle" 现象),关键信息可能被忽略。
- 装不下。一个中型企业的全部数据,早就超过了任何模型的上下文上限。
所以我们需要一种机制:回答时只把"真正相关的少数资料"送给模型。这就是 RAG。
顺带说明:RAG 和长上下文不是二选一的关系。常见的最佳实践是先用 RAG 捞出精准的资料,再用长上下文对这批资料做深入的分析推理------两者互补,各司其职。
3. RAG 的核心思想:先查资料,再回答
RAG 全称 Retrieval-Augmented Generation(检索增强生成)。名字有点拗口,思想却很朴素:
在让模型回答之前,先去资料库里检索相关内容,把检索结果和问题一起交给模型,让它"有据可依"地作答。
可以把它类比成一场开卷考试:
- 闭卷考试(纯 LLM):全凭记忆答题,记不清就现编。
- 开卷考试(RAG):允许翻书。先翻到相关的那几页,再照着书写答案------准确率自然高得多。
注意一个关键细节:模型并不是把资料原样抄一遍 ,而是阅读理解后用自己的话组织答案。所以资料的质量直接决定答案的质量------检索环节做得好不好,比提示词写得漂不漂亮重要得多。这是初学者最容易忽略的一点。
4. RAG 的通用流水线
无论数据是文档、数据库还是图片,一个 RAG 系统都可以拆成两个阶段、七个步骤:
markdown
【离线 · 索引阶段】(数据入库时做一次)
加载/解析 ──→ 切块/结构化 ──→ 向量化/建索引 ──→ 存储
│
【在线 · 查询阶段】(每次提问都执行) │
│
问题理解 ──→ 检索 ←─────────────────────────────┘
│
▼
重排/过滤 ──→ 生成答案
离线索引阶段(数据进入系统时做一次,之后增量更新):
- 加载解析:把 PDF、网页、数据库表、音频等原始数据读进来,转成可处理的格式。
- 切块 / 结构化:把长内容拆成合适大小的单元;或者把数据整理成便于检索的结构。
- 向量化 / 建索引:把内容转成向量(Embedding),或建立其他形式的索引(全文索引、图索引等)。
- 存储:存入向量数据库、搜索引擎或图数据库。
在线查询阶段(每次提问都执行):
- 问题理解:理解用户意图,必要时改写问题。
- 检索:用合适的钥匙去开对应的锁,找出最相关的候选内容。
- 生成:把检索到的内容作为参考资料,连同问题一起交给 LLM,生成最终答案。
记住这个骨架。接下来所有内容,讲的都是不同数据类型如何影响第 2、3、6 步的做法。
关于示例代码 :本文示例基于 LangChain 1.0(Python),先安装核心依赖:
bashpip install -U langchain langchain-openai langchain-community langchain-text-splitters pypdf示例中的模型字符串(如
openai:gpt-5.5)可替换为任意 LangChain 支持的模型;Embedding 服务同理,代码结构不变。
5. 核心:按数据类型选择 RAG 策略
这是全文的重点。我们先建立一个判断原则:
检索的本质是匹配。数据是模糊的语义,就用语义匹配(向量);数据是精确的结构,就用结构匹配(查询语言)。
带着这个原则,我们逐类来看。
5.1 非结构化文本
典型来源:PDF、Word、Markdown、网页、Wiki、邮件、会议纪要、工单。
这是最经典、也是你最先会遇到的 RAG 场景。
难点在哪?
一段长文档没法整段塞给模型,必须切块(chunk);而切块会带来两个问题:
- 切断了语义:一刀切在第 500 字,可能正好把一句话腰斩。
- 语义鸿沟:用户问"钱花超了怎么找公司要回来",文档里写的却是"费用报销申请流程"------字面不同,意思相同。关键词搜索在这里会失效。
再加上第三个短板:搜到的资料不行,也照样拿去生成。切块粗暴、语义鸿沟、来者不拒------这就是最朴素的 Naive RAG 的三大经典短板。本节的增强技术和第 6 节的质检纠错,都是围绕它们展开的。
标准做法:向量检索
用 Embedding 模型把文字变成一串数字(向量)。语义相近的文本,向量在空间中的距离也近。于是"怎么要回钱"和"报销流程"虽然字面不同,向量却很接近------语义鸿沟就此填平。
直观感受一下(数字为示意,真实向量有上千维):
| 文本 | 向量(示意) |
|---|---|
| 钱花超了怎么找公司要回来 | 0.21, 0.85, 0.13, ... |
| 费用报销申请流程 | 0.23, 0.82, 0.15, ... |
| 今天中午吃什么 | 0.87, 0.11, 0.44, ... |
前两句话面完全不同,向量却挨得很近;和第三句就离得远了。
进阶增强(生产环境几乎必备):
| 技术 | 解决什么问题 | 一句话原理 |
|---|---|---|
| 混合检索(向量 + BM25 关键词) | 编号、型号、专有名词搜不到 | 向量管"意思相近",关键词管"字面精确",两路结果用 RRF 等算法融合成统一排序;生产环境几乎都应以它取代纯向量检索 |
| 重排(Reranker) | 检索结果里混着噪声 | Embedding 模型分别给问题和文档算向量,快但糙;Reranker 把"问题+文档"成对送进模型精打分,慢但准。生产上常做级联:先粗筛一两百条,再逐级精排到 5 条左右 |
| 查询改写(Multi-Query) | 用户问法千奇百怪 | 让 LLM 把问题改写成多个表述,分别检索再合并去重;代价是多一次改写调用和多路检索,改写跑偏还会引入无关文档 |
| HyDE | 问题很短、文档很长,向量空间里对不上 | 让 LLM 先凭空写一段"假答案"(不必准确),用假答案的向量去检索------它的文体和真文档更接近。适合 LLM 有基本领域认知的场景;冷门领域或私有术语慎用,编跑偏了反而更糟 |
这四招分别解决"搜得到、搜得准、问得清"。还有一类问题是"搜到的全是垃圾,模型还硬答"------它的解法(检索质检与自我纠错)与数据类型无关,统一放在第 6 节讲。
最小可运行示例(LangChain 1.0)
二十来行代码,就是一个能跑通的文本 RAG,每一步都对应第 4 节的骨架:
python
from langchain_community.document_loaders import PyPDFLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_openai import OpenAIEmbeddings, ChatOpenAI
from langchain_core.vectorstores import InMemoryVectorStore
# ---------- 离线索引:加载 → 切块 → 向量化 → 入库 ----------
docs = PyPDFLoader("差旅报销制度.pdf").load() # 1. 加载解析
chunks = RecursiveCharacterTextSplitter( # 2. 切块
chunk_size=500, chunk_overlap=80
).split_documents(docs)
embeddings = OpenAIEmbeddings(model="text-embedding-3-small")
vector_store = InMemoryVectorStore(embeddings) # 3+4. 向量化并入库
vector_store.add_documents(chunks) #(生产环境可换 Chroma / Milvus / Qdrant)
# ---------- 在线查询:检索 → 生成 ----------
retriever = vector_store.as_retriever(search_kwargs={"k": 5})
question = "钱花超了怎么找公司要回来?"
context = "\n\n".join(d.page_content for d in retriever.invoke(question))
llm = ChatOpenAI(model="gpt-5.5") # 模型可按需替换
answer = llm.invoke(
f"请仅依据以下资料回答问题,资料中没有的信息请直接说明。\n\n"
f"资料:\n{context}\n\n问题:{question}"
)
print(answer.text)
切块策略怎么选?
- 按结构切(优先考虑):文档本身有清晰标题层级(如产品手册)时,直接按章节、标题切块,便宜且效果好。
- 固定长度切:每块 300~800 字、相邻块保留 10%~20% 重叠。简单通用,适合起步。
- 语义切:计算相邻句子的相似度,在"话题转折处"下刀。适合话题跳来跳去的会议记录、访谈稿,但成本更高、阈值难调。
- 父子块技巧:用小块做检索(精准),命中后返回它所在的大块(上下文完整)。兼顾两头,长文档推荐。
什么时候文本 RAG 不够用? 当答案不落在任何"一段话"里,而是散落在多个实体、多条记录之间的关系中时------请往下看。
5.2 关系型数据库
典型来源:MySQL / PostgreSQL 里的订单表、用户表、财务流水。
先做一个思想实验:员工问小智------
"上季度华东区销售额最高的三个产品是什么?"
这个问题能用向量检索解决吗?不能。 因为答案不在某段文字里,而是需要对成千上万行数据做过滤、聚合、排序。你就算把所有订单行都向量化了,也"搜"不出一个 SUM 结果。
这印证了我们的判断原则:结构化的精确问题,要用结构化的查询语言来答。
标准做法:Text-to-SQL
流程只有四步:
- 把数据库的表结构(有哪些表、字段、含义、示例值)写进提示词;
- 让 LLM 把自然语言问题翻译成 SQL;
- 执行这条 SQL;
- 把查询结果交回给 LLM,用自然语言组织成答案。
sql
-- 第 2 步的产物示例:LLM 把上面的中文问题翻译成了这样一条 SQL
SELECT product_name, SUM(amount) AS total
FROM orders
WHERE quarter = '2026Q2' AND region = '华东'
GROUP BY product_name
ORDER BY total DESC
LIMIT 3;
用 LangChain 1.0 实现,核心思路是把"执行 SQL"包装成一个带安全约束的工具:
python
import sqlite3
from langchain.agents import create_agent
from langchain.tools import tool
@tool
def query_orders(sql: str) -> str:
"""对订单库执行只读 SQL 并返回结果。仅支持 SELECT。
表结构:orders(id, product_name, amount, region, quarter)。"""
if not sql.strip().lower().startswith("select"):
raise ValueError("只允许 SELECT 查询") # 语法白名单
conn = sqlite3.connect("file:orders.db?mode=ro", uri=True) # 只读模式打开
try:
rows = conn.execute(sql).fetchmany(50) # 行数上限,防慢查询
return str(rows)
finally:
conn.close()
agent = create_agent(
model="openai:gpt-5.5",
tools=[query_orders],
system_prompt="你是数据分析助手。回答数值问题前,必须先通过 query_orders 工具查询数据库。",
)
result = agent.invoke({"messages": [
{"role": "user", "content": "上季度华东区销售额最高的三个产品是什么?"}
]})
print(result["messages"][-1].text)
注意 @tool 函数的文档字符串(docstring)------它会成为工具的"说明书",模型靠它判断什么时候该用这个工具、怎么传参数。写得越清楚,调用越准确。
安全红线(务必重视)
生产环境绝不能把 LLM 生成的 SQL 直接丢给数据库执行。最低限度的防护:
- 使用只读账号,并限制可访问的表;
- SQL 审计:解析语句,只放行 SELECT,拦截任何写操作和危险函数;
- 设置超时和行数上限,防止一条慢查询拖垮业务库;
- 高危场景增加人工确认环节。
一个容易被忽略的高级技巧:结构化与语义检索混用
很多真实问题一半精确、一半模糊。比如:
"找出去年退货率超过 10% 的商品里,用户评论主要在抱怨什么。"
最好的做法是两步走 :先用 SQL 查出符合数值条件的商品 ID 列表,再拿这些 ID 作为过滤条件,去文本库中语义检索相关评论。SQL 负责算数,向量负责理解语义,两者是搭档,不是对手。
5.3 JSON / 文档型数据库
典型来源:MongoDB 集合、Elasticsearch 文档、商品 SKU、配置信息、用户画像。
这类数据介于前两类之间,是半结构化的:
- 有固定字段 :
price: 299、category: "运动户外"------适合精确过滤; - 也有自由长文本 :
description: "轻量便携,适合徒步和日常通勤......"------适合语义检索。
标准做法:过滤 + 向量检索(Filtered Vector Search)
核心思路一句话:数值和枚举字段做前置过滤,自由文本字段做语义搜索。
举个例子,用户问小智:
"有没有 300 元以内、适合新手徒步的登山鞋?"
拆解一下这个问题:
| 部分 | 性质 | 处理方式 |
|---|---|---|
| "300 元以内" | 精确条件 | 过滤:price <= 300 |
| "登山鞋" | 枚举类别 | 过滤:category = "登山鞋" |
| "适合新手、徒步" | 模糊语义 | 向量检索 |
执行顺序上,先过滤、后向量搜索能同时拿到"算得对"和"搜得准":
ini
候选集 = 文档库.filter(price <= 300, category == "登山鞋")
结果 = 向量库.search(embed("适合新手 徒步 舒适"), filter=候选集, top_k=5)
用 LangChain 1.0 实现:入库时把结构化字段放进 metadata,查询时用 filter 先收窄再搜:
python
from langchain_core.documents import Document
# 入库:自由文本做语义检索,结构化字段放 metadata 做过滤
docs = [
Document(
page_content="轻量防水,鞋底抓地力强,适合新手徒步和日常通勤。",
metadata={"category": "登山鞋", "price": 268},
),
# ... 更多商品
]
vector_store.add_documents(docs)
# 查询:精确条件交给 filter,模糊语义交给向量
results = vector_store.similarity_search(
"轻便舒适,适合新手",
k=5,
# Chroma 写法:多个条件必须用 $and 显式连接;不同向量库的语法差异较大,用前查文档
filter={"$and": [
{"category": {"$eq": "登山鞋"}},
{"price": {"$lte": 300}},
]},
)
实践建议
- 索引设计时就想清楚:哪些字段进过滤索引,哪些字段做 Embedding,不要把整份 JSON 无脑向量化------数字和日期本身没有"语义",向量化它们纯属浪费。
- 字段较多的文档,可以只把"标题 + 描述 + 标签"拼接后做 Embedding,把结构化字段留给过滤层。
- JSON 数据库(如 Elasticsearch 8.x、MongoDB Atlas)现在大多原生支持向量字段,一个库就能同时完成过滤和语义检索,架构可以很简单。
5.4 图数据库与知识图谱
典型来源:Neo4j 等图数据库,或散落在文档中、尚未成图的实体关系(人员↔项目↔系统↔部门)。
向量检索的盲区:多跳问题
回到开头的例子:
"支付网关这个项目,前后参与过哪些人、依赖哪些系统?"
假设语料里有这些零散的信息:
- 文档 A:"老张是支付网关项目的第一任负责人。"
- 文档 B:"支付网关上线后由小李接手维护。"
- 文档 C:"支付网关依赖风控服务和账务系统。"
答案不在任何单独一段话里 ,而是要把三条信息沿着关系串起来。向量检索只能分别找到它们,拼装推理则全靠模型临场发挥,容易漏、容易错。
标准做法:GraphRAG
分两步:
- 建图(离线):用 LLM 阅读原始文档,抽取实体(人、项目、系统、部门)和它们之间的关系,存入图数据库。这一步成本较高------每篇文档都要过一遍 LLM。
- 查图(在线) :从问题中定位起始实体 → 在图上沿关系扩展一到两跳,取回一张子图 → 把子图序列化成文字(如
老张 ---负责→ 支付网关 ←维护--- 小李)→ 交给 LLM 作答。 - 全局问题的秘密武器:社区摘要 。微软提出的 GraphRAG 还会用 Leiden 算法把图聚成一个个联系紧密的"社区",并让 LLM 为每个社区生成摘要。面对"这个产品线的技术债主要集中在哪些模块"这类全局汇总型问题,系统直接调用社区摘要作答,不必遍历全图------这正是它在全局类问题上显著优于向量 RAG 的原因(简单事实查询两者差不多)。
arduino
提问:"老张参与过哪些项目?"
↓ 定位实体
在图中找到"老张"
↓ 沿关系扩展 2 跳
取出子图:老张 ──负责──→ 支付网关 ──依赖──→ 风控服务
↓ 序列化成文字
LLM 组织成最终答案
要不要建图?先想清楚三件事
- 图谱构建和查询的成本显著高于向量索引;
- 如果你关心的是全局性、汇总性的问题("这个产品线的技术债主要集中在哪些模块?"),图的价值最大;
- 如果只是普通的事实查询,向量 RAG 往往就够,不必为了用图而用图。
一个务实的折中:不预先建图,查询时让 LLM 从检索到的几段文本中现场抽取关系再推理。成本更低,适合数据量不大的阶段。
5.5 多模态数据:图片、音频、视频
现实中大量信息根本不是文字:架构图、报表截图、客服录音、培训视频。纯文本 RAG 对它们束手无策。好消息是,处理思路可以归结为三条通用路线。
路线一:先转成文字(最常用,起步推荐)
把非文本内容"翻译"成文字,然后走普通的文本 RAG:
| 数据类型 | 转换手段 | 补充要点 |
|---|---|---|
| 图片(含截图、流程图) | OCR 提取文字;或用视觉模型(VLM)生成详细描述(caption) | 架构图/流程图建议让 VLM 描述"结构和连接关系",而不只是罗列元素 |
| PDF 里的表格 | 解析成 Markdown 或 CSV | 表格直接向量化效果很差,务必先结构化 |
| 音频(录音、播客) | 语音识别(ASR)转写 | 保留时间戳,方便答案溯源到"第几分钟" |
| 视频 | 关键帧抽取 + 逐帧 VLM 描述 + 音轨 ASR | 三者合并成带时间轴的结构化文本再入库 |
路线二:跨模态统一向量空间
用 CLIP 这类多模态编码模型,把文字和图片映射到同一个向量空间。这样就可以"用文字搜图片"------拿"海边日落"这句话的向量,直接匹配出语义相近的图片。适合以图为主、文字描述为辅的场景(素材库、商品图检索)。文本转文字路线做不到这种直接匹配,这是它独有的价值。
路线三:生成端支持多模态
检索到的如果包含原始图片,最后一步就不能用纯文本 LLM,而要用视觉语言模型(VLM),让它"看着图"回答。典型组合是:路线一负责"搜得到",路线三负责"答得准"。
选择建议:拿不准就从路线一开始。转文字方案组件成熟、调试直观;等文本化明显丢失关键信息(比如图表中的空间布局)时,再引入路线二、三。
5.6 其他值得了解的数据类型
除了上面五类,这三个类型在实践中也高频出现,提前了解一下:
① 代码库
- 切块要感知语法:按函数、类、文件为单位切,而不是按字数硬切------一个被拦腰截断的函数对模型毫无意义。
- 关键词索引反而更重要 :代码里充满精确标识符(
parseConfig、ERR_TIMEOUT),向量检索对这类字符串并不敏感,BM25 全文索引往往是主力,向量做辅助。 - 当前主流的 AI 编程工具,普遍采用"语义检索 + 关键词检索 + 让 Agent 主动调用搜索工具"的混合方式。
② 时序数据与日志
- 检索前先做时间范围和指标维度过滤("昨晚 8 点到 10 点的 ERROR 日志"),再对过滤后的结果做语义/模式检索。
- 数值趋势类问题("QPS 是不是涨了?")交给聚合查询,不要指望向量检索。
③ 表格文件(Excel / CSV)
- 小表:整表转成 Markdown 放进上下文,简单直接;
- 大表:入库(SQLite 即可)后走 Text-to-SQL;
- 有大量文字批注的表格,字段值做 Embedding,数值列做过滤,回到 5.3 的半结构化套路。
6. 数据混杂怎么办:路由、质检纠错与 Agentic RAG
真实系统里,五类数据几乎总是同时存在。员工问小智"对比一下华东和华北去年成交量,再总结下两区的客诉焦点",一句话就同时砸中了关系型数据库和文本文档。
这时硬编码"if 问题类型 A 就走管道 A"会越来越难维护。更优雅的做法是 Agentic RAG:
给 Agent 一组检索工具(向量搜索、SQL 查询、图遍历、图片理解......),让它自主决定:先查哪个 → 结果够不够 → 要不要换个工具再查 → 信息够了再作答。
sql
用户问题
↓
Agent(决策中枢:判断信息够不够,决定下一步)
├── 需要精确数值 ──→ SQL 工具
├── 需要语义资料 ──→ 向量检索工具
├── 需要关系推理 ──→ 图查询工具
└── 需要看图 ──→ VLM 工具
↓ 工具结果返回给 Agent,信息不够就换个工具或换个关键词再查
↓ 信息足够
生成最终答案
用 LangChain 1.0 的 create_agent,这套架构只需要三样东西:一组工具、一句系统提示、一个模型:
python
from langchain.agents import create_agent
from langchain.tools import tool
@tool
def search_docs(query: str) -> str:
"""语义检索制度文档库。适合回答流程、政策、规定类问题。"""
return "\n\n".join(d.page_content for d in doc_retriever.invoke(query)) # doc_retriever 见 5.1
@tool
def query_orders(sql: str) -> str:
"""对订单库执行只读 SQL。适合销量、金额、对比等精确数值问题。仅允许 SELECT。"""
... # 实现见 5.2
@tool
def search_graph(entity: str) -> str:
"""在项目关系图中查询实体间的关联。适合"谁参与过什么、依赖哪些系统"类多跳问题。"""
... # 图查询实现,思路见 5.4
agent = create_agent(
model="openai:gpt-5.5",
tools=[search_docs, query_orders, search_graph],
system_prompt=(
"你是公司助手小智。回答前先选择合适的工具收集信息;"
"如果结果不够,换一个工具或换个关键词再查;信息足够后再作答。"
),
)
result = agent.invoke({"messages": [
{"role": "user", "content": "对比华东和华北去年成交量,再总结两个区的客诉焦点"}
]})
print(result["messages"][-1].text)
注意系统提示里那句"信息不够就换个工具再查"------这正是 Agentic RAG 与固定管道的本质区别:检索策略不再是写死的流程,而是模型在循环中的自主决策。
好处是扩展新数据源只需注册一个新工具;代价是多轮 LLM 调用带来的延迟和成本。建议:单数据源、问题模式固定的阶段,老老实实用固定管道;数据源多了、问题开始"跨界"了,再升级到 Agent。
与数据类型无关的四个编排层增强
下面这些方案不改变"先查再答"的骨架,而是在流程编排上做增强,任何数据类型都用得上:
- Adaptive RAG(按复杂度路由):在一切开始前先判断问题复杂度。"今天周几"直接让模型答,根本不用检索;一般问题检索一次就够;复杂问题才走完整的"多工具 + 质检"流程。流量混杂的系统中,这一招能省下大量不必要的开销。复杂度分类器可以是一个微调的小模型,也可以直接用提示词让 LLM 判断,不必一开始就训练模型。
- CRAG / Self-RAG(检索质检与自我纠错) :CRAG 在检索和生成之间加一道"质检员",逐条判断资料和问题相不相关:相关就用;不相关就换来源重查(比如回退到 Web 搜索,或退回"资料库中未找到"的拒答出口,见第 9 节误区 5);模棱两可就两边合并。Self-RAG 更进一步,生成后还要自查"我的每条论断都有资料支撑吗",没有就重写。它们共同对付的问题是:搜到的资料不行,还硬答。
- Multi-Agent RAG(多智能体分工):动机是单个 Agent 的过载------让它同时负责理解意图、挑选工具、校验质量、组织答案,提示词会越写越长,决策质量随之下降,就像让一个人同时当程序员、教练和主持人。于是把它拆成多个专职角色:路由 Agent 分发问题、各领域 Agent 各管一摊、质检 Agent 复核答案、写作 Agent 统一输出。每个环节可以独立优化,代价是链路更长、成本更高,数据源和权限复杂的企业级知识库才值得上。
- Speculative RAG(推测式生成):延迟敏感的场景,可以把检索资料分成几组,用小模型并行各写一版草稿,再由大模型一次性验证、挑出最佳答案,缩短总耗时。它解决的不只是慢:资料全塞进一个提示词时,一条噪声资料就可能带偏整份答案,多草稿竞争能显著降低这种风险。
一句话总结:按数据类型选对检索方式是地基,这些编排技巧是地基之上的装修。
7. 选型决策速查表
前八行按数据类型选"怎么搜",后四行与数据类型无关,选"怎么编排":
| 你的情况 | 典型问题 | 推荐路线 |
|---|---|---|
| PDF / Wiki / 工单等长文本 | "报销流程是什么?" | 文本切块 → 混合检索 → 重排 |
| 关系型数据库 | "上季度销售额 Top 3?" | Text-to-SQL(务必加安全防护) |
| JSON / 商品库等半结构化数据 | "300 元以内的徒步鞋?" | 字段过滤 + 向量检索 |
| 实体关系网络 | "这个项目依赖哪些系统?" | GraphRAG 或现场关系抽取 |
| 图片 / 音频 / 视频 | "这张架构图里网关上游是什么?" | 先转文字;图多再上跨模态向量 |
| 代码仓库 | "这个函数在哪被调用?" | 语法感知切块 + 关键词检索为主 |
| 日志 / 时序数据 | "昨晚的报错在抱怨什么?" | 时间维度过滤 + 语义检索 |
| 多种数据混合 | "对比销量并总结客诉" | 路由 / Agentic RAG |
| 用户问法口语化、措辞多变 | 同一个意思有一百种问法 | 查询改写(Multi-Query / HyDE) |
| 不能容忍幻觉 | 医疗、法律、金融答复 | 检索质检与纠错(CRAG / Self-RAG) |
| 问题复杂度差异大 | 从闲聊到多文档综合分析 | Adaptive RAG 路由 |
| 对响应速度敏感 | 在线客服、实时对话 | Speculative RAG |
8. 如何评估一个 RAG 系统
"感觉变好了"不是优化依据。建议从四个维度建立量化评估(RAGAS 等框架用的就是这套指标):
| 维度 | 问的问题 | 定位问题所在 |
|---|---|---|
| 上下文召回率 | 该找到的资料,找到了吗? | 低 → 改切块、改检索、加查询改写 |
| 上下文精确率 | 找到的资料里,有用的占多少? | 低 → 加重排、加过滤 |
| 忠实度 | 答案是否严格基于检索到的资料? | 低 → 收紧提示词、加强引用校验 |
| 答案相关性 | 答的是不是用户问的? | 低 → 改问题理解、改生成提示 |
操作上,先人工写 30~50 个真实问题和标准答案组成测试集,每改一处就跑一遍。这套流程比任何"玄学调参"都有效。
9. 五个常见误区
- 一上来就堆全家桶。Naive RAG + 人工看 badcase,永远是你该迈出的第一步。先跑通最简单的,再针对暴露的问题逐个升级。
- 只调提示词,不调检索。答案不好时,十有八九问题出在"找错了资料"而不是"没嘱咐好模型"。先检查检索结果,再动提示词。
- 忽视数据质量。文档本身版本混乱、内容冲突,RAG 只会把混乱放大。数据治理的投入回报常常高于算法优化。
- 所有数据都无脑向量化。数字、日期、枚举值没有语义,向量化它们既浪费又引入噪声。先分类:哪些该过滤,哪些该 Embedding。
- 没有兜底策略。检索不到任何相关内容时,系统应该明确回答"资料库中未找到",而不是逼着模型硬编。留好"拒答 + 转人工"的出口。
10. 小结与学习路线
三句话带走全文:
- RAG 的核心思想只有一句:先查资料,再回答------把"闭卷考试"变成"开卷考试"。
- 所有 RAG 系统共享同一个骨架(索引 + 查询),数据类型决定每个环节的具体做法:模糊语义用向量,精确结构用查询语言,关系网络用图,非文本内容先转文字。
- 从最简单的方案开始,用测试集量化评估,让 badcase 告诉你下一步该升级哪个环节。
建议的动手路径:
- 用一份几十页的 PDF + 任意向量数据库,跑通最朴素的"切块 → 向量化 → 检索 → 生成";
- 加上混合检索和重排,对比效果;
- 找一个真实数据库表,实现一个带只读防护的 Text-to-SQL;
- 把文本和 SQL 两条链路用一个路由(或 Agent)串起来;
- 建立你的 30 题测试集,开始量化迭代。
工具生态方面,向量数据库(Milvus、Qdrant、Chroma)、编排框架(本文示例使用的 LangChain 1.0 / LangGraph、LlamaIndex)、一体化平台(Dify、RAGFlow)都值得按需了解------但请记住,工具会过时,本文的思考框架不会。
祝搭建顺利。