LlmAgent
LlmAgent 是 ADK-Rust 中的核心 agent 类型,它使用大型语言模型进行推理和决策。
快速开始
创建一个新项目:
cargo new llm_agent
cd llm_agent
向 Cargo.toml 添加依赖:
[dependencies]
adk-rust = "2.0.0"
tokio = { version = "1.40", features = ["full"] }
dotenvy = "0.15"
serde_json = "1.0"
使用你的 API 密钥创建 .env:
echo 'GOOGLE_API_KEY=your-api-key' > .env
替换 src/main.rs:
use adk_rust::prelude::*;
use adk_rust::{SessionId, UserId};
use adk_rust::Launcher;
use std::sync::Arc;
#[tokio::main]
async fn main() -> std::result::Result<(), Box<dyn std::error::Error>> {
dotenvy::dotenv().ok();
let api_key = std::env::var("GOOGLE_API_KEY")?;
let model = GeminiModel::new(&api_key, "gemini-2.5-flash")?;
let agent = LlmAgentBuilder::new("my_agent")
.instruction("You are a helpful assistant.")
.model(Arc::new(model))
.build()?;
Launcher::new(Arc::new(agent)).run().await?;
Ok(())
}
运行它:
cargo run
与你的 Agent 交互
你会看到一个交互式提示符:
🤖 Agent ready! Type your questions (or 'exit' to quit).
You: Hello! What can you help me with?
Assistant: Hello! I'm a helpful assistant. I can help you with:
- Answering questions on various topics
- Explaining concepts clearly
- Having a conversation
What would you like to know?
You: exit
👋 Goodbye!
使用指令塑造 Agent 行为
instruction() 方法定义了你的 agent 的个性和行为。这是指导每次响应的系统提示词:
// A formal business assistant
let formal_agent = LlmAgentBuilder::new("formal_assistant")
.instruction("You are a professional business consultant. \
Use formal language. Be concise and data-driven.")
.model(Arc::new(model))
.build()?;
// A friendly coding tutor
let tutor_agent = LlmAgentBuilder::new("code_tutor")
.instruction("You are a friendly coding tutor for beginners. \
Explain concepts simply. Use examples. \
Encourage questions. Never make the user feel bad for not knowing.")
.model(Arc::new(model))
.build()?;
// A creative storyteller
let story_agent = LlmAgentBuilder::new("storyteller")
.instruction("You are a creative storyteller. \
Craft engaging narratives with vivid descriptions. \
Use plot twists and memorable characters.")
.model(Arc::new(model))
.build()?;
示例输出
用户提示:“什么是 Rust?”
正式商务助手:
Rust is a systems programming language focused on performance, memory safety,
and concurrency. Key metrics: 0 runtime overhead, 0 garbage collection.
Adoption growing 30% YoY in enterprise. Recommended for performance-critical
applications where reliability is paramount.
友好的编程导师:
Great question! 🎉 Rust is a programming language that helps you write fast
and safe code. Think of it like a helpful friend who stops you from making
mistakes before they happen!
Here's a simple example:
let name = "Alice"; // This creates a variable
What makes Rust special is that it catches bugs at compile time, not when
your program is running. Pretty cool, right? Want me to explain more?
创意讲故事者:
In the twilight of 2010, in a secret Mozilla laboratory, a lone developer
named Graydon Hoare breathed life into something extraordinary—a language
that would challenge the old gods of C and C++.
They called it Rust, named for a fungus that survives against all odds.
Like a phoenix rising from the ashes of segmentation faults and memory
leaks, Rust promised a new era: speed without sacrifice, power without peril.
And so, the revolution began...
指令模板化
指令支持使用 {var} 语法进行变量注入。变量会在运行时从 session state 中解析:
let agent = LlmAgentBuilder::new("personalized")
.instruction("You are helping {user_name}. Their role is {user_role}. \
Tailor your responses to their expertise level.")
.model(Arc::new(model))
.build()?;
使用模板的分步指南:
- 创建 agent,在指令中使用模板变量
- 设置 Runner 和 SessionService 来管理状态
- 使用状态变量创建 session,其变量需与模板匹配
- 运行 agent - 模板会自动替换
下面是一个完整可运行的示例:
use adk_rust::prelude::*;
use adk_rust::{SessionId, UserId};
use adk_rust::runner::{Runner, RunnerConfig};
use adk_rust::session::{CreateRequest, InMemorySessionService, SessionService};
use adk_rust::futures::StreamExt;
use serde_json::json;
use std::collections::HashMap;
use std::sync::Arc;
#[tokio::main]
async fn main() -> std::result::Result<(), Box<dyn std::error::Error>> {
dotenvy::dotenv().ok();
let api_key = std::env::var("GOOGLE_API_KEY")?;
let model = GeminiModel::new(&api_key, "gemini-2.5-flash")?;
// 1. Agent with templated instruction
let agent = LlmAgentBuilder::new("personalized")
.instruction("You are helping {user_name}. Their role is {user_role}. \
Tailor your responses to their expertise level.")
.model(Arc::new(model))
.build()?;
// 2. Create session service and runner
let session_service = Arc::new(InMemorySessionService::new());
let runner = Runner::new(RunnerConfig {
app_name: "templating_demo".to_string(),
agent: Arc::new(agent),
session_service: session_service.clone(),
artifact_service: None,
memory_service: None,
run_config: None,
})?;
// 3. Create session with state variables
let mut state = HashMap::new();
state.insert("user_name".to_string(), json!("Alice"));
state.insert("user_role".to_string(), json!("Senior Developer"));
let session = session_service.create(CreateRequest {
app_name: "templating_demo".to_string(),
user_id: "user123".to_string(),
session_id: None,
state,
}).await?;
// 4. Run the agent - instruction becomes:
// "You are helping Alice. Their role is Senior Developer..."
let mut response_stream = runner.run(
UserId::new("user123")?,
SessionId::new(session.id())?,
Content::new("user").with_text("Explain async/await in Rust"),
).await?;
// Print the response
while let Some(event) = response_stream.next().await {
let event = event?;
if let Some(content) = event.content() {
for part in &content.parts {
if let Part::Text { text } = part {
print!("{}", text);
}
}
}
}
Ok(())
}
模板变量类型:
| 模式 | 示例 | 来源 |
|---|---|---|
{var} | {user_name} | 会话状态 |
{prefix:var} | {user:name}, {app:config} | 带前缀的状态 |
{var?} | {user_name?} | 可选(若缺失则为空) |
{artifact.file} | {artifact.resume.pdf} | 制品内容 |
输出示例:
模板:"You are helping {user_name}. Their role is {user_role}."
变为:"You are helping Alice. Their role is Senior Developer."
代理随后会根据用户的姓名和专业水平返回个性化内容!
添加工具
工具让你的代理拥有超越对话的能力——它们可以获取数据、执行计算、搜索网页,或调用外部 APIs。LLM 会根据用户的请求决定何时使用工具。
工具如何工作
- 代理接收用户消息 → "东京的天气怎么样?"
- LLM 决定调用工具 → 选择带有
get_weather和{"city": "Tokyo"}的 - 工具执行 → 返回
{"temperature": "22°C", "condition": "sunny"} - LLM 格式化响应 → "东京天气晴朗,22°C。"
使用 FunctionTool 创建工具
FunctionTool 是创建工具最简单的方法——将任意异步 Rust 函数包装起来,LLM 就可以调用它。你需要提供名称、描述以及处理函数;该函数接收 JSON 参数并返回 JSON 结果。
let weather_tool = FunctionTool::new(
"get_weather", // Tool name (used by LLM)
"Get the current weather for a city", // Description (helps LLM decide when to use it)
|_ctx, args| async move { // Handler function
let city = args.get("city") // Extract arguments from JSON
.and_then(|v| v.as_str())
.unwrap_or("unknown");
Ok(json!({ "city": city, "temperature": "22°C" })) // Return JSON result
},
);
内置的 provider 原生工具现在可以与同一代理中的 FunctionTool 实例混合使用。ADK 会将原生工具声明转发给 provider,同时仍在本地执行普通函数工具。
构建一个多工具代理
创建一个新项目:
cargo new tool_agent
cd tool_agent
向 Cargo.toml 添加依赖:
[dependencies]
adk-rust = { version = "2.0.0", features = ["tools"] }
tokio = { version = "1.40", features = ["full"] }
dotenvy = "0.15"
serde_json = "1.0"
创建 .env:
echo 'GOOGLE_API_KEY=your-api-key' > .env
将 src/main.rs 替换为一个拥有三个工具的代理:
use adk_rust::prelude::*;
use adk_rust::Launcher;
use serde_json::json;
use std::sync::Arc;
#[tokio::main]
async fn main() -> std::result::Result<(), Box<dyn std::error::Error>> {
dotenvy::dotenv().ok();
let api_key = std::env::var("GOOGLE_API_KEY")?;
let model = GeminiModel::new(&api_key, "gemini-2.5-flash")?;
// Tool 1: Weather lookup
let weather_tool = FunctionTool::new(
"get_weather",
"Get the current weather for a city. Parameters: city (string)",
|_ctx, args| async move {
let city = args.get("city").and_then(|v| v.as_str()).unwrap_or("unknown");
Ok(json!({ "city": city, "temperature": "22°C", "condition": "sunny" }))
},
);
// Tool 2: Calculator
let calculator = FunctionTool::new(
"calculate",
"Perform arithmetic. Parameters: a (number), b (number), operation (add/subtract/multiply/divide)",
|_ctx, args| async move {
let a = args.get("a").and_then(|v| v.as_f64()).unwrap_or(0.0);
let b = args.get("b").and_then(|v| v.as_f64()).unwrap_or(0.0);
let op = args.get("operation").and_then(|v| v.as_str()).unwrap_or("add");
let result = match op {
"add" => a + b,
"subtract" => a - b,
"multiply" => a * b,
"divide" => if b != 0.0 { a / b } else { 0.0 },
_ => 0.0,
};
Ok(json!({ "result": result }))
},
);
// Tool 3: Built-in Google Search (Note: Currently unsupported in ADK-Rust)
// let search_tool = GoogleSearchTool::new();
// Build agent with weather and calculator tools
let agent = LlmAgentBuilder::new("multi_tool_agent")
.instruction("You are a helpful assistant. Use tools when needed: \
- get_weather for weather questions \
- calculate for math")
.model(Arc::new(model))
.tool(Arc::new(weather_tool))
.tool(Arc::new(calculator))
// .tool(Arc::new(search_tool)) // Currently unsupported
.build()?;
Launcher::new(Arc::new(agent)).run().await?;
Ok(())
}
运行你的代理:
cargo run
示例交互
You: What's 15% of 250?
Assistant: [Using calculate tool with a=250, b=0.15, operation=multiply]
15% of 250 is 37.5.
You: What's the weather in Tokyo?
Assistant: [Using get_weather tool with city=Tokyo]
The weather in Tokyo is sunny with a temperature of 22°C.
You: Search for latest Rust features
Assistant: I don't have access to search functionality at the moment, but I can help with other questions about Rust or perform calculations!
使用 JSON Schema 的结构化输出
对于需要结构化数据的应用,请使用 output_schema():
use adk_rust::prelude::*;
use serde_json::json;
use std::sync::Arc;
#[tokio::main]
async fn main() -> std::result::Result<(), Box<dyn std::error::Error>> {
dotenvy::dotenv().ok();
let api_key = std::env::var("GOOGLE_API_KEY")?;
let model = GeminiModel::new(&api_key, "gemini-2.5-flash")?;
let extractor = LlmAgentBuilder::new("entity_extractor")
.instruction("Extract entities from the given text.")
.model(Arc::new(model))
.output_schema(json!({
"type": "object",
"properties": {
"people": {
"type": "array",
"items": { "type": "string" }
},
"locations": {
"type": "array",
"items": { "type": "string" }
},
"dates": {
"type": "array",
"items": { "type": "string" }
}
},
"required": ["people", "locations", "dates"]
}))
.build()?;
println!("Entity extractor ready!");
Ok(())
}
Provider 如何强制执行 Schema
output_schema 作为 GenerateContentConfig::response_schema 发送到 provider。
provider 如何处理它会有所不同,但无论哪种方式,代理都会验证结果:
| 提供方 | 原生强制 |
|---|---|
| Gemini | 完整 schema,作为 response schema 发送 |
| OpenAI 和 OpenAI 兼容 | 完整 schema,作为严格的 json_schema response format 发送 |
| OpenRouter | 完整 schema |
| DeepSeek | 仅 JSON 语法 — DeepSeek 的 JSON 输出模式没有 json_schema 变体,因此 schema 由代理的验证强制执行 |
当提供方仅强制语法,或者根本不强制时,agent 仍然会将 schema 作为指令注入并验证回复,因此不符合规范的答案只会触发重试,而不会返回错误数据。
注意: DeepSeek 要求在提示中始终出现单词 "json",只要 JSON Output 处于开启状态,否则 API 可能返回空内容。当你的提示中尚未包含该词时,适配器会自动添加这一提及。
JSON 输出示例
输入: "John met Sarah in Paris on December 25th"
输出:
{
"people": ["John", "Sarah"],
"locations": ["Paris"],
"dates": ["December 25th"]
}
高级功能
包含内容
控制对话历史的可见性:
// Full history (default)
.include_contents(IncludeContents::Default)
// Stateless - sees only injected instructions plus the current user turn
.include_contents(IncludeContents::None)
输出键
将 agent 回复保存到会话状态:
.output_key("summary") // Response saved to state["summary"]
动态指令
在运行时计算指令:
.instruction_provider(|ctx| {
Box::pin(async move {
let user_id = ctx.user_id();
Ok(format!("You are assisting user {}.", user_id))
})
})
回调
拦截 agent 行为:
.before_model_callback(|ctx, request| {
Box::pin(async move {
println!("About to call LLM with {} messages", request.contents.len());
Ok(BeforeModelResult::Continue)
})
})
构建器参考
| 方法 | 描述 |
|---|---|
new(name) | 使用 agent 名称创建构建器 |
model(Arc<dyn Llm>) | 设置 LLM(必需) |
description(text) | Agent 描述 |
instruction(text) | 系统提示词 |
tool(Arc<dyn Tool>) | 添加一个静态工具 |
toolset(Arc<dyn Toolset>) | 添加一个按次调用解析的动态工具集 |
output_schema(json) | 用于结构化输出的 JSON 模式 |
output_key(key) | 将响应保存到状态 |
include_contents(mode) | 历史可见性 |
max_iterations(n) | 最大 LLM 往返次数(默认:100) |
tool_execution_strategy(strategy) | 工具分发模式:Sequential、Parallel 或 Auto |
default_retry_budget(RetryBudget) | 带延迟重试失败的工具最多 N 次 |
tool_retry_budget(name, RetryBudget) | 按工具覆盖重试设置 |
circuit_breaker_threshold(u32) | 连续失败 N 次后禁用工具 |
on_tool_error(callback) | 为工具失败注册回退处理器 |
after_tool_callback_full(callback) | 带有工具、参数和响应的 V2 丰富 after-tool 回调 |
build() | 创建该代理 |
迭代控制
max_iterations() 方法限制了代理在停止之前可以进行多少次 LLM 往返。这对于以下情况很有用:
- 防止失控的工具调用循环
- 在生产环境中控制成本
- 为复杂任务设置合理的边界
let agent = LlmAgentBuilder::new("bounded_agent")
.model(Arc::new(model))
.instruction("You are a helpful assistant.")
.tool(Arc::new(my_tool))
.max_iterations(10) // Stop after 10 LLM calls
.build()?;
默认值为 100 次迭代,这对大多数使用场景已经足够。对于简单的问答代理,建议使用较低的值(5-20);而对于复杂的多步骤推理任务,则可能需要更高的值。
动态工具集
对于依赖调用上下文的工具(例如按用户区分的浏览器会话),请使用 .toolset() 而不是 .tool()。工具集会在每次 run() 调用开始时解析:
use adk_browser::{BrowserSessionPool, BrowserToolset, BrowserConfig};
use adk_agent::LlmAgentBuilder;
use std::sync::Arc;
let pool = Arc::new(BrowserSessionPool::new(BrowserConfig::new(), 10));
let toolset = Arc::new(BrowserToolset::with_pool(pool));
let agent = LlmAgentBuilder::new("web_agent")
.model(model)
.instruction("You are a web automation assistant.")
.toolset(toolset) // Resolved per-user at runtime
.build()?;
你可以在同一个代理上同时混用静态 .tool() 和动态 .toolset()。静态工具和工具集之间如果存在重复的工具名称,会产生确定性的错误。
RealtimeAgentBuilder 也支持具有相同语义的 .toolset(),因此实时语音代理也能获得动态工具解析。
工具集组合
使用来自 adk-tool 的 FilteredToolset、MergedToolset 和 PrefixedToolset 来组合复杂的工具集配置:
use adk_tool::{BasicToolset, FilteredToolset, MergedToolset, PrefixedToolset, string_predicate};
// Prefix weather tools to avoid name collisions
let weather = Arc::new(PrefixedToolset::new(weather_toolset, "wx"));
// Filter utility tools to only expose search and calculate
let utils = Arc::new(FilteredToolset::new(
utility_toolset,
string_predicate(vec!["search".into(), "calculate".into()]),
));
// Merge into a single toolset
let composed = MergedToolset::new("all", vec![weather, utils]);
let agent = LlmAgentBuilder::new("agent")
.model(model)
.toolset(Arc::new(composed))
.build()?;
所有组合实用工具都适用于任何 Toolset 实现,包括 McpToolset 和 BrowserToolset。
并行工具执行
当 LLM 在单个响应中返回多个工具调用时,你可以控制它们的分发方式:
use adk_core::ToolExecutionStrategy;
let agent = LlmAgentBuilder::new("fast_agent")
.model(Arc::new(model))
.instruction("You are a research assistant. Use multiple tools in parallel.")
// Auto requires both safety signals for concurrent inclusion
.tool_execution_strategy(ToolExecutionStrategy::Auto)
.tool(Arc::new(
search_tool
.with_read_only(true)
.with_concurrency_safe(true),
))
.tool(Arc::new(
lookup_tool
.with_read_only(true)
.with_concurrency_safe(true),
))
.tool(Arc::new(save_tool)) // runs after the concurrent safe subset
.build()?;
提供三种策略:
Sequential(默认)—— 工具按 LLM 顺序逐个执行Parallel—— 所有工具并发执行;此显式覆盖会绕过安全元数据,因此安全性由调用方负责Auto—— 工具既只读又并发安全的调用先并发执行;其余所有调用随后按顺序执行
无论采用哪种策略,结果始终按原始 LLM 顺序返回。失败的工具会产生一个 JSON 错误响应,而不会中止整个批处理。
策略通过 LlmAgentBuilder::tool_execution_strategy() 按代理设置。如果未设置,默认值为 Sequential。
工具弹性
为生产代理配置重试预算和熔断器:
use adk_core::RetryBudget;
use std::time::Duration;
let agent = LlmAgentBuilder::new("resilient_agent")
.model(model)
.tool(Arc::new(my_tool))
// Retry all tools up to 2 times with 500ms delay
.default_retry_budget(RetryBudget::new(2, Duration::from_millis(500)))
// Override for a specific tool
.tool_retry_budget("flaky_api", RetryBudget::new(4, Duration::from_secs(1)))
// Disable a tool after 3 consecutive failures in one invocation
.circuit_breaker_threshold(3)
// Provide a fallback when a tool fails
.on_tool_error(Box::new(|_ctx, tool, _args, error| {
Box::pin(async move {
tracing::warn!(tool = tool.name(), %error, "tool failed");
Ok(None) // None = propagate error; Some(value) = use as fallback
})
}))
.build()?;
工具后的回调可以通过 CallbackContext::tool_outcome() 检查结构化的 ToolOutcome 元数据:
.after_tool_callback(Box::new(|ctx| {
Box::pin(async move {
if let Some(outcome) = ctx.tool_outcome() {
println!(
"Tool '{}' {} in {:?} (attempt {})",
outcome.tool_name,
if outcome.success { "succeeded" } else { "failed" },
outcome.duration,
outcome.attempt,
);
}
Ok(None)
})
}))
完整示例
一个适用于生产环境的代理,包含多个工具(天气、计算器、搜索),并将输出保存到会话状态中:
use adk_rust::prelude::*;
use adk_rust::Launcher;
use serde_json::json;
use std::sync::Arc;
#[tokio::main]
async fn main() -> std::result::Result<(), Box<dyn std::error::Error>> {
dotenvy::dotenv().ok();
let api_key = std::env::var("GOOGLE_API_KEY")?;
let model = GeminiModel::new(&api_key, "gemini-2.5-flash")?;
// Weather tool
let weather = FunctionTool::new(
"get_weather",
"Get weather for a city. Parameters: city (string)",
|_ctx, args| async move {
let city = args.get("city").and_then(|v| v.as_str()).unwrap_or("unknown");
Ok(json!({
"city": city,
"temperature": "22°C",
"humidity": "65%",
"condition": "partly cloudy"
}))
},
);
// Calculator tool
let calc = FunctionTool::new(
"calculate",
"Math operations. Parameters: expression (string like '2 + 2')",
|_ctx, args| async move {
let expr = args.get("expression").and_then(|v| v.as_str()).unwrap_or("0");
Ok(json!({ "expression": expr, "result": "computed" }))
},
);
// Build the full agent
let agent = LlmAgentBuilder::new("assistant")
.description("A helpful assistant with weather and calculation abilities")
.instruction("You are a helpful assistant. \
Use the weather tool for weather questions. \
Use the calculator for math. \
Be concise and friendly.")
.model(Arc::new(model))
.tool(Arc::new(weather))
.tool(Arc::new(calc))
// .tool(Arc::new(GoogleSearchTool::new())) // Provider-native tools can be mixed with FunctionTool
.output_key("last_response")
.build()?;
println!("✅ Agent '{}' ready!", agent.name());
Launcher::new(Arc::new(agent)).run().await?;
Ok(())
}
尝试这些提示:
You: What's 25 times 4?
Assistant: It's 100.
You: How's the weather in New York?
Assistant: The weather in New York is partly cloudy with a temperature of 22°C and 65% humidity.
You: Calculate 15% tip on $85
Assistant: A 15% tip on $85 is $12.75, making the total $97.75.