Idée en 30 secondes
Les unit tests pour agents IA valident la logique locale : choix d'outil, traitement de réponse, stop reason et format de sortie.
Leur valeur principale : ils sont rapides, déterministes et isolés, donc on voit tout de suite quelle partie précise du système est cassée.
Le problème
Sans unit tests, les équipes testent souvent les agents uniquement via des runs manuels ou des tests end-to-end lourds.
Cela crée des problèmes classiques :
- les erreurs de logique locale sont détectées trop tard ;
- il devient difficile de savoir si c'est le code ou une dépendance externe qui a cassé ;
- de petites régressions s'accumulent et arrivent en production.
Au final, même un changement simple peut déclencher une chaîne d'incidents opaques en production.
Quand l'utiliser
Il faut écrire des unit tests dès que vous avez une logique locale et vérifiable :
- choix d'outil selon le type de requête ;
- validation de la structure de sortie ;
- gestion des erreurs d'outils ;
- conditions de fin de run (
stop_reason) ; - règles de sécurité au niveau étape ou fonction.
Si le comportement peut être validé sans réseau et sans environnement agent complet, c'est un bon candidat pour un unit test.
Implémentation
En pratique, le unit testing d'agents repose sur une règle simple : un comportement, un test, des conditions contrôlées. Les exemples ci-dessous sont schématiques et non liés à un framework précis.
Le niveau unit n'est pas adapté pour mesurer la qualité globale des réponses, l'utilité du résultat final ou la "capacité" générale de l'agent. Pour cela, utilisez eval harness et golden datasets.
Comment cela fonctionne dans un test
Cycle court d'un unit test
- Test case - un comportement à vérifier.
- Setup - fakes, mocks et conditions figées.
- Run - exécuter une fonction ou une étape précise.
- Assertions - vérifier
tool choice,schema,stop reason.
1. Isoler la logique de décision de l'agent
def choose_tool(intent: str, tools_allowed: list[str]) -> str:
if intent == "price_lookup" and "crypto_price_api" in tools_allowed:
return "crypto_price_api"
return "web_search"
Moins une fonction a de dépendances latérales, plus le test est stable.
2. Remplacer les outils externes
class FakeTools:
def crypto_price_api(self, symbol: str):
return {"symbol": symbol, "price": 65000}
Un unit test doit vérifier la logique de l'agent, pas la disponibilité d'APIs externes.
3. Vérifier plus que le texte final
def test_tool_selection_and_schema():
tools = FakeTools()
agent = Agent(tools=tools)
result = agent.run("What is the price of BTC?")
assert result.selected_tool == "crypto_price_api"
assert isinstance(result.output, dict)
assert result.output["symbol"] == "BTC"
Mieux vaut verrouiller des invariants structurels (selected_tool, schema, stop reason), et pas seulement le texte final.
4. Tester les scénarios négatifs
def test_tool_error_is_handled():
tools = FailingTools()
agent = Agent(tools=tools)
result = agent.run("Find BTC price")
assert result.stop_reason == "tool_error_handled"
assert result.error is not None
Les erreurs d'outil doivent avoir un comportement prévisible et testable.
5. Intégrer les unit tests dans CI
name: unit-tests
on:
pull_request:
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.11"
- run: pip install -r requirements.txt
- run: pytest tests/unit -q
Si un test est lent ou instable, déplacez-le vers eval harness ou vers la couche d'intégration.
Erreurs typiques
Dépendance à de vraies APIs
Le test échoue non pas à cause de la logique agent, mais à cause du réseau ou d'un service externe.
Cause typique : absence de fakes ou mocks pour les outils.
Tester uniquement le texte final
Le test passe, mais ne garantit ni le bon choix d'outil ni le bon format de sortie.
Cause typique : pas de checks sur selected_tool, schema et stop reason.
Trop de logique dans un seul test
Un test vérifie plusieurs scénarios à la fois, et en cas d'échec il est difficile de savoir ce qui a cassé.
Cause typique : pas de règle "un test - un comportement".
Environnement de test instable
Même des unit tests corrects deviennent bruités si les dépendances, la configuration ou les remplacements d'outils changent entre runs.
Cause typique : les unit tests dépendent encore partiellement d'une runtime réelle ou d'appels externes.
Tentative de tout couvrir via des runs e2e
L'équipe écrit seulement de gros scénarios et saute les vérifications locales de base.
Cause typique : pas de séparation claire entre niveaux unit, eval et regression.
En bref
- Les unit tests d'agents valident une logique locale et déterministe.
- Remplacez les outils via fakes ou mocks pour retirer le bruit réseau.
- Verrouillez des checks structurels : tool choice, schema, stop reason.
- Les unit tests rapides doivent tourner sur chaque PR.
FAQ
Q: Les unit tests peuvent remplacer eval harness ?
R: Non. Les unit tests attrapent des pannes locales, alors que eval harness valide le comportement complet de l'agent sur des scénarios entiers.
Q: Faut-il connecter une vraie LLM dans les unit tests ?
R: Le moins possible. Au niveau unit, la logique déterministe avec fakes ou mocks et des conditions contrôlées fonctionne mieux.
Q: Que faut-il vérifier obligatoirement dans un unit test d'agent ?
R: Le choix d'outil, la structure de sortie, la gestion d'erreur et le stop reason dans les scénarios négatifs.
Q: Quand déplacer un test du niveau unit vers le niveau eval ?
R: Quand il dépend du comportement complet sur scénario, de métriques de qualité de réponse ou de comparaisons avec baseline.
Et ensuite
Après le niveau unit, ajoutez des validations de scénarios via Eval Harness, et maintenez un ensemble stable de cas via Golden Datasets.
Pour contrôler les changements entre versions, ajoutez Regression Testing. Pour analyser les incidents production, utilisez Replay and Debugging. Gardez la vue complète dans Testing Strategy.