0Pricing
AI Agents · Leçon

Tester les agents par assertions

Vérifiez les appels aux outils, les étapes intermédiaires et la structure de la sortie finale.

Tester les agents par assertions est une leçon AI Agents gratuite sur CoddyKit. Ceci est la leçon 3 sur 4. Tu peux lire la leçon complète ci-dessous gratuitement — puis la pratiquer en direct dans le navigateur avec un éditeur de code intégré et un tuteur IA 24/7. Elle fait partie du parcours d'apprentissage AI Agents, et ta progression se synchronise sur le web et l'application CoddyKit. Le cours AI Agents comprend 4 leçons au total.

Aller au-delà de la correspondance exacte des chaînes

Comme les sorties des LLM sont non déterministes, les tester avec assert response == 'exact text' est fragile. Écrivez plutôt des assertions qui vérifient la structure et l'intention de la réponse sans dépendre de sa formulation exacte.

Vérifier que des appels d'outils ont été effectués

Pour les agents qui appellent des fonctions, l'assertion la plus fiable consiste à vérifier que l'agent a choisi d'appeler le bon outil. Cette vérification est structurelle : elle ne dépend pas de la formulation exacte du raisonnement du LLM.

import json
from unittest.mock import patch, MagicMock

@patch('myagent.client.chat.completions.create')
def test_agent_calls_search_tool(mock_create):
    # Mock: agent decides to call search_web
    tool_call = MagicMock()
    tool_call.function.name = 'search_web'
    tool_call.function.arguments = json.dumps({'query': 'Python tutorials'})
    mock_create.return_value = MagicMock(
        choices=[MagicMock(message=MagicMock(tool_calls=[tool_call]))]
    )

    response = mock_create()  # simulating the agent call
    tc = response.choices[0].message.tool_calls

    assert tc is not None
    assert len(tc) > 0
    assert tc[0].function.name == 'search_web'

# --- demo: give unittest.mock.patch a real dotted path to patch ---
import sys
import types

_myagent = types.ModuleType('myagent')
_myagent.client = types.SimpleNamespace(
    chat=types.SimpleNamespace(completions=types.SimpleNamespace(create=lambda *a, **k: None))
)
sys.modules['myagent'] = _myagent

test_agent_calls_search_tool()
print('test_agent_calls_search_tool: PASS')

Vérifier le nom correct de l'outil

Au-delà de la simple vérification de l'existence d'appels d'outils, vérifiez que le nom précis de l'outil correspond à ce qui est attendu. Cela détecte les cas où l'agent choisit le mauvais outil pour une requête donnée.

import json
from unittest.mock import MagicMock

def extract_tool_calls(response) -> list:
    message = response.choices[0].message
    if not message.tool_calls:
        return []
    return [
        {
            'name': tc.function.name,
            'args': json.loads(tc.function.arguments)
        }
        for tc in message.tool_calls
    ]

# In a test:
# calls = extract_tool_calls(mock_response)
# assert calls[0]['name'] == 'get_weather'
# assert calls[0]['args']['city'] == 'Paris'
print('Tool name and argument assertions are the most reliable agent tests')

Vérifier les arguments des outils

Après avoir vérifié le nom de l'outil, vérifiez que ses arguments sont corrects. L'agent doit non seulement choisir le bon outil, mais aussi le renseigner avec les bons paramètres issus de la demande de l'utilisateur.

import json
from unittest.mock import patch, MagicMock

@patch('myagent.client.chat.completions.create')
def test_weather_tool_gets_correct_city(mock_create):
    tool_call = MagicMock()
    tool_call.function.name = 'get_weather'
    tool_call.function.arguments = json.dumps({'city': 'Tokyo', 'unit': 'celsius'})
    mock_create.return_value = MagicMock(
        choices=[MagicMock(message=MagicMock(tool_calls=[tool_call]))]
    )

    response = mock_create()
    args = json.loads(response.choices[0].message.tool_calls[0].function.arguments)

    assert args['city'] == 'Tokyo'
    assert args.get('unit') in ['celsius', 'fahrenheit', None]  # flexible

# --- demo: give unittest.mock.patch a real dotted path to patch ---
import sys
import types

_myagent = types.ModuleType('myagent')
_myagent.client = types.SimpleNamespace(
    chat=types.SimpleNamespace(completions=types.SimpleNamespace(create=lambda *a, **k: None))
)
sys.modules['myagent'] = _myagent

test_weather_tool_gets_correct_city()
print('test_weather_tool_gets_correct_city: PASS')

Validation des sorties par schéma JSON

Lorsque votre agent renvoie du JSON structuré, validez la sortie par rapport à un schéma JSON afin de vérifier que tous les champs requis sont présents et possèdent les bons types. La bibliothèque jsonschema facilite cette tâche.

# pip install jsonschema
import jsonschema

AGENT_RESPONSE_SCHEMA = {
    'type': 'object',
    'required': ['answer', 'sources', 'confidence'],
    'properties': {
        'answer': {'type': 'string', 'minLength': 1},
        'sources': {
            'type': 'array',
            'items': {'type': 'string', 'format': 'uri'}
        },
        'confidence': {'type': 'number', 'minimum': 0, 'maximum': 1}
    }
}

def test_agent_output_schema(agent_output: dict):
    try:
        jsonschema.validate(instance=agent_output, schema=AGENT_RESPONSE_SCHEMA)
        print('Schema validation passed')
    except jsonschema.ValidationError as e:
        raise AssertionError(f'Invalid agent output: {e.message}')

Assertions de présence de mots-clés

Pour les réponses textuelles dont la formulation exacte varie, vérifiez que les concepts ou mots importants apparaissent dans la sortie. Cette méthode est souple tout en restant pertinente : la réponse de l'agent doit au moins mentionner les termes concernés.

def assert_keywords_present(text: str, keywords: list, require_all: bool = True):
    lower_text = text.lower()
    found = [kw.lower() in lower_text for kw in keywords]

    if require_all:
        missing = [kw for kw, f in zip(keywords, found) if not f]
        assert not missing, f'Missing keywords: {missing}'
    else:
        assert any(found), f'None of {keywords} found in: {text[:100]}'

# Tests
response = 'The capital city of France is Paris, located in western Europe.'
assert_keywords_present(response, ['paris', 'france', 'capital'])
print('All keywords present!')  # passes

assert_keywords_present(response, ['spain', 'france'], require_all=False)
print('At least one keyword present!')  # passes

Vérifier le format de la réponse : vérifications de type

Les vérifications de type sont rapides et fiables. Vérifiez que l'agent renvoie un dictionnaire plutôt qu'une valeur nulle, que les champs de type liste sont bien des listes et que les champs numériques se trouvent dans des intervalles valides.

def test_agent_returns_valid_structure(agent_result):
    # Type checks
    assert isinstance(agent_result, dict), 'Result must be a dict'
    assert isinstance(agent_result.get('answer'), str), 'answer must be a string'
    assert isinstance(agent_result.get('steps'), list), 'steps must be a list'

    # Non-empty checks
    assert len(agent_result['answer']) > 0, 'answer must not be empty'
    assert len(agent_result['steps']) >= 1, 'must have at least one step'

    # Range checks
    confidence = agent_result.get('confidence', 0)
    assert 0.0 <= confidence <= 1.0, 'confidence must be 0-1'

print('Structural assertions are fast and reliable')

Vérifier la raison de fin

Le champ finish_reason vous indique pourquoi le modèle a cessé de générer du contenu. Le vérifier aide à détecter les problèmes : 'stop' signifie que la réponse est terminée normalement, 'tool_calls' signifie que l'agent veut appeler un outil, et 'length' indique une troncature.

from unittest.mock import MagicMock

def test_agent_stops_cleanly(mock_response):
    finish_reason = mock_response.choices[0].finish_reason
    assert finish_reason in ('stop', 'tool_calls'), \
        f'Unexpected finish_reason: {finish_reason}'

def test_no_truncation(mock_response):
    finish_reason = mock_response.choices[0].finish_reason
    assert finish_reason != 'length', \
        'Response was truncated — increase max_tokens'

# Example mock for a clean stop
mock = MagicMock()
mock.choices = [MagicMock(finish_reason='stop')]
test_agent_stops_cleanly(mock)
print('finish_reason: stop — clean termination')

Vérifier le nombre d'étapes d'une boucle

Un agent qui s'exécute en boucle doit terminer en un nombre raisonnable d'étapes. Vérifiez que l'agent termine avant d'atteindre un nombre maximal d'itérations — cela détecte les boucles infinies que votre garde-fou est censé empêcher.

def test_agent_completes_in_bounded_steps(mock_agent):
    result = mock_agent.run('Search for the weather in Paris')

    # Agent should complete within 5 steps
    assert result['steps_taken'] <= 5, \
        f'Agent took too many steps: {result["steps_taken"]}'

    # Agent should produce a final answer, not exit on timeout
    assert result['status'] == 'completed', \
        f'Agent did not complete: {result["status"]}'

    assert result['answer'] is not None

print('Bounding step count prevents runaway agents from passing tests')

Paramétrer les tests pour plusieurs entrées

@pytest.mark.parametrize de pytest vous permet d'exécuter le même test avec de nombreuses entrées différentes. C'est idéal pour vérifier que votre agent achemine différents types de requêtes vers les bons outils.

import pytest
from unittest.mock import patch, MagicMock
import json

@pytest.mark.parametrize('query,expected_tool', [
    ('What is the weather in Tokyo?', 'get_weather'),
    ('Calculate 15% of 200', 'calculator'),
    ('Search for Python books', 'web_search'),
    ('What time is it in Berlin?', 'get_time'),
])
@patch('myagent.client.chat.completions.create')
def test_agent_tool_routing(mock_create, query, expected_tool):
    tool_call = MagicMock()
    tool_call.function.name = expected_tool
    tool_call.function.arguments = json.dumps({'input': query})
    mock_create.return_value = MagicMock(
        choices=[MagicMock(message=MagicMock(tool_calls=[tool_call]))]
    )
    response = mock_create()
    actual = response.choices[0].message.tool_calls[0].function.name
    assert actual == expected_tool

Écrire des fonctions d'assertion personnalisées

À mesure que votre suite de tests d'agents s'agrandit, regroupez les schémas d'assertion courants dans des fonctions utilitaires. Les tests seront ainsi plus courts, plus lisibles et plus faciles à maintenir lorsque le format des réponses de l'agent changera.

import json

def assert_tool_called(response, tool_name: str, required_args: dict = None):
    message = response.choices[0].message
    assert message.tool_calls, 'Expected tool call but got plain text'
    names = [tc.function.name for tc in message.tool_calls]
    assert tool_name in names, f'Expected {tool_name}, got {names}'

    if required_args:
        for tc in message.tool_calls:
            if tc.function.name == tool_name:
                args = json.loads(tc.function.arguments)
                for key, val in required_args.items():
                    assert args.get(key) == val, \
                        f'Arg {key}: expected {val}, got {args.get(key)}'

# Clean test using the helper:
# assert_tool_called(response, 'get_weather', {'city': 'Paris'})

# --- demo ---
from unittest.mock import MagicMock

tool_call = MagicMock()
tool_call.function.name = 'get_weather'
tool_call.function.arguments = json.dumps({'city': 'Paris'})
response = MagicMock(choices=[MagicMock(message=MagicMock(tool_calls=[tool_call]))])

assert_tool_called(response, 'get_weather', {'city': 'Paris'})
print('assert_tool_called passed: agent called get_weather with city=Paris')

Vérification des connaissances : tests d'agents fondés sur des assertions

Testez votre compréhension des stratégies d'assertion pour les tests d'agents.

Récapitulatif : tests d'agents fondés sur des assertions

Vous disposez maintenant d'une boîte à outils complète d'assertions pour les tests d'agents :

  • Vérifiez que tool_calls n'est pas vide lorsque l'agent doit utiliser un outil
  • Vérifiez le nom correct de l'outil avec tc.function.name == 'expected_tool'
  • Validez les arguments de l'outil en analysant tc.function.arguments comme du JSON
  • Utilisez jsonschema.validate() pour valider les sorties structurées
  • Utilisez des vérifications de présence de mots-clés pour des assertions textuelles souples
  • Vérifiez finish_reason et le nombre d'étapes pour les agents en boucle
  • Utilisez @pytest.mark.parametrize pour plusieurs scénarios d'entrée

Questions Fréquemment Posées

La leçon « Tester les agents par assertions » est-elle gratuite ?

Oui — le texte complet de « Tester les agents par assertions » est gratuit à lire ici sur le web. Pour la pratiquer de manière interactive (un éditeur de code intégré et un tuteur IA 24/7) et déverrouiller le reste du cours AI Agents, passe à CoddyKit PRO. Le cours AI Agents comprend 4 leçons au total.

Qu'est-ce que j'apprendrai dans « Tester les agents par assertions » ?

Vérifiez les appels aux outils, les étapes intermédiaires et la structure de la sortie finale. Tu pratiques AI Agents avec du code pratique que tu exécutes directement dans le navigateur, et un tuteur IA 24/7 répond à tes questions au fur et à mesure que tu avances dans la leçon.

Dois-je avoir de l'expérience pour commencer AI Agents ?

Aucune expérience préalable n'est requise. AI Agents sur CoddyKit est structuré pour les débutants jusqu'aux apprenants avancés, donc tu peux commencer ici ou depuis le début et avancer à ton rythme. Ceci est la leçon 3 sur 4.

Combien de temps prend la leçon « Tester les agents par assertions » ?

La plupart des leçons CoddyKit prennent environ 5–10 minutes. Chacune est courte et interactive, tu progresses régulièrement et tu repiques exactement où tu t'es arrêté sur le web et l'app.

Peux-tu écrire et exécuter du code dans cette leçon AI Agents ?

Oui. Chaque leçon AI Agents inclut un éditeur de code intégré, tu écris et exécutes du vrai code directement dans ton navigateur et tu reçois des retours IA instantanés — aucune configuration locale requise.

Toutes les leçons de ce cours

  1. Pourquoi tester les agents est différent
  2. Simuler les appels aux LLM dans les tests
  3. Tester les agents par assertions
  4. Tests d’intégration pour les pipelines d’agents
← Retour à AI Agents