第四章:模型集成生态 —— Partner 包架构与 init_chat_model 统一入口

系列:深入 LangChain ------ 从核心原理到生产实践

前置阅读第一章:LangChain 生态全景第二章:LangChain Core 深度剖析第三章:LangChain Classic vs. 新版 LangChain


学习目标

  • 理解 Partner 包的标准化架构和实现模式
  • 掌握 init_chat_model() 的 Provider 注册机制与字符串解析逻辑
  • 了解 _ConfigurableModel 运行时动态切换模型的设计
  • 深入 Model Profiles 系统:从 models.dev 到 _profiles.py 的完整数据流
  • 理解标准化测试框架如何保证 Partner 包的质量

4.1 Partner 包生态概览

LangChain 的 Partner 包位于 libs/partners/ 目录下,是连接 LangChain 核心抽象与具体 LLM 提供商的桥梁。目前 monorepo 中维护着 15 个官方 Partner 包:

复制代码
libs/partners/
├── openai/          # ChatOpenAI, AzureChatOpenAI, OpenAIEmbeddings
├── anthropic/       # ChatAnthropic, AnthropicLLM
├── deepseek/        # ChatDeepSeek(继承 OpenAI)
├── groq/            # ChatGroq
├── ollama/          # ChatOllama, OllamaLLM, OllamaEmbeddings
├── mistralai/       # ChatMistralAI, MistralAIEmbeddings
├── fireworks/       # ChatFireworks, FireworksEmbeddings
├── huggingface/     # ChatHuggingFace, HuggingFaceEmbeddings
├── xai/             # ChatXAI(继承 OpenAI)
├── perplexity/      # ChatPerplexity, PerplexitySearchRetriever
├── openrouter/      # ChatOpenRouter(Beta)
├── chroma/          # Chroma(向量库)
├── qdrant/          # QdrantVectorStore, FastEmbedSparse
├── exa/             # ExaSearchRetriever(搜索工具)
└── nomic/           # NomicEmbeddings

这 15 个包覆盖了四大类集成需求:

类别 Partner 包 数量
LLM / Chat Model openai, anthropic, deepseek, groq, ollama, mistralai, fireworks, huggingface, xai, perplexity, openrouter 11
向量数据库 chroma, qdrant 2
Embedding nomic(openai/ollama/mistralai 等也提供 Embedding) 1
搜索/检索 exa 1

需要注意的是,这个 monorepo 并不包含所有 LangChain 集成。Google(langchain-google-vertexailangchain-google-genai)、AWS(langchain-aws)、Cohere(langchain-cohere)等提供商的集成包维护在独立的仓库中。


4.2 Partner 包标准化架构

所有 Partner 包遵循统一的目录结构、依赖规范和构建配置。这种标准化使得维护 15+ 个包变得可行。

4.2.1 标准目录结构

langchain-openai 为代表:

复制代码
libs/partners/openai/
├── pyproject.toml                      # 包配置、依赖、构建系统
├── langchain_openai/                   # 主包目录
│   ├── __init__.py                     # 公开 API 导出
│   ├── chat_models/
│   │   ├── __init__.py                 # 导出 ChatOpenAI, AzureChatOpenAI
│   │   ├── base.py                     # BaseChatOpenAI 核心实现
│   │   └── azure.py                    # Azure 特定实现
│   ├── llms/
│   │   ├── __init__.py                 # 导出 OpenAI, AzureOpenAI
│   │   ├── base.py                     # 基础 LLM 实现
│   │   └── azure.py                    # Azure 特定实现
│   ├── embeddings/
│   │   ├── __init__.py                 # 导出 OpenAIEmbeddings
│   │   ├── base.py                     # 基础 Embedding 实现
│   │   └── azure.py                    # Azure 特定实现
│   ├── tools/                          # 工具定义
│   ├── data/
│   │   ├── __init__.py
│   │   ├── _profiles.py               # 自动生成的模型能力数据(~41KB)
│   │   └── profile_augmentations.toml  # 本地增强配置
│   └── py.typed                        # PEP 561 类型提示标记
└── tests/
    ├── unit_tests/                     # 单元测试(无网络)
    └── integration_tests/              # 集成测试(需要 API Key)

并非所有包都有这么完整的结构。较小的包可能只有一个 chat_models.py 文件:

复制代码
libs/partners/deepseek/
├── pyproject.toml
├── langchain_deepseek/
│   ├── __init__.py                     # 仅导出 ChatDeepSeek
│   ├── chat_models.py                  # 单文件实现(~20KB)
│   ├── data/
│   │   ├── __init__.py
│   │   └── _profiles.py               # DeepSeek 模型配置
│   └── py.typed
└── tests/

4.2.2 统一的 pyproject.toml 规范

所有 Partner 包的 pyproject.toml 遵循统一模式:

toml 复制代码
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"        # 统一使用 hatchling 构建

[project]
name = "langchain-openai"
version = "1.1.12"
requires-python = ">=3.10.0,<4.0.0"     # 统一 Python 版本要求

dependencies = [
    "langchain-core>=1.2.21,<2.0.0",    # 统一的核心依赖版本范围
    "openai>=2.26.0,<3.0.0",            # 提供商特定 SDK
    "tiktoken>=0.7.0,<1.0.0",           # 可选的辅助依赖
]

[dependency-groups]
test = [
    "pytest>=7.3.0,<8.0.0",
    "langchain-tests",                   # 标准化测试套件
]
lint = ["ruff>=0.13.1,<0.14.0"]
typing = ["mypy>=1.10.0,<2.0.0"]

[tool.uv.sources]                        # 本地开发时使用 editable install
langchain-core = { path = "../../core", editable = true }
langchain-tests = { path = "../../standard-tests", editable = true }

[tool.mypy]
disallow_untyped_defs = "True"           # 强制类型注解

[tool.ruff.lint]
select = ["ALL"]                         # 启用所有 linter 规则

统一规范要点

维度 规范
构建后端 hatchling(所有包统一)
Python 版本 >=3.10.0,<4.0.0
核心依赖 langchain-core>=1.2.21,<2.0.0
类型检查 mypydisallow_untyped_defs = True
代码规范 ruffselect = ["ALL"]
测试框架 pytest + langchain-tests(标准化测试套件)
类型标记 py.typed 文件(PEP 561)

4.2.3 公开 API 设计模式

每个 Partner 包的 __init__.py 遵循精心设计的导出模式:

OpenAI (来源:libs/partners/openai/langchain_openai/__init__.py):

python 复制代码
from langchain_openai.chat_models import AzureChatOpenAI, ChatOpenAI
from langchain_openai.embeddings import AzureOpenAIEmbeddings, OpenAIEmbeddings
from langchain_openai.llms import AzureOpenAI, OpenAI
from langchain_openai.tools import custom_tool

__all__ = [
    "AzureChatOpenAI",
    "AzureOpenAI",
    "AzureOpenAIEmbeddings",
    "ChatOpenAI",
    "OpenAI",
    "OpenAIEmbeddings",
    "custom_tool",
]

Anthropic (来源:libs/partners/anthropic/langchain_anthropic/__init__.py):

python 复制代码
from langchain_anthropic.chat_models import ChatAnthropic, convert_to_anthropic_tool
from langchain_anthropic.llms import AnthropicLLM

__all__ = ["AnthropicLLM", "ChatAnthropic", "__version__", "convert_to_anthropic_tool"]

Chroma(向量库,更加精简):

python 复制代码
from langchain_chroma.vectorstores import Chroma

__all__ = ["Chroma"]

设计原则很明确:只导出用户直接需要的类,隐藏内部实现细节

4.2.4 三种代码复用策略

Partner 包之间存在三种不同的代码复用策略:

策略一:完全独立实现(OpenAI、Anthropic、Ollama)

这些包从 BaseChatModel 直接继承,完全独立实现所有能力:

python 复制代码
# langchain_openai/chat_models/base.py
from langchain_core.language_models.chat_models import BaseChatModel

class BaseChatOpenAI(BaseChatModel):
    """OpenAI Chat Model 基类"""
    # 完整的 188KB 实现
    # 包含工具调用、流式、异步、Token 计数等所有功能
python 复制代码
# langchain_anthropic/chat_models.py
from langchain_core.language_models.chat_models import BaseChatModel

class ChatAnthropic(BaseChatModel):
    """Anthropic Chat Model"""
    # 完整的 85KB 实现
    # 包含 Claude 特有的图像/PDF 处理、工具调用等

策略二:继承复用(DeepSeek、xAI)

一些提供商的 API 与 OpenAI 兼容,通过继承 BaseChatOpenAI 并修改端点来实现:

python 复制代码
# langchain_deepseek/chat_models.py
from langchain_openai.chat_models.base import BaseChatOpenAI

class ChatDeepSeek(BaseChatOpenAI):
    """DeepSeek chat model --- 继承 OpenAI 的全部实现"""
    DEFAULT_API_BASE = "https://api.deepseek.com/v1"
    DEFAULT_BETA_API_BASE = "https://api.deepseek.com/beta"
    # 只需覆盖 API 端点,复用 OpenAI 的所有功能

注意 langchain-deepseek 的依赖声明也体现了这一点:

toml 复制代码
# libs/partners/deepseek/pyproject.toml
dependencies = [
    "langchain-core>=1.2.21,<2.0.0",
    "langchain-openai>=1.1.0,<2.0.0",  # 依赖 OpenAI 包进行代码复用
]

策略三:SDK 封装(Groq、Fireworks、HuggingFace)

这些包直接封装各自的 SDK,但不继承其他 Partner 包:

python 复制代码
# langchain_ollama/chat_models.py
from langchain_core.language_models.chat_models import BaseChatModel

class ChatOllama(BaseChatModel):
    """使用 Ollama Python SDK 封装本地模型调用"""
    # 依赖 ollama>=0.6.1 SDK

4.2.5 各 Partner 包核心数据

包名 版本 核心导出 特定依赖 代码量
langchain-openai v1.1.12 ChatOpenAI, AzureChatOpenAI, OpenAIEmbeddings openai>=2.26.0, tiktoken>=0.7.0 ~188KB
langchain-anthropic v1.4.0 ChatAnthropic, AnthropicLLM anthropic>=0.85.0 ~85KB
langchain-deepseek v1.1.0 ChatDeepSeek langchain-openai>=1.1.0 ~20KB
langchain-groq v1.1.2 ChatGroq groq>=0.30.0 ~64KB
langchain-ollama v1.1.0 ChatOllama, OllamaLLM, OllamaEmbeddings ollama>=0.6.1 ~70KB
langchain-mistralai v1.1.2 ChatMistralAI, MistralAIEmbeddings 自实现 HTTP ~50KB
langchain-fireworks v1.1.0 ChatFireworks, FireworksEmbeddings fireworks-ai>=0.13.0 ~40KB
langchain-huggingface v1.2.1 ChatHuggingFace, HuggingFaceEmbeddings huggingface-hub>=0.33.4 ~60KB
langchain-xai v1.2.2 ChatXAI langchain-openai>=1.1.7 ~15KB
langchain-perplexity v1.1.0 ChatPerplexity, PerplexitySearchRetriever perplexityai>=0.22.0 ~30KB
langchain-chroma v1.1.0 Chroma chromadb>=1.5.5 ~54KB
langchain-qdrant v1.1.0 QdrantVectorStore, FastEmbedSparse qdrant-client>=1.15.1 ~40KB
langchain-nomic v1.0.1 NomicEmbeddings nomic>=3.5.3 ~15KB
langchain-exa v1.1.0 ExaSearchRetriever exa-py>=1.0.8 ~20KB
langchain-openrouter v0.2.1 ChatOpenRouter openrouter>=0.7.11 ~15KB

4.3 init_chat_model() ------ 统一模型初始化入口

init_chat_model() 是新版 LangChain 最重要的工厂函数之一。它将 30+ 个 Provider 的初始化逻辑统一到一个入口,是 create_agent() 内部处理字符串 model 参数的核心。

4.3.1 Provider 注册表

一切始于 _BUILTIN_PROVIDERS 字典(来源:base.py):

python 复制代码
_BUILTIN_PROVIDERS: dict[str, tuple[str, str, Callable[..., BaseChatModel]]] = {
    "anthropic": ("langchain_anthropic", "ChatAnthropic", _call),
    "anthropic_bedrock": ("langchain_aws", "ChatAnthropicBedrock", _call),
    "azure_ai": ("langchain_azure_ai.chat_models", "AzureAIOpenAIApiChatModel", _call),
    "azure_openai": ("langchain_openai", "AzureChatOpenAI", _call),
    "baseten": ("langchain_baseten", "ChatBaseten", _call),
    "bedrock": ("langchain_aws", "ChatBedrock", _call),
    "bedrock_converse": ("langchain_aws", "ChatBedrockConverse", _call),
    "cohere": ("langchain_cohere", "ChatCohere", _call),
    "deepseek": ("langchain_deepseek", "ChatDeepSeek", _call),
    "fireworks": ("langchain_fireworks", "ChatFireworks", _call),
    "google_anthropic_vertex": (
        "langchain_google_vertexai.model_garden", "ChatAnthropicVertex", _call,
    ),
    "google_genai": ("langchain_google_genai", "ChatGoogleGenerativeAI", _call),
    "google_vertexai": ("langchain_google_vertexai", "ChatVertexAI", _call),
    "groq": ("langchain_groq", "ChatGroq", _call),
    "huggingface": (
        "langchain_huggingface", "ChatHuggingFace",
        lambda cls, model, **kwargs: cls.from_model_id(model_id=model, **kwargs),
    ),
    "ibm": (
        "langchain_ibm", "ChatWatsonx",
        lambda cls, model, **kwargs: cls(model_id=model, **kwargs),
    ),
    "litellm": ("langchain_litellm", "ChatLiteLLM", _call),
    "mistralai": ("langchain_mistralai", "ChatMistralAI", _call),
    "nvidia": ("langchain_nvidia_ai_endpoints", "ChatNVIDIA", _call),
    "ollama": ("langchain_ollama", "ChatOllama", _call),
    "openai": ("langchain_openai", "ChatOpenAI", _call),
    "openrouter": ("langchain_openrouter", "ChatOpenRouter", _call),
    "perplexity": ("langchain_perplexity", "ChatPerplexity", _call),
    "together": ("langchain_together", "ChatTogether", _call),
    "upstage": ("langchain_upstage", "ChatUpstage", _call),
    "xai": ("langchain_xai", "ChatXAI", _call),
}

每个条目是一个三元组 (module_path, class_name, creator_func)

字段 含义 示例
module_path 包含聊天模型类的 Python 模块路径 "langchain_openai"
class_name 要导入的类名 "ChatOpenAI"
creator_func 实例化函数 _call(大多数)或自定义 lambda

默认创建器 _call (来源:base.py):

python 复制代码
def _call(cls: type[BaseChatModel], **kwargs: Any) -> BaseChatModel:
    return cls(**kwargs)

大多数 Provider 使用标准的 model= 参数构造,但有两个特殊情况:

  • HuggingFace :使用 from_model_id() 类方法,因为需要分离 Pipeline 参数和 Chat 参数
  • IBM Watson :使用 model_id= 参数而非 model=

4.3.2 字符串解析流程

当你调用 init_chat_model("openai:gpt-4o") 时,内部的解析流程如下:

_parse_model() 函数 (来源:base.py):

python 复制代码
def _parse_model(model: str, model_provider: str | None) -> tuple[str, str]:
    """Parse model name and provider, inferring provider if necessary."""
    # 步骤 1:处理 "provider:model" 格式
    if (
        not model_provider
        and ":" in model
        and model.split(":", maxsplit=1)[0] in _BUILTIN_PROVIDERS
    ):
        model_provider = model.split(":", maxsplit=1)[0]
        model = ":".join(model.split(":")[1:])

    # 步骤 2:尝试自动推断 provider
    model_provider = model_provider or _attempt_infer_model_provider(model)

    # 步骤 3:验证 provider 有效
    if not model_provider:
        supported_list = ", ".join(sorted(_BUILTIN_PROVIDERS))
        msg = (
            f"Unable to infer model provider for {model=}. "
            f"Please specify 'model_provider' directly.\n\n"
            f"Supported providers: {supported_list}"
        )
        raise ValueError(msg)

    # 步骤 4:规范化 provider 名称
    model_provider = model_provider.replace("-", "_").lower()
    return model, model_provider

解析遵循三级策略:

  1. 显式前缀"openai:gpt-4o"provider="openai", model="gpt-4o"
  2. 模型名推断"gpt-4o" → 通过前缀匹配推断为 "openai"
  3. 显式指定init_chat_model("my-model", model_provider="openai")

4.3.3 模型名自动推断

_attempt_infer_model_provider() 函数(来源:base.py)通过模型名前缀推断 Provider:

python 复制代码
def _attempt_infer_model_provider(model_name: str) -> str | None:
    model_lower = model_name.lower()

    if any(model_lower.startswith(pre) for pre in ("gpt-", "o1", "o3", "chatgpt", "text-davinci")):
        return "openai"
    if model_lower.startswith("claude"):
        return "anthropic"
    if model_lower.startswith("command"):
        return "cohere"
    if model_lower.startswith("accounts/fireworks"):
        return "fireworks"
    if model_lower.startswith("gemini"):
        return "google_vertexai"
    if model_lower.startswith(("amazon.", "anthropic.", "meta.")):
        return "bedrock"
    if model_lower.startswith(("mistral", "mixtral")):
        return "mistralai"
    if model_lower.startswith("deepseek"):
        return "deepseek"
    if model_lower.startswith("grok"):
        return "xai"
    if model_lower.startswith("sonar"):
        return "perplexity"
    if model_lower.startswith("solar"):
        return "upstage"
    return None

推断映射表

模型名前缀 推断的 Provider
gpt-*, o1*, o3*, chatgpt*, text-davinci* openai
claude* anthropic
command* cohere
accounts/fireworks* fireworks
gemini* google_vertexai
amazon.*, anthropic.*, meta.* bedrock
mistral*, mixtral* mistralai
deepseek* deepseek
grok* xai
sonar* perplexity
solar* upstage

4.3.4 延迟导入与缓存

模型类的导入使用了两个关键的优化手段:

_import_module() ------ 延迟导入 + 友好错误 (来源:base.py):

python 复制代码
def _import_module(module: str, class_name: str) -> ModuleType:
    try:
        return importlib.import_module(module)
    except ImportError as e:
        pkg = module.split(".", maxsplit=1)[0].replace("_", "-")
        msg = (
            f"Initializing {class_name} requires the {pkg} package. "
            f"Please install it with `pip install {pkg}`"
        )
        raise ImportError(msg) from e

_get_chat_model_creator() ------ LRU 缓存 (来源:base.py):

python 复制代码
@functools.lru_cache(maxsize=len(_BUILTIN_PROVIDERS))
def _get_chat_model_creator(provider: str) -> Callable[..., BaseChatModel]:
    if provider not in _BUILTIN_PROVIDERS:
        supported = ", ".join(_BUILTIN_PROVIDERS.keys())
        msg = f"Unsupported {provider=}.\n\nSupported model providers are: {supported}"
        raise ValueError(msg)

    pkg, class_name, creator_func = _BUILTIN_PROVIDERS[provider]
    try:
        module = _import_module(pkg, class_name)
    except ImportError as e:
        if provider != "ollama":
            raise
        # Ollama 向后兼容:尝试从 langchain-community 导入
        try:
            module = _import_module("langchain_community.chat_models", class_name)
        except ImportError:
            raise e from None

    cls = getattr(module, class_name)
    return functools.partial(creator_func, cls=cls)

设计要点:

  1. @lru_cache:每个 Provider 只导入一次,后续调用直接从缓存返回
  2. 友好错误信息 :当 Partner 包未安装时,自动提示 pip install langchain-openai
  3. Ollama 向后兼容 :如果 langchain-ollama 不可用,回退到 langchain-community

4.3.5 完整调用流程

将以上组件串联起来,init_chat_model() 的完整执行流程:

复制代码
init_chat_model("openai:gpt-4o", temperature=0.7)
│
├─ 判断是否有 configurable_fields
│  └─ 无 → 调用 _init_chat_model_helper()
│
├─ _init_chat_model_helper("openai:gpt-4o", temperature=0.7)
│  │
│  ├─ _parse_model("openai:gpt-4o", None)
│  │  ├─ 检测到 ":" 且 "openai" 在 _BUILTIN_PROVIDERS 中
│  │  └─ 返回 ("gpt-4o", "openai")
│  │
│  ├─ _get_chat_model_creator("openai")
│  │  ├─ 从 _BUILTIN_PROVIDERS 获取 ("langchain_openai", "ChatOpenAI", _call)
│  │  ├─ importlib.import_module("langchain_openai")
│  │  ├─ getattr(module, "ChatOpenAI")
│  │  └─ 返回 partial(_call, cls=ChatOpenAI)  [缓存]
│  │
│  └─ creator_func(model="gpt-4o", temperature=0.7)
│     └─ ChatOpenAI(model="gpt-4o", temperature=0.7)
│
└─ 返回 ChatOpenAI 实例

4.3.6 init_chat_model() 函数签名

函数提供了三个重载(来源:base.py):

python 复制代码
# 重载 1:固定模型,返回 BaseChatModel
@overload
def init_chat_model(
    model: str, *, model_provider: str | None = None,
    configurable_fields: None = None, config_prefix: str | None = None,
    **kwargs: Any,
) -> BaseChatModel: ...

# 重载 2:无默认模型,返回可配置模型
@overload
def init_chat_model(
    model: None = None, *, model_provider: str | None = None,
    configurable_fields: None = None, config_prefix: str | None = None,
    **kwargs: Any,
) -> _ConfigurableModel: ...

# 重载 3:指定可配置字段,返回可配置模型
@overload
def init_chat_model(
    model: str | None = None, *,
    configurable_fields: Literal["any"] | list[str] | tuple[str, ...] = ...,
    config_prefix: str | None = None,
    **kwargs: Any,
) -> _ConfigurableModel: ...

三种使用模式:

python 复制代码
from langchain.chat_models import init_chat_model

# 模式 1:固定模型 → 返回 BaseChatModel
model = init_chat_model("openai:gpt-4o", temperature=0.7)

# 模式 2:无默认值的可配置模型 → 返回 _ConfigurableModel
model = init_chat_model()  # 必须在 invoke 时通过 config 指定模型

# 模式 3:带默认值的可配置模型 → 返回 _ConfigurableModel
model = init_chat_model(
    "openai:gpt-4o",
    configurable_fields=("model", "model_provider", "temperature"),
    config_prefix="main",
)

4.4 _ConfigurableModel ------ 运行时动态模型切换

_ConfigurableModelinit_chat_model() 在可配置模式下返回的包装类。它实现了一个精巧的"延迟实例化 + 操作队列"模式。

4.4.1 核心设计

类定义 (来源:base.py):

python 复制代码
class _ConfigurableModel(Runnable[LanguageModelInput, Any]):
    def __init__(
        self,
        *,
        default_config: dict[str, Any] | None = None,
        configurable_fields: Literal["any"] | list[str] | tuple[str, ...] = "any",
        config_prefix: str = "",
        queued_declarative_operations: Sequence[tuple[str, tuple[Any, ...], dict[str, Any]]] = (),
    ) -> None:
        self._default_config: dict[str, Any] = default_config or {}
        self._configurable_fields: Literal["any"] | list[str] = (
            "any" if configurable_fields == "any" else list(configurable_fields)
        )
        self._config_prefix = (
            config_prefix + "_"
            if config_prefix and not config_prefix.endswith("_")
            else config_prefix
        )
        self._queued_declarative_operations: list[...] = list(queued_declarative_operations)

四个核心属性:

属性 作用
_default_config 默认模型参数(model、temperature 等)
_configurable_fields 允许运行时配置的字段列表,或 "any" 表示全部
_config_prefix 配置键前缀,用于从 RunnableConfig 中筛选参数
_queued_declarative_operations 排队的声明式操作(bind_toolswith_structured_output

4.4.2 操作队列机制

_ConfigurableModel 的核心创新在于它的操作队列。当你在可配置模型上调用 bind_tools()with_structured_output() 时,这些操作不会立即执行,而是被记录到队列中:

python 复制代码
def __getattr__(self, name: str) -> Any:
    if name in _DECLARATIVE_METHODS:  # ("bind_tools", "with_structured_output")
        def queue(*args: Any, **kwargs: Any) -> _ConfigurableModel:
            queued_declarative_operations = list(self._queued_declarative_operations)
            queued_declarative_operations.append((name, args, kwargs))
            return _ConfigurableModel(
                default_config=dict(self._default_config),
                configurable_fields=...,
                queued_declarative_operations=queued_declarative_operations,
            )
        return queue

只有在实际调用 invoke()/stream() 时,队列中的操作才会被依次应用到真正的模型实例上:

python 复制代码
def _model(self, config: RunnableConfig | None = None) -> Runnable[Any, Any]:
    params = {**self._default_config, **self._model_params(config)}
    model = _init_chat_model_helper(**params)
    # 依次应用排队的操作
    for name, args, kwargs in self._queued_declarative_operations:
        model = getattr(model, name)(*args, **kwargs)
    return model

这种设计使得以下代码成为可能:

python 复制代码
from pydantic import BaseModel

class Answer(BaseModel):
    answer: str
    confidence: float

# 创建可配置模型并绑定工具 ------ 此时 bind_tools 被排队
model = init_chat_model(configurable_fields="any")
model_with_tools = model.bind_tools([Answer])

# 在运行时指定具体模型 ------ 此时 bind_tools 才真正执行
result = model_with_tools.invoke(
    "What's 2+2?",
    config={"configurable": {"model": "openai:gpt-4o"}}
)

# 切换模型 ------ bind_tools 在新模型上重新执行
result = model_with_tools.invoke(
    "What's 2+2?",
    config={"configurable": {"model": "anthropic:claude-sonnet-4-20250514"}}
)

4.4.3 配置参数提取

_model_params() 方法从 RunnableConfig 中提取模型参数:

python 复制代码
def _model_params(self, config: RunnableConfig | None) -> dict[str, Any]:
    config = ensure_config(config)
    # 从 config["configurable"] 中筛选匹配前缀的键
    model_params = {
        _remove_prefix(k, self._config_prefix): v
        for k, v in config.get("configurable", {}).items()
        if k.startswith(self._config_prefix)
    }
    # 如果不是 "any",则只保留允许的字段
    if self._configurable_fields != "any":
        model_params = {
            k: v for k, v in model_params.items()
            if k in self._configurable_fields
        }
    return model_params

带前缀的配置示例:

python 复制代码
model = init_chat_model(
    "openai:gpt-4o",
    configurable_fields=("model", "temperature"),
    config_prefix="primary",
)

# 运行时配置 ------ 注意键名带有 "primary_" 前缀
result = model.invoke(
    "Hello",
    config={
        "configurable": {
            "primary_model": "anthropic:claude-sonnet-4-20250514",
            "primary_temperature": 0.5,
        }
    }
)

4.4.4 安全警告

init_chat_model() 的文档中包含一个重要的安全提示:

设置 configurable_fields="any" 意味着 api_keybase_url 等敏感字段也可以在运行时被修改,可能将模型请求重定向到不同的服务或用户。如果接收不受信任的配置输入,请显式枚举 configurable_fields=("model", "temperature") 来限制可配置范围。


4.5 init_embeddings() ------ 嵌入模型的统一入口

新版 LangChain 还提供了 init_embeddings() 函数(来源:libs/langchain_v1/langchain/embeddings/base.py),与 init_chat_model() 形成对称的设计:

Embeddings Provider 注册表

python 复制代码
_BUILTIN_PROVIDERS: dict[str, tuple[str, str, Callable[..., Embeddings]]] = {
    "azure_ai": ("langchain_azure_ai.embeddings", "AzureAIOpenAIApiEmbeddingsModel", _call),
    "azure_openai": ("langchain_openai", "AzureOpenAIEmbeddings", _call),
    "bedrock": (
        "langchain_aws", "BedrockEmbeddings",
        lambda cls, model, **kwargs: cls(model_id=model, **kwargs),
    ),
    "cohere": ("langchain_cohere", "CohereEmbeddings", _call),
    "google_genai": ("langchain_google_genai", "GoogleGenerativeAIEmbeddings", _call),
    "google_vertexai": ("langchain_google_vertexai", "VertexAIEmbeddings", _call),
    "huggingface": (
        "langchain_huggingface", "HuggingFaceEmbeddings",
        lambda cls, model, **kwargs: cls(model_name=model, **kwargs),
    ),
    "mistralai": ("langchain_mistralai", "MistralAIEmbeddings", _call),
    "ollama": ("langchain_ollama", "OllamaEmbeddings", _call),
    "openai": ("langchain_openai", "OpenAIEmbeddings", _call),
}

使用方式:

python 复制代码
from langchain.embeddings import init_embeddings

embeddings = init_embeddings("openai:text-embedding-3-small")
embeddings = init_embeddings("ollama:nomic-embed-text")

init_chat_model() 的主要区别:

特性 init_chat_model() init_embeddings()
可配置模式 支持 _ConfigurableModel 不支持
模型名推断 支持(11 种前缀规则) 不支持(必须显式指定 Provider)
Provider 数量 26 个 10 个
返回类型 BaseChatModel_ConfigurableModel Embeddings

4.6 Model Profiles 系统

Model Profiles 是 LangChain 用于管理模型能力元数据的系统。它回答一个关键问题:"这个模型支持什么能力?"

4.6.1 ModelProfile 类型定义

ModelProfile TypedDict (来源:model_profile.py):

python 复制代码
class ModelProfile(TypedDict, total=False):
    """Model profile --- Beta feature."""
    __pydantic_config__ = ConfigDict(extra="allow")

    # --- 模型元数据 ---
    name: str                   # 可读模型名称
    status: str                 # active / deprecated
    release_date: str           # ISO 8601 发布日期
    last_updated: str           # ISO 8601 更新日期
    open_weights: bool          # 权重是否公开

    # --- 输入能力 ---
    max_input_tokens: int       # 最大上下文窗口
    text_inputs: bool           # 支持文本输入
    image_inputs: bool          # 支持图像输入
    image_url_inputs: bool      # 支持图像 URL 输入
    pdf_inputs: bool            # 支持 PDF 输入
    audio_inputs: bool          # 支持音频输入
    video_inputs: bool          # 支持视频输入
    image_tool_message: bool    # ToolMessage 中支持图像
    pdf_tool_message: bool      # ToolMessage 中支持 PDF

    # --- 输出能力 ---
    max_output_tokens: int      # 最大输出 Token 数
    reasoning_output: bool      # 支持推理/思维链
    text_outputs: bool          # 文本输出
    image_outputs: bool         # 图像输出
    audio_outputs: bool         # 音频输出
    video_outputs: bool         # 视频输出

    # --- 工具与结构化输出 ---
    tool_calling: bool          # 支持工具调用
    tool_choice: bool           # 支持工具选择
    structured_output: bool     # 支持原生结构化输出

    # --- 其他 ---
    attachment: bool            # 支持文件附件
    temperature: bool           # 支持温度参数

ModelProfileRegistry = dict[str, ModelProfile]

设计要点:

  • total=False:所有字段都是可选的,允许 Profile 只声明已知的能力
  • extra="allow":允许额外字段,支持向前兼容------新版本添加的字段不会导致旧版本出错
  • Beta 状态 :API 可能变化,通过 !!! warning "Beta feature" 明确标注

4.6.2 数据流:从 models.dev 到 _profiles.py

Model Profiles 的数据源自 models.dev 项目,通过 CLI 工具生成到各 Partner 包中:

复制代码
┌────────────────┐     HTTP GET        ┌──────────────────────┐
│  models.dev    │ ──────────────────► │ langchain-profiles   │
│  API (JSON)    │                     │ CLI 工具              │
└────────────────┘                     └──────────┬───────────┘
                                                  │
                                    ┌─────────────┼─────────────┐
                                    │             │             │
                              ┌─────▼─────┐ ┌────▼────┐  ┌────▼─────┐
                              │ 数据转换   │ │ 加载    │  │ 应用     │
                              │ models.dev │ │ TOML   │  │ overrides│
                              │ → Profile  │ │ 增强    │  │ 合并     │
                              └─────┬─────┘ └────┬────┘  └────┬─────┘
                                    └─────────────┼────────────┘
                                                  │
                                           ┌──────▼──────┐
                                           │ 生成         │
                                           │ _profiles.py │
                                           │ (原子写入)    │
                                           └──────┬──────┘
                                                  │
                              ┌────────────────────┼────────────────────┐
                              │                    │                    │
                     ┌────────▼────────┐  ┌───────▼────────┐  ┌───────▼────────┐
                     │ openai/data/    │  │ anthropic/data/│  │ deepseek/data/ │
                     │ _profiles.py    │  │ _profiles.py   │  │ _profiles.py   │
                     └─────────────────┘  └────────────────┘  └────────────────┘

CLI 工具libs/model-profiles/):

bash 复制代码
# 刷新 OpenAI 模型配置
cd libs/model-profiles
uv run langchain-profiles refresh --provider openai \
    --data-dir ../partners/openai/langchain_openai/data

# 刷新外部仓库的 Partner 包(需要确认)
echo y | uv run langchain-profiles refresh --provider google \
    --data-dir /path/to/langchain-google/libs/genai/langchain_google_genai/data

4.6.3 profile_augmentations.toml ------ 本地增强

models.dev 的数据不一定完全覆盖 LangChain 需要的所有能力声明。profile_augmentations.toml 允许在 Provider 级别和模型级别进行覆盖:

OpenAI 的增强配置

toml 复制代码
provider = "openai"

[overrides]
# Provider 级别 ------ 应用于所有 OpenAI 模型
image_url_inputs = true
pdf_inputs = true
pdf_tool_message = true
image_tool_message = true
tool_choice = true

# 模型级别 ------ 覆盖特定模型
[overrides."gpt-3.5-turbo"]
image_url_inputs = false    # GPT-3.5 不支持图像
pdf_inputs = false
pdf_tool_message = false
image_tool_message = false

Anthropic 的增强配置

toml 复制代码
provider = "anthropic"

[overrides]
image_url_inputs = true
pdf_inputs = true
pdf_tool_message = true
image_tool_message = true
structured_output = false     # Anthropic 默认不支持原生结构化输出

[overrides."claude-sonnet-4-5"]
structured_output = true      # 但 Claude Sonnet 4.5 支持

[overrides."claude-opus-4-1"]
structured_output = true      # Claude Opus 4.1 也支持

4.6.4 生成的 _profiles.py

生成的文件是一个纯 Python 字典,包含所有模型的能力数据:

python 复制代码
"""Auto-generated model profiles.

DO NOT EDIT THIS FILE MANUALLY.
This file is generated by the langchain-profiles CLI tool.
Source: https://github.com/sst/models.dev
License: MIT License
"""

from typing import Any

_PROFILES: dict[str, dict[str, Any]] = {
    "gpt-4o-mini": {
        "name": "GPT-4o mini",
        "release_date": "2024-07-18",
        "open_weights": False,
        "max_input_tokens": 128000,
        "max_output_tokens": 16384,
        "text_inputs": True,
        "image_inputs": True,
        "audio_inputs": False,
        "pdf_inputs": True,
        "video_inputs": False,
        "text_outputs": True,
        "image_outputs": False,
        "reasoning_output": False,
        "tool_calling": True,
        "structured_output": True,
        "image_url_inputs": True,
        "pdf_tool_message": True,
        "image_tool_message": True,
        "tool_choice": True,
        "temperature": True,
    },
    "gpt-4o": { ... },
    "o1": { ... },
    # ... 更多模型
}

4.6.5 Profile 在运行时的使用

Partner 包在运行时通过 _resolve_model_profile() 方法加载 Profile:

python 复制代码
# Partner 包内部实现(以 OpenAI 为例)
from langchain_core.language_models import ModelProfile, ModelProfileRegistry
from langchain_openai.data._profiles import _PROFILES

_MODEL_PROFILES = cast(ModelProfileRegistry, _PROFILES)

class ChatOpenAI(BaseChatModel):
    profile: ModelProfile | None = Field(default=None, exclude=True)

    def _resolve_model_profile(self) -> ModelProfile | None:
        if self.model:
            return _MODEL_PROFILES.get(self.model, {}).copy() or None
        return None

用户可以通过 profile 属性查询模型能力:

python 复制代码
model = init_chat_model("openai:gpt-4o")
profile = model.profile

if profile and profile.get("tool_calling"):
    print("此模型支持工具调用")

if profile and profile.get("image_inputs"):
    print(f"此模型支持图像输入,上下文窗口: {profile.get('max_input_tokens')} tokens")

4.6.6 未知键警告

langchain-core 提供了版本兼容性检查(来源:model_profile.py):

python 复制代码
def _warn_unknown_profile_keys(profile: ModelProfile) -> None:
    """Warn if profile contains keys not declared on ModelProfile."""
    declared = frozenset(get_type_hints(ModelProfile).keys())
    extra = sorted(set(profile) - declared)
    if extra:
        warnings.warn(
            f"Unrecognized keys in model profile: {extra}. "
            f"This may indicate a version mismatch between langchain-core "
            f"and your provider package. Consider upgrading langchain-core.",
            stacklevel=2,
        )

当 Partner 包的 _profiles.py 包含 langchain-core 中未声明的键时(通常是因为 Partner 包版本更新但 core 未更新),会发出友好的版本不匹配警告。


4.7 标准化测试框架

LangChain 通过 libs/standard-tests/(包名 langchain-tests)提供了一套标准化测试框架,确保所有 Partner 包遵循一致的接口和行为。

4.7.1 测试类层次

复制代码
BaseStandardTests                          # 基类:test_no_overrides_DO_NOT_OVERRIDE
│
├── ChatModelTests                         # 聊天模型公共属性
│   ├── ChatModelUnitTests                 # 单元测试(无网络)
│   │   ├── test_init                      # 初始化测试
│   │   ├── test_init_from_env             # 环境变量初始化
│   │   ├── test_init_streaming            # 流式初始化
│   │   ├── test_bind_tool_pydantic        # 工具绑定
│   │   ├── test_with_structured_output    # 结构化输出
│   │   ├── test_standard_params           # 标准参数
│   │   ├── test_serdes                    # 序列化/反序列化
│   │   └── test_init_time                 # 初始化性能基准
│   │
│   └── ChatModelIntegrationTests          # 集成测试(需要 API)
│       ├── test_invoke / test_ainvoke     # 基础调用
│       ├── test_stream / test_astream     # 流式调用
│       ├── test_batch / test_abatch       # 批处理
│       ├── test_conversation              # 多轮对话
│       ├── test_usage_metadata            # Token 使用统计
│       ├── test_tool_calling              # 工具调用
│       ├── test_tool_choice               # 工具选择
│       ├── test_structured_few_shot       # 结构化 few-shot
│       └── ... (更多)
│
├── EmbeddingsTests                        # Embedding 公共属性
│   ├── EmbeddingsUnitTests
│   └── EmbeddingsIntegrationTests
│
└── (VectorStore, Retriever, Tools 等测试)

4.7.2 test_no_overrides_DO_NOT_OVERRIDE ------ 测试完整性守卫

这是标准测试框架中最有特色的设计。它防止 Partner 包开发者意外删除或覆盖标准测试:

python 复制代码
class BaseStandardTests:
    def test_no_overrides_DO_NOT_OVERRIDE(self) -> None:
        """检查标准测试的完整性:
        1. 没有删除任何标准测试
        2. 所有被重写的测试都必须标记 @pytest.mark.xfail(reason="...")
        3. xfail 标记必须包含说明失败原因的字符串
        """
        comparison_class = ...  # 找到标准测试基类
        running_tests = ...     # 当前类的所有测试方法
        base_tests = ...        # 基类的所有测试方法
        deleted_tests = base_tests - running_tests
        overridden_not_xfail = ...  # 被重写但未标记 xfail 的测试

        assert not deleted_tests, f"Standard tests deleted: {deleted_tests}"
        assert not overridden_not_xfail, f"Overridden without xfail: ..."

如果一个 Partner 包确实无法通过某个标准测试(比如不支持工具调用),必须显式标记:

python 复制代码
class TestMyModelStandard(ChatModelIntegrationTests):
    @pytest.mark.xfail(reason="MyModel does not support tool calling yet")
    def test_tool_calling(self):
        super().test_tool_calling()

4.7.3 功能特性属性

ChatModelTests 基类通过一系列属性自动检测模型能力:

python 复制代码
class ChatModelTests:
    @property
    @abstractmethod
    def chat_model_class(self) -> type[BaseChatModel]:
        """必须指定被测试的模型类"""
        ...

    @property
    def has_tool_calling(self) -> bool:
        """自动检测:是否重写了 bind_tools()"""
        return self.chat_model_class.bind_tools is not BaseChatModel.bind_tools

    @property
    def has_tool_choice(self) -> bool:
        """自动检测:bind_tools() 签名中是否有 tool_choice 参数"""
        params = inspect.signature(self.chat_model_class.bind_tools).parameters
        return "tool_choice" in params

    @property
    def has_structured_output(self) -> bool:
        """自动检测:是否重写了 with_structured_output()"""
        return (
            self.chat_model_class.with_structured_output
            is not BaseChatModel.with_structured_output
        ) or self.has_tool_calling

    @property
    def supports_image_inputs(self) -> bool:
        """需要手动声明"""
        return False

    @property
    def supports_pdf_inputs(self) -> bool:
        """需要手动声明"""
        return False

自动检测 vs 手动声明:

  • 自动检测has_tool_callinghas_structured_output ------ 通过反射检查方法是否被重写
  • 手动声明supports_image_inputssupports_pdf_inputs ------ 需要 Partner 开发者显式设置

4.7.4 Partner 包如何使用标准测试

以 OpenAI 为例:

单元测试

python 复制代码
# libs/partners/openai/tests/unit_tests/chat_models/test_base_standard.py
from langchain_tests.unit_tests import ChatModelUnitTests
from langchain_openai import ChatOpenAI

class TestOpenAIStandard(ChatModelUnitTests):
    @property
    def chat_model_class(self):
        return ChatOpenAI

    @property
    def init_from_env_params(self):
        return (
            {   # 环境变量
                "OPENAI_API_KEY": "api_key",
                "OPENAI_ORG_ID": "org_id",
                "OPENAI_API_BASE": "api_base",
            },
            {},  # 初始化参数
            {   # 期望属性值
                "openai_api_key": "api_key",
                "openai_organization": "org_id",
                "openai_api_base": "api_base",
            },
        )

集成测试

python 复制代码
# libs/partners/openai/tests/integration_tests/chat_models/test_base_standard.py
from langchain_tests.integration_tests import ChatModelIntegrationTests
from langchain_openai import ChatOpenAI

class TestOpenAIStandard(ChatModelIntegrationTests):
    @property
    def chat_model_class(self):
        return ChatOpenAI

    @property
    def chat_model_params(self):
        return {"model": "gpt-4o-mini"}

    @property
    def supports_image_inputs(self):
        return True

    @property
    def supports_image_urls(self):
        return True

    @property
    def supports_json_mode(self):
        return True

    @property
    def supports_pdf_inputs(self):
        return True

    @property
    def enable_vcr_tests(self):
        return True

4.7.5 VCR 录制/回放

标准测试框架集成了 VCR(Video Cassette Recorder)机制,用于录制和回放 HTTP 交互:

python 复制代码
# langchain_tests/conftest.py
def base_vcr_config() -> dict[str, Any]:
    return {
        "record_mode": "once",             # 第一次录制,后续回放
        "filter_headers": [
            ("authorization", "PLACEHOLDER"),  # 自动脱敏 API Key
            ("x-api-key", "PLACEHOLDER"),
            ("api-key", "PLACEHOLDER"),
        ],
        "match_on": ["method", "uri", "body"],
        "allow_playback_repeats": True,
        "decode_compressed_response": True,
        "cassette_library_dir": "tests/cassettes",
    }

VCR 的录制文件使用 gzip 压缩的 YAML 格式存储在 tests/cassettes/ 目录下,确保 CI 中无需实际 API 调用即可运行集成测试。


4.8 依赖关系全景

将以上所有组件串联起来,LangChain 模型集成生态的依赖关系如下:

复制代码
┌─────────────────────────────────────────────────────────────┐
│                     用户应用                                 │
│   from langchain.chat_models import init_chat_model          │
│   model = init_chat_model("openai:gpt-4o")                  │
└───────────────────────────┬─────────────────────────────────┘
                            │
              ┌─────────────▼─────────────┐
              │   langchain v1.2.15        │
              │   init_chat_model()        │
              │   _BUILTIN_PROVIDERS       │
              │   _ConfigurableModel       │
              └─────────────┬─────────────┘
                            │
            ┌───────────────┼───────────────┐
            │               │               │
  ┌─────────▼──────┐ ┌─────▼──────┐ ┌─────▼──────────┐
  │ langchain-core │ │ langgraph  │ │ Partner 包      │
  │ v1.2.27        │ │ v1.1.5     │ │ (按需安装)      │
  │                │ │            │ │                  │
  │ BaseChatModel  │ │ StateGraph │ │ langchain-openai │
  │ ModelProfile   │ │ ...        │ │ langchain-anthro │
  │ Runnable       │ │            │ │ langchain-ollama │
  └────────────────┘ └────────────┘ │ ...              │
                                    └──────┬───────────┘
                                           │
                                    ┌──────▼───────────┐
                                    │ 第三方 SDK        │
                                    │ openai>=2.26.0    │
                                    │ anthropic>=0.85.0 │
                                    │ ollama>=0.6.1     │
                                    │ ...               │
                                    └──────────────────┘

  ┌──────────────────┐            ┌─────────────────────┐
  │ model-profiles   │────生成───►│ Partner 包/data/     │
  │ CLI 工具 v0.0.5   │            │ _profiles.py        │
  └──────────────────┘            │ profile_augments.toml│
         │                        └─────────────────────┘
         │ HTTP GET
  ┌──────▼───────────┐
  │ models.dev API   │
  └──────────────────┘

  ┌──────────────────┐            ┌─────────────────────┐
  │ langchain-tests  │────继承───►│ Partner 包/tests/    │
  │ v1.1.5           │            │ test_base_standard   │
  └──────────────────┘            └─────────────────────┘

4.9 本章小结

主题 关键内容
Partner 包架构 15 个官方包,统一的 hatchling 构建、pyproject.toml 规范、py.typed 类型标记
代码复用策略 三种模式:完全独立实现(OpenAI/Anthropic)、继承复用(DeepSeek/xAI)、SDK 封装(Ollama/Groq)
init_chat_model() 26 个 Provider 注册表,三级解析(显式前缀 → 名称推断 → 显式指定),LRU 缓存 + 延迟导入
_ConfigurableModel 操作队列模式------bind_tools/with_structured_output 排队延迟执行,运行时动态切换模型
init_embeddings() 10 个 Provider,与 init_chat_model() 对称设计,但不支持可配置模式
Model Profiles ModelProfile TypedDict 定义 25 个能力字段,CLI 从 models.dev 生成 _profiles.py,本地 TOML 增强
标准化测试 ChatModelUnitTests/ChatModelIntegrationTeststest_no_overrides_DO_NOT_OVERRIDE 守卫,VCR 录制/回放

LangChain 的模型集成生态体现了"标准化 + 可扩展"的设计哲学:通过严格的标准化(统一的包结构、依赖规范、测试框架)降低了维护 15+ 个集成包的成本,同时通过灵活的扩展点(_BUILTIN_PROVIDERS 注册表、profile_augmentations.toml 本地增强、三种代码复用策略)保证了足够的灵活性。


下一章预告第五章:工具系统与函数调用 ------ 从定义到执行的完整链路 将深入探讨 LangChain 的工具系统,包括四种工具定义方式、Tool Calling 的完整流程、ToolNode 的执行机制,以及如何将 Runnable 转换为工具。

相关推荐
云烟成雨TD2 小时前
Spring AI Alibaba 1.x 系列【13】 检查点 (Checkpoint) 机制及各类持久化实现
java·人工智能·spring
AINative软件工程2 小时前
我给自己的MCP Server做了一次渗透测试,结果吓出一身冷汗
人工智能
水如烟2 小时前
孤能子视角:创新–幻觉“三线模型“,豆包的“飞“
人工智能
火山引擎开发者社区2 小时前
ArkClaw 养虾省钱攻略,这 10% 的返利你还不知道?
人工智能
跨境卫士苏苏2 小时前
跨境电商成本持续上升卖家利润空间如何守住
大数据·人工智能·跨境电商·亚马逊·跨境
IT大师兄吖2 小时前
SAM3 提示词 视频分割 ComfyUI 懒人整合包
人工智能
AI、少年郎2 小时前
MiniMind第 3 篇:底层原理|Decoder-Only 小模型核心:RMSNorm/SwiGLU/RoPE 极简吃透
人工智能·ai编程·大模型训练·大模型微调·大模型原理
雾喔2 小时前
【学习笔记3】AI 工程实战
人工智能·笔记·学习
火山引擎开发者社区2 小时前
玩转 ArkClaw:用自动修复打造稳定可靠的 AI 助理
人工智能