1. AI Agent开发实战:从零构建具备工具调用能力的智能体
最近在开发一个需要调用外部工具的AI项目时,我发现市面上关于AI Agent开发的实战资料比较零散。经过几周的摸索和实践,我总结出一套基于LangChain框架的AI Agent开发方法,特别适合需要让AI系统具备工具调用能力的场景。下面我就来详细分享这个过程中的关键技术和踩过的坑。
AI Agent与传统聊天机器人的最大区别在于,它能够主动感知环境、进行多步推理、自主决策并调用外部工具来完成复杂任务。这种能力使得AI可以处理更复杂的业务场景,比如数据分析、知识检索、自动化流程等。在本文中,我将通过一个公司内部信息查询的案例,展示如何构建一个具备RAG知识库查询和数学计算能力的AI Agent。
2. AI Agent核心架构解析
2.1 Agent四大核心组件
一个完整的AI Agent通常由四个关键组件构成:
-
LLM(大语言模型):负责自然语言理解和生成,是Agent的"大脑"。在项目中我选用的是通义千问的qwen-plus模型,它在中文场景下表现优秀。
-
记忆系统:
- 短期记忆:保存当前对话的上下文历史
- 长期记忆:通过RAG(检索增强生成)技术接入的知识库
-
规划能力:Agent需要能够拆解复杂任务为多个子步骤,并决定何时调用哪个工具。这通常通过精心设计的prompt工程来实现。
-
工具使用:Agent可以调用的外部函数或API,比如计算器、数据库查询等。
2.2 工具调用机制详解
工具调用是AI Agent最强大的能力之一。它的工作流程可以概括为:
- 用户输入问题
- LLM分析问题并决定是否需要调用工具
- 如果需要,LLM生成工具调用请求(包括工具名和参数)
- 系统执行工具并返回结果
- LLM整合工具结果生成最终回复
这个流程可能循环多次,直到LLM认为已经收集到足够信息来回答用户问题。在我的实现中,最多允许5轮工具调用循环,防止无限循环。
3. 实战开发:构建公司信息查询Agent
3.1 环境准备与工具定义
首先需要安装必要的Python包:
bash复制pip install langchain langchain-community faiss-cpu
然后定义两个核心工具函数:
python复制@tool
def calculator(expression: str) -> str:
"""
计算数学表达式。需要精确计算时使用。
参数:
expression: 数学算式,如 "2 + 2" 或 "500 * 0.8"。
返回:
str: 计算结果,如 "4.0" 或 "400.0"。
"""
print(f" [工具调用] 计算器正在计算: {expression}")
try:
return str(eval(expression))
except Exception as e:
return f"计算错误: {e}"
@tool
def rag_search(query: str) -> str:
"""
从公司内部RAG知识库检索信息。
参数:
query: 查询字符串,如"项目预算"或"截止日期"。
返回:
str: 检索到的文档内容。
"""
# RAG实现代码...
重要提示:工具函数的文档字符串(docstring)非常关键!LLM完全依赖这些描述来决定是否以及如何调用工具。务必详细说明参数、返回值和功能。
3.2 RAG知识库实现细节
RAG(检索增强生成)是Agent长期记忆的核心。我的实现采用了FAISS向量数据库和DashScope的嵌入模型:
python复制from langchain_community.vectorstores import FAISS
from langchain_community.embeddings import DashScopeEmbeddings
# 初始化嵌入模型
embeddings = DashScopeEmbeddings(model="text-embedding-v1")
# 加载或创建向量数据库
if os.path.exists("faiss_index"):
ragdb = FAISS.load_local("faiss_index", embeddings, allow_dangerous_deserialization=True)
else:
# 分割文档并创建索引
text_splitter = RecursiveCharacterTextSplitter(chunk_size=25, chunk_overlap=5)
split_docs = text_splitter.split_documents(docs)
ragdb = FAISS.from_documents(split_docs, embeddings)
ragdb.save_local("faiss_index")
关键参数说明:
chunk_size=25:每个文本块的最大长度chunk_overlap=5:块之间的重叠字符数allow_dangerous_deserialization=True:允许加载本地序列化的FAISS索引
3.3 工具绑定与多轮对话实现
将工具绑定到LLM并实现多轮对话是核心难点:
python复制def run_agent(query: str):
# 初始化工具映射
tool_maps = {
"rag_search": rag_search,
"calculator": calculator
}
# 初始化LLM并绑定工具
llm = ChatTongyi(model_name="qwen-plus")
tool_llm = llm.bind_tools(tools=list(tool_maps.values()))
# 初始化消息历史
message = [HumanMessage(content=query)]
# 最多5轮工具调用循环
for i in range(5):
response = tool_llm.invoke(message)
message.append(response)
if not response.tool_calls:
return response.content
for tool_call in response.tool_calls:
# 执行工具调用
tool_func = tool_maps[tool_call["name"]]
tool_output = tool_func.invoke(tool_call["args"])
# 将工具结果加入消息历史
message.append(
ToolMessage(
content=tool_output,
tool_call_id=tool_call["id"],
name=tool_call["name"]
)
)
这个实现有几个关键点:
bind_tools()方法将工具描述注入LLM的prompt- 每次LLM响应可能包含多个工具调用请求
- 必须将工具执行结果封装为ToolMessage返回给LLM
- 需要限制最大循环次数防止无限循环
4. 安全风险与最佳实践
4.1 常见安全风险
在开发过程中,我遇到了几个重要的安全问题:
-
代码注入风险:使用eval()执行数学表达式非常危险,可能被利用执行任意代码。
-
敏感信息泄露:RAG系统可能返回不应公开的内部信息。
-
无限循环:Agent可能陷入工具调用的死循环。
4.2 安全加固方案
针对上述风险,我采取了以下防护措施:
1. 替换eval()实现
python复制import ast
import operator
def safe_eval(expr: str) -> str:
# 允许的操作符
allowed_operators = {
ast.Add: operator.add,
ast.Sub: operator.sub,
ast.Mult: operator.mul,
ast.Div: operator.truediv,
ast.Pow: operator.pow,
ast.BinOp: ast.BinOp
}
# 解析表达式
tree = ast.parse(expr, mode='eval')
# 验证语法树
for node in ast.walk(tree):
if isinstance(node, ast.Call):
raise ValueError("函数调用不被允许")
if isinstance(node, ast.Attribute):
raise ValueError("属性访问不被允许")
# 安全求值
return str(eval(compile(tree, '', 'eval'), {'__builtins__': None}, allowed_operators))
2. RAG访问控制
- 实现基于角色的访问控制
- 对敏感文档进行脱敏处理
- 记录所有查询日志
3. 循环防护
- 限制最大工具调用次数(如5次)
- 设置超时机制
- 监控资源使用情况
4.3 性能优化技巧
在实际使用中,我还总结出几个性能优化点:
-
工具描述优化:精简但清晰地描述工具功能,减少token消耗。
-
缓存机制:对常见查询结果进行缓存。
-
批量处理:支持批量工具调用,减少往返次数。
-
异步执行:并行执行不依赖的工具调用。
5. 典型问题排查指南
在开发过程中,我遇到了以下典型问题及解决方案:
5.1 工具未被正确识别
症状:LLM不调用预期工具,或调用错误工具。
排查步骤:
- 检查工具描述是否清晰完整
- 验证工具是否成功绑定到LLM
- 检查prompt模板是否支持工具调用
5.2 工具调用循环失控
症状:Agent陷入无限工具调用循环。
解决方案:
- 实现最大循环次数限制
- 添加超时机制
- 在prompt中明确限制工具使用条件
5.3 工具执行错误
症状:工具返回错误或异常结果。
排查步骤:
- 检查工具输入参数是否符合预期
- 验证工具函数本身的逻辑
- 检查权限和依赖项是否满足
5.4 性能瓶颈
症状:响应时间过长。
优化方向:
- 分析是LLM推理慢还是工具执行慢
- 考虑异步或并行处理
- 实现缓存机制
6. 扩展应用场景
基于这个框架,可以扩展出许多实用场景:
- 数据分析助手:集成SQL查询和可视化工具
- 自动化办公:连接邮件、日历和文档系统
- 智能客服:结合知识库和工单系统
- 教育辅导:集成题库和解题工具
在实际项目中,我发现AI Agent特别适合那些需要结合自然语言理解和结构化数据操作的场景。比如我们团队用它来自动分析周报数据并生成汇总报告,效率提升了3倍以上。
开发过程中最大的收获是:设计良好的工具接口和清晰的文档描述比模型本身的能力更重要。一个简单但描述清晰的工具,往往比功能强大但文档模糊的工具表现更好。
