基于LangChain与本地大模型构建智能Agent:从工具调用到实战调优

发布时间:2026/8/27 5:04:35
基于LangChain与本地大模型构建智能Agent:从工具调用到实战调优 1. 项目概述从“问答机”到“执行者”的跨越最近在折腾大模型应用开发的朋友估计没少被“Agent”这个词刷屏。从去年开始这个概念就火得不行好像不提Agent都不好意思说自己搞AI应用。但说实话很多刚接触的朋友包括我一开始也是看着各种框架和教程感觉Agent就是个“黑盒”——知道它能调用工具但具体怎么让它听话、怎么设计它的思考逻辑总有点雾里看水。所以今天我们不谈那些宏大的概念就从一个最实际的问题出发如何让一个本地部署的大模型比如用Ollama跑的Llama 3学会自己调用工具去解决一个具体问题比如我给它一个任务“帮我查一下上海今天的天气然后根据天气建议我是否要带伞最后把这个建议用中文写成一个简洁的备忘录。” 如果只用基础的对话模型可能会给你一段描述性的文字。但有了Agent它能自己决定先调用一个天气查询工具获取数据再根据数据推理最后调用一个文本格式化工具输出结果。这个从“被动回答”到“主动规划并执行”的转变就是Agent的核心魅力。我选择用LangChain来实现这个想法。虽然现在Dify、Workbuddy这类低代码平台也很火但LangChain提供了最大的灵活性和透明度你能清楚地看到Agent的“思考链”Chain of Thought这对于学习和深度定制至关重要。很多人问LangChain工具调用和LLM本身的Function Call有什么区别简单说LLM的Function Call是模型原生能力告诉模型有哪些函数可用模型在回复中会声明它想调用哪个函数以及参数是什么但执行还得靠开发者自己写代码去解析和执行。而LangChain的Agent则是把工具定义、模型决策、工具执行、结果处理这一整套流程都封装好了提供了一个更高阶的、可编排的自动化工作流。它负责驱动整个“思考-行动-观察”的循环。那么一个能自己调用工具解决问题的Agent到底是怎么构建出来的它的“大脑”LLM和“手脚”Tools如何协同为什么有时候它表现得像个“人工智障”乱调用工具或者陷入死循环接下来我就结合一次完整的实战拆解LangChain Agent的构建过程、核心机制以及那些官方文档里不会写的“踩坑”经验。2. 核心组件拆解构建Agent的“五脏六腑”在动手写代码之前我们必须先理解组成一个LangChain Agent的核心部件。这就像组装一台电脑你得清楚CPU、内存、硬盘各自的作用。2.1 大脑语言模型LLMAgent的“大脑”就是大语言模型。它的核心职责是理解和规划理解用户指令规划解决问题的步骤序列Plan并在每一步决定是进行常规推理还是调用某个工具Action。模型选型考量对于Agent任务模型的选择至关重要。它需要具备较强的指令遵循Instruction Following和逻辑推理Reasoning能力。云端API vs. 本地模型为了完全离线、可控且无网络延迟我选择了本地部署。Ollama是目前管理本地模型最方便的工具之一。在模型选择上我测试了Llama 3 8B、Qwen 7B和Hermes 2 Pro。最终选择了Llama 3 8B因为它在工具调用和格式遵循上表现更稳定。Hermes 2 Pro虽然在某些对话任务上很出色但在复杂的多步工具调用规划上我实测发现它更容易“跑偏”或忘记之前的上下文。关键参数配置通过LangChain调用本地模型时温度和max_tokens是核心参数。温度temperatureAgent任务需要确定性的决策因此温度不宜过高。我通常设置为0.1或0.2以减少模型响应的随机性让工具调用的决策更稳定。最大令牌数max_tokens这限制了模型单次响应的长度。对于Agent它的响应里包含了“思考过程”和“行动指令”需要足够的空间。我一般设置为512或1024。设置过低会导致响应被截断Agent无法输出完整的决策。注意不要盲目追求大参数模型。对于大多数工具调用场景一个7B-13B参数量的、精调过的模型如专门为工具调用优化的版本其表现往往优于一个未经优化的更大模型。推理速度也是一个重要因素。2.2 手脚工具Tools工具是Agent与外部世界交互的“手脚”。一个工具本质上就是一个Python函数加上清晰的描述。LangChain要求工具描述必须清晰因为模型就是靠这个描述来决定是否以及何时调用它。如何设计一个好工具单一职责一个工具只做一件事。比如get_weather只负责查询天气并返回结构化数据send_email只负责发送邮件。不要把查询天气和发送建议合并到一个工具里。清晰的描述description这是最重要的部分。描述要像给一个“外星人”下指令一样精确。必须说明这个工具是干什么的输入参数是什么名称、类型、含义输出是什么例如“””根据城市名称查询该城市当前的天气情况。输入是一个字符串代表城市名如‘上海’。返回一个包含天气状况、温度、湿度等信息的JSON对象。“””模糊的描述会导致模型错误调用。结构化的返回尽可能返回结构化的数据如字典、Pydantic模型而不是一大段自然语言。这有利于模型在后续步骤中解析和使用这些数据。2.3 协调中枢Agent执行器AgentExecutor这是LangChain Agent框架的“调度中心”。它负责驱动ReActReasoning Acting循环将用户输入和当前状态历史记录、中间结果传递给LLM大脑。解析LLM的响应。响应可能是Action: 模型决定调用某个工具并给出工具名和输入参数。Final Answer: 模型认为已经得到最终答案直接输出给用户。如果解析到Action则查找对应的工具并执行得到Observation观察结果。将Observation连同之前的记录再次喂给LLM让它进行下一轮“思考”。重复此过程直到模型输出Final Answer或达到最大迭代次数。AgentExecutor的关键配置max_iterations:必设参数防止Agent陷入死循环。比如一个简单问题它可能调用十几次工具还没结果这通常是模型“卡住”了。我一般设为5或6对于复杂任务可以适当提高。handle_parsing_errors: 设置为True。当模型输出的格式不符合Agent预期的Action或Answer格式时比如模型“说起了胡话”这个处理器能捕获错误并以一种友好的方式重新提示模型而不是直接让程序崩溃。verbose: 开发时设为True这样可以在控制台看到完整的ReAct循环日志包括模型的“思考”和每一步的“行动”、“观察”对于调试无比重要。2.4 思维框架Agent类型与提示模板LangChain提供了多种预定义的Agent类型如ZERO_SHOT_REACT_DESCRIPTION,CONVERSATIONAL_REACT_DESCRIPTION等。它们本质上是不同的提示模板Prompt Template预先写好了引导模型进行ReAct推理的指令。ZERO_SHOT_REACT_DESCRIPTION: 最常用的一种。它不保留对话历史每次都是新会话提示词明确要求模型以“Thought:”, “Action:”, “Observation:”的格式进行响应。它适合一次性的、任务型的场景。CONVERSATIONAL_REACT_DESCRIPTION: 适用于多轮对话场景它会将历史对话也纳入上下文让Agent能基于之前的交流进行决策。提示模板的魔力你可以完全自定义这个模板。官方模板是一个很好的起点但如果你发现模型总是忽略某个工具或者格式经常出错可以微调提示词。例如在工具列表前加上强调“你必须从以下工具中选择一个来使用不能自己编造工具。” 这能显著提升工具调用的准确性。3. 实战构建一个本地天气助手Agent理论说得再多不如一行代码。我们现在就来构建一个能完成开篇那个任务的Agent查询天气并生成建议备忘录。3.1 环境准备与模型本地化部署首先确保你的环境已经就绪。我使用Python 3.10的环境。# 安装核心库 pip install langchain langchain-community langchain-core # 安装Ollama的LangChain集成包如果你用Ollama pip install ollama # 或者如果你使用其他本地API服务器如LM Studio、text-generation-webui提供的兼容OpenAI的API pip install openai部署本地模型我使用Ollama因为它最简单。# 拉取Llama 3 8B模型确保你的磁盘空间和内存足够 ollama pull llama3:8b # 运行模型服务Ollama默认会在本地11434端口启动API服务 ollama run llama3:8b此时一个兼容OpenAI API格式的本地服务就已经在http://localhost:11434运行了。3.2 定义工具给Agent装上“手”和“眼”我们将定义两个工具一个天气查询工具一个文本格式化工具。import requests from langchain.tools import tool from datetime import datetime # 工具1模拟天气查询工具 # 在实际项目中这里应该调用真实的天气API如和风天气、OpenWeatherMap等。 # 为了演示和离线环境我们模拟一个。 tool def get_weather(city: str) - str: 根据城市名称查询该城市当前的天气情况。输入是一个字符串代表城市名如‘上海’。返回一个格式化的天气信息字符串。 # 模拟数据 - 真实情况请替换为API调用 weather_data { 上海: {condition: 多云, temp: 22, humidity: 65, rain_probability: 30}, 北京: {condition: 晴, temp: 18, humidity: 40, rain_probability: 10}, 广州: {condition: 雷阵雨, temp: 28, humidity: 85, rain_probability: 80}, } if city in weather_data: data weather_data[city] result f{city}的天气{data[condition]}温度{data[temp]}°C湿度{data[humidity]}%降雨概率{data[rain_probability]}%。 return result else: return f未找到{city}的天气信息。 # 工具2文本格式化工具模拟生成备忘录 tool def format_memo(content: str, style: str 简洁) - str: 将给定的内容按照指定风格格式化成备忘录。输入是内容字符串和风格字符串默认为‘简洁’。返回格式化后的文本。 current_time datetime.now().strftime(%Y-%m-%d %H:%M) if style 简洁: memo f【备忘录】{current_time}\n\n{content}\n\n--- 结束 --- elif style 正式: memo f致相关人员\n发件人AI助手\n日期{current_time}\n主题天气建议\n\n{content}\n\n此致\n敬礼 else: memo f内容{content} return memo # 将工具放入列表供Agent使用 tools [get_weather, format_memo]关键点tool装饰器是LangChain社区版langchain-community中定义工具的标准方式。它自动将函数及其文档字符串转换为LangChain能识别的工具对象。确保你的函数有类型注解和清晰的docstring这能极大帮助模型理解。3.3 连接大脑初始化本地LLM接下来我们使用LangChain连接到本地运行的Ollama服务。from langchain.llms import Ollama # 或者使用ChatOllama它对消息格式处理更好 from langchain.chat_models import ChatOllama from langchain.schema import SystemMessage, HumanMessage # 方式一使用基础的Ollama类较简单 # llm Ollama(base_urlhttp://localhost:11434, modelllama3:8b, temperature0.1) # 方式二使用ChatOllama推荐更符合对话/Agent场景 llm ChatOllama( base_urlhttp://localhost:11434, modelllama3:8b, temperature0.1, # 可以传入系统提示词来进一步约束模型行为 # system你是一个乐于助人且严谨的AI助手。在回答时请严格遵循ReAct格式进行思考。 )为什么用ChatOllama因为大多数现代LLM包括Llama 3都是基于“消息”System, User, Assistant格式训练的。ChatOllama能更好地处理这种格式在与Agent配合时生成符合Action/Final Answer格式的响应更可靠。3.4 组装Agent并运行现在把大脑LLM、手脚Tools和调度器AgentExecutor组装起来。from langchain.agents import create_react_agent, AgentExecutor from langchain import hub # 1. 获取ReAct提示模板 # LangChain有一个“提示词中心”可以拉取预定义的优质提示模板 prompt hub.pull(hwchase17/react) # 你可以打印出来看看 prompt.template 里面是什么理解其结构 # print(prompt.template) # 2. 创建Agent agent create_react_agent(llmllm, toolstools, promptprompt) # 3. 创建执行器 agent_executor AgentExecutor( agentagent, toolstools, handle_parsing_errorsTrue, # 关键处理解析错误 max_iterations6, # 防止无限循环 verboseTrue, # 开发时打开看详细思考过程 ) # 4. 运行Agent question 帮我查一下上海今天的天气然后根据天气建议我是否要带伞最后把这个建议用中文写成一个简洁的备忘录。 try: result agent_executor.invoke({input: question}) print(\n *50) print(最终答案) print(result[output]) except Exception as e: print(f运行出错{e})当你运行这段代码并将verboseTrue时控制台会输出类似下面的精彩过程 Entering new AgentExecutor chain... Thought: 用户想了解上海的天气并根据天气决定是否带伞最后生成备忘录。我需要先获取天气信息。 Action: get_weather Action Input: {city: 上海} Observation: 上海的天气多云温度22°C湿度65%降雨概率30%。 Thought: 降雨概率是30%不算很高但多云天气有可能下雨。为了保险起见建议带伞。现在我需要将这个建议格式化成简洁的备忘录。 Action: format_memo Action Input: {content: 根据天气预报上海今天多云降雨概率30%。建议携带雨伞以备不时之需。, style: 简洁} Observation: 【备忘录】2024-05-27 14:30\n\n根据天气预报上海今天多云降雨概率30%。建议携带雨伞以备不时之需。\n\n--- 结束 --- Thought: 我已经完成了所有步骤查询了天气、给出了带伞建议、并生成了备忘录。现在可以给出最终答案了。 Final Answer: 已完成您的请求。查询到上海今日天气为多云降雨概率30%建议携带雨伞。已生成简洁备忘录如下\n【备忘录】2024-05-27 14:30\n\n根据天气预报上海今天多云降雨概率30%。建议携带雨伞以备不时之需。\n\n--- 结束 --- Finished chain.看到这个输出你会清晰地看到模型的“思考”Thought、“行动”Action和从工具获得的“观察”Observation。这就是ReAct框架的可解释性也是调试Agent的黄金标准。4. 性能调优与深度解析让Agent跑起来只是第一步让它跑得“快、准、稳”才是挑战。下面分享几个关键的调优点和深度解析。4.1 工具调用速度瓶颈分析很多人抱怨LangChain Agent慢尤其是调用本地模型时。速度瓶颈主要来自以下几方面模型推理速度这是最大的瓶颈。本地7B/8B模型在普通CPU上推理一次生成可能需要数秒甚至十几秒。解决方案使用GPU加速CUDA或者选择更小、更快的模型如Phi-3 mini。对于生产环境考虑使用量化后的模型GGUF格式用llama.cpp或text-generation-webui加载推理速度会有数量级提升。网络延迟如果使用云端API每个工具调用前后的模型推理都需要一次网络往返。解决方案优化提示词减少不必要的思考步骤使用流式响应如果支持来提升感知速度考虑在离用户更近的区域部署模型或API网关。工具本身执行时间如果你的工具需要调用一个慢速的外部API比如某些需要复杂计算的API那么整个Agent会被阻塞。解决方案对工具进行超时设置和错误重试考虑将同步工具改为异步执行LangChain支持异步Agent。ReAct循环次数每次循环都是一次完整的模型调用。不必要的循环会累积延迟。优化方法通过设计更精准的工具描述和提示词引导模型用更少的步骤解决问题。例如如果任务明确是“查天气”可以在提示词中暗示“你可以直接使用get_weather工具”。4.2 提示工程让Agent更“听话”Agent的“智商”很大程度上取决于你给它的提示词。hwchase17/react这个默认模板已经不错但我们可以针对特定场景优化。自定义提示模板示例from langchain.prompts import PromptTemplate custom_prompt PromptTemplate.from_template( 你是一个专业的任务处理助手。请严格按以下步骤思考 工具列表 {tools} 使用工具时请严格使用以下JSON格式 {{ action: 工具名, action_input: 工具的输入参数 }} 请开始记住你必须使用上述工具之一不能编造工具。 用户问题{input} 你之前的步骤如果有{agent_scratchpad} 请开始你的思考 ) # 然后用这个 custom_prompt 替换 hub.pull 的提示 agent create_react_agent(llmllm, toolstools, promptcustom_prompt)优化技巧强调格式在提示词中明确写出模型应该输出的JSON格式示例可以大幅减少解析错误。限制行动空间明确告知“你必须使用上述工具之一”防止模型尝试回答“我不知道怎么用工具但我认为...”。提供示例Few-Shot对于复杂任务可以在提示词中加入一两个完整的ReAct示例教模型如何推理。这被称为“少样本提示”效果极佳。4.3 错误处理与鲁棒性提升一个健壮的Agent必须能处理各种意外。工具调用失败网络错误、API限流、参数错误等。需要在工具函数内部做好try...catch并返回一个清晰的错误信息作为Observation让模型知道发生了什么并可能调整策略。tool def get_weather(city: str) - str: try: # ... API调用 ... return result except requests.exceptions.RequestException as e: return f错误无法连接到天气服务。原因{str(e)}。请检查网络或稍后再试。 except KeyError: return f错误未找到城市‘{city}’的天气数据。请确认城市名称是否正确。模型输出格式错误Parsing Error这是最常见的问题。模型可能不按Action:格式输出而是说了一堆废话。这就是为什么AgentExecutor的handle_parsing_errorsTrue如此重要。当设置后LangChain会捕获解析错误并向模型发送一个类似“你的格式不对请重试”的提示让它重新生成。你还可以自定义这个错误处理函数。无限循环模型可能在一个步骤里来回调用相同的工具或者陷入“思考-不行动”的循环。除了设置max_iterations硬性限制外可以在agent_scratchpad记录了之前所有步骤的变量中检测重复动作并在提示词中加入警告如“注意你已经查询过这个城市的天气了请基于已有信息进行下一步。”5. 进阶探索从单Agent到多Agent与工作流当单个Agent无法处理复杂任务时我们就需要更高级的模式。5.1 LangChain Agent vs. LangGraph这是最近常被问到的问题。简单来说LangChain Agent专注于单个代理的“思考-行动”循环。它结构相对简单适合线性任务。LangGraph是LangChain的一个扩展库用于构建有状态、多参与者的图工作流。你可以把多个Agent或任何函数作为节点用条件逻辑边连接它们构建复杂的、分支的、循环的工作流。适用场景对比你的任务是一个清晰的、线性的“用户提问 - Agent规划并执行工具 - 返回答案”流程用LangChain Agent就够了。你的任务需要多个专家Agent协作比如一个负责检索一个负责分析一个负责生成报告或者流程中存在“如果...那么...”的分支判断或者需要维护一个跨步骤的共享状态那么LangGraph是更好的选择。例如用LangGraph可以轻松构建一个“评审-修改”循环Writer Agent生成初稿Critic Agent评审并提出修改意见然后流程跳回Writer Agent进行修改直到Critic Agent满意为止。这种带循环和状态的工作流用基础的Agent实现起来会很别扭。5.2 构建一个简单的多Agent系统雏形即使不使用LangGraph我们也可以用多个AgentExecutor组合起来。思路是创建一个“主管Agent”它的工具是调用其他“专家Agent”。# 假设我们已经定义了一个天气查询Agentweather_agent_executor # 和一个备忘录生成Agentmemo_agent_executor tool def delegate_to_weather_agent(query: str) - str: 将关于天气查询的复杂问题委托给专门的天气助手处理。输入是用户关于天气的原始问题。 # 这里可以做一些输入预处理 result weather_agent_executor.invoke({input: query}) return result[output] tool def delegate_to_memo_agent(content: str) - str: 将格式化文本和生成备忘录的任务委托给专门的文案助手处理。 result memo_agent_executor.invoke({input: f请将以下内容格式化为简洁备忘录{content}}) return result[output] # 主管Agent的工具列表 supervisor_tools [delegate_to_weather_agent, delegate_to_memo_agent, ...] # 为主管创建Agent和Executor supervisor_prompt hub.pull(hwchase17/react) supervisor_agent create_react_agent(llmllm, toolssupervisor_tools, promptsupervisor_prompt) supervisor_executor AgentExecutor(agentsupervisor_agent, toolssupervisor_tools, verboseTrue) # 用户向主管提问 final_result supervisor_executor.invoke({input: 上海和北京天气怎么样对比一下然后给我个出差建议。})这种方式实现了简单的任务分发。但对于更复杂的协作和流程控制学习LangGraph将是更高效的选择。6. 常见问题与排查实录在开发Agent的过程中我踩过不少坑。这里列几个典型问题及其解决方法。问题1Agent总是输出“Final Answer: I dont know”或者不调用工具。可能原因1工具描述不清。模型不理解你的工具能干什么。解决重写工具的描述docstring确保清晰、无歧义并包含输入输出示例。可能原因2提示词不适合。默认的ReAct提示词可能对你的模型效果不好。解决尝试拉取不同的提示模板如hwchase17/react-chat用于对话或者进行上文提到的自定义提示工程。可能原因3模型能力不足。某些小模型或未针对工具调用优化的模型工具调用能力很弱。解决换用更强的模型或者寻找针对工具调用进行过微调的模型版本如ToolLlama、ChatGLM3的Tool Calling版本。问题2Agent陷入死循环反复调用同一个工具。可能原因1工具返回的信息不足以让模型做出决策。比如查询天气返回“天气很好”但模型需要具体的“降雨概率”来判断是否带伞于是它反复查询希望得到更详细的数据。解决优化工具返回的信息确保其包含决策所需的关键数据字段。可能原因2模型上下文混乱。在长循环中之前的Thought、Action、Observation都堆在上下文里可能导致模型困惑。解决尝试使用CONVERSATIONAL类型的Agent它可能对长上下文管理更好。或者在自定义提示词中明确要求模型总结当前状态避免重复劳动。终极方案必须设置max_iterations这是安全绳。问题3解析错误Parsing Error频繁发生。可能原因1模型输出格式不稳定。解决开启handle_parsing_errorsTrue。此外可以尝试降低temperature到0增加输出的确定性。在提示词中强化格式要求用JSON示例。可能原因2使用了不适合的Chat模型/非Chat模型。有些纯补全模型Completion Model不擅长输出结构化的Action格式。解决确保使用Chat模型如ChatOllama,ChatOpenAI。问题4本地模型调用速度太慢无法忍受。可能原因模型未量化在CPU上推理。解决使用GPU确保你的PyTorch或相关库安装了CUDA版本并且模型加载到了GPU上。模型量化将模型转换为GGUF格式并使用llama.cpp、gpt4all或text-generation-webui进行加载和推理。4-bit或5-bit量化能在几乎不损失精度的情况下大幅提升推理速度并降低内存占用。Ollama本身也使用了量化技术。使用更小的模型对于许多工具调用任务3B-7B量级的精调模型可能已经足够速度会快很多。问题5如何让Agent使用自定义的Python函数/类方法解决tool装饰器是首选。它可以直接装饰你的函数。对于类方法你需要先定义一个函数来包装这个方法或者使用Tool.from_function()方法。from langchain.tools import Tool class MyCalculator: def add(self, a: int, b: int) - int: return a b calc MyCalculator() # 将类方法包装成工具 add_tool Tool( nameCalculator_Add, funclambda x: calc.add(**x), # 注意输入可能是字典 description将两个整数相加。输入是一个包含a和b两个键的字典如{{a: 5, b: 3}}。返回它们的和。, args_schema... # 可以定义更严格的输入模式 )更推荐的方式是使用Pydantic来定义严格的输入模式这能让模型更准确地生成参数。构建一个稳定可靠的Agent是一个不断迭代和调试的过程。从最简单的工具和提示词开始逐步增加复杂性并始终通过verboseTrue来观察其内部思考过程是最高效的学习和开发方式。当你看到大模型按照你设计的逻辑一步步调用工具最终解决一个实际问题时那种成就感是无可替代的。这不仅仅是调用API而是在塑造一个能够自主行动的智能体。