Middleware는 에이전트 내부에서 일어나는 일을 더 세밀하게 제어할 수 있는 방법을 제공합니다. 핵심 에이전트 루프는 모델을 호출하고, 실행할 도구를 선택하게 한 다음, 더 이상 도구를 호출하지 않을 때 종료하는 과정을 포함합니다:
핵심 에이전트 루프 다이어그램
Middleware는 각 단계의 전후에 훅을 노출합니다:
Middleware 흐름 다이어그램

Middleware로 무엇을 할 수 있나요?

모니터링

로깅, 분석 및 디버깅으로 에이전트 동작을 추적하세요

수정

프롬프트, 도구 선택 및 출력 형식을 변환하세요

제어

재시도, 폴백 및 조기 종료 로직을 추가하세요

강제

속도 제한, 가드레일 및 PII 탐지를 적용하세요
create_agent에 전달하여 middleware를 추가하세요:
from langchain.agents import create_agent
from langchain.agents.middleware import SummarizationMiddleware, HumanInTheLoopMiddleware


agent = create_agent(
    model="openai:gpt-4o",
    tools=[...],
    middleware=[SummarizationMiddleware(), HumanInTheLoopMiddleware()],
)

내장 middleware

LangChain은 일반적인 사용 사례를 위한 사전 구축된 middleware를 제공합니다:

요약

토큰 제한에 도달할 때 대화 기록을 자동으로 요약합니다.
다음에 완벽합니다:
  • 컨텍스트 윈도우를 초과하는 장기 실행 대화
  • 광범위한 기록이 있는 다중 턴 대화
  • 전체 대화 컨텍스트 보존이 중요한 애플리케이션
from langchain.agents import create_agent
from langchain.agents.middleware import SummarizationMiddleware


agent = create_agent(
    model="openai:gpt-4o",
    tools=[weather_tool, calculator_tool],
    middleware=[
        SummarizationMiddleware(
            model="openai:gpt-4o-mini",
            max_tokens_before_summary=4000,  # Trigger summarization at 4000 tokens
            messages_to_keep=20,  # Keep last 20 messages after summary
            summary_prompt="Custom prompt for summarization...",  # Optional
        ),
    ],
)
model
string
required
요약 생성을 위한 모델
max_tokens_before_summary
number
요약을 트리거하는 토큰 임계값
messages_to_keep
number
default:"20"
보존할 최근 메시지 수
token_counter
function
커스텀 토큰 카운팅 함수. 기본값은 문자 기반 카운팅입니다.
summary_prompt
string
커스텀 프롬프트 템플릿. 지정하지 않으면 내장 템플릿을 사용합니다.
summary_prefix
string
default:"## Previous conversation summary:"
요약 메시지의 접두사

Human-in-the-loop

도구 호출이 실행되기 전에 사람의 승인, 편집 또는 거부를 위해 에이전트 실행을 일시 중지합니다.
다음에 완벽합니다:
  • 사람의 승인이 필요한 고위험 작업 (데이터베이스 쓰기, 금융 거래)
  • 사람의 감독이 필수인 규정 준수 워크플로우
  • 사람의 피드백이 에이전트를 안내하는 데 사용되는 장기 실행 대화
from langchain.agents import create_agent
from langchain.agents.middleware import HumanInTheLoopMiddleware
from langgraph.checkpoint.memory import InMemorySaver


agent = create_agent(
    model="openai:gpt-4o",
    tools=[read_email_tool, send_email_tool],
    checkpointer=InMemorySaver(),
    middleware=[
        HumanInTheLoopMiddleware(
            interrupt_on={
                # Require approval, editing, or rejection for sending emails
                "send_email_tool": {
                    "allowed_decisions": ["approve", "edit", "reject"],
                },
                # Auto-approve reading emails
                "read_email_tool": False,
            }
        ),
    ],
)
interrupt_on
dict
required
도구 이름과 승인 구성의 매핑. 값은 True (기본 구성으로 중단), False (자동 승인) 또는 InterruptOnConfig 객체일 수 있습니다.
description_prefix
string
default:"Tool execution requires approval"
작업 요청 설명의 접두사
InterruptOnConfig 옵션:
allowed_decisions
list[string]
허용된 결정 목록: "approve", "edit" 또는 "reject"
description
string | callable
커스텀 설명을 위한 정적 문자열 또는 호출 가능한 함수
중요: Human-in-the-loop middleware는 중단 간 상태를 유지하기 위해 checkpointer가 필요합니다.전체 예제 및 통합 패턴은 human-in-the-loop 문서를 참조하세요.

Anthropic 프롬프트 캐싱

Anthropic 모델로 반복적인 프롬프트 접두사를 캐싱하여 비용을 절감하세요.
다음에 완벽합니다:
  • 길고 반복되는 시스템 프롬프트가 있는 애플리케이션
  • 호출 간에 동일한 컨텍스트를 재사용하는 에이전트
  • 대량 배포의 API 비용 절감
Anthropic Prompt Caching 전략 및 제한 사항에 대해 자세히 알아보세요.
from langchain_anthropic import ChatAnthropic
from langchain_anthropic.middleware import AnthropicPromptCachingMiddleware
from langchain.agents import create_agent


LONG_PROMPT = """
Please be a helpful assistant.

<Lots more context ...>
"""

agent = create_agent(
    model=ChatAnthropic(model="claude-sonnet-4-latest"),
    system_prompt=LONG_PROMPT,
    middleware=[AnthropicPromptCachingMiddleware(ttl="5m")],
)

# cache store
agent.invoke({"messages": [HumanMessage("Hi, my name is Bob")]})

# cache hit, system prompt is cached
agent.invoke({"messages": [HumanMessage("What's my name?")]})
type
string
default:"ephemeral"
캐시 유형. 현재 "ephemeral"만 지원됩니다.
ttl
string
default:"5m"
캐시된 콘텐츠의 유효 시간. 유효한 값: "5m" 또는 "1h"
min_messages_to_cache
number
default:"0"
캐싱이 시작되기 전 최소 메시지 수
unsupported_model_behavior
string
default:"warn"
Anthropic이 아닌 모델 사용 시 동작. 옵션: "ignore", "warn" 또는 "raise"

모델 호출 제한

무한 루프나 과도한 비용을 방지하기 위해 모델 호출 수를 제한합니다.
다음에 완벽합니다:
  • 너무 많은 API 호출을 하는 폭주 에이전트 방지
  • 프로덕션 배포에 대한 비용 제어 강제
  • 특정 호출 예산 내에서 에이전트 동작 테스트
from langchain.agents import create_agent
from langchain.agents.middleware import ModelCallLimitMiddleware


agent = create_agent(
    model="openai:gpt-4o",
    tools=[...],
    middleware=[
        ModelCallLimitMiddleware(
            thread_limit=10,  # Max 10 calls per thread (across runs)
            run_limit=5,  # Max 5 calls per run (single invocation)
            exit_behavior="end",  # Or "error" to raise exception
        ),
    ],
)
thread_limit
number
스레드의 모든 실행에 걸친 최대 모델 호출 수. 기본값은 제한 없음입니다.
run_limit
number
단일 호출당 최대 모델 호출 수. 기본값은 제한 없음입니다.
exit_behavior
string
default:"end"
제한에 도달했을 때의 동작. 옵션: "end" (정상 종료) 또는 "error" (예외 발생)

도구 호출 제한

특정 도구 또는 모든 도구에 대한 도구 호출 수를 제한합니다.
다음에 완벽합니다:
  • 비용이 많이 드는 외부 API에 대한 과도한 호출 방지
  • 웹 검색 또는 데이터베이스 쿼리 제한
  • 특정 도구 사용에 대한 속도 제한 강제
from langchain.agents import create_agent
from langchain.agents.middleware import ToolCallLimitMiddleware


# Limit all tool calls
global_limiter = ToolCallLimitMiddleware(thread_limit=20, run_limit=10)

# Limit specific tool
search_limiter = ToolCallLimitMiddleware(
    tool_name="search",
    thread_limit=5,
    run_limit=3,
)

agent = create_agent(
    model="openai:gpt-4o",
    tools=[...],
    middleware=[global_limiter, search_limiter],
)
tool_name
string
제한할 특정 도구. 제공하지 않으면 모든 도구에 제한이 적용됩니다.
thread_limit
number
스레드의 모든 실행에 걸친 최대 도구 호출 수. 기본값은 제한 없음입니다.
run_limit
number
단일 호출당 최대 도구 호출 수. 기본값은 제한 없음입니다.
exit_behavior
string
default:"end"
제한에 도달했을 때의 동작. 옵션: "end" (정상 종료) 또는 "error" (예외 발생)

모델 폴백

기본 모델이 실패할 때 대체 모델로 자동 폴백합니다.
다음에 완벽합니다:
  • 모델 중단을 처리하는 탄력적인 에이전트 구축
  • 더 저렴한 모델로 폴백하여 비용 최적화
  • OpenAI, Anthropic 등에 걸친 제공자 중복성
from langchain.agents import create_agent
from langchain.agents.middleware import ModelFallbackMiddleware


agent = create_agent(
    model="openai:gpt-4o",  # Primary model
    tools=[...],
    middleware=[
        ModelFallbackMiddleware(
            "openai:gpt-4o-mini",  # Try first on error
            "anthropic:claude-3-5-sonnet-20241022",  # Then this
        ),
    ],
)
first_model
string | BaseChatModel
required
기본 모델이 실패할 때 시도할 첫 번째 폴백 모델. 모델 문자열 (예: "openai:gpt-4o-mini") 또는 BaseChatModel 인스턴스일 수 있습니다.
*additional_models
string | BaseChatModel
이전 모델이 실패할 경우 순서대로 시도할 추가 폴백 모델

PII 탐지

대화에서 개인 식별 정보를 탐지하고 처리합니다.
다음에 완벽합니다:
  • 규정 준수 요구 사항이 있는 의료 및 금융 애플리케이션
  • 로그를 정제해야 하는 고객 서비스 에이전트
  • 민감한 사용자 데이터를 처리하는 모든 애플리케이션
from langchain.agents import create_agent
from langchain.agents.middleware import PIIMiddleware


agent = create_agent(
    model="openai:gpt-4o",
    tools=[...],
    middleware=[
        # Redact emails in user input
        PIIMiddleware("email", strategy="redact", apply_to_input=True),
        # Mask credit cards (show last 4 digits)
        PIIMiddleware("credit_card", strategy="mask", apply_to_input=True),
        # Custom PII type with regex
        PIIMiddleware(
            "api_key",
            detector=r"sk-[a-zA-Z0-9]{32}",
            strategy="block",  # Raise error if detected
        ),
    ],
)
pii_type
string
required
탐지할 PII 유형. 내장 유형 (email, credit_card, ip, mac_address, url) 또는 커스텀 유형 이름일 수 있습니다.
strategy
string
default:"redact"
탐지된 PII를 처리하는 방법. 옵션:
  • "block" - 탐지 시 예외 발생
  • "redact" - [REDACTED_TYPE]으로 대체
  • "mask" - 부분 마스킹 (예: ****-****-****-1234)
  • "hash" - 결정론적 해시로 대체
detector
function | regex
커스텀 탐지기 함수 또는 정규식 패턴. 제공하지 않으면 PII 유형에 대한 내장 탐지기를 사용합니다.
apply_to_input
boolean
default:"True"
모델 호출 전 사용자 메시지 확인
apply_to_output
boolean
default:"False"
모델 호출 후 AI 메시지 확인
apply_to_tool_results
boolean
default:"False"
실행 후 도구 결과 메시지 확인

계획

복잡한 다단계 작업을 위한 할 일 목록 관리 기능을 추가합니다.
이 middleware는 에이전트에 write_todos 도구와 효과적인 작업 계획을 안내하는 시스템 프롬프트를 자동으로 제공합니다.
from langchain.agents import create_agent
from langchain.agents.middleware import TodoListMiddleware
from langchain.messages import HumanMessage


agent = create_agent(
    model="openai:gpt-4o",
    tools=[...],
    middleware=[TodoListMiddleware()],
)

result = agent.invoke({"messages": [HumanMessage("Help me refactor my codebase")]})
print(result["todos"])  # Array of todo items with status tracking
system_prompt
string
할 일 사용을 안내하는 커스텀 시스템 프롬프트. 지정하지 않으면 내장 프롬프트를 사용합니다.
tool_description
string
write_todos 도구에 대한 커스텀 설명. 지정하지 않으면 내장 설명을 사용합니다.

LLM 도구 선택기

LLM을 사용하여 메인 모델을 호출하기 전에 관련 도구를 지능적으로 선택합니다.
다음에 완벽합니다:
  • 많은 도구(10개 이상)가 있지만 쿼리당 대부분이 관련 없는 에이전트
  • 관련 없는 도구를 필터링하여 토큰 사용량 감소
  • 모델 집중도 및 정확도 향상
from langchain.agents import create_agent
from langchain.agents.middleware import LLMToolSelectorMiddleware


agent = create_agent(
    model="openai:gpt-4o",
    tools=[tool1, tool2, tool3, tool4, tool5, ...],  # Many tools
    middleware=[
        LLMToolSelectorMiddleware(
            model="openai:gpt-4o-mini",  # Use cheaper model for selection
            max_tools=3,  # Limit to 3 most relevant tools
            always_include=["search"],  # Always include certain tools
        ),
    ],
)
model
string | BaseChatModel
도구 선택을 위한 모델. 모델 문자열 또는 BaseChatModel 인스턴스일 수 있습니다. 기본값은 에이전트의 메인 모델입니다.
system_prompt
string
선택 모델에 대한 지침. 지정하지 않으면 내장 프롬프트를 사용합니다.
max_tools
number
선택할 최대 도구 수. 기본값은 제한 없음입니다.
always_include
list[string]
선택에 항상 포함할 도구 이름 목록

도구 재시도

구성 가능한 지수 백오프로 실패한 도구 호출을 자동으로 재시도합니다.
다음에 완벽합니다:
  • 외부 API 호출의 일시적 실패 처리
  • 네트워크 종속 도구의 안정성 향상
  • 일시적 오류를 우아하게 처리하는 탄력적인 에이전트 구축
from langchain.agents import create_agent
from langchain.agents.middleware import ToolRetryMiddleware


agent = create_agent(
    model="openai:gpt-4o",
    tools=[search_tool, database_tool],
    middleware=[
        ToolRetryMiddleware(
            max_retries=3,  # Retry up to 3 times
            backoff_factor=2.0,  # Exponential backoff multiplier
            initial_delay=1.0,  # Start with 1 second delay
            max_delay=60.0,  # Cap delays at 60 seconds
            jitter=True,  # Add random jitter to avoid thundering herd
        ),
    ],
)
max_retries
number
default:"2"
초기 호출 후 최대 재시도 횟수 (기본값으로 총 3회 시도)
tools
list[BaseTool | str]
재시도 로직을 적용할 도구 또는 도구 이름의 선택적 목록. None이면 모든 도구에 적용됩니다.
retry_on
tuple[type[Exception], ...] | callable
default:"(Exception,)"
재시도할 예외 유형의 튜플 또는 예외를 받아 재시도해야 하면 True를 반환하는 호출 가능 객체입니다.
on_failure
string | callable
default:"return_message"
모든 재시도가 소진되었을 때의 동작. 옵션:
  • "return_message" - 오류 세부 정보가 포함된 ToolMessage 반환 (LLM이 실패를 처리하도록 허용)
  • "raise" - 예외 재발생 (에이전트 실행 중지)
  • 커스텀 호출 가능 - 예외를 받아 ToolMessage 콘텐츠에 대한 문자열을 반환하는 함수
backoff_factor
number
default:"2.0"
지수 백오프의 승수. 각 재시도는 initial_delay * (backoff_factor ** retry_number) 초를 대기합니다. 일정한 지연을 위해 0.0으로 설정하세요.
initial_delay
number
default:"1.0"
첫 번째 재시도 전 초기 지연 시간(초)
max_delay
number
default:"60.0"
재시도 간 최대 지연 시간(초) (지수 백오프 증가 제한)
jitter
boolean
default:"true"
thundering herd를 피하기 위해 지연에 무작위 지터(±25%)를 추가할지 여부

LLM 도구 에뮬레이터

테스트 목적으로 LLM을 사용하여 도구 실행을 에뮬레이트하고, 실제 도구 호출을 AI 생성 응답으로 대체합니다.
다음에 완벽합니다:
  • 실제 도구를 실행하지 않고 에이전트 동작 테스트
  • 외부 도구를 사용할 수 없거나 비용이 많이 들 때 에이전트 개발
  • 실제 도구를 구현하기 전에 에이전트 워크플로우 프로토타이핑
from langchain.agents import create_agent
from langchain.agents.middleware import LLMToolEmulator


agent = create_agent(
    model="openai:gpt-4o",
    tools=[get_weather, search_database, send_email],
    middleware=[
        # Emulate all tools by default
        LLMToolEmulator(),

        # Or emulate specific tools
        # LLMToolEmulator(tools=["get_weather", "search_database"]),

        # Or use a custom model for emulation
        # LLMToolEmulator(model="anthropic:claude-3-5-sonnet-latest"),
    ],
)
tools
list[str | BaseTool]
에뮬레이트할 도구 이름(str) 또는 BaseTool 인스턴스 목록. None (기본값)이면 모든 도구가 에뮬레이트됩니다. 빈 목록이면 도구가 에뮬레이트되지 않습니다.
model
string | BaseChatModel
default:"anthropic:claude-3-5-sonnet-latest"
에뮬레이트된 도구 응답 생성에 사용할 모델. 모델 식별자 문자열 또는 BaseChatModel 인스턴스일 수 있습니다.

컨텍스트 편집

도구 사용을 트리밍, 요약 또는 지워서 대화 컨텍스트를 관리합니다.
다음에 완벽합니다:
  • 주기적인 컨텍스트 정리가 필요한 긴 대화
  • 컨텍스트에서 실패한 도구 시도 제거
  • 커스텀 컨텍스트 관리 전략
from langchain.agents import create_agent
from langchain.agents.middleware import ContextEditingMiddleware, ClearToolUsesEdit


agent = create_agent(
    model="openai:gpt-4o",
    tools=[...],
    middleware=[
        ContextEditingMiddleware(
            edits=[
                ClearToolUsesEdit(max_tokens=1000),  # Clear old tool uses
            ],
        ),
    ],
)
edits
list[ContextEdit]
default:"[ClearToolUsesEdit()]"
적용할 ContextEdit 전략 목록
token_count_method
string
default:"approximate"
토큰 카운팅 방법. 옵션: "approximate" 또는 "model"
ClearToolUsesEdit 옵션:
trigger
number
default:"100000"
편집을 트리거하는 토큰 수
clear_at_least
number
default:"0"
회수할 최소 토큰 수
keep
number
default:"3"
보존할 최근 도구 결과 수
clear_tool_inputs
boolean
default:"False"
도구 호출 매개변수를 지울지 여부
exclude_tools
list[string]
default:"()"
지우기에서 제외할 도구 이름 목록
placeholder
string
default:"[cleared]"
지워진 출력에 대한 플레이스홀더 텍스트

커스텀 middleware

에이전트 실행 흐름의 특정 지점에서 실행되는 훅을 구현하여 커스텀 middleware를 구축하세요. 두 가지 방법으로 middleware를 만들 수 있습니다:
  1. 데코레이터 기반 - 단일 훅 middleware를 위한 빠르고 간단한 방법
  2. 클래스 기반 - 여러 훅이 있는 복잡한 middleware를 위한 더 강력한 방법

데코레이터 기반 middleware

단일 훅만 필요한 간단한 middleware의 경우, 데코레이터가 기능을 추가하는 가장 빠른 방법을 제공합니다:
from langchain.agents.middleware import before_model, after_model, wrap_model_call
from langchain.agents.middleware import AgentState, ModelRequest, ModelResponse, dynamic_prompt
from langchain.messages import AIMessage
from langchain.agents import create_agent
from langgraph.runtime import Runtime
from typing import Any, Callable


# Node-style: logging before model calls
@before_model
def log_before_model(state: AgentState, runtime: Runtime) -> dict[str, Any] | None:
    print(f"About to call model with {len(state['messages'])} messages")
    return None

# Node-style: validation after model calls
@after_model(can_jump_to=["end"])
def validate_output(state: AgentState, runtime: Runtime) -> dict[str, Any] | None:
    last_message = state["messages"][-1]
    if "BLOCKED" in last_message.content:
        return {
            "messages": [AIMessage("I cannot respond to that request.")],
            "jump_to": "end"
        }
    return None

# Wrap-style: retry logic
@wrap_model_call
def retry_model(
    request: ModelRequest,
    handler: Callable[[ModelRequest], ModelResponse],
) -> ModelResponse:
    for attempt in range(3):
        try:
            return handler(request)
        except Exception as e:
            if attempt == 2:
                raise
            print(f"Retry {attempt + 1}/3 after error: {e}")

# Wrap-style: dynamic prompts
@dynamic_prompt
def personalized_prompt(request: ModelRequest) -> str:
    user_id = request.runtime.context.get("user_id", "guest")
    return f"You are a helpful assistant for user {user_id}. Be concise and friendly."

# Use decorators in agent
agent = create_agent(
    model="openai:gpt-4o",
    middleware=[log_before_model, validate_output, retry_model, personalized_prompt],
    tools=[...],
)

사용 가능한 데코레이터

Node 스타일 (특정 실행 지점에서 실행):
  • @before_agent - 에이전트 시작 전 (호출당 한 번)
  • @before_model - 각 모델 호출 전
  • @after_model - 각 모델 응답 후
  • @after_agent - 에이전트 완료 후 (호출당 최대 한 번)
Wrap 스타일 (실행을 가로채고 제어): 편의 데코레이터:

데코레이터를 사용할 때

데코레이터 사용 시

• 단일 훅이 필요할 때
• 복잡한 구성이 없을 때

클래스 사용 시

• 여러 훅이 필요할 때
• 복잡한 구성
• 프로젝트 간 재사용 (초기화 시 구성)

클래스 기반 middleware

두 가지 훅 스타일

Node 스타일 훅

특정 실행 지점에서 순차적으로 실행됩니다. 로깅, 검증 및 상태 업데이트에 사용하세요.

Wrap 스타일 훅

핸들러 호출에 대한 완전한 제어로 실행을 가로챕니다. 재시도, 캐싱 및 변환에 사용하세요.

Node 스타일 훅

실행 흐름의 특정 지점에서 실행됩니다:
  • before_agent - 에이전트 시작 전 (호출당 한 번)
  • before_model - 각 모델 호출 전
  • after_model - 각 모델 응답 후
  • after_agent - 에이전트 완료 후 (호출당 최대 한 번)
예제: 로깅 middleware
from langchain.agents.middleware import AgentMiddleware, AgentState
from langgraph.runtime import Runtime
from typing import Any

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
예제: 대화 길이 제한
from langchain.agents.middleware import AgentMiddleware, AgentState
from langchain.messages import AIMessage
from langgraph.runtime import Runtime
from typing import Any

class MessageLimitMiddleware(AgentMiddleware):
    def __init__(self, max_messages: int = 50):
        super().__init__()
        self.max_messages = max_messages

    def before_model(self, state: AgentState, runtime: Runtime) -> dict[str, Any] | None:
        if len(state["messages"]) == self.max_messages:
            return {
                "messages": [AIMessage("Conversation limit reached.")],
                "jump_to": "end"
            }
        return None

Wrap 스타일 훅

실행을 가로채고 핸들러가 호출되는 시점을 제어합니다:
  • wrap_model_call - 각 모델 호출 주위
  • wrap_tool_call - 각 도구 호출 주위
핸들러를 0번 (단락), 1번 (정상 흐름) 또는 여러 번 (재시도 로직) 호출할지 결정합니다. 예제: 모델 재시도 middleware
from langchain.agents.middleware import AgentMiddleware, ModelRequest, ModelResponse
from typing import Callable

class RetryMiddleware(AgentMiddleware):
    def __init__(self, max_retries: int = 3):
        super().__init__()
        self.max_retries = max_retries

    def wrap_model_call(
        self,
        request: ModelRequest,
        handler: Callable[[ModelRequest], ModelResponse],
    ) -> ModelResponse:
        for attempt in range(self.max_retries):
            try:
                return handler(request)
            except Exception as e:
                if attempt == self.max_retries - 1:
                    raise
                print(f"Retry {attempt + 1}/{self.max_retries} after error: {e}")
예제: 동적 모델 선택
from langchain.agents.middleware import AgentMiddleware, ModelRequest, ModelResponse
from langchain.chat_models import init_chat_model
from typing import Callable

class DynamicModelMiddleware(AgentMiddleware):
    def wrap_model_call(
        self,
        request: ModelRequest,
        handler: Callable[[ModelRequest], ModelResponse],
    ) -> ModelResponse:
        # Use different model based on conversation length
        if len(request.messages) > 10:
            request.model = init_chat_model("openai:gpt-4o")
        else:
            request.model = init_chat_model("openai:gpt-4o-mini")

        return handler(request)
예제: 도구 호출 모니터링
from langchain.tools.tool_node import ToolCallRequest
from langchain.agents.middleware import AgentMiddleware
from langchain_core.messages import ToolMessage
from langgraph.types import Command
from typing import Callable

class ToolMonitoringMiddleware(AgentMiddleware):
    def wrap_tool_call(
        self,
        request: ToolCallRequest,
        handler: Callable[[ToolCallRequest], ToolMessage | Command],
    ) -> ToolMessage | Command:
        print(f"Executing tool: {request.tool_call['name']}")
        print(f"Arguments: {request.tool_call['args']}")

        try:
            result = handler(request)
            print(f"Tool completed successfully")
            return result
        except Exception as e:
            print(f"Tool failed: {e}")
            raise

커스텀 상태 스키마

Middleware는 커스텀 속성으로 에이전트의 상태를 확장할 수 있습니다. 커스텀 상태 유형을 정의하고 state_schema로 설정하세요:
from langchain.agents.middleware import AgentState, AgentMiddleware
from typing_extensions import NotRequired
from typing import Any

class CustomState(AgentState):
    model_call_count: NotRequired[int]
    user_id: NotRequired[str]

class CallCounterMiddleware(AgentMiddleware[CustomState]):
    state_schema = CustomState

    def before_model(self, state: CustomState, runtime) -> dict[str, Any] | None:
        # Access custom state properties
        count = state.get("model_call_count", 0)

        if count > 10:
            return {"jump_to": "end"}

        return None

    def after_model(self, state: CustomState, runtime) -> dict[str, Any] | None:
        # Update custom state
        return {"model_call_count": state.get("model_call_count", 0) + 1}
agent = create_agent(
    model="openai:gpt-4o",
    middleware=[CallCounterMiddleware()],
    tools=[...],
)

# Invoke with custom state
result = agent.invoke({
    "messages": [HumanMessage("Hello")],
    "model_call_count": 0,
    "user_id": "user-123",
})

실행 순서

여러 middleware를 사용할 때 실행 순서를 이해하는 것이 중요합니다:
agent = create_agent(
    model="openai:gpt-4o",
    middleware=[middleware1, middleware2, middleware3],
    tools=[...],
)
Before 훅은 순서대로 실행됩니다:
  1. middleware1.before_agent()
  2. middleware2.before_agent()
  3. middleware3.before_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()
에이전트 루프 종료
  1. middleware3.after_agent()
  2. middleware2.after_agent()
  3. middleware1.after_agent()
주요 규칙:
  • before_* 훅: 처음부터 마지막까지
  • after_* 훅: 마지막부터 처음까지 (역순)
  • wrap_* 훅: 중첩 (첫 번째 middleware가 다른 모든 것을 감쌈)

에이전트 점프

middleware에서 조기 종료하려면 jump_to가 포함된 딕셔너리를 반환하세요:
class EarlyExitMiddleware(AgentMiddleware):
    def before_model(self, state: AgentState, runtime) -> dict[str, Any] | None:
        # Check some condition
        if should_exit(state):
            return {
                "messages": [AIMessage("Exiting early due to condition.")],
                "jump_to": "end"
            }
        return None
사용 가능한 점프 대상:
  • "end": 에이전트 실행의 끝으로 점프
  • "tools": tools 노드로 점프
  • "model": model 노드로 점프 (또는 첫 번째 before_model 훅)
중요: before_model 또는 after_model에서 점프할 때, "model"로 점프하면 모든 before_model middleware가 다시 실행됩니다. 점프를 활성화하려면 @hook_config(can_jump_to=[...])로 훅을 데코레이트하세요:
from langchain.agents.middleware import AgentMiddleware, hook_config
from typing import Any

class ConditionalMiddleware(AgentMiddleware):
    @hook_config(can_jump_to=["end", "tools"])
    def after_model(self, state: AgentState, runtime) -> dict[str, Any] | None:
        if some_condition(state):
            return {"jump_to": "end"}
        return None

모범 사례

  1. Middleware를 집중적으로 유지 - 각각 한 가지를 잘 수행해야 합니다
  2. 오류를 우아하게 처리 - middleware 오류가 에이전트를 충돌시키지 않도록 하세요
  3. 적절한 훅 유형 사용:
    • 순차적 로직을 위한 Node 스타일 (로깅, 검증)
    • 제어 흐름을 위한 Wrap 스타일 (재시도, 폴백, 캐싱)
  4. 커스텀 상태 속성을 명확하게 문서화하세요
  5. 통합하기 전에 middleware를 독립적으로 단위 테스트하세요
  6. 실행 순서 고려 - 중요한 middleware를 목록의 첫 번째에 배치하세요
  7. 가능하면 내장 middleware 사용, 바퀴를 재발명하지 마세요 :)

예제

동적으로 도구 선택하기

런타임에 관련 도구를 선택하여 성능과 정확도를 향상시킵니다.
이점:
  • 더 짧은 프롬프트 - 관련 도구만 노출하여 복잡성 감소
  • 더 나은 정확도 - 모델이 더 적은 옵션에서 올바르게 선택
  • 권한 제어 - 사용자 액세스에 따라 도구를 동적으로 필터링
from langchain.agents import create_agent
from langchain.agents.middleware import AgentMiddleware, ModelRequest
from typing import Callable


class ToolSelectorMiddleware(AgentMiddleware):
    def wrap_model_call(
        self,
        request: ModelRequest,
        handler: Callable[[ModelRequest], ModelResponse],
    ) -> ModelResponse:
        """Middleware to select relevant tools based on state/context."""
        # Select a small, relevant subset of tools based on state/context
        relevant_tools = select_relevant_tools(request.state, request.runtime)
        request.tools = relevant_tools
        return handler(request)

agent = create_agent(
    model="openai:gpt-4o",
    tools=all_tools,  # All available tools need to be registered upfront
    # Middleware can be used to select a smaller subset that's relevant for the given run.
    middleware=[ToolSelectorMiddleware()],
)

추가 리소스


Connect these docs programmatically to Claude, VSCode, and more via MCP for real-time answers.
I