/
Вопросы и ответы
/
AI-агенты
/

Как тестировать агентов?

Как тестировать агентов?

11 часов назад

Никита Вихров

Ответы

0

Как тестировать агентов

Агенты сложнее тестировать, чем обычный код: они недетерминированы, делают API-вызовы и работают в цикле. Гонять реальные запросы к LLM на каждый прогон тестов — дорого и медленно. Но тестировать надо.

Стратегия простая: разделяй то, что детерминировано (инструменты, логика цикла), и то, что недетерминировано (ответы модели). Первое тестируй обычными юнит-тестами. Второе — моками.


Тестирование инструментов отдельно

Инструменты — обычные функции. Тестируются без LLM:

import pytest
from unittest.mock import patch, mock_open

# Инструмент
def read_file(path: str) -> str:
    try:
        with open(path) as f:
            return f.read()
    except FileNotFoundError:
        return f"Ошибка: файл {path} не найден"
    except PermissionError:
        return f"Ошибка: нет прав на чтение {path}"

# Тесты
def test_read_file_success():
    with patch("builtins.open", mock_open(read_data="print('hello')")):
        result = read_file("/project/main.py")
    assert result == "print('hello')"

def test_read_file_not_found():
    result = read_file("/nonexistent/file.py")
    assert "не найден" in result
    assert "/nonexistent/file.py" in result

def test_read_file_permission_error():
    with patch("builtins.open", side_effect=PermissionError):
        result = read_file("/etc/shadow")
    assert "нет прав" in result

Мок LLM-вызовов

Вместо реального API подставляем заранее заготовленные ответы:

from unittest.mock import MagicMock, patch
import pytest

def run_agent(task: str, tools: list, client) -> str:
    messages = [{"role": "user", "content": task}]

    for _ in range(10):
        response = client.messages.create(
            model="claude-opus-4-5",
            max_tokens=2048,
            tools=tools,
            messages=messages
        )

        if response.stop_reason == "end_turn":
            return response.content[0].text

        # ... обработка tool_use

    return "Лимит шагов исчерпан"


def make_mock_response(stop_reason: str, text: str = None, tool_name: str = None, tool_input: dict = None):
    """Фабрика для создания mock-ответов"""
    response = MagicMock()
    response.stop_reason = stop_reason

    if stop_reason == "end_turn":
        content_block = MagicMock()
        content_block.type = "text"
        content_block.text = text
        response.content = [content_block]

    elif stop_reason == "tool_use":
        tool_block = MagicMock()
        tool_block.type = "tool_use"
        tool_block.name = tool_name
        tool_block.input = tool_input
        tool_block.id = "tool_123"
        response.content = [tool_block]

    return response


def test_agent_completes_simple_task():
    mock_client = MagicMock()
    mock_client.messages.create.return_value = make_mock_response(
        stop_reason="end_turn",
        text="Задача выполнена"
    )

    result = run_agent("Простая задача", tools=[], client=mock_client)
    assert result == "Задача выполнена"


def test_agent_calls_tool_then_answers():
    mock_client = MagicMock()
    # Первый вызов — модель хочет использовать инструмент
    # Второй вызов — даёт финальный ответ
    mock_client.messages.create.side_effect = [
        make_mock_response("tool_use", tool_name="read_file", tool_input={"path": "/app/main.py"}),
        make_mock_response("end_turn", text="Файл содержит точку входа приложения"),
    ]

    result = run_agent("Что в файле main.py?", tools=[], client=mock_client)
    assert "точку входа" in result
    assert mock_client.messages.create.call_count == 2


def test_agent_stops_after_max_steps():
    mock_client = MagicMock()
    # Модель бесконечно хочет вызывать инструмент
    mock_client.messages.create.return_value = make_mock_response(
        "tool_use", tool_name="search", tool_input={"query": "test"}
    )

    result = run_agent("Ищи вечно", tools=[], client=mock_client)
    assert result == "Лимит шагов исчерпан"

Интеграционные тесты с реальным API

Иногда нужно проверить, что агент реально справляется с задачей — не мок, а настоящий вызов. Такие тесты запускают редко и отдельно:

import pytest

@pytest.mark.integration
@pytest.mark.skipif(not os.getenv("ANTHROPIC_API_KEY"), reason="Нет API ключа")
def test_agent_real_file_analysis():
    """Интеграционный тест — запускать вручную или в CI раз в день"""
    client = Anthropic()
    result = run_agent("Сколько Python-файлов в директории tests/?", tools=tools, client=client)
    assert any(char.isdigit() for char in result)  # В ответе есть число

Запуск только интеграционных тестов:

pytest -m integration

Юнит-тесты — при каждом коммите. Интеграционные — в CI раз в день или перед релизом.

11 часов назад

Никита Вихров

+7 800 100 22 47

бесплатно по РФ

+7 495 085 21 62

бесплатно по Москве

108813 г. Москва, вн.тер.г. поселение Московский,
г. Московский, ул. Солнечная, д. 3А, стр. 1, помещ. 20Б/3
ОГРН 1217300010476
ИНН 7325174845