Skip to content

第18章 设计模式与架构决策

开篇引言

经过前面十七章的源码深潜,我们已经逐层拆解了 LangChain 的每一个核心组件。现在是时候抬起头来,从全局视角审视这个框架的设计哲学了。

LangChain 不仅仅是一个工具库,它是一个关于"如何构建 AI 应用框架"的设计范本。在它的源码中,蕴含着一系列精心选择的设计模式和架构决策。这些模式不是 LangChain 独创的 -- 它们来自分布式系统、编译器设计、Web 框架等多个领域 -- 但 LangChain 将它们巧妙地组合在一起,应用到 AI 应用这个全新的领域。

本章将从 LangChain 的具体实现中提炼出五个核心设计模式:Runnable 协议、回调洋葱模型、Partner 解耦架构、LCEL 组合优于继承、以及安全纵深防御。每个模式我们都将分析其动机、实现、权衡和可迁移性。最后,我们会讨论如何将这些模式应用到你自己的 AI 应用框架中。

本章要点

  • Runnable 协议模式:统一接口的设计动机与 12 种标准操作
  • 回调洋葱模型:无侵入式可观测性的实现
  • Partner 解耦架构:如何管理爆炸式增长的集成生态
  • LCEL 组合优于继承:从 Chain 子类到管道组合的演进
  • 安全纵深防御:序列化系统的五层安全模型
  • 构建你自己的 AI 应用框架的实践指南

18.1 模式一:Runnable 协议 -- 统一接口

动机

在 LangChain 早期,每种组件都有自己的接口。LLM 用 predict,Chain 用 run,Tool 用 _run。开发者需要记住每种组件的 API,组合时还需要手动编写胶水代码。

Runnable 协议的引入解决了这个问题:所有可执行组件共享同一套接口

核心设计

python
class Runnable(Generic[Input, Output], ABC):
    def invoke(self, input: Input, config: RunnableConfig = None) -> Output: ...
    async def ainvoke(self, input: Input, config: RunnableConfig = None) -> Output: ...
    def stream(self, input: Input, config: RunnableConfig = None) -> Iterator[Output]: ...
    async def astream(self, input: Input, config: RunnableConfig = None) -> AsyncIterator[Output]: ...
    def batch(self, inputs: list[Input], config: RunnableConfig = None) -> list[Output]: ...
    async def abatch(self, inputs: list[Input], config: RunnableConfig = None) -> list[Output]: ...

    # 组合操作
    def pipe(self, *others) -> RunnableSequence: ...
    def __or__(self, other) -> RunnableSequence: ...  # 支持 | 操作符

    # 配置操作
    def with_config(self, config) -> RunnableBinding: ...
    def with_retry(self, **kwargs) -> RunnableRetry: ...
    def with_fallbacks(self, fallbacks) -> RunnableWithFallbacks: ...
    def configurable_fields(self, **kwargs) -> DynamicRunnable: ...

设计权衡

统一的代价:所有组件必须接受 Input 返回 Output,这意味着类型信息在组合时可能被弱化。LangChain 通过泛型和 Pydantic 的 get_input_schema / get_output_schema 来缓解这个问题,但运行时的类型安全仍然有限。

"方法爆炸":每种组件需要实现 invoke、ainvoke、stream、astream、batch、abatch 六个基本方法。LangChain 通过默认实现减轻了这个负担:ainvoke 默认调用线程池中的 invokestream 默认 yield 单个 invoke 结果,batch 默认对每个输入调用 invoke。子类只需覆盖性能关键的方法。

可迁移性

Runnable 协议模式适用于任何需要将异构组件统一起来的场景。它的核心价值在于将"做什么"的多样性与"怎么调用"的统一性分开。

关键原则是:定义最小但完备的公共接口,所有组件必须支持的操作集合不能太多(否则实现负担太重)也不能太少(否则功能受限)。提供合理的默认实现来降低门槛,让开发者可以只覆盖关键方法就获得完整功能。支持组合操作让组件可以无缝连接,这是框架价值的倍增器。通过泛型保留类型信息,尽管有统一接口,输入输出的类型仍然可以在静态分析中追踪。

这个模式在函数式编程社区中有着深厚的理论基础。Runnable 本质上是一个范畴论中的态射(morphism),而 | 操作符就是态射的组合。RunnableParallel 对应积(product),RunnableBranch 对应余积(coproduct)。虽然 LangChain 的实现不需要开发者了解范畴论,但底层的数学结构保证了组合操作的一致性和可预测性。

18.2 模式二:回调洋葱模型 -- 无侵入式可观测性

动机

AI 应用的调试和监控比传统应用更加困难。一次 Agent 执行可能涉及多轮 LLM 调用、多次工具执行、数十个 token 的流式输出。开发者需要看到整个过程的详细信息,但又不希望在业务代码中到处插入日志语句。

核心设计

LangChain 的回调系统借鉴了 Web 框架的中间件模式,形成一个"洋葱模型":每个 Runnable 的执行被包裹在 on_xxx_starton_xxx_end 回调对中。

python
class BaseCallbackHandler:
    def on_llm_start(self, serialized, prompts, **kwargs): ...
    def on_llm_new_token(self, token, **kwargs): ...
    def on_llm_end(self, response, **kwargs): ...
    def on_llm_error(self, error, **kwargs): ...

    def on_chain_start(self, serialized, inputs, **kwargs): ...
    def on_chain_end(self, outputs, **kwargs): ...
    def on_chain_error(self, error, **kwargs): ...

    def on_tool_start(self, serialized, input_str, **kwargs): ...
    def on_tool_end(self, output, **kwargs): ...
    def on_tool_error(self, error, **kwargs): ...

    def on_agent_action(self, action, **kwargs): ...
    def on_agent_finish(self, finish, **kwargs): ...

洋葱层次

CallbackManager 的父子关系

关键的设计决策是 get_child() 方法。当 AgentExecutor 调用 Agent 的 plan 方法时,它创建一个子 CallbackManager:

python
output = self._action_agent.plan(
    intermediate_steps,
    callbacks=run_manager.get_child() if run_manager else None,
    **inputs,
)

子 CallbackManager 继承父级的所有 handler,但有独立的 run_id。这确保了:

  • 层次关系:调用链的父子关系在追踪系统中清晰可见
  • handler 传播:顶层注册的 handler 自动作用于所有子调用
  • 隔离性:子调用的错误不会污染父级的回调状态

设计权衡

性能开销:每次 Runnable 调用都会触发回调,即使没有注册任何 handler。LangChain 通过 verbose 标志和懒加载来缓解这个问题。

handler 接口膨胀:随着组件类型增加,回调方法也在增加(on_llm_xxx、on_chain_xxx、on_tool_xxx、on_agent_xxx)。这是"统一 handler 接口"与"类型安全"之间的权衡。一种可能的改进方向是使用事件系统取代固定方法名 -- handler 注册关心的事件类型,而非实现特定的方法。LangChain 的 astream_events 已经在这个方向上迈出了一步。

回调传播的隐式性:回调通过 RunnableConfig 在调用链中隐式传播,这使得追踪回调的来源变得困难。当一个 handler 被意外触发或未被触发时,调试的难度较高。开发者需要理解 get_child 的父子关系创建机制,才能正确理解回调的传播路径。

可迁移性

洋葱回调模式适用于任何需要非侵入式观测的系统。三个关键要素:

  1. start/end 成对回调:确保资源可以正确释放
  2. 父子关系传播:通过 get_child() 建立调用链层次
  3. handler 注册与分发分离:handler 的注册在使用点,分发在框架内部

18.3 模式三:Partner 解耦架构 -- 管理集成生态爆炸

动机

AI 生态系统的服务提供商数量以指数级增长。LangChain 需要支持数十个 LLM 提供商、数十个向量数据库、数十个工具服务。如果全部放在一个包中,依赖冲突和安装体积将变得不可控。

核心设计

langchain-core (稳定锚点)
    |
    +-- langchain-openai      (独立发布)
    +-- langchain-anthropic    (独立发布)
    +-- langchain-groq         (独立发布)
    +-- langchain-chroma       (独立发布)
    +-- ...
    |
langchain-tests (行为契约)

三层分离

抽象层(langchain-core):定义接口,极少变动。所有 Partner 包的稳定依赖。

实现层(Partner 包):独立开发、独立发版。每个包只依赖 langchain-core 和自己的 SDK。

验证层(langchain-tests):定义行为契约。通过继承测试基类,Partner 包自动获得完整的测试覆盖。

设计权衡

发现性:用户需要知道要安装哪个 Partner 包。LangChain 通过文档和错误消息来引导("你需要安装 langchain-openai")。

版本协调:当 langchain-core 更新接口时,所有 Partner 包都需要适配。通过 semver 和 >=x.y.z,<(x+1).0.0 的版本约束来管理。

重复代码:每个 Partner 包都需要实现类似的消息转换、错误处理、重试逻辑。LangChain 通过在 langchain-core 中提供工具函数(如 convert_to_openai_tool)来减少重复。

可迁移性

Partner 解耦架构适用于任何需要管理大量第三方集成的框架:

  1. 定义稳定的抽象层:接口一旦发布就不轻易修改
  2. 每个集成独立打包:依赖隔离是核心目标
  3. 提供标准测试套件:作为行为契约,降低集成门槛
  4. 通过映射表管理路径迁移:类可以在包之间移动而不破坏序列化兼容性

18.4 模式四:LCEL 组合优于继承

从继承到组合的演进

LangChain 的历史清楚地展示了从继承到组合的演进路径。

早期(继承模式)

python
# 旧方式:通过继承创建自定义 Chain
class MyCustomChain(Chain):
    llm: BaseLLM
    prompt: PromptTemplate
    output_parser: OutputParser

    def _call(self, inputs: dict) -> dict:
        prompt_text = self.prompt.format(**inputs)
        llm_output = self.llm.predict(prompt_text)
        parsed = self.output_parser.parse(llm_output)
        return {"output": parsed}

现在(组合模式)

python
# 新方式:通过 LCEL 组合
chain = prompt | llm | output_parser

组合的优势

  1. 自动获得所有 Runnable 能力:stream、batch、async、retry、fallbacks 等
  2. 无需胶水代码| 操作符自动处理输入输出的对接
  3. 可组合性:子管道可以被提取、复用、替换
  4. 可视化:每个管道自动生成执行图

在 Agent 中的体现

Agent 构建函数完美体现了这个模式:

python
def create_tool_calling_agent(llm, tools, prompt, *, message_formatter=...):
    llm_with_tools = llm.bind_tools(tools)

    return (
        RunnablePassthrough.assign(
            agent_scratchpad=lambda x: message_formatter(x["intermediate_steps"]),
        )
        | prompt
        | llm_with_tools
        | ToolsAgentOutputParser()
    )

四个阶段通过 | 连接,每个阶段都是独立的 Runnable。想换一个输出解析器?替换最后一段。想加一个缓存层?在 llm 前面插入。这种灵活性是继承模式无法提供的。

设计权衡

可读性:对于简单的链,LCEL 非常直观。但对于复杂的分支逻辑(RunnableBranch、RunnableParallel 嵌套),可读性可能下降。

调试难度:管道中的错误堆栈可能很深,不如在单个 _call 方法中设断点直观。LangChain 通过回调系统和 LangSmith 追踪来缓解这个问题。

类型推导:Python 的类型系统难以完美追踪 | 操作符链中的类型变换。IDE 的自动补全在管道末端可能失效。

可迁移性

组合优于继承的模式在 AI 应用框架中特别有价值,因为 AI 管道的组合方式千变万化,难以通过有限的类层次来覆盖。三个实践建议:

  1. 定义统一的组件接口:类似 Runnable 的 invoke/stream/batch
  2. 提供管道操作符:让组件可以声明式地连接
  3. 保留"裸代码"出口:允许用户在必要时直接写函数,不强制一切都通过管道

18.5 模式五:安全纵深防御

动机

序列化/反序列化是安全敏感的操作。Python 的 pickle 因为允许任意代码执行而臭名昭著。LangChain 的序列化系统需要在便利性和安全性之间找到平衡。

五层防御

每一层都是独立的安全关卡,任何一层的失败都会阻止对象实例化。这种纵深防御确保了即使某一层被绕过,后面的层仍然能够拦截攻击。

关键决策

  1. 默认不可序列化is_lc_serializable() 返回 False。开发者必须显式启用。
  2. 默认最严格白名单allowed_objects="core" 只允许 langchain_core 中的类。
  3. 默认不读取环境变量secrets_from_env=False 防止通过构造密钥字段来泄露环境变量。
  4. 默认阻止危险模板default_init_validator 阻止 template_format="jinja2"

可迁移性

纵深防御模式适用于任何需要反序列化不可信数据的场景:

  1. 白名单优于黑名单:明确允许什么,而非尝试禁止什么
  2. 默认安全:安全策略的默认值应该是最严格的
  3. 多层独立防护:每层不依赖其他层的正确性
  4. 可审计的安全边界:每层的检查逻辑应该简单、可读、可测试

18.6 构建你自己的 AI 应用框架

如果你要构建一个 AI 应用框架(无论是公司内部的还是开源的),以下是从 LangChain 中可以借鉴的核心架构决策。

18.6.1 第一步:定义核心协议

确定你的框架中所有组件必须遵循的最小接口。不要试图一开始就定义完整的接口,从最简单的 invoke 开始:

python
class Component(Generic[Input, Output], ABC):
    @abstractmethod
    def invoke(self, input: Input) -> Output: ...

    def __or__(self, other: Component) -> Pipeline:
        return Pipeline([self, other])

18.6.2 第二步:设计可观测性

在框架的最早期就设计好回调/追踪系统。后期加入的可观测性总是不够深入。关键原则:

python
class Observable:
    def invoke(self, input, callbacks=None):
        callbacks = callbacks or []
        for cb in callbacks:
            cb.on_start(input)
        try:
            result = self._invoke(input)
            for cb in callbacks:
                cb.on_end(result)
            return result
        except Exception as e:
            for cb in callbacks:
                cb.on_error(e)
            raise

18.6.3 第三步:保持核心稳定

将核心抽象(接口定义、基本数据类型、配置系统)放在一个独立的包中。这个包的 API 变更要极其谨慎。所有集成都只依赖这个核心包。

my-ai-core (稳定)
    - Component 协议
    - Message 类型
    - Config 系统
    - 回调基类

my-ai-openai (频繁更新)
    依赖: my-ai-core + openai SDK

my-ai-anthropic (频繁更新)
    依赖: my-ai-core + anthropic SDK

18.6.4 第四步:组合优先

不要让用户通过继承来扩展框架。提供组合原语:

python
# 不要这样
class MyPipeline(Pipeline):
    def _run(self, input):
        ...

# 鼓励这样
pipeline = format_step | llm_step | parse_step

18.6.5 第五步:安全从一开始就考虑

如果你的框架涉及序列化,从第一天就设计安全模型。后期补救总是不够的。关键原则:默认安全、白名单控制、纵深防御。

18.7 模式六的启示:约定优于配置

除了前面明确列举的五个模式之外,LangChain 中还有一个隐含但无处不在的设计原则值得特别提出:约定优于配置。

这个原则体现在多个层面。首先是方法命名的约定。所有 Runnable 都通过 invoke 调用,同步版本不加前缀,异步版本加 a 前缀(ainvoke),流式版本改为 streamastream,批量版本改为 batchabatch。这套命名约定一旦被开发者记住,就可以在不查文档的情况下猜测任何 Runnable 的方法名。

其次是配置传播的约定。RunnableConfig 通过函数参数自动向下传播,开发者不需要手动在每个子调用中传递配置。run_manager.get_child() 约定了父子回调的创建方式。这些约定减少了样板代码,也减少了遗漏配置传播的风险。

然后是 Partner 包的结构约定。包名遵循 langchain-xxx 格式,模块结构遵循 langchain_xxx/chat_models/base.py 的路径约定,密钥使用 XXX_API_KEY 的环境变量名约定,序列化使用 is_lc_serializableget_lc_namespace 的方法约定。新的 Partner 开发者只需要参照现有包的结构,就能快速上手。

最后是测试的约定。标准测试通过继承基类和声明属性的方式工作,不需要额外的配置文件或注解。能力检测通过检查方法是否被覆盖来自动完成,不需要手动声明。这种"检测而非声明"的约定减少了开发者需要维护的配置项。

约定优于配置的核心价值在于降低认知负担。在一个拥有数百个类和数千个方法的框架中,如果每个行为都需要显式配置,开发者会被淹没在配置选项中。通过建立一致的约定,开发者可以将注意力集中在真正需要定制的地方,而非框架的机制性细节。

当然,约定也有局限性。约定是隐式的,新开发者如果不了解这些约定,可能会感到困惑。LangChain 通过详细的文档、丰富的示例和有意义的错误消息来缓解这个问题。例如,当提示模板缺少 agent_scratchpad 变量时,Agent 构建函数会给出明确的错误消息,引导开发者理解这个约定。

18.8 LangChain 的局限与反思

公正地审视 LangChain 的设计,也需要承认其局限性。

抽象泄漏

Runnable 协议试图统一所有组件,但不同组件的本质差异有时会泄漏出来。例如,ChatModel 的 invoke 输入是 list[BaseMessage],而 PromptTemplate 的输入是 dict。在管道中组合它们需要理解每个组件的实际输入输出类型,协议的"统一性"在此处打了折扣。

版本碎片化

独立 Partner 包带来了版本碎片化问题。langchain-core 1.2 和 1.3 之间的接口变更可能导致部分 Partner 包暂时不兼容。社区需要持续的版本协调工作。

学习曲线

尽管 LCEL 简化了简单场景,复杂场景下的调试(嵌套的 RunnableParallel、条件分支、动态配置)仍然有不低的学习曲线。框架的抽象层次越多,出错时的定位就越困难。

过度抽象的风险

并非所有 AI 应用都需要 Runnable 协议的全部功能。对于简单的"调用模型-解析输出"场景,直接使用 SDK 可能更直观。框架的价值在复杂场景中才充分体现。

18.8 模式之间的相互作用

五个设计模式不是孤立存在的,它们之间存在深层的相互依赖和协同关系。理解这些关系,才能完整地把握 LangChain 的架构哲学。

Runnable 协议是一切的基础

Runnable 协议定义了统一的组件接口,是其他所有模式的根基。LCEL 的管道组合依赖 Runnable 的 __or__ 操作符。回调系统需要 Runnable 提供统一的生命周期钩子(invoke 开始和结束)。配置系统通过 DynamicRunnable 扩展 Runnable 接口。Partner 包通过实现 Runnable 子类(如 BaseChatModel)接入生态。序列化系统依赖 Serializable,而 RunnableSerializable 同时继承了 RunnableSerializable

可以说,如果 Runnable 协议的设计出了问题,整个框架都会受到影响。这也是为什么 langchain-core 对 Runnable 接口的修改极其谨慎。

回调系统与 Partner 包的协作

回调系统为 Partner 包提供了标准化的可观测性接口。ChatOpenAI 在调用 OpenAI API 前触发 on_llm_start,在每个 token 返回时触发 on_llm_new_token,在调用完成时触发 on_llm_end。Partner 包的实现者不需要了解回调系统的内部机制,只需在正确的时机调用 run_manager 的方法即可。

这种"约定优于配置"的设计让所有 Partner 包自动获得了与 LangSmith、标准日志等追踪系统的集成能力,无需 Partner 开发者进行任何额外工作。

LCEL 组合与安全模型的张力

组合模式让用户可以自由地将组件串联起来,但这也增加了安全性的复杂度。一个 LCEL 管道中可能包含数十个 Runnable,每个都有自己的序列化表示。当整个管道被序列化时,需要递归地序列化每个子组件,并确保每个子组件的密钥都被正确替换。反序列化时,需要逐层重建整个管道,每个节点都经过白名单验证。

这种复杂度是"便利性"与"安全性"之间不可避免的张力。LangChain 的选择是在安全性上不妥协(默认最严格的白名单),同时提供足够的配置灵活性(allowed_objectsadditional_import_mappings)让开发者可以根据信任等级调整策略。

配置系统与 Agent 执行的联动

配置系统通过 RunnableConfig 贯穿整个调用链。在 Agent 场景下,这意味着顶层传入的配置(tags、metadata、callbacks、configurable)会自动传播到 Agent 内部的每次 LLM 调用和工具执行。AgentExecutor 通过 run_manager.get_child() 创建子回调管理器时,配置信息也随之传递。

这种自动传播在多租户场景下尤为重要:不同租户的请求可以携带不同的 configurable 参数(如模型名称、温度),通过 DynamicRunnable 在 Agent 执行循环的每次 LLM 调用时动态解析为对应的模型实例。整个过程对 Agent 的 plan 逻辑完全透明。

18.9 与其他 AI 框架的设计对比

将 LangChain 的设计模式与其他 AI 应用框架进行对比,有助于更好地理解每种设计选择的优劣。

与 LlamaIndex 的对比

LlamaIndex 专注于数据索引和检索增强生成(RAG),与 LangChain 的通用框架定位不同。在接口设计上,LlamaIndex 使用 QueryEngineChatEngine 等更具领域语义的抽象,而非 LangChain 的通用 Runnable。这种设计在 RAG 场景下更加直观,但通用性和组合性不如 Runnable 协议。

在集成管理上,LlamaIndex 同样采用了独立包模式(llama-index-llms-openai 等),但包名约定和目录结构与 LangChain 不同。两个框架都认识到了独立包模式在依赖管理上的优势。

与 Semantic Kernel 的对比

微软的 Semantic Kernel 采用了更加面向对象的设计风格。它使用 Kernel 作为中心注册表,所有插件(Plugin)和函数(Function)在 Kernel 中注册后才能使用。这种设计与 LangChain 的去中心化组合形成了鲜明对比。

Semantic Kernel 的优势在于类型安全更强、IDE 支持更好(尤其是在 C# 和 Java 中)。LangChain 的优势在于组合更灵活、管道更声明式。两者代表了 AI 框架设计的两种哲学:集中注册与去中心化组合。

与 Haystack 的对比

Haystack 使用管道(Pipeline)作为核心抽象,组件(Component)通过输入输出端口连接。每个组件声明自己的输入和输出的类型和名称,管道在组装时验证连接的兼容性。

这种显式端口声明的设计比 LangChain 的 Runnable 泛型参数更加严格,能够在编译时(管道组装时)捕获更多类型错误。但它也更加冗长 -- 每个组件需要显式声明端口,而不是像 LangChain 那样通过泛型自动推导。

共性与启示

所有这些框架都认同几个核心设计原则:统一的组件接口、可组合的管道抽象、独立的集成包管理、以及某种形式的可观测性支持。差异主要在于抽象层次的选择:更通用还是更领域特定?更灵活还是更类型安全?更声明式还是更命令式?

没有绝对正确的答案。最佳选择取决于你的目标用户、技术栈和应用场景。LangChain 的设计选择 -- 通用、灵活、声明式 -- 适合快速原型和多样化的应用场景,特别是当你需要频繁实验不同的模型、工具和管道配置时。在类型安全和编译时检查更重要的场景下,Semantic Kernel 或 Haystack 的方案可能更合适,因为它们在编译阶段就能捕获更多的配置错误。

从这些对比中可以提炼出一个通用的框架设计原则:抽象层次的选择应该与目标用户的需求匹配。如果你的用户主要是应用开发者(希望快速构建产品),更高级别、更自动化的抽象会更受欢迎。如果你的用户主要是平台工程师(需要精确控制每个环节),更低级别、更显式的抽象会更合适。LangChain 试图通过"层次化的抽象"来兼顾两者 -- LCEL 提供高级声明式组合,Runnable 提供中级可编程接口,底层的 BaseChatModel 和 BaseTool 提供低级可覆盖的钩子。

18.10 总结:设计模式速查表

模式核心思想LangChain 实现可迁移场景
Runnable 协议统一接口,一套 API 驱动所有组件invoke/stream/batch + pipe任何需要统一异构组件的框架
回调洋葱无侵入式可观测性BaseCallbackHandler + get_child需要追踪调用链的系统
Partner 解耦核心稳定,集成独立langchain-core + Partner 包大量第三方集成的框架
组合优于继承管道声明优于子类重写LCEL | 操作符流水线组合场景
纵深防御多层独立安全检查转义+白名单+验证器反序列化不可信数据

小结

本章从 LangChain 的具体实现中提炼出五个核心设计模式。Runnable 协议模式解决了异构组件统一调用的问题。回调洋葱模型提供了无侵入式的可观测性。Partner 解耦架构管理了爆炸式增长的集成生态。LCEL 组合优于继承,让复杂管道的构建变得声明式和可组合。安全纵深防御为序列化系统提供了多层保护。

这些模式不是孤立的 -- 它们相互支撑。Runnable 协议是 LCEL 组合的基础,回调系统需要 Runnable 的统一生命周期钩子,Partner 解耦依赖 langchain-core 中稳定的 Runnable 抽象,安全系统保护了 Serializable(Runnable 的父类)的持久化。

如果说前十七章是"LangChain 是怎么做的",那本章要回答的是"为什么这样做,以及你可以怎样借鉴"。希望这些设计模式能为你构建自己的 AI 应用框架提供有价值的参考。

至此,我们对 LangChain 的源码之旅就告一段落了。从最底层的 Runnable 协议到最顶层的 Agent 执行循环,从消息系统到序列化安全,从单个工具到整个 Partner 生态 -- 这些源码中蕴含的工程智慧,远比 API 文档能告诉你的要丰富得多。理解了"为什么这样设计",你才能在框架之上,而非框架之内,构建真正优秀的 AI 应用。

基于 VitePress 构建