Fonctionnel API
Ăcrivez des workflows dâagents sous forme de fonctions Rust asynchrones normales, avec une sauvegarde automatique des points de contrĂŽle, des rĂ©ducteurs dâĂ©tat typĂ©s et la prise en charge des interruptions et reprises.
Vue dâensemble
Le API (fonctionnalitĂ© functional dans adk-graph) offre une alternative de niveau supĂ©rieur Ă la construction explicite de graphes/nĆuds/arĂȘtes. Au lieu de construire manuellement un StateGraph, vous annotez les fonctions avec les macros #[entrypoint] et #[task] et utilisez le flux de contrĂŽle standard de Rust.
Avantages principaux :
- Ăcrire des workflows en Rust asynchrone normal â aucun DSL de graphe requis
- Sauvegarde automatique dâun point de contrĂŽle aprĂšs chaque tĂąche pour la rĂ©cupĂ©ration aprĂšs incident
- Flux de contrĂŽle Rust standard (if/else, for, loop, match)
- Conteneurs dâĂ©tat typĂ©s avec garanties de persistance
- Compatible avec lâinfrastructure StreamEvent et Checkpointer existante
Pour commencer
[dependencies]
adk-graph = { version = "2.1.0", features = ["functional"] }
adk-rust-macros = "2.1.0"
Types principaux
TaskContext
Le contexte dâexĂ©cution transmis Ă toutes les fonctions du workflow. Fournit un accĂšs Ă lâĂ©tat, aux points de contrĂŽle, aux interruptions et au streaming.
use adk_graph::functional::TaskContext;
// Read state
let count: Option<i64> = ctx.get("counter");
// Write state (uses configured reducer)
ctx.set("counter", serde_json::json!(count.unwrap_or(0) + 1));
// Emit progress events
ctx.emit(StreamEvent::custom("my_task", "progress", json!({"pct": 50})));
// Interrupt for human-in-the-loop
let approval: bool = ctx.interrupt("Please approve").await?;
ReducedValue<T>
Conteneur dâĂ©tat en ajout seulement, conservĂ© entre les points de contrĂŽle. Les valeurs sâaccumulent et ne sont jamais Ă©crasĂ©es.
use adk_graph::functional::ReducedValue;
let mut results: ReducedValue<String> = ReducedValue::new();
results.push("step 1 output".to_string());
results.push("step 2 output".to_string());
assert_eq!(results.len(), 2);
assert_eq!(&results[0], "step 1 output");
UntrackedValue<T>
Valeurs dâexĂ©cution temporaires exclues de la persistance des points de contrĂŽle. RĂ©initialisĂ©es Ă leur valeur par dĂ©faut lors de la reprise.
use adk_graph::functional::UntrackedValue;
let mut temp: UntrackedValue<Vec<u8>> = UntrackedValue::new();
temp.set(vec![1, 2, 3]);
// After checkpoint restore: temp.get() == &[]
MessagesValue
Conteneur de messages de discussion avec déduplication automatique fondée sur les identifiants des messages.
use adk_graph::functional::{MessagesValue, ChatMessage, MessageRole};
let mut messages = MessagesValue::new();
messages.push(ChatMessage {
id: "msg-1".to_string(),
role: MessageRole::User,
content: "Hello".to_string(),
metadata: None,
});
// Pushing same ID replaces the message (upsert)
StateSchemaValidator
Valide les types dâĂ©tat aux limites du workflow â dĂ©tecte rapidement les incompatibilitĂ©s.
use adk_graph::functional::{StateSchemaValidator, ExpectedType};
use adk_graph::state::StateSchema;
let validator = StateSchemaValidator::new(schema)
.expect_type("counter", ExpectedType::Number)
.expect_type("status", ExpectedType::String)
.require_field("status");
validator.validate_state(&state)?; // Fails with descriptive error
ExecutionLog
Suit lâachĂšvement des tĂąches pour le comportement dâignorance lors de la reprise. Les tĂąches terminĂ©es sont ignorĂ©es lors de la reprise du workflow.
use adk_graph::functional::ExecutionLog;
let mut log = ExecutionLog::new();
log.record_start("fetch");
log.record_completion("fetch", json!({"data": [1,2,3]}));
// On resume: skip completed tasks
if log.is_completed("fetch") {
let cached = log.get_result("fetch"); // Returns cached result
}
Exécutions en arriÚre-plan
La fonctionnalitĂ© background dans adk-server ajoute des points de terminaison REST pour lâexĂ©cution asynchrone des workflows.
Points de terminaison
| Méthode | Chemin | Description |
|---|---|---|
| POST | /runs | Soumettre une exécution en arriÚre-plan |
| GET | /runs/{run_id} | Interroger lâĂ©tat de lâexĂ©cution |
| SUPPRIMER | /runs/{run_id} | Annuler une exécution |
Utilisation
Une exécution nomme un workflowId. Quelque chose doit transformer ce nom en tùche ; il faut donc enregistrer un
WorkflowExecutor â sans cela, une exĂ©cution soumise Ă©choue au lieu de signaler
son achĂšvement :
use adk_server::background::{
BackgroundState, WorkflowRegistry, background_runs_router_with_state,
};
use serde_json::json;
use std::sync::Arc;
let registry = WorkflowRegistry::new().register("summarize", |input, cancel| async move {
if cancel.is_cancelled() {
return Err("cancelled before starting".to_string());
}
Ok(json!({ "summary": "âŠ", "of": input.get("document") }))
});
let state = BackgroundState::new().with_executor(Arc::new(registry));
let app = axum::Router::new().merge(background_runs_router_with_state(state));
La soumission dâun workflowId non enregistrĂ© renvoie 404 au lieu de mettre en file une exĂ©cution qui
ne pourra jamais ĂȘtre exĂ©cutĂ©e. ImplĂ©mentez directement WorkflowExecutor pour faire le lien avec adk-graph, le
API fonctionnel, ou votre propre rĂ©partiteur ; renvoyez Err pour faire Ă©chouer lâexĂ©cution, ce qui rend
le budget de nouvelles tentatives pertinent.
Cycle de vie de lâĂ©tat
queued â running â completed
â failed (retries if configured)
â cancelled (via DELETE)
Une nouvelle tentative rĂ©exĂ©cute le workflow depuis le dĂ©but avec lâentrĂ©e dâorigine. Elle ne tient pas compte des points de contrĂŽle ; un workflow ayant des effets de bord doit donc ĂȘtre idempotent ou protĂ©ger lui-mĂȘme sa progression.
Important : les enregistrements dâexĂ©cution rĂ©sident dans un magasin en mĂ©moire. Le redĂ©marrage dâun processus fait perdre tous les enregistrements, y compris les exĂ©cutions en cours. ConsidĂ©rez ces points de terminaison comme une interface dâexĂ©cution asynchrone, et non comme une file dâattente de tĂąches durable.
Planification Cron
La fonctionnalité background inclut également la gestion des tùches cron.
Points de terminaison
| Méthode | Chemin | Description |
|---|---|---|
| POST | /cron | Créer une tùche cron |
| GET | /cron | Lister toutes les tĂąches |
| GET | /cron/{job_id} | Obtenir une tĂąche |
| PATCH | /cron/{job_id} | Mettre en pause/reprendre |
| DELETE | /cron/{job_id} | Supprimer une tĂąche |
Politiques de concurrence
- skip : Ignorer lâoccurrence si une exĂ©cution prĂ©cĂ©dente est toujours active (par dĂ©faut)
- allow : Autoriser les exécutions concurrentes
- queue : Mettre lâoccurrence en file dâattente et lâexĂ©cuter lorsque lâexĂ©cution active est terminĂ©e
Chaque occurrence de planification est revendiquĂ©e une seule fois ; ainsi, une occurrence qui arrive alors quâune exĂ©cution est active produit exactement une exĂ©cution en file dâattente, plutĂŽt quâune exĂ©cution par interrogation du planificateur. Les exĂ©cutions en file dâattente sont traitĂ©es dans lâordre, et chacune est surveillĂ©e de la mĂȘme maniĂšre quâune exĂ©cution dĂ©clenchĂ©e directement. Ainsi, le nombre dâexĂ©cutions actives revient Ă zĂ©ro mĂȘme si une exĂ©cution Ă©choue ou est annulĂ©e.
Les tĂąches Cron utilisent le mĂȘme exĂ©cuteur que les exĂ©cutions en arriĂšre-plan â une tĂąche dont workflowId nâest pas enregistrĂ© Ă©chouera lors de ses exĂ©cutions.
Utilisation
use adk_server::background::{BackgroundState, CronState, cron_jobs_router_with_state, start_cron_scheduler};
let bg_state = BackgroundState::new();
let cron_state = CronState::new(bg_state);
let app = axum::Router::new().merge(cron_jobs_router_with_state(cron_state.clone()));
// Start the background scheduler
start_cron_scheduler(cron_state);
Exemples
# Functional API (TaskContext, ReducedValue, MessagesValue, etc.)
cargo run --manifest-path examples/functional_workflow/Cargo.toml
# Background Runs (REST API with Axum)
cargo run --manifest-path examples/background_runs/Cargo.toml
# Cron Scheduling (full lifecycle demo)
cargo run --manifest-path examples/cron_scheduling/Cargo.toml
Indicateurs de fonctionnalité
| Fonctionnalité | BibliothÚque | Ajouts |
|---|---|---|
functional | adk-graph | TaskContext, réducteurs typés, validation de schéma, macros procédurales |
background | adk-server | Points de terminaison dâexĂ©cution en arriĂšre-plan, planification cron, boucle du planificateur |
PrĂ©cĂ©dent : â Agents graphiques | Suivant : Agents en temps rĂ©el â
Interruption et reprise typée
TaskContext::interrupt<T> suspend le workflow et, lors dâune exĂ©cution ultĂ©rieure, renvoie la valeur fournie
pour ce point.
Chaque point dâinterruption reçoit une clĂ© de continuation Ă partir de sa position dans lâexĂ©cution â interrupt-1,
interrupt-2, et ainsi de suite â afin quâun workflow rejouĂ© atteigne les mĂȘmes interruptions dans le mĂȘme ordre
et retrouve la valeur qui lui a été fournie.
// First run: no value supplied, so the workflow suspends.
let error = ctx.interrupt::<Approval>("approve the refund").await.unwrap_err();
// workflow suspended at interrupt 'interrupt-1': approve the refund
// Second run: supply the value under that key.
let ctx = ctx.with_resume_values(HashMap::from([
("interrupt-1".to_string(), serde_json::json!({ "approved": true, "approver": "alice" })),
]));
let approval: Approval = ctx.interrupt("approve the refund").await?;
La clĂ© est Ă©galement Ă©crite dans le point de contrĂŽle de lâinterruption sous continuation_key.
| Situation | Résultat |
|---|---|
| Aucune valeur pour la clé | FunctionalError::Suspended { continuation_key, message } |
Une valeur qui se désérialise en T | Ok(value) |
Une valeur qui ne se désérialise pas en T | FunctionalError::InterruptTypeMismatch désignant le site |
Important :
interruptrenvoyait auparavantInterruptTypeMismatchavec « workflow interrupted » Ă chaque appel, et rien en dehors de la mĂ©thode ne consommait de valeur de reprise. Lâappelant ne pouvait pas distinguer « entrĂ©e requise » de « votre valeur Ă©tait du mauvais type », ne disposait dâaucune clĂ© sous laquelle fournir une valeur et ne recevait jamais de valeur typĂ©e au point dâappel.
Remarque :
From<FunctionalError> for GraphErrorsâaplatit enGraphError::Other(String); par consĂ©quent, un appelant effectuant une correspondance structurelle devrait effectuer la correspondance surFunctionalErroravant la conversion.