AI-агент у production не є prompt із більшим контекстним вікном. Це сервіс, у якому модель обирає дії, спостерігає їхні результати й продовжує роботу, доки не досягне обмеженого результату або не попросить людину про допомогу. Модель є лише одним компонентом. Control plane визначає, що модель може бачити, які tools може викликати, що потребує approval, як state переживає перезапуск і які докази потрібні перед випуском нової версії.
Нижче наведено reference architecture для одного агента з невеликим набором інструментів. Ті самі межі підходять для workflow із фіксованими кроками та майбутньої multi-agent системи. Починайте з найменшого циклу, який вирішує задачу. У посібнику Anthropic про ефективних агентів так само розрізняються передбачувані workflow і системи, у яких модель динамічно керує використанням tools. Автономність є продуктовим рішенням, а не архітектурним default.
Reference architecture
Розділіть шлях запиту, цикл прийняття рішень і ефекти, що змінюють зовнішній світ. Gateway автентифікує викликач, створює request ID і trace ID, застосовує квоти та вилучає або класифікує чутливі поля. Orchestrator володіє циклом. Model adapter перетворює нейтральний state на запит до провайдера, а відповідь провайдера, на невеликий тип рішення. Tool gateway перевіряє аргументи, авторизує principal, застосовує policy та викликає ізольований connector. State і event storage мають бути поза процесом, щоб worker міг продовжити роботу після збою.
[Користувач або API-клієнт]
|
[Gateway: identity, limits, request ID]
|
[Orchestrator: policy -> model -> tool loop]
/ | \
[State store] [Approval service] [Tool gateway]
| | |
[Memory/RAG] [Рішення людини] [API, файли, черги]
|
[Events і traces]
Не давайте моделі raw credentials, необмежений доступ до network або прямий запис у базу. Tool gateway має надавати вузькі операції на кшталт "search_orders", "draft_refund" чи "send_message", а не універсальний HTTP-клієнт або SQL-консоль. Для кожної операції потрібні owner, input schema, permission, risk class, timeout і задокументований формат результату.
Використовуйте явний request envelope:
| Поле | Призначення |
|---|---|
| tenantId і actorId | Прив’язати кожне читання та ефект до автентифікованого principal. |
| requestId і traceId | Пов’язати retries, approvals, tool calls і logs. |
| goal | Зберігати ціль користувача окремо від повідомлень моделі. |
| policyVersion і modelVersion | Зробити run достатньо відтворюваним для розслідування. |
| deadline і stepBudget | Обмежити час і кількість ходів model-tool. |
Профіль Generative AI для NIST AI RMF допомагає організувати роботу з ризиками протягом життєвого циклу. Перекладіть його запитання в конкретні controls envelope і tool gateway. Framework сам по собі не є доказом безпеки конкретного deployment.
Tool loop
Цикл повинен мати одного власника і видимий перехід state на кожному ході:
- Завантажити run, policy, summary розмови та дозволений tool catalog.
- Побудувати обмежений model input. Позначити user text, retrieved text, tool output і system instructions як різні trust classes.
- Попросити модель повернути final response або typed tool call. Відхилити неправильні name та arguments до виконання.
- Вирішити authorization і risk policy поза моделлю. Твердження моделі про безпечність дії не є рішенням authorization.
- Якщо дія перетинає approval gate, зберегти pending action і зупинитися. Після явного рішення продовжити зі збереженої дії.
- Виконати connector із deadline та idempotency key. Записати redacted result і додати його до state.
- Перевірити budgets кроків, токенів, вартості та часу. Продовжувати лише якщо policy дозволяє ще один хід.
- Повернути відповідь, де вказано, що сталося, що не сталося і яка approval або невизначеність залишилася.
Не обгортайте виклик моделі в необмежений цикл "while". Step budget не дозволяє prompt або tool перетворити один запит на дорогу послідовність. Deadline захищає caller і worker pool. Detector повторних викликів ловить модель, яка постійно просить одну невдалу дію. Circuit breaker може тимчасово вимкнути degraded connector, не вимикаючи read-only tools.
Специфікація Model Context Protocol стандартизує resources, prompts і tools, але не замінює consent, authorization та isolation controls host application. Ставтеся до MCP server як до зовнішньої залежності. Зафіксуйте його identity, перевірте заявлені tools, обмежте передані дані й застосуйте ту саму gateway policy, що й до внутрішнього connector.
State, memory і context
State є довговічним записом одного run. Зберігайте user goal, messages або references, tool calls і results, approvals, policy decisions, версії model і tools, timestamps, status та error classification. Спочатку додавайте events, а потім виводьте поточний run view. Recovery й audit стають простішими, ніж при зміні одного непрозорого JSON blob. Шифруйте sensitive fields, ізолюйте tenants, задайте retention і зробіть deletion повним для snapshots, indexes, traces та caches.
Conversation history не дорівнює memory. Для поточного run залишайте короткоживучий working context. User або business memory зберігайте лише за наявності purpose, retention rule, source і способу виправлення користувачем чи адміністратором. Факти мають містити provenance та confidence або freshness field. Не записуйте здогадки моделі в durable memory тільки тому, що вони звучать правдоподібно.
Retrieval є tool із trust boundary. Перед ranking застосовуйте tenant і authorization filters. Переносьте source identifiers і timestamps у context. Вказуйте моделі, що retrieved text є data, а не instructions. Обмежуйте chunks, вилучайте непотрібні secrets і за замовчуванням логовуйте query та document identifiers без private content. У PostgreSQL порівнюйте lexical і vector candidates та робіть rerank лише після authorization. Внутрішній посібник із hybrid RAG та pgvector детальніше описує цю межу.
Context compaction має бути достатньо deterministic для debugging. Підсумовуйте старі turns за фіксованою схемою, зберігаючи decisions, unresolved questions, tool effects, citations і user constraints. Original event log залишайте поза prompt. Якщо summary змінює сенс pending action, зупиніться й попросіть review замість тихого продовження.
Tools і threat boundaries
Tool schema, security contract, а не лише model metadata. Gateway має перевіряти types, lengths, enum values, resource ownership і зв’язки між полями. Після authorization перетворюйте names на internal identifiers. Розділяйте read і write tools. Видавайте connectors короткоживучі credentials, обмежені одним tenant і operation. Небезпечний code або file work виконуйте в ізольованому worker з filesystem і network allowlist.
Увесь external content вважайте потенційно ворожим. Email, web page, issue, document, tool description або MCP resource можуть містити indirect prompt injection. Відокремлюйте його в model input, за потреби прибирайте executable markup і залишайте рішення про permissions application. Output validation має бути незалежною від моделі. Перед передачею refund payment connector перевіряйте amount, currency, actor permission і policy maximum.
Threat model має охоплювати:
| Межа | Що потрібно не допустити |
|---|---|
| User → gateway | Account takeover, oversized requests і чужі tenant identifiers. |
| Model → tool gateway | Argument injection, плутанину privileges і excessive agency. |
| Retrieval або MCP → model | Indirect prompt injection і шкідливі instructions у data. |
| Connector → external service | Credential leakage, SSRF, replay і data exfiltration. |
| Worker → state store | Tampered events, stale policy і неповний audit history. |
OWASP GenAI LLM Top 10 описує prompt injection, excessive agency, insecure output handling і unbounded consumption як ризики, що потребують application-level mitigations. Внутрішній посібник із prompt injection та MCP security містить threat-focused checklist. System prompt корисний як guidance, але не є sandbox, authorization layer чи secret store.
Approval gates і human control
Розміщуйте approvals навколо effects, а не harmless reasoning. Читання власного календаря користувача може бути автоматичним. Надсилання external message, зміна record, видача коштів, видалення data або deploy code зазвичай потребують policy decision з урахуванням actor, target, amount, reversibility і confidence. Gate має бути в application code, щоб prompt не міг його обійти.
Зберігайте approval request із proposed tool, normalized arguments, affected resources, reason, policy version, expiration і hash відповідного state. Reviewer повинен бачити ті самі дані. Прив’яжіть його рішення до action hash та actor. Після approval повторно перевірте authorization, freshness і budget перед execution. Після rejection або expiry запишіть рішення та повідомте моделі, що дія не відбулася. Стара approval не повинна авторизувати змінений payload.
Режими human-in-the-loop:
| Режим | Застосування |
|---|---|
| Observe | Записувати або вибірково перевіряти low-risk actions, залишаючи agent automatic. |
| Confirm | Запитувати approval безпосередньо перед irreversible effect. |
| Review | Дати людині перевірити повний draft і selected evidence. |
| Take over | Передати run оператору з поточними state та lock. |
Проєктуйте pause як звичайний state, а не exception. Queue доставляє pending approvals, notification може expire, а worker може resume run на іншому host. Користувач має бачити, чи agent думає, чекає data, approval, retry або завершення.
Retries, idempotency і recovery
Класифікуйте errors до retry. Validation error потребує виправленого call або запитання користувачу. Authentication і authorization errors мають зупиняти. Rate limits повинні поважати retry signal провайдера. Timeout і connection reset неоднозначні для writes, бо remote service могла вже застосувати effect. Повторюйте лише операцію, для якої semantics або idempotency key робить повтор безпечним. Розділ 9.2.2 RFC 9110 пояснює, чому non-idempotent methods не можна автоматично повторювати без способу встановити безпечність effect.
Генеруйте stable key із run і logical action, а не з attempt number. Connector зберігає key і final result протягом retry window. Якщо той самий key приходить з іншими arguments, відхиляйте його. Використовуйте exponential backoff із jitter і невеликий maximum attempt count. Retry не є новим model decision. Зберігайте original call, attempt number, response class і connector request identifier.
Recovery, питання state machine. Використовуйте statuses "running", "waiting_for_approval", "retrying", "failed", "completed" і "cancelled". Lease не дає двом workers виконувати один run одночасно. Після втрати lease зупиніться перед наступним effect. Reconciler може зіставити pending actions із connector records після worker crash. Cancellation має поширюватися на model requests, tool calls, queues та approval requests, якщо provider це підтримує.
Observability
Створюйте один trace на user request і spans для model calls, retrieval, policy checks, approvals та tools. Записуйте duration, status, retry count, input і output token counts за наявності, model і prompt versions, tool name, risk class та cost estimate. Redact secrets і sensitive content до export. Stable run ID пов’язує approval або support ticket із trace, але не кладіть personal data у baggage. OpenTelemetry context propagation описує зв’язування trace context між services і попереджає про untrusted headers та sensitive baggage.
Корисні measures: task completion за визначеною rubric, успішність tool calls, validation failures, approval rate, retry і timeout rate, p50 і p95 latency, token і tool cost на completed task, cancellation rate та policy blocks. Розділяйте їх за model version, tool, tenant class і release. Низька кількість errors може приховувати silent wrong answers, тому пов’язуйте traces із sampled transcripts і evaluator results. Не логовуйте chain-of-thought. Зберігайте короткі decision metadata та user-visible reasoning або citations, дозволені policy.
Внутрішній посібник про observability AI-агентів показує, як зробити signals корисними й не перетворити logs на другу базу secrets.
Evals до і після release
Agent eval є scenario з визначеними starting state, allowed tools, expected invariants і scoring rule. Final answer недостатньо. Перевіряйте, що agent використав authorized tool, зберіг tenant scope, попросив approval коли потрібно, не повторив write, процитував правильний source і зупинився на budget. Додавайте adversarial scenarios: malicious retrieved text, unavailable connector, timeout після write, stale approval, ambiguous request і malformed tool data.
Використовуйте layered suite:
- Deterministic unit tests для schemas, authorization, redaction, idempotency, state transitions і budget enforcement.
- Replay tests із recorded tool responses і fixed model decisions для перевірки recovery.
- Scenario tests із model та rubric для task outcome, safety і communication.
- Red-team tests для direct і indirect injection, data leakage, excessive agency і denial of service.
- Production sampling із privacy controls, human review і шляхом перетворення failures на regression cases.
Версіонуйте scenario, tools, policy, prompts, model і evaluator. Зберігайте failures разом із trace та найменшим reproducing input. Порівнюйте candidate release із baseline і не допускайте regression hard safety invariants, навіть якщо average quality зросла. Посібник Anthropic про evals агентів пояснює, чому multi-turn tool use потребує trajectory-level evaluation. Внутрішній посібник з evals AI-агентів дає практичну test matrix.
Cost і latency
Кожен model turn, retrieved token, tool call, approval pause і retry додає time або money. Задавайте budgets для класів задач, а не один global number. Використовуйте меншу model для routing, extraction і policy prechecks, якщо виміряна точність достатня. Сильнішу model залишайте для ambiguous planning або final synthesis. Кешуйте stable tool catalogs і retrieval embeddings. Стискайте context до того, як він виросте, але вимірюйте, чи summary не спричиняє додаткових turns або втрати facts.
Паралелізуйте незалежні read-only calls, потім об’єднуйте результати з явним provenance. Writes залишайте послідовними, якщо connector не надає transaction або спроєктовану compensation. Показуйте progress без розкриття secrets. Для довгої роботи збережіть job і дозвольте worker продовжити після завершення HTTP request. Швидша model не дешевша, якщо помилки викликають human review, compensating writes або повторні runs. Вимірюйте total cost на correct і policy-compliant outcome.
Мінімальний runnable loop
Наведений protocol-independent TypeScript example використовує fake model і локальні tools, тому запускається без provider SDK або network. Він показує typed decision loop, write approval, bounded retries, stable idempotency key і захист від duplicate effect. Реальний adapter може замінити model, не змінюючи policy та tool boundary.
type ToolCall = { id: string; name: string; input: unknown };
type Message =
| { role: "user"; content: string }
| { role: "assistant"; content: string; toolCall?: ToolCall }
| { role: "tool"; callId: string; content: string };
type Decision =
| { kind: "answer"; text: string }
| { kind: "call"; call: ToolCall };
type Tool = {
sideEffect: "read" | "write";
run(input: unknown, idempotencyKey: string): Promise<string>;
};
const issued = new Set<string>();
const tools: Record<string, Tool> = {
getBalance: {
sideEffect: "read",
async run(): Promise<string> {
return JSON.stringify({ account: "demo", cents: 4200 });
},
},
sendInvoice: {
sideEffect: "write",
async run(input: unknown, idempotencyKey: string): Promise<string> {
if (issued.has(idempotencyKey)) return "already-sent";
if (typeof input !== "object" || input === null) throw new Error("invalid input");
issued.add(idempotencyKey);
return "invoice-sent";
},
},
};
const model = {
async decide(messages: readonly Message[]): Promise<Decision> {
const toolCount = messages.filter((message) => message.role === "tool").length;
if (toolCount === 0) {
return { kind: "call", call: { id: "balance-1", name: "getBalance", input: {} } };
}
if (toolCount === 1) {
return { kind: "call", call: { id: "invoice-1", name: "sendInvoice", input: { cents: 1200 } } };
}
return { kind: "answer", text: "The balance was checked and the invoice was sent." };
},
};
const wait = (milliseconds: number) => new Promise((resolve) => setTimeout(resolve, milliseconds));
async function execute(call: ToolCall, tool: Tool, key: string): Promise<string> {
for (let attempt = 0; attempt < 3; attempt += 1) {
try {
return await tool.run(call.input, key);
} catch (error) {
if (attempt === 2) throw error;
await wait(10 * 2 ** attempt);
}
}
throw new Error("unreachable");
}
async function run(): Promise<string> {
const state: { messages: Message[]; completed: Set<string> } = {
messages: [{ role: "user", content: "Check the balance and send the invoice." }],
completed: new Set<string>(),
};
const sessionId = "session-demo";
for (let step = 0; step < 6; step += 1) {
const decision = await model.decide(state.messages);
if (decision.kind === "answer") return decision.text;
const tool = tools[decision.call.name];
if (!tool) throw new Error("unknown tool");
const key = sessionId + ":" + decision.call.id;
if (tool.sideEffect === "write" && !process.argv.includes("--approve")) {
return "Paused for approval: " + decision.call.name;
}
if (state.completed.has(key)) continue;
const result = await execute(decision.call, tool, key);
state.completed.add(key);
state.messages.push(
{ role: "assistant", content: "", toolCall: decision.call },
{ role: "tool", callId: decision.call.id, content: result },
);
}
throw new Error("step budget exceeded");
}
run().then(console.log).catch((error: unknown) => {
console.error(error);
process.exitCode = 1;
});
Скомпілюйте код за допомогою TypeScript toolchain проєкту й запустіть згенерований JavaScript без flag, щоб побачити approval pause, а потім із --approve, щоб дозволити write. Приклад навмисно зберігає state в memory. У production state має бути durable, tenant-scoped, за потреби encrypted і відновлюватися lease-aware worker.
Порядок побудови
Визначте task outcome і заборонені effects до вибору model. Реалізуйте один read tool і один reversible write tool за schemas та authorization. Додайте durable events, budgets, approval states і idempotency до появи нових tools. Одразу instrument перший end-to-end trace. Створюйте eval scenarios із реальних failure modes і запускайте їх після кожної зміни prompt, policy, tool або model. Додавайте MCP чи multi-agent delegation лише коли виміряна потреба виправдовує додаткову trust boundary.
Пов’язане читання
- Prompt injection і MCP security
- Evals AI-агентів
- Observability AI-агентів
- Hybrid RAG із pgvector
- Anthropic: Building effective agents
- Anthropic: Demystifying evals for AI agents
- Model Context Protocol specification
- NIST AI RMF Generative AI Profile
- OWASP GenAI LLM Top 10
- OpenTelemetry context propagation
- RFC 9110 HTTP Semantics