Урок 5: команда агентів за вісім цін.

Системний промт 1 агента займає 3 екрани, тому команда агентів здається хорошим планом. Ми зберемо оркестратора і спеціалістів і знайдемо 3 проблеми команди. Потім ми порівняємо single і multi на 1 наборі тікетів: точність та сама, вартість у 8 разів більша.

Урок 5: Multi-agent і чесна ціна команди

Image: METAHEURISTIC

Alexander Myasoedov

+Alexander Myasoedov Alexander будує AI-агентів і пише про те, що лишається за кадром красивих демок: цикл агента, межі доступу і контроль людини, який не вбиває автономність.

Урок 4 закінчився попередженням. Агент тепер шукає, пам’ятає і планує. Але кожна нова здатність зробила промт довшим. Подивіться на промт сьогодні. Він містить:

  • правила білінгу;
  • політику рефандів;
  • поведінку export;
  • API-ліміти;
  • тон відповіді;
  • правила ескалації.

Це 3 екрани тексту. Тепер приходить тікет T-3001: «Upgraded my plan yesterday, there is a duplicate charge on my card, and export gives an empty file». Тікет вимагає 2 експертизи одночасно:

  • білінгову: подвійний чардж після апгрейда означає, що стара підписка ще не закрила свій інвойс;
  • технічну: порожній export означає проблему з фільтром статусів або з чергою.

1 агент уміє обидві речі. Але кожен новий домен у промті робить агента трохи гіршим у всіх інших доменах. Застереження про рефанди з’являються у відповіді про export. Технічні деталі потрапляють у відповідь про гроші.

У цей момент продакт-менеджер пропонує: «Створимо команду агентів. Один агент для білінгу, один для техніки, і головний агент над ними. Як у людей». Архітектори часто реагують на цю фразу негативно. Але ідея не погана. Погане буває тільки виконання.

У цьому уроці ми зберемо оркестратора і спеціалістів. Потім ми зламаємо цю команду 4 способами. Найважливіше: ми виміряємо вартість команди і її реальну користь. Числа покажуть результат.

Аксіоматична ідея 1
Команда агентів не додає інтелекту. Команда додає комунікацію.
Аксіоматична ідея 2
Кожна передача між агентами коштує гроші і втрачає деталі.
Аксіоматична ідея 3
Згода 2 однакових агентів - це луна, а не перевірка.
Системна ідея
Спеціаліст - це той самий цикл у вужчому світі. Делегування - це ще 1 інструмент оркестратора. Результат делегування - звіт, а не діалог.

Терміни

Тема multi-agent має нечіткі поняття. Тому спочатку визначимо терміни.

  • workflow - фіксована послідовність кроків. Workflow надійний і дешевий, але не адаптується.
  • Агент - процес, який адаптується. Агент сам вирішує наступний крок на основі спостережень.
  • Multi-agent система - не «рій розумів», а конкретна конструкція: 1 оркестратор, 2 або більше спеціалістів і чіткий контракт між ними.
БУЛО: один агент на всі домениsupport agentекран 1: білінг, рефанди, інвойсиекран 2: export, api, кеші, логиекран 3: тон, винятки, ескалаціїкожен новий домен у промті -мінус якість в усіх іншихСТАЛО: оркестратор + спеціалістиоркестратортріаж + одне рішенняdelegatebillingпромт: пів екранакорпус: білінг KBінструменти: свої 3tech-світу не бачитьtechпромт: пів екранакорпус: KB Уроку 3інструменти: read-onlyбілінгу не бачитьта сама архітектура Уроку 2: delegate - просто ще один інструмент планувальника
Великий генераліст проти команди. Права половина не має колективного інтелекту. Вона має ізоляцію контексту. Кожен спеціаліст бачить тільки свій корпус і свої інструменти. Вузький промт спеціаліста легко підтримувати правильним.
Workflow
workflow
Фіксована послідовність кроків. Надійний і дешевий, але не адаптується до неочікуваних подій.
Агент
agent
Процес, який адаптується: сам вибирає наступний крок за спостереженнями. Дорожчий і менш передбачуваний.
Оркестратор
orchestrator
Планувальник з Уроку 2 з новим інструментом delegate. Приймає 1 кінцеве рішення.
Спеціаліст
specialist
Той самий агентний цикл, але з вузьким промтом, своїм корпусом і своїми інструментами.

Головна реальна цінність multi-agent Ця цінність не в тому, що 2 агенти розумніші за 1. Оркестратор і спеціаліст використовують ту саму модель. Система не отримала більше інтелекту. Система отримала ізоляцію контексту. Правила білінгу більше не губляться серед технічних правил. Промт кожного агента займає пів екрана. Кожен агент має тільки свій корпус. Інші обіцянки multi-agent зводяться до ізоляції, до прав доступу або до театру. Ви побачите це в числах.

Контракт рівня мінімальний. Далі кожен пункт покаже свою користь:

Команда тріажу - вимоги v1
01
Тріаж
Оркестратор читає тікет і ділить тікет на підзадачі за доменами. Оркестратор делегує кожну підзадачу 1 спеціалісту.
02
Структурований звіт
Спеціаліст повертає facts + recommendation + confidence, а не сирий діалог.
03
1 рішення
Тікет має 1 кінцеве рішення. Це рішення приймає оркестратор.
04
Спільний бюджет
Код рахує делегації, LLM-виклики і токени на тікет, а не на агента.

Спеціаліст: той самий цикл, вузький світ

Перша добра новина уроку: нова архітектура не потрібна. Спеціаліст - це агент з Уроків 1-2 з вужчим світом. search_kb з Уроку 3 отримує корпус як параметр:

  • TECH_KB - відомі статті з Уроку 3: kb-101 про порожній export, kb-102 про чергу, kb-105 про ліміти;
  • BILLING_KB - kb-103 про інвойси з Уроку 3 і нові kb-201 і kb-202 про чарджі і рефанди.

Спеціаліст має 3 частини: промт, корпус, інструменти:

@dataclass
class Specialist:
    name: str
    prompt: str
    corpus: dict[str, str]
    tools: tuple[str, ...]

SPECIALISTS = {
    "billing": Specialist("billing", BILLING_PROMPT, BILLING_KB,
                          ("search_kb", "get_subscription", "get_invoices")),
    "tech": Specialist("tech", TECH_PROMPT, TECH_KB,
                       ("search_kb", "check_system", "read_logs")),
}

def can_use(agent: str, tool: str) -> bool:
    # межі доступу з Уроку 1 стають архітектурою: least privilege по агентах
    return agent in SPECIALISTS and tool in SPECIALISTS[agent].tools

Подивіться на can_use. В Уроці 1 межі доступу захищали дані від тікета. Тепер межі доступу визначають форму системи. Billing-агент фізично не має технічних інструментів. Tech-агент не бачить інвойсів. Це least privilege для агентів. Це друга реальна причина для multi-agent. Ми повернемося до неї пізніше:

search_kbget_subscriptionget_invoicescheck_systemread_logsbillingtechBILLING_KBтактакdenieddeniedTECH_KBdenieddeniedтактак"denied by policy: read_logs is outside the billing scope"відмову повертає can_use() у коді - як спостереження, не як винятокнавіть search_kb у кожного свій: корпус відфільтровано по домену
Матриця «агент × інструмент» є в коді, а не в промті. Якщо агент пробує вийти за свій рядок, агент отримує звичайне спостереження «denied by policy». Так само в Уроках 1 і 2 код відмовляв планам, які виходили за межі.

Промт спеціаліста тепер займає пів екрана, і весь текст стосується задачі. Ось промт білінгового агента. Порівняйте його з 3 екранами генераліста:

BILLING_PROMPT = (
    "You are the billing specialist. Scope: charges, refunds, invoices, proration, "
    "subscription state. Work only from your own knowledge base and billing tools, "
    "product behavior is not your scope. Quote amounts and invoice ids exactly as "
    "the tools return them, never promise refund timelines shorter than the "
    "policy, escalate charges older than 90 days to a human. " + REPORT_RULES
)

Звіт, а не діалог

Тепер питання, від якого залежить уся конструкція: що спеціаліст повертає оркестратору? Перша ідея - повернути діалог. Оркестратор прочитає міркування спеціаліста і перекаже їх далі. Не робіть так. У розділі Break ви побачите причину на реальному тікеті. Правильна відповідь - структурований звіт. Факти зі звіту потрапляють на спільну дошку:

@dataclass
class Fact:
    key: str
    value: str
    source: str  # хто знайшов і де: "billing:kb-201"

@dataclass
class Report:
    specialist: str
    facts: list[Fact]
    recommendation: str
    confidence: float

@dataclass
class Board:
    facts: dict[str, Fact] = field(default_factory=dict)

    def merge(self, report: Report) -> int:
        new = [f for f in report.facts if f.key not in self.facts]
        self.facts.update({f.key: f for f in new})
        return len(new)

    def pretty(self) -> str:
        return "; ".join(f"{f.key}={f.value}" for f in self.facts.values())

Це той самий словник фактів з Уроку 2. Але тепер команда має спільний словник: blackboard . Кожен факт має provenance : хто знайшов факт і в якому документі. Також merge повертає кількість нових фактів. Це важлива деталь. Пізніше це число стане детектором прогресу для всієї команди:

чужі висновки в контекст спеціаліста не повертаються - погляди незалежніbillingReport: 1 факт,рекомендація, 0.9techReport: 1 факт,рекомендація, 0.9merge()merge()дошка фактів (Board)kb-201 <- billing:kb-201kb-101 <- tech:kb-101дублікат факту: merge = +0 новихкожен факт підписаний: агент:документоркестратородне рішення
Дошка фактів з provenance. Потік іде в 1 напрямку. Код об'єднує звіти на дошці. Оркестратор читає дошку для кінцевого рішення. Але контекст наступного спеціаліста не отримує дошку. Ця асиметрія навмисна. Вона захищає від луна-камери з розділу Break.

Усередині спеціаліст має відомий цикл choose-observe-extract. Але цикл працює тільки зі своїми інструментами і своїм корпусом. Назовні спеціаліст повертає звіт, а не діалог:

def run_specialist(spec: Specialist, task: str, meter: CostMeter) -> Report:
    # той самий цикл, що в Уроках 1-2, тільки вузький. Назовні - звіт, не діалог.
    facts: list[Fact] = []
    history = ""
    for tool in spec.tools:
        obs = observe(spec.name, tool, task)
        meter.add(spec.prompt + task + history, obs)   # choose
        meter.add(spec.prompt + obs, "facts")          # extract
        history += "\n" + obs
        if tool == "search_kb":
            facts = [Fact(cid, spec.corpus[cid], f"{spec.name}:{cid}")
                     for cid in search_kb(spec.corpus, task)]
    if not facts:
        return Report(spec.name, [], "out of my scope, return to orchestrator", 0.2)
    meter.add(spec.prompt + task + history, str(facts))  # зібрати звіт
    return Report(spec.name, facts, f"apply {facts[0].key}", 0.9)

Задача поза корпусом не є винятком або галюцинацією. Це правильний звіт з порожніми фактами і низьким confidence. Запам’ятайте цей рядок. Він скоро буде корисним.

delegate: 1 новий інструмент, 0 нової архітектури

Друга добра новина. Оркестратор - не нова сутність. Це планувальник з Уроку 2 з новим інструментом delegate(name, task) поруч з lookup-інструментами. Вхід - задача, вихід - спостереження. Ви добре знаєте цей контракт інструмента. Усередині інструмента є межі. Межі знову є в коді, а не в промті:

@dataclass
class Budget:
    max_delegations: int = 4
    max_no_progress: int = 2
    delegations: int = 0
    no_progress: int = 0

def delegate(name: str, task: str, board: Board, budget: Budget, meter: CostMeter) -> str:
    # інструмент планувальника з Уроку 2; межі знову в коді, а не в промті
    if budget.delegations >= budget.max_delegations:
        return "denied by policy: delegation budget exhausted, escalate"
    if budget.no_progress >= budget.max_no_progress:
        return "denied by policy: two delegations without new facts, escalate"
    budget.delegations += 1
    # незалежність: спеціаліст бачить задачу, а не чужі висновки з дошки
    report = run_specialist(SPECIALISTS[name], task, meter)
    new = board.merge(report)
    budget.no_progress = 0 if new else budget.no_progress + 1
    return (f"{report.specialist}: +{new} facts, confidence "
            f"{report.confidence}, {report.recommendation}")

Тут ви бачите відомі механізми:

  • бюджет кроків з Уроку 2 став бюджетом делегацій;
  • лічильник прогресу рахує нові факти на дошці, а не виклики;
  • відмова - звичайне спостереження.

Після відмови оркестратор повинен вибрати інший шлях, найчастіше ескалацію. Цикл оркестратора:

def route_fanout(ticket: str) -> list[str]:
    # v1: "хай глянуть обидва" - і за кожен погляд платиш (тріаж - у triage_llm)
    return list(SPECIALISTS)

def run_multi(ticket_id: str, router=route_fanout):
    # оркестратор - сам планувальник з Уроку 2: choose і extract щокроку
    ticket = TICKETS[ticket_id]
    board, budget, meter = Board(), Budget(), CostMeter()
    history = ""
    meter.add(ORCH_PROMPT + ticket, "plan")
    contribs = []
    for name in router(ticket):
        before = len(board.facts)
        ctx = ORCH_PROMPT + ticket + history + board.pretty()
        meter.add(ctx, f"delegate({name})")            # choose
        obs = delegate(name, ticket, board, budget, meter)
        meter.add(ORCH_PROMPT + obs, "facts")          # extract звіту
        history += "\n" + obs
        contribs.append((name, len(board.facts) - before))
    meter.add(ORCH_PROMPT + ticket + history + board.pretty(), "final decision")
    return board, meter, contribs

Роутер v1 - це fan-out : «нехай обидва спеціалісти подивляться». Перша чернетка будь-якої multi-agent системи працює так. Зараз ви побачите вартість цієї чернетки.

Виміряйте, а потім довіряйте команді

Multi-agent architecture коштує в 8 разів дорожче.

скарга з office hours

Команда готова, тікети надходять, демо виглядає розумно. Оркестратор делегує, спеціалісти звітують, дошка заповнюється. Більшість команд зупиняється саме тут, бо система працює. Ми не зупиняємося. Кожен LLM-виклик системи має лічильник:

@dataclass
class CostMeter:
    llm_calls: int = 0
    tokens: int = 0

    def add(self, prompt: str, output: str) -> None:
        # 1 токен ~ 4 символи: груба оцінка, але для порівняння архітектур чесна
        self.llm_calls += 1
        self.tokens += (len(prompt) + len(output)) // 4

Поруч з multi є run_single. Це агент з Уроку 4: 1 промт, спільний корпус, той самий лічильник. Це важливо. Порівнюйте конфігурації тільки на 1 золотому наборі і тільки 1 методом вимірювання. GOLD містить 3 тікети і факти, які потрібні для повного рішення. Успіх означає, що агент зібрав усі потрібні факти:

def evaluate(run_fn) -> tuple[float, float, float]:
    ok = calls = tokens = 0
    for tid, want in GOLD.items():
        board, meter, _ = run_fn(tid)
        ok += want <= set(board.facts)
        calls += meter.llm_calls
        tokens += meter.tokens
    return ok / len(GOLD), calls / len(GOLD), tokens / len(GOLD)

Запускаємо обидві конфігурації:

=== Success і вартість: single проти multi на одному GOLD ===
конфігурація      success  llm/тікет  токени/тікет  x single
single               1.00        7.0           517      1.0x
multi fan-out        1.00       19.3          4086      7.9x

Прочитайте цю таблицю 2 рази. Точність однакова. Вартість у 7.9 раза більша. Людина з епіграфа не перебільшила, вона тільки округлила.

Множник має 3 джерела:

  • кожен спеціаліст виконує свій повний цикл choose-extract;
  • оркестратор платить за план, за кожен delegate і за кінцеве рішення;
  • кожен рівень додає свій промт у кожен виклик.

Це agent theater , патерн F05 з нашого каталогу. Команда агентів створює відчуття інтелекту. Але число на золотому наборі не змінилося навіть на 1 відсоток.

однакова точність1.001.00singlemultisuccess rate на GOLD5174086x7.9agent theatersinglemulti fan-outтокени на тікет
Головна схема уроку. Ліворуч - якість: стовпчики однакові. Праворуч - вартість: майже у 8 разів більша. Якщо ви показуєте тільки ліву половину, це демо. Якщо ви показуєте обидві половини, це інженерія.

Демо також показує детальніший аналіз: хто реально приніс факти:

=== Чи додав другий спеціаліст хоч один факт (fan-out) ===
T-3001  billing +1  tech +1
T-3002  billing +1  tech +0
T-3003  billing +0  tech +1

Тікет T-3001 має 2 домени, і обидва спеціалісти корисні. Саме для таких тікетів ми будували команду. Але тікети T-3002 і T-3003 мають 1 домен. На цих тікетах другий спеціаліст виконує повний платний цикл і додає 0 фактів.

Це луна-метрика: частка тікетів, на яких другий спеціаліст реально додав факти. У типовій підтримці більшість тікетів має 1 домен. Якщо луна-метрика низька, fan-out платить за театр на кожному другому тікеті. Повторні спроби збільшують цю вартість ще швидше (F07).

Перше реальне зменшення вартості - делегувати тільки потрібним спеціалістам. У повному коді це triage_llm. Оркестратор 1 викликом зі structured output ділить тікет на підзадачі. Потім оркестратор викликає тільки потрібних спеціалістів. Точність та сама, множник зменшується.

Ламаємо: 3 способи зламати команду

Вартість була першою проблемою. Тепер зламаємо саму механіку. Ми зробимо це вручну, з журналами.

Злам 1: ping-pong делегація. Тікет T-3005: «Our legal team needs a GDPR data processing addendum signed». Цей тікет не стосується нашої команди. Це не білінг і не технічний домен. Подивіться, що робить наївний оркестратор без бюджетів:

=== Ping-pong делегація: тікет поза обома доменами ===
delegate(billing) -> billing: +0 facts, confidence 0.2, out of my scope, return to orchestrator
delegate(tech) -> tech: +0 facts, confidence 0.2, out of my scope, return to orchestrator
delegate(billing) -> denied by policy: two delegations without new facts, escalate
delegate(tech) -> denied by policy: two delegations without new facts, escalate
витрачено даремно: 12 LLM-викликів, ~2437 токенів

Billing відповідає «не мій домен». Tech відповідає «не мій домен». Без запобіжника оркестратор делегує знову і сподівається на інший результат.

Ви знаєте цю проблему. Це нескінченний цикл з Уроку 1, але на рівень вище. Там модель повторювала виклик інструмента. Тут оркестратор повторює виклик цілого агента. Рішення те саме, і воно вже є в коді delegate: бюджет делегацій і прогрес за фактами. Після 2 делегацій без нових фактів на дошці третя делегація не запускає спеціаліста. Третя делегація повертає «escalate». Агенти використали 12 викликів, але цикл зупинився. Його зупинив код, а не промт.

оркестраторT-3005: GDPR addendumпоза обома доменамиbillingскоуп: грошіtechскоуп: продукт1. delegate(billing)2. out of my scope, +0 фактів3. delegate(tech)4. out of my scope, +0 фактів5. denied by policy: no progress x2-> escalate до людини зі звітом
Цикл делегацій - це F01 на рівень вище. Замість повторного виклику інструмента відбувається повторний виклик агента. Цикл зупиняє не прохання «будь розумнішим» у промті, а лічильник прогресу за фактами в коді delegate.

Злам 2: зіпсований телефон. Повернімося до T-3001. Уявіть систему без звіту і без дошки. Оркестратор переказує результат своїми словами. Так працюють наївні multi-agent системи, де агенти передають сирий текст. Повний код містить навмисний антиприклад:

def relay_naive(report: Report) -> str:
    # АНТИПРИКЛАД: переказ без дошки - рекомендація доїжджає, факти ні
    return f"{report.specialist} says: {report.recommendation}"
=== Зіпсутий телефон: переказ проти дошки ===
наївний переказ: billing says: apply kb-201
дошка фактів з provenance:
  kb-201 <- billing:kb-201: Duplicate charge after a plan upgrade: the old subscription stays active until its invoice closes. Refund the smaller charge and cancel the stale subscription, otherwise the customer is billed twice again next cycle.

Подивіться на різницю. Переказ передав рекомендацію: «застосуй kb-201». Але факт містить критичну другу частину. Рефанду недостатньо. Агент також повинен скасувати стару підписку. Інакше в наступному циклі оплати система знову спише гроші 2 рази.

Оркестратор з переказом зробить рефанд і закриє тікет. Через 1 місяць оркестратор отримає той самий тікет, і клієнт буде ще більш роздратований. Факт загубився на межі між агентами. Модель не винна. Ви самі створили зіпсований телефон.

ШЛЯХ 1: переказ своїми словамизвіт billingфакт kb-201: рефанд меншого чарджа+ скасувати стару підпискурекомендація: apply kb-201переказсвоїми словами"billing says: apply kb-201"рефанд зроблять - і всезагубилось: cancel the stale subscriptionчерез місяць - знову подвійний чарджШЛЯХ 2: структурований звіт + дошкаbillingReportдошка: kb-201 <- billing:kb-201факт доїхав дослівно, з джереломоркестраторрефанд + скасувати підписку
Кожна межа між агентами - місце, де факт може загубитися. Переказ стискає звіт до рекомендації і втрачає критичну деталь. Дошка передає факт точно і з підписом джерела. Людина в ескалації може перевірити факт.

Злам 3: луна-камера. Ідея: для надійності покажемо другому спеціалісту висновок першого, щоб другий перевірив. На папері це розумно. На практиці це дуже погано. Якщо контекст моделі містить впевнений чужий висновок, модель схильна погодитися. Фраза «billing вважає, що це kb-201» майже гарантує відповідь «згоден, kb-201». 2 агенти, 2 платні виклики, 1 думка і 0 перевірки.

Тому в delegate спеціаліст отримує тільки задачу. Рядок «Never forward one specialist’s conclusions to another» в ORCH_PROMPT - тільки додатковий захист. Основна межа вже є в коді.

ЛУНА-КАМЕРАbillingtechмій висновок: Xпогоджуюсь: Xдва голоси, один погляд - перевірки немаНЕЗАЛЕЖНІ ЗАПИТИзадача, без чужих висновківbillingtechдошка: два незалежні звітирозбіжність видно, а не замальовано
Другий погляд має цінність тільки тоді, коли він не бачить першого. Ліворуч агенти передають висновки по колу і погоджуються з першою думкою. Праворуч обидва агенти працюють тільки із задачею. Якщо їхні висновки різні, дошка показує цю різницю.

Ще 1 злам не вимагає сценарію: спеціаліст сам пробує використати чужі інструменти. У наївній системі це залежить від промту. У нашій системі це залежить від 1 if:

=== Межі доступу: спеціаліст проситься в чужі інструменти ===
billing -> read_logs: denied by policy: read_logs is outside the billing scope
tech -> get_invoices: denied by policy: get_invoices is outside the tech scope

Коли multi-agent справді виправданий

Після всіх зламів подивимося на матрицю рішення. Є 4 причини, які виправдовують вартість, і 1 причина, яка не виправдовує:

чотири чесні причини платити за командуІзоляція контекстутри екрани правил стають вузькими промтамивиграш: якість кожного доменуРізні права доступуbilling фізично без tech-інструментів, і навпакивиграш: безпека, least privilegeПаралелізм підзадачнезалежні підзадачі йдуть одночасновиграш: латентність, не якістьНезалежні перспективидругий погляд, сліпий до першого висновкувиграш: чесна перевірка (Урок 7)"більше агентів - розумніше" - не причина: та сама точність за вісім цін (F05)
Матриця рішення. Перед тим, як ділити систему на агентів, знайдіть свою причину в зелених картках. Якщо вашої причини там немає, у вас театр. Вартість театру ви вже бачили.

Розглянемо кожну причину.

  • Ізоляція контексту. Корпус і правила спеціаліста не вміщаються в спільний промт без втрати якості. Це наш сьогоднішній випадок.
  • Різні права доступу. Агенти вимагають різних привілеїв. В Уроці 1 це був guardrail, а тут це архітектура. Якщо атакер контролює 1 агента, він не отримує інструменти іншого агента.
  • Паралелізм. Незалежні підзадачі можуть працювати одночасно. Це перевага в латентності. Не подавайте цю перевагу як перевагу в якості.
  • Незалежні перспективи. Задача вимагає перевірки, а не луни. Це найскладніша причина. Урок 7 повністю присвячений цій темі.

Усі інші причини - театр. Приклади: «сучасний стек», «так робить індустрія», «у демо виглядало розумно».

Той самий оркестратор в інших доменах

Скелет «оркестратор + спеціалісти + дошка + бюджети» повністю працює в інших доменах. Перевіримо це на 3 відомих агентах.

On-call агент. Диспетчер інциденту і вузькі runbook-агенти: 1 для баз даних, 1 для мережі, 1 для деплоїв. Головна перевага тут - права, а не контекст. Діагностика тільки на читання є в одних агентів. restart_service має окремий агент, і цей агент вимагає approval людини з Уроку 2. Матриця «агент × інструмент», яку ви сьогодні написали, у продакшні стає політикою безпеки інциденту.

Аналітик. SQL-агент пише запит. Незалежний верифікатор перевіряє результат. Ключове слово - «незалежний». Верифікатор бачить питання і дані, але не бачить SQL і міркування першого агента. Якщо ви покажете верифікатору запит, верифікатор «перевірить» запит так само, як спеціаліст у луна-камері: погодиться.

Кодовий агент. Планувальник, виконавець, рев’юер. Рев’юер бачить тільки diff і прогін тестів, а не пояснення виконавця. Це незалежна перспектива в найчистішій формі. Ця трійка з’єднує 2 наступні уроки. Зараз ви зрозумієте чому.

Каталог: що закрито, що відкрито

  • Великий промт генераліста - закрито через ізоляцію контексту. Спеціаліст має пів екрана правил, свій корпус і 3 інструменти.
  • Цикли делегацій - закрито через бюджет делегацій і прогрес за фактами в delegate. Це той самий нескінченний цикл з Уроку 1 і те саме рішення, але на рівень вище.
  • Зіпсований телефон - закрито через структурований звіт і дошку з provenance. Дошка передає факт точно і з підписом автора.
  • Луна-камера - закрито через незалежні запити. Спеціаліст бачить задачу, а не чужі висновки.
  • Вихід за межі scope - закрито через least privilege у коді. can_use повертає відмову як спостереження. Жоден промт не змінює цю відмову.
  • Agent theater - код не закриває цю проблему. Єдиний захист - вимірювати single проти multi на 1 золотому наборі. Сьогодні числа показали ту саму точність і 7.9x токенів. Якщо ви не маєте своєї версії цієї таблиці, ваша система за замовчуванням є театром.
  • Незгода спеціалістів - відкрито. Billing може сказати одне, а tech - інше. Дошка показує різницю, але система не має арбітра.

Еволюція вимог

Вимоги v1

  • Тріаж і делегування підзадач
  • Структурований звіт спеціаліста
  • 1 кінцеве рішення
  • Спільний бюджет на тікет

Вимоги v2 (після Break)

  • Бюджет делегацій і прогрес за фактами в коді delegate. Відмова - це спостереження
  • Дошка фактів з provenance замість переказу своїми словами
  • Незалежні запити: чужі висновки не потрапляють у контекст спеціаліста
  • Least privilege у коді: матриця агент x інструмент, denied by policy
  • Тріаж замість fan-out: делегуйте тільки потрібним спеціалістам
  • Порівняння з single на золотому наборі до релізу: multi повинен довести перевагу числом, а не відчуттям

Завантажте повний код

1 файл містить:

  • корпуси за доменами;
  • спеціалістів з least privilege;
  • дошку фактів;
  • delegate з бюджетами;
  • single проти multi з лічильником вартості;
  • усі сценарії Break.

Код працює офлайн без LLM-викликів. Ключ API потрібен тільки для LLM-тріажу тікета на підзадачі в demo.

Завантажити lesson-5-multi-agent.py

pip install anthropic
export ANTHROPIC_API_KEY=sk-...
python lesson-5-multi-agent.py

Команда працює, її вартість виміряна, межі знову є в коді. Але подивіться ще раз на останній пункт каталогу. Він відкритий навмисно. Якщо 2 агенти мають різні оцінки, сьогодні останнє слово має людина. У підтримці «правильно» означає те, що підтвердив клієнт або старший співробітник зміни. Це працює, але повільно, дорого і без масштабування.

Тепер найцікавіше. Є домен, у якому задача сама має арбітра. Цей арбітр не втомлюється і відповідає за секунди. Це код. Тести або зелені, або червоні. Гарні слова моделі не змінять результат тестів.

Наступний урок змінює домен і будує кодового агента. Репозиторій - це стан, а цикл «зміна, тести» працює в пісочниці. Там ви побачите найгірше відкриття курсу. Агент з чесним арбітром дуже швидко вчиться обманювати арбітра: агент змінює тести замість коду.

Курс Metaheuristic

Збудуй AI-агента з нуля.

11 рівнів практики: від виклику LLM до власного coding-агента. Приховані тести, failure patterns, office hours.

Записатись - early bird 9 500 UAH →