Interacciones de Gemini API (Beta)

ADK-Rust proporciona un cliente dedicado para las Interacciones API de Google: la nueva dirección de Google para API de Gemini. Reemplaza la estructura de solicitud/respuesta de generateContent por un recurso con estado de Interaction basado en una línea temporal de pasos tipada, un historial del lado del servidor y flujos de trabajo agénticos nativos.

API está en beta. Google recomienda generateContent para cargas de trabajo de producción estables y podría realizar cambios incompatibles en el esquema de Interacciones. ADK-Rust fija el contrato de Api-Revision: 2026-05-20 (esquema de pasos).

Descripción general

┌─────────────────────────────────────────────────────────────────────┐
│                  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                            │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

Cuándo usar cada API

AspectogenerateContent (GeminiModel)Interacciones API (create_interaction)
Punto de conexiónPOST /v1beta/models/{model}:generateContentPOST /v1beta/interactions
EstabilidadEstable, recomendado para producciónBeta, el esquema puede cambiar
HistorialEl cliente reenvía la transcripción completaEn el servidor mediante previous_interaction_id
Forma de respuestacandidates + partsLínea temporal de steps
Entorno de ejecución del agente (rasgo Llm)✅ transporte predeterminado✅ activación opcional mediante use_interactions_api
Nuevos modelos / herramientasLanzar aquí primero

El runtime del agente ADK (el trait Llm, el bucle de herramientas y Runner) utiliza generateContent de forma predeterminada. También puedes gestionar las Interactions API mediante el mismo runtime activando use_interactions_api(true) en un GeminiModel; consulta Interactions como transporte del runtime a continuación. El cliente directo (documentado primero) sigue estando disponible para los llamadores que quieran conservar el historial en el servidor, observar los pasos o utilizar modelos exclusivos de la beta sin involucrar a un agente.

Activación

# 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"] }

La funcionalidad no añade ninguna dependencia nueva y es totalmente aditiva al generateContent API existente.

Inicio rápido

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

Durante el streaming, API emite un modelo de eventos SSE orientado a pasos. La ruta más habitual consiste en acumular fragmentos de texto de los eventos 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}");
    }
}

Tipos de eventos: interaction.created, step.start, step.delta, step.stop, interaction.status_update, interaction.completed y error. Los eventos futuros desconocidos se deserializan como InteractionSseEvent::Other en lugar de provocar un error en el stream.

Conversaciones de varios turnos en el servidor

Pasa el id de una interacción anterior para continuar la conversación sin volver a enviar el historial. Ten en cuenta que tools, system_instruction y generation_config están asociados al ámbito de la interacción y deben especificarse de nuevo en cada turno:

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?;

Llamadas a funciones

Interactions API expone las llamadas a herramientas del cliente como pasos function_call con un estado requires_action. Proporciona los resultados en un turno posterior:

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());
}

Salida estructurada

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?;

Ciclo de vida

Las interacciones almacenadas (el valor predeterminado del servidor) se pueden recuperar, eliminar o cancelar:

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?;

Valores de estado

InteractionStatus refleja el ciclo de vida de API: InProgress, RequiresAction, Completed, Failed, Cancelled, Incomplete, BudgetExceeded. Utiliza is_terminal() y requires_action() para el flujo de control.

Limitaciones

Interactions API aún no admite Batch API ni el almacenamiento en caché explícito (el almacenamiento en caché implícito del lado del servidor está disponible mediante previous_interaction_id). El entorno de ejecución del agente ADK utiliza generateContent de forma predeterminada; Interactions API está disponible tanto como cliente independiente documentado anteriormente como transporte de ejecución opcional (consulta a continuación).


Interactions como transporte de ejecución (agentes + ejecutor)

Todo lo anterior documenta el cliente directo del protocolo (adk_gemini::interactions) — una capacidad independiente que se invoca manualmente. Esta sección documenta el transporte de ejecución construido sobre él: un interruptor en GeminiModel que permite que un LlmAgent, Runner normal, el bucle de herramientas y las sesiones controlen Interactions API sin cambios en el código del agente.

Esto refleja ADK-Python, donde Gemini(model=..., use_interactions_api=True) conserva el mismo Agent, ejecutor y herramientas. Un agente es independiente del transporte: cambiar la forma en que el modelo se comunica con el backend no debe requerir un nuevo tipo de agente.

generateContent sigue siendo el valor predeterminado

generateContent sigue siendo el transporte predeterminado y recomendado para cargas de trabajo de producción estables. Interactions API está en fase beta y su esquema puede cambiar. Activa el transporte de forma deliberada, por modelo. Cuando no llamas a use_interactions_api(true), un GeminiModel se comporta exactamente como antes; no hay ningún cambio de comportamiento en la ruta de generateContent.

Activar el transporte

El transporte está condicionado a la funcionalidad gemini-interactions (reenviada desde adk-rustadk-modeladk-gemini/interactions):

adk-model = { version = "2.1.0", features = ["gemini-interactions"] }
adk-rust  = { version = "2.1.0", features = ["gemini-interactions"] }

Activa el interruptor en el modelo y envuélvelo en un LlmAgent y Runner normales; no cambia nada más de la configuración del agente:

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}");
            }
        }
    }
}

Valores predeterminados fieles

El transporte adopta de forma predeterminada la postura prevista de Interactions API, configurada mediante InteractionOptions (reexportada desde adk_model::gemini):

OpciónPredeterminadoSignificado
storetrueLas interacciones se almacenan en el servidor, por lo que la continuación con estado y la observabilidad funcionan de forma inmediata.
statefultrueLas conversaciones de varios turnos continúan mediante previous_interaction_id; al encadenar, solo se envía el contenido del turno actual.
backgroundBackgroundMode::AgentTargetsOnlybackground=true para objetivos de agentes (investigación profunda, de larga duración); false para objetivos de modelos, para que los turnos de chat mantengan una latencia baja.
poll_interval1sCon qué frecuencia se sondea una interacción en segundo plano hasta que finaliza.

Anula cualquiera de estos con 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 tiene tres variantes: AgentTargetsOnly (predeterminada), Always y Never.

Cuando store es false, las reglas de incompatibilidad de API deshabilitan la continuación con estado y la ejecución en segundo plano; el transporte envía entonces la entrada de la transcripción, exactamente igual que generateContent.

Destinos compatibles (lista de permitidos)

El API de Interactions admite un conjunto fijo de destinos. use_interactions_api(true) valida el id del modelo en el momento de la configuración y devuelve un AdkError con la categoría InvalidInput (que indica los destinos compatibles) cuando el id no está en la lista de permitidos, en lugar de posponerlo a un rechazo opaco del servidor.

Un destino de modelo establece el campo model de la solicitud; un destino de agente establece el campo agent.

Destinos de modelo:

  • gemini-3.7-flash
  • gemini-3.6-flash
  • gemini-3.5-flash
  • gemini-3.5-flash-lite
  • gemini-3.1-flash-lite
  • gemini-3.1-pro-preview
  • gemini-3-flash-preview
  • gemini-2.5-pro
  • gemini-2.5-flash
  • gemini-2.5-flash-lite
  • lyria-3-clip-preview
  • lyria-3-pro-preview

Esta es la lista de permitidos de compatibilidad del transporte, no una lista de recomendaciones. Las aplicaciones nuevas deberían comenzar con gemini-3.7-flash; los identificadores antiguos y de vista previa siguen incluidos porque el endpoint de Interactions todavía los acepta.

Destinos de agente:

  • deep-research-pro-preview-12-2025
  • deep-research-preview-04-2026
  • deep-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, .. }

La enumeración InteractionTarget (también reexportada desde adk_model::gemini) representa un destino validado si necesitas inspeccionar la clasificación directamente.

Mezcla de herramientas integradas y personalizadas (bypass_multi_tools_limit)

El API de Interactions prohíbe mezclar herramientas integradas (del servidor) con herramientas de función personalizadas en una sola solicitud. Para usar, por ejemplo, Google Search junto con tu propia herramienta de función, convierte la herramienta integrada en una herramienta de llamada a función para que todo el conjunto de herramientas sea uniforme. Esto refleja ADK-Python y su bypass_multi_tools_limit=True.

La conversión se encuentra en el trait BypassMultiToolsLimit, implementado por los envoltorios de herramientas integradas (GoogleSearchTool, UrlContextTool, GeminiFileSearchTool). with_bypass_multi_tools_limit(agent) toma un agente interno de búsqueda fundamentada de un solo turno —un LlmAgent ordinario configurado con la herramienta integrada y un modelo Gemini— y devuelve un Arc<dyn Tool> que informa de is_builtin() == false y ejecuta internamente el comportamiento integrado, devolviendo una respuesta de función normal.

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 dejas una herramienta integrada sin omitir mientras la combinas con herramientas de función mediante el transporte Interactions, la construcción de la solicitud devuelve un AdkError con la categoría InvalidInput, que te dirige a with_bypass_multi_tools_limit. El identificador de la llamada de función se conserva sin cambios durante el ciclo de herramientas, exactamente igual que con generateContent.

Continuidad con estado y alternativa de retención

interaction_id es un campo de primera clase, no un canal lateral. Cada LlmResponse contiene interaction_id: Option<String> (rellenado por el transporte Interactions, None en caso contrario), y Event lo expone mediante el accesor event.interaction_id(), imitando el event.interaction_id de ADK-Python.

La continuidad es independiente del proveedor. LlmRequest contiene un campo aditivo previous_response_id: Option<String> que LlmAgent rellena a partir del interaction_id del evento más reciente. El transporte Interactions lo asigna a previous_interaction_id de la solicitud y envía solo el contenido del turno actual (en lugar de la transcripción completa). No hay lógica específica de Gemini en adk-agent; el campo no se utiliza (es una operación no-op) para generateContent y otros proveedores.

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"

Alternativa por vencimiento de la ventana de retención. Las interacciones almacenadas caducan. Si un previous_interaction_id proporcionado está obsoleto o ha caducado, el servidor devuelve NotFound. El transporte gestiona esto de forma transparente: vuelve a enviar la transcripción completa e inicia una interacción nueva — no se muestra ningún error al agente ni al ejecutor. Las conversaciones de varios turnos siguen funcionando al superar el límite de retención sin necesidad de un tratamiento especial en el código.

Tipos reexportados

Con la característica gemini-interactions, los siguientes elementos están disponibles desde adk_model::gemini:

  • GeminiTransportGenerateContent (predeterminado) o Interactions.
  • InteractionOptionsstore, stateful, background, poll_interval.
  • BackgroundModeAgentTargetsOnly (predeterminado), Always, Never.
  • InteractionTarget — un destino de modelo/agente validado.

La superficie de omisión se encuentra en adk-tool (accesible mediante adk_tool o el paquete general):

  • Trait BypassMultiToolsLimit con with_bypass_multi_tools_limit(agent).
  • Implementado por GoogleSearchTool, UrlContextTool, GeminiFileSearchTool.

Los campos principales aditivos LlmResponse.interaction_id y LlmRequest.previous_response_id siempre están presentes (no están condicionados por características), por lo que el accesor event.interaction_id() se compila independientemente de qué proveedores estén habilitados.