LlmAgent
The LLM-powered agent: its builder, instruction templating, conversation history control, and the LLM-to-tool loop.
LlmAgent is the workhorse of adk-rs: an agent that sends the session history to a model, dispatches any function calls the model makes through a gate pipeline (confirmation → auth → run), feeds the results back, and repeats until the model produces a final answer.
Building one
use std::sync::Arc;
use adk_rs::agents::LlmAgent;
use adk_rs::providers::gemini::Gemini;
let agent = LlmAgent::builder("assistant")
.description("Answers questions about the product.")
.model(Arc::new(Gemini::from_env("gemini-2.5-flash")?))
.instruction("You are a concise, factual assistant.")
.build()?;LlmAgent::builder(name) returns an LlmAgentBuilder. build() fails if name is empty or no model was set. The crate exports adk_rs::agents::DEFAULT_MODEL ("gemini-2.5-flash") as a convenient model id constant, but the builder always requires an explicit .model(...).
Builder reference
pub fn description(self, d: impl Into<String>) -> Self- Human-readable description, shown to a parent model when deciding whether to delegate to this agent.
pub fn model(self, m: Arc<dyn Model>) -> Self- The provider model (required). Any
Modelimplementation: Gemini, Anthropic, OpenAI-compatible, or your own. pub fn instruction(self, s: impl Into<String>) -> Self- Static system instruction. Templated with
{key}placeholders against session state on every turn (see below). pub fn instruction_dyn(self, p: InstructionProvider) -> Self- Dynamic instruction: an async function
Fn(&ReadonlyContext) -> BoxFuture<Result<String>>evaluated per turn. Bypasses templating — the provider reads state itself. pub fn global_instruction(self, s: impl Into<String>) -> Self- Static instruction prefixed before
instruction(separated by a blank line). Also templated. pub fn static_instruction(self, s: impl Into<String>) -> Self- Cache-stable instruction prefix. Sent verbatim — never templated or re-evaluated — so the system instruction stays byte-identical across turns; when set, the dynamic
instructionrides in the request contents instead. Pair with context caching. pub fn static_instruction_content(self, c: Content) -> Self- Like
static_instructionbut accepts arbitraryContent(e.g. multimodal parts). pub fn tool(self, t: Arc<dyn DynTool>) -> Self- Registers one tool.
pub fn tools(self, ts: impl IntoIterator<Item = Arc<dyn DynTool>>) -> Self- Registers many tools at once (e.g. the output of an OpenAPI toolset).
pub fn sub_agent(self, a: Arc<dyn BaseAgent>) -> Self- Registers a child agent. Call repeatedly for multiple children. Declaring a sub-agent auto-registers the
transfer_to_agenttool and advertises the sub-agents’ names and descriptions in the system instruction. See Multi-agent systems. pub fn disable_transfer(self, yes: bool) -> Self- When true,
transfer_to_agentrequests raised by tools are ignored and the agent keeps control. Also suppresses the auto-registration of the transfer tool and the sub-agent roster in the system instruction. pub fn max_iterations(self, n: u32) -> Self- Caps iterations of the LLM↔tool loop within a single run. Default: 16.
pub fn output_key(self, key: impl Into<String>) -> Self- Saves the agent’s final response into session state under this key, via the final event’s
state_delta. Withoutput_schema, the stored value is the parsed JSON rather than raw text. pub fn output_schema(self, schema: Schema) -> Self- Forces structured JSON output conforming to
schema(sets the request’s response schema andapplication/jsonmime type). See Structured output. pub fn include_contents(self, ic: IncludeContents) -> Self- Controls how much conversation history the model sees (see below).
pub fn code_executor(self, ex: Arc<dyn CodeExecutor>) -> Self- (feature
code-exec) ExecutesExecutableCodeparts emitted by the model and feeds backCodeExecutionResultparts. See Code execution. pub fn before_agent_callback(self, cb: BeforeAgentCallback) -> Self- Hook before the agent runs; returning
Some(content)short-circuits the run. See Callbacks & plugins. pub fn after_agent_callback(self, cb: AfterAgentCallback) -> Self- Hook after the agent completes; returning
Some(content)appends one more event. pub fn before_model_callback(self, cb: BeforeModelCallback) -> Self- Hook before every model call; may rewrite the
LlmRequestin place or returnSome(response)to skip the call. pub fn after_model_callback(self, cb: AfterModelCallback) -> Self- Hook after every model call; returning
Some(response)replaces the model’s response. pub fn on_model_error_callback(self, cb: OnModelErrorCallback) -> Self- Recovery hook for failed model calls; returning
Some(response)recovers the turn instead of failing the run. pub fn before_tool_callback(self, cb: BeforeToolCallback) -> Self- Hook before every tool run; may rewrite the args in place or return
Some(value)to skip the tool. pub fn after_tool_callback(self, cb: AfterToolCallback) -> Self- Hook after every tool run; returning
Some(value)replaces the tool’s result. pub fn on_tool_error_callback(self, cb: OnToolErrorCallback) -> Self- Recovery hook for failed tool calls; returning
Some(value)recovers the call (otherwise the model sees{"error": ...}). pub fn build(self) -> Result<LlmAgent>- Validates (non-empty name, model present) and constructs the agent.
Conversation history: IncludeContents
| Variant | Effect |
|---|---|
IncludeContents::Default | Full session history is sent (assembled through event compaction when summaries exist), followed by the current user turn. |
IncludeContents::None | No history: only the system instruction and the current turn’s user content are sent. Useful for stateless steps in workflow pipelines. |
Instruction templating
Static instructions (set via instruction or global_instruction) pass through adk_rs::agents::inject_session_state, which replaces {placeholder} references before each model call. Dynamic providers and static_instruction bypass injection entirely.
| Syntax | Meaning |
|---|---|
{key} | Replaced with the session-state value for key. Errors if the key is missing. |
{key?} | Optional: replaced with the empty string when missing. |
{app:key} / {user:key} / {temp:key} | Prefixed state keys, matching the state scopes. |
{artifact.name} | Replaced with the named artifact’s content (text parts verbatim, other parts as JSON). Requires an artifact service; errors if the artifact is missing unless suffixed with ?. |
| anything else | Bodies that are not valid state names — {"a": 1}, {1, 2}, { } — are left untouched, so JSON snippets in instructions survive. |
let agent = LlmAgent::builder("support")
.model(model)
.instruction("Speak in {language}. Customer tier: {user:tier?}.")
.build()?;The LLM↔tool loop
- Build the request: system instruction (static prefix + resolved dynamic part), output schema, registered tools, then the history per
include_contentsand the current user content. When sub-agents are declared, the sub-agent roster and thetransfer_to_agenttool are appended too. - Check cancellation and the
RunConfig::max_llm_callsbudget, then call the model. UnderStreamingMode::Ssethis callsstream_generate_content, yields each content chunk as apartialevent, and aggregates the chunks into the final persisted event. The call is wrapped by thebefore_model/after_model/on_model_errorcallbacks. - Persist the model event into the session and inspect it. No function calls? It is the final response —
output_keystamps the text (or parsed JSON) into the event’sstate_delta, the event is yielded, and the stream ends. - Otherwise dispatch every call through the gate pipeline: tool confirmation first, then auth resolution, then the tool’s
run— wrapped by thebefore_tool/after_tool/on_tool_errorcallbacks. Tool errors become{"error": ...}values the model can react to. - Yield a tool-response event, then act on side effects: a
transfer_to_agenthands the rest of the invocation to the named sub-agent;escalateemits a marker event and stops; a long-running or consent-gated call pauses the invocation for a later resume. - Append the assistant turn and tool responses to the request contents and loop — up to
max_iterationstimes, after which a fail-safe event witherror_code: "MAX_ITERATIONS"is emitted.
Worked example
use std::sync::Arc;
use adk_rs::agents::{IncludeContents, LlmAgent};
use adk_rs::genai_types::Schema;
use adk_rs::providers::gemini::Gemini;
let model = Arc::new(Gemini::from_env("gemini-2.5-flash")?);
let extractor = LlmAgent::builder("extractor")
.description("Extracts structured facts from a support ticket.")
.model(model)
.instruction("Extract the customer's issue from the ticket. Locale: {user:locale?}")
.include_contents(IncludeContents::None) // stateless pipeline step
.max_iterations(4)
.output_schema(
Schema::object()
.property("summary", Schema::string())
.property("severity", Schema::string()),
)
.output_key("ticket_info") // parsed JSON lands in state
.build()?;A downstream agent in the same SequentialAgent can now reference {ticket_info} in its instruction, or a callback-free plugin can read it from the session state.
Related pages
- Tools overview — the
Tool/DynToolcontract andFunctionTool. - Structured output —
output_schemain depth. - Multi-agent systems —
sub_agent, transfer, andAgentTool. - Context caching — why
static_instructionexists.