Function calling avancé : schémas et validation
Function calling avancé : schémas et validation
Le function calling est le mécanisme central qui permet à un agent de décider quand et comment appeler vos outils. Dans cette leçon, vous allez maîtriser les schémas avancés, la validation stricte, et les patterns de gestion d’erreurs.
Schémas JSON et strict mode
La Responses API utilise des schémas JSON pour décrire les paramètres de chaque tool. Le mode strict garantit que le modèle respecte exactement le schéma :
from agents import function_tool
from pydantic import BaseModel, Field
from typing import Literal
from enum import Enum
class Priorite(str, Enum):
BASSE = "basse"
MOYENNE = "moyenne"
HAUTE = "haute"
CRITIQUE = "critique"
class CreerTicket(BaseModel):
titre: str = Field(description="Titre court du ticket (max 100 caractères)")
description: str = Field(description="Description détaillée du problème")
priorite: Priorite = Field(description="Niveau de priorité du ticket")
composant: Literal["frontend", "backend", "infra", "data"] = Field(
description="Composant technique affecté"
)
tags: list[str] = Field(
default_factory=list,
description="Tags optionnels pour catégoriser le ticket"
)
@function_tool
def creer_ticket_support(ticket: CreerTicket) -> str:
"""Crée un nouveau ticket de support technique."""
# En production : appel à Jira, Linear, GitHub Issues, etc.
return f"Ticket créé : [{ticket.priorite.value}] {ticket.titre} ({ticket.composant})"
Avec le strict mode activé par défaut, le modèle ne pourra jamais envoyer une priorité invalide ou un composant hors de la liste.
Validation personnalisée avec Pydantic
Pydantic permet d’ajouter des validations métier directement dans le schéma :
from pydantic import BaseModel, Field, field_validator
class TransfertArgent(BaseModel):
compte_source: str = Field(description="IBAN du compte source")
compte_destination: str = Field(description="IBAN du compte destination")
montant: float = Field(gt=0, le=50000, description="Montant en euros (max 50 000)")
motif: str = Field(min_length=5, max_length=200, description="Motif du transfert")
@field_validator("compte_source", "compte_destination")
@classmethod
def valider_iban(cls, v):
if not v.startswith("FR"):
raise ValueError("Seuls les IBAN français sont acceptés")
if len(v) != 27:
raise ValueError("L'IBAN doit contenir 27 caractères")
return v
@function_tool
def effectuer_transfert(transfert: TransfertArgent) -> str:
"""Effectue un transfert bancaire entre deux comptes."""
return f"Transfert de {transfert.montant}€ initié de {transfert.compte_source[:8]}... vers {transfert.compte_destination[:8]}..."
Gérer les erreurs dans les tools
Quand un tool échoue, retournez un message d’erreur clair plutôt que de lever une exception :
import json
from agents import function_tool
@function_tool
def interroger_api_externe(endpoint: str, parametres: dict) -> str:
"""Interroge une API externe avec les paramètres spécifiés."""
try:
# En production : appel HTTP réel
import httpx
response = httpx.get(f"https://api.example.com/{endpoint}", params=parametres, timeout=10)
response.raise_for_status()
return json.dumps(response.json(), ensure_ascii=False)
except httpx.TimeoutException:
return json.dumps({
"erreur": True,
"message": f"Timeout lors de l'appel à {endpoint}. Réessayez dans quelques instants."
})
except httpx.HTTPStatusError as e:
return json.dumps({
"erreur": True,
"message": f"Erreur HTTP {e.response.status_code} sur {endpoint}.",
"details": str(e)
})
except Exception as e:
return json.dumps({
"erreur": True,
"message": f"Erreur inattendue : {str(e)}"
})
L’agent recevra le message d’erreur et pourra décider de réessayer, d’informer l’utilisateur, ou de tenter une autre approche.
Structured output pour les tools
Vous pouvez forcer l’agent à retourner sa réponse finale dans un format structuré avec output_type :
from pydantic import BaseModel
from agents import Agent, Runner
class ReponseAnalyse(BaseModel):
resume: str
points_cles: list[str]
recommandation: str
score_confiance: float
agent = Agent(
name="Analyste",
instructions="Analysez les données fournies et produisez un rapport structuré.",
model="gpt-5.3",
output_type=ReponseAnalyse,
)
result = Runner.run_sync(agent, "Analysez les tendances de vente du Q1 2026.")
# result.final_output est de type ReponseAnalyse
print(f"Résumé : {result.final_output.resume}")
print(f"Score : {result.final_output.score_confiance}")
Pattern : tools avec contexte partagé
Parfois vos tools ont besoin d’accéder à un état partagé (session utilisateur, connexion DB). Utilisez le contexte :
from dataclasses import dataclass
from agents import Agent, Runner, RunContextWrapper, function_tool
@dataclass
class ContexteApp:
user_id: str
db_connection: object # Votre connexion DB
api_key: str
@function_tool
def obtenir_profil_utilisateur(ctx: RunContextWrapper[ContexteApp]) -> str:
"""Récupère le profil de l'utilisateur courant."""
user_id = ctx.context.user_id
# Utiliser ctx.context.db_connection pour la requête
return f"Profil utilisateur {user_id} : Premium, actif depuis 2024"
agent = Agent(
name="Agent personnalisé",
instructions="Vous assistez l'utilisateur connecté.",
tools=[obtenir_profil_utilisateur],
)
contexte = ContexteApp(user_id="usr_123", db_connection=None, api_key="xxx")
result = Runner.run_sync(agent, "Quel est mon profil ?", context=contexte)
Points clés à retenir
- Les schémas Pydantic avec enums et Literal garantissent la validité des appels
- Les field validators ajoutent une validation métier en plus du typage
- Retournez des messages d’erreur JSON plutôt que de lever des exceptions dans les tools
output_typeforce l’agent à structurer sa réponse finale- Le contexte partagé (
RunContextWrapper) permet aux tools d’accéder à l’état de l’application