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:

  1. Crie o agente com variáveis de template na instrução
  2. Configure o Runner e SessionService para gerenciar o estado
  3. Crie a sessão com variáveis de estado que correspondam ao seu template
  4. 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ãoExemploFonte
{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

  1. O agente recebe a mensagem do usuário → "Qual é o clima em Tóquio?"
  2. LLM decide chamar a ferramenta → Seleciona get_weather com {"city": "Tokyo"}
  3. A ferramenta é executada → Retorna {"temperature": "22°C", "condition": "sunny"}
  4. 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:

ProvedorAplicação nativa
GeminiEsquema completo, enviado como o esquema de resposta
OpenAI e compatível com OpenAIEsquema completo, enviado como um formato de resposta json_schema estrito
OpenRouterEsquema completo
DeepSeekJSON 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étodoDescriçã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 LLM
  • Parallel — 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 chamador
  • Auto — 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.


Anterior: Início rápido | Próximo: Agentes de fluxo de trabalho →

LlmAgent - Documentação ADK-Rust | ADK-Rust