0Pricing
AI Agents · Урок

Имитация вызовов LLM в тестах

unittest.mock, фикстуры pytest и запись с последующим воспроизведением ответов LLM.

«Имитация вызовов LLM в тестах» — бесплатный урок AI Agents на CoddyKit. Это урок 2 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения AI Agents, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс AI Agents содержит 4 уроков всего.

Что такое имитация

Имитация заменяет реальную функцию или объект фиктивной версией, которая возвращает контролируемые ответы. При тестировании агентов мы имитируем вызовы API LLM, чтобы тесты выполнялись мгновенно, не требовали затрат и давали предсказуемые результаты.

Стандартным инструментом для этого служит модуль Python unittest.mock.

Основы unittest.mock.patch()

unittest.mock.patch(target) временно заменяет именованный объект на время выполнения теста. target — это строка с точками, указывающая на объект так, как он импортирован в тестируемом модуле.

from unittest.mock import patch, MagicMock

# The function under test calls openai.chat.completions.create
# We patch it so no real API call is made

def ask_llm(question: str) -> str:
    import openai
    client = openai.OpenAI(api_key='test')
    resp = client.chat.completions.create(
        model='gpt-4o-mini',
        messages=[{'role': 'user', 'content': question}]
    )
    return resp.choices[0].message.content

with patch('openai.OpenAI') as mock_client_class:
    mock_instance = MagicMock()
    mock_client_class.return_value = mock_instance
    mock_instance.chat.completions.create.return_value = MagicMock(
        choices=[MagicMock(message=MagicMock(content='Paris'))]
    )
    result = ask_llm('Capital of France?')
    print(result)  # 'Paris' — no API call made

Использование patch как декоратора в pytest

При использовании в качестве декоратора с pytest @patch() передаёт имитацию в качестве параметра функции. После завершения теста имитация удаляется автоматически.

from unittest.mock import patch, MagicMock
import pytest

# Assume agent.py contains: import openai; client = openai.OpenAI(...)

@patch('agent.openai.OpenAI')
def test_agent_calls_llm(mock_openai_class):
    # Set up the mock chain
    mock_client = MagicMock()
    mock_openai_class.return_value = mock_client
    mock_client.chat.completions.create.return_value = MagicMock(
        choices=[MagicMock(message=MagicMock(content='Paris is the capital of France.'))]
    )

    from agent import ask_llm
    result = ask_llm('What is the capital of France?')

    assert 'Paris' in result
    mock_client.chat.completions.create.assert_called_once()

Создание повторно используемого имитируемого ответа

Создавать объекты имитируемых ответов вручную многословно. Создайте вспомогательную функцию, которая формирует правильно структурированные имитируемые объекты, соответствующие структуре ответа OpenAI SDK.

from unittest.mock import MagicMock

def make_mock_response(content: str, tool_calls: list = None) -> MagicMock:
    message = MagicMock()
    message.content = content
    message.tool_calls = tool_calls or []

    choice = MagicMock()
    choice.message = message
    choice.finish_reason = 'stop' if not tool_calls else 'tool_calls'

    response = MagicMock()
    response.choices = [choice]
    response.usage = MagicMock(total_tokens=42)
    return response

# Usage in tests:
# mock_create.return_value = make_mock_response('Hello!')
# mock_create.return_value = make_mock_response('', tool_calls=[...])

# --- demo ---
response = make_mock_response('The weather in Paris is 18C and sunny.')
print('content:', response.choices[0].message.content)
print('finish_reason:', response.choices[0].finish_reason)
print('total_tokens:', response.usage.total_tokens)

Имитация вызовов инструментов в ответах

При тестировании логики вызова инструментов агентом имитируемый ответ должен содержать правильно структурированное поле tool_calls, чтобы код разбора агента мог корректно его обработать.

import json
from unittest.mock import MagicMock

def make_tool_call_response(tool_name: str, arguments: dict) -> MagicMock:
    tool_call = MagicMock()
    tool_call.id = 'call_abc123'
    tool_call.type = 'function'
    tool_call.function = MagicMock()
    tool_call.function.name = tool_name
    tool_call.function.arguments = json.dumps(arguments)

    message = MagicMock()
    message.content = None
    message.tool_calls = [tool_call]

    response = MagicMock()
    response.choices = [MagicMock(message=message, finish_reason='tool_calls')]
    return response

# mock.return_value = make_tool_call_response('search_web', {'query': 'Python tutorials'})

# --- demo ---
response = make_tool_call_response('search_web', {'query': 'Python tutorials'})
call = response.choices[0].message.tool_calls[0]
print('tool name:', call.function.name)
print('tool arguments:', call.function.arguments)
print('finish_reason:', response.choices[0].finish_reason)

Фикстуры pytest для имитации

Фикстуры pytest позволяют определить повторно используемый код настройки. Создайте fixture, которая заменяет клиент LLM и передаёт его любому запросившему её тесту, — повторять этот код не потребуется.

import pytest
from unittest.mock import patch, MagicMock

@pytest.fixture
def mock_openai(make_mock_response):
    with patch('myagent.client.chat.completions.create') as mock_create:
        mock_create.return_value = MagicMock(
            choices=[MagicMock(message=MagicMock(
                content='Default mocked response',
                tool_calls=[]
            ))]
        )
        yield mock_create

# Now any test can use it:
def test_agent_responds(mock_openai):
    from myagent import agent
    result = agent.run('Hello')
    assert result is not None
    mock_openai.assert_called_once()

Фикстура mocker из pytest-mock

pytest-mock предоставляет фикстуру mocker, упрощающую замену объектов. Она автоматически удаляет имитации и предоставляет более удобный синтаксис, чем непосредственное использование unittest.mock.patch.

# pip install pytest-mock

# In your test file:
def test_agent_with_mocker(mocker):
    mock_create = mocker.patch('myagent.client.chat.completions.create')
    mock_create.return_value = mocker.MagicMock(
        choices=[mocker.MagicMock(message=mocker.MagicMock(
            content='Mocked answer',
            tool_calls=[]
        ))]
    )

    from myagent import agent
    result = agent.run('What is 2+2?')
    assert 'answer' in result.lower() or '4' in result

    mock_create.assert_called_once()
    # No cleanup needed — mocker handles it

Запись и воспроизведение с помощью vcr.py

vcrpy при первом запуске записывает реальные взаимодействия по HTTP в файл «кассету», а при последующих запусках воспроизводит их. Это особенно удобно для тестирования кода, использующего низкоуровневый HTTP API, а не SDK.

# pip install vcrpy
import vcr
import httpx

@vcr.use_cassette('fixtures/cassettes/openai_chat.yaml')
def test_with_recorded_response():
    # First run: makes a real HTTP call and records it
    # Subsequent runs: uses the recorded cassette (no network, no cost)
    response = httpx.post(
        'https://api.openai.com/v1/chat/completions',
        json={'model': 'gpt-4o-mini', 'messages': [{'role': 'user', 'content': 'Hello'}]},
        headers={'Authorization': 'Bearer YOUR_KEY'}
    )
    data = response.json()
    assert data['choices'][0]['message']['content'] is not None

Проверка правильности вызова имитации

После теста проверьте, что имитация была вызвана с правильными аргументами. Это позволяет обнаружить ошибки, при которых агент отправляет неправильную модель, пропускает параметры или передаёт некорректные сообщения.

from unittest.mock import patch, MagicMock, call

@patch('myagent.client.chat.completions.create')
def test_agent_sends_correct_model(mock_create):
    mock_create.return_value = MagicMock(
        choices=[MagicMock(message=MagicMock(content='ok', tool_calls=[]))]
    )

    from myagent import agent
    agent.run('Hello')

    # Verify the mock was called with correct arguments
    mock_create.assert_called_once()
    call_kwargs = mock_create.call_args.kwargs
    assert call_kwargs['model'] == 'gpt-4o-mini'
    assert len(call_kwargs['messages']) >= 1
    assert call_kwargs['messages'][0]['role'] == 'system'

Моделирование ошибок API в тестах

Проверьте, как агент обрабатывает сбои LLM, настроив имитацию на вызов исключений. Так вы проверите обработку ошибок и логику повторных попыток, не вызывая реальные сбои API.

from unittest.mock import patch
import openai

@patch('myagent.client.chat.completions.create')
def test_agent_handles_rate_limit(mock_create):
    # Simulate a rate limit error
    mock_create.side_effect = openai.RateLimitError(
        message='Rate limit exceeded',
        response=None,
        body=None
    )

    from myagent import agent
    result = agent.run('Hello')

    # Agent should handle this gracefully
    assert result['error'] == 'rate_limit'
    # or
    assert result['retry_after'] is not None

Организация имитирующих фикстур в conftest.py

Поместите общие фикстуры в conftest.py в корне каталога с тестами. pytest автоматически обнаружит этот файл и сделает фикстуры доступными во всех файлах тестов без импорта.

# tests/conftest.py
import pytest
from unittest.mock import patch, MagicMock

@pytest.fixture(autouse=False)
def mock_llm():
    with patch('myagent.client.chat.completions.create') as mock_create:
        mock_create.return_value = MagicMock(
            choices=[MagicMock(message=MagicMock(
                content='Test response',
                tool_calls=[]
            ))]
        )
        yield mock_create

@pytest.fixture
def mock_search_tool():
    with patch('myagent.tools.search_web') as mock_search:
        mock_search.return_value = [{'title': 'Test', 'url': 'https://example.com'}]
        yield mock_search

Проверка знаний: имитация вызовов LLM

Проверьте, насколько хорошо Вы понимаете методы имитации для тестов агентов.

Итоги: имитация вызовов LLM в тестах

Теперь у Вас есть инструменты для написания быстрых и надёжных модульных тестов агентов:

  • Используйте unittest.mock.patch(), чтобы заменять клиенты LLM имитациями
  • Создавайте повторно используемые вспомогательные функции для имитируемых ответов, соответствующих структуре ответа SDK
  • Имитируйте вызовы инструментов с правильно структурированными полями tool_calls
  • Используйте фикстуры pytest и conftest.py, чтобы совместно использовать имитации в разных тестах
  • Используйте pytest-mock для более удобного синтаксиса
  • Используйте vcrpy для записи и воспроизведения реальных взаимодействий по HTTP

Имитации — основа быстрого и удобного в сопровождении набора тестов агента.

Часто задаваемые вопросы

Урок «Имитация вызовов LLM в тестах» бесплатный?

Да — полный текст урока «Имитация вызовов LLM в тестах» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс AI Agents, подпишись на CoddyKit PRO. Курс AI Agents содержит 4 уроков всего.

Чему я научусь в уроке «Имитация вызовов LLM в тестах»?

unittest.mock, фикстуры pytest и запись с последующим воспроизведением ответов LLM. Ты практикуешь AI Agents с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.

Нужен ли мне опыт, чтобы начать AI Agents?

Предыдущий опыт не требуется. AI Agents на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 2 из 4.

Сколько времени занимает урок «Имитация вызовов LLM в тестах»?

Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.

Можно ли писать и запускать код в этом уроке AI Agents?

Да. Каждый урок AI Agents включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.

Все уроки этого курса

  1. Почему тестирование агентов отличается
  2. Имитация вызовов LLM в тестах
  3. Тестирование агентов на основе утверждений
  4. Интеграционные тесты для конвейеров агентов
← Назад к AI Agents