use std::sync::Arc;
use adk_agent::LlmAgentBuilder;
use adk_core::{Agent, Content, Part, SessionId, UserId};
use adk_model::GeminiModel;
use adk_runner::Runner;
use adk_session::{CreateRequest, InMemorySessionService, SessionService};
use futures::StreamExt;
const APP_NAME: &str = "interactions-runtime-it";
const SESSION_ID: &str = "interactions-single-turn";
const MULTI_TURN_SESSION_ID: &str = "interactions-multi-turn";
const USER_ID: &str = "user";
fn api_key() -> Option<String> {
std::env::var("GOOGLE_API_KEY")
.ok()
.or_else(|| std::env::var("GEMINI_API_KEY").ok())
.filter(|key| !key.trim().is_empty())
}
#[tokio::test]
#[ignore = "requires GOOGLE_API_KEY/GEMINI_API_KEY, network access, and Interactions API (Beta) access"]
async fn single_turn_model_interaction_populates_interaction_id() {
dotenvy::dotenv().ok();
let Some(api_key) = api_key() else {
eprintln!("skipping: GOOGLE_API_KEY/GEMINI_API_KEY not set");
return;
};
let model = Arc::new(
GeminiModel::new(api_key, "gemini-2.5-flash")
.expect("failed to construct GeminiModel")
.use_interactions_api(true)
.expect("gemini-2.5-flash must be on the Interactions allowlist"),
);
let agent = Arc::new(
LlmAgentBuilder::new("interactions-agent")
.model(model)
.instruction("You are a helpful assistant. Answer concisely.")
.build()
.expect("failed to build LlmAgent"),
);
let sessions: Arc<dyn SessionService> = Arc::new(InMemorySessionService::new());
sessions
.create(CreateRequest {
app_name: APP_NAME.into(),
user_id: USER_ID.into(),
session_id: Some(SESSION_ID.into()),
state: std::collections::HashMap::new(),
})
.await
.expect("failed to create session");
let runner = Runner::builder()
.app_name(APP_NAME)
.agent(agent as Arc<dyn Agent>)
.session_service(sessions)
.build()
.expect("failed to build Runner");
let mut stream = runner
.run(
UserId::new(USER_ID).expect("valid user id"),
SessionId::new(SESSION_ID).expect("valid session id"),
Content::new("user").with_text("Say hello in one sentence."),
)
.await
.expect("runner.run failed");
let mut full_text = String::new();
let mut saw_interaction_id = false;
while let Some(event) = stream.next().await {
let event = event.expect("event stream yielded an error");
if event.interaction_id().is_some() {
saw_interaction_id = true;
}
if let Some(content) = &event.llm_response.content {
for part in &content.parts {
if let Part::Text { text } = part {
full_text.push_str(text);
}
}
}
}
assert!(
!full_text.trim().is_empty(),
"expected the Interactions transport to produce text output",
);
assert!(
saw_interaction_id,
"expected at least one emitted Event to carry a populated interaction_id",
);
}
async fn run_turn(runner: &Runner, session_id: &str, prompt: &str) -> (String, Option<String>) {
let mut stream = runner
.run(
UserId::new(USER_ID).expect("valid user id"),
SessionId::new(session_id).expect("valid session id"),
Content::new("user").with_text(prompt),
)
.await
.expect("runner.run failed");
let mut full_text = String::new();
let mut interaction_id: Option<String> = None;
while let Some(event) = stream.next().await {
let event = event.expect("event stream yielded an error");
if interaction_id.is_none()
&& let Some(id) = event.interaction_id()
{
interaction_id = Some(id.to_string());
}
if let Some(content) = &event.llm_response.content {
for part in &content.parts {
if let Part::Text { text } = part {
full_text.push_str(text);
}
}
}
}
(full_text, interaction_id)
}
#[tokio::test]
#[ignore = "requires GOOGLE_API_KEY/GEMINI_API_KEY, network access, and Interactions API (Beta) access"]
async fn multi_turn_stateful_conversation_retains_context() {
dotenvy::dotenv().ok();
let Some(api_key) = api_key() else {
eprintln!("skipping: GOOGLE_API_KEY/GEMINI_API_KEY not set");
return;
};
let model = Arc::new(
GeminiModel::new(api_key, "gemini-2.5-flash")
.expect("failed to construct GeminiModel")
.use_interactions_api(true)
.expect("gemini-2.5-flash must be on the Interactions allowlist"),
);
let agent = Arc::new(
LlmAgentBuilder::new("interactions-agent")
.model(model)
.instruction("You are a helpful assistant. Answer concisely.")
.build()
.expect("failed to build LlmAgent"),
);
let sessions: Arc<dyn SessionService> = Arc::new(InMemorySessionService::new());
sessions
.create(CreateRequest {
app_name: APP_NAME.into(),
user_id: USER_ID.into(),
session_id: Some(MULTI_TURN_SESSION_ID.into()),
state: std::collections::HashMap::new(),
})
.await
.expect("failed to create session");
let runner = Runner::builder()
.app_name(APP_NAME)
.agent(agent as Arc<dyn Agent>)
.session_service(sessions)
.build()
.expect("failed to build Runner");
let (_turn1_text, id1) =
run_turn(&runner, MULTI_TURN_SESSION_ID, "My favorite color is blue. Remember it.").await;
let (turn2_text, id2) =
run_turn(&runner, MULTI_TURN_SESSION_ID, "What is my favorite color?").await;
assert!(id1.is_some(), "expected turn 1 to carry a populated interaction_id",);
assert!(id2.is_some(), "expected turn 2 to carry a populated interaction_id",);
assert_ne!(id1, id2, "expected turn 2 to be a distinct interaction chained from turn 1",);
assert!(
turn2_text.to_lowercase().contains("blue"),
"expected the turn-2 answer to recall the favorite color \"blue\", got: {turn2_text:?}",
);
}