LlmAgent
O LlmAgent é o tipo de agente principal em ADK-Rust que usa um Large Language Model para raciocínio e tomada de decisões.
Início rápido
Crie um novo projeto:
cargo new llm_agent
cd llm_agent
Adicione as dependências a Cargo.toml:
[dependencies]
adk-rust = "2.1.0"
tokio = { version = "1.40", features = ["full"] }
dotenvy = "0.15"
serde_json = "1.0"
Crie .env com sua chave API:
echo 'GOOGLE_API_KEY=your-api-key' > .env
Substitua 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-3.7-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(())
}
Execute:
cargo run
Interagindo com seu agente
Você verá um prompt interativo:
🤖 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!
Moldando o comportamento do agente com instruções
O método instruction() define a personalidade e o comportamento do seu agente. Este é o prompt do sistema que orienta cada resposta:
// 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()?;
Exemplos de saída
Prompt do usuário: "O que é Rust?"
Assistente de negócios formal:
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.
Tutor de programação amigável:
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?
Contador de histórias criativo:
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...
Criação de templates de instruções
As instruções permitem a injeção de variáveis usando a sintaxe {var}. As variáveis são resolvidas a partir do estado da sessão em tempo de execução:
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()?;
Guia passo a passo para usar templates:
- Crie o agente com variáveis de template na instrução
- Configure o Runner e SessionService para gerenciar o estado
- Crie a sessão com variáveis de estado que correspondam ao seu template
- Execute o agente — os templates são substituídos automaticamente
Aqui está um exemplo completo funcional:
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-3.7-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(())
}
Tipos de variáveis de template:
| Padrão | Exemplo | Fonte |
|---|---|---|
{var} | {user_name} | Estado da sessão |
{prefix:var} | {user:name}, {app:config} | Estado com prefixo |
{var?} | {user_name?} | Opcional (vazio se ausente) |
{artifact.file} | {artifact.resume.pdf} | Conteúdo do artefato |
Exemplo de saída:
Modelo: "You are helping {user_name}. Their role is {user_role}."
Torna-se: "You are helping Alice. Their role is Senior Developer."
O agente responderá então com conteúdo personalizado com base no nome e no nível de conhecimento do usuário!
Adicionando ferramentas
As ferramentas dão ao seu agente capacidades que vão além da conversa — elas podem buscar dados, realizar cálculos, pesquisar na web ou chamar APIs externos. O LLM decide quando usar uma ferramenta com base na solicitação do usuário.
Como as ferramentas funcionam
- O agente recebe a mensagem do usuário → "Qual é o clima em Tóquio?"
- LLM decide chamar a ferramenta → Seleciona
get_weathercom{"city": "Tokyo"} - A ferramenta é executada → Retorna
{"temperature": "22°C", "condition": "sunny"} - LLM formata a resposta → "O clima em Tóquio está ensolarado, com 22 °C."
Criando uma ferramenta com FunctionTool
FunctionTool é a maneira mais simples de criar uma ferramenta — basta encapsular qualquer função assíncrona do Rust, e o LLM poderá chamá-la. Você fornece um nome, uma descrição e uma função manipuladora que recebe argumentos JSON e retorna um resultado 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
},
);
Agora, as ferramentas nativas do provedor podem ser combinadas com instâncias de FunctionTool no mesmo agente. ADK encaminha as declarações das ferramentas nativas ao provedor, enquanto continua executando localmente as ferramentas de função comuns.
Criando um agente com várias ferramentas
Crie um novo projeto:
cargo new tool_agent
cd tool_agent
Adicione dependências a Cargo.toml:
[dependencies]
adk-rust = { version = "2.1.0", features = ["tools"] }
tokio = { version = "1.40", features = ["full"] }
dotenvy = "0.15"
serde_json = "1.0"
Crie .env:
echo 'GOOGLE_API_KEY=your-api-key' > .env
Substitua src/main.rs por um agente que tenha três ferramentas:
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-3.7-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(())
}
Execute seu agente:
cargo run
Exemplo de interação
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!
Saída estruturada com o esquema JSON
Para aplicações que precisam de dados estruturados, use 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-3.7-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(())
}
Como os provedores aplicam o esquema
output_schema chega ao provedor como GenerateContentConfig::response_schema.
O que o provedor faz com isso varia, e o agente valida o resultado de qualquer forma:
| Provedor | Aplicação nativa |
|---|---|
| Gemini | Esquema completo, enviado como o esquema de resposta |
| OpenAI e compatível com OpenAI | Esquema completo, enviado como um formato de resposta json_schema estrito |
| OpenRouter | Esquema completo |
| DeepSeek | JSON apenas de sintaxe — o JSON DeepSeek's modo de saída não tem uma variante json_schema, portanto o esquema é imposto pela validação do agente |
Quando um provedor impõe apenas a sintaxe, ou não impõe nada, o agente ainda injeta o esquema como uma instrução e valida a resposta; portanto, uma resposta que não esteja em conformidade custa uma nova tentativa em vez de retornar dados inválidos.
Observação: DeepSeek exige que a palavra "json" apareça no prompt sempre que JSON
Output estiver ativado; caso contrário, API poderá retornar conteúdo vazio. O adaptador adiciona essa
menção por conta própria quando o prompt ainda não a contém.
Exemplo de JSON Output
Entrada: "John encontrou Sarah em Paris em 25 de dezembro"
Saída:
{
"people": ["John", "Sarah"],
"locations": ["Paris"],
"dates": ["December 25th"]
}
Recursos avançados
Incluir conteúdo
Controle a visibilidade do histórico da conversa:
// Full history (default)
.include_contents(IncludeContents::Default)
// Stateless - sees only injected instructions plus the current user turn
.include_contents(IncludeContents::None)
Chave de saída
Salve as respostas do agente no estado da sessão:
.output_key("summary") // Response saved to state["summary"]
Instruções dinâmicas
Calcule as instruções em tempo de execução:
.instruction_provider(|ctx| {
Box::pin(async move {
let user_id = ctx.user_id();
Ok(format!("You are assisting user {}.", user_id))
})
})
Callbacks
Intercepte o comportamento do agente:
.before_model_callback(|ctx, request| {
Box::pin(async move {
println!("About to call LLM with {} messages", request.contents.len());
Ok(BeforeModelResult::Continue)
})
})
Referência do Builder
| Método | Descrição |
|---|---|
new(name) | Cria um construtor com o nome do agente |
model(Arc<dyn Llm>) | Define o LLM (obrigatório) |
description(text) | Descrição do agente |
instruction(text) | Prompt do sistema |
tool(Arc<dyn Tool>) | Adiciona uma ferramenta estática |
toolset(Arc<dyn Toolset>) | Adiciona um conjunto de ferramentas dinâmico resolvido por invocação |
output_schema(json) | JSON esquema para saída estruturada |
output_key(key) | Salva a resposta no estado |
include_contents(mode) | Visibilidade do histórico |
max_iterations(n) | Máximo de ciclos de ida e volta de LLM (padrão: 100) |
tool_execution_strategy(strategy) | Modo de despacho de ferramentas: Sequential, Parallel ou Auto |
default_retry_budget(RetryBudget) | Repetir ferramentas com falha até N vezes com atraso |
tool_retry_budget(name, RetryBudget) | Substituição da repetição por ferramenta |
circuit_breaker_threshold(u32) | Desabilitar a ferramenta após N falhas consecutivas |
on_tool_error(callback) | Registra o manipulador alternativo para falhas de ferramentas |
after_tool_callback_full(callback) | Callback avançado pós-ferramenta da V2 com ferramenta, argumentos e resposta |
build() | Cria o agente |
Controle de iterações
O método max_iterations() limita quantas interações de ida e volta LLM um agente pode realizar antes de parar. Isso é útil para:
- Impedir loops descontrolados de chamadas de ferramentas
- Controlar custos em produção
- Estabelecer limites razoáveis para tarefas complexas
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()?;
O padrão é de 100 iterações, o que é suficiente para a maioria dos casos de uso. Valores menores (5–20) são recomendados para agentes simples de perguntas e respostas, enquanto valores maiores podem ser necessários para tarefas complexas de raciocínio em várias etapas.
Conjuntos de ferramentas dinâmicos
Para ferramentas que dependem do contexto de invocação (por exemplo, sessões de navegador por usuário), use .toolset() em vez de .tool(). Os conjuntos de ferramentas são resolvidos no início de cada chamada 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()?;
Você pode combinar .tool() estáticas e .toolset() dinâmicas no mesmo agente. Nomes de ferramentas duplicados entre ferramentas estáticas e conjuntos de ferramentas produzem um erro determinístico.
RealtimeAgentBuilder também oferece suporte a .toolset() com a mesma semântica, portanto agentes de voz em tempo real também obtêm resolução dinâmica de ferramentas.
Composição de conjuntos de ferramentas
Use FilteredToolset, MergedToolset e PrefixedToolset de adk-tool para compor configurações complexas de conjuntos de ferramentas:
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()?;
Todos os utilitários de composição funcionam com qualquer implementação de Toolset, incluindo McpToolset e BrowserToolset.
Execução paralela de ferramentas
Quando um LLM retorna várias chamadas de ferramentas em uma única resposta, você pode controlar como elas são despachadas:
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()?;
Três estratégias estão disponíveis:
Sequential(padrão) — as ferramentas são executadas uma por vez, na ordem LLMParallel— todas as ferramentas são executadas simultaneamente; essa substituição explícita ignora os metadados de segurança, portanto a responsabilidade pela segurança é do chamadorAuto— as chamadas cujas ferramentas são somente leitura e seguras para execução simultânea são executadas primeiro em paralelo; todas as chamadas restantes são executadas sequencialmente
Os resultados são sempre retornados na ordem original de LLM, independentemente da estratégia. As ferramentas que falharem produzirão uma resposta de erro JSON sem interromper o lote.
A estratégia é definida por agente via LlmAgentBuilder::tool_execution_strategy(). Se não for definida, o padrão será Sequential.
Resiliência de ferramentas
Configure limites de novas tentativas e disjuntores para agentes em produção:
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()?;
Os retornos de chamada após a ferramenta podem inspecionar metadados estruturados de ToolOutcome via CallbackContext::tool_outcome():
.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)
})
}))
Exemplo completo
Um agente pronto para produção com várias ferramentas (clima, calculadora, pesquisa) e saída salva no estado da sessão:
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-3.7-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(())
}
Experimente estes prompts:
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.
Relacionado
- Agentes de fluxo de trabalho - Agentes sequenciais, paralelos e de loop
- Sistemas multiagente - Construindo hierarquias de agentes
- Ferramentas de função - Criando ferramentas personalizadas
- Retornos de chamada - Interceptando o comportamento do agente
Anterior: Início rápido | Próximo: Agentes de fluxo de trabalho →