Actualités

Un harnais, trois modèles ouverts : ce qui change d'un modèle à l'autre

Gemma 4, Qwen et Mistral Small tournent derrière la même API, et pourtant chacun demande ses propres réglages. Ce que la Plateforme Bayes adapte modèle par modèle, ce qu'elle applique à tous, et ce qu'elle tient à l'écart des agents.

Sur la Plateforme Bayes, un agent ne sait jamais quel modèle est utilisé. Gemma 4 31B, Qwen3.8-27B et Mistral Small 3.2 tournent sur nos propres GPU, derrière la même API compatible OpenAI, servie par vLLM. Ce qui les distingue (les réglages de génération, les fichiers qu’ils acceptent) tient dans une classe par modèle, d’une vingtaine de lignes chacune. Les garde-fous dont tous les modèles ont besoin sont, eux, communs, dans le code partagé. Quand le modèle change, les agents gardent leurs outils, leurs sources et leurs règles.

Cet article détaille comment nous intégrons un nouveau modèle : ce qui lui est propre, ce qu’il reçoit du code commun, et comment nous vérifions que rien ne casse.

Les couches entre un agent et un modèleAgentsoutils, sources, règles : les mêmes quel que soit le modèleCommun à tous les modèlesCode partagé : les garde-fousarguments d'outil réparés, appels écrits en texte récupérés,texte répété supprimé, erreurs remontées, schémas strictsPropre à chaque modèleGemma 4réglages + overrideQwenréglages de générationMistral Smallréglages + overridevLLMla même API compatible OpenAI pour tous les modèles

Pourquoi une seule API ne suffit-elle pas ?

vLLM donne à chaque modèle le même point d’entrée de chat. Le transport est donc réglé, mais pas le comportement. Chaque modèle a été entraîné avec ses propres conventions, et ses auteurs publient les réglages avec lesquels ils obtiennent les meilleurs résultats. Un mauvais réglage est un échec silencieux : l’appel réussit, aucune erreur technique ne remonte, et l’écart ne se voit que dans le contenu des réponses.

Ce qui diffère Gemma 4 31B Qwen3.8-27B Mistral Small 3.2
Température par défaut (recommandée par les auteurs) 1 0,7 0,15
Une pièce jointe PDF Images des pages Images des pages Non acceptée en sortie structurée

La température suit la recommandation des auteurs de chaque modèle. Le traitement des PDF est un choix de notre côté, modèle par modèle, selon les performances mesurées de chacun.

À quoi ressemble la déclaration d’un modèle dans le code ?

Ajouter un modèle, c’est écrire une classe qui déclare ses options. Par exemple, pour Qwen, voici le provider en entier :

@Injectable()
export class AISDKQwenProvider extends AISDKVllmProviderBase<AgentProvider.Qwen> {
  protected readonly providerName = "Qwen Provider"

  protected readonly modelOptions: Record<QwenAgentModel, ModelOptions> = {
    // Qwen's recommended non-thinking sampling (model card): temperature 0.7, top_p 0.8, top_k 20, presence_penalty 1.5.
    [AgentModel.Qwen38_27B]: defineModelOptions(
      z.object({ temperature: z.number().min(0).max(2) }),
      { temperature: 0.7 },
    ),
  }
  protected readonly modelInfos: Record<QwenAgentModel, ModelInfos> = {
    [AgentModel.Qwen38_27B]: {},
  }

  getAgentProvider(): AgentProvider.Qwen {
    return AgentProvider.Qwen
  }
}

La température par défaut vient de la fiche du modèle, et le commentaire le dit. Une équipe qui la modifiera plus tard saura de quoi elle s’écarte.

Comment préciser un comportement spécifique ? Un simple override

La base commune expose quelques méthodes qu’un provider peut redéfinir par un override : pour le prompt système, pour les messages d’un appel en sortie structurée, ou pour la lecture de son résultat. Par exemple, si nous avons besoin de réécrire le parsing par défaut de la sortie structurée :

protected override readStructuredOutput(result: {
  text: string
  output: Record<string, unknown>
}): Record<string, unknown> {
  return this.parseStructuredOutputText(result.text)
}

La différence reste dans le provider. L’agent qui demande une extraction reçoit le même objet, quel que soit le modèle.

Qu’est-ce que chaque modèle reçoit sans rien faire ?

Certaines erreurs ne sont propres à aucun modèle. La base les traite donc une fois pour toutes, dans l’appel que partagent tous les providers :

  • Les arguments d’outil cassés sont réparés. Quand un modèle émet un appel d’outil dont les arguments ne sont pas du JSON valide, le hook experimental_repairToolCall d’AI SDK régénère ces arguments par un appel en sortie structurée, au lieu d’abandonner l’appel.
  • Un appel d’outil écrit comme du texte est récupéré. Il arrive qu’un modèle écrive l’appel dans sa réponse au lieu de l’émettre. Nous capturons le flux brut, sortons l’appel du texte et l’exécutons à la fin du flux.
  • Le texte répété après un résultat d’outil est supprimé. Un modèle peut écrire son message, appeler un outil dans la même étape, puis réécrire le même message au retour du résultat. Les étapes sont enregistrées comme une seule réponse : l’utilisateur lirait donc le message deux fois. Un filtre supprime le texte d’une étape quand il reprend mot pour mot le précédent, et le journalise.
  • Une erreur de génération n’est jamais silencieuse. En streaming, le SDK ne lève pas d’erreur quand la génération échoue : il glisse l’erreur dans le flux, au milieu du texte. Un code qui ne lit que le texte obtient alors une réponse vide, sans savoir pourquoi. Nous lisons tous les éléments du flux : une erreur interrompt l’appel et arrive dans nos journaux.
  • Des schémas d’outils stricts, seulement là où l’API les gère. supportsStrictTools() est désactivé par défaut et activé pour les providers qui vérifient les schémas côté serveur.

La personne qui conçoit un agent n’écrit rien de tout cela, et un nouveau modèle en hérite entièrement.

Qu’est-ce qui doit rester hors de la couche modèle ?

Notre règle : la couche agent ne demande jamais quel modèle tourne. Aucun if (model === "gemma") dans un agent, un outil ou un modèle de prompt. Une différence entre modèles va dans le provider, ou elle ne va nulle part.

Un exemple. Quand une question demande deux outils indépendants, comme afficher une fiche ressource et chercher dans la base de connaissances, un modèle peut les appeler l’un après l’autre au lieu des deux à la fois. Chaque appel en plus coûte un aller-retour.

Nous avons testé une ligne de prompt qui demandait de regrouper ces appels. Au premier essai, le résultat semblait meilleur. Mesurée ensuite sur plusieurs modèles, avec des messages proches du réel, cette ligne changeait aussi quels outils étaient appelés, en mieux ou en moins bien selon le modèle. Nous ne l’avons pas mise en production : une ligne ajoutée pour un modèle agit sur tous les autres. Nous prenons beaucoup de décisions de ce type, toujours sur la base de tests : le script de mesure reste dans le dépôt, pour tester la prochaine formulation de la même façon.

Comment savoir qu’une modification du harnais est sûre ?

Chaque provider passe les mêmes suites de tests de contrat en conditions réelles, hors CI, puisqu’elles appellent de vrais serveurs :

  • un tour de recherche documentaire sur un guide de test dont la valeur diffère volontairement de la valeur légale : une bonne réponse prouve que l’agent a lu le document ;
  • un tour qui demande deux outils indépendants ;
  • un agent sans recherche documentaire, avec un long prompt système et des garde-fous stricts, testé sur une salutation, une question sur un service, un refus et un message hors sujet.

Prenons une correction récente : nous avons corrigé l’anglais d’une consigne envoyée à Mistral à chaque tour avec outils. Ce n’est qu’une phrase, mais nous l’avons traitée comme une modification du harnais : test unitaire mis à jour, puis suite de tests en conditions réelles lancée sur Mistral avant et après. Cinq des six vérifications sont passées. La sixième échouait déjà avant la correction : c’est pour cela que nous lançons la suite de tests avant et après.

La suite

Les suites de tests en conditions réelles nous disent si un modèle respecte le contrat. Elles ne disent pas s’il sert mieux les utilisateurs d’un agent donné. C’est le rôle des jeux d’évaluation propres à chaque agent, construits à partir de vraies conversations, qu’un client MCP peut désormais lancer sur un modèle candidat. Ce sera certainement le sujet du prochain article : comment la sortie d’un nouveau modèle devient une décision mesurée, et non une annonce.

Le code se trouve dans le dépôt de la Plateforme Bayes, sous apps/api/src/external/llm/ai-sdk/.

Écrit par

Construire vos agents sur la Plateforme Bayes

Parlez-nous de votre premier cas d’usage, ou installez vous-même la plateforme open-source.