课程目标
精读 Agent 的状态管理机制:AgentState 的构建方式、Annotation 系统(基于 LangGraph 的 StateSchema)、reducer 函数的消息累积策略,以及状态不可变性原则。
25.1 Agent 状态的核心数据结构
每个 Agent 都维护一个状态对象,在 ReAct 循环中不断更新。内置状态定义如下:
源码位置: libs/langchain/src/agents/runtime.ts:17
export type AgentBuiltInState = {
/** 对话历史:包含所有消息 */
messages: BaseMessage[];
/** 结构化响应(仅在配置了 responseFormat 时存在) */
structuredResponse?: Record<string, unknown>;
};
messages 是 Agent 状态中最重要的字段——它记录了完整的对话历史,包括用户输入、LLM 响应、工具调用结果。
25.2 BuiltInState 与跳转控制
除了用户可见的状态,Agent 还维护内部控制状态:
源码位置: libs/langchain/src/agents/types.ts:426
export interface BuiltInState<TMessageStructure extends MessageStructure = MessageStructure> {
messages: BaseMessage<TMessageStructure>[];
__interrupt__?: Interrupt[];
/** 用于中间件控制路由跳转 */
jumpTo?: JumpToTarget;
}
jumpTo 是中间件用来控制执行流程的内部字段。可选的跳转目标:
源码位置: libs/langchain/src/agents/constants.ts
export const JUMP_TO_TARGETS = ["model", "tools", "end"] as const;
export type JumpToTarget = (typeof JUMP_TO_TARGETS)[number];
25.3 createAgentState — 状态 Schema 的构建
createAgentState() 是 Agent 状态系统的核心函数。它将用户自定义 schema、中间件 schema 和内置字段合并为一个完整的状态定义。
源码位置: libs/langchain/src/agents/annotation.ts:24
export function createAgentState<
TStateSchema extends StateDefinitionInit | undefined = undefined,
TMiddleware extends readonly AgentMiddleware<any, any, any>[] = [],
>(
hasStructuredResponse = true,
stateSchema: TStateSchema,
middlewareList: TMiddleware = [] as unknown as TMiddleware
) {
const stateFields: Record<string, any> = {
// jumpTo 用于内部导航控制,使用 UntrackedValue 不参与 checkpoint
jumpTo: new UntrackedValue<JumpToTarget>(),
};
const inputFields: Record<string, any> = {};
const outputFields: Record<string, any> = {};
// 合并用户 schema
if (stateSchema) { applySchema(stateSchema); }
// 合并 middleware schema
for (const middleware of middlewareList) {
if (middleware.stateSchema) { applySchema(middleware.stateSchema); }
}
// 只在有 responseFormat 时才包含 structuredResponse
if (hasStructuredResponse) {
outputFields.structuredResponse = new UntrackedValue<any>();
}
return {
state: new StateSchema({ messages: MessagesValue, ...stateFields }),
input: new StateSchema({ messages: MessagesValue, ...inputFields }),
output: new StateSchema({ messages: MessagesValue, ...outputFields }),
};
}
关键设计:
- 三份 Schema:
state(完整状态)、input(输入通道)、output(输出通道) MessagesValue:内置的消息 reducer,自动处理消息的追加与合并UntrackedValue:不参与 checkpoint 持久化的临时值- 下划线前缀约定:以
_开头的字段是私有状态,不暴露到 input/output 通道
25.4 Schema 类型支持
createAgentState 的 applySchema 内部函数支持两种 Schema 格式:
25.4.1 Zod Schema
import { z } from "zod";
import { createAgent } from "langchain";
const agent = createAgent({
model: "openai:gpt-4o",
tools: [],
stateSchema: z.object({
userId: z.string(),
conversationCount: z.number().default(0),
}),
});
Zod schema 通过 getInteropZodObjectShape() 提取字段,通过 schemaMetaRegistry 查找 reducer 元数据。
25.4.2 LangGraph StateSchema
import { StateSchema, ReducedValue } from "@langchain/langgraph";
import { z } from "zod";
const agent = createAgent({
model: "openai:gpt-4o",
tools: [],
stateSchema: new StateSchema({
count: z.number().default(0),
history: new ReducedValue(
z.array(z.string()).default(() => []),
{
inputSchema: z.string(),
reducer: (current, next) => [...current, next],
}
),
}),
});
StateSchema 提供更精细的控制:
ReducedValue:定义自定义 reducer 函数UntrackedValue:不持久化的临时状态
25.5 Reducer 函数:状态如何累积
Reducer 是 LangGraph 状态管理的核心概念。它定义了新值如何与旧值合并。
25.5.1 消息的内置 Reducer
messages 字段使用 MessagesValue,这是 LangGraph 提供的内置 reducer。它的行为是追加新消息到列表末尾,而不是替换整个列表。
状态更新前: messages = [HumanMessage("你好")]
节点返回: messages = [AIMessage("你好!")]
状态更新后: messages = [HumanMessage("你好"), AIMessage("你好!")]
25.5.2 自定义 Reducer
const countingState = new StateSchema({
// 计数器:每次更新累加
count: new ReducedValue(
z.number().default(0),
{
reducer: (current: number, next: number) => current + next,
}
),
// 标签集合:合并去重
tags: new ReducedValue(
z.array(z.string()).default(() => []),
{
inputSchema: z.string(),
reducer: (current: string[], next: string) =>
current.includes(next) ? current : [...current, next],
}
),
});
ReducedValue 的两个关键参数:
valueSchema:状态值的完整类型inputSchema(可选):输入值的类型(可以与状态值不同)reducer:(current, next) => merged合并函数
25.6 StateManager — 节点间状态共享
StateManager 负责在不同节点间共享状态,是 ReactAgent 的内部工具。
源码位置: libs/langchain/src/agents/state.ts:16
export class StateManager {
#nodes = new Map<string, AgentNode[]>();
addNode(middleware: AgentMiddleware, node: AgentNode) {
this.#nodes.set(middleware.name, [
...(this.#nodes.get(middleware.name) ?? []),
node,
]);
}
getState(name: string) {
const middlewareNodes = this.#nodes.get(name) ?? [];
const state = middlewareNodes.reduce((prev, node) => {
return { ...prev, ...((node.getState() as Record<string, unknown>) ?? {}) };
}, {} as Record<string, unknown>);
// jumpTo 是内部属性,不暴露给中间件
delete state.jumpTo;
return state;
}
}
不可变性原则:注意 getState 使用 ...prev 展开运算符创建新对象,而非直接修改旧状态。
25.7 用户输入类型
UserInput 定义了 invoke() 和 stream() 方法接受的输入类型:
源码位置: libs/langchain/src/agents/types.ts:445
export type UserInput<TStateSchema extends StateDefinitionInit | undefined = undefined> =
InferSchemaInput<TStateSchema> & {
messages: Messages;
};
messages 是必须的,自定义 schema 的字段按照其定义是否可选而定。
25.8 私有状态约定
以下划线 _ 开头的状态字段被视为私有状态:
// annotation.ts 中的判断逻辑
const isPrivate = key.startsWith("_");
if (!isPrivate) {
inputFields[key] = fieldSchema;
outputFields[key] = fieldSchema;
}
私有状态的特性:
- 保存在图的内部状态中,参与 checkpoint 持久化
- 不暴露为 input 或 output 通道
- 适合中间件内部使用的状态(如调用计数器、上下文缓存)
25.9 实战练习:自定义状态的 Agent
import { z } from "zod";
import { createAgent, tool } from "langchain";
import { StateSchema, ReducedValue, MemorySaver } from "@langchain/langgraph";
// 定义带 reducer 的自定义状态
const agentState = new StateSchema({
userId: z.string().optional(),
// 追踪工具调用次数
toolCallCount: new ReducedValue(
z.number().default(0),
{ reducer: (current: number, next: number) => current + next }
),
// 追踪搜索过的查询词
searchHistory: new ReducedValue(
z.array(z.string()).default(() => []),
{
inputSchema: z.string(),
reducer: (current: string[], next: string) => [...current, next],
}
),
});
const searchTool = tool(
async ({ query }) => `搜索结果:关于「${query}」的信息`,
{
name: "search",
description: "搜索信息",
schema: z.object({ query: z.string() }),
}
);
const agent = createAgent({
model: "openai:gpt-4o",
tools: [searchTool],
stateSchema: agentState,
checkpointer: new MemorySaver(),
});
// 第一次调用
const result1 = await agent.invoke(
{
messages: [{ role: "user", content: "搜索 LangChain.js" }],
userId: "user-001",
},
{ configurable: { thread_id: "thread-1" } }
);
console.log("工具调用次数:", result1.toolCallCount);
console.log("搜索历史:", result1.searchHistory);
// 第二次调用(同一线程,状态持续累积)
const result2 = await agent.invoke(
{ messages: [{ role: "user", content: "再搜索 LangGraph" }] },
{ configurable: { thread_id: "thread-1" } }
);
console.log("累积工具调用次数:", result2.toolCallCount);
console.log("累积搜索历史:", result2.searchHistory);
25.10 源码精读路线
| 优先级 | 文件 | 关注点 |
|---|---|---|
| P0 | agents/annotation.ts | createAgentState() — 状态 Schema 的构建 |
| P0 | agents/runtime.ts:17-44 | AgentBuiltInState — 内置状态类型定义 |
| P1 | agents/state.ts | StateManager — 节点间状态共享 |
| P1 | agents/types.ts:426-450 | BuiltInState, UserInput — 状态类型 |
| P2 | agents/constants.ts | JumpToTarget — 跳转目标定义 |
| P2 | agents/middleware/types.ts:97-123 | InferSchemaValueType — 状态类型推断 |
本课收获总结
| 级别 | 你应该掌握的 |
|---|---|
| 🟢 基础 | 理解 Agent 状态的核心数据结构:messages 列表 + 自定义字段 |
| 🔵 中阶 | 掌握 Zod schema 和 StateSchema 两种状态定义方式 |
| 🟡 高阶 | 理解 ReducedValue 的 reducer 机制:消息如何累积和更新 |
| 🟠 资深 | 分析 state/input/output 三通道分离设计;理解私有状态约定 |
| 🔴 架构 | 设计有状态 Agent 的内存管理策略:短期(UntrackedValue)vs 长期(checkpoint) |
下一课预告
第 26 课深入 Agent 中间件与节点系统 —— 理解 Agent 图的中间件钩子、内置节点和 RunnableCallable 的桥接作用。