版本参考:LangChain v1.x(2026年4月,langchain-core 1.3.0 / langchain 1.1+) 适用读者:有 Python 经验、希望构建生产级 LLM 应用、理解 DAG 与协议型组合的资深工程师 当前 github 地址:github.com/langchain-a…
目录
- 框架定位与 v1.0 的范式转变
- Monorepo 结构与包拆分
- LCEL 与 Runnable 协议
- 组合原语:Sequence / Parallel / Branch / Lambda / Passthrough
- RunnableConfig、回调传播与 contextvars
- 语言模型层次:BaseChatModel / BaseLLM / init_chat_model
- 消息类型与标准化 Content Blocks
- Prompt 模板体系
- 工具定义:@tool、StructuredTool、BaseTool
- 工具调用与结构化输出:bind_tools / with_structured_output
- create_agent:v1.0 唯一的 Agent 抽象
- Middleware 子系统(v1.0 旗舰特性)
- 结构化输出策略:Auto / Provider / Tool
- 检索与 RAG 体系
- Memory:从旧内存类到 LangGraph Checkpointer
- Callbacks 与 LangSmith 追踪
- 输出解析器
- Partner 包生态
- v0.x → v1.0 迁移要点
- LangChain 与 LangGraph 的关系与选型
- 生产最佳实践
- API 快速参考
1. 框架定位与 v1.0 的范式转变
1.1 LangChain 是什么
LangChain 是一套面向 LLM 应用的可组合抽象层,其设计目标是:
提供统一的 模型 / 提示 / 工具 / 检索 / 消息 / 回调 协议,让开发者以声明式方式拼装 LLM 管道,而无需被具体提供商 SDK 锁定。
在 v1.0(2025年10月22日 GA)之后,它的定位更加明确:
langchain-core:所有抽象协议的定义(Runnable、BaseChatModel、BaseTool、BaseMessage、Callbacks)langchain:面向用户的主包,核心是create_agent、init_chat_model、标准化 content blockslangchain-classic:旧版链、旧版 Agent、旧版 memory、旧版检索器的归档- partner packages:OpenAI / Anthropic / Google 等供应商实现
- LangGraph:真正的执行运行时(v1.0 的
create_agent底层就是 LangGraph)
1.2 v1.0 的核心范式转变
| 维度 | v0.x | v1.0 |
|---|---|---|
| Agent 抽象 | AgentExecutor + 多种 create_*_agent 工厂 | 统一 create_agent,底层编译为 LangGraph |
| 自定义状态 | 支持 dataclass / Pydantic / TypedDict | 仅允许 TypedDict 继承 AgentState |
| Hooks 系统 | 散落的 pre_model_hook、post_model_hook | 统一的 Middleware 子系统 |
| 消息内容 | 各家 provider 原生格式不一致 | 标准化 content_blocks 属性 |
| 结构化输出 | Prompted JSON + 各种 response_format | AutoStrategy / ProviderStrategy / ToolStrategy |
| Memory | 18 个 *Memory 类 | 委托给 LangGraph Checkpointer + Store |
langchain-community | 包含 200+ 集成 | 大多迁出到独立 repo,留骨架 |
1.3 LangChain、LangGraph、LangSmith 三件套
LangSmith — 观测与评估平台(SaaS,独立)
↑ traces
LangChain — 高层组合层、Agent、Middleware、检索
↓ compiles to
LangGraph — 低层有状态执行运行时(StateGraph)
↓ built on
langchain-core — Runnable / BaseMessage / BaseTool / Callbacks 协议
v1.0 之后,这三层边界前所未有地清晰:核心协议 → 执行引擎 → 应用封装。
2. Monorepo 结构与包拆分
2.1 目录布局
截至 2026 年 4 月,libs/ 目录结构:
langchain-ai/langchain/
├── libs/
│ ├── core/ # langchain-core (v1.3.0),所有协议
│ ├── langchain_v1/ # langchain (v1.x),主用户包
│ ├── langchain/ # langchain-classic,旧链/旧 Agent/旧 memory
│ ├── partners/ # OpenAI / Anthropic / Ollama 等
│ ├── text-splitters/ # langchain-text-splitters
│ ├── standard-tests/ # 接口一致性测试套件
│ ├── model-profiles/ # 模型能力元数据
│ └── cli/ # langchain CLI
├── docs/ # 文档源码
└── cookbook/ # 示例 notebook
2.2 包职责与依赖
langchain
├── depends on → langchain-core
└── depends on → langgraph (create_agent 运行时)
langchain-classic
└── depends on → langchain-core
→ langchain (部分旧 API 从新包重导出)
langchain-openai / langchain-anthropic / ...
└── peer dependency → langchain-core
langchain-text-splitters
└── depends on → langchain-core (仅 Document 类型)
2.3 版本策略
- 独立版本号:
langchain-core与langchain独立发布,4 月中旬时 core 在1.2.30 → 1.3.0这条小步快跑的线上。 - "2.0 前无破坏"承诺:v1.0 GA 之后公开承诺在 2.0 前不再做破坏性变更,只做增量扩展。
- Partner 包独立节奏:每个 partner 包有自己的 test matrix 与 release 节奏,新模型 ID 直接透传到 provider SDK,无需等 LangChain 发版。
standard-tests:强制所有 partner 实现共享的一致性测试(BaseChatModel/Embeddings/VectorStore都跑同一套用例),是"协议型"架构的质量基石。
2.4 langchain-community 的退场
v0.x 时代 langchain-community 是一个巨大的胶水包,装了 200+ 第三方集成。在 v1.0 时代:
- 主流集成(Google、AWS、Pinecone、MongoDB 等)各自拆出独立 repo、独立 PyPI 包;
langchain-community保留了小众/低频的集成,以及一些过渡期尚未拆出的模块;- 新集成不再向
langchain-community提交,而是按 partner package 模板新建 repo。
3. LCEL 与 Runnable 协议
3.1 为什么要有 LCEL
LCEL(LangChain Expression Language)是一套管道式组合 DSL。核心动机:
- 同一套代码覆盖 sync/async/streaming/batch:
invoke / ainvoke / stream / astream / batch / abatch由协议保证。 - 天然并行:
RunnableParallel与batch自动利用线程池 / asyncio.gather。 - 天然可观测:每一层 Runnable 都通过 CallbackManager 上报 run,LangSmith 自动串联。
- 运行时可配置:
with_config、with_retry、with_fallbacks、with_listeners、configurable_fields全部免费。
3.2 Runnable 协议
所有 LCEL 组件都实现 langchain_core.runnables.base.Runnable[Input, Output]:
class Runnable(Generic[Input, Output], ABC):
# 同步 API
def invoke(self, input: Input, config: RunnableConfig | None = None, **kwargs) -> Output: ...
def stream(self, input: Input, config=None, **kwargs) -> Iterator[Output]: ...
def batch(self, inputs: list[Input], config=None, *, return_exceptions=False,
max_concurrency: int | None = None) -> list[Output]: ...
def batch_as_completed(self, inputs, ...) -> Iterator[tuple[int, Output | Exception]]: ...
# 异步 API
async def ainvoke(self, input, config=None, **kwargs) -> Output: ...
async def astream(self, input, config=None, **kwargs) -> AsyncIterator[Output]: ...
async def abatch(self, inputs, config=None, *, return_exceptions=False, **kwargs) -> list[Output]: ...
async def abatch_as_completed(self, inputs, ...) -> AsyncIterator[tuple[int, Output | Exception]]: ...
# 高保真事件流
async def astream_events(self, input, config=None, *,
version: Literal["v1","v2"]="v2", ...) -> AsyncIterator[StreamEvent]: ...
# 组合辅助
def __or__(self, other) -> RunnableSequence: ...
def __ror__(self, other) -> RunnableSequence: ...
def with_config(self, config: RunnableConfig) -> Runnable: ...
def with_retry(self, *, stop_after_attempt=3, wait_exponential_jitter=True, ...) -> Runnable: ...
def with_fallbacks(self, fallbacks: list[Runnable], *, exceptions_to_handle=(Exception,)) -> Runnable: ...
def with_types(self, *, input_type=None, output_type=None) -> Runnable: ...
def with_listeners(self, *, on_start=None, on_end=None, on_error=None) -> Runnable: ...
def bind(self, **kwargs) -> RunnableBinding: ...
def assign(self, **mappers: Runnable | Callable) -> Runnable: ...
# 自省
def get_graph(self, config=None) -> Graph: ...
def get_input_schema(self, config=None) -> type[BaseModel]: ...
def get_output_schema(self, config=None) -> type[BaseModel]: ...
def config_schema(self, *, include=None) -> type[BaseModel]: ...
3.3 管道操作符 | 的秘密
chain = prompt | model | parser
| 由 Runnable.__or__ / __ror__ 重载。等价于:
chain = RunnableSequence(first=prompt, middle=[model], last=parser)
非 Runnable 的 Python 值(如 dict、callable)会被自动包装:
dict→RunnableParallelcallable→RunnableLambda
所以 {"ctx": retriever, "q": RunnablePassthrough()} | prompt | model 是合法的。
3.4 batch 与 abatch 的实现细节
batch默认在线程池里跑invoke,max_concurrency控制并发。abatch用asyncio.gather展开。- 但
BaseLLM.batch特化过:max_concurrency=None时走 provider 原生批量接口(generate_prompt(prompts=[...]))。因此推理 API 费率敏感时应避免显式传max_concurrency。
3.5 astream_events v2 事件分类
每个事件结构为:
StreamEvent = TypedDict("StreamEvent", {
"event": str, # "on_chat_model_start" 等
"name": str, # Runnable 名称
"run_id": str,
"parent_ids": list[str], # v2 才有:从 root 到 immediate parent
"tags": list[str],
"metadata": dict,
"data": dict, # 具体 payload
})
事件名形如 on_<type>_<start|stream|end>,<type> 取值:chat_model | llm | chain | tool | retriever | prompt | parser。外加 on_custom_event(v2 专有)用于用户自定义事件。
3.6 LCEL 的经典套路
from langchain_core.runnables import RunnablePassthrough, RunnableParallel
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
prompt = ChatPromptTemplate.from_messages([
("system", "Answer using the context only."),
("human", "Context:\n{ctx}\n\nQuestion: {question}"),
])
rag_chain = (
RunnableParallel(
ctx=retriever,
question=RunnablePassthrough(),
)
| prompt
| model
| StrOutputParser()
)
answer = rag_chain.invoke("What is LCEL?")
关键理解:每一根 | 都是 Runnable,每一层都可以 stream / batch / trace / retry。
4. 组合原语
4.1 核心组合类
| 类 | 用途 | 典型写法 |
|---|---|---|
RunnableSequence | 顺序管道 | 由 a | b | c 自动构造 |
RunnableParallel | 并发扇出到 N 个 runnable | {"a": a, "b": b} 或 RunnableParallel(a=a, b=b) |
RunnableLambda | 把普通函数变 Runnable | RunnableLambda(lambda x: x.upper()) |
RunnablePassthrough | 原样透传 | RunnablePassthrough() |
RunnablePassthrough.assign | 在 dict 上追加计算字段 | RunnablePassthrough.assign(length=lambda d: len(d["text"])) |
RunnableBranch | if/elif/else 分支 | RunnableBranch((cond1, branch1), (cond2, branch2), default) |
RunnableEach | 对列表元素逐个应用 | sub_chain.map() |
RunnableConfigurableAlternatives | 运行时切换实现 | model.configurable_alternatives(...) |
RunnableConfigurableFields | 运行时改参数 | model.configurable_fields(temperature=ConfigurableField(id="temp")) |
4.2 RunnablePassthrough.assign 的价值
RAG 场景下特别好用:
chain = (
RunnablePassthrough.assign(
ctx=lambda x: retriever.invoke(x["question"])
)
| prompt
| model
)
chain.invoke({"question": "..."})
# ctx 自动算出来,并与 question 合并进下游
它等价于 RunnableAssign(mapper=RunnableParallel(ctx=...)),但代码更短。
4.3 RunnableBranch 的条件分发
from langchain_core.runnables import RunnableBranch
router = RunnableBranch(
(lambda x: "code" in x["intent"], code_chain),
(lambda x: "sql" in x["intent"], sql_chain),
fallback_chain, # 末尾是默认分支
)
每个 predicate 必须同步返回 bool,不能是 Runnable。需要用 LLM 做路由时,先在上游跑一条 chain 产出 intent。
4.4 .bind():冻结参数
strict_json_model = model.bind(response_format={"type": "json_object"})
web_model = model.bind(tools=[web_search_tool], tool_choice="any")
返回 RunnableBinding,是一个轻量代理:所有调用自动附加绑定的 kwargs。
5. RunnableConfig、回调传播与 contextvars
5.1 RunnableConfig 结构
class RunnableConfig(TypedDict, total=False):
tags: list[str] # LangSmith 过滤器列列
metadata: dict[str, Any] # LangSmith 自定义列
callbacks: Callbacks # 回调处理器列表
run_name: str # 在 trace 里显示的名字
run_id: UUID # 强制指定 run_id
max_concurrency: int | None # batch 并发
recursion_limit: int # LangGraph 递归深度
configurable: dict[str, Any] # configurable_* 运行时覆盖
任何 Runnable 方法都可以接收 config 作为第二个位置参数:
chain.invoke(
{"question": "..."},
config={"tags": ["prod"], "metadata": {"user_id": "u-42"}, "callbacks": [MyHandler()]},
)
5.2 回调在子 Runnable 中的传播
_call_with_config 是 Runnable 内部的 wrapper,它会:
- 基于当前 config 构造一个子
CallbackManager; - 触发
on_chain_start; - 把
parent_run_id写入子 Runnable 的 config; - 执行实际逻辑;
- 触发
on_chain_end/on_chain_error。
于是 prompt | model | parser 整条链上,每一步的 trace 都自动挂到前一步下面。
5.3 Python 3.9/3.10 的 async 陷阱
Python 3.11 开始,contextvars 在 asyncio.Task 间自动拷贝,RunnableConfig 会自动传播。3.9/3.10 必须手动把 config 传进每层 await:
# 3.9 / 3.10:必须手动 thread config
async def my_node(state, config):
msg = await model.ainvoke(state["messages"], config) # ← 一定要显式传
return {"messages": [msg]}
astream_events 在老版本 Python 上经常因为丢 config 导致 "miss events"。3.11+ 是安全的。
5.4 with_config:永久附加配置
prod_chain = chain.with_config({"tags": ["prod"], "metadata": {"env": "prod"}})
返回新 Runnable,之后每次 invoke 都带这套 config,不影响原链。
6. 语言模型层次
6.1 继承关系
Runnable
└── RunnableSerializable
└── BaseLanguageModel[Output]
├── BaseChatModel (Output = AIMessage)
└── BaseLLM (Output = str)
└── LLM (单 prompt 的 BaseLLM 简化)
6.2 BaseChatModel 核心钩子
子类必须实现 _generate,可选 _stream / _agenerate / _astream:
class BaseChatModel(BaseLanguageModel[AIMessage]):
def _generate(self, messages: list[BaseMessage], stop=None,
run_manager: CallbackManagerForLLMRun | None = None, **kwargs) -> ChatResult: ...
def _stream(self, messages, stop=None, run_manager=None, **kwargs) -> Iterator[ChatGenerationChunk]: ...
async def _agenerate(...) -> ChatResult: ...
async def _astream(...) -> AsyncIterator[ChatGenerationChunk]: ...
# 元数据
@property
def _llm_type(self) -> str: ... # 用于 trace
@property
def _identifying_params(self) -> dict: ...
# 流控
disable_streaming: Union[bool, Literal["tool_calling"]] = False
output_version: Optional[str] = None # v0 / v1 / provider-specific
# LangSmith
def _get_ls_params(self, stop=None, **kwargs) -> LangSmithParams: ...
output_version 控制 AIMessage.content 的 shape:v1 会写标准化 content blocks,v0 保留 provider 原生格式。
6.3 init_chat_model:统一工厂
核心 API,位于 langchain.chat_models.init_chat_model:
from langchain.chat_models import init_chat_model
# 简写:自动推断 provider
model = init_chat_model("gpt-5.2")
model = init_chat_model("claude-sonnet-4-6")
# 显式前缀
model = init_chat_model("openai:gpt-4")
model = init_chat_model("anthropic:claude-haiku-4-5-20251001")
# 运行时可切模型
model = init_chat_model(
model="gpt-5.2",
configurable_fields=("model", "temperature"),
config_prefix="llm",
)
# 任意字段可改
model = init_chat_model(configurable_fields="any")
# 用法
model.invoke([HumanMessage("Hi")],
config={"configurable": {"llm_model": "claude-sonnet-4-6",
"llm_temperature": 0.2}})
configurable_fields="any" 特别强大,也特别危险——永远不要把未经校验的用户输入直接塞进 configurable dict,否则用户可以切换到贵模型或覆盖 system prompt。
6.4 返回类型:_ConfigurableModel
configurable_fields 非空时,init_chat_model 返回一个 _ConfigurableModel,它把实际模型构造推迟到第一次调用时,并按 config["configurable"] 动态 resolve。
7. 消息类型与标准化 Content Blocks
7.1 基础消息类
langchain_core.messages:
| 类 | 角色 | 常见字段 |
|---|---|---|
SystemMessage | system | content |
HumanMessage | user | content(可为 str 或多模态 list) |
AIMessage | assistant | content, tool_calls, additional_kwargs, usage_metadata, response_metadata |
ToolMessage | tool | content, tool_call_id, artifact |
FunctionMessage | function(legacy) | 同上 |
ChatMessage | 自定义 role | content, role |
每种消息都有对应的 Chunk:AIMessageChunk、HumanMessageChunk 等。Chunk 可以用 + 累加:
acc = AIMessageChunk(content="")
async for chunk in model.astream(messages):
acc = acc + chunk
# acc 就是完整的 AIMessage,tool_call_chunks 也会被正确合并
7.2 AIMessage.tool_calls
v1 时代 tool_calls 字段是一等公民:
class ToolCall(TypedDict):
name: str
args: dict[str, Any]
id: str
type: Literal["tool_call"]
流式场景下用 tool_call_chunks:
class ToolCallChunk(TypedDict):
name: str | None
args: str | None # partial JSON string
id: str | None
index: int | None
累加时 LangChain 会自动将 partial JSON 拼接并在可解析时 promote 到 tool_calls。
7.3 标准化 Content Blocks(v1 旗舰特性)
为了解决各 provider 返回结构迥异的问题(Anthropic 返回 text + thinking + tool_use 的 block list,OpenAI 返回 string + 外挂 tool_calls,Gemini 又不一样),v1 新增懒属性 content_blocks:
from langchain_core.messages import AIMessage
msg: AIMessage = model.invoke(...)
for block in msg.content_blocks:
match block["type"]:
case "text": print(block["text"])
case "reasoning": print("[thinking]", block["reasoning"])
case "citation": print("[cite]", block["url"])
case "tool_call": print("→", block["name"], block["args"])
case "image": handle_image(block["source"])
case "audio": ...
所有受支持的 provider 适配器都实现了 native → 标准 block 的转换器。msg.content 仍然保留原生结构,你可以按需选择哪一层。
7.4 v1 消息 API 收紧
AIMessage.example字段已移除,改用additional_kwargs["example"]。.text()由方法改为属性:msg.text能拿到纯文本拼接。方法形式在 v1 还能用但会 warn,v2 将移除。- 老代码
if m.text():要改成if m.text:。
8. Prompt 模板体系
8.1 类层次
BasePromptTemplate
├── StringPromptTemplate
│ ├── PromptTemplate # 传统字符串模板
│ └── FewShotPromptTemplate
└── BaseChatPromptTemplate
├── ChatPromptTemplate # 主要使用的
└── FewShotChatMessagePromptTemplate
8.2 ChatPromptTemplate
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
prompt = ChatPromptTemplate.from_messages([
("system", "You are {persona}."),
MessagesPlaceholder("history", optional=True),
("human", "{question}"),
])
messages = prompt.invoke({
"persona": "a senior Python engineer",
"history": [HumanMessage("hi"), AIMessage("hello")],
"question": "Explain asyncio.",
}).to_messages()
- 元组简写:
("system", ...),("human", ...),("ai", ...),("placeholder", "{var}")。 - 占位符支持
optional=True,允许变量缺失不报错。 template_format可选"f-string"(默认)或"jinja2"。
8.3 Few-shot 模板
from langchain_core.prompts import FewShotChatMessagePromptTemplate
example_prompt = ChatPromptTemplate.from_messages([
("human", "{input}"), ("ai", "{output}"),
])
few_shot = FewShotChatMessagePromptTemplate(
example_prompt=example_prompt,
examples=[
{"input": "2+2", "output": "4"},
{"input": "3+3", "output": "6"},
],
)
final = ChatPromptTemplate.from_messages([
("system", "Do math."),
few_shot,
("human", "{question}"),
])
配合 ExampleSelector(embedding 近邻、长度加权等)可以做动态样例选取。
9. 工具定义
9.1 @tool 装饰器(90% 场景的首选)
from langchain_core.tools import tool
@tool
def add(a: int, b: int) -> int:
"""Add two integers."""
return a + b
# 高级形式
@tool("weather", args_schema=WeatherSchema, return_direct=True)
def get_weather(city: str) -> dict: ...
# 带 artifact 的工具
@tool(response_format="content_and_artifact")
def search(q: str) -> tuple[str, list[dict]]:
hits = do_search(q)
summary = format_hits(hits)
return summary, hits # (content, artifact)
- 自动从类型注解与 docstring 生成
args_schema、description、name。 response_format="content_and_artifact"时,content喂回 LLM,artifact留在ToolMessage.artifact供后续取用。
9.2 StructuredTool
适合从已有函数快速生成工具:
from langchain_core.tools import StructuredTool
def book(flight_no: str, seat: str) -> str: ...
book_tool = StructuredTool.from_function(
func=book,
name="book_flight",
description="Book a flight seat.",
args_schema=BookSchema,
)
9.3 BaseTool 手写类
需要细粒度控制(复杂 async、权限校验)时:
from langchain_core.tools import BaseTool
from pydantic import BaseModel
class QuerySchema(BaseModel):
sql: str
class SqlTool(BaseTool):
name: str = "sql_query"
description: str = "Run a read-only SQL query."
args_schema: type[BaseModel] = QuerySchema
def _run(self, sql: str) -> str:
_assert_readonly(sql)
return self._db.run(sql)
async def _arun(self, sql: str) -> str:
_assert_readonly(sql)
return await self._db.arun(sql)
9.4 Injected 注解:对 LLM 不可见的参数
from typing import Annotated
from langchain_core.tools import tool, InjectedToolCallId
from langgraph.prebuilt import InjectedState
@tool
def remember(
fact: str,
*,
state: Annotated[AgentState, InjectedState],
tool_call_id: Annotated[str, InjectedToolCallId],
) -> str:
"""Save a fact to the user's memory."""
state["memory"].append(fact)
return "OK"
InjectedState / InjectedToolCallId / InjectedToolArg 让 LangGraph 在运行时注入值,但生成的 JSON Schema 会自动剔除这些参数——LLM 根本看不到它们。
9.5 把 retriever 变成 tool
from langchain.tools.retriever import create_retriever_tool
kb_tool = create_retriever_tool(
retriever,
name="search_kb",
description="Search the internal knowledge base.",
)
10. 工具调用与结构化输出
10.1 bind_tools
model_with_tools = model.bind_tools(
tools=[add, weather, sql_tool, {"type": "web_search_20250604"}], # BaseTool / @tool / dict
tool_choice="auto", # "auto" | "any" | "none" | "required" | {"type":"function","function":{"name":"..."}}
strict=True, # OpenAI/xAI:严格 schema 约束
parallel_tool_calls=True, # OpenAI
)
msg: AIMessage = model_with_tools.invoke([HumanMessage("用工具加 2 和 3")])
for tc in msg.tool_calls:
print(tc["name"], tc["args"])
- dict 类型参数代表 provider 的内建工具(如 OpenAI Responses API 的
web_search、file_search、computer_use),LangChain 原样透传。 tool_choice="any"在 Anthropic 表示"必须调用某个工具",OpenAI 对应"required"。
10.2 with_structured_output
from pydantic import BaseModel, Field
class Answer(BaseModel):
city: str
temperature_c: float = Field(description="Celsius")
structured = model.with_structured_output(
schema=Answer,
method="json_schema", # 可选 "function_calling" | "json_mode" | "json_schema"
include_raw=False, # True 时返回 {"raw":..., "parsed":..., "parsing_error":...}
strict=True,
)
ans: Answer = structured.invoke("What's the temperature in Tokyo?")
10.3 两个常见坑
坑 1:with_structured_output(S).bind_tools([t]) 会挂
with_structured_output 返回的是 RunnableSequence(内部已经绑好了一个隐藏抽取工具),没有 bind_tools 方法。解决方案:在 bind_tools 传 tool_choice 指定 schema 对应的工具名,不要再走 with_structured_output。
坑 2:流式状态下 tool args 合并不稳
部分 provider 在 .stream(...) 下会把 tool call 的 args 单次吐出(无 partial chunk),导致你等不到增量 JSON。Issue #29129 描述了这一点。Workaround:对 tool call 走 .invoke(非 stream),或只用 .astream_events(version="v2") 来拿 on_chat_model_stream 事件。
10.4 回调注入
msg = model.invoke(
messages,
config={
"callbacks": [UsageMetadataCallbackHandler()],
"tags": ["extraction"],
"metadata": {"task_id": "T-101"},
},
)
不要通过 model = ChatOpenAI(callbacks=[...]) 旧式绑定——v1.0 已完全改用 config 注入模型。
11. create_agent:v1.0 唯一的 Agent 抽象
11.1 签名
from langchain.agents import create_agent, AgentState
from langchain.agents.structured_output import ToolStrategy, ProviderStrategy
agent = create_agent(
model: str | BaseChatModel,
tools: Sequence[BaseTool | Callable | dict],
*,
prompt: str | ChatPromptTemplate | Callable | None = None,
response_format: ToolStrategy | ProviderStrategy | type | None = None,
state_schema: type[AgentState] | None = None,
middleware: Sequence[AgentMiddleware] = (),
checkpointer: BaseCheckpointSaver | None = None, # LangGraph
store: BaseStore | None = None, # LangGraph
context_schema: type | None = None,
name: str | None = None,
version: Literal["v1", "v2"] = "v2",
debug: bool = False,
)
返回值是一个 已编译的 LangGraph CompiledStateGraph:你可以直接 .invoke / .stream / .astream_events / .get_state / .update_state / .get_graph().draw_mermaid()。
11.2 最小示例
from langchain.agents import create_agent
from langchain_core.tools import tool
@tool
def add(a: int, b: int) -> int:
"""Add."""
return a + b
agent = create_agent(
model="openai:gpt-5.2",
tools=[add],
prompt="You are a math assistant.",
)
result = agent.invoke({"messages": [HumanMessage("1+2+3?")]})
print(result["messages"][-1].content)
11.3 AgentState
from typing_extensions import TypedDict, Annotated, NotRequired
from langgraph.graph.message import add_messages
from langgraph.managed import RemainingSteps
class AgentState(TypedDict):
messages: Annotated[Sequence[BaseMessage], add_messages]
remaining_steps: NotRequired[RemainingSteps]
structured_response: NotRequired[Any] # 仅当 response_format 被设置
v1.0 强制要求:自定义 state 必须继承 AgentState 并且是 TypedDict。Pydantic / dataclass 不再被接受,这是 v1.0 迁移中最常见的 breaking change。
class MyState(AgentState): # OK
user_id: str
notes: list[str]
# 以下都会被拒绝(v1.0):
# class MyState(BaseModel): ...
# @dataclass class MyState: ...
11.4 remaining_steps
remaining_steps = recursion_limit − steps_taken。当它低于 2 且还有 pending tool call 时,agent 会优雅返回一条 "Sorry, need more steps to process this request.",而不是 抛 GraphRecursionError——这是一个比 v0.x 更友好的兜底。
11.5 Agent 与 prompt 的几种写法
# 1) 静态字符串 → 变成 SystemMessage
agent = create_agent(model, tools, prompt="You are helpful.")
# 2) ChatPromptTemplate
agent = create_agent(model, tools, prompt=ChatPromptTemplate.from_messages([
("system", "You help users with {domain} questions."),
("placeholder", "{messages}"),
]))
# 3) Callable(state, runtime) → list[BaseMessage]
def dynamic_prompt(state, runtime):
persona = runtime.context.persona
return [SystemMessage(f"Act as {persona}.")] + state["messages"]
agent = create_agent(model, tools, prompt=dynamic_prompt)
11.6 从 AgentExecutor 迁过来
| v0.x | v1.0 |
|---|---|
create_tool_calling_agent(llm, tools, prompt) + AgentExecutor(agent, tools) | create_agent(model, tools, prompt=prompt) |
create_react_agent(llm, tools, prompt)(字符串解析版) | create_agent(model, tools, prompt=...) |
AgentExecutor(..., return_intermediate_steps=True) | 遍历 result["messages"] 自己筛 ToolMessage |
AgentExecutor(..., max_iterations=10) | agent.invoke(..., config={"recursion_limit": 10*2+1}) |
pre_model_hook / post_model_hook | Middleware 的 before_model / after_model |
12. Middleware 子系统
12.1 三种 Hook 风格
| 风格 | 钩子 | 语义 |
|---|---|---|
| Node-style(观察/修饰) | before_agent, before_model, after_model, after_agent | 在节点前后运行,可返回 state 增量 |
| Wrap-style(拦截) | wrap_model_call, wrap_tool_call | 包住 LLM 调用/工具调用,决定是否真正执行 |
| Convenience | modify_model_request | 一次性改 tools / messages / tool_choice / 输出格式 |
执行顺序:
before_agent
→ before_model[各中间件顺序]
→ wrap_model_call[嵌套洋葱]
→ 实际模型调用
→ after_model[各中间件倒序]
→ tool loop / wrap_tool_call
after_agent
12.2 装饰器形式
from langchain.agents.middleware import before_model, wrap_model_call
@before_model
def inject_user_profile(state, runtime):
return {"messages": [SystemMessage(f"User: {runtime.context.user_name}")]}
@wrap_model_call
def retry_on_rate_limit(request, handler):
for i in range(3):
try:
return handler(request)
except RateLimitError:
time.sleep(2 ** i)
raise
12.3 类形式(推荐用于复杂场景)
from langchain.agents.middleware import AgentMiddleware
class AuditMiddleware(AgentMiddleware):
state_schema = AuditState # 扩 state 的首选方式
tools = [audit_tool] # 本 middleware scoped 的工具
def before_model(self, state, runtime):
log.info("model call", user=runtime.context.user_id)
return None
def wrap_tool_call(self, request, handler):
if is_dangerous(request.tool_call):
raise PermissionError("blocked")
return handler(request)
state_schema 属性是 v1.0 扩展 state 的官方方式——比在 create_agent(state_schema=...) 里手动合并更安全。
12.4 v1 自带 Middleware 清单
| Middleware | 功能 |
|---|---|
SummarizationMiddleware | 历史超阈值时在 before_model 里做增量总结 |
ModelRetryMiddleware(v1.1) | wrap_model_call 里指数退避重试 |
PIIMiddleware | 输入/输出/工具结果的 PII 掩码/哈希/硬阻断,失败抛 PIIDetectionError |
LLMToolSelectorMiddleware | 用廉价模型从大 toolset 预选子集 |
ShellToolMiddleware | 注入 shell 工具 + before_agent/after_agent 生命周期管理 |
AnthropicPromptCachingMiddleware(ttl="5m") | 在 langchain-anthropic:自动标记 cache_control |
| Anthropic text_editor / memory_tool / file_search / bash_tool middleware | 把 Anthropic 服务器端工具能力接入 agent |
12.5 Middleware vs 旧 hook 的不兼容警告
langgraph.prebuilt.create_react_agent 的 pre_model_hook / post_model_hook 不能与 middleware 共存——混用会触发 runtime error。迁 middleware 时要一并把 hook 删掉。
13. 结构化输出策略
13.1 三种策略
langchain.agents.structured_output:
| Strategy | 适用 | 原理 |
|---|---|---|
AutoStrategy[T] | 默认,推 schema 就会选它 | 优先选 ProviderStrategy,不支持时回落到 ToolStrategy |
ProviderStrategy(schema, strict=None) | OpenAI JSON schema、Anthropic structured outputs、Gemini JSON mode | 使用 provider 原生结构化 endpoint |
ToolStrategy(schema, tool_message_content=None, handle_errors=...) | 任何支持 tool calling 的模型 | 合成一个 schema 工具,末回合 tool_choice="any" |
13.2 用法
from pydantic import BaseModel
class TicketTriage(BaseModel):
severity: Literal["P0","P1","P2","P3"]
category: str
summary: str
# 方式 1:直接传 schema(AutoStrategy)
agent = create_agent(model, tools, response_format=TicketTriage)
# 方式 2:手动选 ToolStrategy(跨 provider 最稳)
agent = create_agent(
model, tools,
response_format=ToolStrategy(
schema=TicketTriage,
handle_errors=lambda e: f"Please fix: {e}",
),
)
# 结果在 state["structured_response"]
result = agent.invoke({"messages": [HumanMessage("...")]} )
triage: TicketTriage = result["structured_response"]
13.3 常见问题
- 流式受限:两种策略下最后一轮都是"强制 tool call",几乎没有前置 text,
astream会很短。 - Schema 必须静态已知:目前不支持根据运行时状态动态换 schema(路线图上)。
- Gemini 3 误路由:若 Gemini 3 被 AutoStrategy 误选到 ToolStrategy,显式包
ProviderStrategy(TicketTriage)即可。 - 异常:
StructuredOutputError、MultipleStructuredOutputsError、StructuredOutputValidationError。
14. 检索与 RAG 体系
14.1 Document 与 BaseLoader
from langchain_core.documents import Document
doc = Document(
page_content="LangChain is ...",
metadata={"source": "README.md", "page": 1},
id="doc-1",
)
BaseLoader 协议:
class BaseLoader(ABC):
def load(self) -> list[Document]: ...
def lazy_load(self) -> Iterator[Document]: ...
async def aload(self) -> list[Document]: ...
async def alazy_load(self) -> AsyncIterator[Document]: ...
常用 loader:TextLoader / CSVLoader / JSONLoader / UnstructuredFileLoader / DirectoryLoader / PyPDFLoader / PyMuPDFLoader / UnstructuredPDFLoader / WebBaseLoader / RecursiveUrlLoader / PlaywrightURLLoader / SitemapLoader,云存储 loader 分散在各自的 partner 包。
14.2 Text Splitters
from langchain_text_splitters import (
RecursiveCharacterTextSplitter,
TokenTextSplitter,
MarkdownHeaderTextSplitter,
Language,
)
splitter = RecursiveCharacterTextSplitter(
chunk_size=1000,
chunk_overlap=200,
separators=["\n\n", "\n", " ", ""],
length_function=len,
keep_separator=True,
)
py_splitter = RecursiveCharacterTextSplitter.from_language(
language=Language.PYTHON, chunk_size=1500, chunk_overlap=200,
)
chunks: list[Document] = splitter.split_documents(loader.load())
高级:SemanticChunker(基于 embedding 断句)、MarkdownHeaderTextSplitter、HTMLSectionSplitter。
14.3 Embeddings 与 VectorStore
from langchain_openai import OpenAIEmbeddings
from langchain_community.vectorstores import FAISS
embeddings = OpenAIEmbeddings(model="text-embedding-3-large")
store = FAISS.from_documents(chunks, embeddings)
retriever = store.as_retriever(
search_type="mmr",
search_kwargs={"k": 5, "fetch_k": 20, "lambda_mult": 0.5},
)
搜索类型:
"similarity":最近邻"mmr":Maximal Marginal Relevance,平衡相关性与多样性"similarity_score_threshold":带阈值过滤
search_kwargs 支持 filter={...} 做 metadata 过滤(各 vectorstore 表达式略有差异)。
14.4 高级检索器(都在 langchain-classic 下)
| 检索器 | 用途 |
|---|---|
MultiQueryRetriever | LLM 把查询改写成 N 个不同措辞的 query,取并集 |
ContextualCompressionRetriever | 用 LLMChainExtractor / LLMChainFilter / EmbeddingsFilter 压缩命中 |
ParentDocumentRetriever | 存子块(高召回),返父块(全上下文) |
MultiVectorRetriever | 索引 summary / HyDE 问题,返回原文 |
EnsembleRetriever | 稀疏(BM25)+ 稠密融合,RRF 加权 |
SelfQueryRetriever | LLM 把自然语言 query 编译成结构化 metadata 过滤 |
TimeWeightedVectorStoreRetriever | 时间衰减加权 |
14.5 Indexing API(增量同步)
from langchain.indexes import SQLRecordManager, index
rm = SQLRecordManager(namespace="my_ns", db_url="sqlite:///rm.db")
rm.create_schema()
index(
docs,
record_manager=rm,
vector_store=store,
cleanup="incremental", # incremental / full / None
source_id_key="source",
)
基于内容哈希 + source id 做可重跑的去重同步,生产数据管道必备。
14.6 典型 RAG 链
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnablePassthrough
template = """仅基于上下文回答问题。不知道就说不知道。
上下文:
{context}
问题:{question}
"""
prompt = ChatPromptTemplate.from_template(template)
rag_chain = (
{"context": retriever | (lambda docs: "\n\n".join(d.page_content for d in docs)),
"question": RunnablePassthrough()}
| prompt
| model
| StrOutputParser()
)
15. Memory:从旧内存类到 LangGraph Checkpointer
15.1 旧 memory 的终结
下列 v0.x 的类全部在 v1.0 移到 langchain-classic,并且不再推荐使用:
ConversationBufferMemoryConversationBufferWindowMemoryConversationSummaryMemoryConversationSummaryBufferMemoryConversationKGMemoryConversationEntityMemoryVectorStoreRetrieverMemoryCombinedMemoryReadOnlySharedMemory
原因:它们早于 tool calling 时代设计,缺乏 thread/user 维度,且无法跨进程持久化。
15.2 新架构:Checkpointer + Store
Checkpointer(会话内持久化,按 thread_id)
└── InMemorySaver / MemorySaver (开发)
└── SqliteSaver / AsyncSqliteSaver (本地文件)
└── PostgresSaver / AsyncPostgresSaver (生产)
Store(跨会话长期记忆,按 namespace)
└── InMemoryStore
└── PostgresStore
└── 自带向量索引(embedding config)
thread_id 与 user_id 通过 config["configurable"] 在运行时传入:
agent = create_agent(
model, tools,
checkpointer=PostgresSaver.from_conn_string("postgresql://..."),
store=PostgresStore.from_conn_string("postgresql://..."),
)
agent.invoke(
{"messages": [HumanMessage("记住我喜欢 Python")]},
config={"configurable": {"thread_id": "chat-42", "user_id": "u-100"}},
)
15.3 trim_messages 辅助函数
超长历史控制的银弹:
from langchain_core.messages import trim_messages
trimmed = trim_messages(
messages,
max_tokens=4000,
token_counter=model, # BaseLanguageModel 或 Callable
strategy="last", # "first" | "last"
start_on="human", # 确保从 human 开始(保留对话完整性)
end_on=["human", "tool"],
include_system=True,
allow_partial=False,
)
最佳实践:把它写进一个 SummarizationMiddleware 或自定义的 before_model 里,每次模型调用前自动裁剪。
15.4 LangMem
LangMem SDK 构建在 BaseStore 之上,提供语义记忆(用户偏好、事实)与程序记忆(how-to 经验)两种高层 API。适合已经用 Store 做了基础的项目继续深化。
16. Callbacks 与 LangSmith 追踪
16.1 BaseCallbackHandler
位于 langchain_core.callbacks.base,由六个 Mixin 拼成:
BaseCallbackHandler
├── LLMManagerMixin → on_llm_start / new_token / end / error
├── ChainManagerMixin → on_chain_start / end / error
├── ToolManagerMixin → on_tool_start / end / error
├── RetrieverManagerMixin → on_retriever_start / end / error
├── CallbackManagerMixin → on_chat_model_start / on_text
└── RunManagerMixin → on_agent_action / on_agent_finish / on_custom_event
AsyncCallbackHandler 提供异步镜像。每个钩子都接收 run_id、parent_run_id、tags、metadata、serialized。
16.2 最小 Handler
from langchain_core.callbacks.base import BaseCallbackHandler
class TokenPrinter(BaseCallbackHandler):
def on_llm_new_token(self, token: str, **kwargs):
print(token, end="", flush=True)
chain.invoke({"q": "..."}, config={"callbacks": [TokenPrinter()]})
16.3 CallbackManager 内部机制
handle_event 是核心 dispatch:
- 同步上下文 + 同步 handler:直接调用。
- 同步上下文 + 异步 handler:汇集为 coroutine,用
asyncio.Runner(3.11+)或asyncio.run跑完。 - 异步上下文:走
ahandle_event,用asyncio.gather并发所有 async handler。 run_inline=True的 handler 顺序执行,确保关键副作用不并发。- Handler 没实现
on_chat_model_start时,系统自动降级为on_llm_start+ 字符串化消息。
16.4 LangSmith 零配置接入
export LANGSMITH_TRACING=true
export LANGSMITH_API_KEY=lsv2_...
export LANGSMITH_PROJECT=my-agent
langchain_core.tracers.context 会自动注册 LangChainTracer,之后任何 Runnable 的执行都进入 LangSmith:
- 嵌套 run 依靠
parent_run_id自动成树; metadata={...}、tags=[...]成为 UI 可筛选列;- LLM 调用的 token usage 从
AIMessage.usage_metadata上报; - Agent 的多步执行(model → tool → model → ...)在一个 trace 里完整展开。
16.5 不要再用 model = ChatOpenAI(callbacks=[...])
v1.0 已全面改为 config 注入。模型直接带 handler 会在组合进 LCEL 时丢失继承,导致下游 runnable 看不到这些 handler。
17. 输出解析器
17.1 类层次
BaseOutputParser[T] → invoke 入口
└── BaseTransformOutputParser → 支持流式
└── BaseCumulativeTransformOutputParser → 累加后 emit diff
17.2 关键实现
| Parser | 输出 | 特点 |
|---|---|---|
StrOutputParser | str | 直接取 AIMessage.content 的文本 |
JsonOutputParser(pydantic_object=None) | dict | 宽容 JSON,流式下能 emit 部分 dict |
PydanticOutputParser(pydantic_object, diff=False) | Pydantic 实例 | diff=True 流式下吐 JSON-Patch |
StructuredOutputParser.from_response_schemas([...]) | dict | 基于 ResponseSchema 描述,轻量但校验弱 |
XMLOutputParser(tags=[...]) | dict | XML 结构化 |
PydanticToolsParser(tools=[M1, M2]) | list[Union[M1,M2]] | 从 AIMessage.tool_calls 抽取并 dispatch 到对应 Pydantic |
JsonOutputToolsParser(first_tool_only=False) | list[dict] | 同上但 dict 形式 |
OutputFixingParser.from_llm(parser, llm) | T | 解析失败时让 LLM 自我修复 |
RetryOutputParser.from_llm(parser, llm) | T | 类似但带原始 prompt 上下文 |
17.3 get_format_instructions 的地位
所有 parser 都提供 get_format_instructions() -> str,用于塞进 prompt:
parser = PydanticOutputParser(pydantic_object=MySchema)
prompt = ChatPromptTemplate.from_messages([
("system", "输出必须严格遵守格式。\n{format_instructions}"),
("human", "{query}"),
]).partial(format_instructions=parser.get_format_instructions())
chain = prompt | model | parser
但在 v1.0 时代,结构化输出优先走 model.with_structured_output(Schema),比 parser + 模板更稳定。只有 provider 不支持原生结构化时才回落到 parser。
18. Partner 包生态
18.1 langchain-openai
from langchain_openai import ChatOpenAI, AzureChatOpenAI, OpenAIEmbeddings
model = ChatOpenAI(
model="gpt-5.2",
temperature=0,
streaming=True,
use_responses_api=True, # o-series / server-side tool 必开
reasoning_effort="medium",
service_tier="scale",
logprobs=True,
top_logprobs=5,
include=["reasoning.summary"], # 请求推理摘要回显
strict=True, # structured output 严格 schema
)
emb = OpenAIEmbeddings(model="text-embedding-3-large", dimensions=1024)
18.2 langchain-anthropic
from langchain_anthropic import ChatAnthropic
from langchain_anthropic.middleware import AnthropicPromptCachingMiddleware
model = ChatAnthropic(
model="claude-sonnet-4-6",
max_tokens=8192,
thinking={"type": "enabled", "budget_tokens": 10_000}, # extended thinking
)
agent = create_agent(
model,
tools=[...],
middleware=[AnthropicPromptCachingMiddleware(ttl="5m")],
)
Anthropic 特殊工具(text_editor、memory_tool、file_search、bash_tool)以 middleware 形式接入,而不是手写 tool。
18.3 langchain-google-genai v4.0.0+
已迁移到合并后的 google-genai SDK,同时覆盖 Gemini Developer API 与 Vertex AI 的 Gemini,取代了部分 langchain-google-vertexai 功能:
from langchain_google_genai import ChatGoogleGenerativeAI, GoogleGenerativeAIEmbeddings
model = ChatGoogleGenerativeAI(model="gemini-2.5-pro", temperature=0)
18.4 langchain-aws(独立 repo)
from langchain_aws import ChatBedrockConverse # 推荐走 Converse API
# from langchain_aws import ChatBedrock # 旧接口
# from langchain_aws import BedrockLLM, BedrockEmbeddings
model = ChatBedrockConverse(
model="anthropic.claude-sonnet-4-6",
region_name="us-east-1",
)
另含 AmazonKendraRetriever、Neptune graph、DynamoDB 对话历史等。
18.5 Partner 包的公约
- peer depend on
langchain-core:升 core 不强制升 partner。 - 跑
standard-tests:模型必须通过同一套 BaseChatModel 测试;否则 PR 无法合并。 - 模型 ID 透传:新模型一出,provider SDK 能调就能用,不需要 LangChain 升级。
19. v0.x → v1.0 迁移要点
19.1 一眼看懂的变化清单
langchainnamespace 大瘦身:老 chain / 老 agent / 老 memory /MultiQueryRetriever/EnsembleRetriever等都搬到langchain-classic。- Agent 只剩
create_agent:AgentExecutor、create_tool_calling_agent、create_openai_tools_agent、create_openai_functions_agent、create_react_agent(字符串解析)、create_json_agent、create_structured_chat_agent、initialize_agent全部 deprecate。 langgraph.prebuilt.create_react_agent也 deprecate,别名转发到langchain.agents.create_agent。- 自定义 state 必须是 TypedDict:Pydantic / dataclass 被明确拒绝。
- Middleware 取代所有 hook:
pre_model_hook/post_model_hook/runtime_config归一到 middleware。 - 标准化 content blocks:
AIMessage.content_blocks属性统一多模态 / 推理 / 引用。 - 消息 API 收紧:
.text()→ 属性;AIMessage.example移除。 - 结构化输出:prompted JSON
response_format被移除;必须用 Auto/Provider/Tool Strategy 或with_structured_output。 create_agent(tools=...)拒绝ToolNode:只接受BaseTool | Callable | dict。- Memory 全部迁移到 LangGraph:Checkpointer + Store。
- 文档迁到
docs.langchain.com/oss/python/...。
19.2 典型迁移 diff
# v0.x
from langchain.agents import AgentExecutor, create_tool_calling_agent
prompt = ChatPromptTemplate.from_messages([
("system","You are helpful."),
("placeholder","{chat_history}"),
("human","{input}"),
("placeholder","{agent_scratchpad}"),
])
agent = create_tool_calling_agent(llm, tools, prompt)
executor = AgentExecutor(agent=agent, tools=tools, verbose=True, max_iterations=10)
executor.invoke({"input": "...", "chat_history": history})
# v1.0
from langchain.agents import create_agent
agent = create_agent(model, tools, prompt="You are helpful.")
agent.invoke(
{"messages": history + [HumanMessage("...")]},
config={"recursion_limit": 21}, # 大致对应 max_iterations=10
)
19.3 v1.1 要点
- 新增
ModelRetryMiddleware。 - 修复 v1.1.0 的一个 regression:
langchain.agents.__init__一度漏出create_agent(patch 版已修)。 - 稳定性与可观测性增强,无 breaking。
19.4 迁移心法
- 先跑 classic 兼容:
pip install langchain-classic,加from langchain_classic.memory import ConversationBufferMemory这类 alias 让旧代码先活着。 - 再拆 Agent:把
AgentExecutor替换成create_agent,同步把 hook 改写成 middleware。 - 最后拆 state:如果用了 Pydantic / dataclass state,必须转成
TypedDict。
20. LangChain 与 LangGraph 的关系与选型
20.1 层级关系
LangSmith ← 可观测(独立平台)
↑ trace
LangChain (create_agent / Middleware / Chains / Retrievers)
↓ compile
LangGraph (StateGraph / Pregel / Checkpointer / Interrupt)
↓ depends on
langchain-core (Runnable / BaseMessage / BaseTool / Callbacks)
20.2 选型决策树
优先用 LangChain create_agent:
- 标准 tool-calling agent
- 单线性或轻度分支工作流
- RAG 管道、chatbot、原型
- 产品首月想快速上线
下沉到 LangGraph 原生:
- 复杂分支 / 循环 / 子图
- 多 Agent 协作(supervisor / hierarchical / swarm)
- 需要 time-travel、replay、interrupt
- 长耗时持久化任务(数小时/数天)
- 节点级流式、背压控制
混合使用(最常见):
- 顶层用 LangGraph
StateGraph; - 每个节点内部是
create_agent或 LCEL chain; - Checkpointer、Store、tools、messages 全局共享。
20.3 互操作要点
create_agent的返回值 就是 一个 LangGraphCompiledStateGraph,可以.invoke / .stream / .astream_events / .get_state / .update_state。- Middleware 只对 LangChain 有效;在原生 LangGraph 里要靠节点 + 边实现等效行为。
- 工具、消息、checkpointer、store、模型——两边共享同一套抽象,不存在"LangChain 版 tool"和"LangGraph 版 tool"。
20.4 一条经验法则
如果你不确定,从 LangChain 起步,等到遇到
create_agent的 API 没法表达你的需求时,再切到 LangGraph 原生图。不要一开始就跳到StateGraph。
21. 生产最佳实践
21.1 可观测性
- 永远开 LangSmith:
LANGSMITH_TRACING=true是零成本的。 - 关键 Runnable 加
with_config({"run_name": "xxx"}),trace 里可读性高一个数量级。 metadata={"user_id": ..., "session_id": ..., "env": ...}:这些字段在 LangSmith 里直接可筛可 group。
21.2 可靠性
- 重试:关键 LLM 调用走
.with_retry(stop_after_attempt=3, wait_exponential_jitter=True),或用ModelRetryMiddleware。 - 回退:
primary.with_fallbacks([fallback_model])处理 provider 断线。 - 超时:在 provider client 层设置 timeout(OpenAI 的
timeout=...),LangChain 不做全局超时。 - 幂等:对外部副作用工具用
tool_call.id做去重;checkpointer 恢复时同一个 tool call 不会重跑(只要你的工具自身幂等)。
21.3 成本控制
UsageMetadataCallbackHandler聚合 token usage。- 大 toolset 走
LLMToolSelectorMiddleware,用廉价模型预筛。 - 缓存:OpenAI 的 prompt caching 对稳定前缀自动生效;Anthropic 用
AnthropicPromptCachingMiddleware。 - 结构化输出优先走
ProviderStrategy(native JSON mode),比强制 tool call 便宜。
21.4 安全
- PII:
PIIMiddleware做输入/输出/工具结果的掩码或硬阻断。 - 工具访问控制:在
wrap_tool_call中做权限校验;InjectedState注入用户身份。 - configurable="any":永不直接暴露给终端用户。
- prompt injection:retriever 召回的文本应该过滤(或限制到工具使用场景下不执行指令)。
21.5 并发与吞吐
.abatch(inputs, max_concurrency=N)是最直接的并发工具。- I/O 密集链路全走 async(
ainvoke / astream / abatch)。 - LangGraph 的多 Agent 并行默认走
Send,不是asyncio——但底层仍然是 async。
21.6 版本管理
- 把
langchain、langchain-core、partner 包、langgraph都 pin 到 minor(例如langchain~=1.1,langchain-core~=1.3)。 - 关注
langchain/changelog的 patch 公告;v1 承诺 "no breaking until 2.0",但 bug fix 随时可能改行为细节。
22. API 快速参考
22.1 Runnable 方法速查
| 方法 | 返回 | 用途 |
|---|---|---|
invoke(input, config) | Output | 同步单次 |
ainvoke(input, config) | Output | 异步单次 |
stream(input, config) | Iterator[Output] | 同步流 |
astream(input, config) | AsyncIterator[Output] | 异步流 |
batch(inputs, config) | list[Output] | 同步批量 |
abatch(inputs, config) | list[Output] | 异步批量 |
astream_events(input, config, version="v2") | AsyncIterator[StreamEvent] | 高保真事件流 |
with_config(config) | Runnable | 附加配置 |
with_retry(...) | Runnable | 重试 |
with_fallbacks([...]) | Runnable | 回退 |
with_listeners(on_start, on_end, on_error) | Runnable | 侦听 |
bind(**kwargs) | RunnableBinding | 冻结参数 |
assign(**mappers) | RunnableAssign | dict 追加字段 |
get_graph(config) | Graph | 画图 |
22.2 创建 Agent 速查
from langchain.agents import create_agent, AgentState
from langchain.agents.middleware import (
AgentMiddleware, before_model, after_model,
wrap_model_call, wrap_tool_call,
SummarizationMiddleware, ModelRetryMiddleware, PIIMiddleware,
LLMToolSelectorMiddleware,
)
from langchain.agents.structured_output import (
AutoStrategy, ProviderStrategy, ToolStrategy,
StructuredOutputError, MultipleStructuredOutputsError,
StructuredOutputValidationError,
)
from langchain.chat_models import init_chat_model
22.3 工具速查
from langchain_core.tools import (
tool, BaseTool, StructuredTool, Tool,
InjectedToolArg, InjectedToolCallId,
)
from langgraph.prebuilt import InjectedState, InjectedStore
from langchain.tools.retriever import create_retriever_tool
22.4 消息速查
from langchain_core.messages import (
BaseMessage, HumanMessage, AIMessage, SystemMessage,
ToolMessage, FunctionMessage, ChatMessage,
AIMessageChunk, HumanMessageChunk,
trim_messages,
)
# 标准 content blocks(v1)
# AIMessage.content_blocks: list[TextContentBlock | ReasoningContentBlock |
# Citation | ToolCall | ServerToolCall | ImageContentBlock |
# AudioContentBlock | VideoContentBlock | FileContentBlock |
# NonStandardContentBlock]
22.5 关键源码路径
| 文件 | 内容 |
|---|---|
libs/core/langchain_core/runnables/base.py | Runnable, RunnableSequence, RunnableParallel, RunnableBinding |
libs/core/langchain_core/runnables/passthrough.py | RunnablePassthrough, RunnableAssign |
libs/core/langchain_core/runnables/branch.py | RunnableBranch |
libs/core/langchain_core/runnables/config.py | RunnableConfig、回调传播、contextvars |
libs/core/langchain_core/language_models/base.py | BaseLanguageModel, LangSmithParams |
libs/core/langchain_core/language_models/chat_models.py | BaseChatModel |
libs/core/langchain_core/language_models/llms.py | BaseLLM, LLM |
libs/core/langchain_core/messages/ | 所有消息类 |
libs/core/langchain_core/messages/content_blocks.py | 标准 content blocks |
libs/core/langchain_core/prompts/chat.py | ChatPromptTemplate, MessagesPlaceholder |
libs/core/langchain_core/output_parsers/pydantic.py | PydanticOutputParser |
libs/core/langchain_core/tools/ | BaseTool, StructuredTool, @tool |
libs/core/langchain_core/callbacks/base.py | BaseCallbackHandler |
libs/core/langchain_core/callbacks/manager.py | CallbackManager, handle_event |
libs/core/langchain_core/tracers/context.py | LangSmith tracer 注入 |
libs/langchain_v1/langchain/agents/ | create_agent, AgentState |
libs/langchain_v1/langchain/agents/middleware/ | 所有 Middleware |
libs/langchain_v1/langchain/agents/structured_output/ | Auto/Provider/Tool Strategy |
libs/langchain_v1/langchain/chat_models/base.py | init_chat_model |
libs/text-splitters/langchain_text_splitters/ | 所有 splitter |
libs/langchain/ | langchain-classic(legacy) |
libs/partners/openai/, anthropic/, ... | partner 实现 |
结语
LangChain v1.0 的核心收获:
Runnable协议是根——所有东西都是 Runnable,|是协议的糖。create_agent是面——别再手写 AgentExecutor,middleware 是新的扩展点。langchain-core是骨——只要你在 core 的协议里编程,模型/provider/工具都可替换。- LangGraph 是肌肉——真正的执行运行时在底层,需要时随时下沉。
- LangSmith 是神经——零成本接入,线上问题 90% 的答案都在 trace 里。
一句话总结:用
create_agent解决 80% 的日常问题,用 LangGraph 解决剩下 20% 的复杂编排,用 LangChain Core 保证你永远不被 provider 绑死。