AI Architect Trainer Відкрити інтерактивний трек

Головна › Архітектура корпоративної аналітики

Агентні міркування, консенсус і розгортання

Агент, що міркує над типізованою онтологією, а не над текстом; п'ять рівнів оркестрації інструментів; Raft, Paxos і CRDT для спільного стану; доставка в ізольовані та відрізані контури.

Востаннє переглянуто: 2026-09-27 · In English

У цьому напрямі

Четвертий шар платформи — це там, де дані нарешті ухвалюють рішення. Але агент, який отримує текстові фрагменти, вигадує ідентифікатори; агент, який отримує типізовані об'єкти онтології, — ні. Цей модуль розбирає три речі, що визначають, чи виживе агентна система в продакшні: нейросимволічний поділ обов'язків між ймовірнісною моделлю та детермінованим рушієм онтології, інженерія поверхні інструментів та її оркестрація з бюджетом, і механіка спільного стану — коли потрібен Raft із кворумом, коли достатньо CRDT без координації, і що саме перетинає повітряний зазор під час доставки у відрізане середовище.

Агент над онтологією, а не над текстом

E8.1

Типовий RAG-агент отримує фрагменти тексту і здогадується, про які сутності йдеться. Агент над онтологією отримує типізовані об'єкти зі стабільною ідентичністю, типізованими зв'язками та закритим переліком дій. Різниця не косметична — вона змінює три властивості системи одразу.

Нейросимволічний поділ обов'язків. Ймовірнісна половина (LLM) перетворює намір на кандидатний план. Детермінована половина (онтологія + рушій правил) тримає типи, кардинальності, інваріанти та дозволи. Правило, яке не має винятків: інваріанти ніколи не живуть у ймовірнісній половині. Модель, що на 97% дотримується правила «не переспрямовувати вантаж під митним арештом», порушує його раз на тридцять три виклики. І це не проблема якості моделі: 97% — уже добрий показник. Просто інваріант не є статистичною величиною, і 99,9% його теж не задовольняють.

Практична форма поверхні інструментів. search_objects(object_type, filter) повертає масив посилань на об'єкти, а не markdown. get_links(id, link_type) обходить граф. propose_action(action_type, object_id, params, idempotency_key) ініціює двофазний запис. Векторний пошук тут лише звужує кандидатів; вирішує предикат онтології.

Що змінюється в пошуку. Питання перестає бути «який чанк найсхожіший» і стає «які об'єкти задовольняють предикат». Векторний пошук лишається, але як груба відсічка кандидатів; далі працює обхід зв'язків: замість зшивати три абзаци з різних документів, агент робить два кроки get_links і отримує точну відповідь на багатокроковий запит. Побічний ефект приємний і вимірюваний: типізована відповідь на десять об'єктів — це кількасот токенів замість десяти чанків по 400.

Двофазний запис. Пропозиція → валідація → шлюз (політика або людина) → коміт із ключем ідемпотентності. Ключ обов'язковий: агент, який повторив крок після таймауту, не повинен переспрямувати вантаж двічі. Кожен коміт лишає в журналі актора, тип дії, id об'єкта та версію онтології — це і є аудиторський слід, який регулятор може перевірити, жодного разу не читаючи промптів.

# Ontology-grounded tool surface. The model never receives prose:
# every result is a typed object reference it can cite and act on.

TOOLS = [
    {
        'name': 'search_objects',
        'description': 'Find objects of ONE ontology type. Returns object refs, not text.',
        'input_schema': {
            'type': 'object',
            'properties': {
                'object_type': {'enum': ['Shipment', 'Wagon', 'Customer']},
                'filter': {'type': 'object'},
                'limit': {'type': 'integer', 'maximum': 50}
            },
            'required': ['object_type', 'filter']
        }
    },
    {
        'name': 'propose_action',
        'description': 'Propose a typed write. Symbolically validated before commit.',
        'input_schema': {
            'type': 'object',
            'properties': {
                'action_type': {'enum': ['reroute_shipment', 'flag_wagon']},
                'object_id': {'type': 'string'},
                'params': {'type': 'object'},
                'idempotency_key': {'type': 'string'}
            },
            'required': ['action_type', 'object_id', 'idempotency_key']
        }
    }
]


def commit(proposal, actor, ontology, policy, log):
    # The deterministic half owns types, invariants and permissions.
    obj = ontology.get(proposal['object_id'])
    if obj is None or obj.type not in ACTION_DOMAIN[proposal['action_type']]:
        return Rejected('TYPE_MISMATCH', retryable=False)

    if not policy.permits(actor, proposal['action_type'], obj):
        return Rejected('FORBIDDEN', retryable=False)   # never decided in a prompt

    for inv in ontology.invariants(proposal['action_type']):
        if not inv(obj, proposal['params']):
            return Rejected('PRECONDITION_FAILED:' + inv.name, retryable=True)

    # ordered, replicated, and idempotent by key (see E8.3)
    return log.append(proposal, key=proposal['idempotency_key'])

На практиці

Оператор вантажних залізничних перевезень підключив агента до сховища на 4,2 млн об'єктів (вантажі, вагони, митні статуси). Базовий текстовий RAG правильно прив'язував сутність у 61% запитів «які вантажі під ризиком»; після переходу на типізовані інструменти — 94%. Вигадані номери вагонів упали з 12% відповідей до нуля, бо жоден ідентифікатор більше не генерувався моделлю.

Антипатерн

Серіалізувати весь підграф у промпт як JSON-текст і покластися на модель у виконанні з'єднань. Це роздуває контекст, повертає вигадані ідентифікатори і — найгірше — переносить авторизацію в промпт, де її неможливо застосувати примусово.

Оркестрація інструментів: п'ять рівнів і бюджет

E8.2

Здатність агента користуватися інструментами вимірюється не бінарно. Корисно розкласти її на п'ять рівнів — і побачити, що продакшн-збої майже ніколи не на першому. Ці п'ять рівнів — власна навчальна шкала уроку, а не шкала ETOM: ETOM, бенчмарк зі списку джерел уроку, має п'ять власних рівнів — прямий пошук інструмента, пошук з урахуванням контексту серед інструментів із функціональним перекриттям, послідовні ланцюжки в межах одного сервера інструментів, композиційні ланцюжки між серверами та відхилення запитів, які наявні інструменти не здатні виконати.

Головний важіль — не промпт, а поверхня інструментів. Опис інструмента — це його контракт; він важливіший за розмір моделі. Тримайте до ~20 інструментів на одного агента: далі точність вибору просідає, і лікується це маршрутизацією до вужчих під-агентів, а не більшою моделлю. Помилки мають бути дієвими: code, message, retryable. Помилка «щось пішло не так» гарантує, що рівня 5 не буде ніколи.

Опис — це контракт, а не документація. Іменуйте інструменти за типом об'єкта, яким вони оперують (shipment.search, wagon.flag), а не за дією взагалі. Два інструменти з описами, що перетинаються, гарантують плутанину, і жоден промпт її не розводить. Коли поверхня росте, розділяйте не всередині агента, а між агентами: маршрутизатор передає під-агентові явно сформульоване завдання й потрібні посилання на об'єкти, а не спільний блокнот міркувань — інакше контекст росте, а точність падає.

Бюджет — це архітектура, а не налаштування. Цикл ReAct (думка → дія → спостереження) без обмежень на кроки, час і вартість — це аварія зі спінером. Досягнення межі не має завершуватися вигаданою відповіддю: воно має ескалювати з накопиченим трейсом.

Маршрутизація за корисністю. Очікувана цінність виклику = P(корисно) × цінність − вартість (затримка + токени + гроші). Дешеві детерміновані інструменти (фільтр онтології, точковий lookup) йдуть першими; дорогі генеративні — останніми. Паралелити можна лише читання без побічних ефектів; записи серіалізуються через реплікований журнал (E8.3).

Вимірювання. Точність кінцевої відповіді приховує крихкість. Міряйте окремо: точність вибору інструмента, валідність аргументів, частку успішного відновлення після помилки. Агент із 92% кінцевої точності та 40% відновлення розсиплеться при першій деградації API. Причина проста: офлайн-набір складається зі здорових викликів, тож рівень 5 у ньому може й не трапитися — його треба перевіряти окремо, навмисно ламаючи інструменти.

# Bounded ReAct loop with utility-guided tool selection.
# An unbounded agent loop is an outage with a spinner on it.

BUDGET = {'steps': 8, 'seconds': 25.0, 'usd': 0.05}


def expected_utility(tool, trace):
    # value of the information, minus what the call actually costs us
    return tool.p_useful(trace) * tool.value - (
        tool.latency_s * LATENCY_PRICE + tool.usd
    )


def run(goal, tools, model, budget=BUDGET):
    trace = []
    spent = {'steps': 0, 'seconds': 0.0, 'usd': 0.0}

    while spent['steps'] < budget['steps']:
        # cheap deterministic tools outrank expensive generative ones;
        # the slice keeps the visible surface under the selection cliff
        allowed = sorted(tools, key=lambda t: -expected_utility(t, trace))[:9]

        step = model.plan(goal, trace, allowed)
        if step.kind == 'final':
            return step.answer, trace

        obs = call(step.tool, step.args)          # typed result, never raw prose
        if obs.error and obs.error.retryable:
            # actionable error -> level 5 recovery is possible
            trace.append(Observation(obs.error.code, obs.error.message))
        else:
            trace.append(obs)

        spent['steps'] += 1
        spent['usd'] += obs.usd
        spent['seconds'] += obs.latency_s
        if spent['usd'] > budget['usd'] or spent['seconds'] > budget['seconds']:
            return escalate(goal, trace), trace   # hand over the trace, do not spin

    return escalate(goal, trace), trace

На практиці

Агент реагування на інциденти мав 34 інструменти в одній пласкій поверхні. Після розділення на три маршрутизовані під-агенти по ≤9 інструментів точність вибору інструмента зросла з 71% до 96%, медіанна кількість кроків упала з 11 до 6, а вартість опрацювання одного інциденту — з $0.84 до $0.31. Модель не змінювали жодного разу.

Антипатерн

Виставити один узагальнений інструмент run_query(sql) замість типізованих дій. Модель пише синтаксично валідний, але семантично хибний SQL, авторизація на рівні окремої дії стає неможливою, а точність вибору інструмента неможливо виміряти — інструмент один.

Спільний стан: консенсус, CRDT і відрізані контури

E8.3

Дві різні задачі постійно плутають. Узгодження єдиного впорядкованого журналу (Raft, Paxos) потребує кворуму і жертвує доступністю під час розділення мережі, але лінеаризовність сам журнал ще не забезпечує: Raft додає клієнтські порядкові номери проти повторного виконання команд, коміт порожнього запису (no-op) кожним новим лідером і перевірку лідерства з більшістю перед читаннями. Збіжність без координації (CRDT) завжди доступна на запис і зливається детерміновано, але не вміє виражати інваріант «схвалення має бути рівно одне».

Raft у трьох рядках. Вибори лідера з термами, реплікація журналу, коміт за більшістю. Щоб пережити f відмов, потрібно 2f+1 вузлів: п'ять витримують дві. Лідер — це водночас стеля пропускної здатності й підлога затримки (один RTT до більшості). Кладіть у нього те, де важливий порядок: журнал дій агента, версію онтології, версію бандла політик.

Paxos чи Raft — та сама арифметика. Multi-Paxos і Raft дають однакові гарантії й однакову вимогу більшості; Raft виграє зрозумілістю реалізації та явним поняттям лідера, тому його простіше правильно експлуатувати. Що не варто класти в консенсус: високочастотну телеметрію, метрики та проміжні спостереження агента. Лідер миттєво стає вузьким місцем, а порядок цих подій нікому не потрібен — їм місце в потоковому конвеєрі, а не в реплікованому журналі.

CRDT там, де операції комутативні. OR-Set для тегів і міток (add-wins із tombstone), LWW-регістр для нотаток тріажу, лічильники. Критична деталь: LWW за настінним годинником при розбіжності 400 мс тихо втрачає редагування. Використовуйте гібридний логічний годинник — він зберігає причинність і не залежить від NTP. Жорсткіша синхронізація тут не рятує: вона звужує вікно втрат, не усуваючи саму можливість.

Відрізані та ізольовані контури. В анклав заходить підписаний бандл: ваги моделі, версія онтології, бандл політик, маніфест інструментів — усе з контент-хешем і перевіркою підпису при завантаженні. Назовні через односторонній канал виходить лише підписана дельта.

# fleet.yaml - one core Raft group plus disconnected enclaves with CRDT state

core:
  consensus: raft
  members: [core-1, core-2, core-3, core-4, core-5]   # 2f+1 -> tolerates f=2
  linearizable_state:   # needs Raft §8: client serials, leader no-op, read check
    - action_log             # order matters: who approved what, and first
    - ontology_version
    - policy_bundle_version

enclaves:
  - id: fwd-17
    connectivity: intermittent       # observed dark window: 6-72h
    consensus: raft                  # SEPARATE group - never spans the air gap
    members: [fwd-17a, fwd-17b, fwd-17c]
    authority_scope: local           # commits are 'local_commit', never global
    convergent_state:                # coordination-free, always writable
      annotations: or-set            # add-wins, tombstoned removes
      triage_notes: lww-register
      clock: hybrid-logical          # NOT wall clock: 400ms skew loses edits

bundle:                              # the only thing that crosses the diode, one way
  pins:
    ontology_version: 2026.31.0
    policy_bundle: sha256:9f3c1d...
    model: local-8b-q4:sha256:1a77e0...
  signature: minisign
  verify_on_load: true               # refuse to start on a hash mismatch

reconnect:
  annotations: merge                 # CRDT: deterministic, no human in the loop
  actions: replay_as_proposals       # re-validated against the CURRENT policy
  on_version_skew: reject            # never silently coerce a stale action

На практиці

Логістичне розгортання на 40 передових вузлах із вікнами автономності 6–72 год: Raft-група з 5 вузлів у ядрі та CRDT-шар анотацій на кожному вузлі. Після відновлення зв'язку 18 400 анотацій злилися за 40 секунд без жодного ручного розв'язання конфліктів, тоді як 260 дій запису переграли як пропозиції — і 11 із них відхилив рушій політик, переглянутий, поки вузли були відрізані.

Антипатерн

Розтягнути одну Raft-групу через повітряний зазор і покластися на LWW за настінним годинником між вузлами з розбіжністю 400 мс. Перше зупиняє записи всього флоту при кожному від'єднанні; друге тихо втрачає редагування, і виявляється це лише під час аудиту.

Джерела, з яких виведено напрям

  1. Agentic Reasoning for Large Language Mod
  2. A Survey on Large Language Model based A
  3. A Relational Neural Network Database Mod
  4. ReAct Synergizing Reasoning and Acting in Language
  5. ETOM A Five Level Benchmark for Evaluating Tool Orchestrat
  6. The Evolution of Tool Use in LLM Agents From Single Tool C
  7. Utility Guided Agent Orchestration for Efficient LLM Tool
  8. Reinforcement Learning for LLM based Multi Agent Systems t
  9. A Survey on Consensus Algorithms in Bloc
  10. A Deep Dive into Blockchain Consensus Pr
  11. A Security Reference Architecture for Bl
  12. A comparative study on consensus mechani

Першоджерела поза корпусом

  1. In Search of an Understandable Consensus Algorithm (Extended Version)
  2. Logical Physical Clocks and Consistent Snapshots in Globally Distributed Databases

Пройти інтерактивно

У кожного напряму є питання, картки з інтервальним повторенням і облік прогресу. Для них потрібен акаунт — безкоштовний і на одну хвилину.

Відкрити інтерактивний трек Створити безкоштовний акаунт

Далі в цьому треку