agentik_sdk/tools/
conversation.rs1use std::sync::Arc;
7use crate::client::Anthropic;
8use crate::types::{Message, ToolChoice, ToolResult, MessageCreateBuilder};
9use super::{ToolRegistry, ToolExecutor, ToolExecutionConfig, ToolOperationResult, ToolError};
10
11pub struct ToolConversation {
16 client: Arc<Anthropic>,
18
19 registry: Arc<ToolRegistry>,
21
22 executor: ToolExecutor,
24
25 config: ConversationConfig,
27}
28
29#[derive(Debug, Clone)]
31pub struct ConversationConfig {
32 pub max_turns: usize,
34
35 pub model: String,
37
38 pub max_tokens: u32,
40
41 pub tool_choice: Option<ToolChoice>,
43
44 pub auto_execute_tools: bool,
46
47 pub execution_config: ToolExecutionConfig,
49}
50
51impl Default for ConversationConfig {
52 fn default() -> Self {
53 Self {
54 max_turns: 10,
55 model: "claude-3-5-sonnet-latest".to_string(),
56 max_tokens: 1024,
57 tool_choice: Some(ToolChoice::Auto),
58 auto_execute_tools: true,
59 execution_config: ToolExecutionConfig::default(),
60 }
61 }
62}
63
64impl ToolConversation {
65 pub fn new(client: Arc<Anthropic>, registry: Arc<ToolRegistry>) -> Self {
67 let executor = ToolExecutor::new(registry.clone());
68 Self {
69 client,
70 registry: registry.clone(),
71 executor,
72 config: ConversationConfig::default(),
73 }
74 }
75
76 pub fn with_config(
78 client: Arc<Anthropic>,
79 registry: Arc<ToolRegistry>,
80 config: ConversationConfig,
81 ) -> Self {
82 let executor = ToolExecutor::with_config(registry.clone(), config.execution_config.clone());
83 Self {
84 client,
85 registry: registry.clone(),
86 executor,
87 config,
88 }
89 }
90
91 pub async fn start(&self, user_message: impl Into<String>) -> ToolOperationResult<Message> {
96 let tools = self.registry.get_tool_definitions();
97
98 let mut builder = MessageCreateBuilder::new(&self.config.model, self.config.max_tokens)
99 .user(user_message.into());
100
101 if !tools.is_empty() {
103 builder = builder.tools(tools);
104
105 if let Some(ref tool_choice) = self.config.tool_choice {
106 builder = builder.tool_choice(tool_choice.clone());
107 }
108 }
109
110 let message = self.client.messages()
111 .create(builder.build())
112 .await
113 .map_err(|e| ToolError::ExecutionFailed { source: e.into() })?;
114
115 Ok(message)
116 }
117
118 pub async fn continue_with_tools(&self, message: &Message) -> ToolOperationResult<Option<Message>> {
123 let tool_uses = self.executor.extract_tool_uses(message);
124
125 if tool_uses.is_empty() {
126 return Ok(None);
127 }
128
129 if !self.config.auto_execute_tools {
130 return Ok(None);
132 }
133
134 let tool_results = self.executor.execute_multiple(&tool_uses).await;
136
137 let mut results = Vec::new();
139 for (tool_use, result) in tool_uses.iter().zip(tool_results.iter()) {
140 match result {
141 Ok(tool_result) => results.push(tool_result.clone()),
142 Err(error) => {
143 results.push(ToolResult::error(
144 tool_use.id.clone(),
145 format!("Tool execution failed: {}", error),
146 ));
147 }
148 }
149 }
150
151 use crate::types::messages::{MessageContent, ContentBlockParam};
153
154 let tool_result_blocks: Vec<ContentBlockParam> = results.into_iter().map(|result| {
156 let content_string = match result.content {
158 crate::types::ToolResultContent::Text(text) => Some(text),
159 crate::types::ToolResultContent::Json(json) => Some(json.to_string()),
160 crate::types::ToolResultContent::Blocks(blocks) => {
161 let text_parts: Vec<String> = blocks.into_iter().map(|block| {
163 match block {
164 crate::types::ToolResultBlock::Text { text } => text,
165 crate::types::ToolResultBlock::Image { .. } => "[Image]".to_string(),
166 }
167 }).collect();
168 Some(text_parts.join("\n"))
169 }
170 };
171
172 ContentBlockParam::ToolResult {
173 tool_use_id: result.tool_use_id,
174 content: content_string,
175 is_error: result.is_error,
176 }
177 }).collect();
178
179 let mut builder = MessageCreateBuilder::new(&self.config.model, self.config.max_tokens)
180 .user(MessageContent::Blocks(tool_result_blocks));
181
182 let tools = self.registry.get_tool_definitions();
184 if !tools.is_empty() {
185 builder = builder.tools(tools);
186
187 if let Some(ref tool_choice) = self.config.tool_choice {
188 builder = builder.tool_choice(tool_choice.clone());
189 }
190 }
191
192 let next_message = self.client.messages()
193 .create(builder.build())
194 .await
195 .map_err(|e| ToolError::ExecutionFailed { source: e.into() })?;
196
197 Ok(Some(next_message))
198 }
199
200 pub async fn execute_until_complete(&self, initial_message: impl Into<String>) -> ToolOperationResult<Message> {
205 let mut current_message = self.start(initial_message).await?;
206 let mut turn_count = 1;
207
208 while turn_count < self.config.max_turns {
209 match self.continue_with_tools(¤t_message).await? {
210 Some(next_message) => {
211 current_message = next_message;
212 turn_count += 1;
213 }
214 None => {
215 break;
217 }
218 }
219 }
220
221 if turn_count >= self.config.max_turns {
222 return Err(ToolError::ExecutionFailed {
223 source: "Conversation exceeded maximum turns".to_string().into(),
224 });
225 }
226
227 Ok(current_message)
228 }
229
230 pub fn registry(&self) -> &Arc<ToolRegistry> {
232 &self.registry
233 }
234
235 pub fn executor(&self) -> &ToolExecutor {
237 &self.executor
238 }
239
240 pub fn config(&self) -> &ConversationConfig {
242 &self.config
243 }
244
245 pub fn set_config(&mut self, config: ConversationConfig) {
247 self.config = config;
248 self.executor.set_config(self.config.execution_config.clone());
249 }
250
251
252}
253
254pub struct ConversationConfigBuilder {
256 config: ConversationConfig,
257}
258
259impl ConversationConfigBuilder {
260 pub fn new() -> Self {
262 Self {
263 config: ConversationConfig::default(),
264 }
265 }
266
267 pub fn max_turns(mut self, max_turns: usize) -> Self {
269 self.config.max_turns = max_turns;
270 self
271 }
272
273 pub fn model(mut self, model: impl Into<String>) -> Self {
275 self.config.model = model.into();
276 self
277 }
278
279 pub fn max_tokens(mut self, max_tokens: u32) -> Self {
281 self.config.max_tokens = max_tokens;
282 self
283 }
284
285 pub fn tool_choice(mut self, tool_choice: ToolChoice) -> Self {
287 self.config.tool_choice = Some(tool_choice);
288 self
289 }
290
291 pub fn auto_execute_tools(mut self, enabled: bool) -> Self {
293 self.config.auto_execute_tools = enabled;
294 self
295 }
296
297 pub fn execution_config(mut self, config: ToolExecutionConfig) -> Self {
299 self.config.execution_config = config;
300 self
301 }
302
303 pub fn build(self) -> ConversationConfig {
305 self.config
306 }
307}
308
309impl Default for ConversationConfigBuilder {
310 fn default() -> Self {
311 Self::new()
312 }
313}
314
315#[cfg(test)]
316mod tests {
317 use super::*;
318
319 #[test]
320 fn test_conversation_config_builder() {
321 let config = ConversationConfigBuilder::new()
322 .max_turns(5)
323 .model("claude-3-5-sonnet-latest")
324 .max_tokens(2048)
325 .tool_choice(ToolChoice::Any)
326 .auto_execute_tools(false)
327 .build();
328
329 assert_eq!(config.max_turns, 5);
330 assert_eq!(config.model, "claude-3-5-sonnet-latest");
331 assert_eq!(config.max_tokens, 2048);
332 assert_eq!(config.tool_choice, Some(ToolChoice::Any));
333 assert!(!config.auto_execute_tools);
334 }
335
336 #[test]
337 fn test_default_config() {
338 let config = ConversationConfig::default();
339 assert_eq!(config.max_turns, 10);
340 assert_eq!(config.model, "claude-3-5-sonnet-latest");
341 assert_eq!(config.max_tokens, 1024);
342 assert_eq!(config.tool_choice, Some(ToolChoice::Auto));
343 assert!(config.auto_execute_tools);
344 }
345}