Test degli agenti basati su asserzioni
Verifica delle chiamate agli strumenti, dei passaggi intermedi e della struttura dell’output finale
Test degli agenti basati su asserzioni è una lezione AI Agents gratuita su CoddyKit. Questa è la lezione 3 di 4. Puoi leggere la lezione completa qui gratuitamente — poi esercitati direttamente nel browser con un editor di codice integrato e un tutor IA disponibile 24/7. Fa parte del percorso di apprendimento AI Agents, e i tuoi progressi si sincronizzano tra il web e l'app CoddyKit. Il corso AI Agents include 4 lezioni in totale.
Oltre il confronto esatto delle stringhe
Poiché gli output degli LLM sono non deterministici, testarli con assert response == 'exact text' è fragile. Scriva invece asserzioni che verifichino la struttura e l'intento della risposta senza dipendere dalla formulazione esatta.
Verificare che siano state effettuate chiamate ai tool
Per gli agenti che utilizzano il function calling, l'asserzione più affidabile consiste nel verificare che l'agente abbia scelto di chiamare il tool corretto. Si tratta di un controllo strutturale, che non dipende dalla formulazione esatta del ragionamento dell'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')
Verificare il nome corretto del tool
Oltre a controllare che esistano chiamate ai tool, verifichi che il nome specifico del tool corrisponda a quello previsto. In questo modo è possibile rilevare i casi in cui l'agente sceglie il tool sbagliato per una determinata richiesta.
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')Verificare gli argomenti del tool
Dopo aver verificato il nome del tool, controlli che gli argomenti siano corretti. L'agente non deve solo scegliere il tool giusto, ma anche valorizzarlo con i parametri corretti ricavati dalla richiesta dell'utente.
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')
Convalidare l'output con uno schema JSON
Quando l'agente restituisce JSON strutturato, convalidi l'output rispetto a uno schema JSON per assicurarsi che tutti i campi obbligatori siano presenti e abbiano i tipi corretti. La libreria jsonschema semplifica questa operazione.
# 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}')Asserzioni sulla presenza di parole chiave
Per le risposte testuali la cui formulazione esatta può variare, verifichi che nell'output compaiano concetti o parole chiave. È un controllo flessibile ma comunque significativo: la risposta dell'agente deve almeno menzionare i termini pertinenti.
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!') # passesVerificare il formato della risposta: controlli dei tipi
Le asserzioni sui tipi sono rapide e affidabili. Verifichi che l'agente restituisca un dict, non None, che i campi di tipo lista siano effettivamente liste e che i campi numerici rientrino negli intervalli validi.
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')Verificare finish_reason
Il campo finish_reason indica perché il modello ha smesso di generare output. Verificarlo aiuta a rilevare i problemi: 'stop' indica una risposta completata correttamente, 'tool_calls' indica che l'agente vuole chiamare un tool, mentre 'length' indica un troncamento.
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')Verificare il numero di passaggi di un ciclo
Un agente che viene eseguito in un ciclo dovrebbe completare il proprio lavoro in un numero ragionevole di passaggi. Verifichi che l'agente termini entro un numero massimo di iterazioni: in questo modo è possibile rilevare i cicli infiniti che il controllo max_iterations dovrebbe impedire.
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')Parametrizzare i test per più input
@pytest.mark.parametrize di pytest consente di eseguire lo stesso test con molti input diversi. È ideale per verificare che l'agente indirizzi i diversi tipi di richieste verso i tool corretti.
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_toolScrivere helper di asserzione personalizzati
Man mano che la suite di test dell'agente cresce, estragga i pattern di asserzione comuni in appositi helper. In questo modo i test diventano più brevi, leggibili e facili da mantenere quando cambia il formato delle risposte dell'agente.
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')
Verifica delle conoscenze: test degli agenti basati sulle asserzioni
Verifichi la propria comprensione delle strategie di asserzione per i test degli agenti.
Riepilogo: test degli agenti basati sulle asserzioni
Ora dispone di un set completo di strumenti per le asserzioni nei test degli agenti:
- Verifichi che
tool_callsnon sia vuoto quando l'agente dovrebbe utilizzare un tool - Verifichi il nome corretto del tool con
tc.function.name == 'expected_tool' - Convalidi gli argomenti del tool analizzando
tc.function.argumentscome JSON - Utilizzi
jsonschema.validate()per convalidare gli output strutturati - Utilizzi controlli sulla presenza di parole chiave per asserzioni testuali flessibili
- Controlli
finish_reasone il numero di passaggi per gli agenti basati su cicli - Utilizzi
@pytest.mark.parametrizeper scenari con input multipli
Domande Frequenti
La lezione «Test degli agenti basati su asserzioni» è gratuita?
Sì — il testo completo di «Test degli agenti basati su asserzioni» è gratuito qui sul web. Per esercitarvi in modo interattivo (un editor di codice integrato e un tutor IA 24/7) e sbloccare il resto del corso AI Agents, passa a CoddyKit PRO. Il corso AI Agents include 4 lezioni in totale.
Cosa imparerò in «Test degli agenti basati su asserzioni»?
Verifica delle chiamate agli strumenti, dei passaggi intermedi e della struttura dell’output finale Eserciti AI Agents con codice pratico che esegui direttamente nel browser, e un tutor IA 24/7 risponde alle tue domande mentre lavori sulla lezione.
Ho bisogno di esperienza per iniziare AI Agents?
Non è richiesta alcuna esperienza precedente. AI Agents su CoddyKit è strutturato per principianti e studenti avanzati, quindi puoi iniziare da qui o dall'inizio e procedere al tuo ritmo. Questa è la lezione 3 di 4.
Quanto tempo richiede la lezione «Test degli agenti basati su asserzioni»?
La maggior parte delle lezioni CoddyKit richiede circa 5–10 minuti. Ogni lezione è breve e interattiva, quindi fai progressi costanti e riprendi esattamente da dove hai lasciato su web e app.
Posso scrivere ed eseguire codice in questa lezione AI Agents?
Sì. Ogni lezione AI Agents include un editor di codice integrato, quindi scrivi ed esegui codice reale direttamente nel tuo browser e ricevi feedback istantaneo dall'IA — nessuna configurazione locale necessaria.
Tutte le lezioni di questo corso
- Perché testare gli agenti è diverso
- Mock delle chiamate LLM nei test
- Test degli agenti basati su asserzioni
- Test di integrazione per le pipeline degli agenti