Registre des outils internes

Markdown
Deploy a clustered gateway for cross-process toolset discovery and invocation.

Le Internal Tool Registry est un service de passerelle en cluster qui permet la découverte et l’appel d’un ensemble d’outils au-delà des limites des processus. Il est conçu pour les scénarios dans lesquels les ensembles d’outils sont fournis par des services distincts qui peuvent évoluer indépendamment des agents qui les utilisent.

Le catalogue conserve aussi les déclarations natives d’outils d’agents, qui désignent un worker existant et une configuration applicative immuable. Le runtime consommateur lance un workflow enfant ; les baux des fournisseurs, les pings de santé et les appels Pulse concernent uniquement les outils de service.

Les API d’agents dynamiques décrites ici nécessitent Goa-AI v0.84.0 ou une version ultérieure.

Aperçu

Le registre fait office à la fois de catalogue et de passerelle :

  • Catalogue : les agents découvrent les ensembles d’outils disponibles, leurs schémas et leur état de santé
  • Passerelle : les appels d’outils sont acheminés via le registre vers les fournisseurs via les flux Pulse.

Cela dissocie les agents des fournisseurs d’outils, permettant une mise à l’échelle, un déploiement et une gestion du cycle de vie indépendants.

Registre d’outils vs registre d’invites

Il s’agit de différents systèmes avec des responsabilités différentes :

  • Registre d’outils interne (cette page) : découverte/invocation inter-processus d’ensembles d’outils et d’appels d’outils.
  • Runtime Prompt Registry (runtime.PromptRegistry) : enregistrement et rendu des spécifications d’invite en cours, éventuellement soutenu par un magasin de remplacement d’invite (runtime.WithPromptStore).

Le registre d’outils ne stocke pas les modèles d’invite et ne résout pas les remplacements d’invite. Le rendu rapide reste activé la couche d’exécution/planificateur et émet des événements d’observabilité prompt_rendered.

Agent-Registry-Provider Topology

Clustering multi-nœuds

Plusieurs nœuds de registre peuvent participer au même registre logique en utilisant le même Name dans leur configuration et en se connectant à la même instance Redis.

Nœuds portant le même nom automatiquement :

  • Partager les inscriptions des outils en lisant directement leur état dans Redis
  • Coordonner les pings de santé avec des baux Redis à expiration, acquis séparément pour chaque ensemble d’outils
  • Partager l’état de santé du fournisseur sur tous les nœuds

Cela permet une mise à l’échelle horizontale et une haute disponibilité. Les clients peuvent se connecter à n’importe quel nœud et voir le même état du registre.

Registry Cluster Architecture

Démarrage rapide

Utilisation de la bibliothèque

Créez et exécutez un nœud de registre par programme. registry.New initialise le catalogue et les enregistrements d’appels dans Redis, les flux Pulse et l’ordonnanceur de santé. Run démarre le serveur gRPC et attend l’arrêt. L’exemple utilise des adresses de développement locales ; configurez les identifiants Redis et gRPC adaptés à votre déploiement.

package main

import (
    "context"
    "log"

    "github.com/redis/go-redis/v9"
    "goa.design/goa-ai/registry"
)

func main() {
    ctx := context.Background()

    // Connect to Redis
    rdb := redis.NewClient(&redis.Options{
        Addr: "localhost:6379",
    })
    defer rdb.Close()

    // Create the registry
    reg, err := registry.New(ctx, registry.Config{
        Redis: rdb,
        Name:  "my-registry",  // Nodes with same name form a cluster
    })
    if err != nil {
        log.Fatal(err)
    }

    // Run the gRPC server (blocks until shutdown)
    log.Println("starting registry on :9090")
    if err := reg.Run(ctx, ":9090"); err != nil {
        log.Fatal(err)
    }
}

Exemple binaire

Le package de registre comprend un exemple de binaire pour un déploiement rapide. Tous les nœuds avec le même REGISTRY_NAME pointant vers la même instance Redis forment automatiquement un cluster : ils partagent les enregistrements d’ensemble d’outils et coordonnent les contrôles d’état sans configuration supplémentaire.

# Single node (development)
REDIS_URL=localhost:6379 go run ./registry/cmd/registry

# Multi-node cluster (production)
REGISTRY_NAME=prod REGISTRY_ADDR=:9090 REDIS_URL=redis:6379 ./registry
REGISTRY_NAME=prod REGISTRY_ADDR=:9091 REDIS_URL=redis:6379 ./registry
REGISTRY_NAME=prod REGISTRY_ADDR=:9092 REDIS_URL=redis:6379 ./registry

Variables d’environnement

VariableDescriptionDéfaut
REGISTRY_ADDRAdresse d’écoute gRPC:9090
REGISTRY_NAMENom du cluster de registreregistry
REDIS_URLURL de connexion Redislocalhost:6379
REDIS_PASSWORDMot de passe Redis(aucun)
PING_INTERVALIntervalle de ping du contrôle de santé10s
MISSED_PING_THRESHOLDPings manqués avant un mauvais fonctionnement3

Architecture

Registry Internal Architecture

Composants

ComposantDescription
ServiceGestionnaires gRPC pour la découverte et l’invocation
CatalogueSchémas d’outils, jetons d’admission, baux de fournisseurs et historique des retraits stockés dans Redis
Suivi de la santéSurveille la vivacité du fournisseur via ping/pong
Gestionnaire de fluxGère les flux Pulse pour le routage des appels d’outils
Magasin des appelsConserve l’identité de la requête, l’affectation au fournisseur, les échéances, l’état de publication et le résultat terminal canonique

Flux d’appels d’outils

Lorsque CallTool est appelé, le registre effectue ces étapes dans l’ordre :

  1. Validation de l’identité et du schéma : le registre valide la charge utile et dérive un tool_use_id unique dans l’exécution. Une nouvelle tentative identique rejoint l’enregistrement conservé.
  2. Attente d’un fournisseur : un appel non publié attend qu’un fournisseur sain serve l’ensemble d’outils actif, dans la limite de son échéance d’exécution existante.
  3. Publication atomique : une seule opération Redis vérifie que le fournisseur choisi est toujours courant et ne se draine pas, puis ajoute la requête exactement une fois. Si un déploiement a changé de fournisseur après le contrôle de santé, l’appel non publié choisit le remplaçant et réessaie dans la même échéance.
  4. Exécution immuable : la publication réussie fixe l’affectation au fournisseur. L’appel ne peut plus être déplacé, car un effet externe peut avoir commencé.
  5. Livraison du résultat : CallTool renvoie le jeton exact du fournisseur, l’identité du flux de résultat, l’échéance d’exécution et celle de conservation. L’exécuteur lit ce flux jusqu’au résultat terminal ou jusqu’à ce que l’échéance règle l’appel.

Si l’échéance expire avant la publication, le registre enregistre call_not_admitted, ce qui permet à l’exécuteur de choisir un autre plan. Un appel publié dont le résultat reste incertain renvoie outcome_unknown et ne peut pas être remplacé.

Intégration du fournisseur (côté service)

Le routage du registre ne représente que la moitié du problème : les fournisseurs doivent exécuter une boucle d’exécution d’outils dans le processus de service propriétaire de l’ensemble d’outils. Avant d’invoquer un gestionnaire, le fournisseur appelle ClaimToolCall avec le contexte lié au cycle de vie de son worker et le délai maximal existant pour cet appel, indépendamment de l’échéance d’exécution du message. Le registre détermine si l’appel a expiré, dispose déjà d’un résultat final ou si une autre livraison en détient l’exécution. Dans ces cas, le fournisseur accuse réception du message sans invoquer le gestionnaire ni arrêter sa boucle d’exécution. Ce n’est qu’après une décision execute qu’il invoque le gestionnaire avec l’échéance d’exécution initiale du message, sans la prolonger.

Pour les ensembles d’outils appartenant au service et basés sur des méthodes (outils déclarés avec BindTo(...)), la génération de code émet un adaptateur de fournisseur à l’adresse :

  • gen/<service>/toolsets/<toolset>/provider.go

Le fournisseur généré :

  • Décode la charge utile de l’outil entrant JSON à l’aide du codec de charge utile généré
  • Construit la charge utile de la méthode Goa à l’aide des transformations générées
  • Appelle la méthode de service liée
  • Encode le résultat de l’outil JSON avec toutes les données de serveur déclarées à l’aide du codec de résultat généré

L’exemple ci-dessous utilise le module example.com/registry-provider, le service catalog et son ensemble d’outils search, lié aux méthodes du service et enregistré sous catalog.search. Remplacez les deux chemins d’import de l’application et le nom de l’ensemble d’outils par vos valeurs générées. NewProvider, ToolSchemas et SchemaFingerprint proviennent du package d’outils généré ; conservez les schémas générés intacts. Les callbacks d’enregistrement suivent l’exemple Service-Side Tool Providers du fichier AGENTS_QUICKSTART.md généré à la racine du module (Démarrage rapide).

Fournissez votre implémentation du service, un client Pulse construit avec pulse.New(pulse.Options{Redis: rdb}) et une connexion gRPC au registre créée avec grpc.NewClient et les identifiants de votre déploiement. Fournissez un providerID stable pour ce processus et cet ensemble d’outils, unique parmi les réplicas actifs, ainsi que l’admissionRevision obligatoire fournie par le déploiement et partagée par les réplicas du même enregistrement. Serve crée l’identifiant d’incarnation et le transmet aux callbacks. Les méthodes de service liées doivent respecter l’annulation du contexte. Exécutez serveTools dans le cycle de vie du service et attendez son retour avant de fermer l’un des clients. À l’arrêt, le fournisseur cesse d’accepter du travail et finalise les appels dont il détient l’exécution, leurs résultats et les accusés de réception dans la limite de Options.ShutdownTimeout. Seule une finalisation réussie permet de libérer le bail exact, avec le délai distinct Registration.ReleaseTimeout. En cas d’échec de finalisation, l’expiration du bail met fin à cette autorité. Conservez et signalez les erreurs de finalisation ou de libération, même si l’erreur renvoyée correspond aussi à context.Canceled. Tous les callbacks d’enregistrement requis sont configurés ci-dessous :

package providers

import (
	"context"
	"encoding/json"
	"time"

	gencatalog "example.com/registry-provider/gen/catalog"
	gensearch "example.com/registry-provider/gen/catalog/toolsets/search"
	"goa.design/goa-ai/features/stream/pulse/clients/pulse"
	genregistrygrpc "goa.design/goa-ai/registry/gen/grpc/registry/client"
	genregistry "goa.design/goa-ai/registry/gen/registry"
	registrywire "goa.design/goa-ai/runtime/toolregistry"
	"goa.design/goa-ai/runtime/toolregistry/provider"
	"google.golang.org/grpc"
)

// serveTools runs the generated catalog provider until shutdown or a provider error.
// The caller owns the clients, service implementation, and deployment identifiers.
func serveTools(ctx context.Context, pulseClient pulse.Client, conn *grpc.ClientConn,
	serviceImpl gencatalog.Service, providerID, admissionRevision string) error {
	const toolsetID = "catalog.search"
	transport := genregistrygrpc.NewClient(conn, grpc.WaitForReady(true))
	registryClient := genregistry.NewClient(
		transport.Register(),
		transport.RenewProvider(),
		transport.ReleaseProvider(),
		transport.DrainProvider(),
		transport.Unregister(),
		transport.Pong(),
		transport.RegisterAgentToolset(),
		transport.ReplaceAgentToolset(),
		transport.ListToolsets(),
		transport.GetToolset(),
		transport.ResolveToolset(),
		transport.CheckAdmission(),
		transport.Search(),
		transport.CallTool(),
		transport.CallResolvedTool(),
		transport.RetryTool(),
		transport.CompleteToolCall(),
		transport.PublishToolOutputDelta(),
		transport.ReportToolCallOverload(),
		transport.ClaimToolCall(),
	)
	toolSchemas := gensearch.ToolSchemas()
	handler := gensearch.NewProvider(serviceImpl)
	return provider.Serve(ctx, pulseClient, toolsetID, handler,
		provider.Registration{
			AdmissionRevision: admissionRevision,
			Register: func(ctx context.Context, toolset, providerID, incarnationID, admissionRevision string) (provider.RegistrationLease, error) {
				schemaFingerprint, err := gensearch.SchemaFingerprint(toolset)
				if err != nil {
					return provider.RegistrationLease{}, err
				}
				result, err := registryClient.Register(ctx, &genregistry.RegisterPayload{
					Name:                  toolset,
					Tools:                 toolSchemas,
					ProviderID:            providerID,
					ProviderIncarnationID: incarnationID,
					AdmissionRevision:     admissionRevision,
					WireProtocolVersion:   registrywire.WireProtocolVersion,
					SchemaFingerprint:     schemaFingerprint,
				})
				if err != nil {
					return provider.RegistrationLease{}, err
				}
				return provider.RegistrationLease{
					RegistrationToken: result.RegistrationToken,
					Duration:          time.Duration(result.LeaseDurationMs) * time.Millisecond,
				}, nil
			},
			Renew: func(ctx context.Context, toolset, providerID, incarnationID, expectedToken string) (time.Duration, error) {
				result, err := registryClient.RenewProvider(ctx, &genregistry.RenewProviderPayload{
					Name: toolset,
					ProviderID: providerID,
					ProviderIncarnationID: incarnationID,
					ExpectedRegistrationToken: expectedToken,
				})
				if err != nil {
					return 0, err
				}
				return time.Duration(result.LeaseDurationMs) * time.Millisecond, nil
			},
			Drain: func(ctx context.Context, toolset, providerID, incarnationID, expectedToken string, settlementDuration time.Duration) error {
				return registryClient.DrainProvider(ctx, &genregistry.DrainProviderPayload{
					Name:                      toolset,
					ProviderID:                providerID,
					ProviderIncarnationID:     incarnationID,
					ExpectedRegistrationToken: expectedToken,
					SettlementDurationMs:      settlementDuration.Milliseconds(),
				})
			},
			Release: func(ctx context.Context, toolset, providerID, incarnationID, expectedToken string) error {
				return registryClient.ReleaseProvider(ctx, &genregistry.ReleaseProviderPayload{
					Name:                      toolset,
					ProviderID:                providerID,
					ProviderIncarnationID:     incarnationID,
					ExpectedRegistrationToken: expectedToken,
				})
			},
			Complete: func(ctx context.Context, toolset, providerID, incarnationID, providerToken, requestEventID string, result registrywire.ToolResultMessage) error {
				resultJSON, err := json.Marshal(result)
				if err != nil {
					return err
				}
				return registryClient.CompleteToolCall(ctx, &genregistry.CompleteToolCallPayload{
					Toolset:                   toolset,
					ProviderID:                providerID,
					ProviderIncarnationID:     incarnationID,
					RegistrationToken:         result.RegistrationToken,
					ToolUseID:                 result.ToolUseID,
					ResultJSON:                resultJSON,
					RequestEventID:            requestEventID,
					ProviderRegistrationToken: providerToken,
				})
			},
			PublishOutputDelta: func(ctx context.Context, toolset, providerID, incarnationID, providerToken, callToken, toolUseID, requestEventID, stream, delta string) error {
				return registryClient.PublishToolOutputDelta(ctx, &genregistry.PublishToolOutputDeltaPayload{
					Toolset:                   toolset,
					ProviderID:                providerID,
					ProviderIncarnationID:     incarnationID,
					ProviderRegistrationToken: providerToken,
					CallRegistrationToken:     callToken,
					ToolUseID:                 toolUseID,
					RequestEventID:            requestEventID,
					Stream:                    stream,
					Delta:                     delta,
				})
			},
			ReportOverload: func(ctx context.Context, toolset, providerID, incarnationID, providerToken, callToken, toolUseID, requestEventID string) error {
				return registryClient.ReportToolCallOverload(ctx, &genregistry.ProviderToolCallClaimPayload{
					Toolset:                   toolset,
					ProviderID:                providerID,
					ProviderIncarnationID:     incarnationID,
					ProviderRegistrationToken: providerToken,
					CallRegistrationToken:     callToken,
					ToolUseID:                 toolUseID,
					RequestEventID:            requestEventID,
				})
			},
			Claim: func(ctx context.Context, claim provider.ClaimRequest) (provider.ClaimDisposition, error) {
				result, err := registryClient.ClaimToolCall(ctx, &genregistry.ClaimToolCallPayload{
					Toolset:                   claim.Toolset,
					ProviderID:                claim.ProviderID,
					ProviderIncarnationID:     claim.ProviderIncarnationID,
					ProviderRegistrationToken: claim.ProviderRegistrationToken,
					CallRegistrationToken:     claim.CallRegistrationToken,
					ToolUseID:                 claim.ToolUseID,
					RequestEventID:            claim.RequestEventID,
					ClaimOperationID:          claim.OperationID,
				})
				if err != nil {
					return "", err
				}
				return provider.ClaimDisposition(result.Disposition), nil
			},
		},
		provider.Options{
			ProviderID: providerID,
			Pong: func(ctx context.Context, providerID, incarnationID, pingID string) error {
				return registryClient.Pong(ctx, &genregistry.PongPayload{
					PingID:                pingID,
					Toolset:               toolsetID,
					ProviderID:            providerID,
					ProviderIncarnationID: incarnationID,
				})
			},
		},
	)
}

Les ID de flux sont déterministes :

  • Appels d’outil : toolset:<toolsetID>:requests
  • Résultats : result:<toolUseID>

Configuration

Options du registre

L’exemple de la bibliothèque montre la configuration minimale : passez le client Redis de l’application dans Redis et choisissez un Name partagé pour le cluster. Les nœuds utilisant le même nom et la même base Redis partagent le catalogue, les enregistrements d’appels et la coordination des contrôles de santé. Chaque nœud lit directement l’état compact du catalogue dans Redis ; les définitions complètes sont stockées séparément.

Consultez registry.Config pour l’API complète et les valeurs par défaut. PingInterval et MissedPingThreshold règlent les contrôles de santé ; ExecutionTimeout borne les nouvelles exécutions admises ; ResultStreamTTL règle la conservation des résultats ; ProviderLeaseDuration règle le renouvellement des inscriptions des fournisseurs. ExpectedToolsets signale les noms requis dans la télémétrie sans rejeter d’inscription ni d’appel. Logger reçoit les erreurs de finalisation des appels. Définissez ces options lors de la construction du registre.

Stockage Redis

Redis conserve les schémas d’outils, les identités d’admission, les baux des fournisseurs, les horodatages de santé et l’historique des retraits dans le catalogue. Les enregistrements d’appels et les flux Pulse de requêtes et de résultats utilisent aussi Redis. Utilisez un Redis durable pour que les répliques et les processus redémarrés observent les mêmes inscriptions et décisions d’appel. L’application possède le client Redis et le ferme après l’arrêt du registre.

Renouvellement des fournisseurs et mise à niveau du stockage

Les fournisseurs envoient les schémas générés une fois au démarrage. Le callback obligatoire Renew appelle RenewProvider avec le nom de l’ensemble d’outils, l’identifiant du fournisseur, celui de son incarnation et le jeton d’inscription attendu. Il renvoie uniquement la durée accordée au bail. Le renouvellement ne crée aucun bail, ne change pas son jeton et n’annule pas le drainage. Un bail absent, expiré, remplacé ou retiré renvoie provider_lease_lost et Serve s’arrête. Les erreurs de communication temporaires sont réessayées uniquement avant la limite du bail existant. La réparation des flux, groupes et pings reste prise en charge.

Le registre sépare l’état compact d’admission, des baux, de santé et de découverte des définitions complètes et des jetons définitivement retirés. L’inscription met à jour les données concernées atomiquement. Les contrôles de santé et les opérations sur les baux ne transfèrent que l’état compact. Chaque processus réutilise les définitions par empreinte et conserve les validateurs compilés ; Get et Resolve renvoient des valeurs complètes indépendantes.

La migration de l’ancien catalogue combiné exige une maintenance coordonnée. Arrêtez les nouvelles demandes, terminez les appels acceptés, arrêtez proprement les fournisseurs pendant que l’ancien registre peut encore drainer et libérer leurs baux, puis arrêtez tous les processus écrivant dans l’ancien registre. Sauvegardez et convertissez le catalogue hors ligne en préservant chaque définition, identité, bail, horodatage et jeton retiré. Conservez les appels, flux, index de finalisation et échéances de rétention. Démarrez le nouveau registre et les fournisseurs mis à jour ; reprenez après validation stricte au démarrage et réussite des contrôles CheckAdmission exacts. Les empreintes des outils et la version des messages fournisseurs ne changent pas. Ajoutez Renew et régénérez les clients. Ne mélangez jamais les anciens et nouveaux processus d’écriture. Restaurez la sauvegarde intacte uniquement tant que tous sont arrêtés et qu’aucune nouvelle écriture n’a repris ; ensuite, corrigez avec une nouvelle version. Le démarrage normal ne décode pas l’ancien format.

Surveillance de la santé

Le registre envoie les pings de santé sur les flux Pulse. Les fournisseurs répondent par la méthode gRPC Pong.

Comment ça marche

  1. L’ordonnanceur de santé lit les ensembles d’outils actifs dans le catalogue partagé.
  2. Le nœud détenant le bail de ping d’un ensemble d’outils envoie un ping tant qu’un fournisseur actif accepte les appels.
  3. Pong met à jour le catalogue uniquement si la réponse correspond à l’inscription, au processus fournisseur et à l’identité du contrôle de santé actuels.
  4. Le routage exige un bail fournisseur non expiré acceptant de nouveaux appels et un pong accepté suffisamment récent.

La santé est déduite du catalogue à partir de l’heure Redis. L’âge du dernier pong accepté ne doit pas dépasser (MissedPingThreshold + 1) × PingInterval. Un appel non publié attend un fournisseur sain uniquement dans la limite de son échéance d’exécution existante.

Coordination distribuée

Chaque nœud exécute un ordonnanceur local et tente d’acquérir un bail Redis à expiration pour chaque ensemble d’outils. Le nœud qui obtient le bail effectue ce contrôle de santé ; après expiration, un autre peut l’acquérir. Les noms des baux sont propres au cluster du registre.

Les baux fournisseurs, l’identité actuelle du contrôle de santé et le dernier pong accepté sont conservés ensemble dans le catalogue. Chaque nœud déduit la santé de cet enregistrement : une réponse tardive d’un ancien fournisseur ne peut donc pas rendre l’inscription actuelle saine.

Intégration client

Utilisez le client de service généré du registre pour les API fournisseur et d’invocation. Pour découvrir le catalogue, runtime/registry.NewClient enveloppe ce même client et expose ListToolsets, GetToolset et Search, avec les types de ressources utilisés par runtime/registry.Manager.

L’exemple liste le catalogue et récupère le schéma complet d’un ensemble d’outils nommé. Passez une connexion créée avec grpc.NewClient et les identifiants de votre déploiement ; l’appelant conserve la propriété de cette connexion. Tous les endpoints du client généré sont connectés, comme dans l’exemple fournisseur ci-dessus.

package discovery

import (
	"context"

	genregistrygrpc "goa.design/goa-ai/registry/gen/grpc/registry/client"
	genregistry "goa.design/goa-ai/registry/gen/registry"
	runtimeregistry "goa.design/goa-ai/runtime/registry"
	"google.golang.org/grpc"
)

// discoverTools lists the catalog and retrieves the schema of the named toolset.
// The caller creates the gRPC connection and keeps it open during discovery.
func discoverTools(ctx context.Context, conn *grpc.ClientConn, toolsetName string) (
	[]*runtimeregistry.ToolsetInfo, *runtimeregistry.ToolsetSchema, error,
) {
	transport := genregistrygrpc.NewClient(conn, grpc.WaitForReady(true))
	generated := genregistry.NewClient(
		transport.Register(),
		transport.RenewProvider(),
		transport.ReleaseProvider(),
		transport.DrainProvider(),
		transport.Unregister(),
		transport.Pong(),
		transport.RegisterAgentToolset(),
		transport.ReplaceAgentToolset(),
		transport.ListToolsets(),
		transport.GetToolset(),
		transport.ResolveToolset(),
		transport.CheckAdmission(),
		transport.Search(),
		transport.CallTool(),
		transport.CallResolvedTool(),
		transport.RetryTool(),
		transport.CompleteToolCall(),
		transport.PublishToolOutputDelta(),
		transport.ReportToolCallOverload(),
		transport.ClaimToolCall(),
	)
	client := runtimeregistry.NewClient(generated)
	toolsets, err := client.ListToolsets(ctx)
	if err != nil {
		return nil, nil, err
	}
	schema, err := client.GetToolset(ctx, toolsetName)
	if err != nil {
		return nil, nil, err
	}
	return toolsets, schema, nil
}

gRPC API

Le registre expose les méthodes gRPC suivantes :

Opérations du fournisseur

MéthodeDescription
RegisterAdmet un fournisseur au démarrage avec ses définitions générées. Un contrat différent attend la fin des anciens baux.
RenewProviderProlonge le bail exact non expiré sans envoyer les définitions. Préserve le drainage et toute échéance de finalisation plus longue ; la perte du bail renvoie provider_lease_lost.
DrainProviderRend un bail indisponible pour les nouveaux appels tout en conservant son autorité sur les appels déjà admis.
ReleaseProviderRetire le bail exact après que le processus a réglé le travail accepté.
UnregisterRetire de la découverte l’enregistrement courant exact. Les jetons de services sont retirés définitivement ; les déclarations natives peuvent être réactivées avec ReplaceAgentToolset. Les appels enfants natifs déjà acceptés gardent leur déclaration.
PongEnregistre la santé pour le bail et l’époque de contrôle exacts.
ClaimToolCallAccorde l’exécution d’une requête publiée à un bail exact.
CompleteToolCallValide le résultat terminal canonique d’un appel réclamé et le publie dans le flux de résultat.
PublishToolOutputDeltaPublie un fragment de progression limité et au mieux pour un appel réclamé.
ReportToolCallOverloadEnregistre une commande de nouvelle tentative limitée avant l’exécution d’un appel en surcharge.

Opérations d’agents natifs

MéthodeDescription
RegisterAgentToolsetCrée une déclaration native sans bail de fournisseur ; répéter un enregistrement actif identique réussit.
ReplaceAgentToolsetRemplace ou réactive une déclaration native avec son jeton courant. Un jeton périmé renvoie admission_conflict.

Opérations de découverte

MéthodeDescription
ListToolsetsRépertoriez tous les ensembles d’outils enregistrés (avec filtrage de balises facultatif). Renvoie uniquement les métadonnées, pas les schémas complets.
GetToolsetObtenez le schéma complet pour un ensemble d’outils spécifique, y compris tous les schémas d’entrée/sortie des outils.
ResolveToolsetLit ensemble une définition active et son jeton exact ; la santé du fournisseur fait l’objet d’un contrôle distinct.
CheckAdmissionVérifie qu’un enregistrement de service exact possède un bail non expiré, hors drainage, et une réponse de santé récente.
SearchRecherchez des ensembles d’outils par mot-clé correspondant au nom, à la description ou aux balises.

Opérations d’appel

MéthodeDescription
CallToolAppelle un outil via le registre, rejoint une tentative identique, attend un fournisseur sain dans l’échéance existante, publie atomiquement puis renvoie l’identité exacte nécessaire pour lire le résultat.
CallResolvedToolPublie un appel de service uniquement avec son jeton conservé ; un remplacement avant publication enregistre call_not_admitted.
RetryToolRepublie l’admission originale exacte après une surcharge enregistrée. Ne transfère jamais l’exécution à un fournisseur de remplacement.

Meilleures pratiques

Déploiement

  • Utilisez le même Name pour tous les nœuds d’un cluster afin de partager le catalogue et les appels et de coordonner les contrôles de santé
  • Pointez vers la même instance Redis pour la coordination de l’État
  • Déployer derrière un équilibreur de charge pour les connexions client : tous les nœuds servent un état identique
  • Utilisez un Redis durable pour le catalogue, les enregistrements d’appels et les flux Pulse afin que les répliques et les processus redémarrés observent les mêmes décisions

Surveillance de la santé

  • Configurez PingInterval et MissedPingThreshold selon la fréquence des contrôles et l’âge de pong toléré. Consultez registry.Config pour les valeurs par défaut.
  • Observez la télémétrie du catalogue et de santé pour distinguer les ensembles d’outils absents des fournisseurs temporairement indisponibles pour de nouveaux appels.
  • Conservez l’échéance d’exécution : les appels non publiés attendent le rétablissement d’un fournisseur uniquement jusqu’à leur échéance existante.

Mise à l’échelle

  • Ajoutez des nœuds pour gérer davantage de connexions gRPC : chaque nœud peut répondre à n’importe quelle requête.
  • Les nœuds coordonnent les contrôles de santé avec des baux Redis à expiration pour chaque ensemble d’outils
  • Aucune session persistante n’est requise : les flux de résultats utilisent Redis pour la livraison entre nœuds, de sorte qu’un appel d’outil peut être lancé sur un nœud et terminé sur un autre.

Prochaines étapes

Consultez Recherche d’outils et catalogues dynamiques pour la résolution actuelle, les contrats générés, les fournisseurs et la migration.

Dernière modification September 21, 2026: Document dynamically configured Agent tools (#267) (764fa08)