Skip to main content

Détection du fournisseur

FIM One utilise LiteLLM comme adaptateur universel. La fonction _resolve_litellm_model() dans core/model/openai_compatible.py mappe l’LLM_BASE_URL + LLM_MODEL de l’utilisateur à un identifiant de modèle LiteLLM avec un préfixe de fournisseur. Le préfixe détermine comment LiteLLM achemine la requête — protocole API natif (Anthropic Messages API, Gemini, etc.) ou générique OpenAI-compatible /v1/chat/completions. Ordre de résolution :
  1. Fournisseur explicite (champ DB ModelConfig.provider) — priorité la plus élevée. Si le fournisseur correspond à un domaine connu dans l’URL, aucun api_base n’est renvoyé (LiteLLM achemine nativement). Sinon, api_base est défini sur l’URL de relais.
  2. Correspondance de domaine par rapport à KNOWN_DOMAINS — les points de terminaison API officiels sont reconnus par nom d’hôte.
  3. Indice de chemin d’URL par rapport à PATH_PROVIDER_HINTS — courant sur les plateformes de relais comme UniAPI où /claude ou /anthropic dans le chemin indique le protocole en amont.
  4. Secours — préfixe openai/ (générique OpenAI-compatible).
Lorsque le préfixe du fournisseur est un protocole natif (anthropic, gemini, etc.) et que l’URL n’est pas le point de terminaison officiel, LiteLLM utilise le protocole natif mais envoie les requêtes à l’api_base du relais. Cela signifie que les comportements spécifiques au fournisseur — y compris le problème de prefill Bedrock décrit ci-dessous — s’appliquent indépendamment du fait que la requête aille à l’API officielle ou via un relais.
Si votre URL de relais contient /claude dans le chemin, FIM One achemine automatiquement via le protocole natif d’Anthropic. C’est généralement correct (meilleur streaming, support de la réflexion), mais cela signifie que les comportements spécifiques au fournisseur s’appliquent — y compris le problème de prefill Bedrock décrit ci-dessous.

tool_choice — les quatre modes

Le paramètre tool_choice est standardisé via le format OpenAI. LiteLLM le traduit vers le protocole natif de chaque fournisseur avant d’envoyer la requête. La distinction entre "auto" et forcé ({"type":"function",...}) est au cœur de chaque problème de compatibilité dans FIM One. Ces deux modes sont utilisés par des sous-systèmes complètement différents avec des exigences différentes.

Où tool_choice est utilisé

Deux sous-systèmes utilisent tool_choice, et ils l’utilisent de manières fondamentalement différentes.

Moteur ReAct — tool_choice=“auto”

La boucle ReAct nécessite que le modèle décide à chaque itération : appeler un outil ou donner une réponse finale. Seul "auto" a du sens ici — le modèle choisit librement entre produire des tool_calls ou du contenu textuel. Ceci est compatible avec tous les fournisseurs, tous les modèles et tous les modes, y compris la réflexion étendue. Le moteur ReAct utilise l’appel de fonction natif (_run_native) quand abilities["tool_call"] = True, en se repliant sur le mode JSON-dans-le-contenu (_run_json) sinon. Les deux modes utilisent "auto" — la différence est que les outils sont passés via le paramètre tools ou décrits dans l’invite système. Voir Moteur ReAct — Exécution en mode dual pour plus de détails.

structured_llm_call — tool_choice=forced

Extraction structurée en une seule étape (annotation de schéma, planification de DAG, analyse de plan). Force le modèle à appeler une fonction virtuelle spécifique, ce qui garantit une sortie JSON structurée. C’est le point d’appel qui déclenche les erreurs spécifiques aux fournisseurs. structured_llm_call implémente une chaîne de dégradation à 3 niveaux. Les trois paliers sont nommés d’après le code, et leur numérotation ne correspond pas à celle des quatre niveaux de garantie décrits dans la section ci-dessous : La différence de conception essentielle est la suivante : le fallback de structured_llm_call s’effectue à l’exécution — il essaie dynamiquement chaque niveau et intercepte les exceptions pour passer au suivant. La sélection du mode par le moteur ReAct s’effectue au moment de la construction — il vérifie _native_mode_active une fois au début et s’engage dans un mode unique pour toute la boucle. Ainsi, structured_llm_call peut récupérer de manière transparente les erreurs 400 spécifiques aux fournisseurs, tandis que ReAct dépend d’une sélection correcte du mode dès le départ.

Sortie structurée : les quatre niveaux de garantie

« Sortie structurée » est une expression qui recouvre quatre mécanismes différents. Tous produisent du JSON, mais ils n’offrent pas le même niveau de garantie. Ce qui les distingue, c’est l’emplacement de la contrainte : dans le décodeur, qui ne peut pas émettre un token interdit par le schéma, ou dans le prompt, qui peut seulement formuler une demande. Le mécanisme qui sous-tend T1 et T2 est le décodage contraint. Le fournisseur compile le schéma en une grammaire et, à chaque étape du décodage, masque tous les tokens qui rendraient la sortie impossible à analyser conformément à ce schéma. Un champ requis ne peut pas être ignoré, car le token qui fermerait l’objet ne fait pas partie de l’ensemble autorisé tant que ce champ n’a pas été émis. Il s’agit d’un type d’affirmation différent de « le modèle a reçu une demande formulée poliment et s’y est conformé », et c’est pourquoi T1/T2 restent fiables avec une température de 1 et sur de petits modèles, là où l’obéissance aux instructions du prompt ne suffit pas.

T1 et T2 ne sont pas interchangeables

Ils offrent la même garantie par des canaux différents, et le canal compte à trois égards. La structure du tour diffère. T1 renvoie un tour d’appel d’outil ; le modèle a décidé d’agir. T2 renvoie un message d’assistant ordinaire ; le modèle a répondu dans un format donné. Lorsque l’appelant veut une seule extraction et non une boucle d’agent, T2 l’exprime directement au lieu d’inventer une fonction virtuelle que le modèle devrait « appeler ». Forcer T1 entre en conflit avec la réflexion. Obtenir de T1 une charge utile contrainte par un schéma implique généralement de forcer le choix, soit avec required, soit avec une fonction nommée. Plusieurs fournisseurs refusent un choix d’outil forcé lorsque la réflexion étendue est active. Le tableau B ci-dessous indique lesquels. T2 ne présente pas ce conflit : il s’agit d’une contrainte de réponse, et non d’une contrainte d’outil ; c’est donc le seul chemin contraint par un schéma qui reste compatible avec la réflexion activée. Pour un déploiement standardisé sur des modèles de raisonnement, c’est l’argument pratique en faveur de T2, plutôt qu’une préférence stylistique. T1 entraîne une étape de configuration du schéma que T2 évite. Une fonction virtuelle nécessite un nom, une description et une décision quant à la possibilité pour le modèle de refuser de l’appeler.

Ce que coûte T2

Le décodage contraint n’est pas gratuit, et le schéma dont vous disposez déjà n’est généralement pas celui qu’il accepte.
  • Un sous-ensemble de schémas. Le mode strict d’OpenAI exige additionalProperties: false pour chaque objet, ainsi que la présence de chaque propriété dans required ; l’optionalité s’exprime par une union avec null plutôt que par l’omission de required. La racine doit être un objet. Des limites s’appliquent au nombre total de propriétés et à la profondeur d’imbrication. La plupart des schémas écrits manuellement doivent être modifiés avant de pouvoir être utilisés.
  • La compilation de la grammaire. La première requête utilisant un nouveau schéma entraîne une latence de compilation ponctuelle côté fournisseur. La réutilisation d’un schéma stable permet d’amortir ce coût ; générer un nouveau schéma pour chaque requête ne le permet pas.
  • Une nouvelle source d’échec. Lorsqu’un modèle refuse de répondre, il renvoie un refus plutôt qu’un objet conforme au schéma ; l’appelant doit donc prévoir une branche pour ce cas.

Ce qu’aucun niveau ne garantit

Chaque niveau contraint la forme. Aucun ne garantit la véracité. Une réponse T2 peut être parfaitement valide selon le schéma tout en étant factuellement incorrecte, et un champ contraint par une énumération renverra l’une des valeurs autorisées même si aucune n’est correcte, car le rôle du décodeur est de maintenir la sortie dans la grammaire plutôt que de connaître la réponse. Le contrôle par schéma élimine les échecs d’analyse et les erreurs de structure des champs. Il ne dispense pas de vérifier ce que la valeur signifie.
Deux systèmes de numérotation, qui ne correspondent pas. Cette page utilise T1–T4 pour les niveaux de garantie ci-dessus. Elle utilise Niveau 1/2/3 pour les trois paliers de la chaîne de dégradation de structured_llm_call, nommés d’après le code (native_fc, json_mode, plain_text). Le Niveau 2 de FIM One est json_object, qui correspond à T3, et non à T2. La chaîne comporte trois paliers parce qu’elle omet entièrement T2, et non parce qu’il n’existe que trois niveaux.

Où se situe FIM One

Cinq conséquences en découlent, et ce sont les limites réelles de la conception actuelle.
  1. La chaîne ne dispose d’aucun niveau avec validation du schéma vers lequel se replier. Lorsque le niveau 1 échoue, le niveau inférieur suivant est json_object, qui garantit uniquement que le texte peut être analysé. Il n’existe aucune étape intermédiaire qui continue d’imposer les champs.
  2. La garantie du niveau 1 est plus faible que son nom ne le laisse penser. Sans strict, l’appel de fonction natif repose sur la variante non stricte antérieure à 2024 : le modèle respecte généralement le schéma, mais rien ne l’empêche d’omettre un champ obligatoire, d’inventer une clé ou de renvoyer une chaîne là où un nombre a été déclaré.
  3. Sur les routes anthropic/, le niveau 2 n’est pas réellement T3. L’Anthropic Messages API ne possède pas de response_format, si bien que LiteLLM émule le mode JSON en injectant un préremplissage assistant. Il s’agit d’un mécanisme au niveau du prompt, ce qui place la garantie effective entre T4 et T3, plutôt qu’au niveau T3. Le piège du préremplissage Bedrock décrit ci-dessous correspond à l’échec manifeste de cette émulation ; le coût plus discret est que la garantie n’a jamais été celle que le nom du paramètre laissait entendre.
  4. Le résultat n’est jamais validé ensuite par rapport au schéma. jsonschema n’est pas une dépendance. Les vérifications éventuelles sont effectuées par site d’appel, dans la fonction facultative parse_fn, et leur rigueur varie selon le site d’appel.
  5. L’échec est absorbé au lieu d’être signalé. Presque tous les appelants transmettent default_value, si bien qu’une chaîne épuisée renvoie un objet d’apparence plausible au lieu de lever une exception. StructuredCallResult.level_used enregistre le niveau qui a produit la valeur, mais aucun site d’appel ne le lit ; de plus, sur le chemin default_value, il indique plain_text pour une chaîne qui, en réalité, n’a rien produit.
Comment savoir sur quel niveau vous vous trouvez réellement. structured_llm_call consigne une ligne par appel terminé au niveau INFO, émise uniquement après que les données ont été acceptées par parse_fn ; elle indique donc un niveau qui a véritablement réussi :
Un échec total est consigné au niveau WARNING avec level=none outcome=default_value. Rechercher ces deux lignes dans les logs d’une journée de trafic est le seul moyen de déterminer si l’absence du niveau T2 a un coût dans un déploiement donné, car la conception avec default_value se manifeste par une réponse médiocre plutôt que par une erreur.

Prise en charge des fournisseurs pour chaque niveau

Ce tableau documente les API upstream, et non les chemins de code de FIM One. Tous les autres tableaux de cette page indiquent la fonction qui implémente le comportement. Celui-ci ne le peut pas, car FIM One n’utilise aucune fonctionnalité T2 d’un fournisseur et ne définit strict sur aucun d’entre eux. Il est présent afin qu’une décision d’implémenter T2 parte de ce qui est réellement disponible. Les capacités des fournisseurs évoluent rapidement, et la prise en charge arrive souvent sur certains modèles d’une famille avant les autres ; consultez à nouveau la documentation du fournisseur avant de vous fier à une ligne.
Trois tendances méritent d’être nommées, car elles expliquent la forme du tableau plutôt que de se contenter de le répéter. T3 est universel, T2 ne l’est pas. Tous les fournisseurs pris en charge par FIM One proposent json_object, et environ la moitié proposent un niveau avec schéma. Un chemin de sortie structurée portable doit donc reposer sur T3 ou un niveau inférieur, ce qui explique la forme de la chaîne de FIM One. Ajouter T2 signifie ajouter un indicateur de capacité par modèle, et non un commutateur global. Le raisonnement toujours activé tend à fermer la porte de T1. GLM, les modèles de raisonnement de Kimi et deepseek-reasoner limitent ou refusent tous un choix d’outil forcé, et Anthropic le refuse lorsque le raisonnement est actif. MiniMax est l’exception. Lorsque la porte est fermée, T2 est la seule option restante contrôlée par schéma, ce qui explique pourquoi le niveau manquant est plus important sur un déploiement de modèles chinois que sur un déploiement OpenAI. L’auto-hébergement inverse l’ordre habituel. Le décodage contraint est une propriété de la pile de service ; il est donc disponible sur un checkpoint local dont le respect des instructions est bien plus faible que celui d’un modèle de pointe. Les modèles qui ont le plus besoin d’un contrôle par schéma sont ceux qui sont le plus susceptibles de l’obtenir.

Le piège du prefill Bedrock

Lorsque response_format={"type":"json_object"} est transmis pour un modèle résolu avec le préfixe anthropic/, LiteLLM injecte en interne un message d’assistant prefill pour simuler le mode JSON. L’API Messages d’Anthropic n’a pas de paramètre response_format natif, donc LiteLLM l’approxime en ajoutant une accolade ouvrante comme contenu d’assistant :
Cela fonctionne sur l’API directe d’Anthropic. Cependant, les versions plus récentes des modèles AWS Bedrock rejettent toute conversation dont le dernier message a role: "assistant" — ils appellent cela « assistant message prefill » et lèvent :
Cette erreur se produit uniquement lorsque les trois conditions sont remplies simultanément :
  1. Le modèle est résolu avec le préfixe anthropic/ (via correspondance de domaine ou indice de chemin URL).
  2. response_format={"type":"json_object"} est transmis (le chemin de code json_mode dans structured_llm_call).
  3. Le backend réel est AWS Bedrock (qui rejette le prefill).
Bedrock via endpoint compatible OpenAI ? Si votre relais Bedrock expose un endpoint /v1/chat/completions compatible OpenAI (soit la passerelle compatible OpenAI d’AWS elle-même, soit un proxy tiers), et que le chemin URL ne contient pas /claude ou /anthropic, FIM One le résout avec le préfixe openai/. LiteLLM traite alors le backend comme un serveur standard compatible OpenAI, transmet response_format directement sans injecter de prefill, et le serveur gère la contrainte JSON nativement. Le piège du prefill ne s’applique pas — vous n’avez pas besoin de définir json_mode_enabled=false.
Cela n’affecte pas l’appel d’outil natif (tool_choice="auto" avec le paramètre tools=). L’injection de prefill se produit uniquement pour response_format. L’exécution de l’agent ReAct n’est complètement pas affectée.
Si le Niveau 1 (native_fc) et le Niveau 2 (json_mode) échouent tous deux sur Bedrock, le système se rétablit au Niveau 3 (plain_text). Le drapeau json_mode_enabled décrit ci-dessous élimine l’appel gaspillé du Niveau 2.

Le correctif : json_mode_enabled

Un indicateur json_mode_enabled par modèle contrôle si le niveau 2 (json_mode) est susceptible d’être tenté :
  • Modèles configurés dans la DB : activez ou désactivez l’option dans Admin → Modèles → Paramètres avancés. L’indicateur est stocké dans ModelProviderModel.json_mode_enabled (TRUE par défaut).
  • Modèles configurés via ENV : définissez LLM_JSON_MODE_ENABLED=false dans votre environnement.
  • Effet : lorsqu’il est désactivé, abilities["json_mode"] renvoie False → response_format n’est jamais transmis → aucun préremplissage → Bedrock fonctionne. La chaîne de dégradation devient native_fc → plain_text, en ignorant entièrement l’appel json_mode voué à l’échec.
  • Coût de cette désactivation : en pratique, le modèle renvoie toujours un JSON valide, car l’invite système le lui demande et extract_json() analyse de manière fiable le contenu libre des modèles modernes. Ce qui est perdu, ce n’est pas le résultat, mais la garantie : la chaîne s’arrête désormais à T4, où seul le prompt contraint le résultat. Sur une route anthropic/, cette perte est moins importante qu’il n’y paraît, puisque le mode JSON émulé n’offrait déjà pas de garantie au niveau du décodeur.

Modèles de raisonnement + tool_choice forcé

Plusieurs fournisseurs rejettent un tool_choice forcé lorsque le raisonnement étendu est actif, au motif que fixer un appel de fonction spécifique contredit la liberté du modèle de raisonner d’abord :
Il s’agit d’une règle par fournisseur, pas d’une loi des modèles de raisonnement. Anthropic l’applique au niveau du protocole et Moonshot (Kimi) se comporte de la même manière, mais MiniMax raisonne à chaque appel et accepte toujours un tool choice forcé. Le tableau B dans la Matrice de capacités des fournisseurs enregistre le verdict fournisseur par fournisseur ; ne généralise pas à partir d’une ligne à l’autre. Pour les modèles Anthropic, structured_llm_call résout le conflit de lui-même en passant reasoning_effort=None au niveau du FC natif, ce qui désactive le raisonnement pour cet appel uniquement (structured.py::_call_llm). La sortie structurée nécessite une conformité au schéma, pas un raisonnement profond, donc désactiver le raisonnement là est à la fois correct et moins coûteux. Lorsque le raisonnement ne peut pas être désactivé via l’API, native_fc échoue avec un 400 à chaque appel structuré et coûte environ dix secondes avant que la chaîne ne bascule vers json_mode. Kimi est le cas courant : avec le raisonnement activé, seul auto est supporté, et un tool choice forcé nécessite de désactiver le raisonnement, que Moonshot expose uniquement via l’ID du modèle (kimi-k2 l’a désactivé, kimi-k2.5 et kimi-k2-thinking l’ont activé). FIM One n’a aucun paramètre qui le bascule, donc le remède est le drapeau tool_choice_enabled ci-dessous.

Le correctif : tool_choice_enabled

Un drapeau tool_choice_enabled par modèle contrôle si le Niveau 1 (native_fc) est jamais tenté :
  • Modèles configurés en BD : basculer dans Admin → Models → Advanced → “Native Function Calling”. Le drapeau est stocké sur ModelProviderModel.tool_choice_enabled (par défaut TRUE).
  • Modèles configurés par ENV : définissez LLM_TOOL_CHOICE_ENABLED=false dans votre environnement.
  • Effet : lorsque désactivé, abilities["tool_choice"] retourne False → la chaîne de dégradation commence au Niveau 2 (json_mode) ou Niveau 3 (plain_text), en ignorant complètement native_fc. Cela élimine la pénalité d’~10s par appel structuré pour les modèles incompatibles.
  • Agent ReAct non affecté : tool_choice_enabled contrôle uniquement la sélection d’outil forcée dans structured_llm_call. Le moteur ReAct utilise tool_choice="auto" (le modèle décide librement), qui fonctionne avec tous les modèles indépendamment de ce paramètre.
tool_choice_enabled et tool_call sont des drapeaux de capacité distincts. tool_call (toujours True pour OpenAICompatibleLLM) contrôle si les outils sont transmis au modèle du tout — le désactiver casserait l’agent ReAct. tool_choice contrôle uniquement si la sélection d’outil forcée est tentée pour l’extraction de sortie structurée.
tool_choice="auto" n’est pas affecté par le mode de réflexion. Le moteur ReAct utilise "auto" exclusivement, donc l’exécution de l’agent fonctionne avec la réflexion activée.
Ne définissez PAS abilities["tool_call"] = False pour éviter cette contrainte. Cela désactiverait le mode _run_native de ReAct (qui utilise tool_choice="auto" et fonctionne bien avec la réflexion), le forçant dans le mode _run_json moins fiable.
Note de migration de fournisseur : Certains relais tiers suppriment silencieusement les paramètres non supportés comme reasoning_effort (drop_params=True), donc la réflexion n’est jamais activée même si configurée. Lors de la migration vers un fournisseur qui supporte correctement la réflexion (Bedrock, API Anthropic directe), le reasoning_effort=None dans native_fc assure un comportement cohérent. Aucune action utilisateur n’est nécessaire — la sortie structurée fonctionne de manière identique sur tous les fournisseurs.

Matrice des capacités des fournisseurs

Cette section est le registre faisant autorité de ce que chaque fournisseur supporte et ce que FIM One fait à ce sujet. Chaque ligne nomme la fonction qui implémente le comportement, de sorte que toute affirmation ici peut être vérifiée par rapport au code. D’autres pages renvoient à cette section au lieu de répéter les données ; quand le code change, cette section change avec lui. Une ligne décrit le protocole d’un fournisseur, pas un seul modèle. Lorsque les modèles au sein d’une même famille diffèrent (DeepSeek chat par rapport à reasoner, Kimi avec thinking activé par rapport à désactivé), la cellule l’indique.

Tableau A : Routage des protocoles

Comment un base_url et un model configurés se traduisent en appel LiteLLM, et ce qui se passe lorsque la première interface choisie n’est pas disponible. Comment GPT-5.x choisit un protocole. FIM_GPT5_RESPONSES_MODE détermine le protocole : native (valeur par défaut) communique directement avec /v1/responses via litellm.aresponses, bridge utilise la traduction des chat completions de LiteLLM, et off force l’utilisation des chat completions classiques. La route native existe parce que le pont entraîne une perte à un endroit crucial : il supprime les éléments de raisonnement, ce qui oblige un agent GPT-5.x à redériver sa chaîne de pensée à chaque cycle d’outil. La communication directe avec le protocole permet de rejouer ces éléments. Un appel qui transmet explicitement reasoning_effort=None, comme le font structured_llm_call et les sondes de signal de fin, reste sur les chat completions, car un appel qui ne demande aucun raisonnement n’a aucun état de raisonnement à préserver. L’exception concerne les modèles qui ne peuvent pas désactiver le raisonnement (gpt-6.1-*, gpt-6-astra) : leurs chat completions rejettent les outils de fonction quelle que soit la valeur de reasoning_effort. Ces appels restent donc sur /v1/responses et utilisent le niveau d’effort le plus bas, low. Pour la même raison, le mode off ou un point de terminaison sans /v1/responses prive ces modèles des appels d’outils. Deux propriétés de cette requête native sont essentielles et faciles à mal configurer :
  • store=false maintient la conversation sans état côté fournisseur, et include=["reasoning.encrypted_content"] demande que la charge utile chiffrée soit renvoyée. Sans cet élément dans include, les éléments de raisonnement sont vides et leur rejeu ne produit silencieusement aucun effet.
  • L’id côté serveur doit être supprimé d’un élément de raisonnement rejoué. Avec store=false, rien n’est enregistré côté fournisseur ; renvoyer l’identifiant provoque donc l’erreur Item with id 'rs_...' not found. Items are not persisted when 'store' is set to false. Le bloc encrypted_content contient à lui seul l’état ; supprimer l’identifiant n’entraîne donc aucune perte (sanitize_reasoning_item).
Bedrock. Claude hébergé sur Bedrock suit la route à laquelle il est résolu, et non le fait qu’il soit hébergé sur Bedrock. Lorsqu’il passe par un relais routé avec anthropic/, il hérite du comportement du protocole Anthropic, y compris du préremplissage de l’assistant en mode JSON de LiteLLM, que les versions récentes de Bedrock rejettent. Lorsqu’il passe par une passerelle compatible avec OpenAI, il est résolu en openai/, aucun préremplissage n’est injecté et json_mode_enabled peut rester activé.

Table B: Conflits et solutions de contournement

FIM One émet trois des quatre états tool_choice : auto à partir de la boucle ReAct (react.py::_run_native), une fonction nommée à partir de la sortie structurée (structured.py::_call_llm), et none quand la réponse du signal de fin rejoue la charge utile des outils. Aucun site d’appel n’émet required ; cette colonne enregistre la contrainte du fournisseur sur le choix d’outil non-auto, qui s’applique à required et à une fonction nommée de la même manière.

Table C: Protocole de réflexion

LLM_REASONING_EFFORT accepte low, medium et high ; toute autre valeur est lue comme non définie (deps.py::_reasoning_effort). Ce que FIM One place ensuite sur le fil est spécifique au fournisseur, et c’est ce que ce tableau enregistre. La colonne Replay est la valeur de retour de reasoning_replay_policy, qui est un petit ensemble fermé de quatre états plutôt qu’une liste par fournisseur. unsupported et informational_only produisent les mêmes octets sur le fil : tous deux suppriment reasoning_content et signature de l’historique sortant. Ils diffèrent dans l’intention, donc un modèle qui clairement réfléchit mais aboutit en unsupported est une lacune dans la table de fragments plutôt qu’un bug actif.

Relay/proxy gotchas

Les passerelles tierces échouent de manière différente d’un fournisseur direct, et la plupart de ces défaillances sont silencieuses. Chaque ligne ci-dessous associe le symptôme à son mécanisme et à ce que FIM One fait déjà à ce sujet.
Limite du support. FIM One garantit le comportement documenté sur cette page pour les points de terminaison propriétaires : l’API propre d’OpenAI, Anthropic, Google, et tout fournisseur servant directement ses propres modèles. Les relais tiers sont supportés au mieux et ne sont pas couverts par cette garantie, car ce qu’un relais fait à une requête échappe à notre contrôle et fréquemment à sa propre documentation. Un relais peut supprimer un paramètre, réécrire l’historique, retirer un point de rupture de cache, ou répondre à un protocole qu’il n’implémente que partiellement, et dans la plupart de ces cas il retourne un 200 au lieu d’une erreur.C’est une déclaration sur ce que nous promettons, pas une restriction sur ce qui s’exécute. FIM One ne maintient pas de liste blanche d’hôtes approuvés, et rien ici n’est conditionné par un domaine. La capacité est décidée par ce qu’un point de terminaison fait réellement : une route manquante répond 404 et est mémorisée, un include ignoré produit des éléments de raisonnement vides et la relecture devient une non-opération, et une requête rejetée bascule pour cet appel. Sonder le point de terminaison est plus précis que déduire ses capacités de son nom d’hôte, et c’est la seule approche qui continue de fonctionner pour Azure OpenAI, les passerelles d’entreprise, et les proxies auto-hébergés qui implémentent correctement le protocole.Si un relais se comporte mal d’une manière que les basculements ne détectent pas, épinglez le protocole vous-même avec FIM_GPT5_RESPONSES_MODE (bridge ou off) ou les bascules par modèle tool_choice_enabled et json_mode_enabled, et reproduisez contre le point de terminaison propriétaire avant de le signaler comme un bug FIM One.

Configuration recommandée par modèle

Les deux paramètres tool_choice_enabled et json_mode_enabled peuvent être activés/désactivés par modèle dans Admin → Models → Advanced settings. Les valeurs par défaut, toutes deux TRUE, sont correctes pour la plupart des fournisseurs ; n’ajustez que si vous constatez des erreurs ou une latence inutile. Les fournisseurs nécessitant un ajustement sont enregistrés dans le tableau B ci-dessus, et la vue par modèle qu’un opérateur remplit se trouve dans Model Management.
Quand changer : si vous voyez des avertissements structured_llm_call: native_fc call raised dans vos logs suivis d’une extraction json_mode réussie, le modèle ne bénéficie pas de native_fc. Désactivez « Native Function Calling » pour ce modèle afin d’éliminer l’appel API gaspillé (~10s par demande de sortie structurée).
Les remplacements au niveau ENV s’appliquent à tous les modèles configurés via des variables d’environnement (pas l’interface admin) :

Configuration de l’effort de raisonnement et de la réflexion

FIM One expose deux variables d’environnement pour contrôler la réflexion étendue / le raisonnement : Deux comportements suivent automatiquement une fois que la réflexion est activée, et aucun ne nécessite de configuration utilisateur :
  1. La température est gérée pour vous. Sur une route anthropic/ avec réflexion active, _build_request_kwargs épingle temperature à 1.0, ce que Bedrock exige. Les modèles qui rejettent complètement les paramètres d’échantillonnage (Opus 4.7 et 4.8, Fable 5, Mythos 5) ont temperature supprimé de la requête entièrement, réflexion ou non. Ne définissez pas LLM_TEMPERATURE=1 manuellement pour cela.
  2. GPT-5.x maintient les outils et le raisonnement ensemble autant que possible. FIM One sonde d’abord le pont Responses pour GPT-5.x, car c’est la seule surface où les deux se combinent. Un endpoint sans route /v1/responses utilisable bascule vers les complétions de chat, le verdict est mis en cache par endpoint, et sur ce chemin une requête portant tools envoie un reasoning_effort explicite de none. Omettre le champ n’est pas équivalent, car la valeur par défaut du serveur n’est pas none.

Analyse défensive pour la sortie structurée

Même avec native_fc fonctionnant correctement, le pipeline de sortie structurée inclut une couche d’analyse défensive pour gérer les cas limites de n’importe quel fournisseur ou couche de compatibilité. L’analyseur _dict_to_steps du planificateur DAG gère trois cas limites courants :
  1. Objet unique au lieu de tableau. Certains modèles retournent {"steps": {"id": "1", "task": "..."}} (un objet d’étape unique) au lieu de {"steps": [{"id": "1", "task": "..."}]} (un tableau). L’analyseur détecte cela en vérifiant la présence de clés id ou task et enveloppe l’objet dans une liste.
  2. Chaîne JSON double-encodée. Lorsque la sortie structurée revient à json_mode (qui manque d’application de schéma), certains fournisseurs retournent la valeur steps comme chaîne JSON plutôt que comme tableau natif — par exemple, {"steps": "[{\"id\": \"1\", ...}]"}. Cette chaîne peut également contenir des sauts de ligne littéraux (du formatage du modèle) qui cassent le json.loads standard. L’analyseur utilise extract_json_value() (qui inclut _repair_json_strings) pour gérer :
    • Les sauts de ligne littéraux à l’intérieur des valeurs de chaîne JSON
    • Les séquences d’échappement invalides (courantes avec le contenu LaTeX ou de code)
    • Autres particularités de sérialisation des couches de compatibilité
  3. Enveloppe steps manquante. Le modèle peut retourner une seule étape comme objet de niveau supérieur sans la clé d’enveloppe steps. L’analyseur détecte id et task au niveau racine et enveloppe en conséquence.
En fonctionnement normal, native_fc retourne des arguments d’appel d’outil correctement structurés et ces cas limites ne se produisent pas. Les analyseurs défensifs existent comme filet de sécurité pour les sous-classes BaseLLM personnalisées, les comportements inhabituels des fournisseurs, ou les scénarios de secours où la sortie structurée se dégrade en json_mode ou plain_text.

Mise en cache des prompts (multi-fournisseur)

FIM One implémente la mise en cache explicite des prompts d’Anthropic via des points de rupture cache_control et bénéficie simultanément de la mise en cache automatique des préfixes de tous les autres fournisseurs grâce au Registre des sections de prompts. L’objectif est un chemin unique d’assemblage des prompts qui fonctionne sur tous les fournisseurs sans divergence de forme de prompt par appel.

Architecture

Le module fim_one.core.prompt expose trois primitives :
  • PromptSection — un fragment nommé avec soit un content: str statique, soit un content: Callable dynamique
  • PromptRegistry — un magasin mémoïsé (les sections statiques se rendent une fois, les sections dynamiques se re-rendent par appel)
  • DYNAMIC_BOUNDARY — un marqueur sentinel que le registre insère entre la dernière section statique et la première dynamique, afin que les appelants puissent diviser le prompt rendu au point de rupture du cache
Les prompts système pour ReAct (mode JSON, mode d’appel de fonction natif, synthèse) sont divisés en :
  • Préfixe statique (~95% du prompt) — identité, directives principales, descriptions d’outils
  • Suffixe dynamique — date/heure actuelle, directive de langue par requête, contexte de transfert

Détection des capacités

fim_one.core.prompt.caching.is_cache_capable(model_id) retourne True quand l’ID du modèle contient l’un des éléments suivants : claude, anthropic, bedrock/anthropic, vertex_ai/claude. Ces fournisseurs reçoivent deux messages avec role="system" et cache_control: {"type": "ephemeral"} sur le premier message (statique). Tous les autres fournisseurs reçoivent un seul message système concaténé sans champ cache_control — cela est nécessaire car les points de terminaison non-Anthropic rejettent soit le champ, soit le suppriment silencieusement, et l’envoyer via certains relais provoque des erreurs 400 unknown parameter.

Prise en charge de plusieurs fournisseurs

Le PromptRegistry profite gratuitement à tous les fournisseurs qui utilisent la mise en cache automatique des préfixes : en conservant la partie statique identique au niveau des octets d’un appel à l’autre (la date et l’heure actuelles se trouvent dans le suffixe dynamique, et non dans le préfixe), le hash correspond pour chaque fournisseur doté de cette mise en cache et une entrée en cache est trouvée. C’est pourquoi le Registry constitue un avantage fondamental indépendant du modèle, même avant de tenir compte de cache_control, spécifique à Anthropic.

Observabilité

Chaque réponse chat/* inclut maintenant dans sa done_payload :
TurnProfiler émet une ligne de journal structurée par tour : turn_cache summary | model=claude-sonnet-4-6 | read_tokens=1067 | create_tokens=0 | saved_input_tokens=961 (~90%). Cela fonctionne également comme une sonde d’honnêteté de relais — si vous routez via un relais API, comparez les entrées réellement facturées par rapport à read_tokens pour détecter si le relais supprime cache_control ou conserve la réduction de 0,10×. Aucune estimation en dollars n’est retournée au niveau du LLM — la tarification et la majoration du relais sont appliquées au-dessus, donc la couche LLM retourne uniquement les comptages de tokens objectifs.

ROI du cache multi-tour

Mesuré sur les tours Claude 4 ReAct avec l’invite d’agent par défaut : Une exécution ReAct de 10 itérations avec 10 outils économise ~8 640 tokens d’entrée par tour après le premier (9 accès au cache × 1067 tokens × 90 %). Anthropic facture 1,25× pour l’écriture du cache au premier appel, donc le seuil de rentabilité se situe au deuxième appel — les requêtes à un seul tour n’en bénéficient pas.

Politique de relecture du raisonnement (exactitude sans modèle)

Les blocs de réflexion étendue / raisonnement se comportent différemment selon les fournisseurs. Une politique de sérialisation uniforme rompt à la fois les contrats de protocole et les caches de préfixe automatiques. fim_one.core.prompt.reasoning.reasoning_replay_policy(model_id) retourne l’une de quatre valeurs et contrôle ChatMessage.to_openai_dict(replay_policy=...) dans OpenAICompatibleLLM._build_request_kwargs().

Quatre politiques

  • anthropic_thinking — Famille Claude (y compris anthropic/, bedrock/anthropic, vertex_ai/claude). Les blocs de réflexion DOIVENT être relus avec signature attachée ; Anthropic rejette les tours suivants si la signature est manquante ou modifiée.
  • informational_only — modèles qui émettent CoT mais n’ATTENDENT PAS de relecture : mode raisonnement DeepSeek (deepseek-reasoner sur V3.2, et les anciens ids deepseek-r1 et R1-Distill que la table de fragments correspond toujours), Qwen QwQ, Gemini flash-thinking, OpenAI o1 / o3 / o4. Leur documentation dit explicitement « n’envoyez pas reasoning_content dans l’historique des messages ». L’envoyer quand même :
    • Viole le contrat du fournisseur (peut commencer à rejeter dans les versions futures)
    • Invalide silencieusement leur cache de préfixe automatique — les octets du message mutent à chaque tour, cassant le hash
  • openai_responses — GPT-5.x, appariés sur le fragment gpt-5. Son état de raisonnement n’est pas du texte mais une séquence d’éléments opaques portant des charges utiles chiffrées, et seul /v1/responses dispose d’un emplacement pour eux. Sur ce protocole, les éléments sont relus verbatim, ce qui maintient la chaîne de pensée du modèle vivante entre les tours d’outils. Le résumé lisible est toujours supprimé des requêtes sortantes, donc sur le fallback chat-completions, cela se comporte exactement comme informational_only. Vérifié avant les fragments informationnels, dont l’entrée générique reasoning engloutirait autrement les ids GPT-5 avec proxy-tag.
  • unsupported — le fourre-tout : modèles sans capacité de raisonnement (GPT-4o, Gemini 1.5, Mistral, Llama), et modèles de raisonnement dont l’id ne correspond à aucun fragment (GLM, MiniMax, Kimi, Doubao). Aucun champ ne doit être relu dans les deux sens, donc cette politique place les mêmes octets sur le fil que informational_only. C’est aussi le défaut sûr pour les ids de modèle inconnus.
Le reasoning_content lisible et les reasoning_items opaques sont des champs indépendants sur ChatMessage. to_openai_dict() ne sérialise jamais les éléments du tout, donc ils sont structurellement incapables de fuir sur une requête chat-completions, quelle que soit la politique.

Application

Toute l’évaluation des politiques se fait en un seul endroit (_build_request_kwargs). ChatMessage.to_openai_dict(replay_policy=None) préserve la valeur par défaut permissive A3 afin que les appelants non coordonnés ne régressent pas. La matrice de test multi-fournisseurs se trouve dans tests/test_reasoning_replay_policy.py avec des assertions inverses prouvant que les requêtes non-Anthropic ne divulguent PAS reasoning_content.

Pour les utilisateurs

Le comportement des fonctionnalités et des bugs est automatique — vous n’avez besoin de configurer quoi que ce soit. Implications du flux de travail :
  • Si vous basculez entre les agents Claude et DeepSeek dans la même conversation, l’historique est stocké avec les blocs de réflexion intacts ; au tour suivant, la forme du message sortant s’adapte selon le modèle actuel.
  • Si vous utilisez un proxy / une sous-classe BaseLLM personnalisée, assurez-vous que son identifiant de modèle est reconnaissable (contient l’un des fragments) ou la politique unsupported par défaut s’appliquera — ce qui est sûr mais signifie que Claude derrière un proxy inhabituel pourrait perdre la relecture de la réflexion. Ajoutez le fragment d’identifiant de modèle à _CACHE_CAPABLE_MODEL_FRAGMENTS (dans core/prompt/caching.py) et/ou à la recherche de politique de réflexion.

Dépannage

« Ce modèle ne supporte pas le préfixage des messages d’assistant » Bedrock + json_mode. Deux solutions : (1) définir LLM_JSON_MODE_ENABLED=false ou désactiver le mode JSON dans les paramètres du modèle admin ; ou (2) si votre fournisseur Bedrock propose un point de terminaison compatible OpenAI /v1/chat/completions, basculer vers celui-ci — FIM One le résout en tant que openai/ et l’injection de préfixage ne se produit jamais. « La réflexion peut ne pas être activée lorsque tool_choice force l’utilisation d’outils » / « tool_choice ‘specified’ est incompatible avec la réflexion activée » Pour les modèles Anthropic, structured_llm_call désactive automatiquement la réflexion pour les appels native_fc. Lorsque la réflexion ne peut pas être désactivée via l’API, comme avec kimi-k2.5 et kimi-k2-thinking ou deepseek-reasoner, désactiver « Native Function Calling » dans les paramètres avancés du modèle, ou définir LLM_TOOL_CHOICE_ENABLED=false globalement. La chaîne de dégradation ignorera native_fc et extraira la sortie structurée via json_mode ou plain_text à la place. Consultez le tableau B de la Matrice de capacités des fournisseurs avant de supposer qu’un modèle de réflexion a ce problème ; MiniMax ne l’a pas. « Échec du pipeline DAG : le champ ‘steps’ du LLM n’est pas un tableau » Le LLM a renvoyé le champ steps sous forme de chaîne ou d’objet unique au lieu d’un tableau. Cela signifie généralement que la sortie structurée est tombée en json_mode (qui manque d’application de schéma). Vérifiez le journal pour structured_llm_call: level=xxx — s’il affiche json_mode au lieu de native_fc, native_fc échoue silencieusement. Si vous utilisez une sous-classe BaseLLM personnalisée, vérifiez qu’elle accepte l’argument reasoning_effort. ReAct bascule vers le mode JSON de manière inattendue Vérifier que abilities["tool_call"] du modèle est True. C’est toujours True pour OpenAICompatibleLLM, mais une sous-classe BaseLLM personnalisée pourrait l’ignorer. Vérifier avec le point de terminaison de détail du modèle dans l’API admin. structured_llm_call épuise tous les niveaux et lève StructuredOutputError Le modèle n’a pas pu produire de JSON analysable à aucun niveau. C’est rare avec les modèles modernes. Vérifier : (1) le schéma est un JSON Schema valide, (2) le modèle dispose de suffisamment de max_tokens pour produire la réponse complète, (3) le message système ne contredit pas les instructions du schéma. Le planificateur DAG et l’analyseur fournissent tous deux des solutions de secours default_value, donc cette erreur ne se propage que depuis les sites d’appel qui omettent explicitement les valeurs par défaut.