Function Calling : Comment les LLMs appellent des outils et APIs
Le function calling (ou tool use) est la capacité des LLMs modernes à appeler des fonctions et outils externes de manière structurée. C’est ce qui transforme un simple chatbot en assistant intelligent capable d’actions concrètes : interroger une base de données, appeler une API météo, effectuer des calculs, créer un ticket, envoyer un email, et bien plus.

Qu’est-ce que le Function Calling ?
Le problème sans function calling
Imaginons que vous demandiez à un LLM :
Quelle est la météo à Paris actuellement ?
Sans function calling, le LLM répondrait :
Je n'ai pas accès à des données météo en temps réel. Mes données s'arrêtent
en janvier 2025. Je vous suggère de consulter meteo.fr ou OpenWeatherMap.
Avec function calling, le flow est :
- L’utilisateur demande : “Quelle est la météo à Paris ?”
- Le LLM analyse : “Je dois appeler la fonction get_weather avec city=‘Paris’”
- Votre code exécute la fonction :
get_weather("Paris") - L’API retourne :
{"temp": 12, "condition": "Nuageux", "humidity": 75} - Le LLM génère une réponse naturelle : “Il fait actuellement 12°C à Paris avec un ciel nuageux. L’humidité est de 75%.”
Architecture du function calling
┌─────────────┐
│ Utilisateur │
│ "Météo Paris?" │
└───────┬───────┘
│
v
┌───────────────────┐
│ LLM │
│ Analyse requête │
│ Détermine fonction │
│ Extrait paramètres │
└───────┬───────────┘
│ function_call: {
│ name: "get_weather",
│ arguments: {"city": "Paris"}
│ }
v
┌───────────────────┐
│ Votre Code │
│ Exécute fonction │
│ get_weather("Paris")│
└───────┬───────────┘
│
v
┌───────────────────┐
│ API Météo │
│ Retourne JSON │
└───────┬───────────┘
│ {"temp": 12, ...}
v
┌───────────────────┐
│ LLM │
│ Génère réponse │
│ naturelle │
└───────┬───────────┘
│
v
┌─────────────────┐
│ "Il fait 12°C à │
│ Paris, nuageux" │
└─────────────────┘
OpenAI Functions (GPT-4, GPT-4o)
Définir une fonction
Les fonctions sont définies via un JSON Schema :
functions = [
{
"name": "get_weather",
"description": "Obtient la météo actuelle d'une ville",
"parameters": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "Nom de la ville (ex: Paris, London)"
},
"unit": {
"type": "string",
"enum": ["celsius", "fahrenheit"],
"description": "Unité de température"
}
},
"required": ["city"]
}
}
]
Composants clés :
name: Identifiant de la fonction (snake_case recommandé)description: Ce que fait la fonction (crucial pour que le LLM comprenne quand l’utiliser)parameters: Schéma JSON des paramètrestype: Type de chaque paramètre (string, number, boolean, object, array)description: Explication de chaque paramètreenum: Valeurs autorisées (optionnel)required: Liste des paramètres obligatoires
Appeler l’API avec functions
import openai
import json
client = openai.OpenAI(api_key="sk-...")
messages = [
{"role": "user", "content": "Quelle est la météo à Paris ?"}
]
# Premier appel : le LLM détermine quelle fonction appeler
response = client.chat.completions.create(
model="gpt-4o",
messages=messages,
functions=functions,
function_call="auto" # "auto", "none", ou {"name": "fonction_specifique"}
)
message = response.choices[0].message
# Vérifier si le LLM veut appeler une fonction
if message.function_call:
function_name = message.function_call.name
function_args = json.loads(message.function_call.arguments)
print(f"Fonction appelée : {function_name}")
print(f"Arguments : {function_args}")
# → Fonction appelée : get_weather
# → Arguments : {'city': 'Paris', 'unit': 'celsius'}
# Exécuter la fonction (votre code)
if function_name == "get_weather":
result = get_weather(**function_args) # Votre implémentation
# Ajouter le résultat aux messages
messages.append(message) # Appel de fonction du LLM
messages.append({
"role": "function",
"name": function_name,
"content": json.dumps(result)
})
# Second appel : le LLM génère la réponse finale
final_response = client.chat.completions.create(
model="gpt-4o",
messages=messages
)
print(final_response.choices[0].message.content)
# → "Il fait actuellement 12°C à Paris avec un ciel nuageux..."
Implémentation complète
import openai
import json
import requests
def get_weather(city: str, unit: str = "celsius") -> dict:
"""Récupère la météo via OpenWeatherMap API"""
api_key = "YOUR_OPENWEATHER_API_KEY"
base_url = "https://api.openweathermap.org/data/2.5/weather"
params = {
"q": city,
"appid": api_key,
"units": "metric" if unit == "celsius" else "imperial"
}
try:
response = requests.get(base_url, params=params)
response.raise_for_status()
data = response.json()
return {
"city": city,
"temperature": data["main"]["temp"],
"condition": data["weather"][0]["description"],
"humidity": data["main"]["humidity"],
"unit": "°C" if unit == "celsius" else "°F"
}
except Exception as e:
return {"error": str(e)}
def run_conversation(user_message: str):
"""Execute une conversation avec function calling"""
messages = [{"role": "user", "content": user_message}]
functions = [
{
"name": "get_weather",
"description": "Obtient la météo actuelle d'une ville",
"parameters": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "Nom de la ville"
},
"unit": {
"type": "string",
"enum": ["celsius", "fahrenheit"]
}
},
"required": ["city"]
}
}
]
# Appel initial
response = client.chat.completions.create(
model="gpt-4o",
messages=messages,
functions=functions
)
message = response.choices[0].message
# Si fonction appelée
if message.function_call:
function_name = message.function_call.name
function_args = json.loads(message.function_call.arguments)
# Exécuter la fonction
if function_name == "get_weather":
function_response = get_weather(**function_args)
# Ajouter aux messages
messages.append(message)
messages.append({
"role": "function",
"name": function_name,
"content": json.dumps(function_response)
})
# Réponse finale
final_response = client.chat.completions.create(
model="gpt-4o",
messages=messages
)
return final_response.choices[0].message.content
# Pas de fonction appelée, réponse directe
return message.content
# Utilisation
print(run_conversation("Quelle est la météo à Paris ?"))
Anthropic Tools (Claude)
Claude utilise une API légèrement différente appelée “Tools” :
Définir un tool
import anthropic
tools = [
{
"name": "get_weather",
"description": "Obtient la météo actuelle d'une ville. Utilisez cette fonction quand l'utilisateur demande la météo, la température, ou les conditions climatiques.",
"input_schema": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "Le nom de la ville (ex: Paris, Tokyo, New York)"
},
"unit": {
"type": "string",
"enum": ["celsius", "fahrenheit"],
"description": "L'unité de température souhaitée"
}
},
"required": ["city"]
}
}
]
Différence avec OpenAI : input_schema au lieu de parameters.
Appel avec Claude
client = anthropic.Anthropic(api_key="sk-ant-...")
message = client.messages.create(
model="claude-3-5-sonnet-20241022",
max_tokens=1024,
tools=tools,
messages=[
{"role": "user", "content": "Quelle température fait-il à Tokyo ?"}
]
)
# Vérifier si Claude veut utiliser un tool
if message.stop_reason == "tool_use":
tool_use = next(block for block in message.content if block.type == "tool_use")
function_name = tool_use.name
function_args = tool_use.input
print(f"Tool: {function_name}")
print(f"Args: {function_args}")
# → Tool: get_weather
# → Args: {'city': 'Tokyo', 'unit': 'celsius'}
# Exécuter la fonction
result = get_weather(**function_args)
# Continuer la conversation
response = client.messages.create(
model="claude-3-5-sonnet-20241022",
max_tokens=1024,
tools=tools,
messages=[
{"role": "user", "content": "Quelle température fait-il à Tokyo ?"},
{"role": "assistant", "content": message.content},
{
"role": "user",
"content": [
{
"type": "tool_result",
"tool_use_id": tool_use.id,
"content": json.dumps(result)
}
]
}
]
)
print(response.content[0].text)
Google Function Calling (Gemini)
Définir une function declaration
import google.generativeai as genai
tools = [
{
"function_declarations": [
{
"name": "get_weather",
"description": "Obtient la météo actuelle d'une ville",
"parameters": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "Nom de la ville"
},
"unit": {
"type": "string",
"enum": ["celsius", "fahrenheit"]
}
},
"required": ["city"]
}
}
]
}
]
genai.configure(api_key="YOUR_GOOGLE_API_KEY")
model = genai.GenerativeModel(
model_name="gemini-1.5-pro",
tools=tools
)
response = model.generate_content("Météo à Londres ?")
# Vérifier function call
for part in response.candidates[0].content.parts:
if fn := part.function_call:
print(f"Function: {fn.name}")
print(f"Args: {dict(fn.args)}")
# Exécuter
result = get_weather(**dict(fn.args))
# Retourner le résultat
response = model.generate_content([
response.candidates[0].content,
genai.protos.Content(
parts=[genai.protos.Part(
function_response=genai.protos.FunctionResponse(
name=fn.name,
response={"result": result}
)
)]
)
])
print(response.text)
Exemples pratiques avancés
Requête base de données
import sqlite3
def query_database(query_type: str, filters: dict) -> list:
"""Interroge la base de données clients"""
conn = sqlite3.connect("customers.db")
cursor = conn.cursor()
if query_type == "count_customers":
sql = "SELECT COUNT(*) FROM customers WHERE 1=1"
params = []
if "country" in filters:
sql += " AND country = ?"
params.append(filters["country"])
if "min_orders" in filters:
sql += " AND total_orders >= ?"
params.append(filters["min_orders"])
cursor.execute(sql, params)
count = cursor.fetchone()[0]
return {"count": count, "filters": filters}
elif query_type == "top_customers":
limit = filters.get("limit", 10)
sql = f"""
SELECT name, email, total_orders, total_spent
FROM customers
ORDER BY total_spent DESC
LIMIT ?
"""
cursor.execute(sql, [limit])
results = []
for row in cursor.fetchall():
results.append({
"name": row[0],
"email": row[1],
"orders": row[2],
"spent": row[3]
})
return {"customers": results}
conn.close()
# Définition de la fonction
database_function = {
"name": "query_database",
"description": "Interroge la base de données clients pour obtenir des statistiques ou listes de clients",
"parameters": {
"type": "object",
"properties": {
"query_type": {
"type": "string",
"enum": ["count_customers", "top_customers", "customer_details"],
"description": "Le type de requête à effectuer"
},
"filters": {
"type": "object",
"description": "Filtres optionnels",
"properties": {
"country": {"type": "string"},
"min_orders": {"type": "integer"},
"limit": {"type": "integer"}
}
}
},
"required": ["query_type"]
}
}
# Utilisation
user_query = "Combien de clients français ont fait plus de 10 commandes ?"
# → Le LLM appellerait : query_database("count_customers", {"country": "France", "min_orders": 10})
Calculs mathématiques complexes
import sympy as sp
def calculate(expression: str, operation: str = "evaluate") -> dict:
"""
Effectue des calculs mathématiques avancés.
operation peut être:
- "evaluate" : Évalue l'expression
- "simplify" : Simplifie
- "solve" : Résout une équation
- "derivative" : Calcule la dérivée
- "integral" : Calcule l'intégrale
"""
try:
if operation == "evaluate":
result = sp.sympify(expression).evalf()
return {"result": float(result), "expression": expression}
elif operation == "simplify":
result = sp.simplify(expression)
return {"simplified": str(result), "original": expression}
elif operation == "solve":
x = sp.Symbol('x')
equation = sp.sympify(expression)
solutions = sp.solve(equation, x)
return {"solutions": [float(sol) for sol in solutions]}
elif operation == "derivative":
x = sp.Symbol('x')
expr = sp.sympify(expression)
derivative = sp.diff(expr, x)
return {"derivative": str(derivative), "function": expression}
elif operation == "integral":
x = sp.Symbol('x')
expr = sp.sympify(expression)
integral = sp.integrate(expr, x)
return {"integral": str(integral), "function": expression}
except Exception as e:
return {"error": str(e)}
calculate_function = {
"name": "calculate",
"description": "Effectue des calculs mathématiques avancés : évaluation, simplification, résolution d'équations, dérivées, intégrales",
"parameters": {
"type": "object",
"properties": {
"expression": {
"type": "string",
"description": "Expression mathématique (ex: '2*x + 3', 'sin(x)*cos(x)', 'x**2 - 4')"
},
"operation": {
"type": "string",
"enum": ["evaluate", "simplify", "solve", "derivative", "integral"],
"description": "Opération à effectuer"
}
},
"required": ["expression"]
}
}
# Exemples d'utilisation via LLM :
# "Calcule la dérivée de x**2 + 3*x + 2"
# → calculate("x**2 + 3*x + 2", "derivative")
# Résultat : 2*x + 3
# "Résous l'équation x**2 - 5*x + 6 = 0"
# → calculate("x**2 - 5*x + 6", "solve")
# Résultat : [2.0, 3.0]
Intégration API multiple
import requests
from datetime import datetime
def search_flights(origin: str, destination: str, date: str, passengers: int = 1) -> list:
"""Recherche de vols (exemple avec API fictive)"""
# En production, appel réel à une API comme Skyscanner, Amadeus, etc.
api_url = "https://api.flights.example.com/search"
response = requests.get(api_url, params={
"from": origin,
"to": destination,
"date": date,
"passengers": passengers
})
return response.json()["flights"]
def book_flight(flight_id: str, passenger_details: dict) -> dict:
"""Réservation d'un vol"""
api_url = "https://api.flights.example.com/book"
response = requests.post(api_url, json={
"flight_id": flight_id,
"passenger": passenger_details
})
return response.json()
def send_confirmation_email(email: str, booking_ref: str, details: dict) -> bool:
"""Envoie email de confirmation"""
# Implémentation avec SendGrid, SES, etc.
pass
# Définitions des fonctions
travel_functions = [
{
"name": "search_flights",
"description": "Recherche des vols disponibles entre deux villes",
"parameters": {
"type": "object",
"properties": {
"origin": {"type": "string", "description": "Ville de départ (code IATA ou nom)"},
"destination": {"type": "string", "description": "Ville d'arrivée"},
"date": {"type": "string", "description": "Date de départ (YYYY-MM-DD)"},
"passengers": {"type": "integer", "description": "Nombre de passagers", "default": 1}
},
"required": ["origin", "destination", "date"]
}
},
{
"name": "book_flight",
"description": "Réserve un vol spécifique",
"parameters": {
"type": "object",
"properties": {
"flight_id": {"type": "string"},
"passenger_details": {
"type": "object",
"properties": {
"name": {"type": "string"},
"email": {"type": "string"},
"passport": {"type": "string"}
},
"required": ["name", "email"]
}
},
"required": ["flight_id", "passenger_details"]
}
},
{
"name": "send_confirmation_email",
"description": "Envoie un email de confirmation de réservation",
"parameters": {
"type": "object",
"properties": {
"email": {"type": "string"},
"booking_ref": {"type": "string"},
"details": {"type": "object"}
},
"required": ["email", "booking_ref", "details"]
}
}
]
# Conversation multi-étapes
# User: "Je veux réserver un vol Paris → New York pour le 15 juin, 2 personnes"
# → LLM appelle : search_flights("Paris", "New York", "2025-06-15", 2)
# → Résultat : Liste de vols avec IDs
# LLM: "J'ai trouvé 5 vols. Le moins cher est à 450€ avec Air France..."
# User: "Réserve le vol AF123"
# → LLM appelle : book_flight("AF123", {...})
# → Puis : send_confirmation_email(...)
Parallel function calling
Les modèles récents (GPT-4o, Claude 3.5) peuvent appeler plusieurs fonctions en parallèle :
# User: "Quelle est la météo à Paris, Londres et Berlin ?"
# Le LLM peut retourner 3 appels simultanés :
function_calls = [
{"name": "get_weather", "arguments": {"city": "Paris"}},
{"name": "get_weather", "arguments": {"city": "Londres"}},
{"name": "get_weather", "arguments": {"city": "Berlin"}}
]
# Votre code exécute en parallèle
import concurrent.futures
with concurrent.futures.ThreadPoolExecutor() as executor:
futures = [
executor.submit(get_weather, call["arguments"]["city"])
for call in function_calls
]
results = [f.result() for f in futures]
# Retourner tous les résultats au LLM
# Le LLM synthétise : "À Paris il fait 12°C, à Londres 10°C, et à Berlin 8°C..."
💡 Le parallel function calling réduit la latence de moitié pour les requêtes multi-outils ! Au lieu de 3 appels séquentiels LLM, vous n’en faites que 2 (1 pour décider, 1 pour synthétiser).
Gestion d’erreurs et robustesse
Validation des paramètres
from pydantic import BaseModel, validator
class WeatherParams(BaseModel):
city: str
unit: str = "celsius"
@validator('city')
def city_not_empty(cls, v):
if not v or not v.strip():
raise ValueError('City cannot be empty')
return v.strip().title()
@validator('unit')
def unit_valid(cls, v):
if v not in ['celsius', 'fahrenheit']:
raise ValueError('Unit must be celsius or fahrenheit')
return v
def get_weather_safe(city: str, unit: str = "celsius") -> dict:
"""Version sécurisée avec validation"""
try:
params = WeatherParams(city=city, unit=unit)
# Appel API avec params validés
return call_weather_api(params.city, params.unit)
except ValueError as e:
return {"error": f"Invalid parameters: {str(e)}"}
except requests.RequestException as e:
return {"error": f"API error: {str(e)}"}
Retry et fallbacks
from tenacity import retry, stop_after_attempt, wait_exponential
@retry(
stop=stop_after_attempt(3),
wait=wait_exponential(multiplier=1, min=2, max=10)
)
def get_weather_with_retry(city: str, unit: str = "celsius") -> dict:
"""Retry automatique en cas d'erreur réseau"""
response = requests.get(weather_api_url, params={"city": city, "unit": unit})
response.raise_for_status()
return response.json()
def get_weather_with_fallback(city: str, unit: str = "celsius") -> dict:
"""Fallback sur API secondaire si primaire échoue"""
try:
return get_weather_primary_api(city, unit)
except:
try:
return get_weather_backup_api(city, unit)
except:
return {
"error": "Weather data unavailable",
"message": "Please try again later"
}
Error handling dans le flow
def safe_function_execution(function_name: str, args: dict) -> dict:
"""Wrapper sécurisé pour exécution de fonctions"""
available_functions = {
"get_weather": get_weather_safe,
"query_database": query_database_safe,
"calculate": calculate_safe
}
if function_name not in available_functions:
return {
"error": f"Function '{function_name}' not found",
"available": list(available_functions.keys())
}
try:
func = available_functions[function_name]
result = func(**args)
return {"success": True, "data": result}
except TypeError as e:
return {
"error": "Invalid arguments",
"message": str(e),
"received": args
}
except Exception as e:
return {
"error": "Execution failed",
"message": str(e),
"function": function_name
}
Sécurité et best practices
Validation stricte des inputs
🔒 JAMAIS de confiance aveugle ! Le LLM peut générer des paramètres malveillants (injection SQL, path traversal). Validez TOUJOURS avant d’exécuter.
def query_database_safe(query_type: str, filters: dict) -> dict:
"""Version sécurisée avec whitelist"""
# Whitelist des query types autorisés
ALLOWED_QUERIES = ["count_customers", "top_customers"]
if query_type not in ALLOWED_QUERIES:
return {"error": "Query type not allowed"}
# Whitelist des filtres
ALLOWED_FILTERS = ["country", "min_orders", "limit"]
invalid_filters = set(filters.keys()) - set(ALLOWED_FILTERS)
if invalid_filters:
return {"error": f"Invalid filters: {invalid_filters}"}
# Sanitize string inputs
if "country" in filters:
# Accepter seulement alphanumériques et espaces
if not re.match(r'^[a-zA-Z\s]+$', filters["country"]):
return {"error": "Invalid country format"}
# Limiter la taille des résultats
if "limit" in filters:
filters["limit"] = min(int(filters["limit"]), 100) # Max 100
# Exécution sécurisée
return execute_query(query_type, filters)
Sandboxing pour code execution
import subprocess
import tempfile
import os
def execute_python_code(code: str, timeout: int = 5) -> dict:
"""Exécute du code Python dans un environnement isolé"""
# Vérifications basiques
dangerous_keywords = ['import os', 'import subprocess', 'eval', 'exec', '__import__']
if any(kw in code for kw in dangerous_keywords):
return {"error": "Dangerous code detected"}
# Créer fichier temporaire
with tempfile.NamedTemporaryFile(mode='w', suffix='.py', delete=False) as f:
f.write(code)
temp_file = f.name
try:
# Exécuter dans subprocess avec timeout
result = subprocess.run(
['python', temp_file],
capture_output=True,
text=True,
timeout=timeout,
# Limiter les ressources (sur Linux)
# preexec_fn=lambda: resource.setrlimit(resource.RLIMIT_AS, (100*1024*1024, 100*1024*1024))
)
return {
"stdout": result.stdout,
"stderr": result.stderr,
"returncode": result.returncode
}
except subprocess.TimeoutExpired:
return {"error": "Execution timeout"}
except Exception as e:
return {"error": str(e)}
finally:
os.unlink(temp_file)
Rate limiting et cost control
from collections import defaultdict
from datetime import datetime, timedelta
class FunctionCallLimiter:
def __init__(self):
self.calls = defaultdict(list)
self.limits = {
"get_weather": {"per_hour": 100, "cost_per_call": 0.001},
"query_database": {"per_hour": 50, "cost_per_call": 0.01},
"send_email": {"per_hour": 10, "cost_per_call": 0.05}
}
def can_call(self, function_name: str, user_id: str) -> tuple[bool, str]:
"""Vérifie si l'appel est autorisé"""
if function_name not in self.limits:
return False, "Unknown function"
limit_config = self.limits[function_name]
key = f"{user_id}:{function_name}"
# Nettoyer les anciens appels (>1h)
cutoff = datetime.now() - timedelta(hours=1)
self.calls[key] = [t for t in self.calls[key] if t > cutoff]
# Vérifier limite
if len(self.calls[key]) >= limit_config["per_hour"]:
return False, f"Rate limit exceeded for {function_name}"
# Enregistrer appel
self.calls[key].append(datetime.now())
return True, "OK"
def get_cost(self, function_name: str) -> float:
"""Retourne le coût estimé de l'appel"""
return self.limits.get(function_name, {}).get("cost_per_call", 0)
# Utilisation
limiter = FunctionCallLimiter()
def execute_function_with_limits(function_name: str, args: dict, user_id: str):
can_call, message = limiter.can_call(function_name, user_id)
if not can_call:
return {"error": message}
cost = limiter.get_cost(function_name)
print(f"Executing {function_name} (cost: ${cost})")
return execute_function(function_name, args)
Audit logging
import logging
from datetime import datetime
# Configuration du logger
logging.basicConfig(
filename='function_calls.log',
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s'
)
def log_function_call(function_name: str, args: dict, result: dict, user_id: str, duration_ms: float):
"""Enregistre tous les appels de fonction"""
log_entry = {
"timestamp": datetime.now().isoformat(),
"user_id": user_id,
"function": function_name,
"arguments": args,
"success": "error" not in result,
"duration_ms": duration_ms
}
if "error" in result:
log_entry["error"] = result["error"]
logging.warning(f"Function call failed: {log_entry}")
else:
logging.info(f"Function call succeeded: {log_entry}")
# Optionnel : envoyer à un service de monitoring (Datadog, Sentry, etc.)
# send_to_monitoring_service(log_entry)
return log_entry
# Wrapper avec logging
import time
def execute_with_logging(function_name: str, args: dict, user_id: str):
start_time = time.time()
try:
result = execute_function(function_name, args)
duration_ms = (time.time() - start_time) * 1000
log_function_call(function_name, args, result, user_id, duration_ms)
return result
except Exception as e:
duration_ms = (time.time() - start_time) * 1000
error_result = {"error": str(e)}
log_function_call(function_name, args, error_result, user_id, duration_ms)
return error_result
Use cases en production
Chatbot support client
support_functions = [
{
"name": "search_knowledge_base",
"description": "Recherche dans la base de connaissances",
"parameters": {...}
},
{
"name": "create_ticket",
"description": "Crée un ticket support si problème non résolu",
"parameters": {...}
},
{
"name": "check_order_status",
"description": "Vérifie le statut d'une commande",
"parameters": {...}
},
{
"name": "escalate_to_human",
"description": "Transfère à un agent humain",
"parameters": {...}
}
]
Assistant de données
data_functions = [
{
"name": "query_sql",
"description": "Exécute requête SQL (read-only)",
"parameters": {...}
},
{
"name": "generate_chart",
"description": "Génère un graphique",
"parameters": {...}
},
{
"name": "export_csv",
"description": "Exporte les données en CSV",
"parameters": {...}
}
]
Conclusion
Le function calling est la pierre angulaire des agents IA modernes. Il transforme les LLMs de simples générateurs de texte en assistants capables d’actions concrètes. Les clés du succès :
✅ Descriptions claires : Le LLM doit comprendre QUAND utiliser chaque fonction
✅ Validation stricte : Ne jamais faire confiance aveuglément aux paramètres générés
✅ Gestion d’erreurs : Retry, fallbacks, messages d’erreur utiles
✅ Sécurité : Sandboxing, rate limiting, audit logging
✅ Monitoring : Tracer chaque appel pour détecter problèmes et optimiser coûts
Checklist de déploiement
Avant de mettre en production :
- Toutes les fonctions ont des descriptions précises
- Validation Pydantic ou équivalent sur tous les paramètres
- Rate limiting implémenté par fonction et par utilisateur
- Audit logging de tous les appels (succès + échecs)
- Gestion d’erreurs avec messages utilisateur clairs
- Tests unitaires pour chaque fonction
- Tests d’intégration avec le LLM
- Monitoring des coûts (appels API + LLM)
- Documentation pour l’équipe
- Plan de rollback si problème
Pour aller plus loin :
- Découvrez les Agents LangChain qui utilisent function calling
- Explorez le Prompt Engineering pour optimiser vos descriptions
- Maîtrisez LangChain Introduction pour automatiser les workflows
- Comparez les Modèles LLM pour choisir le meilleur
- Apprenez le RAG pour combiner function calling et knowledge base
- Comprenez les Tokens pour optimiser les coûts
- Découvrez les Transformers qui alimentent les LLMs
Le function calling ouvre des possibilités infinies : API, bases de données, outils métier, IoT… L’imagination est la seule limite !