Interactions Gemini API (bêta)
ADK-Rust fournit un client dédié aux Interactions API de Google — la nouvelle orientation de Google pour API Gemini. Il remplace la structure de requête/réponse generateContent par une ressource Interaction avec état, fondée sur une chronologie d’étapes typées, un historique côté serveur et des workflows agentiques natifs.
Les Interactions API sont en bêta. Google recommande generateContent pour les charges de travail de production stables et peut apporter des modifications incompatibles au schéma des Interactions. ADK-Rust verrouille le contrat Api-Revision: 2026-05-20 (schéma des étapes).
Vue d’ensemble
┌─────────────────────────────────────────────────────────────────────┐
│ Gemini Interactions API Client │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ Endpoint: POST /v1beta/interactions │
│ Builder: Gemini::create_interaction() │
│ Feature: interactions (adk-gemini) │
│ gemini-interactions (adk-model / adk-rust) │
│ │
│ Capabilities: │
│ • Single-turn and streaming (step.delta events) │
│ • Server-side history via previous_interaction_id │
│ • Typed step timeline (thought, function_call, model_output, …) │
│ • Multimodal input (text, image, audio, document, video) │
│ • Structured output (response_format JSON schema) │
│ • Client-side function calling + built-in server tools │
│ • Background / long-running tasks (background = true) │
│ • Lifecycle: get / delete / cancel a stored interaction │
│ │
│ vs generateContent (GeminiModel): │
│ • Stateful conversations (server stores history) │
│ • Observable execution steps for agentic UIs │
│ • New models & tools launch here first │
│ │
└─────────────────────────────────────────────────────────────────────┘
Quand utiliser quel API
| Aspect | generateContent (GeminiModel) | Interactions API (create_interaction) |
|---|---|---|
| Point de terminaison | POST /v1beta/models/{model}:generateContent | POST /v1beta/interactions |
| Stabilité | Stable, recommandé pour la production | Bêta, le schéma peut changer |
| Historique | Le client renvoie la transcription complète | Côté serveur via previous_interaction_id |
| Forme de la réponse | candidates + parts | Chronologie steps |
Exécution de l’agent (trait Llm) | ✅ transport par défaut | ✅ activation volontaire via use_interactions_api |
| Nouveaux modèles / outils | — | Lancer ici en premier |
Le runtime d’agent ADK (le trait Llm, la boucle d’outils et Runner) utilise generateContent par défaut. Vous pouvez également piloter les interactions API via le même runtime en activant use_interactions_api(true) sur un GeminiModel — voir Les interactions comme transport du runtime ci-dessous. Le client direct (documenté en premier) reste disponible pour les appelants qui souhaitent conserver l’historique côté serveur, observer les étapes ou utiliser des modèles réservés à la version bêta sans impliquer d’agent.
Activation
# Direct client (adk-gemini)
adk-gemini = { version = "2.1.0", features = ["interactions"] }
# Through the model facade / umbrella
adk-model = { version = "2.1.0", features = ["gemini-interactions"] }
adk-rust = { version = "2.1.0", features = ["gemini-interactions"] }
Cette fonctionnalité n’ajoute aucune nouvelle dépendance et est entièrement additive à l’API generateContent existant.
Démarrage rapide
use adk_gemini::{Gemini, Model, ThinkingLevel};
let gemini = Gemini::new(std::env::var("GEMINI_API_KEY")?)?;
let interaction = gemini
.create_interaction()
.model(Model::Gemini35Flash)
.system_instruction("You are concise.")
.input_text("What is the capital of France?")
.thinking_level(ThinkingLevel::Low)
.send()
.await?;
println!("{}", interaction.output_text().unwrap_or_default());
Diffusion en continu
Lors d’une diffusion en continu, le API émet un modèle d’événements SSE orienté étapes. Le chemin le plus courant consiste à accumuler les fragments de texte provenant des événements step.delta :
use futures::StreamExt;
let mut stream = gemini
.create_interaction()
.model(Model::Gemini35Flash)
.input_text("Write a haiku about Rust.")
.stream()
.await?;
while let Some(event) = stream.next().await {
if let Some(fragment) = event?.text_delta() {
print!("{fragment}");
}
}
Types d’événements : interaction.created, step.start, step.delta, step.stop,
interaction.status_update, interaction.completed et error. Les futurs événements inconnus sont désérialisés en
InteractionSseEvent::Other au lieu de provoquer l’échec
du flux.
Conversations à plusieurs tours côté serveur
Transmettez le id d’une interaction précédente pour poursuivre la conversation sans renvoyer
l’historique. Notez que tools, system_instruction et generation_config sont
associés à l’interaction et doivent être spécifiés à nouveau à chaque tour :
let first = gemini.create_interaction()
.model(Model::Gemini35Flash)
.input_text("My favorite color is teal.")
.send().await?;
let second = gemini.create_interaction()
.model(Model::Gemini35Flash)
.previous_interaction_id(&first.id)
.input_text("What is my favorite color?")
.send().await?;
Appel de fonctions
Les API d’interactions exposent les appels d’outils côté client sous forme d’étapes function_call
avec un statut requires_action. Fournissez les résultats lors d’un tour de suivi :
use serde_json::json;
let interaction = gemini.create_interaction()
.model(Model::Gemini35Flash)
.function("get_weather", "Get the weather",
json!({"type": "object", "properties": {"location": {"type": "string"}}}))
.input_text("Weather in Boston?")
.send().await?;
if interaction.status.requires_action() {
let follow_up = gemini.create_interaction()
.model(Model::Gemini35Flash)
.previous_interaction_id(&interaction.id);
let mut follow_up = follow_up;
for (call_id, name, _args) in interaction.pending_function_calls() {
follow_up = follow_up.function_result(call_id, name, json!({"temperature": "72F"}));
}
let final_interaction = follow_up.send().await?;
println!("{}", final_interaction.output_text().unwrap_or_default());
}
Sortie structurée
use serde_json::json;
let interaction = gemini.create_interaction()
.model(Model::Gemini35Flash)
.input_text("Summarize this article: ...")
.json_schema(json!({
"type": "object",
"properties": { "summary": { "type": "string" } },
"required": ["summary"]
}))
.send().await?;
Cycle de vie
Les interactions stockées (valeur par défaut du serveur) peuvent être récupérées, supprimées ou annulées :
let fetched = gemini.get_interaction(&interaction.id, /* include_input */ true).await?;
gemini.cancel_interaction(&interaction.id).await?; // background tasks only
gemini.delete_interaction(&interaction.id).await?;
Valeurs d’état
InteractionStatus reflète le cycle de vie de API : InProgress, RequiresAction,
Completed, Failed, Cancelled, Incomplete, BudgetExceeded. Utilisez
is_terminal() et requires_action() pour le contrôle du flux.
Limitations
Les Interactions API ne prennent pas encore en charge le Batch API ni la mise en cache explicite
(la mise en cache implicite côté serveur est disponible via previous_interaction_id). Le
runtime d’agent ADK utilise generateContent par défaut ; les Interactions API sont
disponibles à la fois en tant que client autonome décrit ci-dessus et en tant que
transport de runtime opt-in (voir ci-dessous).
Interactions comme transport de runtime (agents + exécuteur)
Tout ce qui précède documente le client direct sur le réseau (adk_gemini::interactions)
— une fonctionnalité autonome que vous appelez manuellement. Cette section documente le
transport de runtime construit par-dessus : un bouton sur GeminiModel qui permet à un
LlmAgent, Runner normal, à une boucle d’outils et aux sessions de piloter les Interactions API
avec zéro modification de votre code d’agent.
Cela reflète ADK-Python, où Gemini(model=..., use_interactions_api=True)
conserve le même Agent, exécuteur et outils. Un agent est indépendant du transport :
modifier la façon dont le modèle communique avec le backend ne doit pas nécessiter un nouveau type d’agent.
generateContent reste la valeur par défaut
generateContent reste le transport par défaut et recommandé pour les charges de travail de production stables. Les Interactions API sont en bêta et leur schéma peut changer.
Activez ce transport délibérément, modèle par modèle. Lorsque vous n’appelez pas
use_interactions_api(true), un GeminiModel se comporte exactement comme auparavant — il
n’y a aucun changement de comportement pour le chemin generateContent.
Activer le transport
Le transport est soumis à la fonctionnalité gemini-interactions (transmise de
adk-rust → adk-model → adk-gemini/interactions) :
adk-model = { version = "2.1.0", features = ["gemini-interactions"] }
adk-rust = { version = "2.1.0", features = ["gemini-interactions"] }
Activez l’option sur le modèle et encapsulez-le dans un LlmAgent et un Runner normaux —
rien d’autre dans la configuration de l’agent ne change :
use adk_agent::LlmAgentBuilder;
use adk_core::{Content, Part, SessionId, UserId};
use adk_model::GeminiModel;
use adk_runner::Runner;
use adk_session::{CreateRequest, InMemorySessionService, SessionService};
use futures::StreamExt;
use std::collections::HashMap;
use std::sync::Arc;
// 1. Build a Gemini model and toggle the Interactions transport.
// `use_interactions_api` validates the model id against the allowlist and
// returns `Result<Self>`, so it is fallible (`?`).
let model = GeminiModel::new(std::env::var("GEMINI_API_KEY")?, "gemini-3.7-flash")?
.use_interactions_api(true)?;
// 2. Wrap it in a normal LlmAgent — unchanged agent API.
let agent = Arc::new(
LlmAgentBuilder::new("assistant")
.instruction("You are concise.")
.model(Arc::new(model))
.build()?,
);
// 3. Drive it through the standard Runner.
let sessions: Arc<dyn SessionService> = Arc::new(InMemorySessionService::new());
sessions
.create(CreateRequest {
app_name: "assistant".into(),
user_id: "user".into(),
session_id: Some("session-1".into()),
state: HashMap::new(),
})
.await?;
let runner = Runner::builder()
.app_name("assistant")
.agent(agent)
.session_service(sessions)
.build()?;
let mut stream = runner
.run(
UserId::new("user")?,
SessionId::new("session-1")?,
Content::new("user").with_text("What is the capital of France?"),
)
.await?;
while let Some(event) = stream.next().await {
let event = event?;
// The server-assigned interaction id is a first-class field on every event.
if let Some(id) = event.interaction_id() {
println!("interaction_id = {id}");
}
if let Some(content) = &event.llm_response.content {
for part in &content.parts {
if let Part::Text { text } = part {
print!("{text}");
}
}
}
}
Valeurs par défaut fidèles
Le transport adopte par défaut la posture prévue par API, configurée
via InteractionOptions (réexportée depuis adk_model::gemini) :
| Option | Par défaut | Signification |
|---|---|---|
store | true | Les interactions sont stockées côté serveur afin que la continuation avec état et l’observabilité fonctionnent immédiatement. |
stateful | true | Les conversations à plusieurs tours se poursuivent via previous_interaction_id ; seul le contenu du tour actuel est envoyé lors de l’enchaînement. |
background | BackgroundMode::AgentTargetsOnly | background=true pour les cibles d’agents (recherche approfondie, exécution longue) ; false pour les cibles de modèles afin que les échanges restent peu latents. |
poll_interval | 1s | Fréquence d’interrogation d’une interaction en arrière-plan jusqu’à son achèvement. |
Remplacer certains de ces éléments par interaction_options :
use adk_model::gemini::{BackgroundMode, InteractionOptions};
use std::time::Duration;
let model = GeminiModel::new(api_key, "gemini-3.7-flash")?
.use_interactions_api(true)?
.interaction_options(InteractionOptions {
store: true,
stateful: true,
background: BackgroundMode::AgentTargetsOnly,
poll_interval: Duration::from_millis(500),
});
BackgroundMode possède trois variantes : AgentTargetsOnly (par défaut), Always et
Never.
Lorsque
storeestfalse, les règles d'incompatibilité de API désactivent la continuation avec état et l'exécution en arrière-plan ; le transport envoie alors l'entrée de transcription, exactement comme generateContent.
Cibles prises en charge (liste autorisée)
Le API Interactions prend en charge un ensemble fixe de cibles. use_interactions_api(true)
valide l'identifiant du modèle lors de la configuration et renvoie une AdkError
avec la catégorie InvalidInput (qui nomme les cibles prises en charge) lorsque l'identifiant
ne figure pas dans la liste autorisée — au lieu de laisser le serveur renvoyer
ultérieurement un rejet opaque.
Une cible modèle définit le champ model de la requête ; une cible agent définit le
champ agent.
Cibles de modèles :
gemini-3.7-flashgemini-3.6-flashgemini-3.5-flashgemini-3.5-flash-litegemini-3.1-flash-litegemini-3.1-pro-previewgemini-3-flash-previewgemini-2.5-progemini-2.5-flashgemini-2.5-flash-litelyria-3-clip-previewlyria-3-pro-preview
Il s'agit de la liste de compatibilité autorisée du transport, et non d'une liste de recommandations.
Les nouvelles applications devraient commencer par gemini-3.7-flash ; les identifiants
plus anciens et en préversion restent répertoriés, car le point de terminaison Interactions
les accepte encore.
Cibles d'agents :
deep-research-pro-preview-12-2025deep-research-preview-04-2026deep-research-max-preview-04-2026
// Unsupported targets fail fast at configuration time:
let result = GeminiModel::new(api_key, "gpt-4")?.use_interactions_api(true);
assert!(result.is_err()); // AdkError { category: InvalidInput, .. }
L'énumération InteractionTarget (également réexportée depuis adk_model::gemini)
représente une destination validée si vous devez inspecter directement la classification.
Mélange d'outils intégrés et personnalisés (bypass_multi_tools_limit)
Le API Interactions interdit de mélanger des outils intégrés (côté serveur) et des outils
de fonctions personnalisés dans une même requête. Pour utiliser, par exemple, Google Search
avec votre propre outil de fonction, convertissez l'outil intégré en outil d'appel de fonction
afin que l'ensemble des outils soit uniforme. Cela reflète le bypass_multi_tools_limit=True de
ADK-Python.
La conversion réside sur le trait BypassMultiToolsLimit, implémenté par les wrappers d’outils intégrés (GoogleSearchTool, UrlContextTool, GeminiFileSearchTool). with_bypass_multi_tools_limit(agent) prend un agent interne de recherche fondée sur les sources à tour unique — un LlmAgent ordinaire configuré avec l’outil intégré et un modèle Gemini — et renvoie un Arc<dyn Tool> qui signale is_builtin() == false et exécute le comportement intégré en interne, en renvoyant une réponse de fonction normale.
use adk_agent::LlmAgentBuilder;
use adk_tool::{BypassMultiToolsLimit, FunctionTool, GoogleSearchTool};
use adk_model::GeminiModel;
use std::sync::Arc;
// The grounded-search agent the bypass tool delegates to: a normal LlmAgent
// with the built-in GoogleSearchTool + a Gemini model.
let search_agent = Arc::new(
LlmAgentBuilder::new("grounded-search")
.instruction("Answer the query using Google Search. Be factual and concise.")
.model(Arc::new(GeminiModel::new(&api_key, "gemini-3.7-flash")?))
.tool(Arc::new(GoogleSearchTool::new()))
.build()?,
);
// Convert the built-in search tool into a function tool (is_builtin() == false).
let search_tool = GoogleSearchTool::new().with_bypass_multi_tools_limit(search_agent);
// A custom function tool to mix alongside it.
let weather_tool: Arc<dyn adk_core::Tool> = Arc::new(/* your FunctionTool */);
// Now the tool set is uniform (all function tools) and the Interactions
// transport accepts it.
let model = GeminiModel::new(&api_key, "gemini-3.7-flash")?.use_interactions_api(true)?;
let agent = Arc::new(
LlmAgentBuilder::new("assistant")
.model(Arc::new(model))
.tool(search_tool)
.tool(weather_tool)
.build()?,
);
Si vous laissez un outil intégré non contourné tout en le mélangeant avec des outils de fonction sous le transport Interactions, la construction de la requête renvoie un AdkError de catégorie InvalidInput qui vous redirige vers with_bypass_multi_tools_limit. L’identifiant de l’appel de fonction est conservé à l’identique tout au long de la boucle de l’outil, exactement comme avec generateContent.
Continuité avec état et solution de repli pour la rétention
interaction_id est un champ de première classe, et non un canal auxiliaire. Chaque LlmResponse contient interaction_id: Option<String> (renseigné par le transport Interactions, None dans le cas contraire), et Event l’expose via l’accesseur event.interaction_id() — en miroir de ADK-Python's event.interaction_id.
La continuité est indépendante du fournisseur. LlmRequest contient un champ additif previous_response_id: Option<String> que LlmAgent renseigne à partir du interaction_id de l’événement le plus récent. Le transport Interactions le mappe vers previous_interaction_id de la requête et envoie uniquement le contenu du tour en cours (plutôt que la transcription complète). Aucune logique spécifique à Gemini ne réside dans adk-agent ; le champ est inutilisé (sans effet) pour generateContent et les autres fournisseurs.
Turn 1: request (transcript) → interaction v1_abc → event.interaction_id() == "v1_abc"
Turn 2: request previous_response_id = "v1_abc"
→ previous_interaction_id = "v1_abc", sends only the new turn
→ interaction v1_def → event.interaction_id() == "v1_def"
Solution de repli pour la fenêtre de conservation. Les interactions stockées expirent. Si un
previous_interaction_id fourni est obsolète ou expiré, le serveur renvoie NotFound. Le
transport gère cela de manière transparente : il revient à envoyer la transcription complète et démarre une nouvelle interaction — aucune erreur n’est remontée à
l’agent ou à l’exécuteur. Les conversations à plusieurs tours continuent de fonctionner au-delà de la limite de conservation sans traitement particulier dans votre code.
Types réexportés
Derrière la fonctionnalité gemini-interactions, les éléments suivants sont disponibles depuis
adk_model::gemini :
GeminiTransport—GenerateContent(par défaut) ouInteractions.InteractionOptions—store,stateful,background,poll_interval.BackgroundMode—AgentTargetsOnly(par défaut),Always,Never.InteractionTarget— une destination de modèle/agent validée.
La surface de contournement se trouve dans adk-tool (accessible via adk_tool ou l’agrégateur) :
- Trait
BypassMultiToolsLimitavecwith_bypass_multi_tools_limit(agent). - Implémenté par
GoogleSearchTool,UrlContextTool,GeminiFileSearchTool.
Les champs de base additionnels LlmResponse.interaction_id et LlmRequest.previous_response_id
sont toujours présents (ils ne dépendent pas d’une fonctionnalité), de sorte que l’accesseur
event.interaction_id() se compile quels que soient les fournisseurs activés.