Перейти к содержанию

16. Best Practices и Области Применения

Зачем это нужно?

В этой главе разберём лучшие практики создания и поддержки агентов, а также области, где они обычно работают лучше всего.

Знание теории и примеров — это хорошо, но без понимания best practices вы можете совершить типичные ошибки и создать неэффективного или небезопасного агента.

Реальный кейс

Ситуация: Вы создали агента для DevOps и запустили его в продакшен. Через неделю агент удалил production базу данных без подтверждения.

Проблема: Вы не реализовали валидацию входных данных и проверку безопасности. Агент выполнил опасное действие без подтверждения.

Решение: Удаление должно было быть destructive-инструментом: dry-run (план: «удалю базу prod, таблиц: 42») + Confirm-коллбек с confirmed_by (гл. 05). Тогда даже кривой промпт не приводит к потере базы — механику держит код. Валидация, safety checks и evals — ниже, по порядку.

Best Practices: Создание агентов

1. Начинайте с простого

Плохо: Сразу пытаться создать сложного агента со множеством инструментов и многошаговым планированием.

Хорошо: Начните с простого агента с 2-3 инструментами, затем постепенно добавляйте функциональность.

Пример эволюции (нейтральный ToolDef из гл. 03):

// Этап 1: Простой агент (1 инструмент)
tools := []ToolDef{
    {Name: "check_status", Description: "Check if a service is running."},
}

// Этап 2: Добавляем инструменты
tools = append(tools,
    ToolDef{Name: "read_logs", Description: "Read the last N lines of service logs."},
    ToolDef{Name: "restart_service", Description: "Restart a service: drops open connections. Confirmed via the Confirm mechanism (chapter 05)."},
)
// restart_service объявлен в реестре с Confirm: true

// Этап 3: Добавляем сложную логику (SOP, планирование)
systemPrompt = addSOP(systemPrompt, incidentSOP)

2. Чётко определяйте границы ответственности

Проблема: Агент пытается делать всё подряд и путается.

Решение: Опишите зону ответственности с причинами. Саму границу держит allowlist инструментов (гл. 05, гл. 17): чего нет в allowlist'е — того модель вызвать не может.

Ты DevOps инженер.

Твоя зона ответственности:
- Проверка статуса сервисов
- Чтение логов
- Перезапуск сервисов (дежурный подтверждает вызов через Confirm)
- Базовая диагностика проблем

Вне зоны ответственности:
- Изменение конфигурации и удаление данных: таких инструментов у тебя нет,
  runtime отклонит вызов с ошибкой «unknown tool»
- Операции на production без разрешения дежурного

Модель соблюдает правила, которые понимает, заметно лучше, чем правила капсом. Но помните: промпт объясняет границы — allowlist их держит.

3. Используйте детальные описания инструментов

Плохо:

ToolDef{
    Name:        "check",
    Description: "Check something",
}

Хорошо:

ToolDef{
    Name:        "check_service_status",
    Description: "Check if a systemd service is running. Use when the user asks about service status, availability, or whether a service is up/down. Returns 'active', 'inactive', or 'failed'.",
}

Почему важно: Модель выбирает инструменты на основе Description. Чем точнее описание (что делает, когда применять, что возвращает, чем рискован), тем лучше выбор. Для опасных инструментов описание объясняет риск — а механику подтверждения держит флаг Confirm в коде, не текст (гл. 05).

4. Всегда валидируйте входные данные

Критично для безопасности:

func executeTool(name string, args json.RawMessage, confirmedBy string) (string, error) {
    // 1. Инструмент существует и разрешён в этом ходу (allowlist)
    tool, ok := toolsByName[name]
    if !ok {
        return "", fmt.Errorf("unknown tool: %s", name)
    }

    // 2. Парсим и валидируем аргументы
    var params ToolParams
    if err := json.Unmarshal(args, &params); err != nil {
        return "", fmt.Errorf("invalid JSON: %v", err)
    }

    // 3. Проверяем обязательные поля
    if params.ServiceName == "" {
        return "", fmt.Errorf("service_name is required")
    }

    // 4. Санитизация входных данных
    params.ServiceName = sanitize(params.ServiceName)

    // 5. Подтверждение — свойство инструмента, а не текст в промпте (гл. 05)
    if tool.Confirm && confirmedBy == "" {
        return "refused: this tool requires confirmed_by — a human id from an authenticated session", nil
    }

    return execute(tool, params)
}

Ключевое отличие от наивной версии: проверка опасности опирается на флаг tool.Confirm и аргумент confirmed_by, который runtime подставляет после решения человека (гл. 05). Поиск «yes» в истории (hasConfirmationInHistory) не защищает: в историю попадает недоверенный текст.

5. Реализуйте защиту от зацикливания

Проблема: Агент может повторять одно и то же действие бесконечно.

Решение: Границы хода задаются в конфиге цикла, а не константой в теле функции (Config/Run-паттерн, гл. 04):

cfg := AgentConfig{
    Limits: Limits{
        MaxIterations: 10,
        Deadline:      time.Hour,  // дедлайн хода — терминальная граница
        TokenBudget:   100_000,
    },
    Loop: newLoopDetector(), // N одинаковых вызовов подряд → блокировка, 2N → терминал
}

res := Run(ctx, cfg) // RunResult возвращается и при терминальной ошибке

Детекция повторяющихся действий — задача цикла, а не вашей бизнес-логики. Полный разбор лимитов и recovery — в гл. 04 и гл. 09.

6. Логируйте всё

Важно для отладки и аудита:

type AgentLog struct {
    Timestamp   time.Time
    UserInput   string
    ToolCalls   []ToolCall
    ToolResults []ToolResult
    FinalAnswer string
    TokensUsed  int
    Latency     time.Duration
}

func logAgentRun(log AgentLog) {
    // Логируем в файл, БД, или систему мониторинга
    logger.Info("Agent run", "log", log)
}

Что логировать и как хранить историю, чтобы её можно было восстановить и проиграть заново, — гл. 11: State Management; трейсинг и метрики — гл. 19: Observability.

7. Используйте evals с самого начала

Не откладывайте тестирование:

// Создайте базовый набор evals сразу
tests := []EvalTest{
    {Name: "Basic tool call", Input: "...", Expected: "..."},
    {Name: "Safety check", Input: "...", Expected: "..."},
}

// Запускайте после каждого изменения
func afterPromptChange(ctx context.Context) {
    metrics := collectMetrics(ctx, tests) // гл. 08: метрики прогонов
    if metrics.PassRate < 90 {            // проценты (×100), согласовано с гл. 08
        panic("Regression detected!")
    }
}

Safety-чек в наборе — это не «модель вежливо просит подтверждение», а «цикл поставил ход на паузу и передал confirmed_by» — тестируйте механику, а не формулировки (гл. 08).

Best Practices: Поддержка агентов

1. Версионируйте промпты

Проблема: После изменения промпта агент стал работать хуже, но вы не знаете, что именно изменилось.

Решение:

type PromptVersion struct {
    Version   string
    Prompt    string
    CreatedAt time.Time
    Author    string
    Notes     string
}

// Храните версии промптов
promptVersions := []PromptVersion{
    {Version: "1.0", Prompt: systemPromptV1, CreatedAt: ..., Notes: "Initial version"},
    {Version: "1.1", Prompt: systemPromptV2, CreatedAt: ..., Notes: "Added SOP for incidents"},
}

// Можете откатиться к предыдущей версии
func rollbackPrompt(version string) {
    prompt := findPromptVersion(version)
    systemPrompt = prompt.Prompt
}

2. Мониторинг метрик

Отслеживайте ключевые метрики:

type AgentMetrics struct {
    RequestsPerDay      int
    AvgLatency          time.Duration
    AvgTokensPerRequest int
    PassRate            float64
    ErrorRate           float64
    MostUsedTools       map[string]int
}

func collectProductionMetrics() AgentMetrics {
    // Собирайте метрики из логов
    return AgentMetrics{
        RequestsPerDay: countRequests(today),
        AvgLatency: calculateAvgLatency(),
        // ...
    }
}

Алерты:

  • Pass Rate упал ниже 80%
  • Latency вырос более чем на 50%
  • Ошибки участились
  • Агент зацикливается чаще обычного

3. Регулярно обновляйте evals

Добавляйте новые тесты по мере обнаружения проблем:

// Обнаружили проблему: агент вызывает рестарт без достаточной диагностики
newTest := EvalTest{
    Name:     "Restart requires completed diagnosis",
    Input:    "Проблемы с базой, разберись",
    // Ожидание в контракте чекера гл. 08: Confirm ставит ход на паузу,
    // модель добирает доказательства и только потом повторяет запись
    Expected: "ask_confirmation",
}

tests = append(tests, newTest)

4. Документируйте решения

Ведите документацию. Типовая ошибка в таких записях — фиксировать проблему «в промпте». Правильный фикс — в коде, промпт лишь объясняет причину:

## Известные проблемы

### Проблема: Агент выполнял рестарты без подтверждения
**Дата:** 2026-01-06
**Симптомы:** Агент перезапускал сервисы по первому упоминанию слова «перезапуск» в тикете
**Решение:** Рестарт объявлен пишущим инструментом с флагом Confirm: цикл ставит
ход на паузу и спрашивает дежурного вне модели, решение передаётся в вызов как
confirmed_by (гл. 05). В промпте добавлено объяснение, зачем нужна пауза, —
чтобы модель строила план вокруг неё, а не пыталась её «обойти».
**Статус:** Исправлено в версии 1.2

Почему «добавили просьбу в промпт» — не фикс: инструкция модели не инвариант системы. Ей не следуют при инъекции, её «забывают» в исключительном случае, и ничто не мешает модели сгенерировать «yes» за пользователя. Разбор — в гл. 05.

5. A/B тестирование промптов

Сравнивайте разные версии:

func abTestPrompt(ctx context.Context, promptA, promptB string, tests []EvalTest) string {
    // Контракт evals гл. 08: подмена system prompt + два прогона collectMetrics
    systemPrompt = promptA
    metricsA := collectMetrics(ctx, tests)

    systemPrompt = promptB
    metricsB := collectMetrics(ctx, tests)

    fmt.Printf("Prompt A: Pass Rate %.1f%%, Avg Latency %v\n",
        metricsA.PassRate, metricsA.AvgLatency)
    fmt.Printf("Prompt B: Pass Rate %.1f%%, Avg Latency %v\n",
        metricsB.PassRate, metricsB.AvgLatency)

    // Выбираем лучший вариант
    if metricsB.PassRate > metricsA.PassRate {
        return promptB
    }
    return promptA
}

Практики прод-эксплуатации

Когда агент пережил первые релизы, на первый план выходят четыре практики.

Бюджеты на контекст (гл. 13):

  • ~128 KB на один tool-результат: что не влезло — усекается с маркером, полный вывод уходит в артефакт (гл. 11);
  • лимит картинок в сессии: изображения дороги в токенах и не сжимаются condense'ом;
  • параллелизм инструментов ограничивайте памятью потомков: go test или браузер в tool'е едят RAM вне рантайма — лимитируйте их число, иначе OOM на хосте, а не в цикле.

Секреты:

  • секреты приходят из vault'а при сборке/старте, а не из env, прокинутого целиком; если env неизбежен — allowlist переменных, которые попадают в процесс;
  • вывод инструментов проходит redaction по умолчанию: маскируется всё, что похоже на секрет, полный вывод — в артефакт с ограниченным доступом.

Ревью субагентом с чистой памятью: финальный ответ (PR, отчёт, план) проверяет отдельный агент с read-only-подмножеством инструментов (запись делает родитель, получив вывод субагента — инвариант гл. 09), чистой историей и инструкцией «найди ошибку» — он не унаследовал уверенность автора в собственном плане.

Гард-тесты и дашборды: инварианты («destructive без confirmed_by невозможен», «allowlist не расширился») фиксируются guard-тестами в CI (гл. 23), поведение — дашбордами метрик и трейсов (гл. 19). Регрессию должна ловить система, а не пользователи.

Как это устроено в реальном SDK. Redaction и бюджет на tool-результат — свойства рантайма, а не договорённости с моделью: фильтр и усечение применяются к результату до записи в историю, а полная версия хранится в артефакте с отдельным доступом. Секретов в env процесса просто нет — они подтягиваются резолвером при старте и в контекст не попадают.

Как это устроено в реальном SDK. Субагент-ревьюер — это тот же цикл, запущенный вторым Run с минимальным Config и пустой историей; корреляция с родительским ходом — общий run_id в телеметрии (гл. 07). Параллельные инструменты ограничены семафором в рантайме, а не «просьбой не торопиться» в промпте.

Области применения агентов

Где агенты наиболее эффективны

1. DevOps и Infrastructure

Хорошо: диагностика по метрикам и логам, инцидент-менеджмент (триаж, сбор информации), рутинные операции через allowlist.

Примеры: "Проверь статус всех сервисов", "Найди причину падения сервиса X", "Очисти логи старше 7 дней".

Границы: архитектурные решения, любые write-операции без подтверждения дежурного — Confirm-механика, не промпт.

Кейс: управление виртуальными машинами

Парк VM на десятках хостов: инвентаризация, capacity planning, создание и изменение. Инженер — узкое место: недоступен, вручную собирает информацию из разных источников, ошибается в рутине.

Агент решает типовые задачи: инвентаризация ("На каких хостах VM проекта X?"), capacity ("Хватит ли ресурсов на 5 новых VM?"), изменение ("Увеличь память app-01 до 16GB").

tools := []ToolDef{
    {Name: "list_vms", Description: "Получить список виртуальных машин. Используй для инвентаризации и поиска VM по имени или проекту."},
    {Name: "get_vm_placement", Description: "Получить размещение VM: хост и кластер. Используй для проверки распределения нагрузки."},
    {Name: "get_cluster_capacity", Description: "Получить доступные ресурсы кластера (CPU, память, диск). Используй для capacity planning перед созданием новых VM."},
    {Name: "create_vm", Description: "Создать VM (имя, CPU, память, диск, хост). Занимает ресурсы кластера и попадает в биллинг — выполняется после подтверждения инженером через Confirm (гл. 05)."},
    {Name: "resize_vm", Description: "Изменить ресурсы работающей VM: требует перезагрузки и прерывает сервисы на ней. Выполняется после подтверждения через Confirm."},
    {Name: "expand_disk", Description: "Расширить диск VM: необратимо и занимает ёмкость хранилища. Выполняется после подтверждения через Confirm."},
}

SOP для изменения VM: capacity → валидация параметров (runtime проверяет корректность CPU/RAM/disk до выполнения) → Confirm-пауза и решение инженера → выполнение → верификация → отчёт. Все операции логируются для аудита; evals проверяют, что создание и изменение не выполняются без confirmed_by.

Результат: рутина уходит агенту, инженёр остаётся для сложных решений; критичные операции проходят через runtime-валидацию и подтверждение.

2. Customer Support

Хорошо: типовые запросы по базе знаний, сбор контекста (версия ПО, ОС, браузер), эскалация.

Примеры: "Пользователь не может войти", "Найди решение проблемы с оплатой".

Границы: эмоциональная поддержка, юридические вопросы, сложные технические проблемы — эскалация на человека.

3. Data Analytics

Хорошо: SQL из естественного языка, проверка качества данных, отчёты, тренды.

Примеры: "Покажи продажи за месяц по регионам", "Почему упали продажи в регионе X?".

Границы: только read-only — соединение от учётки с правами SELECT, а не запрет в промпте; интерпретация в бизнес-контексте — человек.

4. Security (SOC)

Хорошо: триаж алертов, сбор доказательств, анализ паттернов атак, отчёты об инцидентах.

Примеры: "Триажируй алерт о подозрительной активности", "Проверь репутацию IP".

Границы: изоляция хостов и блокировки — dry-run + Confirm + confirmed_by; реальная граница — изоляция процесса и права хоста (гл. 17).

5. Product Operations

Хорошо: планы релизов, проверка зависимостей, документация.

Примеры: "Подготовь план релиза фичи X", "Создай release notes для версии 2.0".

Границы: стратегические решения и согласования — за стейкхолдерами; агент готовит, не решает.

Когда НЕ стоит использовать агентов

1. Критичные операции без механики подтверждения

Плохо: destructive-инструмент без категории — агент сам удаляет production базу, потому что «в промпте просили быть осторожным».

Хорошо: удаление объявлено destructive: сначала dry-run (план: «удалю базу prod, таблиц: 42»), затем Confirm-пауза и confirmed_by от человека из аутентифицированной сессии (гл. 05).

2. Задачи, требующие творчества

Агенты плохо справляются с дизайном интерфейсов, маркетинговыми текстами и архитектурными решениями — там, где нужны креативность, вкус и глубокая экспертиза.

3. Задачи с высокой неопределённостью

Лучше всего — когда есть чёткие критерии успеха, SOP и инструменты для получения информации. Хуже всего — когда критериев нет, нужна интуиция или нет доступа к данным.

4. Задачи, требующие эмпатии

Агенты не понимают эмоции пользователей и не оказывают эмоциональную поддержку — это остаётся человеком.

Типовые ошибки

Большинство типовых ошибок уже разобрано в профильных главах — здесь сводка и место, куда идти:

Ошибка Где разобрано
Нет валидации входных данных Гл. 03, Гл. 09
Нет защиты от зацикливания Гл. 04
Нет логирования и восстановления истории Гл. 11
Нет мониторинга и трейсинга Гл. 19
Нет evals Гл. 08

Три ошибки, которые заслуживают разбора именно здесь — потому что встречаются у команд, уже прочитавших всё остальное:

Разбор 1: подтверждение починили «в промпте»

Симптом: Инцидент с удалением без подтверждения; в changelog пишут «добавили явную просьбу подтверждать опасные действия в системный промпт»; через месяц инцидент повторяется.

Причина: Инструкция модели — не инвариант системы. Ей не следуют при инъекции (гл. 05), и «yes» в истории мог написать не человек.

Решение: Confirm-флаг на инструменте + confirmed_by как обязательный аргумент. Промпт объясняет причину паузы, чтобы модель строила план вокруг неё. Тест на это — guard-тест: destructive-вызов с пустым confirmed_by должен возвращать отказ.

Разбор 2: секреты утекают через вывод инструментов

Симптом: В трейсах и истории видны токены: kubectl get secret вернул базу64-строку, а через неделю она оказалась в экспортированном логе.

Причина: Результат инструмента попал в историю как есть, а env процесса содержал все секреты сразу.

Решение: Redaction по умолчанию для всех tool-результатов (маскируется всё, что похоже на секрет), полный вывод — в артефакт с ограниченным доступом; секреты из vault'а при старте, env — по allowlist'у. Детали — гл. 17.

Разбор 3: регрессию находят пользователи, а не CI

Симптом: Незначительное изменение промпта («переформулировал границы») уронило Pass Rate с 92% до 61%, но узнали об этом через две недели по жалобам.

Причина: Evals запускались вручную перед релизом, guard-тестов на инварианты не было.

Решение: Evals в CI на каждый PR (гл. 23) + дашборд метрик с алертами (гл. 19). Правило простое: если регрессию видно на дашборде — вы узнали первым.

Критерии сдачи / Чек-лист

Сдано (готовность к продакшену):

  • Системный промпт объясняет границы с причинами
  • Все инструменты имеют детальные описания
  • Реализована валидация входных данных (allowlist + схема + санитизация)
  • Лимиты заданы в конфиге цикла (итерации, дедлайн, бюджет)
  • Критичные операции — Confirm-флаг + confirmed_by, не строка в промпте
  • Логирование всех действий и мониторинг метрик
  • Бюджеты: ~128 KB на tool-результат, лимит картинок, ограничение параллелизма
  • Секреты: vault при сборке, redaction вывода инструментов по умолчанию
  • Создан базовый набор evals; guard-тесты инвариантов в CI
  • A/B тестирование промпта и документированные решения

Не сдано:

  • Опасные действия держатся просьбой в промпте
  • Нет лимитов цикла или они зашиты константой в тело функции
  • Нет бюджетов на tool-результаты и redaction
  • Нет evals и мониторинга — регрессию находят пользователи

Связь с другими главами

Что дальше?

После изучения best practices переходите к: