Skip to content

LangChain v1 包分层与运行时模型切换 ​

本文基于 2026 年 LangChain v1 架构整理,覆盖:包分层与依赖关系、各包常用子包、init_chat_model 自适应路由、thread_id 与会话状态、运行时切换模型 / base_url / api_key 的完整机制与坑位。


目录 ​


一、整体架构:为什么 langchain 与 langchain-core 平级 ​

LangChain v1 把代码库拆成了若干独立发版的 Python 包。langchain 与 langchain-core 在 site-packages 下是两个平级目录,不是历史遗留,而是刻意的分层设计。

1.1 分层图 ​

mermaid
flowchart TD
    App["你的业务代码"]
    LC["langchain<br/>实现层:agents / chains / retrieval / middleware"]
    CORE["langchain-core<br/>抽象层:BaseChatModel / BaseTool / BaseMessage / Runnable / LCEL"]
    COMM["langchain-community<br/>第三方集成"]
    PARTNER["langchain-openai / langchain-anthropic / ...<br/>官方独立集成包"]
    LG["langgraph<br/>图运行时:checkpointer / Command / interrupt"]

    App --> LC
    App --> PARTNER
    App --> LG
    LC --> CORE
    COMM --> CORE
    PARTNER --> CORE
    LC --> LG

1.2 拆包的三个动机 ​

动机说明
版本解耦langchain-core 只放稳定的抽象接口,几乎不依赖第三方 SDK;langchain 放会频繁变动的编排逻辑。两者独立发版,避免改了 chain 就逼着所有集成包跟着升版本。
集成包轻量langchain-openai、langchain-anthropic 等只依赖 langchain-core,不依赖 langchain。只想用一个 ChatModel 做单次调用时,不会把整套 agent/chain 编排代码拖进来。
契约统一所有"模型 / 工具 / 消息"在 core 里都是同一套基类,换厂商不需要改业务代码。

1.3 安装与 import 的关系 ​

bash
pip install langchain           # 自动带上 langchain-core
pip install langchain-openai    # 单独装一个官方集成
  • langchain-core 是所有包的公共底座。
  • langchain 是默认推荐的"全家桶入口"。
  • 集成包按需安装,不装就不会在运行时被 import。

二、各包常用子包清单 ​

2.1 langchain-core:写自定义组件时碰这一层 ​

子包用途典型导出
langchain_core.messages消息抽象HumanMessage、AIMessage、SystemMessage、ToolMessage
langchain_core.language_models模型基类BaseChatModel、BaseLLM
langchain_core.tools工具定义BaseTool、@tool 装饰器、ToolException
langchain_core.prompts提示词模板ChatPromptTemplate、MessagesPlaceholder
langchain_core.output_parsers输出解析StrOutputParser、PydanticOutputParser
langchain_core.runnablesLCEL 执行协议Runnable、RunnableLambda、RunnableParallel
langchain_core.documents检索文档Document
langchain_core.retrievers检索器抽象BaseRetriever

注意:@tool 装饰器的真身在 langchain_core.tools。from langchain.tools import tool 是 v1 提供的便捷 re-export,两者等价。

2.2 langchain:把 core 零件拼起来的高层 API ​

子包用途典型导出
langchain.agentsAgent 组装(v1 新入口)create_agent(取代旧的 langgraph.prebuilt.create_react_agent)
langchain.agents.middlewareAgent 中间件HumanInTheLoopMiddleware、SummarizationMiddleware、ToolCallingMiddleware
langchain.agents.structured_output结构化输出策略ToolStrategy
langchain.messages消息类型 re-export等价于 langchain_core.messages
langchain.tools工具工具集 re-export@tool、内置搜索 / 计算器
langchain.chainsLCEL 链(legacy 兼容)RetrievalQA 等老代码
langchain.retrievers检索器具体实现多路召回、重排包装器
langchain.chat_models模型工厂init_chat_model

2.3 独立集成包(按厂商) ​

包用途
langchain-openaiOpenAI、Azure OpenAI、所有 OpenAI 兼容网关
langchain-anthropicClaude 系列
langchain-google-genai / langchain-google-vertexaiGemini
langchain-community大量第三方集成(社区维护)

2.4 langgraph:图运行时 ​

子包用途
langgraph.checkpoint.memoryInMemorySaver(本地调试)
langgraph.checkpoint.postgresPostgresSaver(生产持久化)
langgraph.typesCommand、interrupt
langgraph.prebuilt旧版 create_react_agent(兼容用)

三、init_chat_model:自适应模型工厂 ​

init_chat_model 位于 langchain.chat_models,是 v1 推荐的统一模型入口。

3.1 它不是什么 ​

  • 它不是一个新的模型抽象层。
  • 它不是优先选 OpenAI。
  • 它不是绕过集成包。

3.2 它是什么 ​

一个按字符串路由到具体集成类的工厂。返回值类型完全由字符串决定。

python
from langchain.chat_models import init_chat_model

# 显式 provider:model —— 最稳,推荐
init_chat_model("openai:gpt-5.5")         # -> langchain_openai.ChatOpenAI 实例
init_chat_model("anthropic:claude-...")    # -> langchain_anthropic.ChatAnthropic 实例
init_chat_model("google_genai:gemini-...")# -> langchain_google_genai.ChatGoogleGenerativeAI
init_chat_model("bedrock:anthropic.claude-...")
init_chat_model("openrouter:auto")

3.3 省略 provider 时的启发式猜测 ​

模型名前缀猜测 provider
gpt-*、o1、o3、o4openai
claude-*anthropic
gemini*google_genai
command-*cohere
amazon.*bedrock
mistral*mistralai
python
init_chat_model("gpt-4o-mini")    # 猜成 openai
init_chat_model("claude-3-5-...") # 猜成 anthropic

猜测失败或歧义时,用 model_provider= 显式覆盖:

python
init_chat_model("deepseek-chat", model_provider="deepseek")

3.4 关键事实 ​

  • 返回值就是对应集成类的实例。init_chat_model("openai:gpt-5.5") 等价于 ChatOpenAI(model="gpt-5.5"),对象类型完全一样。
  • 懒加载、不自动装包。字符串解析到哪个 provider,运行时才 import 那个集成包;没装就 ImportError,提示你 pip install langchain-openai。
  • 支持任意初始化参数透传:temperature、max_tokens、base_url、api_key、model_kwargs 等。

3.5 什么时候用它 vs 直接 new ChatOpenAI ​

场景推荐
写死用 OpenAI,要 IDE 补全、类型提示直接 from langchain_openai import ChatOpenAI
模型名来自 yaml / 环境变量 / 数据库init_chat_model(os.environ["MODEL"])
需要在 config 里运行时切 providerinit_chat_model(..., configurable_fields=[...])
内部封装公司模型路由服务init_chat_model("our:xxx", model_provider="our")

四、运行时切换模型 ​

4.1 前提条件(三件套,缺一不可) ​

python
model = init_chat_model(
    "openai:gpt-4o-mini",                              # ① 用 init_chat_model 构造
    configurable_fields=["model", "model_provider"],    # ② 注册要覆盖的字段
    config_prefix="llm",                               # ③ 给这组字段起前缀
)

4.2 写法对比 ​

python
# ❌ 写法 A:写死模型 —— config 里塞什么都不会换
agent = create_agent(
    model=ChatOpenAI(model="gpt-4o-mini"),
    tools=[...],
    checkpointer=InMemorySaver(),
)

# ✅ 写法 B:可配置模型 —— invoke 时在 config 里覆盖
agent = create_agent(
    model=init_chat_model(
        "openai:gpt-4o-mini",
        configurable_fields=["model", "model_provider", "temperature"],
        config_prefix="llm",
    ),
    tools=[...],
    checkpointer=InMemorySaver(),
)

agent.invoke(
    req,
    config={"configurable": {
        "thread_id": "user-1",
        "llm.model": "claude-sonnet-4-6",        # 这一轮换 Claude
        "llm.model_provider": "anthropic",
    }},
)

4.3 本质 ​

agent.invoke 本身不神奇。是底层 Runnable 在被构造时,通过 config_prefix 给自己注册了一组"可被 config 覆盖的槽位"。invoke 时 LangGraph 把 config["configurable"] 里的同名值填进这些槽位,再实例化真正的模型对象。


五、thread_id:会话状态的真正主键 ​

5.1 核心概念 ​

模型是无状态的。 它每一轮只做一件事:接收一组 messages,吐出一个 response,自己不记得任何事。

会话记忆完全由 checkpointer 提供。

┌─────────────────────────────────────────────────────┐
│  checkpointer(InMemorySaver / PostgresSaver)       │
│                                                     │
│  key: thread_id = "user-123"                       │
│  └─ State {                                         │
│       messages: [Human(...), AI(...), Tool(...)]     │
│       interrupt: None / {pending tool call}          │
│       ...middleware 状态                              │
│     }                                               │
└──────────────────┬──────────────────────────────────┘
                   │ 每次 invoke 时把 State 读出来
                   ▼
┌─────────────────────────────────────────────────────┐
│  Model(无状态推理单元)                              │
│  这一轮用 gpt-4o-mini,下一轮换成 claude,           │
│  它只是"读 messages 历史 -> 生成下一条"               │
└─────────────────────────────────────────────────────┘

5.2 一句话比喻 ​

thread_id 是"工单",model 是"接电话的人"。 工单跟着 thread_id 走,接电话的人可以随时换,历史不丢。

5.3 同 thread_id 内换模型的完整例子 ​

python
agent = create_agent(
    model=init_chat_model(
        "openai:gpt-4o-mini",
        configurable_fields=["model", "model_provider"],
        config_prefix="llm",
    ),
    tools=[search_tool],
    checkpointer=InMemorySaver(),
)

cfg = {"configurable": {"thread_id": "user-7"}}

# 第 1 轮:默认模型
agent.invoke({"messages": [HumanMessage("我叫小明,住在上海")]}, config=cfg)

# 第 2 轮:同一个 thread_id,中途换 Claude
agent.invoke(
    {"messages": [HumanMessage("我叫什么?住哪?")]},
    config={"configurable": {
        "thread_id": "user-7",                    # ← 同一个会话
        "llm.model": "claude-sonnet-4-6",         # ← 换大脑
        "llm.model_provider": "anthropic",
    }},
)
# Claude 能正确答"小明 / 上海",因为 messages 历史从 checkpointer 取出

5.4 多用户隔离 ​

  • 多用户隔离 = 多 thread_id,不是多模型。
  • 每个用户一个 thread_id = f"user_{uid}"。
  • 模型可以全局共享,也可以按用户在 config 里覆盖。

六、运行时切换 base_url 与 api_key ​

6.1 可以,且是 init_chat_model 最值钱的场景 ​

configurable_fields 不仅能注册 model / model_provider,也能注册 base_url 和 api_key。

python
model = init_chat_model(
    "openai:gpt-4o-mini",
    configurable_fields=[
        "model", "model_provider",
        "base_url", "api_key", "temperature",
    ],
    config_prefix="llm",
)

agent = create_agent(model=model, tools=[...], checkpointer=InMemorySaver())

# 同一次会话里,临时切到自建中转
agent.invoke(
    {"messages": [HumanMessage("hi")]},
    config={"configurable": {
        "thread_id": "user-1",
        "llm.base_url": "https://one-api.your.com/v1",
        "llm.api_key":  os.environ["RELAY_KEY"],
        "llm.model":    "deepseek-chat",
        # model_provider 保持 openai,因为 one-api 走 OpenAI 兼容协议
    }},
)

6.2 字段名的两个坑 ​

坑说明
统一别名 vs 底层真名ChatOpenAI 内部字段叫 openai_api_key / openai_api_base,但带 alias 叫 api_key / base_url。在 init_chat_model 这一层统一用 api_key / base_url 即可,它负责翻译。
跨 provider 切时语义跟着走切到 model_provider="anthropic",你塞的 api_key 必须是 Anthropic 的 key,base_url 要指向 Anthropic 兼容端点;不能把 OpenAI 中转的 key 塞进 Anthropic provider 还用。

6.3 实战推荐姿势 ​

方案 A:所有端点都是 OpenAI 兼容协议(推荐) ​

one-api、new-api、DeepSeek、Moonshot、本地 vLLM、Ollama 的 /v1 端口、Xinference,全部兼容 OpenAI 协议。

python
model = init_chat_model(
    "gpt-4o-mini",
    model_provider="openai",
    configurable_fields=["model", "base_url", "api_key"],
    config_prefix="llm",
)
# model_provider 永远是 "openai",不动
# 切流量 / 故障转移 / 灰度到本地模型,只改三个字符串

方案 B:真要跨厂商(OpenAI ↔ Anthropic ↔ Gemini) ​

四个一起切,否则会出现"model 换成 claude 但 key 还是 OpenAI 的"这种 401:

python
"llm.model_provider": "anthropic",
"llm.model":          "claude-sonnet-4-6",
"llm.api_key":        os.environ["ANTHROPIC_API_KEY"],
"llm.base_url":       None,   # 用官方默认,或指向你的 Anthropic 代理

6.4 优先级与安全 ​

运行时 config 传值
    > 构造 init_chat_model(api_key=...) 传值
        > OPENAI_API_KEY 环境变量
  • config 里的 api_key 只在当次 invoke 生效,不持久化到 checkpointer。下一轮不传就回退到默认值或环境变量。
  • 不要硬编码密钥。通过 config 注入也要走环境变量 / Secret Manager。
  • LangChain debug log 可能把 configurable 字段打出来,生产环境注意日志级别。

七、优雅换模型:预设配置模式 ​

7.1 核心思想 ​

业务调用点只写 thread_id 和"预设名",所有 base_url / api_key / model 外置。

config = {"configurable": {"thread_id": "1"}} 这种裸写法一旦要切模型,每次 invoke 都得手写一串 llm.model / llm.base_url / llm.api_key,散落全代码库。优雅的姿势是把"换模型"收敛成"换一个配置名"。

7.2 方案 1:预设字典 + 一行构造器(最常用) ​

python
import os
from langchain_core.messages import HumanMessage

# ---------- 预设:模型路由表 ----------
PRESETS: dict[str, dict] = {
    "official": {
        "llm.model":          "gpt-4o-mini",
        "llm.model_provider": "openai",
        # base_url / api_key 留空 -> 走默认与 OPENAI_API_KEY
    },
    "relay": {
        "llm.model":          "deepseek-chat",
        "llm.model_provider": "openai",          # 走 OpenAI 兼容协议
        "llm.base_url":       os.environ["RELAY_BASE_URL"],
        "llm.api_key":        os.environ["RELAY_API_KEY"],
    },
    "local-ollama": {
        "llm.model":          "qwen2.5:7b",
        "llm.model_provider": "openai",          # Ollama 也兼容 OpenAI 协议
        "llm.base_url":       "http://localhost:11434/v1",
        "llm.api_key":        "ollama",
    },
    "claude": {
        "llm.model":          "claude-sonnet-4-6",
        "llm.model_provider": "anthropic",
        "llm.api_key":        os.environ["ANTHROPIC_API_KEY"],
    },
}

def cfg(thread_id: str, preset: str = "official") -> dict:
    """一行拼出 config:thread_id + 预设字段合并"""
    return {"configurable": {"thread_id": thread_id, **PRESETS[preset]}}


# ---------- 业务调用点:干净到只有两行 ----------
agent.invoke(
    {"messages": [HumanMessage("你好")]},
    config=cfg("1", preset="relay"),     # 换预设 = 换模型
)

# 同一会话下一轮想切回官方:
agent.invoke(
    {"messages": [HumanMessage("继续")]},
    config=cfg("1", preset="official"),   # thread_id 不变,历史连续
)

7.3 方案 2:dataclass 化(类型安全、IDE 补全) ​

预设多了以后,字符串 key 容易拼错。用 dataclass 包一层:

python
from dataclasses import dataclass, replace

@dataclass(frozen=True)
class ModelPreset:
    model:          str
    model_provider: str = "openai"
    base_url:       str | None = None
    api_key:        str | None = None

PRESETS = {
    "official": ModelPreset(model="gpt-4o-mini"),
    "relay":    ModelPreset(
                    model="deepseek-chat",
                    base_url=os.environ["RELAY_BASE_URL"],
                    api_key=os.environ["RELAY_API_KEY"],
                ),
    "local":    ModelPreset(
                    model="qwen2.5:7b",
                    base_url="http://localhost:11434/v1",
                    api_key="ollama",
                ),
}

def cfg(thread_id: str, preset: str = "official") -> dict:
    p = PRESETS[preset]
    c: dict = {"thread_id": thread_id}
    if p.model:          c["llm.model"]          = p.model
    if p.model_provider: c["llm.model_provider"] = p.model_provider
    if p.base_url:       c["llm.base_url"]        = p.base_url
    if p.api_key:        c["llm.api_key"]         = p.api_key
    return {"configurable": c}

好处:预设可以用 replace(PRESETS["relay"], model="deepseek-v3") 临时派生,不用复制字典。

7.4 方案 3:预设外置到 yaml(生产姿势) ​

预设一多就别放代码里,丢到 config/models.yaml:

yaml
# config/models.yaml
official:
  model: gpt-4o-mini
  model_provider: openai

relay:
  model: deepseek-chat
  model_provider: openai
  base_url: ${RELAY_BASE_URL}      # 启动时从 .env 插值
  api_key:  ${RELAY_API_KEY}

local:
  model: qwen2.5:7b
  model_provider: openai
  base_url: http://localhost:11434/v1
  api_key: ollama

加载器:

python
import os, yaml
from string import Template

def load_presets(path: str = "config/models.yaml") -> dict:
    raw = Template(open(path).read()).safe_substitute(os.environ)
    data = yaml.safe_load(raw)
    # 把 key 加 prefix
    return {
        name: {f"llm.{k}": v for k, v in fields.items() if v is not None}
        for name, fields in data.items()
    }

PRESETS = load_presets()

def cfg(thread_id: str, preset: str = "official") -> dict:
    return {"configurable": {"thread_id": thread_id, **PRESETS[preset]}}

加新预设 = 改一行 yaml,零代码改动。

7.5 让它"真优雅"的五个细节 ​

  • 字段为 None 就不要写进 config。让底层回退到 init_chat_model 构造时的默认值或环境变量,避免把 base_url=None 这种垃圾塞进去。
  • prefix 必须对齐。create_agent 里 config_prefix="llm",预设 key 就必须是 llm.model、llm.base_url。集中在 cfg() 函数里拼装,别在业务代码里手写字符串。
  • thread_id 永远是第一个参数。预设是"可选覆盖",会话身份才是一等公民。
  • 不要每次都换预设。同一轮对话建议固定一个预设,切换发生在新一轮而不是 HITL 恢复那一刻,否则工具调用格式 / reasoning 字段可能串味。
  • 密钥只走环境变量。yaml 里用 ${VAR} 插值,git 里看不到真 key。

7.6 最终业务调用点长这样 ​

python
# 99% 的业务代码里你只会看到这一行:
agent.invoke(req, config=cfg("user-123", preset="relay"))

# 想切模型?改 preset 名字即可,其他代码一行不动
agent.invoke(req, config=cfg("user-123", preset="official"))
agent.invoke(req, config=cfg("user-123", preset="local"))

优雅的本质:换模型是一个配置名,不是一次代码改动。


八、常见坑位汇总 ​

坑现象正确做法
直接 ChatOpenAI(...) 构造config 里塞 llm.model 无效换成 init_chat_model + configurable_fields
切模型但新开 thread_id历史丢失,会话从头开始同一个 thread_id,只在 config 里换 model
跨 provider 切时只换 model 不换 key401 Unauthorizedmodel / model_provider / api_key / base_url 一起切
OpenAI 兼容中转却换 provider协议不匹配报错保持 model_provider="openai",只换 url/key/model
HITL 中断恢复那一刻换模型工具调用格式不一致切换发生在"新一轮",不要在 resume 那次 invoke 换
频繁切换 providerreasoning_content 字段差异导致跨轮不稳定同一轮对话内模型保持一致
把密钥写进 config 常量进日志 / 进 git走环境变量 / Secret Manager
from langchain.tools import tool 疑惑为什么不是从 core 导入v1 的便捷 re-export,等价
以为 init_chat_model 会自动装包ImportError自己 pip install langchain-xxx

九、完整最小可运行示例 ​

python
# pip install langchain langchain-openai langchain-anthropic
import os
from langchain_core.messages import HumanMessage
from langchain_core.tools import tool
from langchain.chat_models import init_chat_model
from langchain.agents import create_agent
from langchain.agents.middleware import HumanInTheLoopMiddleware
from langgraph.checkpoint.memory import InMemorySaver
from langgraph.types import Command


# ---------- 1. 定义工具 ----------
@tool
def write_file(path: str, content: str) -> str:
    """写文件到磁盘"""
    with open(path, "w") as f:
        f.write(content)
    return f"written {path}"


# ---------- 2. 构造可配置模型 ----------
model = init_chat_model(
    "openai:gpt-4o-mini",
    configurable_fields=[
        "model", "model_provider",
        "base_url", "api_key", "temperature",
    ],
    config_prefix="llm",
)


# ---------- 3. 组装 Agent ----------
agent = create_agent(
    model=model,
    tools=[write_file],
    middleware=[HumanInTheLoopMiddleware(interrupt_on={"write_file": True})],
    checkpointer=InMemorySaver(),
)


# ---------- 4. 第 1 轮:默认模型 ----------
cfg = {"configurable": {"thread_id": "demo-1"}}

for chunk in agent.stream(
    {"messages": [HumanMessage("把 hello 写入 /tmp/a.txt")]},
    config=cfg,
    stream_mode="values",
):
    pass  # 观察流式输出


# ---------- 5. 人工审批后恢复 ----------
agent.invoke(Command(resume={"approve": True}), config=cfg)


# ---------- 6. 第 2 轮:同一个 thread_id,换中转 + 换模型 ----------
agent.invoke(
    {"messages": [HumanMessage("再帮我写 /tmp/b.txt,内容是 world")]},
    config={"configurable": {
        "thread_id": "demo-1",                     # 同一个会话
        "llm.base_url": "https://one-api.your.com/v1",
        "llm.api_key":  os.environ["RELAY_KEY"],
        "llm.model":    "deepseek-chat",
        # model_provider 保持 openai,因为走 OpenAI 兼容协议
    }},
)

十、一句话总结 ​

core 是"接口",langchain 是"默认实现 + 编排",langgraph 是"运行时",集成包是"驱动"。

日常写业务代码时,这四个方向的 import 会同时出现,它们平级共存就是因为职责不同、版本独立。

thread_id 是"工单",model 是"接电话的人"。工单跟着 thread_id 走,接电话的人(包括厂商、端点、密钥)都可以随时换,历史不丢。