系列:深入 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-vertexai、langchain-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 |
| 类型检查 | mypy,disallow_untyped_defs = True |
| 代码规范 | ruff,select = ["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
解析遵循三级策略:
- 显式前缀 :
"openai:gpt-4o"→provider="openai",model="gpt-4o" - 模型名推断 :
"gpt-4o"→ 通过前缀匹配推断为"openai" - 显式指定 :
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)
设计要点:
@lru_cache:每个 Provider 只导入一次,后续调用直接从缓存返回- 友好错误信息 :当 Partner 包未安装时,自动提示
pip install langchain-openai - 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 ------ 运行时动态模型切换
_ConfigurableModel 是 init_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_tools、with_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_key、base_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_calling、has_structured_output------ 通过反射检查方法是否被重写 - 手动声明 :
supports_image_inputs、supports_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/ChatModelIntegrationTests,test_no_overrides_DO_NOT_OVERRIDE 守卫,VCR 录制/回放 |
LangChain 的模型集成生态体现了"标准化 + 可扩展"的设计哲学:通过严格的标准化(统一的包结构、依赖规范、测试框架)降低了维护 15+ 个集成包的成本,同时通过灵活的扩展点(_BUILTIN_PROVIDERS 注册表、profile_augmentations.toml 本地增强、三种代码复用策略)保证了足够的灵活性。
下一章预告 :第五章:工具系统与函数调用 ------ 从定义到执行的完整链路 将深入探讨 LangChain 的工具系统,包括四种工具定义方式、Tool Calling 的完整流程、
ToolNode的执行机制,以及如何将 Runnable 转换为工具。