Expand description
Tool execution engine.
This module provides the runtime layer for executing tools that LLMs
invoke during generation. It builds on the foundational types from
chat (ToolCall, ToolResult) and
provider (ToolDefinition, JsonSchema).
§Architecture
ToolHandler — defines a single tool (schema + execute fn)
│
ToolRegistry — stores handlers by name, validates & dispatches
│
tool_loop() — automates generate → execute → feedback cycle
tool_loop_stream() — unified LoopEvent stream (LLM deltas + loop lifecycle)
ToolLoopHandle — caller-driven resumable variant (borrowed refs)
OwnedToolLoopHandle — caller-driven resumable variant (Arc, Send + 'static)§Example
use llm_stack::tool::{ToolRegistry, tool_fn, ToolLoopConfig, tool_loop};
use llm_stack::{ChatParams, ChatMessage, JsonSchema, ToolDefinition};
use serde_json::{json, Value};
let mut registry: ToolRegistry<()> = ToolRegistry::new();
registry.register(tool_fn(
ToolDefinition {
name: "add".into(),
description: "Add two numbers".into(),
parameters: JsonSchema::new(json!({
"type": "object",
"properties": {
"a": {"type": "number"},
"b": {"type": "number"}
},
"required": ["a", "b"]
})),
retry: None,
},
|input: Value| async move {
let a = input["a"].as_f64().unwrap_or(0.0);
let b = input["b"].as_f64().unwrap_or(0.0);
Ok(format!("{}", a + b))
},
));
let params = ChatParams {
messages: vec![ChatMessage::user("What is 2 + 3?")],
tools: Some(registry.definitions()),
..Default::default()
};
let result = tool_loop(provider, ®istry, params, ToolLoopConfig::default(), &()).await?;
println!("Final answer: {:?}", result.response.text());§Using Context
Tools often need access to shared state like database connections, user identity,
or configuration. Use tool_fn_with_ctx to create tools that receive context:
use llm_stack::tool::{tool_fn_with_ctx, ToolRegistry, ToolError, ToolOutput, tool_loop, ToolLoopConfig, LoopContext};
use llm_stack::{ToolDefinition, JsonSchema, ChatParams, ChatMessage};
use serde_json::{json, Value};
#[derive(Clone)]
struct AppState {
user_id: String,
api_key: String,
}
type AppCtx = LoopContext<AppState>;
let handler = tool_fn_with_ctx(
ToolDefinition {
name: "get_user_data".into(),
description: "Fetch data for the current user".into(),
parameters: JsonSchema::new(json!({"type": "object"})),
retry: None,
},
|_input: Value, ctx: &AppCtx| {
// Clone data from context before the async block
let user_id = ctx.state.user_id.clone();
async move {
Ok(ToolOutput::new(format!("Data for user: {}", user_id)))
}
},
);
let mut registry: ToolRegistry<AppCtx> = ToolRegistry::new();
registry.register(handler);
let ctx = LoopContext::new(AppState {
user_id: "user123".into(),
api_key: "secret".into(),
});
let params = ChatParams {
messages: vec![ChatMessage::user("Get my data")],
tools: Some(registry.definitions()),
..Default::default()
};
let result = tool_loop(provider, ®istry, params, ToolLoopConfig::default(), &ctx).await?;Note on lifetimes: The closure passed to tool_fn_with_ctx uses higher-ranked
trait bounds (for<'c> Fn(Value, &'c Ctx) -> Fut). This means the future returned
by your closure must be 'static — it cannot borrow from the context reference.
Clone any data you need from the context before creating the async block.
Structs§
- Completed
- Terminal: the loop completed successfully.
- FnTool
Handler - A tool handler backed by an async closure.
- Loop
Context - Generic context wrapper with built-in depth tracking.
- Loop
Detection Config - Configuration for detecting repeated tool calls (stuck agents).
- NoCtx
Tool Handler - A tool handler without context, created by
super::tool_fn. - Owned
Tool Loop Handle - Arc-owned resumable tool loop.
- Owned
Yielded - Handle returned when tools were executed on an
OwnedToolLoopHandle. - Stop
Context - Context provided to stop condition callbacks.
- Tool
Error - Error returned by tool execution.
- Tool
Loop Config - Configuration for
tool_loopandtool_loop_stream. - Tool
Loop Handle - Caller-driven resumable tool loop.
- Tool
Loop Result - The result of a completed tool loop.
- Tool
Output - Output returned by a tool handler.
- Tool
Registry - A registry of tool handlers, indexed by name.
- Turn
Error - Terminal: the loop errored.
- Yielded
- Handle returned when tools were executed. Borrows the
ToolLoopHandlemutably, so the caller cannot callnext_turn()again until this is consumed viaresume(),continue_loop(),inject_and_continue(), orstop().
Enums§
- Loop
Action - Action to take when a tool call loop is detected.
- Loop
Command - Commands sent by the caller to control the resumable loop.
- Loop
Event - Unified event emitted during tool loop execution.
- Owned
Turn Result - Result of one turn of the owned tool loop.
- Stop
Decision - Decision returned by a stop condition callback.
- Termination
Reason - Why a tool loop terminated.
- Tool
Approval - Result of approving a tool call before execution.
- Turn
Result - Result of one turn of the tool loop.
Traits§
- Loop
Depth - Trait for contexts that support automatic depth tracking in nested tool loops.
- Tool
Handler - A single tool that can be invoked by the LLM.
Functions§
- tool_fn
- Creates a
ToolHandler<()>from a closure (no context). - tool_
fn_ with_ ctx - Creates a
ToolHandler<Ctx>from a closure that receives context. - tool_
loop - Runs the LLM in a tool-calling loop until completion.
- tool_
loop_ stream - Streaming variant of
tool_loop.
Type Aliases§
- Loop
Stream - A pinned, boxed,
Sendstream ofLoopEventresults. - Stop
Condition Fn - Callback type for stop conditions.
- Tool
Approval Fn - Callback type for tool call approval.