Gemini-Interaktionen API (Beta)
ADK-Rust stellt einen dedizierten Client für Googles Interactions API bereit – Googles neue Ausrichtung für das Gemini API. Es ersetzt die Anforderungs-/Antwortstruktur von generateContent durch eine zustandsbehaftete Ressource Interaction, die auf einer typisierten Schrittchronik, serverseitigem Verlauf und nativen agentischen Workflows basiert.
Die Interactions API befindet sich in der Beta-Phase. Google empfiehlt generateContent für stabile Produktions-Workloads und kann Breaking Changes am Interactions-Schema vornehmen. ADK-Rust legt den Vertrag für Api-Revision: 2026-05-20 (Schrittschema) fest.
Übersicht
┌─────────────────────────────────────────────────────────────────────┐
│ 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 │
│ │
└─────────────────────────────────────────────────────────────────────┘
Wann welches API verwenden
| Aspekt | generateContent (GeminiModel) | Interaktionen API (create_interaction) |
|---|---|---|
| Endpunkt | POST /v1beta/models/{model}:generateContent | POST /v1beta/interactions |
| Stabilität | Stabil, für den Produktionseinsatz empfohlen | Beta, das Schema kann sich ändern |
| Verlauf | Client sendet das vollständige Transkript erneut | Serverseitig über previous_interaction_id |
| Antwortstruktur | candidates + parts | steps-Zeitachse |
Agent-Laufzeit (Llm-Trait) | ✅ Standardtransport | ✅ Opt-in über use_interactions_api |
| Neue Modelle / Tools | — | Zuerst hier starten |
Die ADK-Laufzeit für Agenten (das Llm-Trait, die Tool-Schleife und Runner) verwendet standardmäßig generateContent. Sie können Interactions API auch über dieselbe Laufzeit steuern, indem Sie use_interactions_api(true) auf einem GeminiModel aktivieren — siehe unten Interactions als Laufzeittransport. Der direkte Client (zuerst dokumentiert) bleibt für Aufrufer verfügbar, die serverseitigen Verlauf, beobachtbare Schritte oder ausschließlich in der Beta verfügbare Modelle nutzen möchten, ohne einen Agenten einzubeziehen.
Aktivierung
# 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"] }
Das Feature fügt keine neuen Abhängigkeiten hinzu und ist vollständig additiv zur vorhandenen generateContent API.
Schnellstart
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());
Streaming
Beim Streaming gibt der API ein schrittbasiertes SSE-Ereignismodell aus. Der häufigste
Pfad besteht darin, Textfragmente aus step.delta-Ereignissen zu sammeln:
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}");
}
}
Ereignistypen: interaction.created, step.start, step.delta, step.stop,
interaction.status_update, interaction.completed und error. Unbekannte
zukünftige Ereignisse werden in InteractionSseEvent::Other deserialisiert, anstatt
den Stream fehlschlagen zu lassen.
Serverseitige Mehrfachinteraktionen
Übergeben Sie die id einer vorherigen Interaktion, um die Unterhaltung fortzusetzen, ohne den
Verlauf erneut zu senden. Beachten Sie, dass tools, system_instruction und generation_config
auf die jeweilige Interaktion beschränkt sind und bei jedem Durchlauf
erneut angegeben werden müssen:
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?;
Funktionsaufrufe
Das Interactions-API stellt clientseitige Tool-Aufrufe als function_call-Schritte
mit dem Status requires_action bereit. Übermitteln Sie die Ergebnisse in einem
nachfolgenden Durchlauf:
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());
}
Strukturierte Ausgabe
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?;
Lebenszyklus
Gespeicherte Interaktionen (die Standardeinstellung des Servers) können abgerufen, gelöscht oder abgebrochen werden:
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?;
Statuswerte
InteractionStatus bildet den API-Lebenszyklus ab: InProgress, RequiresAction,
Completed, Failed, Cancelled, Incomplete, BudgetExceeded. Verwenden Sie
is_terminal() und requires_action() für den Kontrollfluss.
Einschränkungen
Die Interactions API unterstützt derzeit weder Batch API noch explizites Caching
(serverseitiges implizites Caching ist über previous_interaction_id verfügbar). Die
ADK-Agent-Laufzeit verwendet standardmäßig generateContent; die Interactions API ist
sowohl als oben dokumentierter eigenständiger Client als auch als optional aktivierbarer
Laufzeit-Transport verfügbar (siehe unten).
Interactions als Laufzeit-Transport (Agents + Runner)
Alles oben beschreibt den direkten Wire-Client (adk_gemini::interactions)
— eine eigenständige Funktionalität, die Sie manuell aufrufen. Dieser Abschnitt beschreibt den
darauf aufbauenden Laufzeit-Transport: einen Schalter in GeminiModel, der es einem normalen
LlmAgent, Runner, einer Tool-Schleife und Sitzungen ermöglicht, die Interactions API
mit null Änderungen an Ihrem Agent-Code zu verwenden.
Dies entspricht ADK-Python, bei dem Gemini(model=..., use_interactions_api=True)
dieselben Agent, denselben Runner und dieselben Tools beibehält. Ein Agent ist transportagnostisch:
Das Ändern der Kommunikationsweise des Modells mit dem Backend darf keinen neuen Agent-Typ erfordern.
generateContent ist weiterhin der Standard
generateContent bleibt der Standard und empfohlene Transport für stabile
Produktions-Workloads. Die Interactions API befindet sich in der Beta-Phase, und ihr Schema kann sich ändern.
Aktivieren Sie den Transport bewusst und pro Modell. Wenn Sie use_interactions_api(true) nicht aufrufen, verhält sich ein
GeminiModel genau wie zuvor — der generateContent-Pfad ändert sich
nicht.
Aktivieren des Transports
Der Transport ist hinter dem Feature gemini-interactions verborgen (weitergeleitet von
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"] }
Aktivieren Sie den Schalter am Modell und verpacken Sie es in einen normalen LlmAgent und Runner —
an der übrigen Agent-Konfiguration ändert sich nichts:
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}");
}
}
}
}
Getreue Standardwerte
Der Transport übernimmt standardmäßig die vorgesehene Ausrichtung von Interactions API's, konfiguriert über InteractionOptions (re-exportiert aus adk_model::gemini):
| Option | Standard | Bedeutung |
|---|---|---|
store | true | Interaktionen werden serverseitig gespeichert, sodass zustandsbehaftete Fortsetzungen und Beobachtbarkeit sofort einsatzbereit funktionieren. |
stateful | true | Mehrere Gesprächsrunden werden über previous_interaction_id fortgesetzt; beim Verketten werden nur die Inhalte der aktuellen Runde gesendet. |
background | BackgroundMode::AgentTargetsOnly | background=true für Agentenziele (Deep Research, lang laufend); false für Modellziele, damit Chat-Runden latenzarm bleiben. |
poll_interval | 1s | Wie häufig eine Hintergrundinteraktion bis zum Abschluss abgefragt wird. |
Überschreiben Sie alle diese mit 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 hat drei Varianten: AgentTargetsOnly (Standard), Always und
Never.
Wenn
storefalseist, deaktivieren die Inkompatibilitätsregeln von API die zustandsbehaftete Fortsetzung und die Hintergrundausführung; der Transport sendet dann die Transkripteingabe, genau wie generateContent.
Unterstützte Ziele (Zulassungsliste)
API unterstützt eine feste Gruppe von Zielen. use_interactions_api(true)
validiert die Modell-ID zum Konfigurationszeitpunkt und gibt einen AdkError
mit der Kategorie InvalidInput (unter Angabe der unterstützten Ziele) zurück, wenn
sich die ID nicht auf der Zulassungsliste befindet – anstatt die Ablehnung
durch einen undurchsichtigen Server aufzuschieben.
Ein Modell-Ziel legt das Feld model der Anfrage fest; ein Agent-Ziel legt das
Feld agent fest.
Modellziele:
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
Dies ist die Kompatibilitäts-Zulassungsliste des Transports, keine Empfehlungsliste.
Neue Anwendungen sollten mit gemini-3.7-flash beginnen; ältere und Vorschau-IDs
bleiben aufgeführt, da der Interactions-Endpunkt sie weiterhin akzeptiert.
Agentenziele:
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, .. }
Das Enum InteractionTarget (auch aus adk_model::gemini erneut exportiert)
stellt ein validiertes Ziel dar, wenn Sie die Klassifizierung direkt überprüfen
müssen.
Kombinieren integrierter und benutzerdefinierter Tools (bypass_multi_tools_limit)
API verbietet die Kombination integrierter (serverseitiger) Tools mit benutzerdefinierten
Funktions-Tools in einer einzelnen Anfrage. Wenn Sie beispielsweise die Google-Suche
zusammen mit Ihrem eigenen Funktions-Tool verwenden möchten, wandeln Sie das integrierte
Tool in ein Tool für Funktionsaufrufe um, sodass das gesamte Toolset einheitlich ist.
Dies entspricht ADK-Pythons
bypass_multi_tools_limit=True.
Die Konvertierung ist im Trait BypassMultiToolsLimit implementiert, der von den integrierten Tool-Wrappern (GoogleSearchTool, UrlContextTool, GeminiFileSearchTool) implementiert wird. with_bypass_multi_tools_limit(agent) nimmt einen internen, auf eine einzelne Runde beschränkten Grounded-Search-Agenten — einen gewöhnlichen LlmAgent, der mit dem integrierten Tool und einem Gemini-Modell konfiguriert ist — und gibt ein Arc<dyn Tool> zurück, das is_builtin() == false meldet und das integrierte Verhalten intern ausführt, wobei eine normale Funktionsantwort zurückgegeben wird.
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()?,
);
Wenn du ein integriertes Tool beim Mischen mit Funktionstools unter dem Interactions-Transport nicht umgehst, gibt die Anfrageerstellung ein AdkError mit der Kategorie InvalidInput zurück, das dich auf with_bypass_multi_tools_limit verweist. Die ID des Funktionsaufrufs wird durch die Tool-Schleife unverändert weitergereicht, genau wie bei generateContent.
Zustandsbehaftete Kontinuität und der Fallback zur Aufbewahrung
interaction_id ist ein erstklassiges Feld, kein Nebenkanal. Jedes LlmResponse enthält interaction_id: Option<String> (vom Interactions-Transport befüllt, andernfalls None), und Event stellt es über den Accessor event.interaction_id() bereit — entsprechend ADK-Pythons event.interaction_id.
Die Kontinuität ist anbieterneutral. LlmRequest enthält ein zusätzliches previous_response_id: Option<String>-Feld, das LlmAgent aus dem interaction_id des jüngsten Ereignisses befüllt. Der Interactions-Transport ordnet es previous_interaction_id der Anfrage zu und sendet nur die Inhalte der aktuellen Runde (statt des vollständigen Transkripts). In adk-agent gibt es keine Gemini-spezifische Verknüpfungslogik; das Feld wird bei generateContent und anderen Anbietern nicht verwendet (ist also wirkungslos).
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"
Fallback bei Ablauf des Aufbewahrungsfensters. Gespeicherte Interaktionen laufen ab. Wenn ein bereitgestelltes
previous_interaction_id veraltet oder abgelaufen ist, gibt der Server NotFound zurück. Der
Transport behandelt dies transparent: Er fällt darauf zurück, das vollständige Transkript zu senden, und startet eine neue Interaktion — dem Agenten oder Runner wird kein Fehler angezeigt. Mehrere Gesprächsrunden funktionieren über die Grenze des Aufbewahrungszeitraums hinweg ohne spezielle Behandlung in Ihrem Code.
Re-exportierte Typen
Hinter dem Feature gemini-interactions sind die folgenden Elemente über
adk_model::gemini verfügbar:
GeminiTransport—GenerateContent(Standard) oderInteractions.InteractionOptions—store,stateful,background,poll_interval.BackgroundMode—AgentTargetsOnly(Standard),Always,Never.InteractionTarget— ein validiertes Modell-/Agentenziel.
Die Umgehungsschnittstelle befindet sich in adk-tool (erreichbar über adk_tool oder das übergeordnete Paket):
BypassMultiToolsLimit-Trait mitwith_bypass_multi_tools_limit(agent).- Implementiert durch
GoogleSearchTool,UrlContextTool,GeminiFileSearchTool.
Die zusätzlichen Core-Felder LlmResponse.interaction_id und LlmRequest.previous_response_id sind immer vorhanden (nicht durch Features gesteuert), sodass der event.interaction_id()-Accessor unabhängig davon kompiliert, welche Provider aktiviert sind.