Example: Gemini chat
The minimal adk-rs program: one LlmAgent backed by Gemini, one runner, one streamed turn printed to stdout.
gemini_chat is the smallest runnable agent in the repository — a single LlmAgent talking to Gemini for one turn. If you understand its forty lines, you understand the skeleton every adk-rs program shares.
What it demonstrates
- Constructing a provider client from the environment with
Gemini::from_env. - Building an
LlmAgentwith a name, description, model, and instruction. - Assembling a
Runnerwith an in-memorySessionService. - Consuming the event stream with
futures::StreamExtand extracting text viaContent::text_concat.
Run it
export GOOGLE_API_KEY="your-key"
cargo run --example gemini_chat --features geminiThe example requires the gemini feature (declared in the [[example]] section of Cargo.toml) and the GOOGLE_API_KEY environment variable; Gemini::from_env returns a configuration error if it is unset.
Full source
use std::sync::Arc;
use adk_rs::agents::LlmAgent;
use adk_rs::core::{Model, SessionService};
use adk_rs::providers::gemini::Gemini;
use adk_rs::runner::Runner;
use adk_rs::services::mem::InMemorySessionService;
use futures::StreamExt;
#[tokio::main]
async fn main() -> adk_rs::error::Result<()> {
let model: Arc<dyn Model> = Arc::new(Gemini::from_env("gemini-2.5-flash")?);
let agent = Arc::new(
LlmAgent::builder("greeter")
.description("A friendly greeter")
.model(model)
.instruction("You greet the user warmly and concisely.")
.build()?,
);
let svc: Arc<dyn SessionService> = Arc::new(InMemorySessionService::new());
let runner = Runner::builder()
.app_name("hello")
.agent(agent)
.session_service(svc)
.build()?;
let mut stream = runner.run("u", None, "Hello!").await?;
while let Some(ev) = stream.next().await {
let ev = ev?;
if let Some(c) = ev.response.content {
let text = c.text_concat();
if !text.is_empty() {
println!("[{}] {}", ev.author, text);
}
}
}
Ok(())
}Setup: the model
Gemini::from_env("gemini-2.5-flash") reads GOOGLE_API_KEY and yields a client speaking the Gemini REST API with SSE streaming. The agent never sees the concrete type — it is erased to Arc<dyn Model>, the single trait every provider implements, which is what makes swapping providers a one-line change.
Agent and runner construction
The LlmAgent::builder("greeter") chain sets a description (used by parent agents for delegation; informational here) and an instruction that becomes the system prompt. build() validates and returns the agent as a value.
The Runner is the orchestrator: it owns the session service, appends every event to the session, and drives the agent. InMemorySessionService is the zero-setup backend — state lives only as long as the process.
The event loop
runner.run("u", None, "Hello!") runs one turn for user "u"; the None session id auto-creates a fresh session. The returned EventStream is a Stream of Result<Event>. The loop unwraps each event, takes ev.response.content (an Option<Content>), and prints non-empty text tagged with ev.author — the name of the agent that produced the event.
[greeter] Hello! It's lovely to hear from you.A plain-text turn yields a single model event, so you see exactly one line (the wording varies by model run).
Related pages
- Quickstart — this program built from scratch, then extended with a tool.
- LLM agent — every builder option.
- Runner —
run,run_with,start, and cancellation. - Example: Weather agent — the next step: add a
#[tool]function.