1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
//! 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`](crate::chat) ([`ToolCall`](crate::chat::ToolCall), [`ToolResult`](crate::chat::ToolResult)) and
//! [`provider`](crate::provider) ([`ToolDefinition`](crate::provider::ToolDefinition), [`JsonSchema`](crate::JsonSchema)).
//!
//! # Architecture
//!
//! ```text
//! 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() — streaming variant
//! ```
//!
//! # Example
//!
//! ```rust,no_run
//! use llm_stack_core::tool::{ToolRegistry, tool_fn, ToolLoopConfig, tool_loop};
//! use llm_stack_core::{ChatParams, ChatMessage, JsonSchema, ToolDefinition};
//! use serde_json::{json, Value};
//!
//! # async fn example(provider: &dyn llm_stack_core::DynProvider) -> Result<(), llm_stack_core::LlmError> {
//! 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());
//! # Ok(())
//! # }
//! ```
//!
//! # 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:
//!
//! ```rust,no_run
//! use llm_stack_core::tool::{tool_fn_with_ctx, ToolRegistry, ToolError, ToolOutput, tool_loop, ToolLoopConfig, LoopDepth};
//! use llm_stack_core::{ToolDefinition, JsonSchema, ChatParams, ChatMessage};
//! use serde_json::{json, Value};
//!
//! // Your application context - must implement Clone for LoopDepth
//! #[derive(Clone)]
//! struct AppContext {
//! user_id: String,
//! api_key: String,
//! depth: u32,
//! }
//!
//! // Implement LoopDepth for automatic depth tracking in nested loops
//! impl LoopDepth for AppContext {
//! fn loop_depth(&self) -> u32 { self.depth }
//! fn with_depth(&self, depth: u32) -> Self {
//! Self { depth, ..self.clone() }
//! }
//! }
//!
//! # async fn example(provider: &dyn llm_stack_core::DynProvider) -> Result<(), llm_stack_core::LlmError> {
//! // Create a tool that uses context
//! 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: &AppContext| {
//! // Clone data from context before the async block
//! let user_id = ctx.user_id.clone();
//! async move {
//! // Use the cloned data in the async block
//! Ok(ToolOutput::new(format!("Data for user: {}", user_id)))
//! }
//! },
//! );
//!
//! // Register with a typed registry
//! let mut registry: ToolRegistry<AppContext> = ToolRegistry::new();
//! registry.register(handler);
//!
//! // Create context and run
//! let ctx = AppContext {
//! user_id: "user123".into(),
//! api_key: "secret".into(),
//! depth: 0,
//! };
//!
//! 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?;
//! # Ok(())
//! # }
//! ```
//!
//! **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.
// Re-export all public types
pub use ;
pub use LoopDepth;
pub use ToolError;
pub use ;
pub use ;
pub use tool_loop_channel;
pub use tool_loop_stream;
pub use tool_loop;
pub use ToolOutput;
pub use ToolRegistry;