
有一次同事拿着一份 LangChain 教程从环境配置敲到模型初始化屏幕上的 agent 也真的回答了问题。结果一接到真实项目就卡住加日志报错换一个模型服务商连密钥都识别不了更别提让 Agent 正确调用工具。后来排查半天问题不在代码而在版本和抽象之间的错配。这个经历很典型。学习 LangChain 最容易被低估的一点是它不是一个“安装后就能一直用”的普通库而是一套仍在快速演进的应用编排体系。从最早期的 Chain到现在的 LCEL、LangGraph、MCP 接入概念层一直在变化。如果只是跟着某篇教程敲代码很容易在换场景后失去方向。LangChain 真正要解决的核心问题不是“怎么调大模型 API”而是“怎么把大模型应用做成一条可维护、可复用、可观测、可接入外部工具的流水线”。顺着这条主线环境配置、模型初始化、Middleware 中间件、ReAct、AI Agent、MCP、Skills、Prompt 这些看起来分散的概念其实都属于同一张地图上的不同层次。1. 先别急着写代码LangChain 解决的是 LLM 应用里的“编排问题”很多人第一次接触 LangChain会以为它只是把prompt和model拼在一起的封装库。这不算错但只理解到这个程度远远不够。如果只是“输入一段文本返回一段文本”原生 SDK 甚至 HTTP 请求就够了根本不需要 LangChain 这种抽象层。真正需要 LangChain 的场景是当你的业务不止一步调用时。比如先判断用户意图再决定是否需要查数据库查完数据库后再根据结果调用某个外部工具工具返回后还要把这些信息整理成一段结构化报告。如果每一步都用原生 SDK 手写第一次能跑通第二次会开始复制粘贴第三次逻辑分支一多代码就变得不可维护。LangChain 的价值在于它把大模型应用拆解成可以组合的零件模型封装、提示模板、输出解析、记忆存储、工具注册、链路编排、状态流转。你不需要自己为每个模型服务商都写一套适配层也不需要为了“先检索再总结”这种流程去手工拼字符串。但这里有一个反直觉的点LangChain 并没有替你解决“架构决策”。它只是提供了更多选择。1.1 一个 LLM 应用要控制的要素远不止“提问”实际落地一个 LLM 应用时需要控制的变量至少有这些模型调用层不同供应商、不同模型版本、超时时间、温度参数、密钥管理提示词层系统提示、用户输入模板、few-shot 示例、工具使用说明输入输出层把用户文本转成结构化请求把模型返回转成可解析结果外部工具层数据库查询、文件读取、API 调用、MCP Server 接入状态与记忆层多轮对话、Agent 内部状态、任务历史可观测层耗时、Token 消耗、失败原因、调用链路这些要素之间的关系是“分层”的。模型是底座Prompt 是输入规范链和 Agent 是流程组织方式工具是外部能力MCP 是工具接入协议中间件负责横切逻辑。很多教程只讲其中某一层导致读者以为 LangChain 就是“Prompt 拼接工具”。实际上LangChain 的核心抽象思路是凡是能进入一条“处理流水线”的零件都可以被统一成 Runnable。在较新的写法里一个 Prompt 模板、一个模型对象、一个输出解析器甚至一个外部工具都可以用管道符串联起来。# 常见写法把 prompt、model、parser 串成一条链 from langchain_core.output_parsers import StrOutputParser from langchain_core.prompts import ChatPromptTemplate from langchain_openai import ChatOpenAI model ChatOpenAI(modelgpt-4o-mini, temperature0.2) prompt ChatPromptTemplate.from_messages([ (system, 你是一个严谨的中文技术助手。), (human, {input}), ]) chain prompt | model | StrOutputParser() result chain.invoke({input: 用一句话解释 LangChain 是什么}) print(result)这段代码不是要你背 API而是理解其中的管道思想。prompt负责把输入整理成消息结构model拿到消息后调用模型StrOutputParser负责把模型返回的内容转换成文本。每一步都是独立零件可以替换也可以插桩。1.2 版本变化不是噪音而是框架演进的信号LangChain 的演进路径会让人困惑早期有Chain后来主推LCEL再往后 Agent 编排越来越多地出现在 LangGraph 或 Agent 相关模块里。加上langchain-openai这类按服务商拆分的包旧教程里的from langchain.llms import OpenAI可能已经跑不通。这其实不是框架故意折腾人而是它在从“简单链式封装”走向“可控的图状态编排”。你越早理解这个方向就越不容易被版本变化打乱节奏。我的建议是不要把 LangChain 当成一套固定 API 去背而是把它当成一组能表达 LLM 应用结构的“语言”。你首先要知道自己需要的是线性链、条件分支、循环还是复杂状态流然后才去查对应模块应该怎么用。2. 环境配置与模型初始化先跑通最小链路再谈中间件和 Agent环境配置是 LangChain 学习中最没技术含量、却最容易卡住人的环节。问题往往不在 Python 本身而在依赖组合和版本匹配。常见的坑是这样的某个包要求langchain-core的版本范围是 A另一个包却要求 B或者网上教程安装的是旧版组合你照着装的时候已经是最新版本接口自然对不上。所以环境配置的第一步不是“装最新”而是“固定一套自己能复现的依赖组合”。2.1 环境准备虚拟环境 依赖锁定先建一个干净的虚拟环境避免和本机其他 Python 项目互相污染。python -m venv .venv source .venv/bin/activate # Windows 下执行 .venv\Scripts\activate python -m pip install --upgrade pip安装时不要一次性把所有包盲装。LangChain 生态里核心包、具体服务商包、工具组件包是分开的。你需要明确自己要接哪个模型服务商。举个例子如果接 OpenAI 风格的接口通常需要langchain、langchain-core、langchain-openai这类相关包如果要接其他服务商则要装对应的langchain-xxx。不同供应商和不同服务商的依赖关系不一样这就意味着与其直接抄一条安装命令不如在项目里用requirements.txt或pyproject.toml把版本锁定。等能正常跑通后再用锁文件把整套依赖固定下来。2.2 模型初始化与密钥管理模型初始化看起来简单但有一个非常值得注意的习惯不要把 API Key 硬编码在代码里。常见的做法是放在.env文件里程序启动时加载环境变量。# .env 示例 LLM_API_KEYyour_api_key_here LLM_BASE_URLhttps://api.example.com/v1 LLM_MODEL_NAMEgpt-4o-mini然后在代码中从环境变量读取配置。import os from langchain_openai import ChatOpenAI model ChatOpenAI( modelos.getenv(LLM_MODEL_NAME, gpt-4o-mini), temperature0.2, api_keyos.getenv(LLM_API_KEY), base_urlos.getenv(LLM_BASE_URL, None), )这里需要区分几个参数的含义model决定模型的能力量级。不同模型在逻辑推理、指令遵循、成本、延迟上差异很大。temperature控制随机性。Agent 场景通常不建议太高否则工具选择会不稳定。base_url有些服务商兼容 OpenAI 协议可以通过这个参数指向自己的服务地址。如果你的模型来自不同服务商就需要选择对应的 LangChain 包。密钥管理看起来是很小的习惯但实际影响很大。一旦把 Key 写死在代码里项目很可能因为一次代码同步就把密钥泄露出去。更合理的是让密钥与代码分离同时为不同环境准备不同配置。2.3 最小链路验证先证明“输入到输出”没有断很多项目失败不是因为后期 Agent 设计复杂而是从一开始就没有把最小链路验证清楚。所谓最小链路就是构造一个最简单的问题。调用一次模型。拿到文本结果。确认整个流程没有报错。顺便记录一次 Token 消耗。不要在一开始就引入向量库、Agent、MCP 这些组件。每多引入一个组件就多一个排查变量。先用单次调用确认密钥有效、模型名称正确、网络能通、Prompt 和输出解析器能正常工作。只有最基础的链条稳定后面的中间件和 Agent 才有意义。3. 新版 Middleware 中间件把横切逻辑从业务链里抽出来项目标题里频繁出现“新版 Middleware 中间件应用”这其实是 LangChain 系列应用从“能跑”走向“能维护”的分水岭。中间件这个概念本身不神秘。它是在请求进入和结果返回的路径上插入一段与具体业务无关的公共逻辑。你不需要改动主链路的业务代码就能完成日志、限流、缓存、重试、输入校验等操作。LangChain 里的管道式写法天然适合这种横切逻辑。你可以把一次invoke()想象成这样一条流水线用户请求 - 中间件前的检查限流 / 入参日志 / 身份注入 - Prompt 模板 - LLM 模型调用 - 输出解析 - 中间件后的处理耗时日志 / Token 统计 / 结果缓存 - 返回给业务代码中间件不修改“这道菜怎么做”而是在菜从厨房端出来的前后负责记录、检查、过滤和补偿。3.1 为什么中间件在 AI 应用里尤其重要传统服务端有拦截器、过滤器、中间件体系是因为请求会经过网关节。但 LangChain 应用的调用链不像 Web 请求那样天然分层。每个业务链内部都可能多次调用模型如果日志、重试、限流逻辑全部散落在代码里排查起来会非常痛苦。举一个典型例子你有一个任务需要先总结用户输入再调用工具查询订单最后生成回复。如果这三步都要记录耗时和 Token没有中间件时你就得在每一步前后手动加日志。一旦链路变长日志就会混进业务代码别人读代码时根本分不清哪些是业务逻辑哪些是公共逻辑。中间件解决的是让“系统级关注点”和“业务级关注点”分离。业务代码只关心“怎么完成任务”日志、限流、重试这类逻辑放到公共层统一处理。3.2 哪些逻辑适合放中间件根据实践有四类横切逻辑比较适合放进中间件层日志与可观测记录输入摘要、输出摘要、耗时、Token 消耗。特别要注意脱敏不要把用户的隐私字段、API Key、完整上下文都打出来。限流与配额在进入模型调用之前做检查和等待避免单个任务把请求额度打满。缓存对完全可复用、且和用户上下文无关的结果做缓存。缓存命中时可以直接跳过模型调用能节省大量成本。重试与超时控制模型偶发超时或返回格式异常时进行有限次重试。这里特别提醒一句重试要小心。如果一条链只做“文本生成”重试通常安全。但如果 Agent 已经调用了真实业务工具比如创建订单、发消息、扣减库存等盲目重试可能导致重复执行。所以重试逻辑必须配合幂等设计或者在调用非幂等工具前增加人工确认。3.3 中间件不是“背 API”而是先理解前置与后置关于新版中间件的具体类名和包结构不同版本差别比较大。我不建议读者直接去记忆某个固定实现而是先理解它的结构在模型调用前要做哪些事在模型返回后要做哪些事在异常发生时要不要补偿或重试。用一个通用伪代码来表达这种结构# 通用中间件伪代码仅用于理解结构 class LoggingMiddleware: def wrap(self, runnable): def handler(inputs): print(before:, truncate(inputs)) result runnable.invoke(inputs) print(after:, truncate(result)) return result return handler实际项目里的实现可能复杂很多但原理是一致的。先把这种“前置逻辑 / 主调用 / 后置逻辑”的思维模型建立起来再去看具体版本对应什么语法就不会被接口变化绕晕。中间件的边界同样要明确。不要把强业务分支判断塞进中间件也不要在中间件里塞太多统计逻辑。否则你会在排查问题时遇到“流程总在中间件中断但业务代码里没有任何线索”的窘境。4. ReAct 与 AI Agent不是多调一次模型而是进入循环聊到 AI Agent很多资料都会提 ReAct。这个词有一个很容易踩的混淆点它和前端框架 React 没有任何关系。虽然拼写接近但 ReAct 在智能体语境里代表的是“Reasoning Acting”也就是“推理与行动交替进行”。你会在搜索框里看到 React 18 的更新批处理机制、React 面试题、React Router 等内容那些属于前端领域。LangChain 里的 ReAct讨论的是模型如何在一轮又一轮的推理和行动中完成复杂任务。4.1 为什么普通 Chain 不够用一条普通 Chain本质上是“输入 - Prompt - 模型 - 解析 - 输出”。它适合任务路径完全确定的场景。但现实里很多任务并不能一次性决定完整的执行路径。比如用户问帮我查一下订单 A7 的状态如果失败了就触发一次补偿重试。如果用普通 Chain你会先让模型回答一个 JSON里面包含是否查询、查询参数、后续动作。但这没有真正解决“动态决策”的问题因为你不能提前知道订单状态是什么也就不能把分支预先写在 Prompt 里。ReAct 的思路是把决策和执行拆成循环Thought思考现在需要判断“订单 A7 是什么状态”。Action行动调用订单查询工具传入订单号。Observation观察工具返回“订单失败原因是库存不足”。Thought再思考库存不足并不适合直接重试应该返回用户并提示补充库存状态。Final Answer最终回答向用户说明失败原因。在这个过程中模型不是一次性“背出”答案而是根据工具返回结果动态决定下一步。这才是 Agent 与 Chain 的本质差别。4.2 一个最小 ReAct 流程的直观理解下面这段不是某个具体框架的代码而是帮助理解循环结构的伪示意Task: 查询订单 A7 状态并决定是否重试 Thought: 我需要先查询订单状态。 Action: query_order(order_idA7) Observation: statusfailed, reasoninventory_not_enough Thought: 库存不足时不适合直接重试应该结束。 Final Answer: 订单 A7 失败原因是库存不足建议补货后再处理。关键点在于Action 的参数和下一轮 Thought 都依赖 Observation 的结果。所以 Agent 必须有“状态”概念它要记住目前已经观察到什么、已经调用过什么工具、下一步还剩哪些选择。4.3 LangGraph 和 LangChain 的分工很多搜索词里都会出现“LangChain 和 LangGraph 的区别”。简单说LangChain 提供的是各种组件和抽象LangGraph 提供的则是能承载循环和状态流转的“运行框架”。如果任务是“查一个值然后回复”普通的 Chain 就够了。如果任务是“模型动态决定要不要多次调用工具并根据中间结果调整策略”就进入 Agent 场景。Agent 场景通常需要这么几个能力维护多轮思考与观察的状态。支持条件分支和循环。能在超时或达到最大轮数时停止。能插入人工审核节点。这些能力如果全部自己实现会很繁琐。LangGraph 这类方案的价值不是让代码更酷而是让你把 Agent 决策过程变成可观测、可恢复、可中止的状态流。给一个实际建议不要为了“用 Agent 而用 Agent”。如果一个任务用链加条件判断就能解决不要引入循环和状态如果一个任务确实依赖动态工具调用再考虑Agent。Agent 越多不可控边界越大。4.4 让 Agent 更容易成功控制工具范围模型不是工具越多就越强。相反当上下文里塞满十几个工具描述时模型做出错误选择或漏