Tool spam: коли AI-агент занадто часто викликає інструменти

Tool spam виникає, коли агент знову і знову викликає ті самі tools без реального прогресу. Чому це стається і як це обмежують у production.
На цій сторінці
  1. Проблема
  2. Чому це стається
  3. Які збої трапляються найчастіше
  4. Повтори того самого signature (Repeated signature spam)
  5. Косметичні зміни аргументів (Argument jitter spam)
  6. Множення ретраїв між шарами (Retry amplification)
  7. Неконтрольований fan-out (Fan-out spam)
  8. Як виявляти ці проблеми
  9. Як відрізнити tool spam від справді широкого пошуку
  10. Як зупиняти такі збої
  11. Де це реалізується в архітектурі
  12. Самоперевірка
  13. FAQ
  14. Пов'язані сторінки

Проблема

Запит виглядає звичайним: перевірити статус повернення і дати коротку відповідь.

У трейсах видно інше: за 6 хвилин один run зробив 52 виклики інструментів (search.read — 31, crm.lookup — 14, http.get — 7) і все одно завершився timeout. Для задачі такого класу це може бути ~$3 замість звичних ~$0.10.

API формально "живе": більшість відповідей 200, явного падіння немає. Але користувач не отримує відповідь, а вартість run'а росте з кожним повтором.

Система не падає.

Вона просто множить однакові виклики і тихо спалює бюджет.

Аналогія: уяви оператора підтримки, який натискає redial на той самий номер, замість того щоб ескалувати задачу або змінити план. Він зайнятий, але проблема не рухається. Tool spam в агентах виглядає так само: дій багато, корисного прогресу мало.

Чому це стається

Tool spam виникає не тому, що агент "занадто старається", а тому, що runtime не відрізняє нову корисну дію від дубля без прогресу.

У production зазвичай так:

  1. LLM обирає tool_call;
  2. інструмент повертає нестабільний або недостатній сигнал;
  3. агент повторює той самий виклик (або майже той самий);
  4. без dedupe, budget gates і єдиної retry policy цикл розростається.

Проблема не в одному конкретному інструменті. Проблема в тому, що система не обмежує повторні виклики до того, як вони стають інцидентом.

Які збої трапляються найчастіше

Щоб не ускладнювати, у production найчастіше бачать чотири патерни tool spam.

Повтори того самого signature (Repeated signature spam)

Агент викликає той самий tool з тими самими аргументами кілька разів поспіль.

Типова причина: немає dedupe за tool+args_hash у межах run.

Косметичні зміни аргументів (Argument jitter spam)

Змінюється лише дрібниця в аргументах: регістр, пробіл, порядок слів. Семантично це той самий запит, але система сприймає його як новий.

Типова причина: немає нормалізації аргументів перед dedupe.

Множення ретраїв між шарами (Retry amplification)

Ретраї робить і агент, і gateway, і сам SDK інструмента. Один збій перетворюється на серію дубльованих викликів.

Типова причина: retry policy розмазана по кількох місцях.

Неконтрольований fan-out (Fan-out spam)

Один крок агента запускає багато паралельних викликів без жорсткого ліміту. Навіть без циклу це швидко перевантажує зовнішні API.

Типова причина: немає bounded fan-out і per-tool caps.

Як виявляти ці проблеми

Tool spam добре видно по поєднанню runtime і gateway-метрик.

МетрикаСигнал tool spamЩо робити
tool_calls_per_taskрізкий ріст викликів на один runпоставити max_tool_calls і per-tool caps
repeated_tool_signature_rateчасті повтори tool+args у межах runдодати dedupe window і кеш короткого життя
unique_signature_ratioпадає частка унікальних викликівдодати no-progress правило на N кроків
retry_amplification_rateретраї дублюються між шарамицентралізувати retry policy в одному gateway
cost_per_runціна run'а росте без приросту якостівмикати budget gate і kill switch на проблемний інструмент

Як відрізнити tool spam від справді широкого пошуку

Не кожна велика кількість викликів означає збій. Ключове питання: чи додає кожен виклик новий корисний сигнал.

Нормально, якщо:

  • нові tool_call реально відкривають нові джерела або факти;
  • unique_signature_ratio тримається стабільно;
  • витрати ростуть разом із якістю відповіді.

Небезпечно, якщо:

  • повторюється та сама signature або майже та сама;
  • 3-5 кроків підряд не дають нової інформації;
  • вартість і latency ростуть, а відповідь не стає кращою.

Як зупиняти такі збої

Практично це виглядає так:

  1. ставиш max_tool_calls на run і окремі ліміти на кожен інструмент;
  2. додаєш dedupe за tool+args_hash з коротким вікном;
  3. тримаєш retry policy тільки в gateway (з чітким списком non-retryable помилок);
  4. при дублях або перевищенні ліміту повертаєш cached/partial результат і stop reason.

Мінімальний guard для контролю повторних викликів:

PYTHON
from dataclasses import dataclass
import json


def call_signature(tool: str, args: dict) -> str:
    normalized_args = normalize_args(args)
    normalized = json.dumps(normalized_args, sort_keys=True, ensure_ascii=False)
    return f"{tool}:{normalized}"


def normalize_text(value: str) -> str:
    return " ".join(value.strip().lower().split())


def normalize_args(args: dict) -> dict:
    normalized: dict = {}
    for key, value in args.items():
        if isinstance(value, str):
            normalized[key] = normalize_text(value)
        else:
            normalized[key] = value
    return normalized


@dataclass(frozen=True)
class ToolSpamLimits:
    max_tool_calls: int = 12
    max_repeat_per_signature: int = 2


class ToolSpamGuard:
    def __init__(self, limits: ToolSpamLimits = ToolSpamLimits()):
        self.limits = limits
        self.total_calls = 0
        self.by_signature: dict[str, int] = {}

    def on_tool_call(self, tool: str, args: dict) -> str | None:
        self.total_calls += 1
        if self.total_calls > self.limits.max_tool_calls:
            return "budget:tool_calls"

        sig = call_signature(tool, args)
        self.by_signature[sig] = self.by_signature.get(sig, 0) + 1
        if self.by_signature[sig] > self.limits.max_repeat_per_signature:
            return "tool_spam:repeated_signature"

        return None

Це базовий guard: у production перед args_hash часто додають доменну нормалізацію (trim/lowercase/collapse spaces для тексту, а для окремих полів — canonical ordering), а on_tool_call(...) викликають перед фактичним tool виконанням, щоб зупиняти дубль ще до зайвого зовнішнього виклику.

Де це реалізується в архітектурі

Контроль tool spam у production зазвичай лежить у трьох шарах.

Agent Runtime відповідає за ліміти run'а, stop reasons, no-progress правила і кероване завершення. Саме тут зазвичай фіксують budget:tool_calls і tool_spam:*.

Tool Execution Layer відповідає за dedupe, retry policy, короткий кеш і нормалізацію помилок інструментів. Якщо цей шар слабкий, spam швидко розповзається по всьому workflow.

Policy Boundaries задає, які інструменти можна викликати, як часто і за яких умов. Це дозволяє обмежувати ризикові інструменти ще до виконання виклику.

Самоперевірка

Швидка перевірка перед релізом. Відмічайте пункти і дивіться статус нижче.
Це короткий sanity-check, а не формальний аудит.

Прогрес: 0/8

⚠ Є сигнали ризику

Бракує базових контролів. Закрийте ключові пункти цього чекліста перед релізом.

FAQ

Q: Чи достатньо лише max_steps?
A: Ні. Один крок агента може містити кілька tool_call, тому потрібен окремий ліміт саме на інструменти.

Q: Дедуп не вбиває freshness?
A: Ні, якщо дедуп короткий і scoped per run. Його мета — прибрати шумові дублікати, а не кешувати "стару правду" надовго.

Q: Де мають жити retries?
A: У єдиному choke point, зазвичай у tool gateway. Там же варто явно відсікати non-retryable помилки: 401, 403, 404, schema validation errors і policy denials зазвичай завершують run одразу.

Q: Що показувати користувачу, якщо run зупинено через spam?
A: Причину зупинки, що вже встигли перевірити, і безпечний наступний крок (fallback або ручна ескалація).


Tool spam майже ніколи не виглядає як гучна аварія. Це повільне роздування викликів, latency і витрат, яке добре видно лише у трейсах. Тому production-агентам потрібні не лише кращі моделі, а й жорсткий контроль tool_call на рівні runtime і gateway.

Пов'язані сторінки

Щоб глибше закрити цю проблему, подивись:

  • Чому AI агенти ламаються — загальна карта збоїв у production.
  • Infinite loop — як зациклення швидко переходить у повтори викликів.
  • Budget explosion — як spam інструментами роздуває витрати.
  • Tool failure — як нестабільний інструмент провокує хвилі ретраїв.
  • Agent Runtime — де ставити stop reasons і execution-ліміти.
  • Tool Execution Layer — де тримати dedupe, retries і контроль викликів.
⏱️ 6 хв читанняОновлено 12 березня 2026 р.Складність: ★★☆
Реалізувати в OnceOnly
Guardrails for loops, retries, and spend escalation.
Використати в OnceOnly
# onceonly guardrails (concept)
version: 1
budgets:
  max_steps: 25
  max_tool_calls: 12
  max_seconds: 60
  max_usd: 1.00
policy:
  tool_allowlist:
    - search.read
    - http.get
controls:
  loop_detection:
    enabled: true
    dedupe_by: [tool, args_hash]
  retries:
    max: 2
    backoff_ms: [200, 800]
stop_reasons:
  enabled: true
logging:
  tool_calls: { enabled: true, store_args: false, store_args_hash: true }
Інтегровано: продакшен-контрольOnceOnly
Додай guardrails до агентів з tool-calling
Зашип цей патерн з governance:
  • Бюджетами (кроки / ліміти витрат)
  • Kill switch та аварійна зупинка
  • Audit logs та трасування
  • Ідемпотентність і dedupe
  • Дозволами на інструменти (allowlist / blocklist)
Інтегрована згадка: OnceOnly — контрольний шар для продакшен агент-систем.
Приклад policy (концепт)
# Example (Python — conceptual)
policy = {
  "budgets": {"steps": 20, "seconds": 60, "usd": 1.0},
  "controls": {"kill_switch": True, "audit": True},
}

Автор

Микола — інженер, який будує інфраструктуру для продакшн AI-агентів.

Фокус: патерни агентів, режими відмов, контроль рантайму та надійність систем.

🔗 GitHub: https://github.com/mykolademyanov


Редакційна примітка

Ця документація підготовлена з допомогою AI, із людською редакторською відповідальністю за точність, ясність і продакшн-релевантність.

Контент базується на реальних відмовах, постмортемах та операційних інцидентах у розгорнутих AI-агентних системах.