1use crate::agent::{Agent, EndStrategy};
6use crate::context::{generate_run_id, RunContext, RunUsage, UsageLimits};
7use crate::errors::{AgentRunError, OutputParseError, OutputValidationError};
8use chrono::Utc;
9use serde_json::Value as JsonValue;
10use serdes_ai_core::messages::{RetryPromptPart, ToolCallArgs, ToolReturnPart, UserContent};
11use serdes_ai_core::{
12 FinishReason, ModelRequest, ModelRequestPart, ModelResponse, ModelResponsePart, ModelSettings,
13};
14use serdes_ai_models::ModelRequestParameters;
15use serdes_ai_tools::{ToolError, ToolReturn};
16use std::sync::Arc;
17use tokio_util::sync::CancellationToken;
18
19#[derive(Debug, Clone, Default)]
21pub enum CompressionStrategy {
22 #[default]
24 Truncate,
25 Summarize,
27}
28
29#[derive(Debug, Clone)]
31pub struct ContextCompression {
32 pub strategy: CompressionStrategy,
34 pub threshold: f64,
36 pub target_tokens: usize,
38}
39
40impl Default for ContextCompression {
41 fn default() -> Self {
42 Self {
43 strategy: CompressionStrategy::Truncate,
44 threshold: 0.75,
45 target_tokens: 30_000,
46 }
47 }
48}
49
50#[derive(Debug, Clone, Default)]
52pub struct RunOptions {
53 pub model_settings: Option<ModelSettings>,
55 pub message_history: Option<Vec<ModelRequest>>,
57 pub usage_limits: Option<crate::context::UsageLimits>,
59 pub metadata: Option<JsonValue>,
61 pub compression: Option<ContextCompression>,
63}
64
65impl RunOptions {
66 pub fn new() -> Self {
68 Self::default()
69 }
70
71 pub fn model_settings(mut self, settings: ModelSettings) -> Self {
73 self.model_settings = Some(settings);
74 self
75 }
76
77 pub fn message_history(mut self, history: Vec<ModelRequest>) -> Self {
79 self.message_history = Some(history);
80 self
81 }
82
83 pub fn metadata(mut self, metadata: JsonValue) -> Self {
85 self.metadata = Some(metadata);
86 self
87 }
88
89 pub fn with_compression(mut self, config: ContextCompression) -> Self {
91 self.compression = Some(config);
92 self
93 }
94}
95
96#[derive(Debug, Clone)]
98pub struct AgentRunResult<Output> {
99 pub output: Output,
101 pub messages: Vec<ModelRequest>,
103 pub responses: Vec<ModelResponse>,
105 pub usage: RunUsage,
107 pub run_id: String,
109 pub finish_reason: FinishReason,
111 pub metadata: Option<JsonValue>,
113}
114
115impl<Output> AgentRunResult<Output> {
116 pub fn output(&self) -> &Output {
118 &self.output
119 }
120
121 pub fn into_output(self) -> Output {
123 self.output
124 }
125}
126
127pub struct AgentRun<'a, Deps, Output> {
135 agent: &'a Agent<Deps, Output>,
136 #[allow(dead_code)]
137 deps: Arc<Deps>,
138 state: AgentRunState<Output>,
139 ctx: RunContext<Deps>,
140 run_usage_limits: Option<UsageLimits>,
141 cancel_token: Option<CancellationToken>,
143}
144
145struct AgentRunState<Output> {
146 messages: Vec<ModelRequest>,
147 responses: Vec<ModelResponse>,
148 usage: RunUsage,
149 run_id: String,
150 step: u32,
151 output_retries: u32,
152 final_output: Option<Output>,
153 finished: bool,
154 finish_reason: Option<FinishReason>,
155}
156
157fn canonicalize_tool_call_args_in_response(response: &mut ModelResponse) {
163 for part in &mut response.parts {
164 if let ModelResponsePart::ToolCall(tc) = part {
165 let repaired = tc.args.to_json();
166 tc.args = ToolCallArgs::Json(repaired);
167 }
168 }
169}
170
171#[derive(Debug, Clone, PartialEq, Eq)]
173pub enum StepResult {
174 Continue,
176 ToolsExecuted(usize),
178 OutputReady,
180 RetryingOutput,
182 Finished,
184}
185
186impl<'a, Deps, Output> AgentRun<'a, Deps, Output>
187where
188 Deps: Send + Sync + 'static,
189 Output: Send + Sync + 'static,
190{
191 pub async fn new(
193 agent: &'a Agent<Deps, Output>,
194 prompt: UserContent,
195 deps: Deps,
196 options: RunOptions,
197 ) -> Result<Self, AgentRunError> {
198 let run_id = generate_run_id();
199 let deps = Arc::new(deps);
200
201 let model_settings = options
202 .model_settings
203 .unwrap_or_else(|| agent.model_settings.clone());
204
205 let ctx = RunContext {
206 deps: deps.clone(),
207 run_id: run_id.clone(),
208 start_time: Utc::now(),
209 model_name: agent.model().name().to_string(),
210 model_settings: model_settings.clone(),
211 tool_name: None,
212 tool_call_id: None,
213 retry_count: 0,
214 metadata: options.metadata.clone(),
215 };
216
217 let mut messages = options.message_history.unwrap_or_default();
219
220 let system_prompt = agent.build_system_prompt(&ctx).await;
222 if !system_prompt.is_empty() {
223 let mut req = ModelRequest::new();
224 req.add_system_prompt(system_prompt);
225 messages.push(req);
226 }
227
228 let mut user_req = ModelRequest::new();
230 user_req.add_user_prompt(prompt);
231 messages.push(user_req);
232
233 Ok(Self {
234 agent,
235 deps,
236 state: AgentRunState {
237 messages,
238 responses: Vec::new(),
239 usage: RunUsage::new(),
240 run_id,
241 step: 0,
242 output_retries: 0,
243 final_output: None,
244 finished: false,
245 finish_reason: None,
246 },
247 ctx,
248 run_usage_limits: options.usage_limits,
249 cancel_token: None,
250 })
251 }
252
253 pub async fn new_with_cancel(
278 agent: &'a Agent<Deps, Output>,
279 prompt: UserContent,
280 deps: Deps,
281 options: RunOptions,
282 cancel_token: CancellationToken,
283 ) -> Result<Self, AgentRunError> {
284 let run_id = generate_run_id();
285 let deps = Arc::new(deps);
286
287 let model_settings = options
288 .model_settings
289 .unwrap_or_else(|| agent.model_settings.clone());
290
291 let ctx = RunContext {
292 deps: deps.clone(),
293 run_id: run_id.clone(),
294 start_time: Utc::now(),
295 model_name: agent.model().name().to_string(),
296 model_settings: model_settings.clone(),
297 tool_name: None,
298 tool_call_id: None,
299 retry_count: 0,
300 metadata: options.metadata.clone(),
301 };
302
303 let mut messages = options.message_history.unwrap_or_default();
305
306 let system_prompt = agent.build_system_prompt(&ctx).await;
308 if !system_prompt.is_empty() {
309 let mut req = ModelRequest::new();
310 req.add_system_prompt(system_prompt);
311 messages.push(req);
312 }
313
314 let mut user_req = ModelRequest::new();
316 user_req.add_user_prompt(prompt);
317 messages.push(user_req);
318
319 Ok(Self {
320 agent,
321 deps,
322 state: AgentRunState {
323 messages,
324 responses: Vec::new(),
325 usage: RunUsage::new(),
326 run_id,
327 step: 0,
328 output_retries: 0,
329 final_output: None,
330 finished: false,
331 finish_reason: None,
332 },
333 ctx,
334 run_usage_limits: options.usage_limits,
335 cancel_token: Some(cancel_token),
336 })
337 }
338
339 pub async fn run_to_completion(mut self) -> Result<AgentRunResult<Output>, AgentRunError> {
341 while !self.state.finished {
342 self.step().await?;
343 }
344 self.finalize()
345 }
346
347 pub async fn step(&mut self) -> Result<StepResult, AgentRunError> {
352 if self.state.finished {
353 return Ok(StepResult::Finished);
354 }
355
356 if let Some(ref token) = self.cancel_token {
358 if token.is_cancelled() {
359 return Err(AgentRunError::Cancelled);
360 }
361 }
362
363 self.state.step += 1;
364
365 if let Some(limits) = &self.agent.usage_limits {
367 limits.check(&self.state.usage)?;
368 limits.check_time(self.ctx.elapsed_seconds() as u64)?;
369 }
370
371 if let Some(limits) = &self.run_usage_limits {
372 limits.check(&self.state.usage)?;
373 limits.check_time(self.ctx.elapsed_seconds() as u64)?;
374 }
375
376 let tool_defs = self.agent.tool_definitions();
378
379 let params = ModelRequestParameters::new()
381 .with_tools_arc(tool_defs)
382 .with_allow_text(true);
383
384 let messages = self.process_history().await;
386
387 let mut response = self
389 .agent
390 .model()
391 .request(&messages, &self.ctx.model_settings, ¶ms)
392 .await?;
393
394 canonicalize_tool_call_args_in_response(&mut response);
396
397 if let Some(usage) = &response.usage {
399 self.state.usage.add_request(usage.clone());
400 }
401
402 if response.finish_reason.is_some() {
404 self.state.finish_reason = response.finish_reason;
405 }
406 self.state.responses.push(response.clone());
407
408 self.process_response(response).await
410 }
411
412 async fn process_history(&self) -> Vec<ModelRequest> {
413 let mut messages = self.state.messages.clone();
414
415 for processor in &self.agent.history_processors {
417 messages = processor.process(&self.ctx, messages).await;
418 }
419
420 messages
421 }
422
423 async fn process_response(
424 &mut self,
425 response: ModelResponse,
426 ) -> Result<StepResult, AgentRunError> {
427 let mut tool_calls = Vec::new();
428 let mut found_output = None;
429
430 for part in &response.parts {
431 match part {
432 ModelResponsePart::Text(text) => {
433 if !text.content.is_empty() {
434 match self.agent.output_schema.parse_text(&text.content) {
436 Ok(output) => found_output = Some(output),
437 Err(OutputParseError::NotFound) => {}
438 Err(_) => {} }
440 }
441 }
442 ModelResponsePart::ToolCall(tc) => {
443 if self.agent.is_output_tool(&tc.tool_name) {
445 let args = tc.args.to_json();
446 if let Ok(output) = self
447 .agent
448 .output_schema
449 .parse_tool_call(&tc.tool_name, &args)
450 {
451 found_output = Some(output);
452 continue;
453 }
454 }
455
456 tool_calls.push(tc.clone());
458 }
459 ModelResponsePart::Thinking(_) => {
460 }
462 ModelResponsePart::File(_) => {
463 }
465 ModelResponsePart::BuiltinToolCall(_) => {
466 }
468 }
469 }
470
471 if !tool_calls.is_empty() {
477 let count = tool_calls.len();
478 let returns = self.execute_tool_calls(tool_calls).await;
479 self.add_tool_returns(returns)?;
480 return Ok(StepResult::ToolsExecuted(count));
481 }
482
483 if let Some(output) = found_output {
485 match self.validate_output(output).await {
486 Ok(validated) => {
487 self.state.final_output = Some(validated);
488
489 if self.agent.end_strategy == EndStrategy::Early {
491 self.state.finished = true;
492 return Ok(StepResult::OutputReady);
493 }
494 }
495 Err(e) => {
496 self.state.output_retries += 1;
497 if self.state.output_retries > self.agent.max_output_retries {
498 return Err(AgentRunError::OutputValidationFailed(e));
499 }
500
501 self.add_retry_message(e)?;
503 return Ok(StepResult::RetryingOutput);
504 }
505 }
506 }
507
508 if response.finish_reason == Some(FinishReason::Stop) {
510 if self.state.final_output.is_some() {
511 self.state.finished = true;
512 return Ok(StepResult::Finished);
513 }
514
515 if let Some(text) = response.parts.iter().find_map(|p| match p {
517 ModelResponsePart::Text(t) if !t.content.is_empty() => Some(&t.content),
518 _ => None,
519 }) {
520 if let Ok(output) = self.agent.output_schema.parse_text(text) {
522 match self.validate_output(output).await {
523 Ok(validated) => {
524 self.state.final_output = Some(validated);
525 self.state.finished = true;
526 return Ok(StepResult::Finished);
527 }
528 Err(e) => {
529 return Err(AgentRunError::OutputValidationFailed(e));
530 }
531 }
532 }
533 }
534
535 return Err(AgentRunError::UnexpectedStop);
536 }
537
538 Ok(StepResult::Continue)
539 }
540
541 async fn execute_tool_calls(
542 &mut self,
543 calls: Vec<serdes_ai_core::messages::ToolCallPart>,
544 ) -> Vec<(String, Option<String>, Result<ToolReturn, ToolError>)> {
545 if self.agent.parallel_tool_calls {
546 self.execute_tools_parallel(calls).await
547 } else {
548 self.execute_tools_sequential(calls).await
549 }
550 }
551
552 async fn execute_tools_sequential(
556 &mut self,
557 calls: Vec<serdes_ai_core::messages::ToolCallPart>,
558 ) -> Vec<(String, Option<String>, Result<ToolReturn, ToolError>)> {
559 let mut returns = Vec::new();
560
561 for tc in calls {
562 if let Some(ref token) = self.cancel_token {
564 if token.is_cancelled() {
565 returns.push((
566 tc.tool_name.clone(),
567 tc.tool_call_id.clone(),
568 Err(ToolError::Cancelled),
569 ));
570 continue;
571 }
572 }
573
574 self.state.usage.record_tool_call();
575
576 let tool = match self.agent.find_tool(&tc.tool_name) {
577 Some(t) => t,
578 None => {
579 returns.push((
580 tc.tool_name.clone(),
581 tc.tool_call_id.clone(),
582 Err(ToolError::NotFound(tc.tool_name.clone())),
583 ));
584 continue;
585 }
586 };
587
588 let tool_ctx = self.ctx.for_tool(&tc.tool_name, tc.tool_call_id.clone());
590
591 let args = tc.args.to_json();
593 let mut retries = 0;
594 let result = loop {
595 match tool.executor.execute(args.clone(), &tool_ctx).await {
596 Ok(r) => break Ok(r),
597 Err(e) if e.is_retryable() && retries < tool.max_retries => {
598 retries += 1;
599 continue;
600 }
601 Err(e) => break Err(e),
602 }
603 };
604
605 returns.push((tc.tool_name.clone(), tc.tool_call_id.clone(), result));
606 }
607
608 returns
609 }
610
611 async fn execute_tools_parallel(
613 &mut self,
614 calls: Vec<serdes_ai_core::messages::ToolCallPart>,
615 ) -> Vec<(String, Option<String>, Result<ToolReturn, ToolError>)> {
616 use futures::future::join_all;
617
618 for _ in &calls {
620 self.state.usage.record_tool_call();
621 }
622
623 let futures: Vec<_> = calls
625 .into_iter()
626 .map(|tc| {
627 let tool_name = tc.tool_name.clone();
628 let tool_call_id = tc.tool_call_id.clone();
629 let args = tc.args.to_json();
630
631 let tool = self.agent.find_tool(&tc.tool_name).cloned();
633 let tool_ctx = self.ctx.for_tool(&tc.tool_name, tc.tool_call_id.clone());
634
635 async move {
636 let tool = match tool {
637 Some(t) => t,
638 None => {
639 return (
640 tool_name.clone(),
641 tool_call_id,
642 Err(ToolError::NotFound(tool_name)),
643 );
644 }
645 };
646
647 let max_retries = tool.max_retries;
649 let executor = tool.executor;
650 let mut retries = 0;
651
652 let result = loop {
653 match executor.execute(args.clone(), &tool_ctx).await {
654 Ok(r) => break Ok(r),
655 Err(e) if e.is_retryable() && retries < max_retries => {
656 retries += 1;
657 continue;
658 }
659 Err(e) => break Err(e),
660 }
661 };
662
663 (tool_name, tool_call_id, result)
664 }
665 })
666 .collect();
667
668 if let Some(max_concurrent) = self.agent.max_concurrent_tools {
670 self.execute_with_semaphore(futures, max_concurrent).await
671 } else {
672 join_all(futures).await
673 }
674 }
675
676 async fn execute_with_semaphore<F, T>(&self, futures: Vec<F>, max_concurrent: usize) -> Vec<T>
681 where
682 F: std::future::Future<Output = T> + Send,
683 T: Send,
684 {
685 use futures::future::join_all;
686 use std::sync::Arc;
687 use tokio::sync::Semaphore;
688
689 let semaphore = Arc::new(Semaphore::new(max_concurrent));
690
691 let wrapped_futures: Vec<_> = futures
692 .into_iter()
693 .map(|fut| {
694 let sem = Arc::clone(&semaphore);
695 async move {
696 let _permit = sem.acquire().await.expect("Semaphore closed unexpectedly");
698 fut.await
699 }
701 })
702 .collect();
703
704 join_all(wrapped_futures).await
706 }
707
708 fn add_tool_returns(
709 &mut self,
710 returns: Vec<(String, Option<String>, Result<ToolReturn, ToolError>)>,
711 ) -> Result<(), AgentRunError> {
712 if let Some(last_response) = self.state.responses.last() {
716 let mut response_req = ModelRequest::new();
717 response_req
718 .parts
719 .push(ModelRequestPart::ModelResponse(Box::new(
720 last_response.clone(),
721 )));
722 self.state.messages.push(response_req);
723 }
724
725 let mut req = ModelRequest::new();
726
727 for (tool_name, tool_call_id, result) in returns {
728 match result {
729 Ok(ret) => {
730 let mut part = ToolReturnPart::new(&tool_name, ret.content);
731 if let Some(id) = tool_call_id {
732 part = part.with_tool_call_id(id);
733 }
734 req.parts.push(ModelRequestPart::ToolReturn(part));
735 }
736 Err(e) => {
737 let mut part = RetryPromptPart::new(format!("Tool error: {}", e));
738 part = part.with_tool_name(&tool_name);
739 if let Some(id) = tool_call_id {
740 part = part.with_tool_call_id(id);
741 }
742 req.parts.push(ModelRequestPart::RetryPrompt(part));
743 }
744 }
745 }
746
747 if !req.parts.is_empty() {
748 self.state.messages.push(req);
749 }
750
751 Ok(())
752 }
753
754 fn add_retry_message(&mut self, error: OutputValidationError) -> Result<(), AgentRunError> {
755 let mut req = ModelRequest::new();
756 let part = RetryPromptPart::new(error.retry_message());
757 req.parts.push(ModelRequestPart::RetryPrompt(part));
758 self.state.messages.push(req);
759 Ok(())
760 }
761
762 async fn validate_output(&self, output: Output) -> Result<Output, OutputValidationError> {
763 let mut output = output;
764 for validator in &self.agent.output_validators {
765 output = validator.validate(output, &self.ctx).await?;
766 }
767 Ok(output)
768 }
769
770 fn finalize(self) -> Result<AgentRunResult<Output>, AgentRunError> {
771 let output = self.state.final_output.ok_or(AgentRunError::NoOutput)?;
772
773 Ok(AgentRunResult {
774 output,
775 messages: self.state.messages,
776 responses: self.state.responses,
777 usage: self.state.usage,
778 run_id: self.state.run_id,
779 finish_reason: self.state.finish_reason.unwrap_or(FinishReason::Stop),
780 metadata: self.ctx.metadata.clone(),
781 })
782 }
783
784 pub fn messages(&self) -> &[ModelRequest] {
786 &self.state.messages
787 }
788
789 pub fn usage(&self) -> &RunUsage {
791 &self.state.usage
792 }
793
794 pub fn run_id(&self) -> &str {
796 &self.state.run_id
797 }
798
799 pub fn is_finished(&self) -> bool {
801 self.state.finished
802 }
803
804 pub fn step_number(&self) -> u32 {
806 self.state.step
807 }
808
809 pub fn cancel(&self) {
818 if let Some(ref token) = self.cancel_token {
819 token.cancel();
820 }
821 }
822
823 pub fn is_cancelled(&self) -> bool {
828 self.cancel_token
829 .as_ref()
830 .map(|t| t.is_cancelled())
831 .unwrap_or(false)
832 }
833
834 pub fn cancellation_token(&self) -> Option<&CancellationToken> {
839 self.cancel_token.as_ref()
840 }
841}
842
843#[cfg(test)]
844mod tests {
845 use super::*;
846
847 #[test]
848 fn test_run_options_default() {
849 let options = RunOptions::default();
850 assert!(options.model_settings.is_none());
851 assert!(options.message_history.is_none());
852 }
853
854 #[test]
855 fn test_run_options_builder() {
856 let options = RunOptions::new()
857 .model_settings(ModelSettings::new().temperature(0.5))
858 .metadata(serde_json::json!({"key": "value"}));
859
860 assert!(options.model_settings.is_some());
861 assert!(options.metadata.is_some());
862 }
863
864 #[test]
865 fn test_step_result_eq() {
866 assert_eq!(StepResult::Continue, StepResult::Continue);
867 assert_eq!(StepResult::ToolsExecuted(2), StepResult::ToolsExecuted(2));
868 assert_ne!(StepResult::ToolsExecuted(1), StepResult::ToolsExecuted(2));
869 }
870
871 #[test]
872 fn test_canonicalize_tool_call_args_in_response_converts_string_args_to_json() {
873 let mut response = ModelResponse::new();
874 response.add_part(ModelResponsePart::ToolCall(
875 serdes_ai_core::messages::ToolCallPart::new(
876 "demo_tool",
877 ToolCallArgs::string("{foo: bar,}"),
878 )
879 .with_tool_call_id("call_1"),
880 ));
881
882 canonicalize_tool_call_args_in_response(&mut response);
883
884 match &response.parts[0] {
885 ModelResponsePart::ToolCall(tc) => {
886 assert!(matches!(tc.args, ToolCallArgs::Json(_)));
887 }
888 _ => panic!("expected tool call part"),
889 }
890 }
891}