Appels d'outils Function calling Agents Multi-fournisseurs Compatible OpenAI

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.

AveMujica API 8 min de lecture

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_choice et 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 :

ConceptOpenAI Chat CompletionsClaude Messages (natif)Gemini (appel de fonctions / compatibilité OpenAI)
Catalogue d'outilstools[].type=function + function.{name,description,parameters}tools[] avec name, description, input_schemafunction_declarations / tools OpenAI sur l'endpoint compatible
Forcer ou automatiquetool_choice : none / auto / required / outil nommétool_choice avec des formes propres à Claudetool_config fournisseur ou tool_choice OpenAI sur la compatibilité
Le modèle demande un appelmessage.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ésultatmessage role=tool + tool_call_id + chaîne contentbloc tool_result référençant tool_use_idparties de réponse de fonction / role=tool OpenAI sur la compatibilité
Appels parallèlesplusieurs entrées tool_callsplusieurs blocs tool_useplusieurs appels de fonctions lorsqu'ils sont activés
Schéma strictstrict / sorties structurées propres au fournisseursorties structurées natives ; la compatibilité OpenAI ignore strictconsultez 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'échecCe que vous observezTraitement sûr
Arguments presque JSONvirgules finales, guillemets simplesrejeter ; renvoyer une erreur d'outil ; ne pas utiliser eval
Champ requis manquanterreur de validation du schémarenvoyer une erreur d'outil structurée ; laisser le modèle retenter une fois
Nom d'outil inconnule 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èleau moins 5 appels d'outils en un tourlimiter la concurrence, surtout pour les mutations
tool_choice=required ignoréréponse texte au lieu d'un appeltraiter comme un échec lorsque les effets de bord sont obligatoires
strict=true sur la compatibilité Claudeignoré silencieusement selon Anthropictoujours valider localement
Arguments d'outil partiels en streamingchaînes d'arguments fragmentéesmettre en tampon jusqu'à l'événement final
Le fournisseur refuse les outilstexte de sécurité ou de politique uniquementéchouer fermé ; ne pas inventer un appel dans le code
Retentative idempotentele même create_ticket est exécuté deux foisutiliser 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 :

  1. Aucun outil nécessaire, texte pur
  2. Un outil obligatoire avec des arguments valides
  3. Argument requis manquant
  4. Enum non valide
  5. Nom d'outil inconnu
  6. Deux appels d'outils parallèles, si le produit l'autorise
  7. tool_choice forcé pour un outil nommé
  8. 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 search entre 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_v2 vaut 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

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.