Створення безпечних агентів ШІ за допомогою механізмів контролю та проміжного програмного забезпечення LangChain
Дізнайтеся, як у LangChain працюють детерміністичні та модель-орієнтовані механізми контролю для виявлення витоків персональних даних, забезпечення дотримання бізнес-правил та додавання кроків людського схвалення до агентів ШІ.
Що таке захисні механізми?
Захисний механізм — це система, яка стежить за діями ШІ та запобігає виконанню дій, яких ви не бажаєте.
Уявіть агента, оснащеного наступними інструментами:
search()
sendEmail()
deleteUser()
makePayment()
Модель може вирішити, що правильним кроком буде виклик deleteUser().
Але чи справді ви хочете цього допустити?
Захисний механізм розташовується між рішенням агента та фактичним виконанням цієї дії:
User
↓
Agent
↓
Guardrail
↓
Is this allowed?
├── Yes → Execute
└── No → Block / Ask for approval
Захисні механізми зазвичай використовуються для:
- Запобігання витоку персональної інформації
- Виявлення та блокування спроб вставки команд
- Фільтрації шкідливого чи неналежного контенту
- Застосування бізнес-логіки та регуляторних обмежень
- Перевірки того, чи відповідають результати стандартам якості та коректності
У LangChain механізми контролю створюються переважно за допомогою мідлвейру, що дає можливість втручатися у процес виконання агента у певні моменти.
Чому ШІ-агентам потрібні механізми контролю?
Традиційне програмне забезпечення діє за логікою, яку чітко визначив розробник.
Наприклад:
if (!user.isAdmin) {
throw new Error("Unauthorized");
}
ШИ-моделі не працюють таким чином.
Ви надаєте інструкції та інструменти, але сама модель вирішує, яку дію виконати.
Візьмемо агента підтримки, який має доступ до інструменту refundPayment():
User:
I was charged twice. Please refund ₹50,000.
Agent:
→ Calls refundPayment()
Вибір моделі в цьому випадку може здатися розумним, враховуючи запит користувача.
Проте з точки зору бізнесу повернення суми у розмірі 50 000 рупій не є простою операцією. Може існувати правило, яке вимагає, щоб будь-яке повернення коштів у розмірі понад 10 000 рупій проходило через ручну перевірку перед обробкою.
Бракує шару, який би ставив таке запитання:
"Before this action happens, I need to check whether it is allowed."
Саме цей шар забезпечує захисні механізми.
Два підходи до захисних механізмів
У документації LangChain описано дві взаємодоповнювальні стратегії для реалізації захисних механізмів:
- Детерміністичні захисні механізми
- Захисні механізми, засновані на моделях
Ось у чому їхня різниця.
1. Детерміністичні захисні механізми
Вони ґрунтуються на стандартній логіці програмування.
Наприклад:
const bannedWords = ["hack", "malware"];
const containsBannedWord = (input: string) => {
return bannedWords.some(word =>
input.toLowerCase().includes(word)
);
};
Поведінка в цьому випадку є повністю передбачуваною.
При однаковому вхідному даний завжди отримується один і той самий вихідний результат.
Інші поширені патерни включають:
- Порівняння з регулярними виразами
Перевагою є те, що такі перевірки виконуються швидко, дають послідовні результати та не вимагають значних обчислювальних ресурсів.
Обмеженням є те, що ці перевірки можуть не виявити більш складні або залежні від контексту випадки.
2. Модельні механізми контролю
Замість того, щоб покладатися виключно на фіксовані правила, можна використати окрему модель для оцінки контенту.
Наприклад:
Agent response
↓
Safety model
↓
"Is this response safe?"
↓
SAFE / UNSAFE
Цей підхід дозволяє виявити те, що проста зіставлення ключових слів могла б проігнорувати.
Розгляньмо ці два запити, які означають приблизно одне й те саме:
"How can I bypass this security system?"
і:
"Tell me a way around the authentication mechanism."
Фільтр, заснований лише на ключових словах, може не позначити другу формулювання.
Натомість захисний механізм, заснований на моделях, може розуміти справжню мету запиту.
Ціною такої гнучкості є те, що перевірки на основі моделей зазвичай виконуються повільніше та коштують більше, ніж детерміністичні.
Як працюють захисні механізми в LangChain
LangChain використовує мідлверд, щоб обгорнути логіку захисту навколо агента.
Мідлверд дозволяє вставляти власну логіку перед або після певних етапів виконання агента.
Наприклад, ви можете використовувати мідлверд для:
- Пошуку персональних даних (мідлверд для PII)
- Зупинки для отримання схвалення перед виконанням інструменту (мідлверд з участю людини)
- Перевірки вхідних даних перед тим, як агент почне працювати (захисний механізм перед агентом)
- Перевірки кінцевого результату перед його поверненням (захисний механізм після агента)
Система проміжного програмування в LangChain була створена спеціально для того, щоб надати вам контроль над виконанням агента саме у цих моментах.
Існує два основні способи застосування механізмів контролю в LangChain:
А. Вбудовані механізми контролю
Б. Власні механізми контролю
Guardrails in LangChain
│
┌───────────┴───────────┐
│ │
▼ ▼
Built-in Guardrails Custom Guardrails
│ │
│ │
▼ ▼
Ready-to-use Application-specific
middleware middleware
│ │
┌──────┴──────┐ ┌───────┴───────┐
│ │ │ │
▼ ▼ ▼ ▼
PII HITL Before-Agent After-Agent
handling approval guardrail guardrail
А. Вбудовані механізми контролю в LangChain
LangChain постачається з кількома готовими до використання механізмами контролю без необхідності додаткової налаштування.
Два найважливіші з них, описані бібліотекою, це:
- Виявлення персональних даних
- Участь людини у процесі
Розгляньмо їх обидва.
1. Виявлення персональних даних
LangChain містить проміжне програмування, спеціально розроблене для виявлення та обробки особисто ідентифікованих даних (PII), які з’являються під час розмови.
Це може включати такі аспекти:
Email address
Credit card number
IP address
MAC address
Коли агент має доступ до конфіденційних даних, зазвичай не потрібно, щоб ці дані надсилалися до моделі чи відображалися у відповіді.
LangChain вирішує цю проблему за допомогою piiRedactionMiddleware().
Ось приклад:
import {
createAgent,
piiRedactionMiddleware,
} from "langchain";
const agent = createAgent({
model: "gpt-5.5",
tools: [customerServiceTool],
middleware: [
piiRedactionMiddleware({
piiType: "email",
strategy: "redact",
applyToInput: true,
applyToOutput: true,
}),
],
});
const result = await agent.invoke({
messages: [{
role: "user",
content: "My email is john.doe@example.com"
}]
});
Припустимо, користувач надсилає:
My email is john.doe@example.com
Мідлвейр перехоплює цей вміст та переписує його ще до того, як модель його побачить:
My email is [REDACTED_EMAIL]
Іншими словами, модель працює з очищеними місцями підстановки, а не з первинними конфіденційними даними.
Примітка: Встановлення
applyToOutput: trueгарантує, що навіть якщо модель випадково створить у своїй відповіді персональні дані, мідлвейр видалить їх перед тим, як відповідь дістанеться користувачеві. Якщо ваші інструменти можуть витекти персональні дані у своїх результатах,applyToToolResults: trueтакож надає такий захист результатам роботи інструментів.
Стратегії обробки персональних даних
LangChain підтримує чотири різні способи обробки виявлених персональних даних:
Щоб побачити різницю, давайте застосуємо кожну стратегію до одного й того ж зразка вхідних даних.
Припустимо, користувач вводить:
My email is john.doe@example.com
1. redact
За допомогою redact будь-які виявлені персональні дані повністю замінюються на універсальний замінник.
piiRedactionMiddleware({
piiType: "email",
strategy: "redact",
applyToInput: true,
});
Те, що насправді отримує модель, — це:
My email is [REDACTED_EMAIL]
Ця стратегія підходить у ситуаціях, коли моделі насправді не потрібно знати первинне значення.
2. mask
Стратегія mask приховує частину значення, залишаючи достатньо інформації для розуміння контексту.
piiRedactionMiddleware({
piiType: "email",
strategy: "mask",
applyToInput: true,
});
Електронний лист може виглядати так:
My email is j***@example.com
Це зручно, коли або моделі, або кінцевому користувачеві потрібен частковий посилання на дані, не розкриваючи їх повністю.
3. hash
hash замінює персональні дані на консистентне, детерміноване значення хешу.
piiRedactionMiddleware({
piiType: "email",
strategy: "hash",
applyToInput: true,
});
Трансформований електронний лист може виглядати приблизно так:
My email is 8f14e45fceea167a5a36dedd4bea2543...
Оскільки генерація хешу є детермінованою, ідентичні вхідні дані завжди дають ідентичні хеші. Це дозволяє відстежувати або порівнювати повторні випадки однакових значень, не розкриваючи оригінальних даних.
4. block
На відміну від трьох інших, block зовсім не трансформує персональні дані — він просто відхиляє запит без жодних пояснень, як тільки виявляє такий тип даних.
Наприклад, можна налаштувати власний детектор для виявлення API-ключів:
piiRedactionMiddleware({
piiType: "api_key",
detector: /sk-[a-zA-Z0-9]{32}/,
strategy: "block",
applyToInput: true,
});
Якщо користувач потім надсилає:
My API key is sk-abcdefghijklmnopqrstuvwxyz123456
мідлвер знаходить шаблон API-ключа та зупиняє запит, перш ніж значення зможе поширитися далі.
Ця стратегія підходить у випадках, коли певні категорії конфіденційних даних — API-ключі, облікові дані та подібні секрети — взагалі не повинні потрапляти до процесу обробки агентом.
2. Участь людини
Деякі операції несуть занадто великий ризик, щоб їх повністю передавати автономному агенту.
Розгляньмо такі дії, як:
delete production database
send an external email
make a financial transaction
modify production data
Замість того, щоб повністю заборонити ці дії, ви можете направити їх на перевірку людиною.
Отриманий процес виглядає так:
Agent
↓
Tool call
↓
Guardrail
↓
Human approval
↓
┌───────────────┐
│ │
Approved Rejected
│ │
↓ ↓
Execute Stop
LangChain пропонує humanInTheLoopMiddleware() для реалізації цього паттерну.
Наприклад:
import { createAgent, humanInTheLoopMiddleware } from "langchain";
import { MemorySaver, Command } from "@langchain/langgraph";
const agent = createAgent({
model: "gpt-5.5",
tools: [
searchTool,
sendEmailTool,
deleteDatabaseTool,
],
middleware: [
humanInTheLoopMiddleware({
interruptOn: {
send_email: {
allowAccept: true,
allowEdit: true,
allowRespond: true,
},
delete_database: {
allowAccept: true,
allowEdit: true,
allowRespond: true,
},
search: false,
},
}),
],
// A checkpointer is required so the paused run can be resumed later
checkpointer: new MemorySaver(),
});
За наявності цього механізму агент зупиняється перед виконанням send_email або delete_database та чекає на рішення людини. Щоб продовжити виконання, необхідно надати ID потоку та Command:
const config = { configurable: { thread_id: "some_id" } };
// First call pauses and waits for approval
await agent.invoke(
{ messages: [{ role: "user", content: "Send an email to the team" }] },
config
);
// Resume after a human approves the tool call
await agent.invoke(
new Command({ resume: { decisions: [{ type: "approve" }] } }),
config
);
Важливо: без
checkpointerтаthread_idу мідлвейрі немає на чому продовжити виконання, і механізм паузи та затвердження просто не буде функціонувати. Це, безумовно, найпоширеніша помилка конфігурації.
Тут конфігурація фактично зазначає:
search → automatically allowed
send_email → require human approval
delete_database → require human approval
Ця схема є особливо корисною для агентів, які працюють у продакшн-середовищах.
Якщо ви бажаєте детальніше ознайомитися з схемою участі людини, окремий практичний огляд пояснює, як граф зупиняється за допомогою interrupt(), чекає на рішення людини, а потім продовжує виконання через Command.
B. Власні правила безпеки
Бізнес-логіка LangChain, яка постачається вже готовою, не підходить для кожної ситуації, з якою стикається ваше застосунок.
Коли ваші вимоги виходять за межі того, що вже передбачено, LangChain дозволяє написати власну бізнес-логіку та впровадити власні правила безпеки.
Для цієї мети особливо корисні два хуки життєвого циклу:
beforeAgent
afterAgent
Ці хуки дають можливість вставити логіку правил безпеки у конкретні моменти під час роботи агента.
1. Механізми захисту до запуску агента
Хук beforeAgent активується безпосередньо на початку виклику агента. Ви можете використовувати його для створення механізму захисту до запуску агента, який перевіряє чи фільтрує надходжучий запит ще до того, як агент почне ним працювати.
Типові сценарії використання включають:
- Аутентифікацію
- Обмеження частоти запитів
- Фільтрацію вхідних даних
- Відхилення неналежних запитів
- Перевірки, пов’язані з сеансом
Ось ілюстрація:
import { createMiddleware, AIMessage } from "langchain";
const sensitiveDataFilterMiddleware = (sensitiveKeywords: string[]) => {
const keywords = sensitiveKeywords.map((kw) => kw.toLowerCase());
return createMiddleware({
name: "SensitiveDataFilterMiddleware",
beforeAgent: {
hook: (state) => {
// Check if messages exist
if (!state.messages || state.messages.length === 0) {
return;
}
// Get the first user message
const firstMessage = state.messages[0];
// Make sure the message is from the user
if (firstMessage._getType() !== "human") {
return;
}
const content = firstMessage.content.toString().toLowerCase();
// Check for sensitive keywords
for (const keyword of keywords) {
if (content.includes(keyword)) {
// Stop the agent before it starts processing
return {
messages: [
new AIMessage(
"I cannot process requests containing sensitive information. " +
"Please remove passwords, API keys, or secrets and try again."
),
],
jumpTo: "end",
};
}
}
// No sensitive content found
return;
},
canJumpTo: ["end"],
},
});
};
// Create the agent
import { createAgent } from "langchain";
const agent = createAgent({
model: "gpt-5.5",
tools: [searchTool, calculatorTool],
middleware: [
sensitiveDataFilterMiddleware([
"password",
"api_key",
"secret",
"private_key",
]),
],
});
// This request will be blocked
const result = await agent.invoke({
messages: [
{
role: "user",
content: "Show me how to store my production password securely.",
},
],
});
console.log(result);
Алгоритм виконання:
User Request
│
▼
┌──────────────────────┐
│ beforeAgent Hook │
│ │
│ Check user message │
│ for sensitive words │
└──────────┬───────────┘
│
▼
┌─────────────────┐
│ Sensitive │
│ keyword found? │
└───────┬─────────┘
│
┌────────┴────────┐
│ │
YES NO
│ │
▼ ▼
┌──────────────────┐ ┌──────────────┐
│ Return blocked │ │ Continue to │
│ AIMessage │ │ the agent │
└────────┬─────────┘ └───────┬──────┘
│ │
▼ ▼
jumpTo: "end" Agent executes
│ │
▼ ▼
Final Response Final Response
Коли цей хук активний, проблематичний запит зупиняється ще до того, як агент встигне запустити чи використати будь-які інструменти.
2. Механізми захисту після запуску агента
Хук afterAgent активується після того, як агент завершив свою роботу. Він дозволяє реалізувати механізм захисту після запуску агента, який перевіряє чи фільтрує кінцеву відповідь агента перед тим, як вона надійде до користувача.
Поширені застосування включають:
- Перевірки безпеки
- Перевірка якості результату
- Перевірки відповідності стандартам
- Фільтрація результату
- Оцінка, яку виконує інший модель
Як приклад, ви можете направити відповідь через другу модель, завданням якої є лише її оцінка:
import {
createMiddleware,
AIMessage,
initChatModel,
} from "langchain";
const toxicityGuardrailMiddleware = () => {
// Model used only for toxicity evaluation
const evaluatorModel = initChatModel("gpt-5.4-mini");
return createMiddleware({
name: "ToxicityGuardrailMiddleware",
afterAgent: {
hook: async (state) => {
// Get the final AI response
if (!state.messages || state.messages.length === 0) {
return;
}
const lastMessage =
state.messages[state.messages.length - 1];
if (lastMessage._getType() !== "ai") {
return;
}
const response = lastMessage.content.toString();
// Ask the evaluator model to check for toxicity
const evaluationPrompt = `
You are a toxicity detection system.
Analyze the following AI response and determine
whether it contains toxic, abusive, hateful, or
harassing language.
Respond with ONLY:
SAFE
or
TOXIC
AI response:
${response}
`;
const evaluation = await evaluatorModel.invoke([ { role: "user", content: evaluationPrompt, }, ]);
const result = evaluation.content
.toString()
.trim()
.toUpperCase();
// Replace the response if it is toxic
if (result === "TOXIC") {
return {
messages: [
new AIMessage(
"I'm unable to provide that response because it contains inappropriate language."
),
],
jumpTo: "end",
};
}
return;
},
canJumpTo: ["end"],
},
});
};
// Create the agent
import { createAgent } from "langchain";
const agent = createAgent({
model: "gpt-5.5",
tools: [
searchTool,
calculatorTool,
],
middleware: [
toxicityGuardrailMiddleware(),
],
});
// Invoke the agent
const result = await agent.invoke({
messages: [
{
role: "user",
content: "Give me a response to this angry customer.",
},
],
});
console.log(result);
User
↓
Main Agent (GPT-5.5)
↓
Generates response
↓
afterAgent hook
↓
Evaluator Model (GPT-5.4-mini)
↓
┌───────────────┐
│ Is it toxic? │
└───────┬───────┘
│
┌───┴───┐
↓ ↓
SAFE TOXIC
↓ ↓
Return Replace
response response
Головний висновок полягає у тому, що ніколи не варто автоматично довіряти результатам роботи агента. Натомість ви надсилаєте отриману відповідь через модель-оцінювач, завданням якої є перевірка її відповідності вашим критеріям безпеки перед тим, як повернути її користувачеві.
Поєднання кількох захисних механізмів
На практиці одного захисного механізму рідко вистачає для реальних застосувань.
Різні етапи виконання агента вимагають різних видів захисту. Розгляньмо таку послідовність:
1. Input filtering
2. PII protection
3. Tool approval
4. Output safety check
LangChain дозволяє одночасно приєднати кілька елементів проміжного програмування до одного агента.
Ось приклад:
const agent = createAgent({
model: "gpt-5.5",
tools: [
searchTool,
sendEmailTool,
],
middleware: [
// 1. Before-agent guardrail for input filtering
sensitiveDataFilterMiddleware([
"password",
"api_key",
"secret",
"private_key",
]),
// 2. Built-in PII protection middleware
piiRedactionMiddleware({
piiType: "email",
strategy: "redact",
applyToInput: true,
applyToOutput: true,
}),
// 3. Built-in Human approval middleware for sensitive tools
humanInTheLoopMiddleware({
interruptOn: {
send_email: {
allowAccept: true,
allowEdit: true,
allowRespond: true,
},
},
}),
// 4. After-agent guardrail
toxicityGuardrailMiddleware(),
],
});
Кожен шар проміжного програмування відповідає за захист певної частини шляху виконання агента.
У сукупності загальний потік виглядає так:
User Request
│
▼
┌──────────────────────┐
│ Before-agent │
│ guardrail │
│ │
│ Input filtering │
└──────────┬───────────┘
│
▼
┌──────────────────────┐
│ PII protection │
│ │
│ Redact sensitive │
│ information │
└──────────┬───────────┘
│
▼
AI Agent
│
▼
Tool call?
/ \
No Yes
│ │
│ ▼
│ ┌───────────────┐
│ │ Human approval│
│ └───────┬───────┘
│ │
│ ┌────┴────┐
│ │ │
│ Approved Rejected
│ │ │
│ ▼ ▼
│ Execute Stop
│ │
└───────┤
▼
Agent response
│
▼
┌──────────────────────┐
│ After-agent │
│ guardrail │
│ │
│ Toxicity check │
│ using evaluator model│
└──────────┬───────────┘
│
┌────┴────┐
│ │
SAFE TOXIC
│ │
▼ ▼
Return Replace
response response
Це створює стратегію захисту з кількома рівнями для агента.
Основна ідея полягає у тому, що кожен елемент захисту відповідає за різний контрольний пункт:
- Елемент захисту перед агентом перевіряє надісланий запит.
- Проміжне програмування для обробки конфіденційних даних захищає чутливу інформацію.
- Перевірка людиною блокує ризиковані виклики інструментів доки їх не схвалять.
- Елемент захисту після агента перевіряє кінцеву відповідь перед тим, як вона надійде користувачеві.
Замість того, щоб покладатися на один лише механізм безпеки, використовуються кілька рівнів захисту, щоб агент був більш повно захищений на кожному етапі.
„Огородження“ — це не лише питання безпеки
Коли люди чують термін „огородження“, вони зазвичай уявляють собі блокування шкідливого чи образливого контенту.
Однак у продуктивних системах „огородження“ також слугують для забезпечення дотримання бізнес-логіки та правил, специфічних для додатку.
Наприклад:
Customer support agent
Can:
✓ Search orders
✓ Check delivery status
Cannot:
✗ Refund more than ₹10,000
✗ Delete customer account
✗ Change payment details
Ці правила не мають нічого спільного з виявленням шкідливого контенту.
Вони представляють собою правила додатку, які визначають межі того, що дозволено агенту робити.
Оскільки ШІ приймає рішення динамічно під час виконання, потрібна надійна точка застосування правил, які мають діяти незалежно від того, що сам модель вирішить.
Примітка: ШИМ вирішує, що хоче робити; правила обмежень визначають, що саме дозволяє програмі робити.
Заключні міркування
Створення AI-агента передбачає більше, ніж просто підключення ШИМ до кількох інструментів.
Як тільки агент починає обробляти справжні запити користувачів та взаємодіяти з реальними системами, необхідно встановити чіткі межі для його поведінки. Саме цю роль виконують правила обмежень.
LangChain надає кілька елементів для цієї мети:
- Детерміністичні правила обмежень для ситуацій, де результат має бути передбачуваним
- Правила обмежень, засновані на моделях, для перевірок, які залежать від значення та контексту
- Проміжне програмне забезпечення для обробки конфіденційної інформації
- Проміжне програмне забезпечення з участю людини для дій із реальними наслідками
- Проміжне програмне забезпечення перед агентом для перевірок на рівні вхідних даних та сеансу
- Проміжне програмне забезпечення після агента для перевірки кінцевого результату
Найважливіший урок з усього цього полягає у тому: не покладайтеся виключно на LLM для дотримання правил вашого додатку.
Нехай модель відповідає за міркування та прийняття рішень, але критичні межі, які справді мають значення, слід залишити кодованими в коді та проміжному програмуванні, де їх можна безпосередньо перевірити.
Саме це робить штучний інтелект достатньо надійним для справжнього продакшн-середовища.
Посилання
- Офіційний посібник LangChain, який описує концепції guardrail, доступний за адресою https://docs.langchain.com/oss/javascript/langchain/guardrails
- Офіційний довідник LangChain, що пояснює принципи роботи проміжного програмування, доступний за адресою https://docs.langchain.com/oss/javascript/langchain/middleware/overview
Пов’язана література
- Створення локального клону Angry Birds за допомогою Qwen3.8-27B та Pi — Дізнайтеся, як налаштувати повністю локальний робочий процес для програмування з використанням ШІ за допомогою LM Studio та агента Pi для створення ігрового рівня Angry Birds з використанням Qwen3.8-27B.
- Структурні механізми контролю для агентів ШІ: всередині конвеєра ResolveFlow — Пояснюється, як агент на основі LangGraph забезпечує розділення процесів міркувань та виконання за допомогою перевірок на рівні коду замість інструкцій у форматі запитів, включаючи помилку отримання даних, яка з’явилась під час роботи.