LangChain 源码 深度历险:基于GOF的设计模式,穿透 LangChain 源码

本文 的 原文 地址

原始的内容,请参考 本文 的 原文 地址

本文 的 原文 地址

尼恩:LLM大模型学习圣经PDF的起源

在40岁老架构师 尼恩的读者交流群(50+)中,经常性的指导小伙伴们改造简历。

经过尼恩的改造之后,很多小伙伴拿到了一线互联网企业如得物、阿里、滴滴、极兔、有赞、希音、百度、网易、美团的面试机会,拿到了大厂机会。

2025年开始,尼恩一直在辅导小伙伴们做 AI 架构面试, 很多小伙伴 拿到了 Java + AI 架构offer ,比如下面的案例:

34岁无路可走,一个月翻盘,拿 3个架构offer,靠 Java+Al 逆天改命!!!

3年 程序媛 被裁, 25W-》40W 上岸, 逆涨60%。 Java+AI 太神了, 架构小白 2个月逆天改命

36岁/失业7个月/彻底绝望 。狠卷 3个月 Java+AI ,终于逆风翻盘,顺利 上岸

尼恩架构团队,通过 梳理一个《LLM大模型学习圣经》 帮助更多的人做LLM架构,拿到年薪100W, 这个内容体系包括下面的内容:

  • Python学习圣经:从0到1精通Python,打好AI基础

  • LLM大模型学习圣经:从0到1吃透Transformer技术底座

  • 《LangChain学习圣经:从0到1精通LLM大模型应用开发的基础框架》

  • 《LLM大模型学习圣经:从0到1精通RAG架构,基于LLM+RAG构建生产级企业知识库》

  • 《SpringCloud + Python 混合微服务架构,打造AI分布式业务应用的技术底层》

  • 《LLM大模型学习圣经:从0到1吃透大模型的顶级架构》

  • 《LLM 智能体 学习圣经:从0到1吃透 LLM 智能体 的架构 与实操》

  • 《LLM 智能体 学习圣经:从0到1吃透 LLM 智能体 的 中台 架构 与实操》

  • 《 阿里面试:RAG 怎么优化?探讨一下 RAG优化的7大黄金法则 ?》

LangChain 源码 + 24 设计模式:通过设计模式,穿透 LangChain 源码

LangChain 源码 很复杂, 很多小伙伴和尼恩反馈: 看不懂

接下来, 尼恩结合 GOF的 设计模式 , 帮助大家 穿透式、起底式 掌握 LangChain 源码 ,实现大家内力猛涨。

一、引言

1.1 为什么需要深入理解LangChain源码?

在当今人工智能领域,大型语言模型(LLMs)如GPT-4、Claude等展现出了强大的自然语言处理能力。然而,要将这些模型应用于实际场景,开发者往往面临诸多挑战,如上下文管理、工具调用、多模态交互等。LangChain作为一个开源框架,提供了一套灵活且强大的工具集,帮助开发者构建基于LLMs的复杂应用。

深入理解LangChain源码有以下几个重要原因:

  • 定制化需求:不同的应用场景可能需要对LangChain的核心组件进行定制或扩展,例如自定义提示模板、记忆策略或代理决策逻辑。
  • 性能优化:通过分析源码,可以识别性能瓶颈并进行针对性优化,例如缓存机制、异步处理或并行计算。
  • 故障排查:当应用出现问题时,源码级别的理解能够帮助快速定位和解决问题。
  • 技术创新:了解LangChain的实现原理可以启发开发者在其基础上进行技术创新,开发新的组件或应用模式。

二、LangChain架构概述

2.1 LangChain整体架构设计

LangChain的架构设计遵循模块化、可扩展的原则,主要由以下几个核心层组成:

(1) LLM接口层:负责与各种大型语言模型(如OpenAI、Hugging Face等)进行交互,提供统一的调用接口。

(2) 核心组件层:包含Chains、Memory、Prompt Templates、Agents和Tools等核心组件,实现了LLM应用的基本功能单元。

(3) 集成层:提供与外部数据源(如向量数据库、文件系统等)和工具(如搜索引擎、计算器等)的集成能力。

(4) 应用层:基于上述组件构建的具体应用,如聊天机器人、文档问答系统等。

这种分层设计使得LangChain具有良好的可扩展性和灵活性,开发者可以根据需要替换或扩展任意层的功能。

2.2 LangChain核心模块划分

LangChain的源码主要分为以下几个核心模块:


langchain/
├── chains/                # 链组件,用于定义和管理处理流程
├── memory/                # 记忆组件,用于管理对话和交互的上下文
├── prompts/               # 提示模板组件,用于生成向LLM提交的提示
├── agents/                # 代理组件,用于实现智能决策和工具调用
├── tools/                 # 工具组件,封装各种外部功能
├── llms/                  # LLM接口层,支持多种LLM提供商
├── embeddings/            # 嵌入模型接口,用于文本向量化
├── vectorstores/          # 向量数据库接口,用于语义搜索
├── document_loaders/      # 文档加载器,用于处理各种格式的文档
├── text_splitter/         # 文本分割器,用于将长文本分割为小块
├── callbacks/             # 回调系统,用于监控和记录执行过程
└── utils/                 # 工具函数和辅助类

接下来,我们将深入分析每个模块的核心功能和实现原理。

三、LLM接口层实现

3.1 LLM抽象基类

LangChain通过定义抽象基类来统一不同LLM提供商的接口,使得开发者可以无缝切换不同的模型。核心抽象基类位于langchain/llms/base.py


# langchain/llms/base.py

from abc import ABC, abstractmethod
from typing import Any, List, Mapping, Optional, Union

class LLM(ABC):
    """LLM抽象基类,定义了所有LLM实现必须遵循的接口"""
    
    @property
    @abstractmethod
    def _llm_type(self) -> str:
        """返回LLM类型的字符串标识"""
        pass
    
    @abstractmethod
    def _call(
        self, 
        prompt: str, 
        stop: Optional[List[str]] = None
    ) -> str:
        """执行LLM调用的核心方法"""
        pass
    
    @property
    def _identifying_params(self) -> Mapping[str, Any]:
        """返回标识LLM实例的参数"""
        return {
   
   }
    
    def __str__(self) -> str:
        """返回LLM的字符串表示"""
        return f"{
     
     self._llm_type()}: {
     
     self._identifying_params}"

所有具体的LLM实现都必须继承这个抽象基类,并实现_llm_type_call方法。_llm_type返回LLM的类型标识(如"openai"、"huggingface"等),而_call方法则实现了实际的模型调用逻辑。

3.1.1 通过 模板方法模式(Template Method Pattern) 剖析 LLM抽象基类

我们 结合一个经典的设计模式对这段代码进行介绍。

模板方法模式是一种行为型设计模式,它定义了一个算法的骨架,并允许子类在不改变算法结构的前提下重新定义算法的某些步骤。

在我们这段代码中,LLM 类是一个抽象基类(Abstract Base Class),它定义了所有语言模型(如 OpenAI、HuggingFace 等)必须遵循的接口。这个类中定义了抽象方法(_llm_type_call)和一些默认实现的方法(如 __str___identifying_params),这正是模板方法模式的典型应用。


3.1.2代码逐行注释


# langchain/llms/base.py

# 导入 Python 的抽象基类模块
from abc import ABC, abstractmethod

# 导入类型注解相关的模块
from typing import Any, List, Mapping, Optional, Union

# 定义 LLM 抽象基类,继承自 ABC,表示这是一个抽象类
class LLM(ABC):
    """LLM抽象基类,定义了所有LLM实现必须遵循的接口"""
    
    # 抽象属性方法,子类必须实现,用于返回当前 LLM 的类型标识字符串
    @property
    @abstractmethod
    def _llm_type(self) -> str:
        """返回LLM类型的字符串标识"""
        pass
    
    # 抽象方法,子类必须实现,用于执行模型调用的核心逻辑
    @abstractmethod
    def _call(
        self, 
        prompt: str, 
        stop: Optional[List[str]] = None
    ) -> str:
        """执行LLM调用的核心方法"""
        pass
    
    # 可选实现的属性方法,返回标识当前 LLM 实例的参数字典
    @property
    def _identifying_params(self) -> Mapping[str, Any]:
        """返回标识LLM实例的参数"""
        return {
   
   }
    
    # 默认实现的字符串方法,用于打印 LLM 的类型和参数信息
    def __str__(self) -> str:
        """返回LLM的字符串表示"""
        return f"{
     
     self._llm_type()}: {
     
     self._identifying_params}"


3.1.3模板方法模式如何体现?

在这个类中:

  • _llm_type_call 是抽象方法,相当于模板中“必须实现”的步骤。
  • _identifying_params__str__ 是具体方法,相当于模板中“默认实现”的步骤。
  • 整个类定义了使用 LLM 的流程框架,比如调用模型、获取类型、生成字符串表示等。
  • 子类只需要实现抽象方法,就能融入整个框架中,无需关心整体流程。

第一、这段代码使用了模板方法模式,通过定义一个抽象基类 LLM,统一了不同语言模型的调用接口。

第二、抽象方法 _llm_type_call 定义了子类必须实现的逻辑,而其他方法提供了默认实现,构成了算法的骨架。

第三、这种设计让不同模型(如 OpenAI、HuggingFace)可以无缝接入 LangChain,同时保持调用方式的一致性。

第四、通过 图可以清晰看到类结构和继承关系,帮助理解模板方法模式是如何组织代码逻辑的。

3.2 OpenAI LLM实现

以OpenAI LLM实现为例,我们来看具体的实现细节:


# langchain/llms/openai.py

import openai
from typing import Any, Dict, List, Optional, Union

from langchain.llms.base import LLM
from langchain.utils import get_from_dict_or_env

class OpenAI(LLM):
    """OpenAI LLM实现"""
    
    # 模型名称,默认为text-davinci-003
    model_name: str = "text-davinci-003"
    # 温度参数,控制输出的随机性
    temperature: float = 0.7
    # 最大生成token数
    max_tokens: int = 2049
    # 顶部概率采样参数
    top_p: float = 1
    # 频率惩罚参数
    frequency_penalty: float = 0
    # 存在惩罚参数
    presence_penalty: float = 0
    # API密钥
    openai_api_key: Optional[str] = None
    
    @property
    def _llm_type(self) -> str:
        """返回LLM类型标识"""
        return "openai"
    
    def _call(
        self, 
        prompt: str, 
        stop: Optional[List[str]] = None
    ) -> str:
        """执行OpenAI API调用"""
        # 获取API密钥,优先从实例属性获取,否则从环境变量获取
        openai_api_key = get_from_dict_or_env(
            self.__dict__, "openai_api_key", "OPENAI_API_KEY"
        )
        
        # 构建API调用参数
        params = {
   
   
            "model": self.model_name,
            "prompt": prompt,
            "temperature": self.temperature,
            "max_tokens": self.max_tokens,
            "top_p": self.top_p,
            "frequency_penalty": self.frequency_penalty,
            "presence_penalty": self.presence_penalty,
        }
        
        # 如果提供了停止词,添加到参数中
        if stop is not None:
            params["stop"] = stop
        
        # 执行API调用
        response = openai.Completion.create(
            api_key=openai_api_key,
            **params
        )
        
        # 提取并返回生成的文本
        return response.choices[0].text.strip()
    
    @property
    def _identifying_params(self) -> Dict[str, Any]:
        """返回标识LLM实例的参数"""
        return {
   
   
            "model_name": self.model_name,
            "temperature": self.temperature,
            "max_tokens": self.max_tokens,
            "top_p": self.top_p,
            "frequency_penalty": self.frequency_penalty,
            "presence_penalty": self.presence_penalty,
        }

从这个实现中可以看出,OpenAI类继承了LLM抽象基类,并实现了必要的方法。_call方法负责构建API请求参数并执行实际的API调用,处理响应并返回生成的文本。

3.3 LLM调用流程

当使用LangChain调用LLM时,整个流程大致如下:

(1) 用户通过LangChain API创建LLM实例(如OpenAI、HuggingFace等)

(2) 用户构建提示文本并调用LLM实例

(3) LLM实例将提示文本和其他参数传递给_call方法

(4) _call方法根据具体的LLM提供商实现,构建并发送API请求

(5) 接收API响应并处理结果

(6) 将处理后的结果返回给用户

这种设计使得LangChain能够支持多种LLM提供商,用户可以根据需要轻松切换不同的模型,而无需修改业务逻辑代码。

3.4 异步支持

LangChain还提供了对异步LLM调用的支持,通过定义异步方法:


# langchain/llms/base.py

import asyncio
from typing import AsyncGenerator

class LLM(ABC):
    # ... 其他方法 ...
    
    async def agenerate(
        self, 
        prompts: List[str], 
        stop: Optional[List[str]] = None
    ) -> LLMResult:
        """异步生成多个提示的响应"""
        tasks = [self._agenerate_one(prompt, stop=stop) for prompt in prompts]
        results = await asyncio.gather(*tasks)
        return LLMResult(generations=[[res] for res in results])
    
    async def _agenerate_one(
        self, 
        prompt: str, 
        stop: Optional[List[str]] = None
    ) -> Generation:
        """异步生成单个提示的响应"""
        text = await self._acall(prompt, stop=stop)
        return Generation(text=text)
    
    @abstractmethod
    async def _acall(
        self, 
        prompt: str, 
        stop: Optional[List[str]] = None
    ) -> str:
        """异步调用LLM的核心方法,由子类实现"""
        pass

具体的LLM实现需要实现_acall方法,提供异步调用能力。例如,OpenAI的异步实现:


# langchain/llms/openai.py

class OpenAI(LLM):
    # ... 其他方法 ...
    
    async def _acall(
        self, 
        prompt: str, 
        stop: Optional[List[str]] = None
    ) -> str:
        """异步执行OpenAI API调用"""
        openai_api_key = get_from_dict_or_env(
            self.__dict__, "openai_api_key", "OPENAI_API_KEY"
        )
        
        params = {
   
   
            "model": self.model_name,
            "prompt": prompt,
            "temperature": self.temperature,
            "max_tokens": self.max_tokens,
            "top_p": self.top_p,
            "frequency_penalty": self.frequency_penalty,
            "presence_penalty": self.presence_penalty,
        }
        
        if stop is not None:
            params["stop"] = stop
        
        # 异步执行API调用
        response = await openai.Completion.acreate(
            api_key=openai_api_key,
            **params
        )
        
        return response.choices[0].text.strip()

这种异步支持使得LangChain能够高效处理多个并发的LLM请求,提高系统的吞吐量和响应性能。

四、Chain组件实现原理

4.1 Chain抽象基类

Chain是LangChain中最核心的组件之一,用于定义和管理一系列处理步骤。Chain的抽象基类定义在langchain/chains/base.py


# langchain/chains/base.py

from abc import ABC, abstractmethod
from typing import Any, Dict, List, Optional, Tuple, Union

from langchain.callbacks.base import BaseCallbackManager
from langchain.callbacks.manager import (
    AsyncCallbackManagerForChainRun,
    CallbackManagerForChainRun,
    Callbacks,
)

class Chain(ABC):
    """Chain抽象基类,定义了所有Chain实现必须遵循的接口"""
    
    # 回调管理器,用于记录和监控Chain的执行过程
    callback_manager: Optional[BaseCallbackManager] = None
    
    @property
    @abstractmethod
    def input_keys(self) -> List[str]:
        """返回Chain期望的输入键列表"""
        pass
    
    @property
    @abstractmethod
    def output_keys(self) -> List[str]:
        """返回Chain产生的输出键列表"""
        pass
    
    @abstractmethod
    def _call(
        self, 
        inputs: Dict[str, Any],
        run_manager: Optional[CallbackManagerForChainRun] = None
    ) -> Dict[str, Any]:
        """执行Chain的核心方法,由子类实现"""
        pass
    
    async def _acall(
        self, 
        inputs: Dict[str, Any],
        run_manager: Optional[AsyncCallbackManagerForChainRun] = None
    ) -> Dict[str, Any]:
        """异步执行Chain的方法,默认同步实现,可被子类重写"""
        return self._call(inputs, run_manager=run_manager)
    
    def run(
        self, 
        callbacks: Callbacks = None,
        **kwargs: Any
    ) -> Union[str, Dict[str, Any]]:
        """便捷方法,用于直接运行Chain并获取结果"""
        # 检查输入键是否匹配
        if set(kwargs.keys()) != set(self.input_keys):
            raise ValueError(
                f"输入键不匹配。期望: {
     
     self.input_keys}, 实际: {
     
     list(kwargs.keys())}"
            )
        
        # 执行Chain
        output = self(kwargs, callbacks=callbacks)
        
        # 如果只有一个输出键,直接返回该值
        if len(self.output_keys) == 1:
            return output[self.output_keys[0]]
        else:
            return output
    
    async def arun(
        self, 
        callbacks: Callbacks = None,
        **kwargs: Any
    ) -> Union[str, Dict[str, Any]]:
        """异步便捷方法,用于直接运行Chain并获取结果"""
        if set(kwargs.keys()) != set(self.input_keys):
            raise ValueError(
                f"输入键不匹配。期望: {
     
     self.input_keys}, 实际: {
     
     list(kwargs.keys())}"
            )
        
        output = await self.acall(kwargs, call
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值