1use abu_base::chat::{AssistantMessage, ChatMessage, ToolCall, ToolDefinition};
2use abu_provider::ChatProvide;
3use abu_tool::ToolCallResult;
4use crate::{middleware::MiddlewareFlow, AgentError};
5use crate::memory::Memory;
6use super::{Agent, AgentResult};
7
8use thiserrorctx::Context;
9use tracing::{debug, info, warn};
10
11pub enum AgentControl<T> {
12 Normal(T),
13 Break(String),
14}
15
16impl<T> AgentControl<T> {
17 pub fn unwrap(self) -> T {
18 match self {
19 Self::Break(_) => panic!("control is break!"),
20 Self::Normal(v) => v,
21 }
22 }
23}
24
25macro_rules! extract_agent_control {
26 ($control:ident) => {
27 match $control {
28 AgentControl::Break(s) => return Ok(AgentControl::Break(s)),
29 AgentControl::Normal(m) => m,
30 }
31 };
32}
33
34macro_rules! return_middleware_break {
35 ($flow:ident) => {
36 if let MiddlewareFlow::Break(s) = $flow {
37 return Ok(AgentControl::Break(s));
38 }
39 };
40}
41
42impl<C: ChatProvide, M: Memory> Agent<C, M> {
43 pub fn tool_list(&self) -> &[ToolDefinition] {
44 self.toolbox.tool_definitions()
45 }
46
47 pub fn system_prompt(&self) -> &str {
48 &self.context_builder.system_prompt
49 }
50
51 pub async fn run(&mut self, query: &str) -> AgentResult<AgentControl<String>> {
52 info!(query = %query, "🤖 Agent started with user query");
53 self.hooks.on_agent_start(query).await.context("agent start hook")?;
54
55 let memories = self.search_memory(query).await.context("search memory")?;
57 let mut messages = self.build_context(query, memories).await.context("build context")?;
58
59 let mut final_result = None;
61 for step in 0..self.config.max_iteration {
62 debug!(step, "🔄 Agent step begin");
63 self.hooks.on_step_start(step).await.with_context(|| format!("step {step} start"))?;
64
65 let control = self.llm_chat(step, &messages, true).await
67 .with_context(|| format!("chat with llm in step {}", step))?;
68 let mut ai_message = extract_agent_control!(control);
69 messages.push(ai_message.clone().into());
70
71 info!(step, role = "AI", content = ai_message.content, "🗣️ LLM Text Response");
72 if !ai_message.tool_calls.is_empty() {
73 info!(step, count = ai_message.tool_calls.len(), "🛠️ LLM requested tool calls");
74 } else {
75 final_result = Some(ai_message.content);
76 break;
77 }
78
79 for tool_call in ai_message.tool_calls.iter_mut() {
81 info!(step, tool = %tool_call.name, id = %tool_call.id, args = %tool_call.arguments, "🚀 Executing tool");
82 let control = self.execute_tool(step, tool_call).await.context("execute tool")?;
84 let result = extract_agent_control!(control);
85
86 let tool_content = if result.is_error {
87 info!(step, result = %result.context, "Tool execute failed!");
88 format!("Tool execute failed for {}", result.context)
89 } else {
90 info!(step, result = %result.context, "✅ Tool execution finished");
91 format!("Tool execute success with output {}", result.context)
92 };
93
94 messages.push(ChatMessage::tool(tool_content, tool_call.id.clone()));
96 }
97
98 debug!(step, "🔄 Agent step end");
99 self.hooks.on_step_end(step, &ai_message).await.with_context(|| format!("step {step} start"))?;
100 }
101
102 match final_result {
103 Some(final_result) => {
104 info!(output = final_result, "🛑 Finish task with final output");
105 self.add_memory(query, &final_result).await?;
106 Ok(AgentControl::Normal(final_result))
107 }
108 None => {
109 warn!("Agent reached max steps without termination");
110 self.hooks.on_agent_max_iteration().await.context("max iter hook")?;
111 Ok(AgentControl::Normal("Task do not finish yet".to_string()))
112 }
113 }
114 }
115
116 pub async fn chat(&mut self, query: &str) -> AgentResult<AgentControl<String>> {
117 info!(query = %query, "🤖 Agent started with user query");
118
119 let memories = self.search_memory(query).await.context("search memory")?;
121 let messages = self.build_context(query, memories).await.context("build context")?;
122
123 let control = self.llm_chat(0, &messages, false).await.context("chat with llm")?;
125 let ai_message = extract_agent_control!(control);
126
127 info!(role = "AI", content = ai_message.content, "🗣️ LLM Text Response");
128 self.add_memory(query, &ai_message.content).await?;
129
130 Ok(AgentControl::Normal(ai_message.content))
131 }
132
133 async fn execute_tool(&mut self, step: usize, tool_call: &mut ToolCall) -> AgentResult<AgentControl<ToolCallResult>> {
134 let flow = self.middlewares
135 .intercept_tool_call(tool_call)
136 .await.context("intercept tool call")?;
137 return_middleware_break!(flow);
138
139 self.hooks.on_tool_start(step, tool_call).await.context("tool start hook")?;
140
141 let mut result = self.toolbox.execute_tool(&tool_call.name, &tool_call.arguments).await.context("execute tool")?;
142
143 let flow = self.middlewares
144 .intercept_tool_result(&tool_call.name, &mut result)
145 .await.context("intercept tool result")?;
146 return_middleware_break!(flow);
147
148 self.hooks.on_tool_end(step, &result).await.context("tool end hook")?;
149
150 if result.is_error {
151 self.hooks.on_tool_error(step, &result.context).await.context("tool error hook")?;
152 }
153
154 Ok(AgentControl::Normal(result))
155 }
156
157 async fn add_memory(&mut self, user_input: &str, ai_response: &str) -> AgentResult<()> {
158 debug!(user_input = user_input, ai_response = ai_response, "add memory");
159
160 self.memory
161 .add(user_input, ai_response).await
162 .map_err(|e| AgentError::Memory(Box::new(e)))
163 .context("add new memory")?;
164
165 self.hooks.on_memory_add(user_input, ai_response).await.context("memory add hook")?;
166
167 Ok(())
168 }
169
170 async fn search_memory(&mut self, query: &str) -> AgentResult<Vec<ChatMessage>> {
171 debug!(query = query, "search memory");
172 let memories: Vec<ChatMessage> = self.memory.search(query).await
173 .map_err(|e| AgentError::Memory(Box::new(e)))
174 .context("search memory")?;
175 self.hooks.on_memory_search(query, &memories).await.context("memory search hook")?;
176
177 Ok(memories)
178 }
179
180 async fn build_context(&mut self, query: &str, memories: Vec<ChatMessage>) -> AgentResult<Vec<ChatMessage>> {
181 debug!("build context");
182 let messages: Vec<ChatMessage> = self.context_builder.build(query, memories);
183 self.hooks.on_context_build(query, &messages).await.context("context build hook")?;
184
185 Ok(messages)
186 }
187
188 async fn llm_chat(&mut self, step: usize, messages: &[ChatMessage], with_tool: bool) -> AgentResult<AgentControl<AssistantMessage>> {
189 self.hooks.on_llm_start(step, messages).await.context("llm start hook")?;
190
191 let mut ai_message = if with_tool {
192 self.llm.chat(messages).await?.message
193 } else {
194 self.llm.chat_no_tools(messages).await?.message
195 };
196
197 let flow = self.middlewares
198 .intercept_llm_out(&mut ai_message).await
199 .context("intercept llm out")?;
200 return_middleware_break!(flow);
201
202 self.hooks.on_llm_end(step, &ai_message).await.context("llm end hook")?;
203
204 Ok(AgentControl::Normal(ai_message))
205 }
206}