1. 先把 Agent 是什么这件事说透,再谈 LangChain
玩 Agent 开发这件事,我踩过的第一个坑不是代码写错,而是概念没理清就急着上框架。市面上关于 Agent、LangChain、智能体、框架这些词的讨论实在太多,有人说 LangChain 是入门首选,有人又说它已经过时、该换 LangGraph 了,还有一堆 Dify、Coze 之类的平台在旁边晃。新手在这种信息密度下最容易犯的错,就是今天学一个框架、明天换一个平台,结果哪个都停在 demo 阶段。所以这篇笔记我不打算一上来就贴代码,而是先把几个关键概念掰开揉碎,再用 LangChain 把第一个能跑起来的智能体拼出来,让你知道每一步为什么要这么做。
这套内容适合谁看?如果你写过一点 Python,调过几次大模型 API,想让模型不只是聊天、而是能自己决定“调哪个工具、传什么参数、拿结果再继续”,那这篇就是给你准备的。我会覆盖 LangChain 里 Agent 的核心构建方式、工具定义、ReAct 循环、记忆管理、常见报错排查,以及它和 LangGraph 的关系。全程用从业者的视角讲人话,不堆术语,能直接抄的代码片段我都会标注清楚关键参数为什么这么设。
有一点需要先说明:LangChain 的版本迭代很快,API 名字换过好几轮,你按老教程写的代码很可能一跑就报 ImportError。我下面的示例以当前主流的langchain+langchain-core+ 独立集成包的结构为基准,遇到导入路径差异我会顺手提醒。这是新手最容易被劝退的地方,提前打个预防针。
2. 拆解 Agent 的核心构成:它和普通聊天有什么不一样
2.1 从“一问一答”到“自己决定下一步”
普通的大模型调用是线性的:你把 prompt 丢进去,模型吐一段文本,结束。整个过程中模型没有“行动”的能力,它只能输出文字。而智能体(Agent)的本质区别在于,模型输出的不再只是给人看的文本,而可以是“我要调用某个工具,参数是这些”,然后程序真的去执行这个工具,把结果再喂回给模型,模型基于新信息继续判断。
这个“输出→执行→回灌→再判断”的循环,就是 Agent 的心脏。你可以把它想象成一个会自己查资料、自己按计算器、自己翻数据库的实习生。你只给它一个目标,比如“帮我查一下上个月销售额最高的三个产品”,它会自己拆成几步:先调数据库查询工具拿数据,再调用排序或计算工具,最后组织语言回答你。整个过程你不需要写死流程,是模型在运行时动态决定的。
理解这一点非常关键,因为它直接决定了后面所有的代码结构:你需要一个能循环的驱动器、一组它可调用的工具、一套让模型知道“什么时候该调工具”的提示词。LangChain 做的,就是把这几个部件的组装工作标准化。
2.2 为什么入门阶段我仍推荐从 LangChain 入手
网上关于“LangChain 是否过时”的争论很多,尤其在 LangGraph 出现之后。我的看法比较务实:对于刚接触智能体开发的人,LangChain 依然是最快能让你建立直觉的框架。原因是它把 Agent 循环、工具调用、提示词模板这些抽象概念用最短的代码量暴露给你,你几十行就能看到一个完整的 ReAct 流程在跑。先用手动挡把离合器、油门、换挡的逻辑摸清楚,后面再上自动挡(图编排)才不会晕。
LangGraph 更像是给复杂的、带分支和人工介入的生产流程准备的。它用图结构描述状态流转,控制力更强,但也意味着你得先理解状态、节点、边这些概念,学习曲线更陡。如果你连 ReAct 循环是怎么运转的都没跑通过,直接上 LangGraph,大概率是抄了个模板却不知道错在哪。
所以我的建议路径很明确:LangChain 打底,跑通一个带工具的 Agent,理解每一步的输入输出;等你能自己手写一个简易的 Agent 循环时,再去碰 LangGraph,那时候你会发现它解决的是你真实遇到的痛点,而不是为了新而新。
2.3 动手前你需要具备的三样基础
第一,Python 基础要够用,至少不怵装饰器、类和字典操作,因为工具定义大量用到@tool装饰器和类型注解。第二,你要理解大模型 API 的调用方式,知道什么是 system prompt、user prompt、temperature、max_tokens 这些基本参数,否则调试时你不知道该调哪个旋钮。第三,要有基本的异步和异常处理意识,工具调用失败是常态,不做兜底的 Agent 在生产里就是个定时炸弹。
这三样不需要精通,但缺了任何一样,你在遇到报错时都会抓瞎。我见过不少人卡在“模型就是不调用我的工具”这个现象上,排查半天,最后发现是工具函数的文档字符串(docstring)写得含糊,模型根本不知道这个工具是干嘛的。这类问题的答案,都藏在基础里。
3. 环境搭建与第一个可运行 Agent 的完整落地
3.1 依赖安装与项目结构建议
先把环境理干净。我习惯给这类项目单独建虚拟环境,避免包冲突,因为大模型相关的库依赖链很长,混装很容易出问题。核心要装的东西大致分三块:LangChain 的核心包、你选用的模型集成包、以及工具可能用到的第三方库。
python -m venv venv source venv/bin/activate # Windows 用 venv\Scripts\activate pip install langchain langchain-core pip install langchain-openai # 以 OpenAI 兼容接口为例,其他模型有各自集成包 pip install python-dotenv项目目录我会这样分:根目录放.env存密钥(记得加进.gitignore),tools/放自定义工具,agent.py放主逻辑。这样等你工具多了不至于全挤在一个文件里。密钥管理这块我给个硬性建议:绝对不要把 API key 硬编码进代码然后推到远端仓库,用python-dotenv从环境变量读,这是每个从业者都该养成的肌肉记忆。
注意:不同版本的 LangChain 导入路径变化频繁,比如
AgentExecutor曾在多个包之间迁移。如果你复制代码后报ImportError,先别怀疑逻辑,去官方文档确认当前版本的导入路径,这能省你大量时间。
3.2 必须搞懂的四个核心概念
在写代码前,我把 LangChain 里组装 Agent 会用到的四个概念先对齐一下,这是理解后续所有代码的钥匙。
LLM / ChatModel:负责“思考”的大脑,也就是你调用的大模型。在 Agent 里它的角色是决策者,判断下一步该做什么。
Prompt Template:给模型的指令模板。Agent 场景里它不是普通的问答模板,而是包含“你有哪些工具、请按 ReAct 格式输出”的这类结构化说明,模型靠它来决定行为。
Tool:模型可以调用的外部能力,本质就是一个有明确输入输出和用途说明的函数。可以是查天气、算数学、查数据库、发邮件,任何你能写成函数的东西。
AgentExecutor:驱动器,负责跑那个循环。它把你上面的部件串起来,反复执行“让模型想→执行工具→把结果喂回去→再让模型想”,直到模型给出最终答案或达到停止条件。
把这四样理解成“大脑 + 指令 + 手脚 + 循环引擎”,整个架构图在你脑子里就立起来了。后面无论换成哪个框架,这四个部件都会以不同名字出现。
3.3 组装并跑通第一个 ReAct Agent
下面给一个尽量精简但完整的例子。先定义一个工具,然后把它交给 Agent。我特意把 docstring 写得清楚,因为这就是模型判断“要不要用这个工具”的唯一依据。
from langchain_core.tools import tool from langchain_openai import ChatOpenAI from langchain.agents import create_react_agent, AgentExecutor from langchain import hub from dotenv import load_dotenv load_dotenv() @tool def multiply(a: float, b: float) -> float: """计算两个数字的乘积。当用户需要进行乘法运算时使用此工具。 输入应为两个数字,输出为它们的乘积。""" return a * b llm = ChatOpenAI(model="gpt-4o-mini", temperature=0) # 拉取一个标准的 ReAct 提示词模板 prompt = hub.pull("hwchase17/react") tools = [multiply] agent = create_react_agent(llm, tools, prompt) executor = AgentExecutor(agent=agent, tools=tools, verbose=True, max_iterations=5) result = executor.invoke({"input": "帮我算一下 23 乘以 47 等于多少"}) print(result["output"])跑起来后你会看到verbose=True打印出的完整思考链:模型先想“我需要用乘法工具”,然后输出 action 和 action_input,Executor 执行工具拿到结果,再喂回模型,模型给出最终答案。这一串日志就是 ReAct 循环的实况,强烈建议你第一次运行时逐行读一遍,比看十篇文章都管用。
temperature=0这个参数在 Agent 场景几乎是标配。因为你要的是稳定、可复现的决策,而不是有创意的表达。温度高了模型可能天马行空地选错工具或者输出格式不符合解析要求,调试阶段会把你逼疯。max_iterations也要设,避免模型陷入死循环时无限调用,这个后面会细说。
4. ReAct 循环拆解:模型到底是怎么“边想边做”的
4.1 Thought-Action-Observation 三步走
ReAct 这个名字来自 Reasoning + Acting,它的核心是一个固定的文本格式循环。模型每一步都要输出三样东西的思路:思考(Thought,我该怎么解决)、行动(Action,我要调用哪个工具)、行动输入(Action Input,给工具传什么参数)。程序解析出 Action 和 Action Input,执行对应工具,把返回值作为 Observation 追加进上下文,然后再让模型基于这个观察继续下一步。
这个机制最巧妙的地方在于,它把“工具调用”这件结构化的事,转化成了模型的文本生成能力。模型不需要专门的函数调用接口,只要它能按格式写出Action: multiply和Action Input: 23, 47,程序就能解析并执行。这也是为什么提示词模板如此重要——模板规定了模型必须遵守的输出格式,一旦格式跑偏,解析就失败。
你在verbose日志里会反复看到这个循环直到模型判断“信息够了”。判断的标准是模型输出Final Answer: xxx,Executor 检测到这个词就停止循环,把答案返回给你。理解了这个终止条件,你就能明白为什么有时候 Agent 会一直转圈——模型迟迟不给出 Final Answer。
4.2 提示词模板里那些容易被忽略的细节
很多人直接用hub.pull("hwchase17/react")拉现成模板就不管了,但一旦要定制,模板里的几个部分必须保留。模板里会明确列出{tools}和{tool_names}两个占位符,前者是工具的完整描述(名字、参数、用途),后者是工具名列表。模型就是靠这些信息知道有哪些牌可打。如果你自己写模板却漏了工具描述,模型会“看不见”工具,自然永远不会调用。
还有一个细节是{agent_scratchpad}占位符,它承载的是前面所有轮次的 Thought/Action/Observation 历史。没有它,模型每一轮都像失忆一样,不知道上一步做过什么,循环就断了。这几个占位符是 ReAct Agent 能运转的骨架,缺一不可。
我建议你至少完整读一遍默认模板的内容。它不长,但把输出格式、工具说明、停止条件全写清楚了。读懂它,你就能针对自己的场景微调,比如强制模型用中文思考、要求它在最终答案里附上数据来源,这些都是改模板就能实现的事。
4.3 输出解析与格式错乱的兜底
ReAct 的软肋就在这里:它依赖模型严格输出特定格式的文本。现实是,模型偶尔会多写一句、少写个冒号,或者在 Action Input 里加上多余的解释文字,导致解析器报OutputParserException。这不是框架的锅,是这种基于文本约定机制的固有风险。
我的处理策略分三层。第一层,选一个指令遵循能力强的模型,temperature 设低,这是最省事的。第二层,在 Executor 里开启handle_parsing_errors=True,让解析失败时把错误信息回灌给模型,提示它“你格式写错了,请重新按格式输出”,模型往往能自己纠正。第三层,对关键的生产流程,不要只依赖 ReAct,考虑用支持原生函数调用的模型接口,它们的工具调用是结构化的 JSON,解析稳定性高得多。
executor = AgentExecutor( agent=agent, tools=tools, verbose=True, handle_parsing_errors=True, max_iterations=5 )这三个参数放在一起,是让 demo 走向“不那么容易崩”的最小组合。别小看handle_parsing_errors,它能救回相当一部分因为格式问题而中断的对话。
5. 工具定义的艺术:决定 Agent 好不好用的关键
5.1 一个好工具长什么样
工具是 Agent 的手脚,手脚灵不灵直接决定它能干多少事。而模型判断用不用某个工具,几乎全靠工具的name、参数签名和 docstring。所以写工具的第一原则是:描述要像写给一个完全不了解你系统的同事看。说清楚这个工具做什么、什么场景用、输入长什么样、输出是什么。
举个例子,一个查订单的工具,如果你只写"""查询订单""",模型在用户问“我的包裹到哪了”时可能犹豫甚至不调用。如果你写成"""根据订单号查询订单的物流状态和预计送达时间。当用户询问订单进度、包裹位置或预计到达时间时使用。输入为订单号字符串。""",模型判断的准确率会明显提升。这不是玄学,是提示工程在工具层的直接体现。
参数类型也要用类型注解写清楚。LangChain 会根据函数签名自动生成工具的输入 schema,模型看到的参数说明就来自这里。如果一个参数是可选的非必填项,记得给默认值,否则模型可能编造一个值去填。
5.2 工具数量与粒度的权衡
新手容易走两个极端:要么一个工具都不写,要么一口气定义二十个工具啥都想覆盖。工具太多会带来一个隐蔽问题——模型的选择空间变大,选错的概率也随之上升,而且每个工具的描述都会占用上下文,token 成本蹭蹭涨。
我的经验是,先按用户的高频意图划分工具,控制在个位数起步。粒度上,一个工具做一件事,别搞“万能工具”。比如你把“查询数据库”和“格式化成表格”塞进一个工具,模型就很难在只需要其中一半功能时精确调用。反过来,也别把一件事切得太碎,否则完成一个任务要调七八次,又慢又费钱。
判断粒度是否合理有个土办法:站在一个刚入职的助理角度,把工具清单念给他听,如果他听完知道每个工具该在什么时候用,那这个粒度就是合适的。
5.3 工具的异常处理与超时
工具执行失败是常态,网络抖一下、数据库连接断一下、外部接口限流一下,都可能让你的工具抛异常。如果不处理,整个 Agent 循环会因为一个工具报错而中断。稳妥做法是在工具函数内部把可预期的异常捕获,返回一个有意义的错误字符串,让模型读到“这个工具失败了,原因是 xxx”,它就有机会换个工具或者调整参数重试。
@tool def query_order(order_id: str) -> str: """根据订单号查询订单物流状态。用户询问订单进度时使用。""" try: # 实际查询逻辑 result = do_query(order_id) return f"订单 {order_id} 状态:{result}" except TimeoutError: return f"查询订单 {order_id} 超时,请稍后重试" except Exception as e: return f"查询订单 {order_id} 失败:{str(e)}"这种“把异常转成给模型看的信息”的写法,是让 Agent 具备一定自愈能力的关键。外部调用还要设超时,不能让一个卡住的工具把整个循环拖死。
6. 记忆与上下文管理:让 Agent 记住聊过什么
6.1 短期对话记忆的实现
默认情况下,Agent 每次invoke都是无状态的,你不告诉它历史,它就不知道上一轮聊了什么。要实现多轮对话,得把历史消息维护起来,每一轮把之前的对话拼进输入。LangChain 提供了多种 Memory 组件,核心思路都是“存储+注入”。
from langchain.memory import ConversationBufferMemory memory = ConversationBufferMemory(memory_key="chat_history", return_messages=True)带记忆的 Agent 在构建时要把memory和chat_history占位符接入提示词,然后 Executor 会自动在每轮对话前后读写记忆。这里有个坑要提醒:加了记忆后,提示词模板里必须有对应的占位符,否则历史根本没被拼进去,你会以为记忆失灵,实际上是接线没接对。
ConversationBufferMemory简单直接,把所有历史原样存着。但对话一长,上下文就爆了,token 费用也跟着涨。所以它只适合短对话或 demo。
6.2 长对话的压缩与检索方案
对话变长后有两种主流处理方式。一种是窗口式记忆,只保留最近 N 轮对话,简单粗暴但会丢掉早期信息。另一种是摘要式记忆,用模型把早期对话压缩成一段摘要,既省 token 又保留了要点。ConversationSummaryMemory就是干这个的,它每几轮就调用一次模型把历史浓缩一次。
再进一步就是长期记忆,把对话内容向量化存进向量数据库,每次对话时检索相关片段拼进上下文。这就是所谓的本地知识库问答的底层逻辑:用户问一个新问题,系统先从知识库里找出最相关的几段内容,连同问题一起喂给模型,模型基于这些“资料”回答。这套 RAG 思路和 Agent 结合后威力很大,Agent 可以在需要时主动去检索知识库,而不是每次都全量拼接。
选哪种取决于你的场景。客服机器人可能窗口记忆加检索就够;需要长期跟踪用户偏好的助理,就得上向量存储。别一上来就追求最复杂方案,先用最简单的跑通,等真实遇到上下文过长的问题再升级。
6.3 token 成本控制的实际手段
聊记忆就绕不开成本。Agent 的 token 消耗比普通问答高得多,因为每一轮循环都要把完整的历史和工具描述重新发一遍。几轮下来,输入 token 轻松翻好几倍。
控制手段有几个我常年在用的:把工具描述写精炼但完整,别啰嗦;给记忆设上限,超了就用摘要替换;能用小模型做初筛的环节就别全程上大模型;max_iterations设小一点,逼自己优化工具设计而不是靠多轮硬扛。还有一个容易被忽略的点,ReAct 的中间思考过程也会占 token,如果你的场景允许,考虑用更简洁的思考格式。
7. 常见问题与排查实录:这些坑我都替你踩过了
7.1 模型死活不调用工具怎么办
这是新手反馈最多的问题。排查顺序我建议这样走:第一步,看工具的 docstring 是否说清了用途和触发场景,描述模糊是头号嫌疑;第二步,确认提示词模板里{tools}和{tool_names}占位符被正确填充了,没填的话模型根本看不到工具;第三步,看模型本身的能力,指令遵循弱的模型在 ReAct 格式上表现很差;第四步,检查 temperature,太高会导致行为不稳定。
排查时把verbose=True打开,你会看到模型的原始输出。如果它压根没提工具的事,那大概率是工具描述或模板问题;如果它想调但格式写错了,那是解析问题,用handle_parsing_errors兜底。
7.2 Agent 陷入死循环或调用次数超限
模型反复调用同一个工具、拿一样的结果,就是不给出最终答案,这通常有两个原因。一是工具返回的信息没解决模型的问题,它以为还要再试;二是提示词没有明确诱导它输出 Final Answer。
处理办法:设置max_iterations强制止损,避免无限烧钱;优化工具返回值,让它包含模型判断“够不够了”所需的信息;在提示词里强调“如果已经获得足够信息,请直接给出最终答案”。我自己遇到过工具返回空字符串导致模型以为查询失败、反复重试的情况,后来统一要求工具即使无结果也要返回一句明确的“未找到相关记录”,循环立马就正常了。
7.3 常见问题速查表
| 现象 | 可能原因 | 排查方向 |
|---|---|---|
| 模型不调用工具 | 工具描述模糊 / 模板缺占位符 | 检查 docstring 与{tools}填充 |
| 解析报错 OutputParserException | 模型输出格式跑偏 | 开启handle_parsing_errors,降低 temperature |
| 进度卡住不返回 | 工具阻塞无超时 | 给外部调用加超时,异常转字符串返回 |
| 循环停不下来 | 工具结果不满足模型判断 | 设max_iterations,优化返回值信息量 |
| 多轮后失忆 | 记忆未接入或占位符缺失 | 检查 memory 与chat_history接线 |
| token 消耗暴涨 | 历史全量拼接 | 换窗口/摘要记忆,精简工具描述 |
这张表建议截图存着,出问题时按顺序过一遍,能省下大量瞎试的时间。
8. LangChain 和 LangGraph 到底怎么选,以及后续怎么走
8.1 两者不是替代关系,是分工关系
“LangChain 和 LangGraph 都过时了吗”这类问题,我的回答一直是:它们解决的是不同复杂度的问题。LangChain 的 Agent 适合流程相对线性、循环结构清晰的场景,比如“想→调工具→再想→给答案”这种。它的抽象层级高,写得快,代价是对复杂流程的控制力有限。
LangGraph 把工作流抽象成图,节点是处理步骤,边是流转条件,状态在节点间传递。它擅长的是带分支、带循环、带人工审批节点的复杂流程。比如一个审批 Agent,要根据金额大小走不同审批路径,中途可能暂停等人工确认,这种用 LangGraph 表达就自然得多,用 LangChain 硬凑会很别扭。
所以别纠结谁取代谁,先问自己流程复杂到什么程度。简单场景硬上图编排是过度设计,复杂场景硬用链式循环则是自找麻烦。
8.2 什么信号说明你该上 LangGraph 了
有几个信号出现时,我就知道该换工具了。第一,你需要在流程中间暂停、等外部输入(比如人工审批)再继续;第二,你的流程里有明显的条件分支,不同情况走完全不同的路径;第三,你需要对状态做精细的持久化和恢复,比如任务跑一半崩了要能接着跑;第四,你需要多个 Agent 协作,各自负责一块再汇总。
这几种需求用 LangChain 的 AgentExecutor 硬实现会越来越拧巴,代码里塞满 if-else 和状态标记,维护起来痛苦。这时候上 LangGraph,你会觉得它的状态和节点设计终于对上了你的问题形状。但前提是你得先把 Agent 的循环本质理解透,否则学 LangGraph 只是换个地方迷惑。
8.3 我给后续学习排的路线
第一阶段,把这篇里的 ReAct Agent 跑通、改通,能自己加工具、调提示词、排查常见错误。第二阶段,给它加上记忆和检索,做一个能做本地知识库问答的小助手,体会 RAG 和 Agent 结合的效果。第三阶段,找 LangGraph 的入门例子,把你之前用链式写法实现的一个稍微复杂的流程,用图的方式重写一遍,对比两者的表达差异。第四阶段,才是去看各种平台化产品和多 Agent 协作框架,这时候你已经有判断力,不会被概念带着跑。
学 Agent 开发,代码只是表层,真正值钱的是你对“模型如何决策、工具如何设计、循环如何收敛”这些机制的体感。框架会换代,但这些东西不会。我在实际带人的过程中发现,能把一个工具描述改到模型调用准确率明显提升的人,后面学什么框架都快,因为他抓的是本质。反过来,只追新框架、不深挖机制的人,永远在抄模板和调 bug 之间循环。这篇笔记里的每个示例都建议你亲手敲一遍、改几个参数看变化,比读十遍都有用。