5. Когда узел падает¶
Правила по ключевым словам надёжны, но туповаты. Языковая модель читает тикет лучше: она поймёт и тему, и срочность, и настроение. А ещё она может быть недоступна, упереться в лимит или отказать из-за отсутствия ключа.
Это не повод писать try в Python. Падение здесь — такая же развилка в графе,
как любая другая: не смогла модель — идём на правила.
Retry принадлежит узлу¶
Часть падений имеет смысл просто повторить. Например, лимит запросов:
{"id": "triage", "type": "stage", "stage": "LlmTriageStage",
"arguments": {"vars": {"text": "text", "subject": "subject", "api_key": "OPENAI_API_KEY"}},
"outputs": {"topic": "topic", "urgency": "urgency", "summary": "summary"},
"retry": [{"error_equals": ["LlmRateLimited", "LlmUnavailable"],
"max_attempts": 3, "interval_seconds": 2}]}
max_attempts считает и первый запуск, поэтому 3 — это одна попытка и два
повтора. retry может нести любой узел, не только stage.
Try накрывает область¶
Узел try оборачивает не один узел, а кусок графа: всё, что достижимо из
body, но не достижимо из next.
{"id": "guard", "type": "try", "body": "triage", "next": "gather",
"except": [{"error_equals": ["LlmAuthError"], "next": "rules", "result_var": "llm_error"},
{"error_equals": ["*"], "next": "rules", "result_var": "llm_error"}]}
error_equals принимает имя класса, полный путь или * — «что угодно».
result_var кладёт ошибку во фрейм объектом с полями type, full_type,
message и node, так что следующий узел может посмотреть, что случилось.

Редактор обводит обе области: тело — вокруг triage, обработчик — вокруг
rules.
Где кончается дорога¶
Обе дороги блока заканчиваются одинаково. Дорога, которая просто кончилась
("next": null), возвращает управление узлу try, и граф идёт дальше с его
собственного next — неважно, тело это было или обработчик.
Поэтому стадия с правилами может и назвать следующий узел, и обойтись без
next: в обоих случаях граф продолжится с gather:
{"id": "rules", "type": "stage", "stage": "ClassifyByRulesStage",
"arguments": {"vars": {"text": "text", "subject": "subject"}},
"outputs": {"topic": "topic", "urgency": "urgency"},
"next": "gather"}
Исключение — узел terminal внутри блока: он завершает весь запуск, и ничего
после блока не исполняется.
Как это выглядит, когда срабатывает¶
Запустите бота без рабочего ключа. Стадия с моделью падает, обработчик её ловит, дальше работают правила:
stage_failed triage stage: LlmTriageStage, error: no OpenAI API key …
try_caught guard type: LlmAuthError, next: rules
node_enter rules
Путь запуска от старта до ответа:
start → load → guard → triage → rules → gather → customer → search → route → render → send → answered
Результат тот же {'status': 'answered'}, что и раньше. Бот потерял только
прочтение тикета моделью и продолжил работать.
Дальше: граф внутри узла.