Команда Python for Devs подготовила перевод статьи о том, как с помощью middleware в LangChain 1.0 собирать LLM-агентов, готовых к реальному продакшену. В материале разбираются практические паттерны: управление контекстом, защита PII, human-in-the-loop, планирование задач и интеллектуальный выбор инструментов — всё то, что отличает экспериментального агента от надёжного рабочего решения.
Введение
Хотели ли вы когда-нибудь расширить своего LLM-агента дополнительными возможностями, например:
-
Суммировать сообщения, чтобы укладываться в контекстное окно;
-
Фильтровать PII для защиты чувствительных данных;
-
Запрашивать подтверждение человека для критически важных действий,
…но не понимали, как это правильно реализовать?
Если вы пытались сделать это в LangChain v0.x, то, скорее всего, столкнулись со сложными pre/post-хуками, которые было трудно масштабировать и тестировать.
LangChain 1.0 решает эти проблемы, представляя компонуемую архитектуру middleware, которая предлагает переиспользуемые и тестируемые компоненты, построенные по тем же принципам, что и middleware в веб-серверах.
Полный исходный код и Jupyter Notebook для этого туториала доступны на GitHub. Склонируйте репозиторий и повторяйте шаги вместе с нами.
Введение в паттерн Middleware
Опираясь на основы LangChain, которые мы рассматривали ранее, LangChain 1.0 вводит middleware-компоненты, дающие детальный контроль над выполнением агентов. Каждый middleware — это изолированный компонент, который:
-
Решает одну конкретную задачу (наблюдение, изменение, управление или принуждение);
-
Может тестироваться независимо;
-
Комбинируется с другими middleware через стандартный интерфейс.
Существует четыре категории middleware:
-
Monitor: отслеживание поведения агента с помощью ��огирования, аналитики и отладки;
-
Modify: преобразование промптов, выбор инструментов и форматирование вывода;
-
Control: добавление повторных попыток, запасных вариантов и логики досрочного завершения;
-
Enforce: применение лимитов, защитных ограничений и обнаружение PII.
В этой статье рассматриваются пять ключевых middleware-компонентов:
-
Суммирование сообщений (modify): управление контекстным окном за счёт сжатия длинных диалогов;
-
Фильтрация PII (enforce): защита чувствительных данных путём редактирования email-адресов и номеров телефонов;
-
Human-in-the-loop (control): приостановка выполнения для критических действий, требующих одобрения;
-
Планирование задач (modify): разбиение сложных запросов на управляемые подзадачи;
-
Интеллектуальный выбор инструментов (modify): предварительная фильтрация инструментов для снижения затрат и повышения точности.
Давайте разберём, как каждый из этих middleware-компонентов улучшает рабочие процессы продакшен-агентов.
Установка
Установите LangChain 1.0 и интеграцию с OpenAI:
# Option 1: pip
pip install langchain langchain-openai
# Option 2: uv (faster alternative to pip)
uv add langchain langchain-openai
Примечание: если вы обновляетесь с LangChain v0.x, добавьте флаг
--U:
pip install --U langchain langchain-openai
Также потребуется API-ключ OpenAI:
export OPENAI_API_KEY="your-api-key-here"
Суммирование сообщений
При создании диалоговых агентов история сообщений растёт с каждым новым шагом. Длинные диалоги быстро выходят за пределы контекстного окна модели, что приводит к ошибкам API или ухудшению качества ответов.
SummarizationMiddleware автоматизирует решение этой проблемы за счёт:
-
Отслеживания количества токенов во всём диалоге;
-
Сжатия более старых сообщений при превышении заданных порогов;
-
Сохранения недавнего контекста, наиболее важного в текущий момент.
Преимущества такого подхода:
-
Снижение затрат на API за счёт передачи меньшего количества токенов в каждом запросе;
-
Более быстрые ответы благодаря меньшему контекстному окну;
-
Сохранение полного контекста за счёт комбинации кратких сводок и полной недавней истории.
Вот пример использования SummarizationMiddleware в составе агента:
from langchain.agents import create_agent
from langchain.agents.middleware import SummarizationMiddleware
agent = create_agent(
model="openai:gpt-4o",
tools=[],
middleware=[
SummarizationMiddleware(
model="openai:gpt-4o-mini",
max_tokens_before_summary=400,
messages_to_keep=5
)
]
)
Эта конфигурация настраивает автоматическое управление диалогом:
-
model="openai:gpt-4o"— основная модель, которая отвечает за ответы агента. -
max_tokens_before_summary=400— запускает суммирование, когда диалог превышает 400 токенов. -
messages_to_keep=5— сохраняет последние 5 сообщений целиком, без сжатия. -
model="openai:gpt-4o-mini"— использует более быструю и дешёвую модель для создания сводок.
Примечание: эти значения намеренно занижены для демонстрации, чтобы быстрее показать, как работает суммирование. В продакшене обычно используют
max_tokens_before_summary=4000иmessages_to_keep=20(рекомендуемые значения по умолчанию).
Давайте воспользуемся этим агентом, чтобы смоделировать диалог службы поддержки и отследить расход токенов.
Сначала подготовим реалистичный диалог поддержки из нескольких реплик:
# Simulate a customer support conversation
conversation_turns = [
"I ordered a laptop last week but haven't received it yet. Order #12345.",
"Can you check the shipping status? I need it for work next Monday.",
"Also, I originally wanted the 16GB RAM model but ordered 8GB by mistake.",
"Is it too late to change the order? Or should I return and reorder?",
"What's your return policy on laptops? Do I need the original packaging?",
"If I return it, how long does the refund take to process?",
"Can I get expedited shipping on the replacement 16GB model?",
"Does the 16GB version come with the same warranty as the 8GB?",
"Are there any promotional codes I can use for the new order?",
"What if the new laptop arrives damaged? What's the process?",
]
Далее определим вспомогательные функции, чтобы отслеживать расход токенов и проверять, что суммирование действительно срабатывает:
-
estimate_token_count(): приблизительно считает количество токенов, оценивая их по числу слов во всех сообщениях; -
get_actual_tokens(): извлекает фактическое количество токенов из метаданных ответа модели; -
print_token_comparison(): выводит сравнение «оценка vs факт», чтобы было видно, в какой момент включается суммирование.
def estimate_token_count(messages):
"""Estimate total tokens in message history."""
return sum(len(msg.content.split()) * 1.3 for msg in messages)
def get_actual_tokens(response):
"""Extract actual token count from response metadata."""
last_ai_message = response["messages"][-1]
if hasattr(last_ai_message, 'usage_metadata') and last_ai_message.usage_metadata:
return last_ai_message.usage_metadata.get("input_tokens", 0)
return None
def print_token_comparison(turn_number, estimated, actual):
"""Print token count comparison for a conversation turn."""
if actual is not None:
print(f"Turn {turn_number}: ~{int(estimated)} tokens (estimated) → {actual} tokens (actual)")
else:
print(f"Turn {turn_number}: ~{int(estimated)} tokens (estimated)")
Наконец, запустим диалог и посмотрим, как меняется расход токенов от шага к шагу:
messages = []
for i, question in enumerate(conversation_turns, 1):
messages.append(HumanMessage(content=question))
estimated_tokens = estimate_token_count(messages)
response = agent.invoke({"messages": messages})
messages.extend(response["messages"][len(messages):])
actual_tokens = get_actual_tokens(response)
print_token_comparison(i, estimated_tokens, actual_tokens)
Вывод:
Turn 1: ~16 tokens (estimated) → 24 tokens (actual)
Turn 2: ~221 tokens (estimated) → 221 tokens (actual)
Turn 3: ~408 tokens (estimated) → 415 tokens (actual)
Turn 4: ~646 tokens (estimated) → 509 tokens (actual)
Turn 5: ~661 tokens (estimated) → 524 tokens (actual)
Turn 6: ~677 tokens (estimated) → 379 tokens (actual)
Turn 7: ~690 tokens (estimated) → 347 tokens (actual)
Turn 8: ~705 tokens (estimated) → 184 tokens (actual)
Turn 9: ~721 tokens (estimated) → 204 tokens (actual)
Turn 10: ~734 tokens (estimated) → 195 tokens (actual)
Обратите внимание на закономерность в количестве токенов:
-
Шаги 1–3: токены растут равномерно (24 → 221 → 415) по мере того, как диалог «обрастает» контекстом.
-
Шаг 4: подключается суммирование — фактическое число токенов падает до 509, хотя по оценке должно было быть 646.
-
Шаг 8: самое заметное снижение — отправлено всего 184 фактических токена вместо 705 по оценке (сокращение на 74%).
После прохождения порога в 400 токенов middleware автоматически сжимает более старые сообщения, сохраняя при этом последние 5 реплик целиком. Благодаря этому расход токенов остаётся низким, даже когда диалог продолжается.
Обнаружение и фильтрация PII
В диалогах службы поддержки часто встречаются чувствительные данные: email-адреса, номера телефонов, идентификаторы аккаунтов. Если логировать или хранить такую информацию без редактирования, это создаёт риски для соответствия требованиям и безопасности.
PIIMiddleware автоматически защищает персональные данные (PII) за счёт:
-
Встроенных детекторов для распространённых типов PII (email, банковские карты, IP-адреса);
-
Пользовательских regex-шаблонов для чувствительных данных, специфичных для домена;
-
Нескольких стратегий защиты: редактирование, маскирование, хэширование или блокировка;
-
Автоматического применения ко всем сообщениям до того, как их обработает модель.
Сначала настроим агента с несколькими детекторами PII:
Каждый детектор в этом примере демонстрирует свою стратегию защиты:
-
Детектор email: использует встроенный шаблон и стратегию redact (полная замена).
-
Детектор телефона: использует пользовательский regex
bd{3}-d{3}-d{4}bи стратегию mask (частичная видимость). -
Детектор ID аккаунта: использует пользовательский шаблон
b[A-Z]{2}d{8}bи стратегию redact (полное удаление).
from langchain.agents import create_agent
from langchain.agents.middleware import PIIMiddleware
from langchain_core.messages import HumanMessage
agent = create_agent(
model="openai:gpt-4o",
tools=[],
middleware=[
# Built-in email detector - replaces emails with [REDACTED_EMAIL]
PIIMiddleware("email", strategy="redact", apply_to_input=True),
# Custom phone number pattern - shows only last 4 digits
PIIMiddleware(
"phone",
detector=r"bd{3}-d{3}-d{4}b",
strategy="mask",
apply_to_input=True,
),
# Custom regex pattern for account IDs (e.g., AB12345678)
PIIMiddleware(
"account_id",
detector=r"b[A-Z]{2}d{8}b",
strategy="redact",
apply_to_input=True,
),
],
)
Далее создадим сообщение с чувствительными данными и вызовем агента:
# Create a message with PII
original_message = HumanMessage(content="My email is john@example.com, phone is 555-123-4567, and account is AB12345678")
print(f"Original message: {original_message.content}")
# Invoke the agent
response = agent.invoke({"messages": [original_message]})
Вывод:
Original message: My email is john@example.com, phone is 555-123-4567, and account is AB12345678
Теперь посмотрим, какое сообщение фактически было отправлено в модель, чтобы убедиться, что редактирование сработало:
# Check what was actually sent to the model (after PII redaction)
input_message = response["messages"][0]
print(f"Message sent to model: {input_message.content}")
Вывод:
Message sent to model: My email is [REDACTED_EMAIL], phone is ****4567, and account is [REDACTED_ACCOUNT_ID]
Middleware корректно обработал все три типа чувствительной информации:
-
Email: полностью отредактирован и заменён на
[REDACTED_EMAIL]. -
Телефон: замаскирован с сохранением только последних 4 цифр (
****4567). -
ID аккаунта: полностью удалён и заменён на
[REDACTED_ACCOUNT_ID].
Human-in-the-loop
Автономные агенты могут выполнять чувствительные действия — например, обрабатывать возвраты или менять настройки аккаунта. Если делать это без контроля человека, возрастает риск ошибок или злоупотреблений.
HumanInTheLoopMiddleware автоматизирует процесс согласования: он ставит выполнение на паузу и ждёт одобрения, прежде чем продолжить:
from langchain.agents import create_agent
from langchain.agents.middleware import HumanInTheLoopMiddleware
from langchain_core.tools import tool
from langgraph.checkpoint.memory import MemorySaver
@tool
def process_refund(amount: float, reason: str) -> str:
"""Process a customer refund. Use this when a customer requests a refund."""
return f"Refund of ${amount} processed for reason: {reason}"
# Create memory checkpointer for state persistence
memory = MemorySaver()
agent = create_agent(
model="openai:gpt-4o",
tools=[process_refund],
middleware=[HumanInTheLoopMiddleware(interrupt_on={"process_refund": True})],
checkpointer=memory, # Required for state persistence
system_prompt="You are a customer support agent. Use the available tools to help customers. When a customer asks for a refund, use the process_refund tool.",
)
Эта конфигурация настраивает агента, который:
-
Использует
HumanInTheLoopMiddleware, чтобы приостанавливать выполнение перед вызовомprocess_refund; -
Использует чекпойнтер (
MemorySaver), чтобы сохранять состояние агента во время прерываний и позволять продолжить выполнение после одобрения.
Теперь вызовем агента с запросом на возврат:
# Agent pauses before executing sensitive tools
response = agent.invoke(
{"messages": [("user", "I need a refund of $100 for my damaged laptop")]},
config={"configurable": {"thread_id": "user-123"}},
)
Агент поставит выполнение на паузу в момент, когда попытается обработать возврат. Чтобы убедиться, что пауза действительно произошла, определим вспомогательные функции для обнаружения прерываний.
def has_interrupt(response):
"""Check if response contains an interrupt."""
return "__interrupt__" in response
def display_action(action):
"""Display pending action details."""
print(f"Pending action: {action['name']}")
print(f"Arguments: {action['args']}")
print()
def get_user_approval():
"""Prompt user for approval and return decision."""
approval = input("Approve this action? (yes/no): ")
if approval.lower() == "yes":
print("✓ Action approved")
return True
else:
print("✗ Action rejected")
return False
Теперь воспользуемся этими функциями, чтобы проверить наличие прерываний и обработать одобрение:
if has_interrupt(response):
print("Execution interrupted - waiting for approvaln")
interrupts = response["__interrupt__"]
for interrupt in interrupts:
for action in interrupt.value["action_requests"]:
display_action(action)
approved = get_user_approval()
Вывод:
Execution interrupted - waiting for approval
Pending action: process_refund
Arguments: {'amount': 100, 'reason': 'Damaged Laptop'}
Approve this action? (yes/no): yes
✓ Action approved
Middleware успешно перехватил вызов инструмента process_refund до выполнения, показав все необходимые детали (название действия и аргументы) для проверки человеком. И только после явного одобрения агент продолжает выполнение чувствительной операции.
Планирование задач
Сложные задачи вроде «отрефакторить мой код» или «проанализировать этот датасет» требуют разбиения на более мелкие и управляемые шаги. Без явного планирования агенты часто начинают хаотично перескакивать между подзадачами или вовсе пропускают критически важные этапы.
TodoListMiddleware обеспечивает структурированное управление задачами за счёт:
-
Автоматического предоставления инструмента
write_todosдля планирования; -
Отслеживания статуса выполнения в многошаговых сценариях;
-
Возврата структурированного списка задач в результатах работы агента.
Преимущества такого подхода:
-
Более качественная декомпозиция задач благодаря пошаговому планированию;
-
Прозрачное отслеживание прогресса в сложных рабочих процессах;
-
Снижение числа ошибок из-за пропущенных или забытых подзадач.
Вот как включить планирование для агента:
from langchain.agents import create_agent
from langchain.agents.middleware import TodoListMiddleware
from langchain_core.tools import tool
@tool
def analyze_code(file_path: str) -> str:
"""Analyze code quality and find issues."""
return f"Analyzed {file_path}: Found 3 code smells, 2 security issues"
@tool
def refactor_code(file_path: str, changes: str) -> str:
"""Refactor code with specified changes."""
return f"Refactored {file_path}: {changes}"
agent = create_agent(
model="openai:gpt-4o",
tools=[analyze_code, refactor_code],
middleware=[TodoListMiddleware()]
)
Эта конфигурация автоматически добавляет агенту возможности планирования.
Теперь попросим агента выполнить многошаговую задачу по рефакторингу:
from langchain_core.messages import HumanMessage
response = agent.invoke({
"messages": [HumanMessage("I need to refactor my authentication module. First analyze it, then suggest improvements, and finally implement the changes.")]
})
Посмотрим на список задач агента, чтобы понять, как он спланировал работу:
# Access the structured todo list from the response
if "todos" in response:
print("Agent's Task Plan:")
for i, todo in enumerate(response["todos"], 1):
status = todo.get("status", "pending")
print(f"{i}. [{status}] {todo['content']}")
Вывод:
Agent's Task Plan:
1. [in_progress] Analyze the authentication module code to identify quality issues and areas for improvement.
2. [pending] Suggest improvements based on the analysis of the authentication module.
3. [pending] Implement the suggested improvements in the authentication module code.
Отлично! Агент автоматически разложил многошаговый запрос на рефакторинг на три отдельные задачи: одна уже в работе, две ожидают выполнения. Такой структурированный подход обеспечивает последовательное выполнение без пропуска критически важных шагов.
Интеллектуальный выбор инструментов
Агенты с большим количеством инструментов (10+) сталкиваются с проблемой масштабирования: если в каждом запросе отправлять описания всех инструментов, это впустую расходует токены и ухудшает производительность. Модели приходится «переваривать» нерелевантные варианты, что увеличивает задержки и стоимость.
LLMToolSelectorMiddleware решает это, используя более компактную модель для предварительного отбора релевантных инструментов:
-
Использует вторую LLM (отдельную от основной модели агента), чтобы заранее отфильтровать и ограничить набор инструментов, отправляемых в основную модель;
-
Позволяет всегда включать в выбор критически важные инструменты;
-
Анализирует запросы и оставляет только релевантные инструменты.
Преимущества:
-
Более низкие затраты за счёт передачи меньшего числа описаний инструментов в каждом запросе;
-
Более быстрые ответы благодаря меньшему «инструментальному» контексту;
-
Более высокая точность, потому что модель не отвлекается на нерелевантные варианты.
Давайте создадим агента с большим набором инструментов для сценария службы поддержки:
from langchain.agents import create_agent
from langchain.agents.middleware import LLMToolSelectorMiddleware
from langchain_core.tools import tool
# Define multiple tools for different support scenarios
@tool
def lookup_order(order_id: str) -> str:
"""Look up order details and shipping status."""
return f"Order {order_id}: Shipped on 2025-01-15"
@tool
def process_refund(order_id: str, amount: float) -> str:
"""Process a customer refund."""
return f"Refund of ${amount} processed for order {order_id}"
@tool
def check_inventory(product_id: str) -> str:
"""Check product inventory levels."""
return f"Product {product_id}: 42 units in stock"
@tool
def update_address(order_id: str, new_address: str) -> str:
"""Update shipping address for an order."""
return f"Address updated for order {order_id}"
@tool
def cancel_order(order_id: str) -> str:
"""Cancel an existing order."""
return f"Order {order_id} cancelled"
@tool
def track_shipment(tracking_number: str) -> str:
"""Track package location."""
return f"Package {tracking_number}: Out for delivery"
@tool
def apply_discount(order_id: str, code: str) -> str:
"""Apply discount code to order."""
return f"Discount {code} applied to order {order_id}"
@tool
def schedule_delivery(order_id: str, date: str) -> str:
"""Schedule delivery for specific date."""
return f"Delivery scheduled for {date}"
Настроим агента с интеллектуальным выбором инструментов:
agent = create_agent(
model="openai:gpt-4o",
tools=[
lookup_order, process_refund, check_inventory,
update_address, cancel_order, track_shipment,
apply_discount, schedule_delivery
],
middleware=[
LLMToolSelectorMiddleware(
model="openai:gpt-4o-mini", # Use cheaper model for selection
max_tools=3, # Limit to 3 most relevant tools
always_include=["lookup_order"], # Always include order lookup
)
]
)
Эта конфигурация создаёт эффективную систему фильтрации:
-
model="openai:gpt-4o-mini"— использует более компактную и быструю модель для выбора инструментов. -
max_tools=3— ограничивает набор тремя наиболее релевантными инструментами для каждого запроса. -
always_include=["lookup_order"]— гарантирует, что инструмент поиска заказа всегда будет доступен.
Теперь протестируем агента на разных запросах клиентов.
Сначала определим вспомогательную функцию, которая покажет, какие инструменты использовались:
def show_tools_used(response):
"""Display which tools were called during agent execution."""
tools_used = []
for msg in response["messages"]:
if hasattr(msg, "tool_calls") and msg.tool_calls:
for tool_call in msg.tool_calls:
tools_used.append(tool_call["name"])
if tools_used:
print(f"Tools used: {', '.join(tools_used)}")
print(f"Response: {response['messages'][-1].content}n")
Проверим запрос на отслеживание посылки:
# Example 1: Package tracking query
response = agent.invoke({
"messages": [HumanMessage("Where is my package? Tracking number is 1Z999AA10123456784")]
})
show_tools_used(response)
Вывод:
Tools used: track_shipment
Response: Your package with tracking number 1Z999AA10123456784 is currently out for delivery.
Проверим запрос на возврат:
# Example 2: Refund request
response = agent.invoke({
"messages": [HumanMessage("I need a refund of $50 for order ORD-12345")]
})
show_tools_used(response)
Вывод:
Tools used: lookup_order, process_refund
Response: The refund of $50 for order ORD-12345 has been successfully processed.
Проверим запрос на наличие товара:
# Example 3: Inventory check
response = agent.invoke({
"messages": [HumanMessage("Do you have product SKU-789 in stock?")]
})
show_tools_used(response)
Вывод:
Tools used: check_inventory
Response: Yes, we currently have 42 units of product SKU-789 in stock.
Middleware показал точный выбор инструментов для разных типов запросов:
-
track_shipment— для трекинг-номеров; -
lookup_order+process_refund— для запросов на возврат; -
check_inventory— для запросов о наличии на складе.
В каждом запросе middleware отфильтровал 5+ нерелевантных инструментов, передав основной модели только то, что действительно нужно.
Сборка продакшен-агента с несколькими middleware
Давайте объединим три middleware-компонента и соберём готового к продакшену агента поддержки, который обрабатывает реалистичный сценарий: у клиента длинная история переписки, он просит вернуть деньги и одновременно сообщает свой email.
from langchain.agents import create_agent
from langchain.agents.middleware import (
SummarizationMiddleware,
PIIMiddleware,
HumanInTheLoopMiddleware
)
from langchain_core.tools import tool
from langgraph.checkpoint.memory import MemorySaver
@tool
def process_refund(amount: float, reason: str) -> str:
"""Process a customer refund."""
return f"Refund of ${amount} processed for reason: {reason}"
# Create agent with three middleware components
agent = create_agent(
model="openai:gpt-4o",
tools=[process_refund],
middleware=[
SummarizationMiddleware(
model="openai:gpt-4o-mini",
max_tokens_before_summary=400,
messages_to_keep=5
),
PIIMiddleware("email", strategy="redact", apply_to_input=True),
HumanInTheLoopMiddleware(interrupt_on={"process_refund": True})
],
checkpointer=MemorySaver()
)
Теперь протестируем агента на реалистичном взаимодействии с клиентом, обрабатывая каждое сообщение и показывая, как middleware с ними работает.
Сначала определим вспомогательную функцию, чтобы отслеживать поведение middleware, используя ранее определённые хелперы:
def process_message_with_tracking(agent, messages, thread_id, turn_num):
"""Process messages and show middleware behavior."""
print(f"n--- Turn {turn_num} ---")
print(f"User: {messages[-1][1]}")
response = agent.invoke(
{"messages": messages},
config={"configurable": {"thread_id": thread_id}}
)
# Check for interrupts (human-in-the-loop)
if has_interrupt(response):
print(" Execution paused for approval")
else:
# Show agent response
agent_message = response["messages"][-1].content
print(f"Agent: {agent_message}")
# Check for PII redaction
full_response = str(response["messages"])
if "[REDACTED_EMAIL]" in full_response:
print(" PII detected and redacted")
return response
Теперь смоделируем диалог с клиентом так, чтобы продемонстрировать все три middleware-компонента:
-
Шаги 1–3: обычный диалог о повреждённом ноутбуке.
-
Шаг 4: клиент сообщает email и просит подтверждение (проверяем редактирование в
PIIMiddleware). -
Шаг 5: клиент просит вернуть $1200 (срабатывает согласование в
HumanInTheLoopMiddleware).
messages = []
# Turn 1: Initial complaint
messages.append(("user", "I ordered a laptop but it arrived damaged."))
process_message_with_tracking(agent, messages, "customer-456", 1)
# Turn 2: Additional details
messages.append(("user", "I already tried troubleshooting but it won't turn on."))
process_message_with_tracking(agent, messages, "customer-456", 2)
# Turn 3: More context
messages.append(("user", "The screen is cracked and there's a dent on the corner."))
process_message_with_tracking(agent, messages, "customer-456", 3)
# Turn 4: PII exposure - test if middleware hides email from model
messages.append(("user", "My email is customer@example.com. Can you confirm my email address?"))
response = process_message_with_tracking(agent, messages, "customer-456", 4)
# Turn 5: Sensitive action request - triggers human approval
messages.append(("user", "Can I get a full refund of $1200?"))
response = process_message_with_tracking(agent, messages, "customer-456", 5)
# If interrupted, show approval flow
if has_interrupt(response):
interrupts = response["__interrupt__"]
for interrupt in interrupts:
for action in interrupt.value["action_requests"]:
display_action(action)
approved = get_user_approval()
Вывод:
--- Turn 1 ---
User: I ordered a laptop but it arrived damaged.
Agent: I'm sorry to hear that your laptop arrived damaged. To resolve this issue, I can assist you in processing a refund. Could you please provide the amount of the purchase and any specific reason you would like to include for the refund?
--- Turn 2 ---
User: I already tried troubleshooting but it won't turn on.
Agent: Thank you for sharing that information. I'll proceed with processing a refund for the damaged laptop. Could you please provide the purchase amount, so I can include it in the refund request?
--- Turn 3 ---
User: The screen is cracked and there's a dent on the corner.
Agent: I'm sorry to hear about the condition of your laptop. I will help you process a refund. Could you please let me know the purchase amount, so I can proceed with the refund request?
--- Turn 4 ---
User: My email is customer@example.com. Can you confirm my email address?
Agent: I'm sorry, but I'm unable to confirm or access email addresses for privacy and security reasons. However, I can assist you with processing a refund. Could you please provide the amount you paid for the laptop so that I can proceed with the refund request?
PII detected and redacted
--- Turn 5 ---
User: Can I get a full refund of $1200?
Execution paused for approval
Pending action: process_refund
Arguments: {'amount': 1200, 'reason': 'Laptop arrived damaged with a cracked screen and dent on the corner, and it will not turn on after troubleshooting.'}
Approve this action? (yes/no): yes
✓ Action approved
Результат демонстрирует корректные меры безопасности:
-
Шаг 4: агент сообщает, что «не может подтвердить или получить доступ к email-адресам», что подтверждает:
PIIMiddlewareуспешно заменилcustomer@example.comна[REDACTED_EMAIL]. -
Защита email: модель так и не увидела реальный адрес — это снижает риск утечек и попадания PII в логи.
-
Подтверждение возврата: операция на $1200 не выполнялась до тех пор, пока человек явно не дал одобрение.
Если вам нужно координировать несколько агентов с общим состоянием и рабочими процессами, изучите наш туториал по LangGraph.
Итоговые мысли
Создание LLM-агентов, готовых к продакшену, с использованием middleware LangChain 1.0 требует минимум инфраструктурного кода. Каждый компонент отвечает за одну конкретную задачу: управление контекстным окном, защита чувствительных данных, контроль потока выполнения или структурирование сложных задач.
Лучше всего двигаться постепенно. Добавляйте по одному middleware за раз, проверяйте, как он себя ведёт, а затем сочетайте его с другими. Такой модульный подход позволяет начать с простого решения и расширять его по мере того, как растут требования к агенту.
Русскоязычное сообщество про Python

Друзья! Эту статью подготовила команда Python for Devs — канала, где каждый день выходят самые свежие и полезные материалы о Python и его экосистеме. Подписывайтесь, чтобы ничего не пропустить!
Автор: python_leader


