掘金 人工智能 14小时前
LangChain 设计原理分析⁴ | BaseLanguageModel 接口解构:多模型适配的设计模式
index_new5.html
../../../zaker_core/zaker_tpl_static/wap/tpl_guoji1.html

 

本文深入剖析了LangChain中用于语言模型接口的核心抽象BaseLanguageModel。文章详细介绍了BaseLanguageModel作为所有语言模型类的统一基类,其在Runnable体系中的作用,以及如何通过继承BaseLLM和BaseChatModel来适配不同类型的语言模型。文中还阐述了invoke方法作为统一执行入口,并探讨了工厂模式在实现多模型适配、解耦和提升可扩展性方面的价值。此外,文章通过示例展示了如何自定义Chat模型,并对比了BaseLLM与BaseChatModel的主要区别,为理解LangChain的模型集成机制提供了清晰的指导。

♦️ **BaseLanguageModel是LangChain模型抽象的基石**:作为所有语言模型类的统一基类,BaseLanguageModel位于`langchain_core.language_models.base`模块,它定义了统一的配置接口、token使用信息获取和流式生成支持等结构,但不直接实现文本生成功能,而是为`BaseLLM`(纯文本生成)和`BaseChatModel`(对话格式)这两类模型提供基础。

♦️ **Invoke方法实现统一调用与组合**:由于BaseLanguageModel继承自Runnable,所有继承它的模型类都天然拥有`invoke()`方法,这为LangChain中的链、代理和应用框架提供了统一的执行入口,使得不同模型之间可以方便地进行链式组合和切换,增强了系统的灵活性和可扩展性。

♦️ **BaseLLM与BaseChatModel的区别与适配**:`BaseLLM`主要处理纯文本输入输出,适用于文本生成、摘要等场景;而`BaseChatModel`则处理结构化的消息列表,更适合多轮对话、工具调用等复杂交互。两者虽在输入输出格式和使用场景上有所区别,但都遵循Runnable接口,支持`invoke`、`stream`、`batch`等统一操作。

♦️ **工厂模式提升多模型适配能力**:LangChain通过将具体模型实现(如ChatOpenAI、ChatAnthropic)迁移至`langchain-community`等子项目,并利用工厂模式,实现了“核心逻辑”与“社区接入”的分离。这种设计使得LangChain能够高效地适配来自不同厂商的众多语言模型,并保持核心代码的整洁和高复用性。

♦️ **自定义模型增强灵活性与可测试性**:文章通过`EchoChatModel`的示例,展示了如何继承`BaseChatModel`来自定义一个简单的回显模型。这种能力在测试代理行为、调试链路或实现特定功能时非常有用,体现了LangChain在模型集成方面的灵活性和高度可定制性。

本文聚焦 LangChain 对语言模型接口的核心抽象 —— BaseLanguageModel,结合源码深入剖析其在多模型适配中的角色,并探讨工厂模式等设计思想如何提升模型集成能力。


一、什么是 BaseLanguageModel?

在 LangChain 中,BaseLanguageModel 是所有语言模型类的统一基类,位于模块 langchain_core.language_models.base 中。

它作为 LangChain 的 Runnable 抽象体系的一部分,定义了语言模型统一的配置接口、token 使用信息获取、流式生成支持等结构,但 并不直接实现文本生成或对话功能

class BaseLanguageModel(    RunnableSerializable[LanguageModelInput, LanguageModelOutputVar], ABC):    """Abstract base class for interfacing with language models.    All language model wrappers inherited from BaseLanguageModel.    """

该基类是为以下两类模型服务的抽象基类:

class BaseLLM(BaseLanguageModel[str], ABC):    """Base LLM abstract interface.    It should take in a prompt and return a string.    """    callback_manager: Optional[BaseCallbackManager] = Field(default=None, exclude=True)    """[DEPRECATED]"""    model_config = ConfigDict(        arbitrary_types_allowed=True,    )
class BaseChatModel(BaseLanguageModel[BaseMessage], ABC):    """Base class for chat models.    Key imperative methods:        Methods that actually call the underlying model.        +---------------------------+----------------------------------------------------------------+---------------------------------------------------------------------+--------------------------------------------------------------------------------------------------+        | Method                    | Input                                                          | Output                                                              | Description                                                                                      |        +===========================+================================================================+=====================================================================+==================================================================================================+        | `invoke`                  | str | list[dict | tuple | BaseMessage] | PromptValue           | BaseMessage                                                         | A single chat model call.                                                                        |        +---------------------------+----------------------------------------------------------------+---------------------------------------------------------------------+--------------------------------------------------------------------------------------------------+        | `ainvoke`                 | '''                                                            | BaseMessage                                                         | Defaults to running invoke in an async executor.                                                 |        +---------------------------+----------------------------------------------------------------+---------------------------------------------------------------------+--------------------------------------------------------------------------------------------------+        | `stream`                  | '''                                                            | Iterator[BaseMessageChunk]                                          | Defaults to yielding output of invoke.                                                           |        +---------------------------+----------------------------------------------------------------+---------------------------------------------------------------------+--------------------------------------------------------------------------------------------------+        | `astream`                 | '''                                                            | AsyncIterator[BaseMessageChunk]                                     | Defaults to yielding output of ainvoke.                                                          |        +---------------------------+----------------------------------------------------------------+---------------------------------------------------------------------+--------------------------------------------------------------------------------------------------+        | `astream_events`          | '''                                                            | AsyncIterator[StreamEvent]                                          | Event types: 'on_chat_model_start', 'on_chat_model_stream', 'on_chat_model_end'.                 |        +---------------------------+----------------------------------------------------------------+---------------------------------------------------------------------+--------------------------------------------------------------------------------------------------+        | `batch`                   | list[''']                                                      | list[BaseMessage]                                                   | Defaults to running invoke in concurrent threads.                                                |        +---------------------------+----------------------------------------------------------------+---------------------------------------------------------------------+--------------------------------------------------------------------------------------------------+        | `abatch`                  | list[''']                                                      | list[BaseMessage]                                                   | Defaults to running ainvoke in concurrent threads.                                               |        +---------------------------+----------------------------------------------------------------+---------------------------------------------------------------------+--------------------------------------------------------------------------------------------------+        | `batch_as_completed`      | list[''']                                                      | Iterator[tuple[int, Union[BaseMessage, Exception]]]                 | Defaults to running invoke in concurrent threads.                                                |        +---------------------------+----------------------------------------------------------------+---------------------------------------------------------------------+--------------------------------------------------------------------------------------------------+        | `abatch_as_completed`     | list[''']                                                      | AsyncIterator[tuple[int, Union[BaseMessage, Exception]]]            | Defaults to running ainvoke in concurrent threads.                                               |        +---------------------------+----------------------------------------------------------------+---------------------------------------------------------------------+--------------------------------------------------------------------------------------------------+    ...    """

二、invoke 方法

虽然 BaseLanguageModel 没有显式实现 invoke(),但它继承自 RunnableSerializableRunnable,后者为 LangChain 中所有模块提供了统一的执行入口:

# langchain_core.runnables.baseclass Runnable(Protocol):    def invoke(self, input: InputType, config: RunnableConfig = None) -> OutputType: ...

因此,任何继承自 BaseLanguageModel 的模型类 自动拥有了 invoke() 方法,可用于链式组合执行。BaseLLMBaseChatModel 都在各自子类中重写了 invoke() 方法,它们的输入接口保持一致(通常为 LanguageModelInput),但输出类型不同:BaseLLM 输出纯文本结果,而 BaseChatModel 输出结构化的消息对象(BaseMessage)。

@overridedef invoke(    self,    input: LanguageModelInput,    config: Optional[RunnableConfig] = None,    *,    stop: Optional[list[str]] = None,    **kwargs: Any,) -> str:    config = ensure_config(config)    return (        self.generate_prompt(            [self._convert_input(input)],            stop=stop,            callbacks=config.get("callbacks"),            tags=config.get("tags"),            metadata=config.get("metadata"),            run_name=config.get("run_name"),            run_id=config.pop("run_id", None),            **kwargs,        )        .generations[0][0]        .text    )
@overridedef invoke(    self,    input: LanguageModelInput,    config: Optional[RunnableConfig] = None,    *,    stop: Optional[list[str]] = None,    **kwargs: Any,) -> BaseMessage:    config = ensure_config(config)    return cast(        "ChatGeneration",        self.generate_prompt(            [self._convert_input(input)],            stop=stop,            callbacks=config.get("callbacks"),            tags=config.get("tags"),            metadata=config.get("metadata"),            run_name=config.get("run_name"),            run_id=config.pop("run_id", None),            **kwargs,        ).generations[0][0],    ).message

👀 cast() 是 Python 类型系统中的一个函数,来自标准库模块 typing。它的作用主要是给类型系统看的注释,不会改变实际数据类型,所以请确保你的 cast 是合理的,不然只是自欺欺人。


三、工厂模式与多模型适配结构

LangChain 在早期版本中将主流模型厂商(如 OpenAI、Anthropic、Alibaba 等)的封装类直接集中维护在主包中,并统一继承自 BaseChatModel。然而在最新版本中,LangChain 已将这类具体实现迁移到子项目 langchain-community,形成“核心逻辑”和“社区接入”的分离架构。

langchain-community 包中,你可以找到如下模型的适配器类:

class ChatOpenAI(BaseChatModel):    """`OpenAI` Chat large language models API.    To use, you should have the ``openai`` python package installed, and the    environment variable ``OPENAI_API_KEY`` set with your API key.    Any parameters that are valid to be passed to the openai.create call can be passed    in, even if not explicitly saved on this class.    Example:        .. code-block:: python            from langchain_community.chat_models import ChatOpenAI            openai = ChatOpenAI(model="gpt-3.5-turbo")    """
class ChatAnthropic(BaseChatModel, _AnthropicCommon):    """`Anthropic` chat large language models.    To use, you should have the ``anthropic`` python package installed, and the    environment variable ``ANTHROPIC_API_KEY`` set with your API key, or pass    it as a named parameter to the constructor.    Example:        .. code-block:: python            import anthropic            from langchain_community.chat_models import ChatAnthropic            model = ChatAnthropic(model="<model_name>", anthropic_api_key="my-api-key")    """
class ChatTongyi(BaseChatModel):    """Alibaba Tongyi Qwen chat model integration.    ...    """

这些类通过重写 invoke/_generate 方法,实现:


ChatOpenAI._generate()

def _generate(    self,    messages: List[BaseMessage],    stop: Optional[List[str]] = None,    run_manager: Optional[CallbackManagerForLLMRun] = None,    stream: Optional[bool] = None,    **kwargs: Any,) -> ChatResult:    should_stream = stream if stream is not None else self.streaming    if should_stream:        stream_iter = self._stream(            messages, stop=stop, run_manager=run_manager, **kwargs        )        return generate_from_stream(stream_iter)    message_dicts, params = self._create_message_dicts(messages, stop)    params = {        **params,        **({"stream": stream} if stream is not None else {}),        **kwargs,    }    response = self.completion_with_retry(        messages=message_dicts, run_manager=run_manager, **params    )    return self._create_chat_result(response)

ChatTongyi._generate()

def _generate(    self,    messages: List[BaseMessage],    stop: Optional[List[str]] = None,    run_manager: Optional[CallbackManagerForLLMRun] = None,    **kwargs: Any,) -> ChatResult:    generations = []    if self.streaming:        generation_chunk: Optional[ChatGenerationChunk] = None        for chunk in self._stream(            messages, stop=stop, run_manager=run_manager, **kwargs        ):            if generation_chunk is None:                generation_chunk = chunk            else:                generation_chunk += chunk        assert generation_chunk is not None        generations.append(self._chunk_to_generation(generation_chunk))    else:        params: Dict[str, Any] = self._invocation_params(            messages=messages, stop=stop, **kwargs        )        resp = self.completion_with_retry(**params)        generations.append(            ChatGeneration(**self._chat_generation_from_qwen_resp(resp))        )    return ChatResult(        generations=generations,        llm_output={            "model_name": self.model_name,        },    )

示例:OpenAI 聊天模型

import osfrom langchain_community.chat_models import ChatOpenAIfrom langchain_core.messages import HumanMessagemodel = ChatOpenAI(    model='deepseek-chat',    openai_api_key=os.getenv('DEEPSEEK_API_KEY'),    openai_api_base='https://api.deepseek.com',    max_tokens=1024)response = model.invoke(    [HumanMessage(content="一句话回答这个问题:AI会成为第一生产力吗?")])print(response.content)

我没有 OpenAI 的 API Key,DeepSeek API 使用与 OpenAI 兼容的 API 格式,所以替代一下。

输出:


四、BaseLLM 与 BaseChatModel 的区别

在 LangChain 的语言模型抽象体系中,BaseLLMBaseChatModel 是两个并列的核心基类,它们都继承自 BaseLanguageModel,并作为 Runnable 可组合模块用于构建链、代理和应用框架。

它们的区别主要体现在输入输出格式、生成逻辑、使用场景等方面:

特性BaseLLMBaseChatModel
输入类型str(纯文本)List[BaseMessage](结构化消息列表)
输出类型strLLMResultBaseMessageChatResult
使用场景文本生成、摘要、续写、补全多轮对话、工具调用、代理决策、意图识别等
是否重写 invoke()✅ 重写✅ 重写
内部核心方法_generate(prompt: PromptValue)_generate(messages: list[BaseMessage])
是否支持流式输出✅ 支持(token 级流式输出)✅ 支持(message 内容流式分块)

📌 典型子类包括:

🎯 使用接口一致性

尽管输入输出有所不同,二者都遵循 Runnable 接口标准,统一支持:


五、自定义一个 Chat 模型类(继承 BaseChatModel)

自定义一个对话模型 EchoChatModel,用于模拟回显响应。

from langchain_core.language_models.chat_models import BaseChatModelfrom langchain_core.messages import BaseMessage, AIMessagefrom langchain_core.outputs import ChatResult, ChatGenerationclass EchoChatModel(BaseChatModel):    @property    def _llm_type(self) -> str:        return "echo-chat"    def _generate(        self,        messages: list[BaseMessage],        stop: list[str] = None,        **kwargs    ) -> ChatResult:        content = f"Echo: {messages[-1].content}"        return ChatResult(            generations=[                ChatGeneration(                    message=AIMessage(content=content)                )            ]        )

这在测试代理行为或调试链路时非常有用。

from langchain_core.messages import HumanMessagemodel = EchoChatModel()response = model.invoke([HumanMessage(content="我向你提出了一个问题!")])print(response.content)

输出:


六、设计模式与解耦价值

LangChain 采用“抽象基类 + 工厂方法 +统一协议”组合的方式封装 LLM:

这让我们在构建 Chain、Agent、工具执行器等复杂流程时,可以自由切换底层模型而不影响业务逻辑。


🔚 小结


接下来我们将深入解析 LangChain 的 PromptTemplate 模板系统,掌握变量绑定、FewShot 示例注入与格式化机制,构建高可控的提示生成链路。

Fish AI Reader

Fish AI Reader

AI辅助创作,多种专业模板,深度分析,高质量内容生成。从观点提取到深度思考,FishAI为您提供全方位的创作支持。新版本引入自定义参数,让您的创作更加个性化和精准。

FishAI

FishAI

鱼阅,AI 时代的下一个智能信息助手,助你摆脱信息焦虑

联系邮箱 441953276@qq.com

相关标签

LangChain BaseLanguageModel LLM 模型适配 工厂模式
相关文章