想象你是一家连锁奶茶店的老板,正把一家店开成一百家。要让几十家分店做出味道完全一致的杨枝甘露,靠的不是某个老师傅的玄学手感,而是一套标准化的经营体系:后厨按功能分成茶底区、配料区、封口区;每个工位都遵守同一份作业标准;店长会看菜单、调配方、指挥人手;墙上贴着清清楚楚的配方卡。LangChain 就是给大模型应用开设的这样一家“奶茶店总部”——它把模型调用、提示词封装、记忆管理、工具使用这些散落的能力,整理成一套分层清晰、可自由组合、能交给 Agent 灵活调度的框架。这篇文章会带你从奶茶店的视角,逐一拆解 LangChain 的抽象分层、Runnable 协议、Agent 循环、LCEL 表达式、模块化扩展,以及版本演进中那些让人拍大腿的坑。读完你会发现:所谓框架设计,不过是把“开店”的常识变成了代码。

岗位关联

如果你是 AI 应用工程师,理解 LangChain 的架构骨架,意味着你不再被 API 的表象牵着走——能读懂框架的取舍,能写自己的扩展,能在团队里设计出可靠、省钱的 LLM 应用。

目录
  1. 可视化演示
  2. 抽象分层设计
  3. Chain与Runnable
  4. Agent抽象机制
  5. LCEL表达式
  6. 模块化扩展
  7. 版本演进与反模式
  8. AI 应用场景
  9. Python 演示
  10. 常见误区
  11. 面试题

可视化演示

展示LangChain四层抽象架构、Runnable统一协议接口(invoke/batch/stream),以及LCEL管道表达式声明式链路编排。

本节配有交互动效(进入视口后自动加载,约 3KB)。

四层抽象架构:Chain > Agent > Retrieval > Model CHN=链路层,编排组合 AGT=Agent层,工具调用 RET=检索层,RAG向量检索 每层独立演进,可替换实现

抽象分层设计

奶茶店的后厨从不一锅乱炖。茶底区只负责煮茶,配料区只负责备料,封口机只负责封装。店长绝不会让煮茶工去收银,也不会让收银员进后厨炒糖。每个功能区各管一摊、互不越界,整家店才能同时出几百杯单而不乱。这种“职责分离”是任何复杂系统能长大的第一块基石。

LangChain 的第一层设计智慧,正是这种后厨分区。它把一次 LLM 应用拆成四个职责清晰的层:模型层(Model)负责接入各家大模型,提示层(Prompt)负责管理提示词模板,记忆层(Memory)负责保存对话上下文,工具层(Tool)负责把外部能力注册进来。四个层各有标准接口,上层只依赖接口、不依赖具体实现。

模型层中,ChatOpenAI、ChatAnthropic 这些类都实现了统一的 BaseChatModel 接口;提示层用 PromptTemplate 把用户消息模板化、注入变量;记忆层用 ConversationBufferMemory 等组件在每次请求前把历史会话回填进上下文;工具层则通过 @tool 装饰器把任意 Python 函数暴露给模型。分层带来的直接好处是“换件不换机”:今天用 OpenAI,明天换成国产模型,只改模型层一行代码;想加长期记忆,也无需改动提示词和工具。

回到后厨:订单从收银台出发,先经配料柜补料、再被会员本写上“少糖”备注,最后由茶桶做出成品。每一站只认上一站递来的东西,但整张订单却因此井然有序——分层,让“复杂”变成了“各司其职”的简单。

y=M(format(ctx(x,K,T),P))\displaystyle y = \mathcal{M}\big( \text{format}\big( \text{ctx}(x, K, T), P \big) \big)
一个请求从工具、记忆汇聚,经提示词格式化,最终交给模型推理——像一张订单从配料柜、会员本一路流到茶桶。
  1. 上下文汇聚把用户输入 x、记忆 K、工具列表 T 汇集为当前上下文
    ctx(x,K,T)\text{ctx}(x, K, T)
  2. 提示词格式化提示层按模板 P 把上下文渲染为完整提示 p
    p=format(ctx,P)p = \text{format}(\text{ctx}, P)
  3. 模型推理模型层以 p 为输入,生成最终应答 y
    y=M(p)y = \mathcal{M}(p)
  4. 四层复合一次请求等价于四个层的函数复合
    y=M∘format∘ctx(x)y = \mathcal{M} \circ \text{format} \circ \text{ctx} (x)

Chain与Runnable

奶茶店要推一款“杨枝甘露”,店长写了张标准作业卡:先打茶底,再加芒果,接着倒椰浆,最后封口。四个工位无论谁来上班,都按同一份约定传递半成品:接料、加工、递给下一站。这份“接料-加工-传递”的约定,就是整条流水线的灵魂。

在 LangChain 里,这份约定叫 Runnable 协议。任何对象只要实现了 invoke(执行一次调用)就能入链;基类还会基于 invoke 自动派生出 batch(批量)和 stream(流式)能力。一条链(Chain)本质上就是一个 Runnable 的组合体——老版本里 Chain 是一个抽象类,新版本里它被彻底“协议化”了:一切皆 Runnable。

RunnableSequence 把多个 Runnable 顺序串起来,它的 invoke 内部会自动把上一个节点的输出作为下一个节点的输入。正因所有组件都遵守同一套输入输出约定,你才可以像拼管道一样自由拼接。为了衔接普通函数,框架还提供了 RunnableLambda、RunnablePassthrough 等适配器,把任何 Python 函数包装成 Runnable。这样,业务里的文本清洗、格式转换,也都能成为流水线上的一站。

有了这份“接料-加工-传递”的作业标准,换掉任何一个工位都不影响其他工位:把椰浆工换成芝士工,前端的茶底工和后端的封口工完全无感。这就是协议的力量——它让流水线既不挑人,也不怕换人。

y=C(B(A(x)))\displaystyle y = C\big(B\big(A(x)\big)\big)
三个 Runnable 按顺序复合:A 的产出是 B 的投入,B 的产出是 C 的投入——像三个工位依次传递半成品。
  1. 首节点消费输入组件 A 接收原始输入 x,产出中间结果 a
    a=A(x)a = A(x)
  2. 中间节点接力组件 B 以 a 为输入继续加工,产出 b
    b=B(a)b = B(a)
  3. 末节点产出结果组件 C 消费 b,输出最终结果 y
    y=C(b)y = C(b)
  4. 整链等价复合整条链等价于复合函数,写作嵌套调用
    Chain(x)=C∘B∘A(x)\text{Chain}(x) = C \circ B \circ A (x)

Agent抽象机制

流水线再标准,也怕遇到“来杯不酸的”这种模糊需求。这时店长就得亲自出马:先打量店里有什么料,试做一杯让顾客尝,不行再换配方,直到满意为止。店长和普通工位最大的区别是——他会观察、会决策、会动手验证,还会根据结果调整下一步。

LangChain 里的 Agent 就是这位店长。它不是一个固定的流程,而是一套“推理-行动”循环:把当前状态(任务描述 + 已知信息 + 可用工具清单)交给 LLM,LLM 决定调用哪个工具或直接回答;工具返回结果后,状态被更新,再次交给 LLM……如此反复,直到任务完成。最经典的范式叫 ReAct(Reasoning + Acting,推理 + 行动)。

AgentExecutor 负责调度这个循环;用 create_tool_calling_agent 可以把工具列表绑定进提示词,让模型输出结构化的工具调用指令。每个“思考”都是一次模型调用,每个“动手”都是一次工具执行,所以 Agent 的成本通常远高于同等复杂度的 Chain。更进阶的思路是规划器-执行器分工:规划器把大任务拆成子任务清单,执行器逐个完成,适合多步骤复杂场景;多个 Agent 还能组成团队协作。

店长每想一步,脑力成本就涨一分;所以奶茶店只有遇到标准流程搞不定的单子,才舍得让店长出马。这也提醒工程师:能用 Chain 解决的事,别轻易上 Agent——会思考的店员,时薪贵着呢。

at=πθ(st),st+1=st∪{at,ot}\displaystyle a_t = \pi_\theta(s_t), \qquad s_{t+1} = s_t \cup \{a_t, o_t\}
店长每轮都看着“当前局面”出招,出手后的反馈又写回局面里——这就是 Agent 的思考循环。
  1. 局面画像状态 s_t 里装着任务、历史动作与观察结果
    st={x,  (a1,o1),…,(at−1,ot−1)}s_t = \{ x,\; (a_1, o_1),\dots, (a_{t-1}, o_{t-1}) \}
  2. 策略决策LLM 充当策略网络,从局面中选出下一个动作
    at=πθ(st)a_t = \pi_\theta(s_t)
  3. 工具反馈动作交给工具执行,得到观察结果 o_t
    ot=Execute(at)o_t = \text{Execute}(a_t)
  4. 状态更新循环新动作与观察并入状态,直到模型输出最终答案
    st+1=st∪{at,ot}s_{t+1} = s_t \cup \{ a_t, o_t \}

LCEL表达式

传统后厨靠班长口头传达流程,人一多就乱。这家店的墙上却贴着配方卡,一行行竖线把工序串起来:“茶底 | 芒果 | 椰浆 | 封口”。配方卡只描述“成品是怎么一步步做出来的”,至于每个工位是真人还是机器,它一概不管。这就是声明式的魅力。

LCEL(LangChain Expression Language,LangChain 表达式语言)就是这张配方卡。它用管道运算符 | 把组件声明式地串起来:prompt | llm | parser 一行代码就构造出一条完整的可执行链。LCEL 还支持 RunnableParallel 做并行分支、用 .bind 绑定参数、用 .batch 自动批处理。声明式的最大价值在于:同一段配方,既能流式吐字,也能批量生产,还会在运行时自动做优化。

在底层,LCEL 的 | 并不是什么魔法——它是 Runnable 重载的 or 方法:a | b 返回一个 RunnableSequence。表达式被编译为可执行图,自动处理输入键的分配:上一个组件输出的哪些字段传给下一个。框架还内置了追踪能力,每次调用都记录中间结果,方便调试定位。理解了这一层,你就明白为什么官方强烈推荐“能写 LCEL 就别手写 for 循环”。

店长看配方卡,一眼就知道“芒果 → 椰浆 → 封口”的顺序;至于工位是自动机器还是新手店员(流式还是批处理),配方卡完全不关心。把流程写清楚、把执行交给系统,这正是声明式表达带来的从容。

(A∣B)(x)=B(A(x))\displaystyle (A | B)(x) = B\big(A(x)\big)
竖线左边先跑、右边后跑——竖线的含义就是“把结果递给下一站”。
  1. 左组件先执行竖线左侧的 A 先处理输入 x
    u=A(x)u = A(x)
  2. 右组件再消费竖线右侧的 B 以 u 为输入继续加工
    v=B(u)v = B(u)
  3. 整体等价复合a | b 整体等价于复合函数
    (A∣B)(x)=B(A(x))(A | B)(x) = B\big(A(x)\big)
  4. 批处理自动展开对一组输入,管道自动逐条执行
    (A∣B)(X)=[ B(A(xi)) ]i=1n(A | B)(X) = \big[\, B\big(A(x_i)\big) \,\big]_{i=1}^{n}

模块化扩展

生意红火后,店里要加一款“芝士葡萄”。好消息是,不用重装后厨:买一台新奶酪泵,把接口接到现有管路上;调一杯新品,配方卡上新增一行;后期还想监控每台设备的耗时,再在每个工位装个计时器。开店三件套解决,后厨照常运转。

LangChain 的扩展性也靠三件套。第一件是自定义组件:用 @tool 装饰器把任意函数注册成工具,或用 RunnableLambda 把函数包成 Runnable,它们就能无缝接入链路。第二件是回调系统:CallbackHandler 可以在链路的开始、结束、出错处挂上钩子,统一做日志、监控、限流。第三件是配置注入:通过 .bind 或 config 参数把运行时配置传进组件,而不需要改动组件定义。

组合复用是模块化的终极形态:小 Runnable 拼成大 Runnable,大 Runnable 又作为零件拼进更大的链路。官方生态也一样分层演进——需要复杂图状编排时用 LangGraph,大多数业务用 LCEL + 自定义组件就足够。模块化的工程收益很实在:组件可跨链路复用,减少重复开发,也减少重复的 token 消耗。下面用计费公式感受一下成本结构。

新奶酪泵接上就能用,是因为它的接口跟行业标准一致;你的自定义函数能被 Agent 调用,是因为 @tool 注册时遵守了 LangChain 的工具协议。模块化的本质,就是“接口统一加上即插即用”——后厨如此,框架亦然。

ci=p×(niin+niout),C=∑i=1nci\displaystyle c_i = p \times \left( n_i^{\text{in}} + n_i^{\text{out}} \right), \qquad C = \sum_{i=1}^{n} c_i
每次模型调用都按 token 计费,整条链路的总成本等于所有节点成本之和——所以模块越精简,账单越好看。
  1. 单节点计费第 i 个节点的成本等于单价乘以输入输出 token 总量
    ci=p×(niin+niout)c_i = p \times \left( n_i^{\text{in}} + n_i^{\text{out}} \right)
  2. 全链路求和一条链 n 个节点的总成本是所有 c_i 的和
    C=∑i=1nciC = \sum_{i=1}^{n} c_i
  3. 复用零增量成本复用现成组件时,开发成本不增加
    ΔC=C复用−C新写=0\Delta C = C_{\text{复用}} - C_{\text{新写}} = 0
  4. 规模线性增长组件越多,成本随节点数线性上升
    C∝nC \propto n

版本演进与反模式

奶茶店开了三年,店长没少踩坑:刚开店时,每款新品都让老师傅写一份“独家秘籍”,结果老师傅一走,配方就失传;后来统一成配方卡,才把分店开到全国。LangChain 的版本史,几乎就是这段“从独家秘籍到标准作业卡”的进化史。

0.x 时代的 LangChain,Chain 家族庞大臃肿:LLMChain、SequentialChain、各种 Agent 类各写各的样板代码,抽象混乱、学习成本极高。0.1 到 0.2 的过渡期,官方把核心抽象收敛到 Runnable + LCEL;1.0 时代,老式 Chain 逐步退场,RunnableSequence 全面上位,AgentExecutor 也把舞台让给了更通用的图编排方式(LangGraph)。版本演进的核心关键词就四个字:接口统一。

演进路上也留下了不少反模式供后人避坑。最常见的有:把整个业务逻辑塞进一个巨型 prompt 和一次调用,结果出错了根本不知道是哪句话带偏了模型;在链外手工拼字符串来“伪装”LCEL,丢了流式和追踪能力;忽略回调系统,等线上出问题才靠 print 大海捞针;滥用 .bind 传参,导致组件状态在并发请求间泄漏。最佳实践则是:小步组合、显式声明数据流、入口统一做错误处理、用 LangSmith 之类工具做全链路追踪。为什么串行链路要尽量短?用成功率公式说话。

老店长最后总结:配方卡越短越好,一张卡上写满二十行的大长篇最容易翻车;能并行的步骤就并排做——两台机器同时打茶和切芒果,整杯茶出得又快又好。设计框架如此,设计业务链路亦然。

Pok=∏i=1n(1−fi)\displaystyle P_{\text{ok}} = \prod_{i=1}^{n} \left( 1 - f_i \right)
整条链成功,要求每一步都成功;步骤越多,乘积越小——反模式常把链路拖得又长又脆。
  1. 单步成功率第 i 步的失败概率为 f_i,成功概率为
    si=1−fis_i = 1 - f_i
  2. 串行相乘整条链路成功要求每步都成功
    Pok=∏i=1nsiP_{\text{ok}} = \prod_{i=1}^{n} s_i
  3. 长链趋于失效当每步成功率小于 1 时,链越长整体越接近 0
    lim⁡n→∞Pok=0\lim_{n \to \infty} P_{\text{ok}} = 0
  4. 缩短串行路径把可并行步骤拆开,让关键路径更短
    Pok=∏j=1msj,m<nP_{\text{ok}} = \prod_{j=1}^{m} s_j, \quad m < n

AI 应用场景

企业内部知识库问答机器人

阶段:应用编排(RAG 链路组装)

解决的问题:传统做法是把“检索 → 生成”写成一堆散落函数,参数传递靠人肉维护,一旦要换向量库或加流式输出就大改。用 Chain/Runnable 把“查询改写 → 向量检索 → 结果重排 → LLM 生成 → 引用格式化”串成一条 LCEL 链路,每步都是独立可替换的组件,还能自动获得流式与追踪能力。

具体案例:某互联网公司的 HR 问答机器人,原来每次回答都要重读整份制度文档,token 成本高。工程师把链路拆成“意图识别 → 制度库检索 → 精炼生成”三个 Runnable,并在回调里统计每步耗时与 token;上线后单次问答成本下降 60%。

电商客服工单自动处理 Agent

阶段:智能体开发(工具调用 + ReAct 循环)

解决的问题:固定链路无法应对“我的订单少了一件还延迟了”这类复合问题。用 Agent 抽象把查订单、查物流、发起退款等接口注册为工具,Agent 在 ReAct 循环中自己决定先查什么、再做什么,客服只需审核最终处置。

具体案例:某电商平台的大促期间,退款量暴涨。团队用 create_tool_calling_agent 接入订单与库存工具,Agent 循环中自动完成“核对订单 → 检查库存 → 生成补偿方案”,把人工工单处理时长从 12 分钟压到 3 分钟。

多模型批量评测流水线

阶段:模型评估与优化(模块化扩展)

解决的问题:评测不同模型时,评测逻辑和模型调用耦合在一起,每次换模型都改评测代码。把“取样本 → 模型回答 → 评分器打分”拆成模块化 Runnable 链路,模型层只换不修,评分器通过回调系统挂载。

具体案例:某大模型团队每两周要对 8 个候选模型跑一遍 5000 条评测集。他们用 LCEL 声明链路,模型层按配置注入自动切换,回调记录每条样本的耗时与分数,整条流水线复用同一份评测逻辑。

Python 演示

python59 行
class Runnable:    """迷你 Runnable 协议: 实现 invoke 的对象都可入链"""     def invoke(self, x):        raise NotImplementedError     def __or__(self, other):        return RunnableSequence(self, other)  class RunnableSequence(Runnable):    def __init__(self, *steps):        self.steps = list(steps)     def invoke(self, x):        y = x        for step in self.steps:            y = step.invoke(y)        return y     def __or__(self, other):        self.steps.append(other)        return self  class Prompt(Runnable):    def __init__(self, template):        self.template = template     def invoke(self, x):        return self.template.format(topic=x)  class UpperParser(Runnable):    def invoke(self, x):        return x.upper()  class TokenCounter(Runnable):    def __init__(self):        self.gc_count = 0     def invoke(self, x):        self.gc_count += len(x)        return x  def main():    prompt = Prompt("请用一句话介绍{topic}:")    chain = prompt | UpperParser() | TokenCounter()     topics = ["LangChain", "Agent", "LCEL"]    for topic in topics:        print(chain.invoke(topic))    print("累计处理字符数:", chain.steps[-1].gc_count)  if __name__ == "__main__":    main()
目的:用约 70 行 Python 手写一个迷你版 LCEL——通过 Runnable 协议和 __or__ 重载,实现 prompt | parser | counter 的声明式链路组装与数据流。
  1. def __or__(self, other):重载竖线运算符,让 a | b 变成组合链
  2. return RunnableSequence(self, other)组合结果仍是一个 Runnable,可继续被拼装
  3. y = step.invoke(y)LCEL 的数据流核心:上一节点输出喂给下一节点
  4. class TokenCounter(Runnable):自定义组件只需实现协议即可入链
  5. self.gc_count += len(x)每经过一次就累加字符数,演示有状态组件
  6. chain = prompt | UpperParser() | TokenCounter()一行声明式代码组装整条链路

常见误区

误区“我要先学 Chain,再学 LCEL,它们是两个不同的 API,写代码时得二选一。”

正解在新版 LangChain 里,Chain 就是 Runnable 的组合体(RunnableSequence),LCEL 只是声明式书写这种组合的语法。用 prompt | llm | parser 写的链,底层和手写 RunnableSequence 完全等价。就像“作业标准流程”和“配方卡”描述的是同一条流水线,只是表现形式不同。

为何常见:0.x 时代 Chain 单独成体系,网上老教程满天飞;版本升级后文档变了,但很多人的知识还停留在旧版。

误区“Agent 会思考会调用工具,肯定比死板的 Chain 强,所以任何需求都应该用 Agent 实现。”

正解Agent 每“思考”一轮就要调用一次 LLM,成本远高于固定链路。像“格式化输出、固定步骤检索”这种确定性需求,Chain 又快又稳又省钱;只有需要动态决策的任务(如多步骤工具调用)才值得上 Agent。店长虽然厉害,但每杯茶都让店长亲手做,店就亏死了。

为何常见:Agent 是热点概念,宣传多;而成本与稳定性这类工程约束,教程往往讲得少。

误区“`prompt | llm` 只是让代码更好看,跟位运算或装饰器差不多吧?”

正解竖线是 Runnable 重载的 __or__ 方法,执行时会创建一个 RunnableSequence,并且按声明顺序在运行时逐个 invoke,还配套流式、批处理和追踪。它不是装饰器,更不是位运算——是一等公民的“组合操作符”。

为何常见:| 在 Python 里最常见的是位或,看到在链路上也用 | 很容易先入为主。

误区“我的业务有 if-else、有并行,LCEL 只会一路竖线串到底,根本不够用。”

正解LCEL 有 RunnableParallel 做并行分支,有 RunnableBranch 做条件路由,还可以把任意 Runnable 当作参数传给另一个 Runnable,组合出 DAG 乃至图。顺序只是它最简单的一种形态。

为何常见:教程示例大多以 prompt | llm 开场,容易让人以为这就是 LCEL 的全部。

误区“代码能跑就行,回调、追踪是锦上添花,以后有时间再加。”

正解多节点链路中,问题可能出在提示词、模型、解析器、工具任意一环;没有全链路追踪,几乎不可能靠 print 定位。回调正是这种“定位能力”的基础设施,应该在写第一条链路时就接好。后厨不装计时器,永远不知道慢的是哪个工位。

为何常见:单机跑 demo 时链路短、出错直观,追踪的价值被严重低估;一上生产就原形毕露。

面试题

Q1. 关于 LangChain 的 Runnable 协议,下列说法正确的是?

单选题 · easy

  • A.只要实现 invoke 方法即可入链← 正确答案
  • B.必须同时实现 invoke 和 stream
  • C.四个核心方法一个都不能少
  • D.只要求实现 call 方法

答案:A。Runnable 基类以 invoke 为唯一必须实现的方法,并自动派生出 batch、stream 等默认实现;bind 属于配置能力而非入链门槛。像奶茶店工位只需遵守“接料-加工-传递”这一条约定就能上岗,其他能力都可以由总店(基类)补齐。

Q2. 在 LCEL 中,prompt | llm 这个表达式的本质是什么?

单选题 · easy

  • A.等价于 Python 的按位或
  • B.组合返回一条新的 Runnable 链← 正确答案
  • C.立即触发一次模型推理
  • D.只是语法糖没有实际作用

答案:B。| 是 Runnable 重载的 __or__ 方法,返回的是一个 RunnableSequence——它是一条可以后续继续拼装、invoke 的链,并不会立刻调用模型。这就像配方卡上写“茶底 | 芒果”只是把工序画出来,并不会真的做出一杯茶。

Q3. 下列关于 Agent 与 Chain 的区别,正确的是?

单选题 · easy

  • A.Agent 一定比 Chain 回答得更准确
  • B.Agent 会在循环中决策并调用工具← 正确答案
  • C.Chain 可以自主选择调用任何工具
  • D.Agent 不需要绑定任何大模型

答案:B。Agent 的核心是“推理-行动”循环:每轮由 LLM 决策、调用工具、观察结果再决定下一步;Chain 是固定流程,不会自主选择工具。就像店长会根据顾客反馈调整配方,而流水线工人只会照作业卡执行,谈不上谁一定更准,但 Agent 的灵活来自循环,不来自“更聪明的模型”。

Q4. 在 ReAct 循环中,第 t 轮的状态更新方式正确的是?

单选题 · mid

  • A.每轮都从零开始重新构建状态
  • B.只保留最近一轮的行为与结果
  • C.新动作与观察结果追加进旧状态← 正确答案
  • D.直接覆盖并丢弃全部历史信息

答案:C。状态更新公式是 s_{t+1} = s_t ∪ {a_t, o_t},即把本轮动作和观察追加进既有状态。像店长试做新配方时会记住“顾客嫌太酸”这个反馈,而不是丢掉前面的尝试重来——历史是 Agent 决策的依据。

Q5. 关于 LangChain 的模块化扩展,下列说法正确的是?

单选题 · mid

  • A.自定义组件必须继承官方 Chain 类
  • B.普通函数经装饰器即可注册为工具← 正确答案
  • C.回调系统只适用于官方内置组件
  • D.扩展组件必须修改框架源码

答案:B。用 @tool 装饰一个 Python 函数,它就能作为工具进入 Agent 的提示词和调用循环;用 RunnableLambda 包装函数即可入链。模块化扩展的核心是“接口统一、即插即用”,不需要继承特定 Chain 类,也不需要动框架源码——就像新奶酪泵只要接口标准就能接进后厨管路。

Q6. LCEL 声明式写法相比手写 for 循环的最大收益是?

单选题 · mid

  • A.运行速度一定快十倍以上
  • B.自动获得流式批处理与追踪← 正确答案
  • C.可以完全替代所有编程语言
  • D.不需要大模型也能独立运行

答案:B。LCEL 表达式在底层被编译为可执行图,框架自动提供 stream、batch 以及中间结果追踪,这些是手写 for 循环难以白拿的能力。声明式配方卡的价值不在“跑得快”,而在“执行交给系统、人只管描述流程”。

Q7. 一条五节点串行链路,每步成功率为 0.9,整条链成功率约为?

单选题 · hard

  • A.约等于 0.90
  • B.约等于 0.95
  • C.约等于 0.59← 正确答案
  • D.约等于 0.81

答案:C。串行链路成功率等于每步成功率的乘积:0.9^5 = 0.59049,约 0.59。这正是反模式公式 P = ∏(1 - f_i) 的含义——每步都可靠的流水线,串太多站后整体也会变得脆弱。这就是为什么配方卡要短、能并行要并行。

Q8. 生产环境中最推荐用哪种方式观测一条多节点链路?

单选题 · hard

  • A.在每个节点里手工打印耗时
  • B.用回调与追踪平台采集指标← 正确答案
  • C.用线程定时器监控整个进程
  • D.直接让模型输出运行时长

答案:B。CallbackHandler 可以在节点开始、结束、出错时统一采集耗时、token 与中间结果,再交给 LangSmith 等追踪平台展示。手工 print 在链路变长后无法关联上下文,线程监控只能看到进程级信息,靠模型自报时长更是不可靠——后厨要装的是每个工位的计时器,不是门口的秒表。

Q9. Runnable 协议提供的核心能力有哪些?(多选)

多选题 · mid

  • A.同步执行 invoke← 正确答案
  • B.流式输出 stream← 正确答案
  • C.批量处理 batch← 正确答案
  • D.指令编译 compile

答案:ABC。Runnable 协议的核心能力是 invoke(同步)、stream(流式)和 batch(批量),三者由基类统一提供。compile 不是 Runnable 协议的能力——那是机器学习编译优化里的概念。协议三件套对应工位的三种工作模式:做一杯、边做边出、一次做一批。

Q10. 下列关于 LangChain 版本演进的表述,正确的有哪些?(多选)

多选题 · extreme

  • A.老式 Chain 类已被逐步废弃← 正确答案
  • B.Runnable 协议统一了组件接口← 正确答案
  • C.LCEL 与 Chain 是两套互斥体系
  • D.AgentExecutor 仍是官方唯一推荐

答案:AB。1.x 时代老式 Chain 类淡出,Runnable + LCEL 成为统一接口与声明式写法,A、B 正确。LCEL 是构建 Chain 的语法,二者并不互斥,C 错误;AgentExecutor 已把舞台让给更通用的图编排方式(LangGraph),并非唯一推荐,D 错误。这段演进如同奶茶店从“老师傅独家秘籍”走向“标准配方卡”——不是推翻做奶茶,而是把做法统一成可复制、可追责的体系。