Skip to content

自定义中间件 ​

通过实现运行在 Agent 执行流程中特定节点的钩子函数,来构建自定义中间件。

钩子 ​

中间件提供了两种风格的钩子来拦截 Agent 的执行:

节点风格钩子

在特定的执行节点按顺序运行。

包裹风格钩子

环绕每次模型或工具调用运行。

节点风格钩子 ​

在特定的执行节点按顺序运行。用于日志记录、校验和状态更新。

选择中间件所需的钩子。你可以在节点风格钩子和包裹风格钩子之间进行选择。

节点风格钩子在特定的执行节点运行:

钩子运行契机
beforeAgentAgent 启动前(每次调用运行一次)
beforeModel每次模型调用前
afterModel每次模型响应后
afterAgentAgent 完成后(每次调用运行一次)

包裹风格钩子环绕每次调用运行,让你能够控制执行流程:

钩子运行契机
wrapModelCall环绕每次模型调用
wrapToolCall环绕每次工具调用

示例:

typescript
import { createMiddleware, AIMessage } from "langchain";

const createMessageLimitMiddleware = (maxMessages: number = 50) => {
  return createMiddleware({
    name: "MessageLimitMiddleware",
    beforeModel: {
      canJumpTo: ["end"],
      hook: (state) => {
        if (state.messages.length === maxMessages) {
          return {
            messages: [new AIMessage("Conversation limit reached.")],
            jumpTo: "end",
          };
        }
        return;
      }
    },
    afterModel: (state) => {
      const lastMessage = state.messages[state.messages.length - 1];
      console.log(`Model returned: ${lastMessage.content}`);
      return;
    },
  });
};

包裹风格钩子 ​

拦截执行并控制处理函数(handler)的调用时机。用于重试、缓存和数据转换。

由你来决定处理函数是被调用零次(短路)、一次(正常流程)还是多次(重试逻辑)。

可用钩子:

  • wrapModelCall - 环绕每次模型调用
  • wrapToolCall - 环绕每次工具调用

示例:

typescript
import { createMiddleware } from "langchain";

const createRetryMiddleware = (maxRetries: number = 3) => {
  return createMiddleware({
    name: "RetryMiddleware",
    wrapModelCall: (request, handler) => {
      for (let attempt = 0; attempt < maxRetries; attempt++) {
        try {
          return handler(request);
        } catch (e) {
          if (attempt === maxRetries - 1) {
            throw e;
          }
          console.log(`Retry ${attempt + 1}/${maxRetries} after error: ${e}`);
        }
      }
      throw new Error("Unreachable");
    },
  });
};

状态更新 ​

节点风格钩子和包裹风格钩子都可以更新 Agent 状态。两者的机制有所不同:

  • 节点风格钩子(beforeAgent、beforeModel、afterModel、afterAgent):直接返回一个字典(dict/对象)。该字典会通过图的 reducer 应用到 Agent 状态中。
  • 包裹风格钩子(wrapModelCall、wrapToolCall):对于模型调用,直接返回一个 Command,以便在返回模型响应的同时注入状态更新。对于工具调用,同样直接返回一个 Command。当需要根据模型或工具调用期间运行的逻辑来跟踪或更新状态时(例如摘要触发点、使用量元数据,或根据请求/响应计算出的自定义字段),请使用这些钩子。

节点风格钩子 ​

从节点风格钩子中返回一个字典,以将更新合并到 Agent 状态中。字典的键映射到状态字段。

typescript
import { createMiddleware } from "langchain";
import * as z from "zod";

const trackingStateSchema = z.object({
  modelCallCount: z.number().default(0),
});

const incrementAfterModel = createMiddleware({
  name: "incrementAfterModel",
  stateSchema: trackingStateSchema,
  afterModel: (state) => {
    return { modelCallCount: state.modelCallCount + 1 };
  },
});

包裹风格钩子 ​

直接从 wrapModelCall 返回一个 Command,以从模型调用层注入状态更新:

typescript
import * as z from "zod";
import { createMiddleware } from "langchain";
import { Command } from "@langchain/langgraph";

const usageTrackingStateSchema = z.object({
  lastModelCallTokens: z.number().optional(),
});

const trackUsage = createMiddleware({
  name: "trackUsage",
  stateSchema: usageTrackingStateSchema,
  wrapModelCall: async (request, handler) => {
    const response = await handler(request);
    return new Command({ update: { lastModelCallTokens: 150 } });
  },
});

Command 会流经图的 reducer,因此更新能够被正确应用,并且消息是追加累加的,而不是替换现有状态。

多个中间件的组合 ​

当多个中间件层返回响应时,框架会传递最后生成的 AIMessage:

  • AIMessage 流转: 每个中间件的 handler() 都会接收来自上一层的 AIMessage。当某个中间件返回 AIMessage 时,它会成为下一个中间件处理函数的输入。
  • 不包含消息更新的 Command 视为透传: 如果中间件返回的 Command 其状态更新未涉及 messages,框架会将其视为消息流转中的空操作(no-op)。下一个中间件的处理函数将接收来自返回 Command 那个中间件之前的中间件所产生的 AIMessage。
  • Reducer 行为与重试安全性: Command 仍然会通过 reducer 生效(消息追加累加,发生冲突时外层优先)。重试逻辑会丢弃较早调用的 Command。
typescript
import * as z from "zod";
import { createMiddleware } from "langchain";
import { Command, StateSchema, ReducedValue } from "@langchain/langgraph";
import { AIMessage, SystemMessage } from "@langchain/core/messages";

/** 后赢(Last-wins)reducer:当两个中间件同时写入时,外层覆盖内层。 */
const customMiddlewareStateSchema = new StateSchema({
  traceLayer: new ReducedValue(
    z.string().optional(),
    { reducer: (a, b) => b },
  ),
});

const outerMiddleware = createMiddleware({
  name: "OuterMiddleware",
  stateSchema: customMiddlewareStateSchema,
  wrapModelCall: async (_request, handler) => {
    await handler(_request);
    return new Command({
      update: {
        traceLayer: "outer",
        messages: [new SystemMessage({ content: "[Outer ran]" })],
      },
    });
  },
});

const innerMiddleware = createMiddleware({
  name: "InnerMiddleware",
  stateSchema: customMiddlewareStateSchema,
  wrapModelCall: async (_request, handler) => {
    await handler(_request);
    return new Command({
      update: {
        traceLayer: "inner",
        messages: [new SystemMessage({ content: "[Inner ran]" })],
      },
    });
  },
});

创建中间件 ​

python An AgentMiddleware 子类可以声明三个类属性,Agent 工厂会在编译时读取它们:

  • state_schema — 扩展带有自定义字段的 Agent 状态。参见自定义状态模式。
  • tools — 注册随中间件一起提供的额外工具(例如待办列表中间件上的 write_todos)。
  • transformers — 注册具备作用域感知的流转换器工厂。参见自定义流转换器。 :::

createMiddleware 接受三个配置字段,Agent 工厂会在编译时读取它们:

  • stateSchema — 扩展带有自定义字段的 Agent 状态。参见自定义状态模式。
  • tools — 注册随中间件一起提供的额外工具。
  • streamTransformers — 注册具备作用域感知的流转换器工厂。参见自定义流转换器。

示例:

python
from langchain.agents.middleware import (
    AgentMiddleware,
    AgentState,
    ModelRequest,
    ModelResponse,
)
from langgraph.runtime import Runtime
from typing import Any, Callable

class LoggingMiddleware(AgentMiddleware):
    def before_model(self, state: AgentState, runtime: Runtime) -> dict[str, Any] | None:
        print(f"About to call model with {len(state['messages'])} messages")
        return None

    def after_model(self, state: AgentState, runtime: Runtime) -> dict[str, Any] | None:
        print(f"Model returned: {state['messages'][-1].content}")
        return None

    async def abefore_model(
        self, state: AgentState, runtime: Runtime
    ) -> dict[str, Any] | None:
        # before_model 的异步版本
        return None

    async def aafter_model(
        self, state: AgentState, runtime: Runtime
    ) -> dict[str, Any] | None:
        # after_model 的异步版本
        print(f"Model returned: {state['messages'][-1].content}")
        return None


agent = create_agent(
    model="gpt-5.5",
    middleware=[LoggingMiddleware()],
    tools=[...],
)

何时使用类:

  • 为同一个钩子同时定义同步和异步实现
  • 单个中间件中需要多个钩子
  • 需要复杂配置(例如可配置的阈值、自定义模型)
  • 通过初始化时配置在不同项目间复用

:::

使用 createMiddleware 函数定义自定义中间件:

typescript
import { createMiddleware } from "langchain";

const loggingMiddleware = createMiddleware({
  name: "LoggingMiddleware",
  beforeModel: (state) => {
    console.log(`About to call model with ${state.messages.length} messages`);
    return;
  },
  afterModel: (state) => {
    const lastMessage = state.messages[state.messages.length - 1];
    console.log(`Model returned: ${lastMessage.content}`);
    return;
  },
});

自定义状态模式 ​

如果你的中间件需要在不同钩子之间跟踪状态,中间件可以使用自定义属性扩展 Agent 的状态。这使得中间件能够:

  • 跨执行过程跟踪状态:维护在整个 Agent 执行生命周期中持久存在的计数器、标志位或其他值

  • 在钩子之间共享数据:将信息从 beforeModel 传递给 afterModel,或在不同的中间件实例之间传递

  • 实现横切关注点:在不修改核心 Agent 逻辑的情况下,添加限流、使用量统计、用户上下文或审计日志等功能

  • 做出条件决策:使用累积的状态来决定是继续执行、跳转到不同节点,还是动态修改行为

typescript
import { createMiddleware, createAgent, HumanMessage } from "langchain";
import { StateSchema } from "@langchain/langgraph";
import * as z from "zod";

const CustomState = new StateSchema({
  modelCallCount: z.number().default(0),
  userId: z.string().optional(),
});

const callCounterMiddleware = createMiddleware({
  name: "CallCounterMiddleware",
  stateSchema: CustomState,
  beforeModel: {
    canJumpTo: ["end"],
    hook: (state) => {
      if (state.modelCallCount > 10) {
        return { jumpTo: "end" };
      }

      return;
    },
  },
  afterModel: (state) => {
    return { modelCallCount: state.modelCallCount + 1 };
  },
});

const agent = createAgent({
  model: "gpt-5.5",
  tools: [...],
  middleware: [callCounterMiddleware],
});

const result = await agent.invoke({
  messages: [new HumanMessage("Hello")],
  modelCallCount: 0,
  userId: "user-123",
});

状态字段可以是公开的(public),也可以是私有的(private)。以下划线(_)开头的字段被视为私有字段,不会包含在 Agent 的返回结果中。仅返回公开字段(没有前导下划线的字段)。

这对于存储不应暴露给调用方的中间件内部状态非常有用,例如临时跟踪变量或内部标志位:

typescript
import { StateSchema } from "@langchain/langgraph";
import * as z from "zod";

const PrivateState = new StateSchema({
  // 公开字段 - 包含在 invoke 结果中
  publicCounter: z.number().default(0),
  // 私有字段 - 排除在 invoke 结果之外
  _internalFlag: z.boolean().default(false),
});

const middleware = createMiddleware({
  name: "ExampleMiddleware",
  stateSchema: PrivateState,
  afterModel: (state) => {
    // 两个字段在执行期间均可访问
    if (state._internalFlag) {
      return { publicCounter: state.publicCounter + 1 };
    }
    return { _internalFlag: true };
  },
});

const result = await agent.invoke({
  messages: [new HumanMessage("Hello")],
  publicCounter: 0
});

// result 仅包含 publicCounter,不包含 _internalFlag
console.log(result.publicCounter); // 1
console.log(result._internalFlag); // undefined

自定义流转换器 ​

NOTE

中间件注册的转换器需要 langchain@1.4.3 或更高版本。

中间件可以注册流转换器工厂,将来自实时 Agent 流的事件投射到类型化的扩展通道上。这对于显露计数器、旁路产物、部分输出或网络层脱敏非常有用,且无需与框架的内置投射耦合。

在编译时,中间件注册的工厂会与调用方直接传递给 Agent 工厂的内容进行合并。最终排序规则会将内置的 ToolCallTransformer 保留在最前面,并让调用方提供的条目排在最后。

将 streamTransformers 作为工厂元组传递给 createMiddleware。每个工厂的形式为 () => StreamTransformer<any>(零参数),且每个作用域调用一次;每次调用返回一个新的转换器以保持每个子图的相互隔离。

typescript
import { createAgent, createMiddleware } from "langchain";

const toolActivityMiddleware = createMiddleware({
  name: "ToolActivityMiddleware",
  streamTransformers: [toolActivityTransformer],
});

const agent = createAgent({
  model: "gpt-5-nano",
  tools: [...],
  middleware: [toolActivityMiddleware],
});

有关完整的排序规则和 PII 脱敏示例,请参阅在中间件上注册转换器。

自定义上下文 ​

中间件可以定义自定义上下文模式(context schema)以访问单次调用级别的元数据。与状态(state)不同,上下文是只读的,并且不会在各次调用之间持久化。这使其非常适合用于:

  • 用户信息:传递在执行期间不会更改的用户 ID、角色或偏好设置
  • 配置覆盖:提供单次调用设置,如速率限制或特性标志(feature flags)
  • 租户/工作区上下文:为多租户应用程序包含特定于组织的数据
  • 请求元数据:传递中间件所需的请求 ID、API 密钥或其他元数据

使用 Zod 定义上下文模式,并通过中间件钩子中的 runtime.context 访问它。上下文模式中的必填字段将在 TypeScript 层面强制执行,确保你在调用 agent.invoke() 时必须提供它们。

typescript
import { createAgent, createMiddleware, HumanMessage } from "langchain";
import * as z from "zod";

const contextSchema = z.object({
  userId: z.string(),
  tenantId: z.string(),
  apiKey: z.string().optional(),
});

const userContextMiddleware = createMiddleware({
  name: "UserContextMiddleware",
  contextSchema,
  wrapModelCall: (request, handler) => {
    // 从 runtime 中访问上下文
    const { userId, tenantId } = request.runtime.context;

    // 向系统消息中添加用户上下文
    const contextText = `User ID: ${userId}, Tenant: ${tenantId}`;
    const newSystemMessage = request.systemMessage.concat(contextText);

    return handler({
      ...request,
      systemMessage: newSystemMessage,
    });
  },
});

const agent = createAgent({
  model: "gpt-5.5",
  middleware: [userContextMiddleware],
  tools: [],
  contextSchema,
});

const result = await agent.invoke(
  { messages: [new HumanMessage("Hello")] },
  // 必须提供必填字段(userId, tenantId)
  {
    context: {
      userId: "user-123",
      tenantId: "acme-corp",
    },
  }
);

必填上下文字段:当你在 contextSchema 中定义必填字段时(没有 .optional() 或 .default() 的字段),TypeScript 将强制在 agent.invoke() 调用期间必须提供这些字段。这确保了类型安全,并避免因缺少必需上下文而导致的运行时错误。

typescript
// 如果缺少 userId 或 tenantId,这将导致 TypeScript 错误
const result = await agent.invoke(
  { messages: [new HumanMessage("Hello")] },
  { context: { userId: "user-123" } } // 错误:tenantId 是必填的
);

执行顺序 ​

当使用多个中间件时,请理解它们的执行流程:

typescript
const agent = createAgent({
  model: "gpt-5.5",
  middleware: [middleware1, middleware2, middleware3],
  tools: [...],
});

执行流程

Before 钩子按顺序运行:

  1. middleware1.before_agent()
  2. middleware2.before_agent()
  3. middleware3.before_agent()

Agent 循环开始

  1. middleware1.before_model()
  2. middleware2.before_model()
  3. middleware3.before_model()

Wrap 钩子像函数调用一样嵌套:

  1. middleware1.wrap_model_call() → middleware2.wrap_model_call() → middleware3.wrap_model_call() → model

After 钩子按逆序运行:

  1. middleware3.after_model()
  2. middleware2.after_model()
  3. middleware1.after_model()

Agent 循环结束

  1. middleware3.after_agent()
  2. middleware2.after_agent()
  3. middleware1.after_agent()

核心规则:

  • before_* 钩子:从前往后(正序)
  • after_* 钩子:从后往前(逆序)
  • wrap_* 钩子:嵌套(第一个中间件包裹所有其他中间件)

Agent 跳转 ​

要从中间件中提前退出,返回一个包含 jump_to 的字典:

可用的跳转目标:

  • 'end':跳转到 Agent 执行的末尾(或第一个 after_agent 钩子)
  • 'tools':跳转到 tools 节点
  • 'model':跳转到 model 节点(或第一个 before_model 钩子)
typescript
import { createAgent, createMiddleware, AIMessage } from "langchain";

const agent = createAgent({
  model: "gpt-5.5",
  middleware: [
    createMiddleware({
      name: "BlockedContentMiddleware",
      beforeModel: {
        canJumpTo: ["end"],
        hook: (state) => {
          if (state.messages.at(-1)?.content.includes("BLOCKED")) {
            return {
              messages: [new AIMessage("I cannot respond to that request.")],
              jumpTo: "end" as const,
            };
          }
          return;
        },
      },
    }),
  ],
});

const result = await agent.invoke({
    messages: "Hello, world! BLOCKED"
});

/**
 * Expected output:
 * I cannot respond to that request.
 */
console.log(result.messages.at(-1)?.content);

最佳实践 ​

  1. 保持中间件职责单一 —— 每个中间件应当专精于一件事
  2. 优雅地处理错误 —— 不要让中间件的错误导致 Agent 崩溃
  3. 使用合适的钩子类型:
    • 节点风格用于顺序逻辑(日志记录、校验)
    • 包裹风格用于控制流(重试、降级、缓存)
  4. 清晰记录所有自定义状态属性
  5. 在集成之前对中间件进行独立的单元测试
  6. 考虑执行顺序 —— 将关键中间件放在列表的最前面
  7. 尽可能使用内置中间件

示例 ​

动态提示词 ​

在运行时动态修改系统提示词,以便在每次模型调用前注入上下文、特定于用户的指令或其他信息。这是最常见的中间件使用场景之一。

使用 ModelRequest 中的 systemMessage 字段来读取和修改系统提示词。它包含一个 SystemMessage 对象(即使 Agent 是使用字符串 systemPrompt 创建的)。

ts
import { createMiddleware, SystemMessage, createAgent } from "langchain";

const addContextMiddleware = createMiddleware({
  name: "AddContextMiddleware",
  wrapModelCall: async (request, handler) => {
    return handler({
      ...request,
      systemMessage: request.systemMessage.concat(`Additional context.`),
    });
  },
});

const agent = createAgent({
  model: "google:gemini-3.6-flash",
  systemPrompt: "You are a helpful assistant.",
  middleware: [addContextMiddleware],
});
ts
import { createMiddleware, SystemMessage, createAgent } from "langchain";

const addContextMiddleware = createMiddleware({
  name: "AddContextMiddleware",
  wrapModelCall: async (request, handler) => {
    return handler({
      ...request,
      systemMessage: request.systemMessage.concat(`Additional context.`),
    });
  },
});

const agent = createAgent({
  model: "openai:gpt-5.5",
  systemPrompt: "You are a helpful assistant.",
  middleware: [addContextMiddleware],
});
ts
import { createMiddleware, SystemMessage, createAgent } from "langchain";

const addContextMiddleware = createMiddleware({
  name: "AddContextMiddleware",
  wrapModelCall: async (request, handler) => {
    return handler({
      ...request,
      systemMessage: request.systemMessage.concat(`Additional context.`),
    });
  },
});

const agent = createAgent({
  model: "anthropic:claude-sonnet-5",
  systemPrompt: "You are a helpful assistant.",
  middleware: [addContextMiddleware],
});
ts
import { createMiddleware, SystemMessage, createAgent } from "langchain";

const addContextMiddleware = createMiddleware({
  name: "AddContextMiddleware",
  wrapModelCall: async (request, handler) => {
    return handler({
      ...request,
      systemMessage: request.systemMessage.concat(`Additional context.`),
    });
  },
});

const agent = createAgent({
  model: "openrouter:z-ai/glm-5.2",
  systemPrompt: "You are a helpful assistant.",
  middleware: [addContextMiddleware],
});
ts
import { createMiddleware, SystemMessage, createAgent } from "langchain";

const addContextMiddleware = createMiddleware({
  name: "AddContextMiddleware",
  wrapModelCall: async (request, handler) => {
    return handler({
      ...request,
      systemMessage: request.systemMessage.concat(`Additional context.`),
    });
  },
});

const agent = createAgent({
  model: "fireworks:accounts/fireworks/models/glm-5p2",
  systemPrompt: "You are a helpful assistant.",
  middleware: [addContextMiddleware],
});
ts
import { createMiddleware, SystemMessage, createAgent } from "langchain";

const addContextMiddleware = createMiddleware({
  name: "AddContextMiddleware",
  wrapModelCall: async (request, handler) => {
    return handler({
      ...request,
      systemMessage: request.systemMessage.concat(`Additional context.`),
    });
  },
});

const agent = createAgent({
  model: "baseten:zai-org/GLM-5.2",
  systemPrompt: "You are a helpful assistant.",
  middleware: [addContextMiddleware],
});
ts
import { createMiddleware, SystemMessage, createAgent } from "langchain";

const addContextMiddleware = createMiddleware({
  name: "AddContextMiddleware",
  wrapModelCall: async (request, handler) => {
    return handler({
      ...request,
      systemMessage: request.systemMessage.concat(`Additional context.`),
    });
  },
});

const agent = createAgent({
  model: "ollama:north-mini-code-1.0",
  systemPrompt: "You are a helpful assistant.",
  middleware: [addContextMiddleware],
});

使用 SystemMessage.concat 可以保留由其他中间件创建的缓存控制元数据或结构化内容块。

动态模型选择 ​

ts
import { createMiddleware, initChatModel } from "langchain";

const models = {
  complex: await initChatModel("claude-sonnet-4-6"),
  simple: await initChatModel("claude-haiku-4-5-20251001"),
};

const dynamicModelMiddleware = createMiddleware({
  name: "DynamicModelMiddleware",
  wrapModelCall: (request, handler) => {
    const modifiedRequest = { ...request };
    if (request.messages.length > 10) {
      modifiedRequest.model = models.complex;
    } else {
      modifiedRequest.model = models.simple;
    }
    return handler(modifiedRequest);
  },
});

动态选择工具 ​

在运行时选择相关工具以提高性能和准确性。本节介绍如何过滤预先注册的工具。有关注册运行时发现的工具(例如来自 MCP 服务器),请参阅运行时工具注册。

优势:

  • 更短的提示词 - 仅暴露相关工具以降低复杂度
  • 更高的准确性 - 模型能更准确地从较少的选项中做出选择
  • 权限控制 - 根据用户权限动态过滤工具
typescript
import { createAgent, createMiddleware } from "langchain";

const toolSelectorMiddleware = createMiddleware({
  name: "ToolSelector",
  wrapModelCall: (request, handler) => {
    // 根据状态/上下文选择少量相关的工具子集
    const relevantTools = selectRelevantTools(request.state, request.runtime);
    const modifiedRequest = { ...request, tools: relevantTools };
    return handler(modifiedRequest);
  },
});

const agent = createAgent({
  model: "gpt-5.5",
  tools: allTools,
  middleware: [toolSelectorMiddleware],
});

工具调用监控 ​

ts
import { createMiddleware } from "langchain";

const toolMonitoringMiddleware = createMiddleware({
  name: "ToolMonitoringMiddleware",
  wrapToolCall: (request, handler) => {
    console.log(`Executing tool: ${request.toolCall.name}`);
    console.log(`Arguments: ${JSON.stringify(request.toolCall.args)}`);
    try {
      const result = handler(request);
      console.log("Tool completed successfully");
      return result;
    } catch (e) {
      console.log(`Tool failed: ${e}`);
      throw e;
    }
  },
});

提示词缓存(Anthropic) ​

使用 Anthropic 模型时,可以使用带有缓存控制指令的结构化内容块来缓存大型系统提示词:

装饰器

python
from langchain.agents.middleware import wrap_model_call, ModelRequest, ModelResponse
from langchain.messages import SystemMessage
from typing import Callable


@wrap_model_call
def add_cached_context(
    request: ModelRequest,
    handler: Callable[[ModelRequest], ModelResponse],
) -> ModelResponse:
    # 始终处理内容块
    new_content = list(request.system_message.content_blocks) + [
        {
            "type": "text",
            "text": "Here is a large document to analyze:\n\n<document>...</document>",
            # 截至此点的内容将被缓存
            "cache_control": {"type": "ephemeral"}
        }
    ]

    new_system_message = SystemMessage(content=new_content)
    return handler(request.override(system_message=new_system_message))

类

python
from langchain.agents.middleware import AgentMiddleware, ModelRequest, ModelResponse
from langchain.messages import SystemMessage
from typing import Callable


class CachedContextMiddleware(AgentMiddleware):
    def wrap_model_call(
        self,
        request: ModelRequest,
        handler: Callable[[ModelRequest], ModelResponse],
    ) -> ModelResponse:
        # 始终处理内容块
        new_content = list(request.system_message.content_blocks) + [
            {
                "type": "text",
                "text": "Here is a large document to analyze:\n\n<document>...</document>",
                "cache_control": {"type": "ephemeral"}  # 此内容将被缓存
            }
        ]

        new_system_message = SystemMessage(content=new_content)
        return handler(request.override(system_message=new_system_message))

注意事项:

  • ModelRequest.system_message 始终是一个 SystemMessage 对象,即使 Agent 是使用 system_prompt="string" 创建的
  • 使用 SystemMessage.content_blocks 将内容作为块列表进行访问,无论原始内容是字符串还是列表
  • 修改系统消息时,请使用 content_blocks 并追加新块以保留现有结构
  • 对于缓存控制等高级使用场景,你可以将 SystemMessage 对象直接传递给 create_agent 的 system_prompt 参数

:::

使用 ModelRequest 中的 systemMessage 字段在中间件中修改系统消息。它包含一个 SystemMessage 对象(即使 Agent 是使用字符串 systemPrompt 创建的)。

示例:链式组合中间件 - 不同的中间件可以使用不同的方法:

typescript
import { createMiddleware, SystemMessage, createAgent } from "langchain";

// 中间件 1:使用带有简单拼接的 systemMessage
const myMiddleware = createMiddleware({
  name: "MyMiddleware",
  wrapModelCall: async (request, handler) => {
    return handler({
      ...request,
      systemMessage: request.systemMessage.concat(`Additional context.`),
    });
  },
});

// 中间件 2:使用带有结构化内容的 systemMessage(保留结构)
const myOtherMiddleware = createMiddleware({
  name: "MyOtherMiddleware",
  wrapModelCall: async (request, handler) => {
    return handler({
      ...request,
      systemMessage: request.systemMessage.concat(
        new SystemMessage({
          content: [
            {
              type: "text",
              text: " More additional context. This will be cached.",
              cache_control: { type: "ephemeral", ttl: "5m" },
            },
          ],
        })
      ),
    });
  },
});

const agent = createAgent({
  model: "google:gemini-3.6-flash",
  systemPrompt: "You are a helpful assistant.",
  middleware: [myMiddleware, myOtherMiddleware],
});

生成的系统消息将为:

typescript
new SystemMessage({
  content: [
    { type: "text", text: "You are a helpful assistant." },
    { type: "text", text: "Additional context." },
    {
        type: "text",
        text: " More additional context. This will be cached.",
        cache_control: { type: "ephemeral", ttl: "5m" },
    },
  ],
});

使用 SystemMessage.concat 可以保留由其他中间件创建的缓存控制元数据或结构化内容块。

更多资源 ​