
很多人刚开始接触LangChain的时候最容易卡住的概念就是Chain我也一样。当时我拿着官方文档看了半天感觉每个字都认识但一落到自己的项目里还是只会写一句llm.invoke(...)根本不知道Chain到底帮我解决了什么问题。后来在一个需要把“用户评论分析、关键词抽取、文案重写”串起来的项目里我才真正理解Chain的价值它不是为了炫技而是把一次独立的模型调用变成一条可控、可复用、能编排的流水线。这篇文章我会从“Chain解决了什么”讲起拆开最常用的几种Chain的源码行为和传参逻辑再结合我这几年在项目里踩过的坑把使用细节和取舍一次说清楚。不管你是刚入门LangChain还是已经在用但总觉得哪里不对劲这篇都值得你花几分钟认真过一遍。1. 先理解Chain在LangChain中的定位1.1 从“一次调用”到“链式编排”先说一个最朴素的场景。你有一个大模型API的Key想让它帮你做点事比如“把一段产品描述翻译成英文”。最原始的做法就是拼一个Prompt然后调用模型接口prompt fTranslate the following product description into English:\n{description} response openai.chat.completions.create( modelgpt-4o-mini, messages[{role: user, content: prompt}] )这段代码能跑但问题很快就会出现当任务从“翻译”变成“先判断语言再翻译再检查质量再格式化成JSON输出”的时候你就要在代码里手动维护状态、传递中间变量、处理异常分支。开始还能忍等流程变长代码就变成一坨无法复用、没法测试的意大利面。Chain的这个抽象本质上就是把“一次调用”封装成“一个步骤”再把这些步骤按顺序或按条件组合起来。每个步骤有明确的输入输出步骤之间通过变量名传递数据。你不再关心模型API的细节只关心“这一步吃什么、吐什么”。LangChain早期最有代表性的就是LLMChain它把PromptTemplate、LLM、OutputParser粘在一起。后续的SequentialChain、RouterChain、TransformChain都是在“步骤编排”这个思路上做的更细的封装。1.2 常见Chain类型对照我刚接触Chain的时候最困惑的不是单个Chain怎么用而是“我有这么多Chain到底该选哪个”。这里我先给一张我后来自己整理的对照表后面再逐个讲Chain类型解决什么问题典型使用场景记忆点LLMChain完成一次“Prompt模板模型调用输出解析”翻译、生成、抽取最基础的原子步骤SimpleSequentialChain按固定顺序串联多个单输入单输出步骤先总结再翻译只管顺序不管复杂传参SequentialChain串联多个步骤支持多变量输入输出多字段分析后统一决策可控传参变量名要小心RouterChain/LLMRouterChain根据输入内容动态选择后续步骤客服分流、场景识别用路由决定走哪条分支TransformChain在步骤中插入普通Python函数数据清洗、格式转换给流程塞自定义逻辑这张表不用背你只要记住一个原则任务的复杂度决定了Chain的选型而不是反过来。单次调用用LLMChain固定几步流程用SequentialChain需要分叉用RouterChain需要插自定义处理用TransformChain。后面几章我会拿真实代码来印证这个判断。2. 最小可跑的LLMChain组件与运行机制2.1 先搭一个最小实现如果你完全没接触过Chain我建议别急着看复杂的编排先把LLMChain跑通。它其实只有三部分提示模板、模型、链。from langchain_openai import ChatOpenAI from langchain.prompts import ChatPromptTemplate from langchain.chains import LLMChain # 1. 提示模板规定输入变量 prompt ChatPromptTemplate.from_template( 你是一位资深文案编辑。请将下面这段产品描述润色为更吸引人的版本保持原意。\n描述{description} ) # 2. 模型确定谁来干活 llm ChatOpenAI(modelgpt-4o-mini, temperature0.3) # 3. 链把模板和模型组合起来 chain LLMChain(llmllm, promptprompt) # 运行 result chain.invoke({description: 这是一款支持快充的无线蓝牙耳机续航长达30小时。}) print(result[text])注意几个关键点chain.invoke()返回的是一个字典默认键是text不是字符串。新手最容易在这里翻车以为返回的是result本身。模板里写了{description}那么invoke的时候就必须传一个包含description键的字典多传、少传都会报错。如果你是跟着某个老教程写的chain.run()在新版本里也还能用但它只是个易用的包装底层还是invoke。这一步跑通以后你就已经理解了Chain的最小单元。接下来要做的就是在它外面套各种编排逻辑。2.2 输出解析器让结果不再是“裸文本”上面那个例子返回的是纯文本但在真实项目里我们通常希望模型输出的是结构化数据比如JSON、列表、或者布尔值。OutputParser就是干这个的。from langchain.output_parsers import StructuredOutputParser, ResponseSchema from langchain_core.output_parsers import JsonOutputParser response_schemas [ ResponseSchema(namekeywords, description提取到的关键词列表), ResponseSchema(nametone, description文案的语调如积极、中性、消极), ] parser StructuredOutputParser.from_response_schemas(response_schemas) prompt ChatPromptTemplate.from_template( 分析以下产品描述提取字段。\n{description}\n\n{format_instructions} ) chain LLMChain( llmllm, promptprompt, output_parserparser, verboseTrue, ) result chain.invoke({description: 这款耳机降噪效果很好但价格偏高。}) print(result[text])这里我强烈建议把verboseTrue打开。你会看到每一次调用的详细输入输出对于理解Chain内部发生了什么非常有帮助。我自己在调试阶段几乎永远开着它排错效率能提高一倍以上。2.3 LLMChain运行机制拆解LLMChain内部跑起来就三步逻辑很简单第一步填充模板。prompt.format(description...)把传入的字典套进模板生成完整的提示词字符串。第二步调用模型。把提示词传给llm对象得到模型返回的字符串。第三步解析输出。如果配置了output_parser就对模型返回的字符串做解析如果没有就原样返回。这个机制解释了为什么LLMChain的输入输出都跟字典打交道它要保证在不同的组件之间传递时参数名能对上。prompt里声明哪些变量invoke时就得提供哪些变量这是Chain使用中最基本的规矩。3. 多个Chain的串联与传参3.1 SimpleSequentialChain最省事的顺序执行当你有多个LLMChain而且前一个的输出恰好是后一个的输入时SimpleSequentialChain是最无脑的选择。举个我的实际例子。当时我需要处理一批英文客服邮件流程是先把英文邮件翻译成中文再把中文邮件总结成要点。这正好是“单输入→单输出→单输入→单输出”的结构。from langchain.chains import SimpleSequentialChain translate_prompt ChatPromptTemplate.from_template( 将以下英文邮件翻译成中文\n{email} ) translate_chain LLMChain(llmllm, prompttranslate_prompt) summarize_prompt ChatPromptTemplate.from_template( 将以下中文内容总结为三个要点\n{chat_text} ) summarize_chain LLMChain(llmllm, promptsummarize_prompt) overall_chain SimpleSequentialChain( chains[translate_chain, summarize_chain], verboseTrue, ) result overall_chain.invoke(Dear Sir, ...) print(result[output])这里有个容易误解的地方SimpleSequentialChain内部会自动把前一个Chain的输出赋值给后一个Chain的输入变量。在translate_chain里输出变量叫text到了summarize_chaintext就自动变成chat_text的输入值了。你不用手动指定映射关系。但代价就是它只能处理“单输入单输出”的链。只要你的某一步需要额外的外部参数这个Chain就直接不适用了。3.2 SequentialChain处理多变量的关键管道真实业务里更常见的情况是前一步的输出只是后一步的一个输入而已。举个例子我做过一个“商品评论分析”的流程第一步从评论里抽取sentiment和category两个字段 第二步结合原始评论和这两个字段生成一条回复建议。这个流程中第二步需要三个输入原始评论review、情感sentiment、类别category。SimpleSequentialChain做不到SequentialChain就是专门干这个的。from langchain.chains import SequentialChain extract_prompt ChatPromptTemplate.from_template( 分析以下商品评论判断情感倾向积极/消极/中性和商品类别数码/服饰/家居。 请严格按照 JSON 格式回答{\sentiment\: ..., \category\: ...}\n评论{review} ) reply_prompt ChatPromptTemplate.from_template( 请根据评论和情感分析结果生成一条客服回复建议。\n 原始评论{review}\n 情感倾向{sentiment}\n 商品类别{category}\n 回复建议 ) extract_chain LLMChain(llmllm, promptextract_prompt, output_keyanalysis) reply_chain LLMChain(llmllm, promptreply_prompt, output_keyreply) overall_chain SequentialChain( chains[extract_chain, reply_chain], input_variables[review], output_variables[reply], verboseTrue, ) overall_chain.invoke({review: 这耳机音质不错但戴久了耳朵疼。})注意三个点第一extract_chain必须指定output_keyanalysis不然它的输出会被默认的text覆盖后面就找不到了。这一步是我见过最多的报错根源。第二SequentialChain的input_variables声明的是“整个大链接受的外部输入”output_variables声明的是“整个大链对外暴露的结果”。中间变量比如analysis只要链条内部传递就行不用写出来。第三reply_prompt模板里的{review}和{sentiment}、{category}它们分别来自原始输入review和上一步解析出的analysis字典。也就是说analysis可以是一个字典也可以是字符串只要里面包含这些变量名就能被后续的模板取到。3.3 传参细节与命名规范关于Chain的传参我有一个从实战里总结出来的排查顺序先检查模板变量名再检查output_key最后检查input_variables。出错顺序通常也是这个。我整理了一个常见错误对照表你们可以收藏一下报错现象原因解决办法Missing variablesPrompt里写了{var}但invoke时没传检查input_variables和实际传入字典unexpected keyword argument传了模板里不需要的变量删除多余传参或修改模板后一个Chain拿到的是text而非上一步结果没有设置output_key在LLMChain里增加output_key输出结果包含很多无关废话模板没有给格式约束加OutputParser或format_instructions命名规范上我建议所有中间结果都用下划线分隔的全局唯一名称比如sentiment_result、category_result不要用x、y、t这种。因为Chain一多变量名就是数据接口命名乱套后面调试起来会非常痛苦。4. 路由与转换让Chain具备决策能力4.1 LLMRouterChain让模型决定走哪条路固定顺序的Chain处理不了“条件分支”的问题。比如我做过一个客服机器人用户的问题可能属于“退款”“物流”“产品推荐”三类每一类应该走不同的处理流程。这时候就可以用RouterChain。先看核心代码再解释原理from langchain.chains.router import LLMRouterChain from langchain.chains.router.llm_router import LLMRouterChain as _LLMRouterChain from langchain.chains.router.llm_router import RouterOutputParser # 1. 定义多个目标链 refund_prompt ChatPromptTemplate.from_template(你是退款客服请回答{input}) refund_chain LLMChain(llmllm, promptrefund_prompt) logistics_prompt ChatPromptTemplate.from_template(你是物流客服请回答{input}) logistics_chain LLMChain(llmllm, promptlogistics_prompt) recommend_prompt ChatPromptTemplate.from_template(你是推荐顾问请回答{input}) recommend_chain LLMChain(llmllm, promptrecommend_prompt) # 2. 定义路由目标描述 router_template 你是用户意图路由员。根据用户问题选择最合适的处理链。 可选链名称与描述 - refund_chain用户询问退款、退货、金额退回相关 - logistics_chain用户询问物流、快递、配送时间相关 - recommend_chain用户询问产品推荐、选择建议相关 用户问题{input} 请只输出目标链名称。 router_prompt PromptTemplate( templaterouter_template, input_variables[input], ) router_chain LLMRouterChain( llmllm, promptrouter_prompt, router_parserRouterOutputParser(), ) # 3. 路由分发 def route_and_run(user_input): destination router_chain.invoke({input: user_input}) chain_name destination[destination] if chain_name refund_chain: return refund_chain.invoke({input: user_input}) elif chain_name logistics_chain: return logistics_chain.invoke({input: user_input}) elif chain_name recommend_chain: return recommend_chain.invoke({input: user_input}) else: return {text: 抱歉我暂时无法处理该问题。}这段代码里route_and_run的手动分发是我在实际项目中常用的写法。虽然LangChain官方有RouterChain配合MultiRouteChain的封装但那个封装引入了default_chain等概念链多了以后配置反而复杂。我更建议你手动写几行if-elif把路由分发逻辑放在自己的控制范围内调试时一目了然。4.2 TransformChain把Python函数编入流程很多时候流程里不只有模型调用还需要一些纯代码处理比如清洗文本、计算字段、读写数据库。TransformChain的价值就在于让你能在Chain构成的流水线里插入普通Python函数。import re from langchain.chains import TransformChain def clean_text(inputs): text inputs[raw_text] # 去除多余空白和特殊字符 cleaned re.sub(r\s, , text).strip() return {cleaned_text: cleaned} transform_chain TransformChain( input_variables[raw_text], output_variables[cleaned_text], transformclean_text, ) # 单独使用 result transform_chain.invoke({raw_text: 这是 一段 不规\n则的 文本}) print(result[cleaned_text])TransformChain的函数规则很简单接收一个字典返回一个字典。输入字典的键必须在input_variables里声明输出字典的键必须在output_variables里声明多写、漏写都会报错。把它和LLMChain组合起来的时候特别顺手。比如我在一个数据清洗项目里先用TransformChain做正则清理再用LLMChain做错误纠正最后再用TransformChain做格式校验。整个过程三个步骤但只有中间一步调用了模型成本低逻辑也清楚。4.3 RouterChain的底层原理刚才用LLMRouterChain时你可能好奇它内部是怎么知道该选择哪条链的。其实它的核心不是一个特殊的模型而是一个带固定模板的LLMChain。LLMRouterChain内部的模板会让模型输出JSON格式的结果里面包含destination目标链名称和next_inputs传给目标链的输入。然后RouterOutputParser会把模型输出的JSON解析成路由指令。所以你在定义路由模板时必须把链名称和用途描述写得足够清晰否则模型就会选错。这里有一个非常关键的经验路由模板里的链描述不要写得太短。比如光写“退款”两个字模型经常会把“我买的东西不合适想退”归类到“推荐”里去。至少要写到“用户询问退款、退货、金额退回相关”这种程度模型才能准确区分边界。5. 实际项目经验与避坑5.1 常见报错与排查实录我在使用Chain的过程中遇到最多的报错不是链本身的问题而是版本升级带来的API变化。比如langchain从0.x升到1.0的过程中很多Chain类从主包移到了langchain.chains参数名也变过好几轮。我建议你在项目里锁定langchain和langchain-openai的版本不要轻易升。另外还有几个高频问题我列一个速查表场景报错或异常我的处理方式第一次运行LLMChainTypeError: invoke() missing 1 required positional argument确认调用方式是chain.invoke({...})不是chain.invoke(...)输出内容被截断模型只返回了一部分JSON在模板中增加“只输出JSON不要解释”的约束并调高max_tokens中文乱码控制台打印正常但保存文件乱码检查文件写盘编码用utf-8明确指定Token费用飙升每个步骤都在重复传长文本使用TransformChain或自定义函数压缩中间结果只传必要字段无法导入ChainModuleNotFoundError: No module named langchain.chains.router确认安装了langchain-community或升级langchain版本5.2 新旧API差异Chain该不该继续学聊到这里你可能会在网上看到一些说法LangChain官方已经不怎么推荐LLMChain了推荐用LCEL表达式和LangGraph。这个观点有道理但我不建议一上来就直接跳到LCEL和LangGraph原因有三个第一Chain是理解LangChain数据流的最佳入门模型。“Prompt进去结果出来中间变量显式传参”这套思维是理解LCEL管道符|语法的基础。你没搞懂变量传递直接写LCEL很容易被一堆RunnablePassthrough绕晕。第二市面上现存的教程、项目、旧代码大量还是Chain风格。你能读懂Chain才能去改这些项目才能看懂别人代码里的LLMChain在干什么。第三LCEL和LangGraph并不是彻底取代Chain它们解决的是更复杂的图结构和流式处理需求。对于简单的固定流水线Chain的代码可读性反而更高。所以我个人的建议是用Chain入门用LCEL做新项目用LangGraph处理真正的复杂状态流。三者不冲突是递进关系。5.3 从Chain走向LCEL和LangGraph如果你已经掌握了Chain的编排思路再往前进一小步就是LCEL。LCEL用|把组件串联起来看起来更简洁from langchain_core.runnables import RunnablePassthrough chain_lcel ( {description: RunnablePassthrough()} | prompt | llm | parser ) result chain_lcel.invoke(一款支持快充的无线蓝牙耳机)这段代码和前面的LLMChain做的事情几乎一样但写法上把“数据传递”变成了管道流。RunnablePassthrough用于透传变量|相当于把前一步输出接到下一步输入。等你需要更复杂的循环、并行、条件分支时再去LangGraph。LangGraph把每一步看作图中的节点支持循环、停止、状态持久化适合构建真正的Agent系统。但它要理解的概念也更多。所以如果你现在正在读这篇文章我建议你踏踏实实把Chain练熟。遇到报错不要慌用verboseTrue看详细日志再对照变量名和output_key排查。这些基本功比单纯追新框架更值钱。最后再分享一个小技巧凡是Chain里涉及模型输出的结构化解析你都可以在模板末尾加一句“请确保输出是合法的JSON不要包含其他文字”。这句话能省掉你大量的json.loads报错时间。我在多个项目里实测加上这句之后模型输出JSON的稳定性能从80%出头提高到98%以上。