Appels d'outils portables entre OpenAI, Claude et Gemini
Mappez les champs d'outils OpenAI, Claude et Gemini, validez les arguments avec JSON Schema et conservez une seule boucle d'outils entre fournisseurs via une passerelle.
L'appel d'outils portable signifie que vos agents définissent les outils une seule fois—généralement au format tools OpenAI Chat Completions—et peuvent invoquer ces outils via des modèles OpenAI, Claude, Gemini ou d'autres fournisseurs sans maintenir trois dialectes de schémas divergents dans le code applicatif. La voie fiable n'est pas d'« espérer que chaque SDK natif colle », mais de standardiser un contrat client, de valider les arguments dans votre runtime, et de router l'inférence via une passerelle compatible OpenAI qui mappe les différences fournisseurs en bordure.
Cet article se concentre sur les correspondances de champs, la boucle d'appel d'outils renvoyée et une matrice d'incompatibilité. La gouvernance MCP générale, l'authentification, les plateformes de journalisation et le routage d'outils multi-sauts appartiennent à des articles dédiés comme le routage des fournisseurs LLM par serveur MCP et le routage des outils d'agents avec une passerelle MCP.
Pourquoi les schémas d'outils deviennent un problème multi-fournisseurs
L'appel de fonctions crée une seconde surface API : l'interface d'outils. Cette interface doit survivre à toute marque de modèle. Les formats natifs diffèrent encore :
- OpenAI documente les outils,
tool_choiceet les messages d'appel d'outils dans son guide d'appel de fonctions. - L'API Messages native d'Anthropic utilise des blocs tool-use / tool-result ; son chemin de compatibilité avec le SDK OpenAI accepte les clients au format OpenAI pour l'évaluation, mais ne constitue pas la recommandation de production durable d'Anthropic pour la plupart des applications.
- Gemini documente le function calling dans les docs Google AI et offre aussi un endpoint compatible OpenAI.
Si chaque microservice agent parle un dialecte natif différent, la plateforme accumule N convertisseurs, N formats de logs et N modes d'échec. La portabilité commence en choisissant un dialecte pour le code produit.
Aide-mémoire de correspondance des champs
Gardez le code produit sur le dialecte OpenAI Chat Completions. Attendez-vous à ce que les passerelles ou adaptateurs effectuent approximativement les correspondances suivantes ; les noms varient selon les versions, vérifiez-les avant de dépendre des cas limites :
| Concept | OpenAI Chat Completions | Claude Messages (natif) | Gemini (appel de fonctions / compatibilité OpenAI) |
|---|---|---|---|
| Catalogue d'outils | tools[].type=function + function.{name,description,parameters} | tools[] avec name, description, input_schema | function_declarations / tools OpenAI sur l'endpoint compatible |
| Forcer ou automatique | tool_choice : none / auto / required / outil nommé | tool_choice avec des formes propres à Claude | tool_config fournisseur ou tool_choice OpenAI sur la compatibilité |
| Le modèle demande un appel | message.tool_calls[] avec id, function.name, function.arguments (JSON sérialisé) | blocs content[] de type=tool_use avec id, name, input (objet) | parties functionCall ou tool_calls au format OpenAI sur la compatibilité |
| L'application renvoie le résultat | message role=tool + tool_call_id + chaîne content | bloc tool_result référençant tool_use_id | parties de réponse de fonction / role=tool OpenAI sur la compatibilité |
| Appels parallèles | plusieurs entrées tool_calls | plusieurs blocs tool_use | plusieurs appels de fonctions lorsqu'ils sont activés |
| Schéma strict | strict / sorties structurées propres au fournisseur | sorties structurées natives ; la compatibilité OpenAI ignore strict | consultez la documentation Gemini actuelle ; ne supposez pas le mode strict OpenAI |
Règle pratique : normalisez vers OpenAI à l'entrée du code produit, même si la passerelle parle Claude ou Gemini nativement en amont. Les journaux et les outils d'évaluation comparent alors une seule forme.
JSON Schema qui reste portable
{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get the current weather for a city",
"parameters": {
"type": "object",
"properties": {
"city": {"type": "string", "minLength": 1},
"unit": {"type": "string", "enum": ["celsius", "fahrenheit"]}
},
"required": ["city"],
"additionalProperties": false
}
}
}
Gardez des noms stables, des descriptions précises et des enums petits. La portabilité meurt quand chaque expérience fournisseur renomme city en location « parce que le prompt se sentait mieux ».
Boucle client de référence avec validation réelle
json.loads seul n'est pas une validation. Utilisez une liste d'outils autorisés avec JSON Schema, ou Pydantic, avant tout effet de bord :
from openai import OpenAI
import json
from typing import Any, Callable
# pip install jsonschema
from jsonschema import Draft202012Validator
client = OpenAI(
api_key="GATEWAY_API_KEY",
base_url="https://your-gateway.example.com/v1",
)
TOOL_PARAMS = {
"get_weather": {
"type": "object",
"properties": {
"city": {"type": "string", "minLength": 1},
"unit": {"type": "string", "enum": ["celsius", "fahrenheit"]},
},
"required": ["city"],
"additionalProperties": False,
}
}
tools = [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get the current weather for a city",
"parameters": TOOL_PARAMS["get_weather"],
},
}
]
def get_weather(city: str, unit: str = "celsius") -> dict:
return {"city": city, "unit": unit, "temperature": 22}
HANDLERS: dict[str, Callable[..., dict]] = {
"get_weather": get_weather,
}
def parse_and_validate(name: str, raw_arguments: str) -> dict[str, Any]:
if name not in HANDLERS or name not in TOOL_PARAMS:
raise ValueError(f"tool_not_allowed:{name}")
try:
args = json.loads(raw_arguments or "{}")
except json.JSONDecodeError as exc:
raise ValueError(f"invalid_json:{name}") from exc
if not isinstance(args, dict):
raise ValueError(f"arguments_not_object:{name}")
Draft202012Validator(TOOL_PARAMS[name]).validate(args)
return args
MODEL = "claude-sonnet" # gateway alias; swap to gemini-flash or gpt-* without code rewrites
messages = [{"role": "user", "content": "Weather in Lyon in celsius?"}]
response = client.chat.completions.create(
model=MODEL,
messages=messages,
tools=tools,
tool_choice="auto",
)
msg = response.choices[0].message
messages.append(msg)
if msg.tool_calls:
for call in msg.tool_calls:
try:
args = parse_and_validate(call.function.name, call.function.arguments)
result = HANDLERS[call.function.name](**args)
except Exception as exc:
result = {"error": "tool_validation_failed", "detail": str(exc)}
messages.append(
{
"role": "tool",
"tool_call_id": call.id,
"content": json.dumps(result),
}
)
final = client.chat.completions.create(
model=MODEL,
messages=messages,
tools=tools,
)
print(final.choices[0].message.content)
Changez seulement l'alias du modèle pour essayer Gemini ou OpenAI. Gardez l'exécuteur et JSON Schema inchangés. C'est le principal gain de portabilité.
Extrait de réponse à vérifier dans les tests
Une première étape réussie doit présenter cette forme, avec des champs abrégés :
{
"choices": [
{
"message": {
"role": "assistant",
"content": null,
"tool_calls": [
{
"id": "call_123",
"type": "function",
"function": {
"name": "get_weather",
"arguments": "{\"city\":\"Lyon\",\"unit\":\"celsius\"}"
}
}
]
}
}
]
}
Votre banc de test doit vérifier que le nom de l'outil figure dans la liste d'autorisation, que arguments est une chaîne représentant un objet JSON, que le schéma est valide et que la seconde étape reçoit role=tool avec le même tool_call_id.
Matrice d'incompatibilité à tester, sans supposer
| Mode d'échec | Ce que vous observez | Traitement sûr |
|---|---|---|
| Arguments presque JSON | virgules finales, guillemets simples | rejeter ; renvoyer une erreur d'outil ; ne pas utiliser eval |
| Champ requis manquant | erreur de validation du schéma | renvoyer une erreur d'outil structurée ; laisser le modèle retenter une fois |
| Nom d'outil inconnu | le modèle invente getWeather | échec de la liste d'autorisation ; ne jamais exécuter |
arguments vide | "" ou {} alors que des champs sont requis | échec de validation |
| Dispersion parallèle | au moins 5 appels d'outils en un tour | limiter la concurrence, surtout pour les mutations |
tool_choice=required ignoré | réponse texte au lieu d'un appel | traiter comme un échec lorsque les effets de bord sont obligatoires |
strict=true sur la compatibilité Claude | ignoré silencieusement selon Anthropic | toujours valider localement |
| Arguments d'outil partiels en streaming | chaînes d'arguments fragmentées | mettre en tampon jusqu'à l'événement final |
| Le fournisseur refuse les outils | texte de sécurité ou de politique uniquement | échouer fermé ; ne pas inventer un appel dans le code |
| Retentative idempotente | le même create_ticket est exécuté deux fois | utiliser des clés de déduplication dans la couche d'outils |
Automatisez les contrôles de schéma. Gardez l'évaluation subjective de la qualité hors du verrou de portabilité.
Ce qui casse encore quand les fournisseurs changent
Validité des arguments
Les modèles peuvent émettre du quasi-JSON, de mauvais enums ou des champs requis manquants. Les systèmes portables valident avec le même JSON Schema avant les effets de bord. N'exécutez jamais un outil de paiement ou d'écriture sur des arguments non validés.
Appels d'outils parallèles
Certains modèles émettent plusieurs tool_calls en un tour. Votre runtime doit les exécuter en parallèle en sécurité ou les sérialiser avec un ordre clair.
Sémantique de tool_choice
auto, none et la sélection forcée de fonction ne sont pas identiquement implémentés partout. Si un workflow doit appeler un outil, affirmez ce comportement en CI avec l'alias modèle exact.
Outils structurés stricts vs texte libre
Un modèle peut répondre en langage naturel au lieu d'appeler l'outil. La politique produit doit décider si c'est acceptable. Pour le checkout ou les agents d'infra, traitez l'absence d'appel d'outil comme un échec.
Streaming + outils
Streamer des arguments d'outils partiels est plus dur que streamer de la prose. Vérifiez la combinaison fournisseur/passerelle.
Idempotence des effets de bord
Les retries après timeout peuvent ré-exécuter des outils. Les plateformes d'outils portables ont besoin de clés d'idempotence pour tout outil non lecture. La bascule fournisseur sans idempotence est comment vous double-facturez un client.
Responsabilités de la passerelle pour les outils portables
Une passerelle AI doit :
- accepter un seul dialecte d'outils en entrée, généralement les outils OpenAI
- mapper vers les amonts Claude, Gemini ou OpenAI sans obliger chaque application à apprendre les formes natives
- publier des alias de modèles stables pour que les bancs d'outils ne figent pas les identifiants d'instantané des fournisseurs
- émettre des journaux d'usage comprenant l'alias du modèle et la raison de fin des tours d'outils
- imposer les portées de clés afin qu'une clé de traitement par lots ne puisse pas appeler des agents riches en mutations
Le choix d'architecture, proxy ou passerelle, est couvert dans proxy LLM ou passerelle AI. La configuration propre à Claude et Gemini se trouve dans utiliser Claude avec le SDK OpenAI et utiliser Gemini avec le SDK OpenAI.
Matrice de portabilité en huit cas
Exécutez ces cas sur les alias OpenAI, Claude et Gemini avec le même client de passerelle :
- Aucun outil nécessaire, texte pur
- Un outil obligatoire avec des arguments valides
- Argument requis manquant
- Enum non valide
- Nom d'outil inconnu
- Deux appels d'outils parallèles, si le produit l'autorise
tool_choiceforcé pour un outil nommé- Retentative idempotente d'un outil de mutation
Pour les cas 3 à 5 et 8, le critère de réussite doit porter sur la validation ou le comportement de l'exécuteur, pas sur la qualité des excuses du modèle.
Règles de design pour garder les outils portables
- Un nom, un sens. Ne surchargez pas
searchentre systèmes sans rapport. - Préférez un JSON ennuyeux. Les unions imbriquées et les blobs free-text géants réduisent la fiabilité inter-modèles.
- Versionnez les outils explicitement.
create_ticket_v2vaut mieux que des changements de sens silencieux. - Séparez outils de retrieval et de mutation. Appliquez auth et idempotence plus strictes aux mutations.
- Bornez le fan-out. Des boucles d'outils récursives illimitées sont un incident de coût et de sécurité en attente d'une mise à jour de modèle.
- Journalisez les I/O d'outils avec rédaction. Vous ne pouvez pas déboguer la portabilité sans preuves, ni expédier des preuves qui contiennent des secrets.
Les conseils OWASP LLM sont pertinents ici : l'agency excessive et l'exposition de données sensibles apparaissent d'abord dans les systèmes outillés, pas dans le chat simple.
Test curl rapide
curl https://your-gateway.example.com/v1/chat/completions \
-H "Authorization: Bearer $GATEWAY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "gemini-flash",
"messages": [{"role": "user", "content": "What is the weather in Lyon?"}],
"tools": [{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get the current weather for a city",
"parameters": {
"type": "object",
"properties": {"city": {"type": "string"}},
"required": ["city"]
}
}
}],
"tool_choice": "auto"
}'
Inspectez choices[0].message.tool_calls. Répétez avec les alias Claude et OpenAI.
Sources officielles
- Guide OpenAI de l'appel de fonctions
- Compatibilité OpenAI SDK Anthropic
- Compatibilité OpenAI Gemini
- OWASP Top 10 for LLM Applications
Dernière vérification : 2026-07-15. Revérifiez tool_choice, les appels d'outils parallèles et la prise en charge des schémas stricts chez chaque fournisseur avant d'étendre les effets de bord des agents.
Questions fréquentes
Chaque équipe doit-elle apprendre le format d'outils natif de chaque fournisseur ?
Non pour le code applicatif produit. Gardez un schéma d'entrée—typiquement tools OpenAI—et isolez les particularités fournisseurs dans la passerelle ou une seule bibliothèque d'adaptateurs.
L'appel d'outils compatible OpenAI suffit-il pour des agents de production ?
C'est une stratégie forte de transport et de schéma, mais la production a encore besoin de validation, authZ, idempotence, budgets et journaux d'audit lisibles. La compatibilité ne remplace pas ces contrôles.
MCP peut-il remplacer le function calling portable ?
MCP est complémentaire : il standardise comment les outils sont exposés aux clients, tandis que les fournisseurs de modèles ont encore besoin d'un moyen de demander des invocations d'outils. Beaucoup de stacks utiliseront les deux.
Exécuter la matrice avant la bascule multi-fournisseurs
Prenez un schéma d'outil de production, exécutez la matrice de huit cas sur vos alias OpenAI, Claude et Gemini via /v1/chat/completions, puis corrigez les lacunes de validation avant d'activer la bascule multi-fournisseurs pour cet agent. Élargissez le trafic seulement ensuite.