1. AI Agent开发实战:从零构建具备工具调用能力的智能体
最近在开发一个能调用外部工具的AI智能体时,踩了不少坑,也积累了一些实战经验。不同于普通的聊天机器人,这类具备工具调用能力的AI Agent才能真正解决实际问题。今天就来分享一下我的开发历程和心得。
这个智能体的核心功能是能够根据用户需求,自主决定何时调用工具、调用哪些工具,并将工具返回的结果整合到最终回复中。比如当用户询问"公司计划是什么"时,它会调用RAG知识库搜索;当涉及数学计算时,则会调用精确计算器。下面我就详细拆解这个项目的实现过程。
2. AI Agent核心架构解析
2.1 智能体的四大核心组件
一个完整的AI Agent通常包含以下四个关键部分:
-
大语言模型(LLM):负责理解用户输入、生成回复和决策何时调用工具。我选用的是通义千问的qwen-plus模型,它在中文理解和工具调用方面表现不错。
-
记忆系统:
- 短期记忆:保存当前对话历史,让模型保持上下文连贯
- 长期记忆:通过RAG(检索增强生成)技术接入知识库
-
规划能力:决定任务执行流程,比如先搜索知识库再计算结果
-
工具调用:可以执行外部函数,如计算器、API调用等
2.2 工具调用的实现原理
工具调用的核心流程是这样的:
- 用户输入问题
- LLM分析问题,决定是否需要调用工具
- 如果需要,生成工具调用请求(包含工具名和参数)
- 执行对应的工具函数
- 将工具返回结果反馈给LLM
- LLM整合信息生成最终回复
这个过程可能需要进行多轮交互,直到LLM认为已经收集到足够信息来回答用户问题。
3. 实战开发:构建具备双工具的AI Agent
3.1 环境准备与依赖安装
首先需要安装必要的Python库:
bash复制pip install langchain langchain-community faiss-cpu dashscope
然后导入所需模块:
python复制import os
from langchain_community.chat_models.tongyi import ChatTongyi
from langchain_community.embeddings import DashScopeEmbeddings
from langchain_community.vectorstores import FAISS
from langchain_core.messages import ToolMessage, HumanMessage
from langchain_core.tools import tool
from langchain_core.documents import Document
from langchain_text_splitters import RecursiveCharacterTextSplitter
3.2 实现两个核心工具
3.2.1 精确计算器工具
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}"
安全提示:直接使用eval()存在安全风险,生产环境应该替换为更安全的计算方式,比如使用ast.literal_eval或专门的数学表达式解析库。
3.2.2 RAG知识库搜索工具
python复制@tool
def rag_search(query: str) -> str:
"""
从数据库中搜索与查询公司内部相关的文档,包括公司计划名,代号,截止日期等详细信息。
参数:
query (str): 要搜索的查询字符串。
返回:
str: 与查询相关的文档内容。
"""
# 示例公司数据
raw_text = """
【公司内部机密:代号"深蓝计划"】
1. 项目目标:开发一款能听懂猫语的翻译器。
2. 核心技术:基于Transformer的"喵声波"分析算法。
3. 团队暗号:如果有人问"今天天气怎么样?",必须回答"我想吃鱼"。
4. 截止日期:2026年12月31日。
5. 经费预算:仅剩50元人民币,主要用于购买猫条。
"""
RAG_PATH = "faiss_index"
docs = [Document(page_content=raw_text)]
text_splitter = RecursiveCharacterTextSplitter(chunk_size=25, chunk_overlap=5)
split_docs = text_splitter.split_documents(docs)
embeddings = DashScopeEmbeddings(model="text-embedding-v1")
if os.path.exists(RAG_PATH):
ragdb = FAISS.load_local(RAG_PATH, embeddings, allow_dangerous_deserialization=True)
else:
ragdb = FAISS.from_documents(split_docs, embeddings)
ragdb.save_local(RAG_PATH)
return "\n\n".join(doc.page_content for doc in ragdb.similarity_search(query, k=2))
3.3 工具绑定与多轮对话实现
python复制def run_agent(query:str):
# 工具映射
tool_maps={
"rag_search": rag_search,
"calculator": calculator
}
# 初始化模型并绑定工具
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):
print("="*20+"\n第"+str(i+1)+"轮\n"+query+"\n"+"="*20)
response = tool_llm.invoke(message)
message.append(response)
print(f"需要调用{len(response.tool_calls)}个方法")
# 如果没有工具调用,返回最终结果
if not response.tool_calls:
print("最终结果:" + response.content)
return
# 处理每个工具调用
for tool_call in response.tool_calls:
call_id = tool_call["id"]
func_name = tool_call["name"]
func_args = tool_call["args"]
# 安全检查:确保调用的工具存在
if func_name in tool_maps:
tool_func = tool_maps[func_name]
tool_output = tool_func.invoke(func_args)
print("工具调用:" + func_name + ",参数:" + str(func_args) + ",结果:" + tool_output)
else:
tool_output = f"错误: 工具 {func_name} 不存在。"
# 将工具返回结果加入对话历史
message.append(
ToolMessage(
content=tool_output,
tool_call_id=call_id,
name=func_name,
)
)
if __name__ == "__main__":
run_agent("公司计划是什么")
run_agent("公司的经费预算是多少,如果预算提高46%后多少")
run_agent("今天天气真好")
4. 关键技术与实现细节
4.1 工具函数的规范设计
每个工具函数都需要遵循特定规范:
- 使用
@tool装饰器标记 - 函数文档字符串必须清晰描述功能、参数和返回值
- 返回值必须是字符串类型
- 参数要明确类型提示
这是因为LLM会根据函数描述来决定是否以及如何调用该工具。
4.2 多轮对话控制
实现多轮对话时需要注意:
- 限制最大轮数(如5轮)防止无限循环
- 维护完整的对话历史(message列表)
- 正确处理工具返回结果(ToolMessage)
- 当LLM不再调用工具时及时终止循环
4.3 安全考量与风险防范
在实现工具调用时,有几个安全要点:
- 输入验证:检查工具参数是否符合预期
- 权限控制:限制工具的可访问范围
- 沙箱执行:危险操作应在隔离环境中运行
- 日志记录:详细记录所有工具调用
特别是计算器工具中的eval()调用,在生产环境中应该替换为更安全的实现方式。
5. 常见问题与解决方案
5.1 工具不被调用怎么办?
可能原因及解决方法:
- 函数描述不清晰:完善工具函数的文档字符串
- LLM能力不足:尝试更强大的模型或调整温度参数
- 提示词问题:在用户问题中明确提示需要计算或查询
5.2 工具调用结果不准确
排查方向:
- 检查工具函数本身的逻辑是否正确
- 确认传递给工具的参数是否符合预期
- 验证LLM是否正确解析了工具返回结果
5.3 性能优化建议
- 缓存工具结果:对相同输入直接返回缓存结果
- 并行工具调用:当多个工具可并行调用时同时执行
- 精简对话历史:只保留必要的上下文信息
6. 扩展思路与进阶方向
这个基础框架可以进一步扩展:
- 增加更多工具类型(如网络搜索、API调用)
- 实现工具组合使用(先搜索再计算)
- 加入验证机制确保工具调用合理性
- 开发可视化界面监控工具调用过程
我在实际开发中发现,工具描述的质量直接影响调用准确性。建议为每个工具编写详细的使用示例和注意事项,这能显著提升LLM的工具使用能力。
