Agents ambiants
Exécuter un agent ambiant
Un gestionnaire de dĂ©clencheur est requis. Le chemin pris en charge est with_invoker, qui accepte tout ce qui implĂ©mente adk_core::AgentInvoker â Runner le fait :
use adk_agent::ambient::{AmbientAgent, RunnerTriggerConfig};
use std::sync::Arc;
let mut ambient = AmbientAgent::new(agent, source)
.with_invoker(runner, RunnerTriggerConfig::new("system"))
.with_max_concurrent_triggers(4);
let mut outputs = ambient.take_output(64);
ambient.start().await?;
RunnerTriggerConfig contrÎle trois éléments :
| Méthode | Objectif | Valeur par défaut |
|---|---|---|
new(user_id) | IdentitĂ© sous laquelle les exĂ©cutions sont enregistrĂ©es. Un dĂ©clencheur nâa pas dâutilisateur interactif ; utilisez donc "system" ou le nom dâun compte de service. | obligatoire |
with_session_policy | PerTrigger attribue à chaque événement sa propre session ; Shared(id) en réutilise une. | PerTrigger |
with_prompt | Transforme lâĂ©vĂ©nement en texte dâinvite. | indique la source et sĂ©rialise la charge utile |
PerTrigger est utilisĂ© par dĂ©faut, car un dĂ©clenchement chaque minute dans une session partagĂ©e unique fait croĂźtre sans limite l'historique de cette session â ainsi que le coĂ»t en jetons de chaque exĂ©cution ultĂ©rieure.
Runner sĂ©rialise les tours invoquĂ©s de l'extĂ©rieur ciblant la mĂȘme session partagĂ©e jusqu'Ă la fin de chaque flux d'Ă©vĂ©nements, tandis que des identifiants de session diffĂ©rents peuvent toujours s'exĂ©cuter simultanĂ©ment.
AgentInvoker::invoke crée la session lorsqu'elle n'existe pas. Runner::run ne le fait pas : il
résout une session existante et produit session.not_found via le flux dans le cas contraire, ce qui
laisse à une exécution déclenchée de l'extérieur aucune possibilité de la préenregistrer.
Lorsque l'appelant expose sa racine exécutable, comme le fait Runner, with_invoker utilise cet agent pour
la journalisation et les diagnostics ambiants. Cela empĂȘche la tĂ©lĂ©mĂ©trie de nommer un agent alors qu'un autre
traite effectivement le déclencheur.
Fournir directement un gestionnaire
with_trigger_handler reste disponible pour les appelants qui pilotent autre chose qu'un Runner. Il
reçoit l'événement et l'agent, et doit retourner le flux d'événements ; la création de la session relÚve alors de la responsabilité du gestionnaire.
use adk_agent::ambient::{AmbientAgent, TriggerHandler};
use std::sync::Arc;
let handler: TriggerHandler = Arc::new(move |event, agent| {
let backend = backend.clone();
Box::pin(async move { backend.dispatch(event, agent).await })
});
let mut ambient = AmbientAgent::new(agent, source).with_trigger_handler(handler);
start échoue sans gestionnaire :
AmbientAgent has no trigger handler, so starting it would log trigger events without ever
invoking the agent. Call `with_trigger_handler` with a closure that drives the agent through a
Runner.
Important : démarrer sans gestionnaire réussissait auparavant, puis journalisait chaque déclencheur, de sorte que
AmbientAgent::new(..).start()donnait l'impression d'exécuter un agent qui ne s'était jamais exécuté.
Sortie et concurrence
| Comportement | ContrĂŽle |
|---|---|
| Les Ă©vĂ©nements et les erreurs produits par lâagent sont transmis Ă un canal | take_output(capacity) |
| Déclencheurs traités simultanément | with_max_concurrent_triggers (4 par défaut, zéro est traité comme un) |
Les Ă©vĂ©nements produits Ă©taient auparavant consignĂ©s au niveau de dĂ©bogage, puis ignorĂ©s, de sorte quâun appelant ne pouvait pas observer ce quâune exĂ©cution avait fait ni dĂ©terminer si elle avait Ă©chouĂ©. Les dĂ©clencheurs Ă©taient Ă©galement traitĂ©s strictement un par un : la boucle Ă©puisait tout le flux dâĂ©vĂ©nements dâun gestionnaire avant dâinterroger Ă nouveau la source ; ainsi, un dĂ©clencheur lent bloquait tous les suivants.
Remarque : la limite régit la concurrence, et non le parallélisme. Les gestionnaires partagent la tùche ambiante ; un gestionnaire qui bloque le thread immobilise donc toujours la boucle. Utilisez
tokio::task::spawn_blockingdans ce cas. La responsabilitĂ© des dĂ©calages persistants des dĂ©clencheurs, de la gestion des lettres mortes et des nouvelles tentatives reste Ă la charge de lâappelant. Les agents ambiants rĂ©agissent aux Ă©vĂ©nements plutĂŽt quâĂ une intervention de lâutilisateur. UnEventSourceproduit desTriggerEvents et lâagent sâexĂ©cute pour chacun dâeux.
Nécessite la fonctionnalité ambient :
[dependencies]
adk-agent = { version = "2.1.0", features = ["ambient"] }
Sources dâĂ©vĂ©nements
| Source | Se déclenche sur | Principal |
|---|---|---|
CronTrigger | Une planification cron | None â une planification nâa aucun appelant |
FileWatchTrigger | Une modification correspondante du systĂšme de fichiers | None â une modification de fichier nâa aucun appelant |
WebhookTrigger | Un POST HTTP autorisé | Le résultat du vérificateur |
TriggerEvent::principal permet Ă un gestionnaire de distinguer un dĂ©clencheur autorisĂ© dâun dĂ©clencheur anonyme, plutĂŽt que de traiter chaque Ă©vĂ©nement comme Ă©tant Ă©galement digne de confiance.
Tics manqués
CronTrigger::subscribe calcule le prochain tic Ă partir du moment oĂč il est appelĂ©. Un dĂ©clencheur qui redĂ©marre aprĂšs une interruption, ou qui sâexĂ©cute sur un hĂŽte suspendu, reprend donc au prochain tic futur, et chaque tic arrivĂ© Ă Ă©chĂ©ance entre-temps est ignorĂ©.
MissedTickPolicy détermine ce qui se passe pendant cet intervalle :
| Politique | Comportement | Ă utiliser pour |
|---|---|---|
Skip | Ignorer les intervalles Ă©coulĂ©s et attendre le prochain intervalle planifiĂ©. Par dĂ©faut. | Planifications pour lesquelles une exĂ©cution tardive nâa aucune valeur |
CoalesceOne | Ămettre un Ă©vĂ©nement couvrant toute la pĂ©riode Ă©coulĂ©e. | Parcours pour lesquels seul lâĂ©tat actuel importe |
All | Ămet un Ă©vĂ©nement par cycle Ă©coulĂ©, du plus ancien au plus rĂ©cent. | Planifications oĂč chaque occurrence possĂšde son propre travail |
Une stratĂ©gie Ă elle seule ne couvre que les lacunes au sein dâun mĂȘme abonnement. DĂ©tecter une lacune qui sâĂ©tend sur un redĂ©marrage du processus nĂ©cessite un TickWatermark pour enregistrer lâendroit oĂč la planification sâest arrĂȘtĂ©e :
use std::sync::Arc;
use adk_agent::ambient::{CronTrigger, FileTickWatermark, MissedTickPolicy};
let trigger = CronTrigger::new("0 */5 * * * *")?
.with_missed_tick_policy(MissedTickPolicy::CoalesceOne)
.with_watermark(Arc::new(FileTickWatermark::new("/var/lib/my-agent/sweep.tick")));
FileTickWatermark stocke un curseur RFC 3339. Il écrit via un fichier temporaire frÚre unique,
le synchronise, puis remplace atomiquement la destination sous Unix et Windows. Implémentez
TickWatermark pour les autres magasins de sauvegarde.
Limiter une relecture
All selon une planification fréquente peut laisser des milliers de déclenchements en attente aprÚs une longue panne.
with_max_catch_up limite le nombre de relectures effectuées en un seul passage (64 par défaut) ; une fois la limite atteinte, le
reste de la lacune est abandonné, le curseur persistant avance au-delà de celle-ci et le déclencheur reprend au
prochain déclenchement futur, en journalisant le nombre de déclenchements supprimés. Un redémarrage avant le
prochain déclenchement ordinaire ne récupÚre pas le reste abandonné.
Contrat de livraison
Le filigrane avance lorsque le dĂ©clencheur Ă©met un dĂ©clenchement, et non lorsque le consommateur termine son traitement. Un plantage entre lâĂ©mission et lâachĂšvement supprime cette exĂ©cution au lieu de la rĂ©pĂ©ter â au plus une fois, et non au moins une fois. Câest ce qui empĂȘche un consommateur qui cesse dâinterroger le systĂšme de rejouer la mĂȘme lacune Ă chaque redĂ©marrage. Les consommateurs dont le travail doit survivre Ă un plantage en cours dâexĂ©cution doivent enregistrer leur propre Ă©tat dâachĂšvement. Si un filigrane configurĂ© ne peut pas ĂȘtre persistĂ©, le flux cron sâarrĂȘte avant dâĂ©mettre lâĂ©vĂ©nement concernĂ©, plutĂŽt que dâaffaiblir silencieusement cette garantie.
Déclencheurs webhook
Un webhook accessible constitue un point dâentrĂ©e distant vers la logique de lâapplication ; WebhookTrigger
est donc dĂ©fini par dĂ©faut sur lâinterface de bouclage et refuse de servir une adresse plus large sans vĂ©rificateur.
Développement local
use adk_agent::ambient::WebhookTrigger;
// Binds 127.0.0.1 â reachable only from this host.
let trigger = WebhookTrigger::new(8080, "/webhook");
Les écouteurs exposés nécessitent un vérificateur
use adk_agent::ambient::{WebhookRequest, WebhookTrigger, WebhookVerifier};
use std::net::SocketAddr;
use std::sync::Arc;
#[derive(Debug)]
struct SharedToken(String);
impl WebhookVerifier for SharedToken {
fn verify(&self, request: &WebhookRequest<'_>) -> Result<String, String> {
match request.header("x-webhook-token") {
Some(value) if value == self.0 => Ok("ci-system".to_string()),
Some(_) => Err("token mismatch".to_string()),
None => Err("missing x-webhook-token".to_string()),
}
}
}
let address: SocketAddr = "0.0.0.0:8080".parse().unwrap();
let trigger = WebhookTrigger::new(8080, "/webhook")
.with_bind_address(address)
.with_verifier(Arc::new(SharedToken(std::env::var("WEBHOOK_TOKEN").unwrap_or_default())));
Sâabonner Ă une adresse qui nâest pas de bouclage local sans vĂ©rificateur Ă©choue avec
agent.ambient.webhook_unauthenticated. La vĂ©rification a lieu au moment de lâabonnement, lorsque
lâerreur est encore facile Ă corriger.
Important : un vérificateur reçoit le corps brut via
WebhookRequest::body, car les schĂ©mas de signature sont calculĂ©s sur les octets exacts reçus. VĂ©rifiez avant de faire confiance Ă toute forme analysĂ©e de la requĂȘte.
Traitement des requĂȘtes
| Condition | RĂ©ponse | ĂvĂ©nement |
|---|---|---|
Corps dépassant la limite (1 MiB par défaut, with_max_body_bytes) | 413 | aucun |
| Le vérificateur rejette | 401 | aucun |
| Le corps nâest pas valide JSON | 400 | aucun |
Le corps nâest pas valide JSON, accept_non_json() dĂ©fini | 200 | la charge utile est une chaĂźne JSON |
| LâabonnĂ© a disparu | 503 | aucun |
| Sinon | 200 | la charge utile est le JSON analysé |
Un 401 ne contient aucun dĂ©tail sur la partie de lâidentifiant qui a Ă©chouĂ© ; la raison est
plutĂŽt enregistrĂ©e, de sorte que le point de terminaison ne peut pas ĂȘtre utilisĂ© pour sonder les
identifiants.
accept_non_json est désactivé par défaut, car un corps malformé encapsulé en tant que chaßne produit un
Ă©vĂ©nement de dĂ©clenchement impossible Ă distinguer dâun Ă©vĂ©nement dĂ©libĂ©rĂ©.
Durée de vie
LâĂ©couteur HTTP appartient au flux renvoyĂ© par subscribe. La suppression du flux arrĂȘte
gracieusement le serveur et libĂšre le port, de sorte que le mĂȘme port peut ĂȘtre rĂ©affectĂ© :
let stream = trigger.subscribe().await?;
// ... consume events ...
drop(stream); // the listener stops and the port is free
Remarque : auparavant, lâĂ©couteur survivait Ă son consommateur. La suppression du flux laissait le serveur liĂ©, continuant dâaccepter des requĂȘtes quâil ne pouvait pas transmettre, et un redĂ©marrage sur le mĂȘme port Ă©chouait.