Middleware pentru agenți (create_agent) în LangChain & LangGraph

Mecanismul din LangChain 1.x prin care adaugi logică înainte, după sau în jurul apelurilor de model și de unelte ale unui agent: rezumare, aprobare umană, reîncercări, limite, PII.

Sintaxă & invocare Python
from langchain.agents import create_agent
from langchain.agents.middleware import (
    SummarizationMiddleware, HumanInTheLoopMiddleware, ModelCallLimitMiddleware,
)
from langgraph.checkpoint.memory import InMemorySaver

agent = create_agent(
    model="gpt-6-astra",
    tools=[citeste_email, trimite_email],
    middleware=[
        SummarizationMiddleware(model="gpt-6-luna", trigger=("tokens", 4000), keep=("messages", 20)),
        HumanInTheLoopMiddleware(interrupt_on={"trimite_email": True}),
        ModelCallLimitMiddleware(run_limit=5),
    ],
    checkpointer=InMemorySaver(),
)

Middleware-ul controlează ce se întâmplă în bucla unui agent creat cu create_agent. Se dă ca listă în parametrul middleware și rulează în graful LangGraph întors de create_agent, deci rămâne activ și când agentul e folosit ca nod într-un graf mai mare. În v1 a înlocuit parametrii pre_model_hook, post_model_hook și state_modifier ai lui create_react_agent.

Există două feluri de hook-uri. Cele de tip nod rulează în puncte fixe: before_agent și after_agent (o dată pe invocare), before_model și after_model (la fiecare apel de model). Cele de tip wrap înconjoară un apel și decid dacă handler-ul rulează de zero ori, o dată sau de mai multe ori: wrap_model_call și wrap_tool_call. Se scriu ca decoratori (@before_model, @after_model, @wrap_model_call, @wrap_tool_call, @dynamic_prompt) sau ca subclase ale AgentMiddleware.

Ordinea din listă contează: hook-urile before_* rulează de la primul middleware la ultimul, cele after_* în ordine inversă, iar wrap_* se îmbină ca apelurile de funcții (primul le înconjoară pe celelalte). Un hook poate încheia bucla întorcând {"jump_to": "end"}, dacă a declarat can_jump_to. Pe lângă middleware-ul propriu, langchain.agents.middleware are variante gata făcute pentru cazurile frecvente.

Concepte cheie și caracteristici

Concept / Parametru Descriere practică
SummarizationMiddleware Rezumă mesajele vechi când istoricul se apropie de limita de tokeni (trigger) și păstrează ultimele mesaje (keep).
HumanInTheLoopMiddleware Oprește execuția înaintea uneltelor din interrupt_on și așteaptă o decizie: approve, edit, reject sau respond. Cere checkpointer.
ModelRetryMiddleware / ToolRetryMiddleware Reîncearcă apelurile eșuate de model sau de unelte, cu backoff exponențial.
ModelFallbackMiddleware Trece la alte modele, în ordine, dacă modelul principal eșuează.
ModelCallLimitMiddleware Limitează apelurile de model pe fir (thread_limit, cere checkpointer) sau pe rulare (run_limit); exit_behavior este "end" (implicit) sau "error".
ToolCallLimitMiddleware Limitează numărul de apeluri de unelte, pentru toate uneltele sau pentru una anume.
PIIMiddleware Detectează și tratează datele personale, de exemplu PIIMiddleware("email").
TodoListMiddleware Dă agentului o listă de sarcini pentru planificare.
before_model / after_model Hook-uri de tip nod, la fiecare apel de model; primesc starea și runtime-ul și pot întoarce actualizări de stare.
wrap_model_call / wrap_tool_call Hook-uri care înconjoară apelul; folosite pentru reîncercări, cache, schimbarea modelului sau tratarea erorilor uneltelor.

Exemple practice și scenarii reale

Exemplul 1: Hook propriu cu decorator: limită de mesaje

După exemplul din documentație. can_jump_to permite hook-ului să încheie bucla.

from typing import Any
from langchain.agents import create_agent
from langchain.agents.middleware import before_model, AgentState
from langchain.messages import AIMessage
from langgraph.runtime import Runtime

@before_model(can_jump_to=["end"])
def limita_mesaje(state: AgentState, runtime: Runtime) -> dict[str, Any] | None:
    if len(state["messages"]) >= 50:
        return {"messages": [AIMessage("Am atins limita conversației.")], "jump_to": "end"}
    return None

agent = create_agent(model="gpt-6-astra", tools=[], middleware=[limita_mesaje])

Exemplul 2: Erorile uneltelor transformate în mesaje pentru model

from langchain.agents.middleware import wrap_tool_call
from langchain.messages import ToolMessage

@wrap_tool_call
def erori_unelte(request, handler):
    try:
        return handler(request)
    except ValueError as e:   # doar erorile cauzate de intrări greșite
        return ToolMessage(
            content=f"Eroare la unealtă: verifică argumentele ({e})",
            tool_call_id=request.tool_call["id"],
        )

Exemplul 3: Aprobare umană și reluare

Cu version="v2", invoke întoarce un obiect cu atributul interrupts; reluarea folosește același thread_id.

from langgraph.types import Command

config = {"configurable": {"thread_id": "email-1"}}
rez = agent.invoke(
    {"messages": [{"role": "user", "content": "Trimite raportul lui Mihai"}]},
    config=config, version="v2",
)
print(rez.interrupts)   # acțiunile care așteaptă o decizie

agent.invoke(Command(resume={"decisions": [{"type": "approve"}]}),
             config=config, version="v2")

Greșeli frecvente și capcane

  • Să folosești HumanInTheLoopMiddleware fără checkpointer. Pauza se bazează pe persistența LangGraph; în producție, un checkpointer persistent.
  • Să răspunzi cu respond ca să refuzi o unealtă cu efecte. Documentația spune că mesajul ei e tratat ca rezultat reușit al uneltei; pentru refuz se folosește reject.
  • Să prinzi în wrap_tool_call orice excepție. Documentația recomandă să tratezi doar erorile cauzate de intrări greșite; erorile de rețea țin de middleware-ul de retry, iar bug-urile din unealtă trebuie să rămână vizibile.

Noțiuni și module înrudite