Почему ваш агент врет — и это нормально
Вы запустили агента в продакшн. Он отвечает на вопросы клиентов, пишет SQL-запросы, дергает API. Вроде работает, но иногда выдает дичь: то цену не ту посчитает, то забудет выполнить третий шаг. Вы смотрите на логи — пусто. Вы спрашиваете его «почему ты так сделал?» — молчание. Знакомо?
Проблема не в модели. Проблема в том, что вы не измеряете поведение агента в контексте production. Обычный юнит-тест для генеративного AI бесполезен. Вам нужна система, которая ловит регрессии, отслеживает недетерминированные цепочки и превращает «кажется работает» в «точно работает». И такая система есть: LangSmith + Amazon Bedrock + pytest. Без магии, на живых данных.
В этом гайде я покажу, как собрать eval-пайплайн для глубокого AI-агента (text-to-SQL) на AWS с использованием LangSmith, и почему Nova 2 Lite — не компромисс, а осознанный выбор для массового мониторинга.
Слон в комнате: недетерминизм агента
Вы не напишете assert response == expected for AI agent. Почему? Потому что модель может выдать одинаково корректные ответы разными словами или с разной структурой. Классический юнит-тест тут отваливается. Нужны evals — функции, которые не проверяют точное совпадение, а оценивают качество ответа по заданным критериям: полнота, точность, безопасность, выполнение инструментов.
И здесь появляется главная боль: как прогонять сотни eval-тестов на каждую новую версию промпта, не сойдя с ума от затрат и времени? Ответ — автоматизация через pytest с трассировкой в LangSmith и вызовом Bedrock в качестве «судьи» (judge-LLM).
Факт 2026: По данным monday.com, после внедрения eval-driven development на LangSmith среднее время получения фидбэка по агенту сократилось в 8.7 раза. Подробности здесь.
Архитектура eval-пайплайна: три слоя
Прежде чем писать код, давайте договоримся о терминах. Наша система оценки состоит из трех уровней:
| Уровень | Что делаем | Инструмент |
|---|---|---|
| #1 Offline evals | Прогон тестовых кейсов при деплое | pytest + LangSmith Dataset |
| #2 Online monitoring | Логирование и оценка в реальном времени | LangSmith Trace + Feedback |
| #3 Regression guard | Ежедневный прогон с автоматическим оповещением | CI/CD (GitHub Actions) + Bedrock |
Мы пройдемся по каждому уровню на примере агента text-to-SQL, работающего на Amazon Bedrock AgentCore с моделью Nova 2 Lite. Выбор Nova 2 Lite не случаен: она дешевая, быстрая и достаточно умная для роли eval-судьи. К тому же, как показано в фреймворке Amazon по оценке агентов, judge-LLM должен быть дешевле production-агента, чтобы экономия была оправдана.
Настройка трассировки: LangSmith как единственный источник правды
Первое, что нужно сделать — подключить LangSmith к вашему агенту. Без трассировки вы слепы. Каждый шаг агента (мысль, действие, наблюдение) должен быть записан в виде трейса. На AWS это делается через Lambda-extension или напрямую из кода через SDK.
import os
from langsmith import Client
from langchain_aws import ChatBedrock
os.environ["LANGSMITH_TRACING"] = "true"
os.environ["LANGSMITH_API_KEY"] = "your-key"
os.environ["LANGSMITH_PROJECT"] = "text-to-sql-agent"
# Используем Bedrock с Nova 2 Lite как production-модель
llm = ChatBedrock(
model_id="amazon.nova-lite-v1:0",
region_name="us-east-1",
temperature=0.0
)
Теперь каждый запуск агента будет автоматически создавать трейс. Но трейс — это только логи. Чтобы превратить их в метрики, нужны evals.
Пишем pytest-тесты для агента: что и как проверять
Стандартный pytest вы просто так не натянете на AI — слишком много вариативности. Правильный подход: создать несколько типов eval-функций, каждая из которых оценивает один аспект ответа. Вот минимальный набор для text-to-SQL агента:
- SQL_validity — проверка, что сгенерированный SQL синтаксически корректен (через исполнитель или парсер).
- Answer_correctness — сравнение результата запроса с эталоном (используем judge-LLM).
- Tool_calls — проверка, что агент вызвал нужные инструменты в правильном порядке.
- Safety — проверка на утечку данных или вредоносные инструкции.
Пример eval-функции через judge-LLM (Nova 2 Lite):
def eval_answer_correctness(query, expected_sql, actual_sql, actual_result):
judge_prompt = f"""
Ты — судья. Оцени, совпадает ли смысл запроса actual с expected.
Expected SQL: {expected_sql}
Actual SQL: {actual_sql}
Actual result: {actual_result}
Ответь только 'PASS' или 'FAIL'.
"""
response = judge_llm.invoke(judge_prompt)
return response.content == "PASS"
Ошибка новичка: передавать судье полный датасет или свой собственный промпт. Judge-LLM тоже может ошибаться. Обязательно калибруйте его на маленьком наборе, проверяя совпадение с человеческой разметкой.
Теперь собираем это в pytest. Используем фикстуры для загрузки датасета из LangSmith и вызова агента:
import pytest
from langsmith import Client
@pytest.fixture
def dataset():
client = Client()
return list(client.list_examples(dataset_name="text-to-sql-eval"))
def test_agent_on_dataset(dataset):
for example in dataset:
result = run_agent(example.inputs["query"])
assert eval_answer_correctness(
example.inputs["query"],
example.outputs["expected_sql"],
result.sql,
result.data
), f"Failed on '{example.inputs['query']}'"
Запускаем: pytest test_agent.py -v --langsmith-output. Результаты автоматически попадают в LangSmith как feedback.
Интеграция с CI/CD: регрессионный прогон при каждом деплое
Оффлайн-тесты хороши, но без автоматизации они превращаются в рутину. Мы будем запускать eval-пайплайн в GitHub Actions после деплоя агента на Bedrock AgentCore. Полный гайд по CI/CD для AI-агентов на Bedrock уже есть, а я дам ключевой блок — прогон evals.
name: Eval pipeline
on:
push:
branches: [main]
jobs:
eval:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Install deps
run: pip install -r requirements.txt
- name: Run evals
env:
LANGSMITH_API_KEY: ${{ secrets.LANGSMITH_API_KEY }}
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
run: |
pytest test_agent.py -v --junitxml=report.xml
- name: Upload report
uses: actions/upload-artifact@v4
with:
name: eval-report
path: report.xml
Если хоть один тест падает — CI красный, деплой откатывается. Важно: не делайте threshold по метрике «средний pass rate». Лучше жестко фиксировать отдельные кейсы и добавлять новые по мере обнаружения регрессий. Как это делают в тестировании deep agents: single-step vs full-turn.
Online monitoring: ловим регрессии между релизами
Оффлайн-тесты не спасают от того, что модель в продакшне начнет «плыть» из-за изменения поведения LLM или дрейфа данных. Тут в игру вступает online monitoring на базе LangSmith Feedback API.
Идея: каждый вызов агента в production (через Lambda) после ответа асинхронно запускает eval-функцию (например, проверку корректности SQL через judge-LLM). Результат (PASS/FAIL) отправляется обратно в LangSmith как feedback.
from langsmith import Client
def handle_trace(trace):
# После выполнения агента
client = Client()
for run in trace.runs:
if run.run_type == "llm":
result = eval_sql(run.outputs["sql"])
client.create_feedback(
run_id=run.id,
key="sql_validity",
score=1 if result else 0
)
Настраиваем алерты в LangSmith или в AWS CloudWatch: если процент FAIL за час превышает 5% — шлем уведомление в Slack. Кстати, интеграция Bedrock AgentCore со Slack делается за пару часов.
Нюансы, которые сожгут ваш бюджет
Я специально откладываю важное предупреждение на этот момент, чтобы вы прочувствовали:
- Judge-LLM тоже нужно тестировать. Мы используем Nova 2 Lite — она дешева (~$0.2 за 1M токенов), но может галлюцинировать. Раз в неделю прогоняйте калибровочный датасет с человеческой разметкой.
- Не eval-те каждый трейс в production. Выбирайте случайную выборку (например, 10%) или только те, где пользователь поставил дизлайк. Иначе потратите больше на eval, чем на самого агента.
- Трассировка в LangSmith — это тоже затраты. Если агент совершает много внутренних шагов (deep reasoning), каждый шаг — отдельный run. Оптимизируйте: агрегируйте мелкие шаги в один span.
- Регрессионный тест не должен быть хрупким. Если вы проверяете точное совпадение SQL — вы проиграли. Сравнивайте семантику, не синтаксис. Для этого используйте reward-функции из Reinforcement Fine-Tuning.
Тупой пример, на котором всё ломается
Сценарий: пользователь спрашивает «сколько заказов сделал клиент 123?». Агент генерирует SQL SELECT COUNT(*) FROM orders WHERE customer_id = 123. Всё ок. Но в следующем релизе вы обновили промпт — и агент начал выдавать SELECT * FROM orders WHERE customer_id = 123 и потом в коде считать длину списка. Результат тот же, но логика разная. Ваш старый eval (точное совпадение SQL) упадет. Но ваш judge-LLM должен сказать PASS. Если он говорит FAIL — вы перекалибровали судью неправильно.
Мораль: eval-функции должны быть устойчивы к эквивалентным трансформациям. Не завязывайтесь на конкретное выражение, а проверяйте результат и план выполнения.
Что дальше: от evals к continuous improvement
Когда вы научились измерять качество, вы можете начать его улучшать. Собранные feedback-данные из LangSmith используйте для:
- Поиска слабых мест в знаниях агента (сбор новых примеров для few-shot).
- Настройки промпта (A/B тесты с разными system prompt).
- Fine-tuning модели через Bedrock (если есть кастомные данные).
Кстати, если вы ещё не прогоняли свой агент через production-ready framework на ReAct — советую начать с него, а потом добавить eval-слой. Всё равно строить придётся с нуля.
Напоследок: не верьте, что хороший агент — это тот, который никогда не ошибается. Хороший агент — это тот, чьи ошибки вы видите, измеряете и быстро фиксите. LangSmith + pytest + Bedrock дают вам именно это: прозрачность и управляемость. Остальное — дело времени и денег на евлы.