Langchain Middleware

作者 langchain-ai16a992f09ab3無授權條款1.2K 個星標收錄於 2026年10月8日更新於 2026年10月8日儲存庫2 天前更新

INVOKE THIS SKILL when you need human-in-the-loop approval, custom middleware, or structured output. Covers HumanInTheLoopMiddleware for human approval of dangerous tool calls, creating custom middleware with hooks, Command resume patterns, and structured output with Pydantic/Zod.

僅含說明AI & Agents
AI 產生的概覽

LangChain 代理中介軟體參考:人工審核、自訂掛鉤與結構化輸出。

功能
提供設定 LangChain 代理中介軟體的 Python 與 TypeScript 模式。內容涵蓋用於在敏感工具呼叫前暫停的 HumanInTheLoopMiddleware、透過 Command 實現的核准/編輯/拒絕恢復流程、wrap_tool_call 與 before_model 等自訂中介軟體掛鉤,以及使用 Pydantic 或 Zod 的結構化輸出。也列出常見設定錯誤,例如缺少 checkpointer 與 thread_id。
適用情境
適用於建置或偵錯需要在危險工具呼叫前進行人工核准、需要自訂工具呼叫攔截,或需要結構化輸出的 LangChain 代理。當代理的中斷與恢復流程因缺少 checkpointer 或 thread_id 設定而失敗時也適用。
執行需求
沒有指令碼,只有說明與程式碼範例。範例假定已安裝 LangChain 與 LangGraph 套件、使用 MemorySaver 等 checkpointer,以及模型供應商;TypeScript 範例使用 Zod。

<overview>

Middleware patterns for production LangChain agents:

  • HumanInTheLoopMiddleware / humanInTheLoopMiddleware: Pause before dangerous tool calls for human approval
  • Custom middleware: Intercept tool calls for error handling, logging, retry logic
  • Command resume: Continue execution after human decisions (approve, edit, reject)

Requirements: Checkpointer + thread_id config for all HITL workflows.

</overview>


Human-in-the-Loop

<ex-basic-hitl-setup>

<python>

Set up an agent with HITL middleware that pauses before sending emails for approval.

python
from langchain.agents import create_agentfrom langchain.agents.middleware import HumanInTheLoopMiddlewarefrom langgraph.checkpoint.memory import MemorySaverfrom langchain.tools import tool
@tooldef send_email(to: str, subject: str, body: str) -> str:    """Send an email."""    return f"Email sent to {to}"
agent = create_agent(    model="gpt-4.1",    tools=[send_email],    checkpointer=MemorySaver(),  # Required for HITL    middleware=[        HumanInTheLoopMiddleware(            interrupt_on={                "send_email": {"allowed_decisions": ["approve", "edit", "reject"]},            }        )    ],)

</python>

<typescript>

Set up an agent with HITL that pauses before sending emails for human approval.

typescript
import { createAgent, humanInTheLoopMiddleware } from "langchain";import { MemorySaver } from "@langchain/langgraph";import { tool } from "@langchain/core/tools";import { z } from "zod";
const sendEmail = tool(  async ({ to, subject, body }) => `Email sent to ${to}`,  {    name: "send_email",    description: "Send an email",    schema: z.object({ to: z.string(), subject: z.string(), body: z.string() }),  });
const agent = createAgent({  model: "anthropic:claude-sonnet-4-5",  tools: [sendEmail],  checkpointer: new MemorySaver(),  middleware: [    humanInTheLoopMiddleware({      interruptOn: { send_email: { allowedDecisions: ["approve", "edit", "reject"] } },    }),  ],});

</typescript>

</ex-basic-hitl-setup>

<ex-running-with-interrupts>

<python>

Run the agent, detect an interrupt, then resume execution after human approval.

python
from langgraph.types import Command
config = {"configurable": {"thread_id": "session-1"}}
# Step 1: Agent runs until it needs to call toolresult1 = agent.invoke({    "messages": [{"role": "user", "content": "Send email to [email protected]"}]}, config=config)
# Check for interruptif "__interrupt__" in result1:    print(f"Waiting for approval: {result1['__interrupt__']}")
# Step 2: Human approvesresult2 = agent.invoke(    Command(resume={"decisions": [{"type": "approve"}]}),    config=config)

</python>

<typescript>

Run the agent, detect an interrupt, then resume execution after human approval.

typescript
import { Command } from "@langchain/langgraph";
const config = { configurable: { thread_id: "session-1" } };
// Step 1: Agent runs until it needs to call toolconst result1 = await agent.invoke({  messages: [{ role: "user", content: "Send email to [email protected]" }]}, config);
// Check for interruptif (result1.__interrupt__) {  console.log(`Waiting for approval: ${result1.__interrupt__}`);}
// Step 2: Human approvesconst result2 = await agent.invoke(  new Command({ resume: { decisions: [{ type: "approve" }] } }),  config);

</typescript>

</ex-running-with-interrupts>

<ex-editing-tool-arguments>

<python>

Edit the tool arguments before approving when the original values need correction.

python
# Human edits the arguments — edited_action must include name + argsresult2 = agent.invoke(    Command(resume={        "decisions": [{            "type": "edit",            "edited_action": {                "name": "send_email",                "args": {                    "to": "[email protected]",  # Fixed email                    "subject": "Project Meeting - Updated",                    "body": "...",                },            },        }]    }),    config=config)

</python>

<typescript>

Edit the tool arguments before approving when the original values need correction.

typescript
// Human edits the arguments — editedAction must include name + argsconst result2 = await agent.invoke(  new Command({    resume: {      decisions: [{        type: "edit",        editedAction: {          name: "send_email",          args: {            to: "[email protected]",  // Fixed email            subject: "Project Meeting - Updated",            body: "...",          },        },      }]    }  }),  config);

</typescript>

</ex-editing-tool-arguments>

<ex-rejecting-with-feedback>

<python>

Reject a tool call and provide feedback explaining why it was rejected.

python
# Human rejectsresult2 = agent.invoke(    Command(resume={        "decisions": [{            "type": "reject",            "feedback": "Cannot delete customer data without manager approval",        }]    }),    config=config)

</python>

</ex-rejecting-with-feedback>

<ex-multiple-tools-different-policies>

<python>

Configure different HITL policies for each tool based on risk level.

python
agent = create_agent(    model="gpt-4.1",    tools=[send_email, read_email, delete_email],    checkpointer=MemorySaver(),    middleware=[        HumanInTheLoopMiddleware(            interrupt_on={                "send_email": {"allowed_decisions": ["approve", "edit", "reject"]},                "delete_email": {"allowed_decisions": ["approve", "reject"]},  # No edit                "read_email": False,  # No HITL for reading            }        )    ],)

</python>

</ex-multiple-tools-different-policies>

<boundaries>

What You CAN Configure

  • Which tools require approval (per-tool policies)
  • Allowed decisions per tool (approve, edit, reject)
  • Custom middleware hooks: before_model, after_model, wrap_tool_call, before_agent, after_agent
  • Tool-specific middleware (apply only to certain tools)

</boundaries>


Custom Middleware Hooks

Six decorator hooks are available. Two patterns:

  • Wrap hooks (wrap_tool_call, wrap_model_call): (request, handler) — call handler(request) to proceed, or return early to short-circuit.
  • Before/after hooks (before_model, after_model, before_agent, after_agent): (state, runtime) — inspect or modify state. Return None or a dict of state updates.

<ex-wrap-tool-call>

<python>

@wrap_tool_call intercepts tool execution. Do NOT use yield — it creates a generator and causes NotImplementedError.

python
from langchain.agents.middleware import wrap_tool_call
@wrap_tool_calldef retry_middleware(request, handler):    for attempt in range(3):        try:            return handler(request)        except Exception:            if attempt == 2:                raise
@wrap_tool_calldef guard_middleware(request, handler):    if request.tool_call["name"] == "dangerous_tool":        return "This tool is disabled"  # short-circuit    return handler(request)

</python>

<typescript>

createMiddleware({ wrapToolCall }) intercepts tool execution.

typescript
import { createMiddleware } from "langchain";
const retryMiddleware = createMiddleware({  wrapToolCall: async (request, handler) => {    for (let attempt = 0; attempt < 3; attempt++) {      try { return await handler(request); }      catch (e) { if (attempt === 2) throw e; }    }  },});

</typescript>

</ex-wrap-tool-call>

<ex-before-after-hooks>

<python>

before_model / after_model / before_agent / after_agent all share (state, runtime) signature.

python
from langchain.agents.middleware import before_model, after_model
@before_modeldef log_calls(state, runtime):    print(f"Calling model with {len(state['messages'])} messages")
@after_modeldef check_output(state, runtime):    print(f"Model responded")

</python>

<typescript>

All before/after hooks share the same (state, runtime) signature via createMiddleware.

typescript
import { createMiddleware } from "langchain";
const loggingMiddleware = createMiddleware({  beforeModel: (state, runtime) => {    console.log(`Calling model with ${state.messages.length} messages`);  },  afterModel: (state, runtime) => {    console.log("Model responded");  },});

</typescript>

</ex-before-after-hooks>

<boundaries>

What You CANNOT Configure

  • Interrupt after tool execution (must be before)
  • Skip checkpointer requirement for HITL

</boundaries>

<fix-missing-checkpointer>

<python>

HITL middleware requires a checkpointer to persist state.

python
# WRONGagent = create_agent(model="gpt-4.1", tools=[send_email], middleware=[HumanInTheLoopMiddleware({...})])
# CORRECTagent = create_agent(    model="gpt-4.1", tools=[send_email],    checkpointer=MemorySaver(),  # Required    middleware=[HumanInTheLoopMiddleware({...})])

</python>

<typescript>

HITL requires a checkpointer to persist state.

typescript
// WRONG: No checkpointerconst agent = createAgent({  model: "anthropic:claude-sonnet-4-5", tools: [sendEmail],  middleware: [humanInTheLoopMiddleware({ interruptOn: { send_email: true } })],});
// CORRECT: Add checkpointerconst agent = createAgent({  model: "anthropic:claude-sonnet-4-5", tools: [sendEmail],  checkpointer: new MemorySaver(),  middleware: [humanInTheLoopMiddleware({ interruptOn: { send_email: true } })],});

</typescript>

</fix-missing-checkpointer>

<fix-no-thread-id>

<python>

Always provide thread_id when using HITL to track conversation state.

python
# WRONGagent.invoke(input)  # No config!
# CORRECTagent.invoke(input, config={"configurable": {"thread_id": "user-123"}})

</python>

</fix-no-thread-id>

<fix-wrong-resume-syntax>

<python>

Use Command class to resume execution after an interrupt.

python
# WRONGagent.invoke({"resume": {"decisions": [...]}})
# CORRECTfrom langgraph.types import Commandagent.invoke(Command(resume={"decisions": [{"type": "approve"}]}), config=config)

</python>

<typescript>

Use Command class to resume execution after an interrupt.

typescript
// WRONGawait agent.invoke({ resume: { decisions: [...] } });
// CORRECTimport { Command } from "@langchain/langgraph";await agent.invoke(new Command({ resume: { decisions: [{ type: "approve" }] } }), config);

</typescript>

</fix-wrong-resume-syntax>

來源與署名

來源:langchain-ai/langchain-skills位於config/skills/langchain-middleware提交16a992f

授權條款: 無授權條款

內容歸原作者所有。SourceWeft 從公開儲存庫中收錄這些內容。

檢舉或申請下架