Cycle de vie du routage des fournisseurs
Le routage des fournisseurs commence après que ZeroClaw a sélectionné l’agent auquel revient un tour. Il couvre la sélection du profil du fournisseur et du modèle, les réessais et le basculement, la récupération du flux, ainsi que l’attribution indiquant quel backend a traité la requête. L’acheminement des canaux vers les agents relève d’un cycle de vie distinct ; consultez Cycle de vie de l’exécution des canaux.
Utilisez cette page lorsqu’une modification concerne model_routes, la sélection du modèle de session ou en cours de tour, le basculement entre fournisseurs, la classification des nouvelles tentatives, les périodes de refroidissement liées à la limitation du débit, la fin du flux, la relecture après un échec du flux ou l’attribution du fournisseur demandé par rapport au fournisseur effectivement utilisé.
Carte des propriétés
| Inquiétude | Propriétaire actuel | Contrat |
|---|---|---|
| Profils des fournisseurs et graphe de secours | zeroclaw-config schéma et validation du fournisseur | Une valeur <family>.<alias> séparée par un point identifie le point de terminaison, les informations d’identification, le modèle principal facultatif, les capacités et les déclarations de repli ordonnées d’un profil. |
| Construction du fournisseur | zeroclaw-providers fonctions de fabrique | Instanciez chaque profil avec ses propres paramètres, aplatissez les entrées de secours configurées dans l’ordre et concevez le routage en fonction de la fiabilité. |
| Sélection basée sur des indices | RouterModelProvider | Résolvez hint:<name> en une cible de fournisseur configurée et un modèle de routage. La cible principale est épinglée au modèle actif/par défaut. Une cible non principale est épinglée lorsque son profil configure un modèle ; sinon, son entrée fiable reste non épinglée et reçoit le modèle de routage. |
| Nouvelle tentative et basculement | ReliableModelProvider | Classer les échecs, réessayer avec une temporisation plafonnée, respecter les délais de récupération liés à la limitation du débit et parcourir les entrées matérialisées. |
| Arrêt du flux du fournisseur | Fournisseurs concrets et zeroclaw-providers/src/stream_guard.rs | Traduisez la sémantique d’achèvement de chaque protocole de fournisseur en StreamEvent::Final ou en une erreur de troncature. |
| Rejeu du flux et validation d’une sortie partielle | zeroclaw-runtime/src/agent/turn/provider_call.rs and stream_consume.rs | Réessayez un flux ayant échoué en mode non streaming uniquement avant que la sortie d’événements immuable ne soit validée. Ne rejouez jamais une réponse annulée ou visiblement partielle. |
| Attribution par appel | ProviderDispatch | Ouvrez des portées d’attribution autour de l’appel au fournisseur sélectionné pour chaque tentative. |
| Enregistrement de récupération réussie | ReliableModelProvider | Exposez un seul enregistrement local à la tâche, comparant la valeur demandée à la valeur servie, après une récupération réussie. Il ne s’agit pas d’une comptabilisation canonique par tentative. |
| Notifications de récupération destinées aux utilisateurs | Consommateurs de l’environnement d’exécution et des canaux | Affichez l’enregistrement de récupération réussie sur leur propre surface de sortie. Notez que les règles ne sont pas uniformes selon les consommateurs. |
Construction et sélection
L’environnement d’exécution démarre avec une référence active vers le fournisseur et un modèle provenant de l’agent sélectionné, d’une surcharge de session ou d’un model_switch effectué pendant le tour. La construction du fournisseur combine ensuite deux wrappers :
- La fabrique construit un
ReliableModelProviderpour le profil de fournisseur actif. Un modèle principal effectif provient d’une surcharge de construction explicite ou dumodelconfiguré dans le profil. Lorsqu’il en existe un, celui-ci et lesfallback_modelsdu profil deviennent des entrées épinglées. En l’absence d’un tel modèle, le profil fournit une entrée non épinglée et sesfallback_modelsne sont pas matérialisés. Les profilsfallbackréférencés récursivement sont néanmoins parcourus. Chaque profil référencé conserve ses propres identifiants, point de terminaison, en-têtes, modèle et remplacements de capacités. - Lorsque
model_routesest configuré, la fabrique construit un fournisseur fiable distinct pour la route principale et pour chaque cible de route unique, puis les encapsule dans RouterModelProvider. - Un
hint:<name>reconnu sélectionne sa cible configurée avant que l’appel ne soit soumis à la politique de fiabilité de cette cible. Une valeur de modèle standard utilise la route par défaut. Un hint inconnu consigne un avertissement, reste dans le domaine de fiabilité par défaut et conserve la valeur littéralehint:<name>comme modèle demandé. Une entrée par défaut épinglée continue de servir sa valeur épinglée ; une entrée par défaut non épinglée transmet la valeur littérale, que le fournisseur peut rejeter avant la poursuite du repli normal ou de la gestion des erreurs.
Il existe actuellement deux contraintes de construction :
- L’épinglage des routes est conditionnel. La cible principale est épinglée sur le modèle actif/par défaut transmis lors de la construction du fournisseur, y compris lorsqu’un indice reconnu renvoie vers le profil principal actif ; la valeur
model_routes[].modelde cet indice ne remplace pas l’épinglage principal. Une cible non principale avec un modèle de profil configuré est épinglée sur ce modèle ; son modèle de route ne remplace donc pas non plus le modèle du profil. Une cible non principale sans modèle configuré est valide et reste non épinglée ; le modèle de route est transmis à ce fournisseur, et lesfallback_modelsde ce profil ne sont pas matérialisés, même si les profils de secours référencés sont tout de même parcourus. Maintenez chaque modèle de route aligné sur l’épinglage de la cible lorsqu’il existe, et tenez compte du comportement non épinglé lorsque le profil cible ometmodel. - Les cibles de route sont dédupliquées par
model_provider. Si une route fournitapi_key, les premières informations d’identification de route correspondantes sont prioritaires lors de la construction de la cible partagée. Privilégiez les informations d’identification du profil du fournisseur lorsque plusieurs indications partagent une même cible.
Cet ordre est important : le routage choisit un domaine de fiabilité ; il ne contourne pas la fiabilité. Un service de routage externe tel qu’OpenRouter peut toujours effectuer une sélection côté serveur derrière un seul profil ZeroClaw, mais il est facultatif et ne remplace pas les contrats natifs de routage et de basculement de ZeroClaw.
Le schéma et les exemples destinés aux opérateurs se trouvent dans Configuration du fournisseur et Routage. Conservez la syntaxe des champs à cet endroit plutôt que de la dupliquer dans les documents d’architecture.
Ordre des tentatives sans streaming
Pour un alias de production, la fabrique aplatit le graphe configuré en profondeur d’abord. L’ordre effectif est :
- Le modèle principal effectif du profil, ou une entrée non épinglée lorsqu’aucun modèle principal effectif n’existe.
- Les
fallback_modelsde ce profil, dans l’ordre, uniquement lorsqu’un modèle principal effectif existe. - Chaque profil
fallback, dans l’ordre, y compris l’entrée principale ou non épinglée de ce profil, les modèles de secours éligibles et les profils de secours imbriqués.
Pour chaque entrée matérialisée, ReliableModelProvider tente la requête jusqu’à provider_retries + 1 fois. Une erreur réessayable reste normalement sur l’entrée et applique un délai d’attente borné. Une limitation de débit réessayable place ce profil de fournisseur en période de refroidissement en mémoire et passe à l’entrée suivante lorsqu’il en existe une autre. La plupart des erreurs non réessayables passent immédiatement à l’entrée suivante ; les erreurs de fenêtre de contexte font l’objet d’un traitement spécifique à la méthode et peuvent entraîner un retour anticipé pour permettre la récupération à l’exécution. Une réponse réussie met fin au parcours ; si chaque entrée échoue, l’enveloppe renvoie une erreur agrégée contenant les échecs des tentatives.
L’ordre de matérialisation et l’ordre d’exécution effectif peuvent différer après une limitation de débit. Les entrées primary et fallback_models d’un profil partagent une même clé de délai de récupération, de sorte qu’un 429 sur le modèle primary peut entraîner l’omission des autres modèles du même profil tant que le délai de récupération est actif.
Le pool global reliability.api_keys ne constitue pas actuellement un mécanisme de basculement fonctionnel. Le wrapper sélectionne et journalise une clé alternative après une limitation de débit réessayable, mais le trait ModelProvider ne peut pas appliquer cette clé au fournisseur construit ; la nouvelle tentative utilise donc toujours l’identifiant d’origine. Le ticket #9190 suit la correction. Utilisez des profils de secours distincts ou un service de routage externe lorsqu’un basculement au niveau des identifiants est nécessaire.
Les complétions vides font l’objet du même traitement de nouvelle tentative limité, au lieu de devenir immédiatement un message d’assistant vide.
Les déclarations de secours non valides sont traitées à deux niveaux distincts. Les références orphelines, les cycles, les arêtes au-delà de la profondeur autorisée, les identifiants de modèle vides et les modèles principaux dupliqués sont signalés et élagués, comme décrit dans Provider configuration. Un profil de secours qui se résout, mais ne peut pas fournir les informations d’identification requises ou ne peut pas être construit, entraîne l’échec de l’initialisation du fournisseur au lieu de modifier silencieusement la route.
Frontière entre la diffusion en continu et la relecture
Le streaming présente délibérément un contrat de nouvelle tentative plus limité que les appels sans streaming :
ReliableModelProviderchoisit la première entrée dans l’ordre qui prend en charge les capacités de flux demandées et n’est pas en période de refroidissement.- Il ouvre ce flux une seule fois. Il ne change pas d’entrée une fois le flux démarré.
- Le parseur concret du fournisseur traduit la sémantique d’achèvement de son protocole en
Finalou en erreur. La plupart des parseurs SSE protégés exigent le signal d’achèvement configuré. Anthropic considère actuellement aussi qu’un EOF après unmessage_delta.stop_reasonnon vide signale la fin, même simessage_stopn’a pas été observé ; la PR #9447 propose d’exigermessage_stop, mais cette modification n’a pas encore été fusionnée. - L’environnement d’exécution consomme et nettoie les événements du flux. Si le flux échoue avant que la sortie immuable des événements soit visible, l’environnement d’exécution réessaie l’appel complet via le chemin sans flux, qui repasse par l’intégralité du parcours de fiabilité.
- Si du texte, du raisonnement ou des événements d’outils préexécutés ont déjà atteint un réceptacle d’événements immuable, l’interruption devient
StreamInterruptedAfterOutput. L’environnement d’exécution ne rejoue pas la requête. Seul le texte déjà transmis au consommateur est conservé comme texte partiel de l’assistant. - Une annulation ne déclenche jamais de nouvelle tentative automatique auprès du fournisseur. Une annulation avant la transmission de texte interrompt le tour. Une annulation après la transmission de texte conserve ce texte partiel de l’assistant ; une sortie contenant uniquement le raisonnement ou issue d’un outil préexécuté ne devient pas, à elle seule, un texte partiel de l’assistant conservé lors de l’annulation.
Les sinks de mise à jour des brouillons sont mutables. Un mécanisme de secours avant le commit peut remplacer un brouillon sans dupliquer une sortie immuable, tandis que les sinks d’événements définissent la limite de non-rejeu.
Un flux qui se termine sans texte final ni appels d’outils est une réponse sémantiquement vide, et non une réponse réussie. Lorsque le runtime marque ce résultat comme pouvant être rejoué et que provider_retries est différent de zéro, Reliable autorise un appel de récupération sans streaming auprès du fournisseur/modèle exact ayant produit le flux vide. Cette possibilité n’est utilisée qu’une fois ; une récupération échouée passe aux candidats configurés restants avec leurs budgets de tentatives normaux. Avec zéro tentative, l’entrée de flux ayant échoué reste ignorée. Le raisonnement déjà affiché reste visible une seule fois, mais ne compte pas comme réponse finale. Cette exception n’autorise pas la relecture après une annulation, une sortie visible interrompue ou un travail d’outil exécuté par le fournisseur.
Cette division maintient la récupération du transport dans l’environnement d’exécution, le framing propre au fournisseur dans l’adaptateur et la stratégie de nouvelle tentative/de repli dans le wrapper de fiabilité. Une implémentation de fournisseur ne doit pas inventer une seconde stratégie de rejeu au niveau du tour.
Attribution et lacunes connues
ProviderDispatch ouvre le contexte d’attribution autour de chaque appel au fournisseur. ReliableModelProvider enregistre un repli demandé par rapport au repli servi uniquement après la réussite d’un appel sans diffusion en continu ou l’achèvement sans erreur d’un flux de repli. Le code d’exécution et du canal peut exploiter cet enregistrement local à la tâche pour informer un utilisateur qu’une reprise a eu lieu.
L’enregistrement n’est qu’une indication de récupération de la famille et du modèle. Les entrées de production utilisent la famille du fournisseur comme display_name, de sorte que l’enregistrement peut perdre l’alias de profil comportant des points. Un repli entre alias de même famille et de même modèle peut donc être indiscernable de la route demandée. Les réponses d’exécution ajoutent une notification de repli du modèle/fournisseur lorsque l’enregistrement diffère. La diffusion sur le canal ajoute un pied de page uniquement en cas de changement de famille ; issue #7883 suit les notifications intra-famille.
Cet enregistrement est une notification de réussite, et non un registre canonique de toutes les tentatives. Le problème n° 9470 suit l’utilisation incorrecte et l’imputation erronée des coûts pour les tentatives rejetées et les notifications de repli obsolètes après la récupération du flux. Tant que ce problème n’est pas résolu, ne déduisez pas l’exactitude du coût par tentative à partir de la notification de repli finale ou de l’identité du fournisseur demandé.
Le refus de contenu et le mécanisme de repli des garde-fous constituent également un contrat proposé distinct de la fiabilité du transport. Tracker #9293 coordonne ces travaux sur les interfaces du fournisseur, de la configuration, du canal, de la passerelle et du Web. Le travail adjacent sur l’identité de service proposé dans PR #8966 ne comble pas à lui seul la lacune d’attribution fiable.
Liste de contrôle des modifications
Pour les modifications du routage des fournisseurs, répondez à ces questions avant la validation du réviseur :
- Le changement affecte-t-il la distribution des agents, la sélection des indices, le mécanisme de secours lié à la fiabilité ou un routeur externe ? Nommez exactement un responsable pour chaque décision.
- Si une indication cible un profil de fournisseur, sa gestion du modèle est-elle conforme à la construction de la cible ? Comparez la cible principale avec la valeur épinglée active/par défaut. Pour une cible non principale avec un modèle configuré, comparez le modèle de routage à cette valeur épinglée. Si le profil omet
model, confirmez que le modèle de routage doit être transmis tel quel et que lesfallback_modelsdu profil ne seront pas matérialisés. - Chaque profil de repli conserve-t-il son propre point de terminaison, ses identifiants, son modèle, ses en-têtes et ses remplacements de capacités ?
- Qu’est-ce qui peut être réessayé, qu’est-ce qui avance immédiatement et quelle erreur est renvoyée après épuisement des tentatives ?
- Une requête peut-elle être rejouée après toute sortie déjà observée par un utilisateur ou un consommateur immuable ?
- Quel signal exact chaque parseur de fournisseur accepte-t-il comme marqueur d’achèvement ? Une fin de fichier avant ce signal est-elle traitée comme une troncature et entraîne-t-elle un échec ?
- Les identités demandées et effectivement servies du fournisseur et du modèle sont-elles conservées séparément ?
- L’utilisation, le coût, les journaux et les avis destinés aux utilisateurs sont-ils dérivés de la même tentative de traitement, ou la limitation fait-elle l’objet d’un suivi explicite ?
- Les tests en streaming et sans streaming couvrent-ils la même limite d’échec, là où le comportement est censé être le même ?
Pointeurs source
- Trait du fournisseur et événements de flux :
crates/zeroclaw-api/src/model_provider.rs - Sélection de la route :
crates/zeroclaw-providers/src/router.rs - Épinglage du modèle du profil :
crates/zeroclaw-providers/src/model_pin.rs - Réessai, délai d’attente, solution de secours et notifications de solution de secours :
crates/zeroclaw-providers/src/reliable.rs - Construction des fournisseurs et matérialisation du graphe de repli :
crates/zeroclaw-providers/src/lib.rs,crates/zeroclaw-providers/src/factory.rs - Garde de fin de flux du fournisseur :
crates/zeroclaw-providers/src/stream_guard.rs - Relecture du flux d’exécution et gestion des sorties partielles :
crates/zeroclaw-runtime/src/agent/turn/provider_call.rs,crates/zeroclaw-runtime/src/agent/turn/stream_consume.rs - Guides de l’opérateur : Configuration du fournisseur, Routage, Streaming