1. 先理清思路:Chain 到底在解决什么问题
做 LangChain 开发的同学,多多少少都写过这样的代码:一个 PromptTemplate 包一段 Prompt,扔给大模型,拿到字符串就结束了。可一旦业务复杂起来,比如用户输入要先分类,再按不同类型走不同的生成流程,最后还要汇总润色,单链就完全不够用了。这篇文章是 Chain 详解系列的第二篇,上一篇聊了 Chain 的底层构成和接线方式,这一篇重点拆三件事:基础链 LLMChain、顺序链 SequentialChain、分支链 RouterChain,以及怎么把这三者组合起来,搭出一套真正能处理复杂逻辑的管道。适合看这篇内容的读者:已经能用 LangChain 跑通一问一答,但不知道怎么组织多步流程的人;想把业务拆成可维护模块、而不是把所有逻辑都写在一个超长 Prompt 里的开发者。
1.1 LLM 的“一问一答”和业务的“一步一步”
大模型本身是“无状态”的,你问一句,它答一句,上一轮对话里的结论不会自动变成下一轮处理的上下文。打个比方,它像一个能力很强但记性不好的实习生:你让他“帮我写一篇产品推广文案”,他能瞬间交出一版;但你让他“先分析产品卖点,再确定目标人群,再写初稿,最后润色”,如果不把每一步的结果显式传给下一步,他大概率会在某一步跑偏,甚至忘记你最初的需求。
Chain 要解决的核心问题就是“步骤管理”。它把一次完整的大模型任务拆成多个小步骤,每个小步骤只做一件事,然后通过链条把这些步骤串起来。步骤之间传递什么、保留什么、丢弃什么,都由开发者显式控制。这样带来的好处很直接:每一步都可以单独调试,某一步效果不好就只改那一步,不会牵一发而动全身。
1.2 一个现实业务链路长什么样
举个例子,客服机器人。用户消息进来之后,你不能拿同一套 Prompt 去处理“这个订单怎么还没发货”和“你们家保温杯多少钱”,因为这两类问题的答案风格、信息需求完全不同。现实中我们通常这样做:先让一个路由模型判断消息属于“售前咨询”“售后处理”还是“闲聊”;然后每个分类走不同的处理链,比如售后处理链里还可能接入订单查询工具;最后把结果统一修饰成客服口吻返回给用户。
这条链路天然就是“分支链 + 顺序链 + 基础链”的组合:第一步分支,第二步执行各自的子流程,第三步再汇总。如果只用一个大 Prompt 硬扛,Prompt 会越写越长,模型越来越容易输出模板化、偏离业务的结果;就算想调整某一类问题的回复风格,也要在一个巨型 Prompt 里小心翼翼地改,改完还担心影响其他场景。
1.3 三类链各自的定位
| 链类型 | 英文名 | 职责 | 一句话理解 |
|---|---|---|---|
| 基础链 | LLMChain | 单个 Prompt + 单个模型的一次调用 | 最原子的积木 |
| 顺序链 | SequentialChain / SimpleSequentialChain | 多个基础链按固定顺序执行 | 积木按顺序拼接 |
| 分支链 | RouterChain / MultiPromptChain | 根据输入内容动态选择执行哪条链 | 积木按条件选择 |
基础链是积木,顺序链是串行拼法,分支链是选择拼法。后面所有复杂流程,本质上都是这三种拼法的组合。想明白这一点,LangChain 的工程化思路就清晰了一大半。
2. 基础链 LLMChain:一切编排的起点
2.1 它内部到底做了什么
LLMChain 是 LangChain 里最基础、最常用的链,内部结构并不神秘:一个 PromptTemplate,接收一个字典输入,渲染成最终文本,扔给 LLM,再把模型返回的字符串拿出来。用代码表示就是这样:
用户字典{"product": "智能保温杯"} -> PromptTemplate 渲染 -> "给智能保温杯写一句slogan..." -> LLM -> 返回字符串看到这里你会发现,它本质上还是“一次模型调用”。那为什么不直接在代码里写llm(prompt)呢?因为 LLMChain 的价值不在“这一次调用”,而在它是后续所有编排的最小单元。SequentialChain 会把它作为叶子节点挂到流水线上,RouterChain 会把多条 LLMChain 作为候选分支,你需要一个统一的结构来承载这些逻辑。统一之后,每条链的输入、输出、Prompt、verbose 调试开关都有了固定格式,代码才能规模化。
2.2 最小可运行示例
先看一个最朴素的用法。假设我要做一个“产品 slogan 生成器”,一个 LLMChain 就够了:
from langchain.chains import LLMChain from langchain.prompts import PromptTemplate # llm 指你已经接入的大模型实例 # 不管底层用的是哪家 SDK,只要能被 LangChain 调用即可 llm = None # 这里换成实际项目里的模型对象 prompt = PromptTemplate( input_variables=["product"], template=( "你是一位营销文案专家。" "给下面的产品写一句 slogan,要求简洁、有传播力。\n" "产品:{product}\n" "结果:" ), ) chain = LLMChain(llm=llm, prompt=prompt, verbose=True) result = chain.invoke({"product": "智能保温杯"}) print(result["text"])这里有几个细节值得新手注意。第一,invoke的入参是一个字典,即使只有一个变量也要写成{"product": "智能保温杯"},不能直接传字符串。第二,返回结果也是一个字典,默认取result["text"];如果你的链配置了output_key,那就得用对应的 key 去拿。第三,verbose=True非常重要,它会打印每一步的 Prompt 和模型输出,是排查问题最直接的抓手。
2.3 把 PromptTemplate 单独提出来管理
实际项目里我不会把 Prompt 写在业务文件里,而是集中到一个prompts.py模块中统一管理。原因很简单:Prompt 是整个系统里最不稳定、最需要频繁调整的部分。今天想改语气,明天想换风格,后天可能想加几个 few-shot 样例。如果 Prompt 散布在各处,改起来非常痛苦;集中管理之后,你甚至可以给每个 Prompt 写“设计备注”,记录当时的测试结论和注意事项。
命名上我习惯用大写常量,比如PROMPT_SLOGAN_GENERATOR、PROMPT_ARTICLE_OUTLINE。每个常量旁边注明它的输入变量和期望输出格式。另外,如果需要注入固定信息,比如当前日期、用户画像、产品说明,可以用partial_variables做到 PromptTemplate 里,避免在业务代码里反复拼接。
2.4 什么场景不需要用 LLMChain
LLMChain 不是万能钥匙。如果你的场景只是“调一次模型,没有复用价值”,直接调用模型反而更轻量。举个最简单的例子,你只想让模型翻译一句话,那直接llm.invoke("翻译:你好")就够了,没必要再包一层链。
但一旦你的代码里出现“同一个 Prompt 在多处复用”“后续要接其他链”“需要统一配置 verbose 和回调”这些信号,还是建议尽早切到 LLMChain 或类似的可编排对象。我自己的判断标准是:当前调用是不是某个更长流程的一部分。是,就用链;不是,就用裸调用。这个标准能避免很多无意义的封装。
3. 顺序链:把多个步骤串成流水线
3.1 SimpleSequentialChain 和 SequentialChain 看起来像,用起来不一样
LangChain 里和顺序相关有两个链,名字很像,不少新手会搞混。这里用一个表说清楚:
| 对比维度 | SimpleSequentialChain | SequentialChain |
|---|---|---|
| 变量传递 | 自动把上一步输出传给下一步 | 通过 key 显式声明输入输出变量 |
| 输入要求 | 适合单个字符串进、单个字符串出 | 支持多输入、多输出 |
| 配置复杂度 | 低 | 中,需要维护 input_variables 和 output_variables |
| 中间结果可见性 | 不可见,上一步输出被下一步消费 | 中间变量保留在最终结果里 |
| 适合场景 | 短流水线,比如“写故事 -> 提炼观点” | 要保留多个中间结果、或下一步需要多个变量 |
一句话总结:SimpleSequentialChain 适合“一条道走到黑”的场景,SequentialChain 适合“中间过程还要留给后面用”的场景。
3.2 SimpleSequentialChain:一个字符串进,一个字符串出
先看最简单的用法。我要让模型先写一个短故事,再根据故事提炼核心观点:
from langchain.chains import SimpleSequentialChain, LLMChain from langchain.prompts import PromptTemplate first_prompt = PromptTemplate( input_variables=["topic"], template="请围绕「{topic}」写一个 200 字以内的短故事。", ) second_prompt = PromptTemplate( input_variables=["story"], template="请根据下面的故事,提炼出一个核心观点,30 字以内:\n{story}", ) chain_one = LLMChain(llm=llm, prompt=first_prompt) chain_two = LLMChain(llm=llm, prompt=second_prompt) overall_chain = SimpleSequentialChain( chains=[chain_one, chain_two], verbose=True, ) result = overall_chain.invoke("人工智能与教育") print(result["output"])注意两点。第一,SimpleSequentialChain 直接默认上一步的输出就是下一步的输入,所以第二个 PromptTemplate 的输入变量名必须和上一步输出对应上。第一步 Prompt 没有配置output_key,默认输出 key 是text,但 SimpleSequentialChain 会把它自动当作“文本”传给第二环,所以第二步的story变量能拿到故事内容。第二,它只适合单变量场景。如果第二步既想要“原始主题”又想要“生成的故事”,SimpleSequentialChain 就做不到了。
3.3 SequentialChain:多变量显式传递
多变量场景必须用 SequentialChain。这里我举一个很常见的需求:先让模型把标题改得更有吸引力,再让模型分析新标题好在哪里。第二步需要同时拿到“原标题”和“新标题”,所以不能用 SimpleSequentialChain。
from langchain.chains import SequentialChain, LLMChain from langchain.prompts import PromptTemplate first_prompt = PromptTemplate( input_variables=["title"], template="把「{title}」改成一个更吸引人的标题,只输出新标题,不要解释。", ) second_prompt = PromptTemplate( input_variables=["title", "new_title"], template=( "原标题:{title}\n" "新标题:{new_title}\n" "请分析新标题比原标题好在哪,给出 30 字以内的评价。" ), ) chain_one = LLMChain(llm=llm, prompt=first_prompt, output_key="new_title") chain_two = LLMChain(llm=llm, prompt=second_prompt, output_key="comment") sequential = SequentialChain( chains=[chain_one, chain_two], input_variables=["title"], output_variables=["new_title", "comment"], verbose=True, ) result = sequential.invoke({"title": "LangChain 学习笔记"}) print(result["new_title"]) print(result["comment"])这里有三个关键点。第一,第一步链必须显式设置output_key="new_title",相当于是给这段输出起了一个名字,第二步才能用它作为输入变量。第二,第二步 Prompt 的输入变量title和new_title必须和前面定义的 key 完全一致,大小写、拼写都不能错。第三,最外层的output_variables决定了最终返回结果保留哪些字段。如果你想拿到中间大纲,就把大纲的 key 也加进去。
如果你感觉这个设计有点繁琐,可以直接把它理解为“给流水线每个环节分配变量名”。大模型本身没有记忆,前一步的输出不会自动出现在后一步的 Prompt 里,你必须手动搬运。写得越明确,越不容易出错。
3.4 顺序链的顺序敏感问题
顺序链看起来就是把几个 Link 串起来,但实际跑起来会有三个很容易踩的坑。
第一个坑:中间步骤没有约束输出格式。比如第一步让模型“写一个故事”,它可能在故事后面补一句“这个故事告诉我们……”,然后第二步拿到的输入就混入了额外内容。解决方法是每步 Prompt 都强调“只输出某内容,不要解释”,比如“只输出故事正文”“只输出新标题”。这一步看起来简单,但能省掉大量清洗工作。
第二个坑:链的数量失控。我曾经见过有人把十几个 Chain 串成一条超长管道,每步都在传变量,最后调试的时候根本分不清哪个环节把数据搞坏的。我的经验是,一条 SequentialChain 里放 3 到 4 个链就差不多了。如果流程更长,考虑把它拆成多个子流水线,再用 Python 脚本去编排,而不是把所有逻辑都堆在一条链里。
第三个坑:中间结果被覆盖。SequentialChain 的默认行为是“保留你声明过的变量”,但如果你在第二步用了一个和第一步相同的 output_key,旧值就会被覆盖。命名时建议统一加前缀,比如raw_text、clean_text、final_text,避免混淆。
4. 分支链:让流程在关键节点“分岔”
4.1 LangChain 里分支到底是怎么实现的
分支链的官方实现思路,是“用大模型做路由选择”。RouterChain 的底层逻辑并不复杂:拿一个 Prompt 让模型阅读用户输入,然后输出一个包含destination和next_inputs的 JSON,框架再根据destination从字典里找到对应的链去执行。
为什么路由也要用大模型?因为有些输入无法用关键词规则覆盖。比如用户只写了一句“你们产品是不是有问题?”,你很难通过if "退货" in text判断这是售后还是售前咨询。这时候用模型做语义分类更稳。但这也意味着路由环节有代价:一次额外的模型调用、额外的 token 消耗,以及模型偶尔输出不合法 JSON 的风险。所以路由链我一般会把温度设成 0,尽可能减少随机性。
在 LangChain 里,MultiPromptChain是“路由链 + 多条目标链 + 默认链”的组合封装。它能直接用,但在复杂场景下我反而更推荐手动实现分支,后面 5.3 节会讲为什么。
4.2 一个客服分类分支的完整代码
下面的代码基于 LangChain 0.1/0.2 的接口写法,实现功能是:判断用户消息属于“体育”“科技”还是“其他”,然后把消息交给对应领域的专家链处理。
from langchain.chains import LLMChain, LLMRouterChain, MultiPromptChain from langchain.chains.router.llm_router import RouterOutputParser from langchain.chains.router.multi_prompt_prompt import MULTI_PROMPT_ROUTER_TEMPLATE from langchain.prompts import PromptTemplate prompt_infos = [ { "name": "tech", "description": "适合回答技术开发、编程、产品设计相关问题", "prompt_template": "你是一位技术顾问。请回答:{input}", }, { "name": "sports", "description": "适合回答体育比赛、运动员、健身运动相关问题", "prompt_template": "你是一位体育专家。请回答:{input}", }, ] destinations = [f"{p['name']}: {p['description']}" for p in prompt_infos] router_prompt_template = MULTI_PROMPT_ROUTER_TEMPLATE.format( destinations="\n".join(destinations) ) router_prompt = PromptTemplate( template=router_prompt_template, input_variables=["input"], output_parser=RouterOutputParser(), ) router_chain = LLMRouterChain(llm=llm, prompt=router_prompt) destination_chains = {} for p in prompt_infos: prompt = PromptTemplate( template=p["prompt_template"], input_variables=["input"], ) destination_chains[p["name"]] = LLMChain(llm=llm, prompt=prompt) default_prompt = PromptTemplate( input_variables=["input"], template="你是一位万能助理,请简洁专业地回答:{input}", ) default_chain = LLMChain(llm=llm, prompt=default_prompt) multi_prompt_chain = MultiPromptChain( router_chain=router_chain, destination_chains=destination_chains, default_chain=default_chain, verbose=True, ) result = multi_prompt_chain.invoke({"input": "帮我看看这个项目的代码依赖冲突怎么解决"}) print(result["text"])这段代码的运行过程是:先把用户输入丢给路由链,路由链返回一个 JSON,里面指定了要走哪个 destination;然后框架从destination_chains里找到对应链执行;如果路由输出无法识别,就走default_chain。注意default_chain不能省,否则一旦路由解析失败,整个流程会直接报错。
4.3 分支链容易踩的坑
第一个高频问题:路由解析失败。RouterOutputParser 要求模型严格输出 JSON,但大模型偶尔会在 JSON 外面包一层解释,比如输出“好的,我来判断:json {...}”。遇到这种情况,优先在 Prompt 里强调“只输出 JSON,不要任何额外内容”,必要的时候换一个指令遵循能力更强的模型,或者把温度调低。
第二个高频问题:description 写得不够区分。如果“科技”的描述是“回答技术问题”,“体育”的描述是“回答运动问题”,模型很容易混淆。我的经验是,description 要写“适合什么 + 不适合什么”,比如“适合回答与软件开发、产品设计、技术实现相关的问题,不适合回答体育赛事、训练计划问题”。区分度越高,路由准确率越高。
第三个高频问题:用了 MultiPromptChain 之后,下一步没法接后处理链。因为 MultiPromptChain 已经帮你把“路由 + 执行”封装好了,你拿到的只是最终文本,中间到底是哪条链跑的、中间结果是什么,都比较难拿到。如果你还有后续步骤要处理,我建议不要用这个高级封装,而是手动拆开,见下一节。
4.4 规则明确时,用 if-else 分支
不是所有分支都需要 LLM 来做。如果你的输入里有明显的触发词,比如“退货”“退款”“价格”,直接用规则判断更快、更稳、更省钱:
def route_by_rule(text): if "退货" in text or "退款" in text or "售后" in text: return "after_sale" if "价格" in text or "优惠" in text or "多少钱" in text: return "sales" return "default" if route_by_rule(user_input) == "after_sale": result = after_sale_chain.invoke({"input": user_input}) elif route_by_rule(user_input) == "sales": result = sales_chain.invoke({"input": user_input}) else: result = default_chain.invoke({"input": user_input})这个方案不会消耗额外 token,也不存在 JSON 解析失败的问题,而且逻辑完全可控。缺点是无法处理语义模糊的输入。实际项目中我经常采用“规则优先,LLM 兜底”的策略:先跑规则,规则没有命中再用路由链。这样既快又稳,还能控制成本。
5. 组合实战:顺序 + 分支搭一个内容生产管道
5.1 这个管道要做什么
前几节都在讲单个链的用法,这一节我们把三类链组合起来,搭一个小型“内容创作助手”。业务需求是这样的:用户输入一个产品主题,比如“智能保温杯”,系统先判断内容应该走“技术评测”“营销种草”还是“学习心得”风格;每种风格内部都执行“大纲 -> 正文”两步;最后统一用标题生成链,为正文生成一个吸引人的标题。最终输出是一个包含标题和正文的字典。
这个流程里,分支链负责选风格,顺序链负责风格内部的步骤串联,基础链被反复用作每一步的最小单元。组合起来之后,你就能看出 Chain 的真正价值:每一种风格可以单独优化,新增一种风格只需要在多条链里加一个分支,不会影响其他逻辑。
5.2 分支里的顺序链:用工厂函数减少重复
三种风格都做“大纲 -> 正文”两步,区别只在 Prompt 的风格指令。如果一样写一遍会产生大量重复代码,所以我用工厂函数来创建每条分支链:
from langchain.chains import SequentialChain, LLMChain from langchain.prompts import PromptTemplate def build_content_chain(style_instruction): outline_prompt = PromptTemplate( input_variables=["input"], template=f"{style_instruction}\n请围绕「{{input}}」写一份详细大纲,只输出大纲。", ) article_prompt = PromptTemplate( input_variables=["input", "outline"], template=( f"{style_instruction}\n" "主题:{{input}}\n" "大纲:{{outline}}\n" "请根据大纲写一篇完整短文,只输出正文。" ), ) first_chain = LLMChain(llm=llm, prompt=outline_prompt, output_key="outline") second_chain = LLMChain(llm=llm, prompt=article_prompt, output_key="final_article") return SequentialChain( chains=[first_chain, second_chain], input_variables=["input"], output_variables=["outline", "final_article"], ) tech_chain = build_content_chain("你是一位技术评测专家,语言理性,注重参数和场景。") marketing_chain = build_content_chain("你是一位营销种草达人,语言有感染力,注重痛点和使用场景。") study_chain = build_content_chain("你是一位学习心得分享者,语言亲切,注重收获和方法。")注意三点。第一,style_instruction是 Python 的 f-string 变量,所以它在模板里不需要花括号;而真正的输入变量input、outline必须写成{{input}}、{{outline}},这样才能留给 LangChain 后续替换。第二,每条分支链的输入变量统一叫input,这样后面手动路由时变量名一致,少绕弯。第三,最终结果里同时保留了outline和final_article,方便调试时看大纲和正文分别长什么样。
5.3 路由 + 执行 + 后处理:手动编排更灵活
接下来是组合的核心部分。我用一个路由链先分类,然后手动判断 destination,分别调用不同的分支链,最后再接一个统一的标题生成链。
from langchain.chains import LLMChain, LLMRouterChain from langchain.chains.router.llm_router import RouterOutputParser from langchain.chains.router.multi_prompt_prompt import MULTI_PROMPT_ROUTER_TEMPLATE from langchain.prompts import PromptTemplate # 1. 准备路由链 route_destinations = "\n".join([ "tech: 适合技术评测、产品参数分析类问题", "marketing: 适合营销种草、产品卖点包装类问题", "study: 适合学习心得、经验总结类问题", ]) router_prompt = PromptTemplate( template=MULTI_PROMPT_ROUTER_TEMPLATE.format(destinations=route_destinations), input_variables=["input"], output_parser=RouterOutputParser(), ) router_chain = LLMRouterChain(llm=llm, prompt=router_prompt) # 2. 标题生成链 title_prompt = PromptTemplate( input_variables=["article"], template="根据这篇文章提炼一个吸引人的标题,只输出标题:\n{article}", ) title_chain = LLMChain(llm=llm, prompt=title_prompt) # 3. 组合流程 def create_article(user_input): route_result = router_chain.invoke({"input": user_input}) destination = route_result["destination"] if destination == "tech": content_result = tech_chain.invoke({"input": user_input}) elif destination == "marketing": content_result = marketing_chain.invoke({"input": user_input}) else: content_result = study_chain.invoke({"input": user_input}) article = content_result["final_article"] title_result = title_chain.invoke({"article": article}) return {"title": title_result["text"], "body": article}整个过程比 MultiPromptChain 多了几行代码,但换来了非常明确的控制权。我可以随时在某个分支后面追加新的后处理链,可以自定义 destination 不存在时的兜底逻辑,也可以在路由返回结果后打日志做统计。如果你在生产项目里把“路由分发”和“业务处理”分离开来,后面的扩展会轻松很多。
5.4 这段组合流程的调试顺序
组合流程出错时,第一反应不要去看整条管道,而是按下面的顺序隔离问题。
第一步,单独测分支链。给tech_chain、marketing_chain、study_chain分别传入固定主题,确认大纲和正文的 Prompt 输出没有乱码、没有多余解释。任何分支不合格,都不要急着进入下一步。
第二步,单独测路由链。准备几组测试输入,比如“这个产品防水性能如何”“适合送给女朋友吗”“我用了半年有什么心得”,观察destination是否分别命中预期分支。如果分类不准,优先调整 description 的区分度,而不是加更复杂的 Prompt。
第三步,接标题生成链。把前两步的输出硬编码进去,跑通“正文 -> 标题”这一段,确认标题质量没问题。
第四步,才把三段连起来跑全流程,打开verbose=True,逐个环节核对输入输出。按这个顺序调试,大多数问题能在十分钟内定位,而不是对着一条看不见中间状态的管道干瞪眼。
6. 常见问题与排查技巧实录
这一节把我在实际项目里最常碰到的问题整理成表,按现象给原因和解决方案。新手可以把它当成排查手册,遇到类似报错先查这里。
| 问题 | 典型现象 | 原因与解决办法 |
|---|---|---|
| SimpleSequentialChain 传多个变量报错 | 直接抛 ValueError 或下一步拿不到变量 | SimpleSequentialChain 只支持单变量传递,换 SequentialChain 显式声明 |
| SequentialChain 报 Missing variables | 提示缺少 input_variables 或某个 key 不存在 | 检查每条链的output_key、最终input_variables/output_variables和 Prompt 里的变量名是否完全一致 |
| RouterChain 输出解析失败 | 报 parse error 或直接走 default | 模型没有按 JSON 格式输出;调低 temperature、在 Prompt 中强调“只输出 JSON”,考虑换更强模型 |
| 分支总是走到 default | destination 判断错误 | prompt_infos 里的 description 区分度太低;把每条 description 写成“适合什么,不适合什么” |
| 中间步骤混入多余内容 | 下一步输入里出现“好的,我来帮你”这类话 | 每个中间 Prompt 严格要求“只输出结果,不要解释”,必要时让模型输出结构化 JSON |
| 调试看不到中间结果 | verbose=True 仍然没有输出 | 检查链是否在外层被重新封装;确认最终调用的链对象带 verbose 配置 |
| invoke 和 run 混用 | 报 TypeError 或取不到结果 | 旧接口用chain(...)或run(),新版推荐invoke();确认 LangChain 版本后保持统一 |
除了这些表格里的问题,还有三条我再多说两句。
第一,路由链和执行链可以配置不同的模型。路由只是分类任务,用速度快、成本低的模型就够了;真正生成文章的分支链对质量要求高,可以换更强的模型。不要因为一个 llm 变量方便,就让所有环节都用同一个模型,成本和效果都会失衡。
第二,分支链的兜底必须设计好。用户输入再五花八门,最后也得有出口。我通常会在最后加一个“通用助手”分支,保证任何输入都有响应。没有兜底的流程,上线之后迟早会在某个边缘输入上让你难堪。
第三,Chain API 和更新的 LangChain Expression Language(LCEL)并不是互斥的。LCEL 的Runnable对象和|组合方式写起来更简洁,但“基础单元 + 顺序编排 + 分支选择”的思路和 Chain 完全一致。把 Chain 的概念理清楚之后,再切到 LCEL 只需要换一层写法,不用重新学业务建模。
我个人现在搭复杂流程时,最常用的组合是:能用规则判断的分支就用 Pythonif-else解决,需要语义理解时再上 LLM 路由;流程主体用 SequentialChain 或拥有类似概念的 Runnable 序列组织;最后用一个轻量级后处理链统一修饰输出。Chain 不是越复杂越好,而是每一步职责越单一越好。希望这篇能帮你在做 LangChain 工程化时少走一些弯路。