大模型本身的知识截止于训练时间,对企业内部文档、数据库和实时业务数据一无所知。直接把私有数据塞进Prompt又受限于上下文窗口长度,成本也高得离谱。LlamaIndex针对这个痛点给出的答案是数据感知Agent:让Agent先判断要不要查数据、查哪份数据,再结合检索结果和工具调用来完成任务。这篇文章会从原理到代码完整走一遍构建流程。

一、什么是数据感知Agent,它和RAG有什么区别
先厘清概念。传统RAG(检索增强生成)是一条固定流水线:用户提问,系统检索相关文档片段,把片段拼进Prompt,交给模型生成答案。整个过程没有决策环节,无论问题是什么,都会执行同样的检索动作。这在简单问答场景够用,但遇到需要多步推理的任务就力不从心了。比如用户问"对比一下上季度和本季度的销售数据并给出分析建议",固定流水线无法决定先查哪个季度、查哪些指标、要不要再调用计算工具。
数据感知Agent的核心差异在于引入了推理循环。Agent收到任务后,会先思考(Thought),再决定行动(Action),可以是查询某个索引、调用外部工具,或者直接给出答案,然后观察行动结果(Observation),继续下一轮思考,直到问题解决。这就是经典的ReAct模式。LlamaIndex把数据源抽象成QueryEngineTool,让检索能力变成Agent可以按需调用的工具之一,实现了检索与推理的深度结合。
换句话说,RAG是Agent能力的一个子集。在LlamaIndex里,你可以把任意多个QueryEngine、检索器、甚至其他Agent都包装成工具,交给一个主Agent统一调度。这种架构天然适合数据分散在多个来源、任务需要跨数据源协作的场景。
二、核心组件拆解:Agent的思考链路是怎么运转的
LlamaIndex中构建Agent最常用的是ReActAgent。它内部维护一个消息列表,每轮循环会把系统提示词、历史对话、可用工具的定义一起发给模型。模型以特定格式输出思考过程和工具调用指令,框架解析后执行对应工具,把结果追加回消息列表,再次请求模型,直到模型输出最终答案或达到最大迭代次数。
工具的定义很关键。每个工具包含名称、描述和可调用函数,工具描述会直接影响Agent的决策质量——描述写得含糊,Agent就容易选错工具或在不该调用的时候调用。LlamaIndex提供了FunctionTool用于包装任意Python函数,QueryEngineTool专门用于包装查询引擎,后者会自动生成针对数据源的描述信息。
下面这段代码演示了最基础的构建方式:先为一个文档目录建立向量索引,再把索引包装成工具交给Agent。环境使用OpenAI模型,记得先设置环境变量OPENAI_API_KEY。
from llama_index.core.agent import ReActAgent
from llama_index.core.tools import QueryEngineTool
from llama_index.core import VectorStoreIndex, SimpleDirectoryReader
from llama_index.llms.openai import OpenAI
# 读取本地文档并构建向量索引
documents = SimpleDirectoryReader("./data").load_data()
index = VectorStoreIndex.from_documents(documents)
# 生成查询引擎,top_k设为3控制检索数量
query_engine = index.as_query_engine(similarity_top_k=3)
# 把查询引擎包装成Agent可用的工具
data_tool = QueryEngineTool(
query_engine=query_engine,
metadata={
"name": "company_docs",
"description": "包含公司内部产品手册、规章制度的文档库,"
"涉及公司相关问题时必须查询此工具",
},
)
# 创建ReAct Agent
agent = ReActAgent.from_tools(
[data_tool],
llm=OpenAI(model="gpt-4o-mini", temperature=0),
verbose=True,
)
response = agent.chat("公司年假制度是怎么规定的?")
print(response)
运行时打开verbose参数能看到完整的思考链:Agent会先输出"我需要查询公司文档"这类思考,接着调用company_docs工具,拿到检索结果后再组织答案。这个透明的执行过程对调试非常有价值,当Agent表现不符合预期时,第一件事就是查看它的思考轨迹,定位是工具描述不清、检索结果不准还是模型推理出了问题。
三、多数据源与自定义工具的组合实战
真实项目的数据往往分散在多个地方:产品文档在对象存储里,销售数据在数据库中,还有些结构化数据需要调用API获取。LlamaIndex的做法是为每类数据源建立独立的索引或工具,全部注册给同一个Agent。Agent根据工具描述路由请求,这就是它"数据感知"能力的直接体现。
下面的例子组合了向量检索工具、SQL查询工具和一个自定义Python函数工具。SQL工具使用SQLDatabase连接数据库后由NLSQLTableQueryEngine自动生成查询语句,自定义工具则演示了如何让Agent获得任意外部能力。
from llama_index.core.tools import FunctionTool
from llama_index.core import SQLDatabase
from llama_index.core.query_engine import NLSQLTableQueryEngine
from sqlalchemy import create_engine
# SQL工具:自然语言查数据库
engine = create_engine("sqlite:///sales.db")
sql_database = SQLDatabase(engine, include_tables=["orders"])
sql_tool = QueryEngineTool(
query_engine=NLSQLTableQueryEngine(sql_database, tables=["orders"]),
metadata={
"name": "sales_data",
"description": "订单数据库,包含订单金额、时间、地区等信息",
},
)
# 自定义工具:查询实时汇率
def get_exchange_rate(currency: str) -> str:
"""获取指定货币对人民币的汇率"""
rates = {"USD": "7.24", "EUR": "7.85", "JPY": "0.048"}
return rates.get(currency.upper(), "未找到该货币汇率")
rate_tool = FunctionTool.from_defaults(fn=get_exchange_rate)
agent = ReActAgent.from_tools(
[data_tool, sql_tool, rate_tool],
llm=OpenAI(model="gpt-4o-mini"),
context="你是一个企业数据助手,回答务必基于工具返回的真实数据。",
)
print(agent.chat("查一下上个月华东地区的订单总额,换算成美元是多少"))
这个例子中Agent需要串联两步:先调用sales_data拿到人民币金额,再调用汇率工具完成换算。这种多步工具编排正是Agent相对于单次RAG的核心优势。需要注意的是,工具数量不宜过多,实践中超过十个工具后路由准确率会明显下降,此时应考虑分层架构,用多个子Agent各管一组工具,再由顶层Agent分发任务,LlamaIndex的QueryPlanAgent和Agent嵌套写法都支持这种模式。
四、调优建议与常见坑点
检索质量决定Agent上限。向量检索默认参数在中文场景往往不够好,建议替换为效果更好的Embedding模型(如BGE系列),并在切分文档时控制chunk大小在300到500字之间,配合适当重叠,避免关键信息被切断。如果文档有明确结构(如标题、章节),优先用MarkdownNodeParser或HierarchicalNodeParser做结构化切分,检索命中率会有明显提升。
工具描述要写得具体且有区分度。多个数据源工具的描述如果高度相似,Agent会频繁选错。描述中应该写清楚数据覆盖的时间范围、主题领域和使用条件,比如"仅用于查询2023年之后的财务报表数据"就比"财务数据查询工具"好得多。另外,max_iterations参数默认值偏小,复杂任务下适当调大可以避免Agent思考到一半被截断。
记忆方面,ReActAgent默认只保留单轮对话上下文。多轮场景下需要显式配置记忆组件,可以从llama_index.core.memory导入ChatMemoryBuffer并设置token上限。如果希望Agent记住跨会话的历史偏好,就需要把记忆持久化到Redis或数据库,这在客服、私有助理类应用中几乎是必做的。最后提醒一点:Agent执行SQL等有副作用的操作时,务必给数据库账号配置只读权限,并设置查询超时,防止模型生成的错误语句拖垮生产库。
LlamaIndex数据感知AgentAgent框架修改时间:2026-09-15 22:02:45