Архитектура приложения с LLM
Как организовать код, чтобы LLM был надёжной частью приложения, а не источником хаоса.
LLM-слой — изолированная часть приложения, отвечающая за общение с моделью: сборку промптов, вызов API, повторы, фолбэки и парсинг ответов.
Изолируйте работу с LLM
Не разбрасывайте вызовы messages.create по всему коду. Соберите их в один модуль/сервис. Тогда смена провайдера, модели или формата промпта — это правка в одном месте, а не по всему проекту. Бизнес-логика вызывает ваш сервис, а не SDK напрямую.
Где хранить промпты
Промпты — это не «магические строки» внутри функций. Их версионируют как код или конфиг: отдельные файлы/шаблоны, с понятными именами. Тогда их можно ревьюить, тестировать, A/B-сравнивать и откатывать, не трогая логику.
# prompts/support_system.txt — отдельный файл, а не строка в коде
# llm_service.py
def answer_support(question, history):
system = load_prompt("support_system.txt")
return call_llm(system=system, messages=history + [user(question)])
Ретраи и таймауты — на уровне слоя
Повторы временных ошибок и разумные таймауты задают один раз в LLM-слое (через настройки SDK), а не дублируют в каждом месте вызова. Так поведение единообразно.
Фолбэки
Продумайте, что делать, когда модель недоступна или вернула мусор:
- Фолбэк на другую модель: если основная перегружена (529), пробуем запасную (например, более дешёвую/быструю).
- Фолбэк на правило: если LLM не ответил, показать заранее заготовленный ответ или передать оператору.
- Деградация, а не падение: сбой LLM не должен ронять всё приложение.
def call_with_fallback(messages):
try:
return call_model("claude-opus-4-8", messages)
except (RateLimitError, OverloadedError):
return call_model("claude-haiku-4-5", messages) # запасная
except Exception:
return DEFAULT_ANSWER # мягкая деградация
Разделяйте детерминированное и недетерминированное
LLM непредсказуем. Всё, что можно сделать обычным кодом (валидация, маршрутизация, вычисления), делайте кодом, а модели оставляйте то, что требует понимания языка. Так система предсказуемее и дешевле.
Типичная форма LLM-функции
Большинство «умных» функций укладываются в один и тот же конвейер: собрать промпт из входных данных и шаблона → вызвать модель (с ретраями и таймаутом) → распарсить и провалидировать ответ → при необходимости выполнить инструмент и дозапросить → вернуть результат или фолбэк. Если держать эту структуру в голове, любая новая фича становится подстановкой в знакомый каркас, а не изобретением с нуля. И каждый шаг этого конвейера — отдельная точка, которую удобно логировать и тестировать.
Итог
- Изолируйте общение с LLM в один слой; бизнес-логика не зовёт SDK напрямую.
- Промпты храните как версионируемые артефакты, не как строки в коде.
- Ретраи, таймауты и фолбэки задавайте на уровне слоя; сбой LLM = деградация, не падение.