Галоўная / Артыкулы / Захоўка агента Python LangChain за дапамой семі вбудованых мідлвэраў

Захоўка агента Python LangChain за дапамой семі вбудованых мідлвэраў

Дазвольце даклэ научыцца, як мідлвэр LangChain 1.0 дае можлівасць адкладанню падсумку, лімітах вызоў, перапрыбутках, альтернатывах для модэляў, выдаленні персональных даных і пацверджэнні чалавекам да агента Gemini, не паўтараючы його основную логіку.

2568 слоў

Ёнклаву патрабаваць, каб агент LangChain адпавядаў на запытанні, патрабуецца калькі мінут. Аднак стварыць агента, якому можна было б даваць доверлівасць у рэальных умовах, сложней: ён не павінен ціклізаваць да таго часу, паколькі не выкарануе ваш бюджет API, не перадаць номер карткі кляўэнта прадавцу моделі і не апрашваць электранаў, якія ніхто не затвердзіў. LangChain 1.0 рашае гэтыя проблемы за дапамогою мідлвэра — шара хуков, якія знаходзяцца наваколо циклу агента і якія можна настроіць у вачынку простага списку. У гэтым кярыранцы ствараецца псіхалагічны модэль, пасля чыго прыкладзаецца семь мідлвэра да агента на базе Python і Gemini, а таксама прадстаўляецца самастоятны мідлвэр, ёжы дапамагае точна разумець, што кожны хук змінюе і калі ўжо трэба яго выкарыстоўваць.

Чекпойнты наваколо циклу агента

Уявіце аэрапорт. Мета — падзець з аднаго гарода ў іншы, пры чым навакол гэтага адзінаго дзеяння існуе ряд контрольных пунктав: рэўізія пасажыра парабягае падтвердзіць ідэнтычнасць, абаранне перакантралюе багаж, выход на посадку перакантралюе квіты на падлет і выдача багажа — пасля падлету. Ніхто з іх не падзець лятакам, а пілот не перакантралюе багаж. Кожны слой выконвае адну задачу даўна чы раней за основным дзеянням.

Мідлвэрт прымае тую ж ідею для агентаў. Аснова агента — це цыкл: вызваць модэль, дазволіць яй выбраць інструменты, запусціць іх і павтараць, пакуль модэль не вярне заканчыцкі адказ. Мідлвэрт дадае контрольныя пункты навакол гэтага цыклу, не правячы яго:

  • Адзьемленае значэнне номера карткі перш чым тэкст дасягне LLM — гэта сканер абаранння.
  • Трэба, каб чалавек затвердзіў выходны электрана — гэта выход на посадку.
  • Зупінка пасля дзесяці вызоў модэлю для обмежэння вытрачання — гэта аварыйны выключальнік.

Якщо вы працюеце з TypeScript, супутній матэрыял на LangChain guardrails and middleware раскрывае тыя ж ідэі з баку JavaScript; гэтыя нарадчыкі застосоўваюцца толькі з Python і сфокусаваны на конкрэтных вбудованных класах.

Хукі, якія можа выкарыстоўваць мідлвэр

Цикл абяўляе хукі на кожным этапе, і мідлвэр можа прыўяжыцца да адного чы роўна калькі з іх:

  • before_agent і after_agent выкананыя адна раз, на самым пачатку і на самым канцы вызову.
  • before_model і after_model запускаюцца кожны раз, калі цикл збіраецца вызваць чы ўжо вызваў модэль.
  • wrap_model_call і wrap_tool_call абгортаюць сам вызов, таму ўмогацвараець яго перапрыбуткі, замену чы выкарыстоўванне з кэша.

Гэта ўсё ментальная модель. Вы прыўязуеце сервісы-прамежутакі, пасылаючы спіс да create_agent, як у гэтым прыкладзе, дзе адбываецца маскуванне электронных паштоў, обмежэння колькасці вызоў і павтарныя спробы выкарыстоўвання інструментаў:

agent = create_agent(
    model=model,
    tools=[my_tool],
    middleware=[
        PIIMiddleware("email", strategy="redact"),
        ModelCallLimitMiddleware(run_limit=5),
        ToolRetryMiddleware(max_retries=3),
    ],
)

Порядак у спісе мае значэнне. Сервісы-прамежутакі прыменяюцца па спрабе, як шары цвёбуры, абгортаныя навакол агента, таму элемент спіса, які стоіць першым, адбывае обробку неапранутага вхіднага даных раней за іншыя.

Настройка і базовы агент

Для сервісаў-прамежутакоў патрэбна версія LangChain 1.0 або новейшая, таму інсталюйце яе з пазначкай -U, каб апградаваць будзь-якую старэю версію. У прыкладах выкарыстоўваецца Gemini ў яго безплатнай версіи; вы можете стварыць ключ у Google AI Studio.

!pip install -qU langchain langchain-google-genai

Імпортуйце os і клас модэлю чату Gemini:

import os
from langchain_google_genai import ChatGoogleGenerativeAI

Пасля чаго настаўляецца модэль. У гэтым фрагменте кантрольны параметр задаўця ўказваецца безпосередна толькі для дэманстрацыі; у рэальным кодзе GOOGLE_API_KEY вывозится ў ваша среду або запрашваецца з менеджера таямніц, а не размешчаецца ў самым кодзе. Значэнне тэмпературы 0 дапамагае заставіць выходы воспавтовымі:

os.environ["GOOGLE_API_KEY"] = "YOUR_GEMINI_API_KEY_HERE"
model = ChatGoogleGenerativeAI(model="gemini-3.5-flash-lite", temperature=0)

Об’ектам для кожнага эксперымента ёст мінімальны агент без проміжных скарыбаў. Ёму патрэбны функцыя create_agent і декоратар tool:

from langchain.agents import create_agent
from langchain_core.tools import tool

У яго ёсць адна фальшывая прыбор для пагляду на парадзе, якая завжды адмаўляе сонечную пагоду, і яна запускаецца на адной упорыненай паведамленні корыстувальца:

@tool
def get_weather(city: str) -> str:
    """Get the current weather for a city."""
    return f"The weather in {city} is 31°C and sunny."agent = create_agent(model=model, tools=[get_weather])result = agent.invoke(
    {"messages": [{"role": "user", "content": "What's the weather in Bengaluru?"}]}
)
print(result["messages"][-1].text)

Кожны наступны раздзіл абгортае ўжо адну додатковую точку перагляду навакола гэтага агента.

1. SummarizationMiddleware для обмежанай памяці

У дзейнах спілкування історыя паведамленняў продовжвае растаць, пакуль не перасягне межы контекстнага вікна, і за кожны дадзены па-додатку збіраецца плата. SummarizationMiddleware пераканальваеся ў размерз історыі ў функцыі before_model; калі гэты размер перасягае заданы порог, старэйшыя паведамлення сконцентруюцца ў апусце, а толькі найсвежэйшыя залишаюцца без змян.

from langchain.agents.middleware import SummarizationMiddleware

У наведанай выкладзе конфігурацыі для стварэння апусцоў викорыстоўваецца той самы модэль, актывацыя відбываецца пасля 10 паведамленняў, а найсвежэйшыя 4 залишаюцца недаступнымі. У тэсте ствараецца фальшывая історыя з шасці гарадоў (дванаццаць паведамленняў), пасля чаго запытваецца, які гарад быў першым:

agent = create_agent(
    model=model,
    tools=[get_weather],
    middleware=[
        SummarizationMiddleware(
            model=model,               # which LLM writes the summary
            trigger=("messages", 10),  # summarize when history hits 10 messages
            keep=("messages", 4),      # keep the 4 most recent messages intact
        ),
    ],
)# Simulate a long conversation
long_history = []
for city in ["Delhi", "Mumbai", "Chennai", "Kolkata", "Pune", "Jaipur"]:
    long_history.append({"role": "user", "content": f"What's the weather in {city}?"})
    long_history.append({"role": "assistant", "content": f"The weather in {city} is sunny."})
long_history.append({"role": "user", "content": "Which city did I ask about first?"})print("Messages passed IN:", len(long_history))   # 13result = agent.invoke({"messages": long_history})
print("Final answer:", result["messages"][-1].text)
print("Messages now in state:", len(result["messages"]))   # 6

Уваходзіць трохіце слявеся, а пасля застаёцца шэсць: статыс, чатыры захаваныя слявеся і новая адпаведзь. Модэль яшчэ і дае адказ «Делі», таму што гэты факт быў уключаны ў статыс. Підлічэнне слявесаў дапамагае лёгка адразу пабачыць гэтую працэзу ў дамэ, але ў рэальных умовах трыгеры, заснованыя на токенах, такія як ("tokens", 3000), або частка вікна контэкста, напрыклад ("fraction", 0.8), набагато краща контролююць рэальныя витраты. Памятайце, што статысы є збітнымі: точныя цифры або ідэнтыфікаторы, упомянутыя на пачатку кансэрваціі, можа не застацца.

2. Ліміты вызоў як прыемнікі витатаў

Найдорожэйшым фаллам агента є бесканцовая петля, калі модэль і інструменты продавжваюць вызываць адна другую і витрачаюць грошы хвілінкі, пакуль ніхто не з’явіцца. Два мідлвэра ставяць жорсткі ліміт на гэта:

from langchain.agents.middleware import ModelCallLimitMiddleware, ToolCallLimitMiddleware

Ёнтут максымальная колькасць вызоў модэля за адну руну складае тры, пры чым выкорыстоўваецца exit_behavior="end", ў результаты агент зупіняецца чыста, а не выклеквае эксцэпцыю, а максымальная колькасць вызоў інструментаў — два. У запыте спецыяльна прасіма прадасты шэсць гарадоў по аднаму:

agent = create_agent(
    model=model,
    tools=[get_weather],
    middleware=[
        # "end" = stop gracefully instead of raising an error
        ModelCallLimitMiddleware(run_limit=3, exit_behavior="end"),
        ToolCallLimitMiddleware(run_limit=2),
    ],
)result = agent.invoke(
    {"messages": [{"role": "user", "content":
        "Get the weather for Delhi, Mumbai, Chennai, Kolkata, Pune and Jaipur one by one."}]}
)
print(result["messages"][-1].text)

Агент хоча атрымаць шэсць результаатаў, але дасягае своіх меры і завершае роботу з часткавымі результатамі, якія ў яго є. Таксама існуе параметр thread_limit, які дазволяе обмежыць колькасць вызоў у всім потоку размовы, а не толькі за адну руну. Гэтыя два меры є простым страхаваннем; выбірайце значэння, якія будуць значна вышэй за тые, якія трэбуюць легітымныя запыты, каб яны актываўаліся толькі у разы справжніх абэранцэў.

3. ToolRetryMiddleware для ненадзеяных залежнасцей

Рэальныя інструменты выконваюцься няправільна: запыты HTTP заканчываюцца з прахаванням часу, а з’яўленні падтрымкі прыпынаюцца. ToolRetryMiddleware выкарыстоўвае wrap_tool_call, каб зафіксаваць неудачы і праказаць ўсё знову з экспансіяй у часовым інтэрвале паўторэння.

from langchain.agents.middleware import ToolRetryMiddleware

Ёжы ўтрыманню гэтага прыкладу інструмент для вычыслення цены акцій лічыць свае спробы і выклікае ConnectionError пасля першых двух, прычыму не досягае успеху. Мідлвэр адмаўляе да трох паўторэнняў, пачынаючы з адзінага секунднага адлеглена і подвойваючы гэты час кожны раз:

attempt_counter = {"count": 0}@tool
def flaky_stock_price(symbol: str) -> str:
    """Get the current stock price for a ticker symbol."""
    attempt_counter["count"] += 1
    print(f"  [tool called — attempt #{attempt_counter['count']}]")
    if attempt_counter["count"] < 3:
        raise ConnectionError("API timeout — please retry")
    return f"{symbol} is trading at ₹2,845.50"agent = create_agent(
    model=model,
    tools=[flaky_stock_price],
    middleware=[
        ToolRetryMiddleware(
            max_retries=3,       # retry a failed tool up to 3 times
            initial_delay=1.0,   # wait 1s before first retry
            backoff_factor=2.0,  # double the wait each time: 1s, 2s, 4s
        ),
    ],
)result = agent.invoke(
    {"messages": [{"role": "user", "content": "What's the price of RELIANCE stock?"}]}
)
print(result["messages"][-1].text)

Інструмент выконваецца няправільна два разы, мідлвэр чакае і праказае ўсё знову, і трэцяя спроба даходзіць да успеху. З точкі зоры агента нічога не пайшло не так. Паўторэння є безпечным толькі для ідемпатных операцый, такіх як чытанне; паўторэння інструмента, які здымае грошы са карткі або выкарыстоўвае паведамлення, можа перадаць тыя ж наследкі.

4. ModelFallbackMiddleware для перыядоў выключэння прадаўцоў

Тая ж самая ідея стойкасці можа захаваць вызов модэлі. Калі галоўная модэль не працуе, напрыклад, з-за обмежэння частоты вызоў або перыву службы, гэты мідлвэр прабуюць зноў выканаць запыт да резервных модэляў у той порядку, які вы ўказалі:

from langchain.agents.middleware import ModelFallbackMiddleware

У прыкладзе галоўным выступае легшая модэль Gemini, а як резервная дадаёцца ўтрохтая модэль Gemini:

backup_model = ChatGoogleGenerativeAI(model="gemini-3.5-flash", temperature=0)agent = create_agent(
    model=model,  # primary: gemini-3.5-flash-lite
    tools=[get_weather],
    middleware=[ModelFallbackMiddleware(backup_model)],
)

Калі галоўная модэль працуе нормальна, вы нічога не зазначыце, і гэта якраз тое, што прагледзелі. Проблемы з’яўляюцца толькі тады, калі у вашага прадастальніка ёсць труднасці. Для аблегчэння захавання рассмотрзіце можлівасць викорыстоўвання резерва з іншага прадастальніка, адколькі перывы службы часта вплываюць на всі модэлі, якія знаходзяцца за тым самым API.

5. PIIMiddleware для чулых дадзенняў

Часта ситуацыя, калі вы зовсамна не хачаце, каб электронныя пасылкі, номеры картак і IP-адрэсы былі надсыланы прадаўцу модэля. PIIMiddleware скануе тэкст у функцыі before_model, прычыму модэль яго ўжо не бачыць, і прыменяе адну з чатырох стратэгій: redact, mask, hash або block.

from langchain.agents.middleware import PIIMiddleware

Этот агент не мае жадных інструментоў. Ён павольна заменяе электронныя адрэсы на месцачыкі і маскуе номеры картак так, каб засталіся толькі пяць цифр у канцы, прычыму обе правілы прыменяюцца да даных, якія вводзі корыстнікі:

agent = create_agent(
    model=model,
    tools=[],
    middleware=[
        # Replace emails entirely with [REDACTED_EMAIL]
        PIIMiddleware("email", strategy="redact", apply_to_input=True),
        # Mask credit cards — keeps last 4 digits
        PIIMiddleware("credit_card", strategy="mask", apply_to_input=True),
    ],
)result = agent.invoke(
    {"messages": [{"role": "user", "content":
        "Draft a support reply to priya.sharma@example.com confirming her card "
        "4111-1111-1111-1234 was not charged."}]}
)
print(result["messages"][-1].text)

Модэль складае свой адказ, не отрымаўшы ні рэальнай адрэсы, ні полныя номера картак. Вы таксама можете зарэўістраць свой сабестоячы тип PII за дапамогою сябе створанага рэгулярнага выраза, напрыклад, каб заблакаваць любы тэкст, який выглядае як внутршняй ключ API:

PIIMiddleware("api_key", detector=r"sk-[a-zA-Z0-9]{32}", strategy="block")

Адказваўанне на аснове шаблонаў ловіць правільна форматаваная значэння, а не кожны творчы спосаб напісання, таму яго трэба вважаць адным з слоёў захавання, а не гарантіяй сарабіткі.

6. HumanInTheLoopMiddleware для бар’ероў затварэння

Дзеянні, такія як адправка пісьмаў, выдаленне записаў чы адбыць платежы, маюць занадта вялікія наследкі, ўжо таму, што не можна дазволіць ўсю автонамію. HumanInTheLoopMiddleware негайна зупіняе агента прытаманне роботы чулівага інструмента, чакае на рашэнне чалавека і толькі пасля таго продовжвае.

Этот спосаб працы разлічны ад пакульшых мідлвэраў. Агент, які быў зупінены, не завершаецца. Checkpointer запісвае ўсю яго стан, а ID ниткі служыць ключам для пошуку і паўторнага запуску гэтага адказваўання пазней. Адгукнік можа затварыць запуск, выправіць яго параметры чы адхіліць яго.

Экспорты прыносяць мідлвэр, контралер у памяці ад LangGraph і тип Command, які викорыстоўваецца для паўранення:

from langchain.agents.middleware import HumanInTheLoopMiddleware
from langgraph.checkpoint.memory import InMemorySaver
from langgraph.types import Command

Агент нижэй мае інструмент send_email, пазначае яго на перыявленне і зберагае стан паўзы ва InMemorySaver. Першая вызовка, на нітку demo-1, прасіць агента апраўліць электранацыю к манеджэру:

@tool
def send_email(to: str, subject: str, body: str) -> str:
    """Send an email to the given recipient."""
    return f"Email sent to {to} with subject '{subject}'"agent = create_agent(
    model=model,
    tools=[send_email],
    middleware=[
        # Pause and ask a human whenever the agent wants to call send_email
        HumanInTheLoopMiddleware(interrupt_on={"send_email": True}),
    ],
    checkpointer=InMemorySaver(),   # where the paused state is saved
)config = {"configurable": {"thread_id": "demo-1"}}# Step 1: run — the agent PAUSES before sending
result = agent.invoke(
    {"messages": [{"role": "user", "content":
        "Send an email to boss@company.com saying the report is ready."}]},
    config,
)
print("Agent paused! It wants to run:")
print(result["__interrupt__"])

Выконанне зупіняецца перад тым, як інструмент запрацюе. Элемент __interrupt__ у рэзультатах паказвае чакаючы вызов з його адмоўнікам, тэмай і тэкстам, пры чым нічога не было апраўлена. Затверджэнне апраўляецца як Command на той жа нітку, што паўраняе паўзанае выконанне:

# Step 2: approve and resume
result = agent.invoke(
    Command(resume={"decisions": [{"type": "approve"}]}),
    config,  # same thread_id -> resumes the paused run
)
print(result["messages"][-1].text)

У зьвязку з approve можна адказаць reject з прычыной або edit з змененымі параметрамі. У рэальным прыкладзе выкарыстоўвання програмы самэ гэта месца павінна выканаць адобразаванне экрана затверджэння. Две практычныя прыметкі: InMemorySaver втрачае стан, калі процес пачынаецца зноў, таму у прыкладах выкарыстоўвання патрэбны стойкія пункты контролю; кроме таго, формат пакета дадзеных для продажвання змяніўся между версіямі LangChain, таму неабходна пераканацца ў гэтым у даследжэнні middleware для версіі, яку вы выкарыстоўваете.

7. Стварэнне савастайя middleware з декоратарам

Калі ніякі з вбудованых рашэнняў не падходзіць, савастаяя middleware є простай, таму што кожны хук мае адпаведны декоратар. Неабходныя імпорты — это декоратар before_model і тип AgentState:

from langchain.agents.middleware import before_model, AgentState

Эты прыклад фіксуе, сколькі паведамленняў будзе адправлена ў кожны раз, калі вызываецца модель, і дадаўся да спісу як і будь-які заздалега створаны мідлвэр:

@before_model
def log_before_model(state: AgentState, runtime) -> None:
    print(f"  [middleware] Calling model with {len(state['messages'])} messages")
    # Returning None = observe only.
    # Returning a dict would UPDATE the agent's state (e.g., trim messages)
    return Noneagent = create_agent(
    model=model,
    tools=[get_weather],
    middleware=[log_before_model],  # plugs in like any prebuilt middleware
)

Значэнне, якое вяртаецца, ёсць важлівым выборам у дизайне. Вяртанне None означае, што мідлвэр толькі спостерагае. Вяртанне словніка апдэйтуе стан агента, і самэ гэта дазволяе скасаваць паведамленні, дадаць контекст або застосаваць спецыяльныя правілы. Дэкоратары таксама існуюць для іншых точак прыўязкі: @before_agent, @after_model, @wrap_model_call, @wrap_tool_call, а таксама @dynamic_prompt для стварэння запытанняў системы ў час експлуатацыі.

Ключовыя выводы

  • Мідлвэр аддзеляе кантроль за аператыўнымі процесамі ад логіки агента: цыкл застаецца тым самым, а контрольныя пункты дадаюцца як звычны спіс, які пасылаецца да create_agent.
  • Заказваце ліста трэба рэштырана; адредакцыя павінна выконваныць раней за ўсё, што перадае тэкст у іншы месца.
  • Сумарызацыя і ліміты вызоў кантролююць выклікі, павторныя спробы інструментоў і альтернатывныя моделі кантролююць надзеянасць, а обработка персональных даных — рызыкі ўражэння дакументаў, а участка чалавека — неперадворачальныя дзеянні.
  • Кожны з гэтых падходаў мае ліміты, якія трэба памяцать: сумарызацыя ведае пра деталі, павторныя спробы небезпечны для інструментоў, якія не є ідэмпатнымі, а выкарыстоўванне регулярных выразаў є непачатковым, а контрольныя пункты ў памяці зникаюць пасля перзапуску.
  • Большы каталог, укладаючы TodoListMiddleware, LLMToolSelectorMiddleware і ContextEditingMiddleware, описаны ў офіцыяльным даведніку.
  • Спаднёе чытанне

  • Інтеграцыя засобаў MCP у інтерфейс чату на React з вбудованым празраджэнням чалавека — Дазвольце дазнацца, як протакол Model Context Protocol падходзіць для додатка на React: чаму бэкенд должен размешчаць MCP, як працюе сервер засобаў, і як трансляваць та празраджаць вызывы засобаў у інтерфейсе.
  • Распрацоўка запытанняў між засобамі SQL і пошукам у Інтернете за дапамогою агента Gemini — Як агент для вызываў засобаў LangChain на Vertex AI выбірае між трыма засобамі перакладу текста ў SQL у формате SQLite і рэальным пошукам у Інтернете, а таксама якія проблемы з дадзеннямі, залежнасцямі і аутэнтыкацыёй могу выйсці.
  • Задаванне запытанняў да DataFrame: як агент Pandas з LangChain стварае графікі — як create_pandas_dataframe_agent ператварае запытанне на звычнай мове ў код Pandas і графік, як пераглядаць промежуточныя крокі та які захопы для цього неабходны.
  • Складанне пайплайнаў LangChain з LCEL: лінейныя, паралельныя і розгалужаныя — навучыцеся під’ўязваць запрошэння, моделі і парсеры да лінейных, багатаступеневых, паралельных і умовных пайплайнаў LangChain за дапамогою аператара pipe, RunnableParallel і RunnableBranch.