agentlib_reasoning/
autonomous.rs1use crate::utils::extract_text;
2use agentlib_core::{
3 ModelMessage, ModelRequest, ReasoningContext, ReasoningEngine, ReasoningStep, Role,
4 ToolDefinition,
5};
6use anyhow::{Result, anyhow};
7use async_trait::async_trait;
8
9pub struct AutonomousEngine {
10 max_steps: usize,
11 finish_tool_name: String,
12 finish_tool_description: String,
13}
14
15impl AutonomousEngine {
16 pub fn new(
17 max_steps: usize,
18 finish_tool_name: Option<String>,
19 finish_tool_description: Option<String>,
20 ) -> Self {
21 Self {
22 max_steps,
23 finish_tool_name: finish_tool_name.unwrap_or_else(|| "finish".to_string()),
24 finish_tool_description: finish_tool_description.unwrap_or_else(|| {
25 "Signal that you have completed the task. Call this with your final answer."
26 .to_string()
27 }),
28 }
29 }
30}
31
32impl Default for AutonomousEngine {
33 fn default() -> Self {
34 Self::new(30, None, None)
35 }
36}
37
38#[async_trait]
39impl ReasoningEngine for AutonomousEngine {
40 fn name(&self) -> &str {
41 "autonomous"
42 }
43
44 async fn execute(&self, r_ctx: &mut ReasoningContext<'_>) -> Result<String> {
45 let finish_schema = ToolDefinition {
46 name: self.finish_tool_name.clone(),
47 description: Some(self.finish_tool_description.clone()),
48 parameters: serde_json::json!({
49 "type": "object",
50 "properties": {
51 "result": {
52 "type": "string",
53 "description": "Your final answer or output."
54 }
55 },
56 "required": ["result"]
57 }),
58 };
59
60 let mut all_schemas = r_ctx.tools.list();
61 all_schemas.push(finish_schema);
62
63 let mut steps = 0;
64
65 while steps < self.max_steps {
66 let request = ModelRequest {
67 messages: r_ctx.ctx.messages.clone(),
68 tools: Some(all_schemas.clone()),
69 };
70
71 let response = r_ctx.model.complete(request).await?;
72
73 if let Some(usage) = &response.usage {
75 r_ctx.ctx.usage.prompt_tokens += usage.prompt_tokens;
76 r_ctx.ctx.usage.completion_tokens += usage.completion_tokens;
77 r_ctx.ctx.usage.total_tokens += usage.total_tokens;
78 }
79
80 r_ctx.ctx.messages.push(response.message.clone());
81
82 if !response.message.content.is_empty() {
83 r_ctx.push_step(ReasoningStep::Thought {
84 content: response.message.content.clone(),
85 engine: self.name().to_string(),
86 });
87 }
88
89 if let Some(tool_calls) = &response.message.tool_calls {
90 if tool_calls.is_empty() {
91 let answer = extract_text(&response.message.content);
92 r_ctx.push_step(ReasoningStep::Response {
93 content: answer.clone(),
94 engine: self.name().to_string(),
95 });
96 return Ok(answer);
97 }
98
99 let finish_call = tool_calls
100 .iter()
101 .find(|tc| tc.name == self.finish_tool_name);
102
103 if let Some(tc) = finish_call {
104 let result = tc
105 .arguments
106 .get("result")
107 .and_then(|v| v.as_str())
108 .map(|s| s.to_string())
109 .unwrap_or_else(|| response.message.content.clone());
110
111 r_ctx.ctx.messages.push(ModelMessage {
112 role: Role::Tool,
113 content: serde_json::json!({ "done": true }).to_string(),
114 tool_call_id: Some(tc.id.clone()),
115 tool_calls: None,
116 });
117
118 r_ctx.push_step(ReasoningStep::Response {
119 content: result.clone(),
120 engine: self.name().to_string(),
121 });
122 return Ok(result);
123 }
124
125 for tc in tool_calls {
126 r_ctx
127 .call_tool(&tc.name, tc.arguments.clone(), tc.id.clone())
128 .await?;
129 }
130 } else {
131 let answer = extract_text(&response.message.content);
132 r_ctx.push_step(ReasoningStep::Response {
133 content: answer.clone(),
134 engine: self.name().to_string(),
135 });
136 return Ok(answer);
137 }
138
139 steps += 1;
140 }
141
142 Err(anyhow!(
143 "[AutonomousEngine] Max steps ({}) reached. The agent did not call \"{}\".",
144 self.max_steps,
145 self.finish_tool_name
146 ))
147 }
148}