Section 11.4.14 : Agents — Tool use et function calling
🎯 Objectif pédagogique
Maîtriser le function calling (tool use) d'OpenAI pour créer des agents qui appellent des fonctions Python réelles. Vous serez capable de connecter un LLM à des APIs, bases de données, et services externes.
Function Calling — Le LLM qui appelle vos fonctions
Jusqu'ici, nos outils étaient des mocks. Le function calling d'OpenAI permet au modèle de demander explicitement l'exécution de fonctions Python que VOUS définissez. Le LLM décide quoi appeler, avec quels paramètres — votre code l'exécute et renvoie le résultat.
Définir des outils (tools)
from openai import OpenAI
import json
client = OpenAI()
# Définition des outils au format OpenAI
tools = [
\{
"type": "function",
"function": \{
"name": "search_job_offers",
"description": "Recherche des offres d'emploi sur les sites carrière. Utilise cette fonction quand l'utilisateur demande de trouver des offres.",
"parameters": \{
"type": "object",
"properties": \{
"query": \{
"type": "string",
"description": "Termes de recherche (ex: 'Data Analyst Python')"
\},
"location": \{
"type": "string",
"description": "Ville ou région (ex: 'Paris', 'Remote')"
\},
"salary_min": \{
"type": "number",
"description": "Salaire minimum en K€"
\}
\},
"required": ["query"]
\}
\}
\},
\{
"type": "function",
"function": \{
"name": "analyze_cv_match",
"description": "Analyse la compatibilité entre un CV et une offre d'emploi.",
"parameters": \{
"type": "object",
"properties": \{
"cv_summary": \{
"type": "string",
"description": "Résumé du CV du candidat"
\},
"job_description": \{
"type": "string",
"description": "Description de l'offre d'emploi"
\}
\},
"required": ["cv_summary", "job_description"]
\}
\}
\},
\{
"type": "function",
"function": \{
"name": "send_application_email",
"description": "Prépare et envoie un email de candidature.",
"parameters": \{
"type": "object",
"properties": \{
"company": \{"type": "string", "description": "Nom de l'entreprise"\},
"position": \{"type": "string", "description": "Intitulé du poste"\},
"cover_letter": \{"type": "string", "description": "Lettre de motivation"\}
\},
"required": ["company", "position", "cover_letter"]
\}
\}
\}
]
Implémentation des fonctions réelles
def search_job_offers(query, location=None, salary_min=None):
"""Recherche d'offres (simulation — en vrai, appeler Indeed/LinkedIn API)."""
# En production : requests.get("https://api.indeed.com/...", params=\{...\})
offers = [
\{"title": "Data Analyst", "company": "TechCorp", "location": "Paris",
"salary": "48-55k€", "skills": ["Python", "SQL", "Tableau"]\},
\{"title": "Business Data Analyst", "company": "FinanceAI", "location": "Paris",
"salary": "50-60k€", "skills": ["Python", "SQL", "Excel", "Finance"]\},
]
if salary_min:
offers = [o for o in offers if int(o["salary"].split("-")[0]) >= salary_min]
return json.dumps(offers, ensure_ascii=False)
def analyze_cv_match(cv_summary, job_description):
"""Analyse CV vs offre (utilise le LLM en interne)."""
response = client.chat.completions.create(
model="gpt-4o-mini",
messages=[\{
"role": "user",
"content": f"Compare ce CV avec cette offre. Score de 1-10.\nCV: \{cv_summary\}\nOffre: \{job_description\}"
\}],
max_tokens=300
)
return response.choices[0].message.content
def send_application_email(company, position, cover_letter):
"""Simule l'envoi d'un email (en production : SMTP ou API email)."""
return json.dumps(\{
"status": "sent",
"to": f"recrutement@\{company.lower().replace(' ', '')\}.com",
"subject": f"Candidature - \{position\}",
"preview": cover_letter[:200]
\})
# Mapping nom → fonction
FUNCTION_MAP = \{
"search_job_offers": search_job_offers,
"analyze_cv_match": analyze_cv_match,
"send_application_email": send_application_email,
\}
Boucle d'agent avec function calling
def run_agent_with_tools(user_message):
"""Agent complet avec function calling OpenAI."""
messages = [
\{"role": "system", "content": """Tu es un agent de recherche d'emploi.
Tu as accès à des outils pour chercher des offres, analyser des CV, et envoyer des candidatures.
Utilise les outils quand c'est pertinent. Tu peux appeler plusieurs outils si nécessaire."""\},
\{"role": "user", "content": user_message\}
]
while True:
response = client.chat.completions.create(
model="gpt-4o",
messages=messages,
tools=tools,
tool_choice="auto" # Le modèle décide s'il utilise un outil
)
message = response.choices[0].message
messages.append(message)
# Si le modèle ne veut pas utiliser d'outil → réponse finale
if not message.tool_calls:
return message.content
# Exécuter chaque outil demandé
for tool_call in message.tool_calls:
func_name = tool_call.function.name
func_args = json.loads(tool_call.function.arguments)
print(f"🔧 Appel: \{func_name\}(\{func_args\})")
# Exécuter la fonction
func = FUNCTION_MAP[func_name]
result = func(**func_args)
print(f"📋 Résultat: \{result[:150]\}...")
# Renvoyer le résultat au modèle
messages.append(\{
"role": "tool",
"tool_call_id": tool_call.id,
"content": result
\})
# Test
response = run_agent_with_tools(
"Cherche des offres Data Analyst à Paris avec minimum 45k, "
"puis analyse la meilleure par rapport à mon profil (8 ans finance, Python, SQL)."
)
print("\n🤖 Réponse finale:", response)
Parallel function calling
# OpenAI peut appeler PLUSIEURS fonctions en parallèle !
# Exemple : "Cherche des offres à Paris ET à Lyon"
# → Le modèle retourne 2 tool_calls simultanés :
# - search_job_offers(query="Data Analyst", location="Paris")
# - search_job_offers(query="Data Analyst", location="Lyon")
# → Votre code les exécute tous les deux
# → Les résultats sont renvoyés ensemble
# Le code ci-dessus gère déjà ce cas grâce à la boucle :
# for tool_call in message.tool_calls: ← itère sur TOUS les appels
tool_choice : auto vs required vs none
auto : le modèle décide (recommandé). required : le modèle DOIT utiliser un outil (utile pour forcer une action). none : le modèle ne peut PAS utiliser d'outil (pour la réponse finale). \{"type": "function", "function": {"name": "search_job_offers"\}} : force un outil spécifique.
🏋️ Exercice pratique (30 minutes)
- →Définissez 3 outils pour votre agent (un de recherche, un d'analyse, un d'action)
- →Implémentez les fonctions Python correspondantes
- →Construisez la boucle agent avec function calling
- →Testez avec des requêtes nécessitant 1, 2, et 3 outils
- →Observez la trace : quels outils sont appelés et dans quel ordre ?
Quel est l'avantage du function calling vs parser manuellement la réponse du LLM ?
Que se passe-t-il quand le modèle décide qu'il n'a pas besoin d'outil ?