LangChain 从 v0.x 升级到 v1:双轨迁移 Runbook(含回滚)
如果项目里还在用 AgentExecutor、LLMChain、ConversationBufferMemory 这类 0.x 时代的接口,把 langchain 升到 1.x 之后,第一步就会遇到 ModuleNotFoundError。本页写给手上有存量项目、需要升级到 v1 的中文开发者:先把风险量出来,再分两步迁移,每一步都有可回滚的检查点。
适用版本与验证信息
| 项目 | 内容 |
|---|---|
| 验证日期 | 2026-10-25 |
| Python | 3.12.12(官方迁移指南要求 v1 使用 Python 3.10 及以上) |
| 旧轨(迁移前) | langchain==0.3.27、langchain-core==0.3.86、langchain-community==0.3.31、langgraph==0.6.11 |
| 新轨(迁移后) | langchain==1.4.7、langchain-core==1.7.0、langgraph==1.2.17 |
| 兼容层 | langchain-classic==1.0.8、langchain-community==0.4.2、langchain-text-splitters==1.1.3、langchain-openai==1.7.2 |
| 模型 | 实验使用脚本化模型:按预设顺序回放固定的 AIMessage,不联网、不需要 API Key |
| 官方来源 | LangChain v1 migration guide (opens in a new tab),对照官方文档仓库 langchain-ai/docs (opens in a new tab) 提交 b8d4ced(2026-10-16) |
| 实验代码 | 站点仓库 labs/langchain-v1-migration/,各环境的依赖锁定在 labs/requirements/ |
脚本化模型能证明的是框架行为(导入路径、控制流、循环上限),不能证明任何真实模型的效果。换成真实模型后,差异只会出现在模型输出上,本页讨论的框架差异不变。
先说结论
- v1 不是平滑的小版本升级。
langchain包现在只保留agents、messages、tools、chat_models、embeddings;旧的链、记忆、检索器、索引、hub 等移到了langchain-classic,第三方集成需要直接安装对应的提供商包(官方迁移指南的 “Simplified package” 一节)。 - 推荐两步走。 第一步只改 import 路径,行为保持不变;第二步再把 Agent 迁到
create_agent。两步之间各有一个可回滚的提交。 - 有一类行为差异必须主动处理:失控循环。 旧的
AgentExecutor超过max_iterations后返回一段文本;v1 超过recursion_limit后抛出异常。线上代码如果依赖前者,不处理就会变成 500。 - 回滚就是回到旧的依赖锁文件。 只要迁移按步骤提交,回滚不需要改业务代码。
第 0 步:先把风险量出来
升级前先回答两个问题:代码里有多少处旧 import?每一处在 v1 下该去哪里找?
0.1 扫描旧 import
下面是一个 Python 项目里常见的旧写法(实验用的夹具文件):
from langchain.agents import AgentExecutor, create_tool_calling_agent
from langchain.chains import LLMChain
from langchain.memory import ConversationBufferMemory
from langchain.prompts import PromptTemplate
from langchain.chat_models import ChatOpenAIscan_legacy_imports.py 用 ast 解析目录里所有 .py 文件,列出旧 import 和建议的新位置;加上 --verify 后,会在当前环境里真的导入每一个建议的目标,所以映射表本身是被验证过的,而不是凭记忆写的:
python scan_legacy_imports.py fixtures/legacy_app --verifyapp.py:1: langchain.agents.AgentExecutor -> langchain_classic.agents.AgentExecutor [verified]
app.py:1: langchain.agents.create_tool_calling_agent -> langchain_classic.agents.create_tool_calling_agent [verified]
app.py:2: langchain.chains.LLMChain -> langchain_classic.chains.LLMChain [verified]
app.py:3: langchain.memory.ConversationBufferMemory -> langchain_classic.memory.ConversationBufferMemory [verified]
app.py:4: langchain.prompts.PromptTemplate -> langchain_core.prompts.PromptTemplate [verified]
app.py:5: langchain.chat_models.ChatOpenAI -> langchain_openai.ChatOpenAI [verified]
rag.py:1: langchain.document_loaders.TextLoader -> langchain_community.document_loaders.TextLoader [verified]
rag.py:2: langchain.embeddings.OpenAIEmbeddings -> langchain_openai.OpenAIEmbeddings [verified]
rag.py:3: langchain.schema.Document -> langchain_core.documents.Document [verified]
rag.py:3: langchain.schema.HumanMessage -> langchain_core.messages.HumanMessage [verified]
rag.py:4: langchain.text_splitter.RecursiveCharacterTextSplitter -> langchain_text_splitters.RecursiveCharacterTextSplitter [verified]
rag.py:5: langchain.vectorstores.FAISS -> langchain_community.vectorstores.FAISS [verified]
legacy imports: 12, not importable: 0读这份输出要注意三点:
[verified]表示目标已在“兼容层”环境(langchain-classic、langchain-community、langchain-openai都已安装)里成功导入;出现[NOT IMPORTABLE]就需要人工查官方文档,脚本不会替你猜;ChatOpenAI、OpenAIEmbeddings映射到langchain_openai,而不是langchain_community——OpenAI 的类在提供商包里;- 脚本只处理
from x import y形式,不覆盖import langchain.chains这类写法和字符串形式的动态导入,这些需要用grep另行排查。
完整的“旧符号在三种环境下是否可用”的对照表见导入兼容矩阵。
0.2 看清 v1 下的报错长什么样
在只安装了 langchain 1.4.7 的环境里,逐条执行上面这些旧 import,得到的是搜索引擎里最常被问到的几条报错:
"""What the common pre-v1 imports raise on LangChain v1 when langchain-classic is not installed."""
import re
import warnings
warnings.simplefilter("ignore")
CASES = [
("langchain.agents", "AgentExecutor"),
("langchain.chains", "LLMChain"),
("langchain.memory", "ConversationBufferMemory"),
("langchain.chat_models", "ChatOpenAI"),
("langchain_classic.chains", "LLMChain"),
]
for module, name in CASES:
statement = f"from {module} import {name}"
try:
exec(statement, {}) # run the exact statement a user would write
print(f"OK {statement}")
except Exception as exc:
message = re.sub(r" \(/.*\)$", " (<site-packages path>)", str(exc)) # keep output machine-independent
print(f"{statement}\n {type(exc).__name__}: {message}")from langchain.agents import AgentExecutor
ImportError: cannot import name 'AgentExecutor' from 'langchain.agents' (<site-packages path>)
from langchain.chains import LLMChain
ModuleNotFoundError: No module named 'langchain.chains'
from langchain.memory import ConversationBufferMemory
ModuleNotFoundError: No module named 'langchain.memory'
from langchain.chat_models import ChatOpenAI
ImportError: cannot import name 'ChatOpenAI' from 'langchain.chat_models' (<site-packages path>)
from langchain_classic.chains import LLMChain
ModuleNotFoundError: No module named 'langchain_classic'注意前两种“长相”不同:AgentExecutor、ChatOpenAI 是 ImportError(模块还在,名字没了),langchain.chains、langchain.memory 是 ModuleNotFoundError(整个子模块已被移走)。最后一条说明:没有安装 langchain-classic 时,直接写新路径同样会失败,必须先安装。
第 1 步:只换 import 路径,行为保持不变
安装兼容层,再把 langchain.agents 改成 langchain_classic.agents:
pip install langchain-classic旧轨脚本 legacy_agent.py(langchain==0.3.27)与迁移后的 classic_agent.py(langchain==1.4.7 + langchain-classic==1.0.8)逐行对比,只有文档字符串和一行 import 不同:
- from langchain.agents import AgentExecutor, create_tool_calling_agent
+ from langchain_classic.agents import AgentExecutor, create_tool_calling_agent迁移后的脚本:
"""Step 1 of a dual-track migration: only the import path changes (langchain_classic)."""
import warnings
warnings.simplefilter("ignore")
from langchain_classic.agents import AgentExecutor, create_tool_calling_agent
from langchain_core.prompts import ChatPromptTemplate
from shared import get_weather, make_model
prompt = ChatPromptTemplate.from_messages(
[
("system", "You are a weather assistant."),
("human", "{input}"),
("placeholder", "{agent_scratchpad}"),
]
)
agent = create_tool_calling_agent(make_model(), [get_weather], prompt)
executor = AgentExecutor(agent=agent, tools=[get_weather], return_intermediate_steps=True)
result = executor.invoke({"input": "Weather in Beijing?"})
print("output:", result["output"])
print("tool calls:", [(action.tool, action.tool_input) for action, _ in result["intermediate_steps"]])在新环境里运行的输出:
output: Beijing is sunny, 24C.
tool calls: [('get_weather', {'city': 'Beijing'})]这段输出与旧脚本在 langchain==0.3.27 下的输出逐字相同(两份记录文件 expected/legacy_agent.out 与 expected/classic_agent.out 内容一致)。这就是第一步的验收标准:同样的输入,同样的输出,只换了依赖和 import。
这一步结束时:
- 把依赖升级到
langchain 1.x+langchain-classic,业务代码里只改 import 行; - 跑完整回归,确认通过后单独提交,作为回滚点;
- 先灰度,再全量。
第 2 步:迁到 create_agent
第 1 步让项目站在了 v1 上,但 AgentExecutor 仍然是 langchain-classic 里的旧实现。需要 LangGraph 的持久化、人工介入、中间件等能力时,再迁到 create_agent:
"""Step 2: the v1 API. create_agent returns a compiled LangGraph graph."""
from langchain.agents import create_agent
from langchain_core.messages import AIMessage, ToolMessage
from shared import get_weather, make_model
agent = create_agent(
make_model(),
tools=[get_weather],
system_prompt="You are a weather assistant.",
)
result = agent.invoke({"messages": [("user", "Weather in Beijing?")]})
messages = result["messages"]
print("output:", messages[-1].content)
calls = [call for m in messages if isinstance(m, AIMessage) for call in m.tool_calls]
print("tool calls:", [(c["name"], c["args"]) for c in calls])
print("tool results:", [m.content for m in messages if isinstance(m, ToolMessage)])output: Beijing is sunny, 24C.
tool calls: [('get_weather', {'city': 'Beijing'})]
tool results: ['Beijing: sunny, 24C']三种写法在同一个脚本化模型上得到相同的最终回答和相同的工具调用。接口形状的变化可以整理成下面这张对照表:
旧写法(AgentExecutor) | 新写法(create_agent) |
|---|---|
create_tool_calling_agent(llm, tools, prompt) 再包一层 AgentExecutor(...) | create_agent(model, tools=[...], system_prompt="...") 一步完成 |
提示词里要手写 agent_scratchpad 占位符 | 不需要;系统提示通过 system_prompt 传入 |
executor.invoke({"input": 问题}) | agent.invoke({"messages": [("user", 问题)]}) |
result["output"] | result["messages"][-1].content |
return_intermediate_steps=True,读 result["intermediate_steps"] | 遍历 result["messages"]:AIMessage.tool_calls 是调用,ToolMessage 是结果 |
max_iterations | recursion_limit(调用时通过 config 传入,见下一节) |
另外,langgraph.prebuilt.create_react_agent 在本实验的 v1 环境里仍然可以导入(见导入兼容矩阵),但官方迁移指南已经推荐改用 create_agent,并把 prompt= 改名为 system_prompt=,把 pre_model_hook / post_model_hook 改成中间件。
行为差异:失控循环的上限
假设模型一直要求调用工具、永远不给最终回答。两条轨道的表现截然不同。
旧轨,max_iterations=2:
"""Legacy: AgentExecutor(max_iterations=2) when the model never stops calling tools."""
import warnings
warnings.simplefilter("ignore")
from langchain.agents import AgentExecutor, create_tool_calling_agent
from langchain_core.messages import AIMessage
from langchain_core.prompts import ChatPromptTemplate
from shared import ScriptedChatModel, get_weather
looping = ScriptedChatModel(
script=[
AIMessage(content="", tool_calls=[{"name": "get_weather", "args": {"city": "Beijing"}, "id": f"call_{i}"}])
for i in range(10)
]
)
prompt = ChatPromptTemplate.from_messages(
[("system", "You are a weather assistant."), ("human", "{input}"), ("placeholder", "{agent_scratchpad}")]
)
agent = create_tool_calling_agent(looping, [get_weather], prompt)
executor = AgentExecutor(agent=agent, tools=[get_weather], max_iterations=2)
result = executor.invoke({"input": "Weather in Beijing?"})
print("returned normally:", True)
print("output:", result["output"])returned normally: True
output: Agent stopped due to max iterations.正常返回,output 是一句固定的提示文本。调用方根本不知道发生了异常,只会把这句话当作回答。
新轨,recursion_limit=5:
"""v1: the same runaway loop is bounded by recursion_limit and raises instead of returning text."""
from langchain.agents import create_agent
from langchain_core.messages import AIMessage
from langgraph.errors import GraphRecursionError
from shared import ScriptedChatModel, get_weather
looping = ScriptedChatModel(
script=[
AIMessage(content="", tool_calls=[{"name": "get_weather", "args": {"city": "Beijing"}, "id": f"call_{i}"}])
for i in range(10)
]
)
agent = create_agent(looping, tools=[get_weather])
try:
agent.invoke({"messages": [("user", "Weather in Beijing?")]}, {"recursion_limit": 5})
print("returned normally:", True)
except GraphRecursionError as exc:
print("returned normally:", False)
print("exception:", type(exc).__name__)
print("message:", str(exc).splitlines()[0])returned normally: False
exception: GraphRecursionError
message: Recursion limit of 5 reached without hitting a stop condition. You can increase the limit by setting the `recursion_limit` config key.抛出 GraphRecursionError。这是更安全的失败方式(不会把半截结果当回答),但调用方必须显式捕获,否则就是线上 500。
recursion_limit 要设多大
两者的计数单位不同:max_iterations 数的是“Agent 迭代”,recursion_limit 数的是图的执行步数——一次模型调用算一步,一次工具执行再算一步。下面的实验让模型永远要求调用工具,记录每个上限下实际执行了多少次工具:
"""v1: how many tool executions fit under a given recursion_limit when the model never stops."""
from langchain.agents import create_agent
from langchain_core.messages import AIMessage
from langchain_core.tools import tool
from langgraph.errors import GraphRecursionError
from shared import ScriptedChatModel
calls = 0
@tool
def ping(host: str) -> str:
"""Pretend to ping a host."""
global calls
calls += 1
return "pong"
print("limit tool_executions result")
for limit in range(1, 11):
calls = 0
looping = ScriptedChatModel(
script=[
AIMessage(content="", tool_calls=[{"name": "ping", "args": {"host": "a"}, "id": f"c{i}"}])
for i in range(20)
]
)
agent = create_agent(looping, tools=[ping])
try:
agent.invoke({"messages": [("user", "ping a")]}, {"recursion_limit": limit})
result = "returned"
except GraphRecursionError:
result = "GraphRecursionError"
print(f"{limit:5d} {calls:15d} {result}")limit tool_executions result
1 0 GraphRecursionError
2 1 GraphRecursionError
3 1 GraphRecursionError
4 2 GraphRecursionError
5 2 GraphRecursionError
6 3 GraphRecursionError
7 3 GraphRecursionError
8 4 GraphRecursionError
9 4 GraphRecursionError
10 5 GraphRecursionError可以看到,每增加 2 步上限,才多执行一次工具。如果业务期望“最多 N 轮工具调用,然后给出最终回答”,需要多大的上限?再做一个实验,模型调用两次工具后给出最终回答(N=2),并把异常转成显式的降级文本:
"""v1: how big recursion_limit must be for N tool rounds plus the final answer."""
from langchain.agents import create_agent
from langchain_core.messages import AIMessage
from langchain_core.tools import tool
from langgraph.errors import GraphRecursionError
from shared import ScriptedChatModel
ROUNDS = 2 # the scripted model calls a tool twice, then answers
@tool
def lookup(key: str) -> str:
"""Look up a value."""
return f"value-of-{key}"
def build() -> tuple:
script = [
AIMessage(content="", tool_calls=[{"name": "lookup", "args": {"key": f"k{i}"}, "id": f"c{i}"}])
for i in range(ROUNDS)
] + [AIMessage(content="done")]
model = ScriptedChatModel(script=script)
return create_agent(model, tools=[lookup]), model
def ask(limit: int) -> tuple:
agent, model = build()
try:
result = agent.invoke({"messages": [("user", "go")]}, {"recursion_limit": limit})
return model.cursor, result["messages"][-1].content
except GraphRecursionError:
# Legacy AgentExecutor returned text here; v1 raises, so degrade explicitly.
return model.cursor, "FALLBACK: tool budget exhausted"
print(f"tool rounds needed: {ROUNDS}; model calls needed: {ROUNDS + 1}")
for limit in range(2, 8):
calls, answer = ask(limit)
print(f"limit={limit}: model_calls={calls} -> {answer}")tool rounds needed: 2; model calls needed: 3
limit=2: model_calls=1 -> FALLBACK: tool budget exhausted
limit=3: model_calls=2 -> FALLBACK: tool budget exhausted
limit=4: model_calls=2 -> FALLBACK: tool budget exhausted
limit=5: model_calls=3 -> FALLBACK: tool budget exhausted
limit=6: model_calls=3 -> done
limit=7: model_calls=3 -> done结果有一个容易踩坑的细节:limit=5 时第 3 次模型调用已经执行(model_calls=3),最终回答其实生成了,但调用依然以异常结束,结果被丢弃;limit=6 才正常返回。在本页验证的版本组合下,N 轮工具调用加最终回答,需要 recursion_limit 至少为 2N + 2。设置上限时请留足余量,并且一定要处理 GraphRecursionError。
这个规则来自 langgraph==1.2.17 的实测,升级 LangGraph 之后请重新运行 loop_limit_budget.py 确认。
迁移顺序、验收与回滚
| 步骤 | 变更内容 | 通过标准 | 回滚方式 |
|---|---|---|---|
| 0 | 扫描旧 import;把当前依赖锁成文件 | 扫描结果已人工过一遍,[NOT IMPORTABLE] 项有结论 | 无代码变更,无需回滚 |
| 1 | 升级到 langchain 1.x,安装 langchain-classic,只改 import | 回归全部通过;关键场景的输出与旧版逐字一致 | 还原依赖锁文件与该提交 |
| 2 | 将 Agent 迁到 create_agent;处理 GraphRecursionError | 回归通过;失控循环场景返回降级文本而不是 500;输出结构适配完成 | 只回退这一步的提交,保留第 1 步 |
回退到旧轨的依赖示例(完整的锁定列表见站点仓库 labs/requirements/legacy.txt):
pip install "langchain==0.3.27" "langchain-core==0.3.86" "langchain-community==0.3.31" "langgraph==0.6.11"建议把每一步都做成独立的提交和独立的发布,这样回滚永远只是“回到上一个已知可用的依赖集合”。
常见失败与处理
| 现象 | 原因 | 处理 |
|---|---|---|
No module named 'langchain.chains' / 'langchain.memory' | 子模块已迁出 langchain | 安装 langchain-classic,改为 from langchain_classic.chains import ... |
cannot import name 'AgentExecutor' from 'langchain.agents' | 旧 Agent 实现已迁到兼容层 | 第 1 步改 import;或第 2 步迁到 create_agent |
cannot import name 'ChatOpenAI' from 'langchain.chat_models' | 提供商类不在 langchain 里 | pip install langchain-openai,from langchain_openai import ChatOpenAI |
No module named 'langchain_classic' | 写了新路径但没安装兼容层 | pip install langchain-classic |
GraphRecursionError | 工具循环超过 recursion_limit | 按上文 2N + 2 设置上限,并捕获异常做降级 |
| 同一段代码在测试环境正常、线上报错 | 两边依赖版本不一致 | 用锁文件固定两边的版本,并在启动日志里打印 langchain、langchain-core、langgraph 版本 |
下一步
- 不确定该继续用
create_agent还是直接写 LangGraph 图,先看Agent 与 LangGraph 选型; - 查某个具体符号在 0.3 和 1.x 下是否还在,用导入兼容矩阵。
来源
- LangChain v1 migration guide (opens in a new tab):包精简、
langchain-classic、create_agent与create_react_agent的差异; - LangChain v1 发布说明 (opens in a new tab);
- langchain-classic(PyPI) (opens in a new tab)。
以上来源均于 2026-10-25 核对。本页的代码输出来自站点仓库 labs/langchain-v1-migration/ 在上表版本下的实际运行。