ИНЦИДЕНТ · С ЧЕГО НАЧАЛОСЬ
Сообщение приняли. Ответ потеряли.
STACHKA · Суриков Антон · Сбер
Подтверждать получение можно только после того, как работа сохранена
Offset сохраняется до ответа. Если ответ не ушёл, а процесс перезапустился — Telegram этот update уже не пришлёт. «Принято» и «сделано» здесь не одно и то же.
ACK только после сохранённого результата
STACHKA · Суриков Антон · Сбер
Повторная доставка стала возможной — значит, нужна идемпотентность
async def on_update(update: Update) -> None:
task_id = await store.register(update.update_id) # запись в БД до работы
try:
result = await handle(task_id, update)
await store.save_result(task_id, result)
await bot.reply(update, result)
except Exception:
await store.rollback_offset(update.update_id) # update придёт снова
raise
if await store.task_id_for(update.update_id) == task_id:
await store.ack(update.update_id)
task_id создаётся заранее
LLM-агент — ненадёжный компонент
STACHKA · Суриков Антон · Сбер
Это доклад про уроки и поломки, а не про готовый продукт
Надёжность дают не промпты, а обычная инженерия на Python.
Метки на слайдах: ПРОТОТИП — работает · ТЗ — целевое требование · ИНЦИДЕНТ — реально ломалось.
• контракт задачи · состояние в БД · идемпотентность
• права вне текста · сквозные тесты
Почему здесь вообще нужны несколько агентов
STACHKA · Суриков Антон · Сбер
Повод для сети агентов — не число систем, а разные владельцы и права
за данные и решения отвечают разные команды
HR, Finance, Docs, почта, календарь
задача живёт дольше сессии
Пять агентов: один руководитель и четыре блока
STACHKA · Суриков Антон · Сбер
Руководитель ставит поручение, блоки 1–4 держат свои данные. Шлюз A2A и хранилище взаимодействий отвечают за доставку, состояния, защиту от дублей и аудит.
• ✓ Каждое сообщение идёт через шлюз
• ✓ Права у каждого агента свои
• ✓ Шлюз не читает текст поручения
ИНЦИДЕНТ · КАРТА СБОЕВ ПИЛОТА
Что ломалось при работе с агентами
STACHKA · Суриков Антон · Сбер
Проверять нужно и каждого агента, и передачу задачи между ними
| Инцидент | Что произошло |
| КОНТЕКСТ | 106 081 токенов при лимите 65 536 |
| ВЫЗОВЫ | HTTP 400, XML вместо вызова |
| SAFETY | 413 / DNS названы нарушением политики |
| ДОСТАВКА | ACK и конфликт 409 |
| РЕЗЕРВ | общая квота легла одной волной |
| РЕЛИЗ | 6 из 9 тестов падали |
Сколько стоил один неудачный цикл
STACHKA · Суриков Антон · Сбер
Задаче нужны лимиты: вызовы, токены, время, повторы, глубина делегирования
Лимит 65 536 не придуман: сервер запущен с контекстом 131 072 и --parallel 2, то есть по 65 536 на слот.
| Метрика | Значение |
| Запрос на ревью | 106 081 токенов |
| Лимит | 65 536 |
| Не помещается | +40 545 |
| Вход за неудачный цикл | 46 583 |
| Ответ локальной модели | 3–5 минут |
Вызов инструмента пришёл пользователю текстом
STACHKA · Суриков Антон · Сбер
LEAK = re.compile(r'<tool_call>|<function|"arguments"\s*:')
def call_llm(prompt: str) -> str:
for _ in range(3):
out = llm(prompt)
if not LEAK.search(out):
return out
raise ToolCallLeak(prompt)
tool_call в тексте, иногда 400
шаблон чата не совпал с моделью
детектор и контрактный тест
Сбой сети назвали нарушением политики
STACHKA · Суриков Антон · Сбер
class Kind(Enum):
RETRYABLE = "retry" # 429, 5xx, таймаут
INFRA = "infra" # DNS, 413, квота
SAFETY = "safety" # только решение фильтра
INVALID = "invalid" # нарушен контракт
match classify(exc):
case Kind.RETRYABLE: retry()
case _: raise exc
«нарушение safety» и ноль ретраев
413 и DNS в общем except Exception
типизированные классы ошибок
Когда можно подтверждать обработку
STACHKA · Суриков Антон · Сбер
At-least-once: дубли будут, порядок не гарантирован. ACK — про сообщение, а завершение задачи — отдельный статус
• OUTBOX — состояние и событие в одной транзакции
• INBOX — UNIQUE(idempotency_key), дубль вернёт прежний результат
• ПОВТОРЫ — 0/5/30/120 с + jitter → DLQ
Общая квота легла одной волной
STACHKA · Суриков Антон · Сбер
Резерв, который делит квоту с основным путём, — не резерв
Запасной путь без изолированной квоты не является запасным.
Основная и резервная модели недоступны одновременно — они делят один и тот же лимит.
6 из 9 тестов падали, агент коммитил дальше
STACHKA · Суриков Антон · Сбер
Запрет коммита в красную ветку — правило CI, а не просьба в промпте
«Да, я способен переписывать свой код» — а агент ещё не проверил файлы и инструменты.
• Самоотчёт модели — не доказательство
• Красные тесты он не счёл причиной остановиться
• Пользователь об этом вообще не узнал
Три слоя и кто за что отвечает
STACHKA · Суриков Антон · Сбер
• A2A — сообщения, идентичность, статусы
• CONTROL PLANE — права, сроки, повторы, аудит
• MCP — бизнес-запросы и действия
Три вывода для Python-разработчика
STACHKA · Суриков Антон · Сбер
LLM-агент — ненадёжный компонент. Надёжность даёт инженерия вокруг него.
1. Состояние задачи — в БД, а не в asyncio.Task: рестарт не должен терять задачи.
2. Идемпотентность доказывает тест с падением в каждой точке: эффект → commit → ACK.
3. Права, сроки и лимиты проверяет код, а не промпт.