1use std::{collections::HashMap, pin::Pin, sync::Arc};
2
3use async_trait::async_trait;
4use futures::{Stream, StreamExt};
5use serde_json::json;
6use tokio::sync::Mutex;
7use tokio_util::sync::CancellationToken;
8
9use super::{
10 agent::{Agent, PlanEvent},
11 AgentError,
12};
13use crate::observability::traced;
14use crate::schemas::{LogTools, Message};
15use crate::{
16 chain::{chain_trait::Chain, ChainError},
17 language_models::{GenerateResult, TokenUsage},
18 prompt::{human_message_from_inputs, PromptArgs, DEFAULT_IMAGES_KEY},
19 schemas::{
20 agent::{AgentAction, AgentEvent},
21 memory::BaseMemory,
22 },
23 tools::Tool,
24};
25use tracing::Instrument;
26
27#[derive(Debug, Clone, PartialEq)]
29#[non_exhaustive]
30pub enum AgentStreamEvent {
31 Text(String),
34 ToolStart { tool: String, input: String },
36 ToolEnd { tool: String, output: String },
39 Usage(TokenUsage),
42 Finish { output: String },
44}
45
46pub type AgentEventStream<'a> =
48 Pin<Box<dyn Stream<Item = Result<AgentStreamEvent, ChainError>> + Send + 'a>>;
49
50const MAX_ITERATIONS_OUTPUT: &str = "Max iterations reached";
52const CANCELLED: &str = "agent run cancelled";
54
55pub struct AgentExecutor<A>
56where
57 A: Agent,
58{
59 agent: A,
60 max_iterations: Option<i32>,
61 break_if_error: bool,
62 pub memory: Option<Arc<Mutex<dyn BaseMemory>>>,
63 cancellation: Option<CancellationToken>,
64}
65
66impl<A> AgentExecutor<A>
67where
68 A: Agent,
69{
70 pub fn from_agent(agent: A) -> Self {
71 Self {
72 agent,
73 max_iterations: Some(10),
74 break_if_error: false,
75 memory: None,
76 cancellation: None,
77 }
78 }
79
80 pub fn with_max_iterations(mut self, max_iterations: i32) -> Self {
81 self.max_iterations = Some(max_iterations);
82 self
83 }
84
85 pub fn with_memory(mut self, memory: Arc<Mutex<dyn BaseMemory>>) -> Self {
86 self.memory = Some(memory);
87 self
88 }
89
90 pub fn with_break_if_error(mut self, break_if_error: bool) -> Self {
91 self.break_if_error = break_if_error;
92 self
93 }
94
95 pub fn with_cancellation(mut self, token: CancellationToken) -> Self {
103 self.cancellation = Some(token);
104 self
105 }
106
107 fn get_name_to_tools(&self) -> HashMap<String, Arc<dyn Tool>> {
108 let mut name_to_tool = HashMap::new();
109 for tool in self.agent.get_tools().iter() {
110 tracing::debug!("Loading Tool:{}", tool.name());
111 name_to_tool.insert(tool.name().trim().replace(" ", "_"), tool.clone());
112 }
113 name_to_tool
114 }
115}
116
117impl<A> AgentExecutor<A>
118where
119 A: Agent + Send + Sync,
120{
121 pub fn stream_events(&self, input_variables: PromptArgs) -> AgentEventStream<'_> {
144 let run = async_stream::stream! {
145 let (tx, mut rx) = tokio::sync::mpsc::channel(1);
148 let run = self.run(input_variables, Emitter(Some(tx)));
149 tokio::pin!(run);
150 let result = loop {
151 tokio::select! {
152 biased;
153 Some(event) = rx.recv() => yield Ok(event),
154 result = &mut run => break result,
155 }
156 };
157 while let Ok(event) = rx.try_recv() {
158 yield Ok(event);
159 }
160 match result {
161 Ok(run) => yield Ok(AgentStreamEvent::Finish { output: run.output }),
162 Err(e) => yield Err(e),
163 }
164 };
165 Box::pin(run)
166 }
167
168 async fn run(
170 &self,
171 input_variables: PromptArgs,
172 emitter: Emitter,
173 ) -> Result<RunOutput, ChainError> {
174 let span = tracing::info_span!(
175 "agent.run",
176 max_iterations = self.max_iterations,
177 steps = tracing::field::Empty,
178 total_tokens = tracing::field::Empty,
179 error = tracing::field::Empty,
180 );
181 let run = self.run_inner(input_variables, emitter);
182 let run = async {
183 match &self.cancellation {
184 Some(token) => token
185 .run_until_cancelled(run)
186 .await
187 .unwrap_or_else(|| Err(ChainError::AgentError(CANCELLED.into()))),
188 None => run.await,
189 }
190 };
191 traced(span, run, |span, run: &RunOutput| {
192 if let Some(tokens) = &run.tokens {
193 span.record("total_tokens", tokens.total_tokens);
194 }
195 })
196 .await
197 }
198
199 async fn run_inner(
200 &self,
201 input_variables: PromptArgs,
202 emitter: Emitter,
203 ) -> Result<RunOutput, ChainError> {
204 let mut input_variables = input_variables.clone();
205 let name_to_tools = self.get_name_to_tools();
206 let mut steps: Vec<(AgentAction, String)> = Vec::new();
207 let mut tokens: Option<TokenUsage> = None;
208 if let Some(memory) = &self.memory {
209 let messages = memory.lock().await.messages().await?;
210 input_variables.insert("chat_history".to_string(), json!(messages));
211 } else {
212 input_variables.insert("chat_history".to_string(), json!(Vec::<Message>::new()));
213 }
214
215 loop {
216 let plan_span = tracing::info_span!("agent.plan", step = steps.len());
217 let (agent_event, usage) = async {
218 if emitter.is_streaming() {
219 self.plan_streaming(&steps, input_variables.clone(), &emitter)
220 .await
221 } else {
222 self.agent
223 .plan_with_usage(&steps, input_variables.clone())
224 .await
225 }
226 }
227 .instrument(plan_span)
228 .await
229 .map_err(|e| ChainError::AgentError(format!("Error in agent planning: {}", e)))?;
230 if let Some(usage) = usage {
231 tokens.get_or_insert_with(TokenUsage::default).add(&usage);
232 }
233
234 match agent_event {
235 AgentEvent::Action(actions) => {
236 for action in actions {
237 tracing::debug!("Action: {:?}", action.tool_input);
238 let tool = name_to_tools
239 .get(&action.tool.trim().replace(" ", "_"))
240 .ok_or_else(|| {
241 AgentError::ToolError(format!("Tool {} not found", action.tool))
242 })
243 .map_err(|e| ChainError::AgentError(e.to_string()))?;
244
245 emitter
246 .emit(AgentStreamEvent::ToolStart {
247 tool: action.tool.clone(),
248 input: action.tool_input.clone(),
249 })
250 .await;
251 let tool_span = tracing::info_span!(
252 "tool.call",
253 tool = %action.tool,
254 error = tracing::field::Empty,
255 );
256 let observation =
257 match traced(tool_span, tool.call(&action.tool_input), |_, _| {}).await
258 {
259 Ok(result) => result,
260 Err(err) => {
261 tracing::info!(
262 "The tool return the following error: {}",
263 err.to_string()
264 );
265 if self.break_if_error {
266 return Err(ChainError::AgentError(
267 AgentError::ToolError(err.to_string()).to_string(),
268 ));
269 } else {
270 format!("The tool return the following error: {}", err)
271 }
272 }
273 };
274 emitter
275 .emit(AgentStreamEvent::ToolEnd {
276 tool: action.tool.clone(),
277 output: observation.clone(),
278 })
279 .await;
280
281 steps.push((action, observation));
282 }
283 }
284 AgentEvent::Finish(finish) => {
285 tracing::Span::current().record("steps", steps.len());
286 if let Some(memory) = &self.memory {
287 let mut memory = memory.lock().await;
288
289 memory
291 .add_message(human_message_from_inputs(
292 &input_variables,
293 "input",
294 DEFAULT_IMAGES_KEY,
295 )?)
296 .await?;
297
298 let mut tools_ai_message_seen: HashMap<String, ()> = HashMap::default();
299 for (action, observation) in steps {
300 let Ok(LogTools { tool_id, tools }) =
303 serde_json::from_str::<LogTools>(&action.log)
304 else {
305 continue;
306 };
307 let tools_value: serde_json::Value = serde_json::from_str(&tools)?;
308 if tools_ai_message_seen.insert(tools, ()).is_none() {
309 memory
310 .add_message(
311 Message::new_ai_message("").with_tool_calls(tools_value),
312 )
313 .await?;
314 }
315 memory
316 .add_message(Message::new_tool_message(observation, tool_id))
317 .await?;
318 }
319
320 memory.add_ai_message(&finish.output).await?;
321 }
322 return Ok(RunOutput {
323 output: finish.output,
324 tokens,
325 });
326 }
327 }
328
329 if let Some(max_iterations) = self.max_iterations {
330 if steps.len() >= max_iterations as usize {
331 tracing::Span::current().record("steps", steps.len());
332 tracing::warn!(max_iterations, "agent stopped: max iterations reached");
333 return Ok(RunOutput {
334 output: MAX_ITERATIONS_OUTPUT.to_string(),
335 tokens,
336 });
337 }
338 }
339 }
340 }
341
342 async fn plan_streaming(
344 &self,
345 steps: &[(AgentAction, String)],
346 inputs: PromptArgs,
347 emitter: &Emitter,
348 ) -> Result<(AgentEvent, Option<TokenUsage>), AgentError> {
349 let mut plan = self.agent.plan_stream(steps, inputs);
350 let mut tokens: Option<TokenUsage> = None;
351 while let Some(event) = plan.next().await {
352 match event? {
353 PlanEvent::Text(text) => emitter.emit(AgentStreamEvent::Text(text)).await,
354 PlanEvent::Usage(usage) => {
355 tokens.get_or_insert_with(TokenUsage::default).add(&usage);
356 emitter.emit(AgentStreamEvent::Usage(usage)).await;
357 }
358 PlanEvent::Done(event) => return Ok((event, tokens)),
359 }
360 }
361 Err(AgentError::OtherError(
362 "agent plan stream ended without a plan".into(),
363 ))
364 }
365}
366
367struct RunOutput {
370 output: String,
371 tokens: Option<TokenUsage>,
372}
373
374struct Emitter(Option<tokio::sync::mpsc::Sender<AgentStreamEvent>>);
376
377impl Emitter {
378 fn is_streaming(&self) -> bool {
379 self.0.is_some()
380 }
381
382 async fn emit(&self, event: AgentStreamEvent) {
383 if let Some(tx) = &self.0 {
384 let _ = tx.send(event).await;
386 }
387 }
388}
389
390#[async_trait]
391impl<A> Chain for AgentExecutor<A>
392where
393 A: Agent + Send + Sync,
394{
395 async fn call(&self, input_variables: PromptArgs) -> Result<GenerateResult, ChainError> {
398 let run = self.run(input_variables, Emitter(None)).await?;
399 Ok(GenerateResult {
400 generation: run.output,
401 tokens: run.tokens,
402 ..Default::default()
403 })
404 }
405
406 async fn invoke(&self, input_variables: PromptArgs) -> Result<String, ChainError> {
407 let result = self.call(input_variables).await?;
408 Ok(result.generation)
409 }
410}