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éthodeCheminDescription
POST/runsSoumettre 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éthodeCheminDescription
POST/cronCréer une tùche cron
GET/cronLister 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ÚqueAjouts
functionaladk-graphTaskContext, réducteurs typés, validation de schéma, macros procédurales
backgroundadk-serverPoints 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.

SituationRésultat
Aucune valeur pour la cléFunctionalError::Suspended { continuation_key, message }
Une valeur qui se désérialise en TOk(value)
Une valeur qui ne se désérialise pas en TFunctionalError::InterruptTypeMismatch désignant le site

Important : interrupt renvoyait auparavant InterruptTypeMismatch avec « 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 GraphError s’aplatit en GraphError::Other(String) ; par consĂ©quent, un appelant effectuant une correspondance structurelle devrait effectuer la correspondance sur FunctionalError avant la conversion.

Fonctionnel API - Documentation ADK-Rust | ADK-Rust