LlmAgent

O LlmAgent é o tipo de agente central em ADK-Rust que usa um Large Language Model para raciocínio e tomada de decisão.

Início Rápido

Crie um novo projeto:

cargo new llm_agent
cd llm_agent

Adicione dependências a Cargo.toml:

[dependencies]
adk-rust = "2.0.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-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(())
}

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 system prompt 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: "What is Rust?"

Assistente Formal de Negócios:

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...

Modelagem de Instruções

As instruções suportam injeção de variáveis usando a sintaxe {var}. As variáveis são resolvidas 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 modelagem:

  1. Crie o agente com variáveis de modelo na instrução
  2. Configure Runner e SessionService para gerenciar o estado
  3. Crie a sessão com variáveis de estado que correspondam ao seu modelo
  4. Execute o agente - os modelos são substituídos automaticamente

Aqui está um exemplo completo funcionando:

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(())
}

Tipos de Variáveis de Modelo:

PadrãoExemploOrigem
{var}{user_name}Estado da sessão
{prefix:var}{user:name}, {app:config}Estado prefixado
{var?}{user_name?}Opcional (vazio se ausente)
{artifact.file}{artifact.resume.pdf}Conteúdo do artefato

Exemplo de saída:

Template: "You are helping {user_name}. Their role is {user_role}."
Torna-se: "You are helping Alice. Their role is Senior Developer."

O agente então responderá com conteúdo personalizado com base no nome e no nível de experiência do usuário!


Adicionando Ferramentas

As ferramentas dão ao seu agente capacidades além da conversa — elas podem buscar dados, realizar cálculos, pesquisar na web ou chamar APIs externas. 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 é a previsão do tempo em Tóquio?"
  2. LLM decide chamar a ferramenta → Seleciona get_weather com {"city": "Tokyo"}
  3. A ferramenta executa → Retorna {"temperature": "22°C", "condition": "sunny"}
  4. LLM formata a resposta → "O tempo em Tóquio está ensolarado, com 22°C."

Criando uma Ferramenta com FunctionTool

FunctionTool é a maneira mais simples de criar uma ferramenta — envolva qualquer função assíncrona em Rust e o LLM pode chamá-la. Você fornece um nome, uma descrição e uma função handler 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
    },
);

As ferramentas nativas integradas ao provedor agora podem ser combinadas com instâncias de FunctionTool no mesmo agente. ADK encaminha as declarações de ferramentas nativas para o provedor enquanto ainda executa as ferramentas de função comuns localmente.

Criando um Agente com Múltiplas Ferramentas

Crie um novo projeto:

cargo new tool_agent
cd tool_agent

Adicione dependências a Cargo.toml:

[dependencies]
adk-rust = { version = "2.0.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-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(())
}

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 Schema 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-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(())
}

Como os Provedores Impõem o Schema

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 OpenAI-compatívelEsquema completo, enviado como um formato de resposta estrito json_schema
OpenRouterEsquema completo
DeepSeekJSON sintaxe apenas — DeepSeek's JSON Modo de saída não tem variante json_schema, então o esquema é imposto pela validação do agente

Onde um provedor impõe apenas sintaxe, ou nada, o agente ainda injeta o esquema como instrução e valida a resposta, então uma resposta não conforme custa uma nova tentativa em vez de retornar dados incorretos.

Nota: DeepSeek requer que a palavra "json" apareça no prompt sempre que JSON Output estiver ativado, caso contrário o API pode retornar conteúdo vazio. O adaptador adiciona essa menção por conta própria quando seu prompt ainda não a contém.

JSON Exemplo de Saída

Input: "John encontrou Sarah em Paris no dia 25 de dezembro"

Output:

{
  "people": ["John", "Sarah"],
  "locations": ["Paris"],
  "dates": ["December 25th"]
}

Recursos Avançados

Incluir Conteúdos

Controlar 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

Salvar respostas do agente no estado da sessão:

.output_key("summary")  // Response saved to state["summary"]

Instruções Dinâmicas

Calcular 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

Interceptar 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 schema 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 LLM round-trips (padrão: 100)
tool_execution_strategy(strategy)Modo de despacho da ferramenta: Sequential, Parallel ou Auto
default_retry_budget(RetryBudget)Tentar novamente as ferramentas com falha até N vezes com atraso
tool_retry_budget(name, RetryBudget)Substituição de nova tentativa por ferramenta
circuit_breaker_threshold(u32)Desativar a ferramenta após N falhas consecutivas
on_tool_error(callback)Registrar manipulador de fallback para falhas de ferramenta
after_tool_callback_full(callback)Callback rico v2 após a ferramenta com ferramenta, args e response
build()Cria o agente

Controle de Iteração

O método max_iterations() limita quantas idas e vindas de LLM um agente pode fazer antes de parar. Isso é útil para:

  • Evitar loops descontrolados de chamadas de ferramentas
  • Controlar custos em produção
  • Definir 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 é 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.


Toolsets 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 toolsets 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 misturar .tool() estáticas e .toolset() dinâmicas no mesmo agente. Nomes de ferramentas duplicados entre ferramentas estáticas e toolsets produzem um erro determinístico.

RealtimeAgentBuilder também oferece suporte a .toolset() com a mesma semântica, então agentes de voz em tempo real também recebem resolução dinâmica de ferramentas.

Composição de Toolset

Use FilteredToolset, MergedToolset e PrefixedToolset de adk-tool para compor configurações complexas de toolset:

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()?;

Todas as utilidades 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 distribuídas:

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 executam uma de cada vez na ordem LLM
  • Parallel — todas as ferramentas executam concorrentemente; essa substituição explícita ignora os metadados de segurança, então a segurança fica por conta do chamador
  • Auto — chamadas cujas ferramentas são somente leitura e seguras para concorrência executam concorrentemente primeiro; todas as chamadas restantes executam sequencialmente depois

Os resultados são sempre retornados na ordem original LLM, independentemente da estratégia. Ferramentas com falha produzem uma resposta de erro JSON sem abortar o lote.

A estratégia é definida por agente via LlmAgentBuilder::tool_execution_strategy(). Se não for definida, o padrão é Sequential.

Resiliência de Ferramentas

Configure orçamentos de tentativas e circuit breakers para agentes de 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()?;

Callbacks pós-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 múltiplas ferramentas (tempo, calculadora, busca) 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-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(())
}

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 →