LangChain实战:构建具备RAG与计算能力的AI Agent
1. 从零构建AI AgentLangChain实战指南最近在开发一个需要结合知识检索和数学计算的智能助手项目时我深入研究了LangChain框架的Agent机制。与普通聊天机器人不同Agent能够自主调用工具、进行多轮决策真正实现会思考的AI系统。下面分享我的实战经验手把手教你构建一个具备RAG知识库查询和精确计算能力的智能Agent。2. AI Agent核心架构解析2.1 Agent的四大核心组件一个完整的AI Agent需要具备以下能力大脑LLM使用通义千问qwen-plus作为推理引擎记忆系统短期记忆维护对话历史message数组长期记忆基于FAISS构建的RAG知识库规划能力通过多轮工具调用实现复杂任务分解工具集本例包含两个关键工具公司知识检索rag_search数学计算器calculator2.2 工具调用机制详解LangChain的工具调用流程遵循以下范式用户输入查询HumanMessageLLM判断是否需要调用工具若需调用返回工具名称和参数tool_calls执行具体工具函数将结果封装为ToolMessage返回对话历史LLM基于工具结果生成最终回复这个过程中最精妙的是第5步——通过ToolMessage将工具执行结果重新注入对话流使得LLM能基于工具输出进行后续推理。3. 实战开发步骤3.1 环境准备与依赖安装首先确保已安装必要库pip install langchain-core langchain-community faiss-cpu dashscope注意FAISS在不同平台可能有兼容性问题Mac用户建议使用conda安装conda install -c conda-forge faiss-cpu3.2 工具函数实现3.2.1 知识检索工具tool def rag_search(query: str) - str: 从RAG知识库检索公司内部信息包括 - 项目计划名称/代号 - 技术方案 - 预算信息 - 截止日期 示例查询 - 深蓝计划的预算是多少 - 项目截止日期是什么时候 # 初始化向量数据库具体实现见下文 ...关键点使用tool装饰器声明工具函数文档字符串必须详细说明功能、参数和返回示例返回类型必须为字符串3.2.2 数学计算工具tool def calculator(expression: str) - str: 执行精确数学计算支持 - 四则运算 - * / - 百分比计算 - 括号优先级 示例 - 2 3 * 5 → 17.0 - 500 * 0.8 → 400.0 # 安全计算实现见3.4节 ...3.3 多轮对话引擎实现核心循环逻辑def run_agent(query: str, max_turns5): tool_maps {rag_search: rag_search, calculator: calculator} llm ChatTongyi(model_nameqwen-plus) tool_llm llm.bind_tools(toolslist(tool_maps.values())) messages [HumanMessage(contentquery)] for turn in range(max_turns): # 获取LLM响应 response tool_llm.invoke(messages) messages.append(response) if not response.tool_calls: return response.content # 处理工具调用 for tool_call in response.tool_calls: tool_name tool_call[name] if tool_name in tool_maps: tool_output tool_maps[tool_name](**tool_call[args]) messages.append( ToolMessage( contenttool_output, tool_call_idtool_call[id], nametool_name ) ) return 超过最大对话轮数关键设计通过max_turns参数防止无限循环实测表明复杂任务通常3轮内可完成。4. 安全增强方案4.1 eval函数的安全隐患原始计算器实现直接使用eval()存在严重风险# 危险实现 return str(eval(expression)) # 可能执行恶意代码4.2 三种加固方案方案1输入过滤import re def safe_eval(expr: str) - str: if not re.match(r^[\d\-*/(). ]$, expr): return 非法输入 return str(eval(expr))方案2使用ast.literal_evalimport ast def safe_eval(expr: str) - str: try: node ast.parse(expr, modeeval) if not all(isinstance(n, (ast.Num, ast.BinOp, ast.UnaryOp)) for n in ast.walk(node)): raise ValueError return str(eval(expr)) except: return 计算错误方案3自定义解析器推荐from operator import add, sub, mul, truediv ops {: add, -: sub, *: mul, /: truediv} def safe_calculate(expr: str) - str: try: tokens expr.split() stack [] for token in tokens: if token in ops: b, a stack.pop(), stack.pop() stack.append(ops[token](a, b)) else: stack.append(float(token)) return str(stack[0]) except: return 计算错误实测表明方案3安全性最佳虽然仅支持二元运算但已满足大多数计算场景。5. 性能优化技巧5.1 向量数据库缓存避免每次调用都重建FAISS索引RAG_PATH faiss_index if os.path.exists(RAG_PATH): ragdb FAISS.load_local(RAG_PATH, embeddings) else: ragdb FAISS.from_documents(docs, embeddings) ragdb.save_local(RAG_PATH)5.2 对话历史管理过长的对话历史会影响性能建议设置最大历史长度如最近10条对历史消息进行摘要压缩重要信息显式存入知识库5.3 工具调用优化通过bind_tools的tool_choice参数可以控制工具调用行为# 强制使用特定工具 llm.bind_tools(..., tool_choice{type: function, function: {name: calculator}}) # 禁止工具调用 llm.bind_tools(..., tool_choicenone)6. 典型问题排查6.1 工具未被识别现象LLM不调用预期工具 排查步骤检查工具描述是否完整参数、示例缺一不可验证工具是否正确绑定print(tool_llm.tools) # 应显示已绑定的工具测试直接工具调用是否正常6.2 无限循环问题现象Agent持续调用同一工具 解决方案检查ToolMessage是否正确返回限制最大对话轮数如前文max_turns在工具描述中明确使用边界条件6.3 中文处理异常现象中文查询结果不准确 优化方案调整文本分块策略text_splitter RecursiveCharacterTextSplitter( chunk_size500, # 增大分块大小 chunk_overlap100, separators[\n\n, \n, 。, , ] # 中文友好分隔符 )使用支持中文的embedding模型embeddings DashScopeEmbeddings(modeltext-embedding-v2)7. 扩展应用场景基于此框架可轻松扩展更多实用场景7.1 电商客服Agent新增工具订单查询物流跟踪退换货政策检索7.2 数据分析Agent集成SQL查询可视化生成统计计算7.3 智能家居控制对接设备状态查询情景模式切换能耗统计我在实际项目中发现当工具数量超过5个时建议使用ToolExecutor进行统一调度管理避免工具冲突。另外为每个工具添加明确的元数据描述如适用场景、输入输出示例能显著提升调用准确率。