1use std::sync::Arc;
9
10use serde::{Deserialize, Serialize};
11use tokio::task::JoinSet;
12
13use crate::error::Error;
14use crate::llm::LlmProvider;
15use crate::llm::types::TokenUsage;
16
17use super::AgentOutput;
18use super::AgentRunner;
19use super::dag::DagAgent;
20use super::debate::DebateAgent;
21use super::mixture::MixtureOfAgentsAgent;
22use super::voting::VotingAgent;
23
24type StopCondition = Box<dyn Fn(&str) -> bool + Send + Sync>;
26
27pub struct SequentialAgent<P: LlmProvider> {
35 agents: Vec<AgentRunner<P>>,
36}
37
38impl<P: LlmProvider> std::fmt::Debug for SequentialAgent<P> {
39 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
40 f.debug_struct("SequentialAgent")
41 .field("agent_count", &self.agents.len())
42 .finish()
43 }
44}
45
46pub struct SequentialAgentBuilder<P: LlmProvider> {
48 agents: Vec<AgentRunner<P>>,
49}
50
51impl<P: LlmProvider> SequentialAgent<P> {
52 pub fn builder() -> SequentialAgentBuilder<P> {
86 SequentialAgentBuilder { agents: Vec::new() }
87 }
88
89 pub async fn execute(&self, task: &str) -> Result<AgentOutput, Error> {
92 let mut current_input = task.to_string();
93 let mut total_usage = TokenUsage::default();
94 let mut total_tool_calls = 0usize;
95 let mut total_cost: Option<f64> = None;
96 let mut last_output: Option<AgentOutput> = None;
97
98 for agent in &self.agents {
99 let result = agent
100 .execute(¤t_input)
101 .await
102 .map_err(|e| e.accumulate_usage(total_usage))?;
103 result.accumulate_into(&mut total_usage, &mut total_tool_calls, &mut total_cost);
104 current_input = result.result.clone();
105 last_output = Some(result);
106 }
107
108 let mut output = last_output.expect("at least one agent");
110 output.tokens_used = total_usage;
111 output.tool_calls_made = total_tool_calls;
112 output.estimated_cost_usd = total_cost;
113 Ok(output)
114 }
115}
116
117impl<P: LlmProvider> SequentialAgentBuilder<P> {
118 pub fn agent(mut self, agent: AgentRunner<P>) -> Self {
120 self.agents.push(agent);
121 self
122 }
123
124 pub fn agents(mut self, agents: Vec<AgentRunner<P>>) -> Self {
126 self.agents.extend(agents);
127 self
128 }
129
130 pub fn build(self) -> Result<SequentialAgent<P>, Error> {
132 if self.agents.is_empty() {
133 return Err(Error::Config(
134 "SequentialAgent requires at least one agent".into(),
135 ));
136 }
137 Ok(SequentialAgent {
138 agents: self.agents,
139 })
140 }
141}
142
143pub struct ParallelAgent<P: LlmProvider + 'static> {
151 agents: Vec<Arc<AgentRunner<P>>>,
152}
153
154impl<P: LlmProvider + 'static> std::fmt::Debug for ParallelAgent<P> {
155 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
156 f.debug_struct("ParallelAgent")
157 .field("agent_count", &self.agents.len())
158 .finish()
159 }
160}
161
162pub struct ParallelAgentBuilder<P: LlmProvider + 'static> {
164 agents: Vec<Arc<AgentRunner<P>>>,
165}
166
167impl<P: LlmProvider + 'static> ParallelAgent<P> {
168 pub fn builder() -> ParallelAgentBuilder<P> {
170 ParallelAgentBuilder { agents: Vec::new() }
171 }
172
173 pub async fn execute(&self, task: &str) -> Result<AgentOutput, Error> {
175 let mut set = JoinSet::new();
176
177 for agent in &self.agents {
178 let agent = Arc::clone(agent);
179 let task = task.to_string();
180 set.spawn(async move {
181 let name = agent.name().to_string();
182 let result = agent.execute(&task).await;
183 (name, result)
184 });
185 }
186
187 let mut results: Vec<(String, AgentOutput)> = Vec::with_capacity(self.agents.len());
188 let mut total_usage = TokenUsage::default();
189 let mut total_tool_calls = 0usize;
190 let mut total_cost: Option<f64> = None;
191
192 while let Some(join_result) = set.join_next().await {
193 let (name, agent_result) = join_result.map_err(|e| {
194 Error::Agent(format!("parallel agent task panicked: {e}"))
198 .accumulate_usage(total_usage)
199 })?;
200 let output = agent_result.map_err(|e| e.accumulate_usage(total_usage))?;
201 output.accumulate_into(&mut total_usage, &mut total_tool_calls, &mut total_cost);
202 results.push((name, output));
203 }
204
205 results.sort_by(|a, b| a.0.cmp(&b.0));
207
208 let merged_text = results
209 .iter()
210 .map(|(name, output)| format!("## {name}\n{}", output.result))
211 .collect::<Vec<_>>()
212 .join("\n\n");
213
214 Ok(AgentOutput {
215 result: merged_text,
216 tool_calls_made: total_tool_calls,
217 tokens_used: total_usage,
218 structured: None,
219 estimated_cost_usd: total_cost,
220 model_name: None,
221 tool_call_results: Vec::new(),
223 goal_met: None,
224 })
225 }
226}
227
228impl<P: LlmProvider + 'static> ParallelAgentBuilder<P> {
229 pub fn agent(mut self, agent: AgentRunner<P>) -> Self {
231 self.agents.push(Arc::new(agent));
232 self
233 }
234
235 pub fn agents(mut self, agents: Vec<AgentRunner<P>>) -> Self {
237 self.agents.extend(agents.into_iter().map(Arc::new));
238 self
239 }
240
241 pub fn build(self) -> Result<ParallelAgent<P>, Error> {
243 if self.agents.is_empty() {
244 return Err(Error::Config(
245 "ParallelAgent requires at least one agent".into(),
246 ));
247 }
248 Ok(ParallelAgent {
249 agents: self.agents,
250 })
251 }
252}
253
254pub struct LoopAgent<P: LlmProvider> {
262 agent: AgentRunner<P>,
263 max_iterations: usize,
264 should_stop: StopCondition,
265}
266
267impl<P: LlmProvider> std::fmt::Debug for LoopAgent<P> {
268 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
269 f.debug_struct("LoopAgent")
270 .field("max_iterations", &self.max_iterations)
271 .finish()
272 }
273}
274
275pub struct LoopAgentBuilder<P: LlmProvider> {
277 agent: Option<AgentRunner<P>>,
278 max_iterations: Option<usize>,
279 should_stop: Option<StopCondition>,
280}
281
282impl<P: LlmProvider> LoopAgent<P> {
283 pub fn builder() -> LoopAgentBuilder<P> {
285 LoopAgentBuilder {
286 agent: None,
287 max_iterations: None,
288 should_stop: None,
289 }
290 }
291
292 pub async fn execute(&self, task: &str) -> Result<AgentOutput, Error> {
294 let mut current_input = task.to_string();
295 let mut total_usage = TokenUsage::default();
296 let mut total_tool_calls = 0usize;
297 let mut total_cost: Option<f64> = None;
298 let mut last_output: Option<AgentOutput> = None;
299
300 for _ in 0..self.max_iterations {
301 let result = self
302 .agent
303 .execute(¤t_input)
304 .await
305 .map_err(|e| e.accumulate_usage(total_usage))?;
306 result.accumulate_into(&mut total_usage, &mut total_tool_calls, &mut total_cost);
307 current_input = result.result.clone();
308 let should_stop = (self.should_stop)(&result.result);
309 last_output = Some(result);
310 if should_stop {
311 break;
312 }
313 }
314
315 let mut output = last_output.expect("at least one iteration");
317 output.tokens_used = total_usage;
318 output.tool_calls_made = total_tool_calls;
319 output.estimated_cost_usd = total_cost;
320 Ok(output)
321 }
322}
323
324impl<P: LlmProvider> LoopAgentBuilder<P> {
325 pub fn agent(mut self, agent: AgentRunner<P>) -> Self {
327 self.agent = Some(agent);
328 self
329 }
330
331 pub fn max_iterations(mut self, n: usize) -> Self {
333 self.max_iterations = Some(n);
334 self
335 }
336
337 pub fn should_stop(mut self, f: impl Fn(&str) -> bool + Send + Sync + 'static) -> Self {
340 self.should_stop = Some(Box::new(f));
341 self
342 }
343
344 pub fn build(self) -> Result<LoopAgent<P>, Error> {
346 let agent = self
347 .agent
348 .ok_or_else(|| Error::Config("LoopAgent requires an agent".into()))?;
349 let max_iterations = self
350 .max_iterations
351 .ok_or_else(|| Error::Config("LoopAgent requires max_iterations".into()))?;
352 if max_iterations == 0 {
353 return Err(Error::Config(
354 "LoopAgent max_iterations must be at least 1".into(),
355 ));
356 }
357 let should_stop = self
358 .should_stop
359 .ok_or_else(|| Error::Config("LoopAgent requires a should_stop condition".into()))?;
360 Ok(LoopAgent {
361 agent,
362 max_iterations,
363 should_stop,
364 })
365 }
366}
367
368#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
374#[serde(rename_all = "snake_case")]
375pub enum WorkflowType {
376 Sequential,
378 Parallel,
380 Loop,
382 Dag,
384 Debate,
386 Voting,
388 Mixture,
390}
391
392pub enum WorkflowRouter<P: LlmProvider + 'static> {
399 Sequential(Box<SequentialAgent<P>>),
401 Parallel(Box<ParallelAgent<P>>),
403 Loop(Box<LoopAgent<P>>),
405 Dag(Box<DagAgent<P>>),
407 Debate(Box<DebateAgent<P>>),
409 Voting(Box<VotingAgent<P>>),
411 Mixture(Box<MixtureOfAgentsAgent<P>>),
413}
414
415impl<P: LlmProvider + 'static> WorkflowRouter<P> {
416 pub async fn execute(&self, task: &str) -> Result<AgentOutput, Error> {
422 match self {
423 Self::Sequential(a) => a.execute(task).await,
424 Self::Parallel(a) => a.execute(task).await,
425 Self::Loop(a) => a.execute(task).await,
426 Self::Dag(a) => a.execute(task).await,
427 Self::Debate(a) => a.execute(task).await,
428 Self::Mixture(a) => a.execute(task).await,
429 Self::Voting(a) => a.execute(task).await.map(|vr| vr.output),
430 }
431 }
432
433 pub fn workflow_type(&self) -> WorkflowType {
435 match self {
436 Self::Sequential(_) => WorkflowType::Sequential,
437 Self::Parallel(_) => WorkflowType::Parallel,
438 Self::Loop(_) => WorkflowType::Loop,
439 Self::Dag(_) => WorkflowType::Dag,
440 Self::Debate(_) => WorkflowType::Debate,
441 Self::Voting(_) => WorkflowType::Voting,
442 Self::Mixture(_) => WorkflowType::Mixture,
443 }
444 }
445}
446
447impl<P: LlmProvider + 'static> std::fmt::Debug for WorkflowRouter<P> {
448 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
449 f.debug_tuple("WorkflowRouter")
450 .field(&self.workflow_type())
451 .finish()
452 }
453}
454
455impl<P: LlmProvider + 'static> From<SequentialAgent<P>> for WorkflowRouter<P> {
456 fn from(agent: SequentialAgent<P>) -> Self {
457 Self::Sequential(Box::new(agent))
458 }
459}
460
461impl<P: LlmProvider + 'static> From<ParallelAgent<P>> for WorkflowRouter<P> {
462 fn from(agent: ParallelAgent<P>) -> Self {
463 Self::Parallel(Box::new(agent))
464 }
465}
466
467impl<P: LlmProvider + 'static> From<LoopAgent<P>> for WorkflowRouter<P> {
468 fn from(agent: LoopAgent<P>) -> Self {
469 Self::Loop(Box::new(agent))
470 }
471}
472
473impl<P: LlmProvider + 'static> From<DagAgent<P>> for WorkflowRouter<P> {
474 fn from(agent: DagAgent<P>) -> Self {
475 Self::Dag(Box::new(agent))
476 }
477}
478
479impl<P: LlmProvider + 'static> From<DebateAgent<P>> for WorkflowRouter<P> {
480 fn from(agent: DebateAgent<P>) -> Self {
481 Self::Debate(Box::new(agent))
482 }
483}
484
485impl<P: LlmProvider + 'static> From<VotingAgent<P>> for WorkflowRouter<P> {
486 fn from(agent: VotingAgent<P>) -> Self {
487 Self::Voting(Box::new(agent))
488 }
489}
490
491impl<P: LlmProvider + 'static> From<MixtureOfAgentsAgent<P>> for WorkflowRouter<P> {
492 fn from(agent: MixtureOfAgentsAgent<P>) -> Self {
493 Self::Mixture(Box::new(agent))
494 }
495}
496
497#[cfg(test)]
502mod tests {
503 use super::*;
504 use crate::agent::test_helpers::{MockProvider, make_agent};
505
506 #[test]
511 fn sequential_builder_rejects_empty_agents() {
512 let result = SequentialAgent::<MockProvider>::builder().build();
513 assert!(result.is_err());
514 assert!(
515 result
516 .unwrap_err()
517 .to_string()
518 .contains("at least one agent")
519 );
520 }
521
522 #[test]
523 fn sequential_builder_accepts_one_agent() {
524 let provider = Arc::new(MockProvider::new(vec![MockProvider::text_response(
525 "done", 10, 5,
526 )]));
527 let agent = make_agent(provider, "a");
528 let seq = SequentialAgent::builder().agent(agent).build();
529 assert!(seq.is_ok());
530 }
531
532 #[tokio::test]
537 async fn sequential_single_agent() {
538 let provider = Arc::new(MockProvider::new(vec![MockProvider::text_response(
539 "hello world",
540 100,
541 50,
542 )]));
543 let agent = make_agent(provider, "step1");
544 let seq = SequentialAgent::builder().agent(agent).build().unwrap();
545
546 let output = seq.execute("start").await.unwrap();
547 assert_eq!(output.result, "hello world");
548 assert_eq!(output.tokens_used.input_tokens, 100);
549 assert_eq!(output.tokens_used.output_tokens, 50);
550 }
551
552 #[tokio::test]
553 async fn sequential_chains_output_as_input() {
554 let provider_a = Arc::new(MockProvider::new(vec![MockProvider::text_response(
557 "step-a-output",
558 100,
559 50,
560 )]));
561 let provider_b = Arc::new(MockProvider::new(vec![MockProvider::text_response(
562 "step-b-output",
563 200,
564 80,
565 )]));
566
567 let agent_a = make_agent(provider_a, "agent-a");
568 let agent_b = make_agent(provider_b, "agent-b");
569
570 let seq = SequentialAgent::builder()
571 .agent(agent_a)
572 .agent(agent_b)
573 .build()
574 .unwrap();
575
576 let output = seq.execute("initial task").await.unwrap();
577 assert_eq!(output.result, "step-b-output");
578 assert_eq!(output.tokens_used.input_tokens, 300);
580 assert_eq!(output.tokens_used.output_tokens, 130);
581 }
582
583 #[tokio::test]
584 async fn sequential_three_agents_accumulates_usage() {
585 let p1 = Arc::new(MockProvider::new(vec![MockProvider::text_response(
586 "out1", 10, 5,
587 )]));
588 let p2 = Arc::new(MockProvider::new(vec![MockProvider::text_response(
589 "out2", 20, 10,
590 )]));
591 let p3 = Arc::new(MockProvider::new(vec![MockProvider::text_response(
592 "out3", 30, 15,
593 )]));
594
595 let seq = SequentialAgent::builder()
596 .agent(make_agent(p1, "a"))
597 .agent(make_agent(p2, "b"))
598 .agent(make_agent(p3, "c"))
599 .build()
600 .unwrap();
601
602 let output = seq.execute("go").await.unwrap();
603 assert_eq!(output.result, "out3");
604 assert_eq!(output.tokens_used.input_tokens, 60);
605 assert_eq!(output.tokens_used.output_tokens, 30);
606 }
607
608 #[tokio::test]
609 async fn sequential_error_carries_partial_usage() {
610 let p1 = Arc::new(MockProvider::new(vec![MockProvider::text_response(
611 "ok", 100, 50,
612 )]));
613 let p2 = Arc::new(MockProvider::new(vec![]));
615
616 let seq = SequentialAgent::builder()
617 .agent(make_agent(p1, "good"))
618 .agent(make_agent(p2, "bad"))
619 .build()
620 .unwrap();
621
622 let err = seq.execute("task").await.unwrap_err();
623 let partial = err.partial_usage();
624 assert!(partial.input_tokens >= 100);
626 }
627
628 #[test]
633 fn parallel_builder_rejects_empty_agents() {
634 let result = ParallelAgent::<MockProvider>::builder().build();
635 assert!(result.is_err());
636 assert!(
637 result
638 .unwrap_err()
639 .to_string()
640 .contains("at least one agent")
641 );
642 }
643
644 #[test]
645 fn parallel_builder_accepts_one_agent() {
646 let provider = Arc::new(MockProvider::new(vec![MockProvider::text_response(
647 "ok", 10, 5,
648 )]));
649 let agent = make_agent(provider, "a");
650 let par = ParallelAgent::builder().agent(agent).build();
651 assert!(par.is_ok());
652 }
653
654 #[tokio::test]
659 async fn parallel_single_agent() {
660 let provider = Arc::new(MockProvider::new(vec![MockProvider::text_response(
661 "result-a", 100, 50,
662 )]));
663 let agent = make_agent(provider, "agent-a");
664 let par = ParallelAgent::builder().agent(agent).build().unwrap();
665
666 let output = par.execute("task").await.unwrap();
667 assert!(output.result.contains("agent-a"));
668 assert!(output.result.contains("result-a"));
669 assert_eq!(output.tokens_used.input_tokens, 100);
670 assert_eq!(output.tokens_used.output_tokens, 50);
671 }
672
673 #[tokio::test]
674 async fn parallel_multiple_agents_accumulates_usage() {
675 let p1 = Arc::new(MockProvider::new(vec![MockProvider::text_response(
676 "out-a", 100, 50,
677 )]));
678 let p2 = Arc::new(MockProvider::new(vec![MockProvider::text_response(
679 "out-b", 200, 80,
680 )]));
681
682 let par = ParallelAgent::builder()
683 .agent(make_agent(p1, "alpha"))
684 .agent(make_agent(p2, "beta"))
685 .build()
686 .unwrap();
687
688 let output = par.execute("same task").await.unwrap();
689 assert!(output.result.contains("out-a"));
691 assert!(output.result.contains("out-b"));
692 assert!(output.result.contains("## alpha"));
694 assert!(output.result.contains("## beta"));
695 assert_eq!(output.tokens_used.input_tokens, 300);
697 assert_eq!(output.tokens_used.output_tokens, 130);
698 }
699
700 #[tokio::test]
701 async fn parallel_output_sorted_by_name() {
702 let p1 = Arc::new(MockProvider::new(vec![MockProvider::text_response(
703 "out-z", 10, 5,
704 )]));
705 let p2 = Arc::new(MockProvider::new(vec![MockProvider::text_response(
706 "out-a", 10, 5,
707 )]));
708
709 let par = ParallelAgent::builder()
710 .agent(make_agent(p1, "zebra"))
711 .agent(make_agent(p2, "alpha"))
712 .build()
713 .unwrap();
714
715 let output = par.execute("task").await.unwrap();
716 let alpha_pos = output.result.find("## alpha").unwrap();
718 let zebra_pos = output.result.find("## zebra").unwrap();
719 assert!(alpha_pos < zebra_pos);
720 }
721
722 #[tokio::test]
723 async fn parallel_error_fails_fast() {
724 let p1 = Arc::new(MockProvider::new(vec![MockProvider::text_response(
725 "ok", 100, 50,
726 )]));
727 let p2 = Arc::new(MockProvider::new(vec![]));
729
730 let par = ParallelAgent::builder()
731 .agent(make_agent(p1, "good"))
732 .agent(make_agent(p2, "bad"))
733 .build()
734 .unwrap();
735
736 let result = par.execute("task").await;
737 assert!(result.is_err());
738 }
739
740 #[test]
745 fn loop_builder_rejects_missing_agent() {
746 let result = LoopAgent::<MockProvider>::builder()
747 .max_iterations(3)
748 .should_stop(|_| true)
749 .build();
750 assert!(result.is_err());
751 assert!(
752 result
753 .unwrap_err()
754 .to_string()
755 .contains("requires an agent")
756 );
757 }
758
759 #[test]
760 fn loop_builder_rejects_missing_max_iterations() {
761 let provider = Arc::new(MockProvider::new(vec![]));
762 let agent = make_agent(provider, "a");
763 let result = LoopAgent::builder()
764 .agent(agent)
765 .should_stop(|_| true)
766 .build();
767 assert!(result.is_err());
768 assert!(
769 result
770 .unwrap_err()
771 .to_string()
772 .contains("requires max_iterations")
773 );
774 }
775
776 #[test]
777 fn loop_builder_rejects_zero_max_iterations() {
778 let provider = Arc::new(MockProvider::new(vec![]));
779 let agent = make_agent(provider, "a");
780 let result = LoopAgent::builder()
781 .agent(agent)
782 .max_iterations(0)
783 .should_stop(|_| true)
784 .build();
785 assert!(result.is_err());
786 assert!(result.unwrap_err().to_string().contains("at least 1"));
787 }
788
789 #[test]
790 fn loop_builder_rejects_missing_should_stop() {
791 let provider = Arc::new(MockProvider::new(vec![]));
792 let agent = make_agent(provider, "a");
793 let result = LoopAgent::builder().agent(agent).max_iterations(3).build();
794 assert!(result.is_err());
795 assert!(
796 result
797 .unwrap_err()
798 .to_string()
799 .contains("requires a should_stop")
800 );
801 }
802
803 #[test]
804 fn loop_builder_accepts_valid_config() {
805 let provider = Arc::new(MockProvider::new(vec![MockProvider::text_response(
806 "x", 1, 1,
807 )]));
808 let agent = make_agent(provider, "a");
809 let result = LoopAgent::builder()
810 .agent(agent)
811 .max_iterations(5)
812 .should_stop(|_| true)
813 .build();
814 assert!(result.is_ok());
815 }
816
817 #[tokio::test]
822 async fn loop_stops_on_condition() {
823 let provider = Arc::new(MockProvider::new(vec![
825 MockProvider::text_response("working...", 10, 5),
826 MockProvider::text_response("DONE", 10, 5),
827 MockProvider::text_response("should not reach", 10, 5),
828 ]));
829 let agent = make_agent(provider, "worker");
830
831 let loop_agent = LoopAgent::builder()
832 .agent(agent)
833 .max_iterations(10)
834 .should_stop(|text| text.contains("DONE"))
835 .build()
836 .unwrap();
837
838 let output = loop_agent.execute("start").await.unwrap();
839 assert_eq!(output.result, "DONE");
840 assert_eq!(output.tokens_used.input_tokens, 20);
842 assert_eq!(output.tokens_used.output_tokens, 10);
843 }
844
845 #[tokio::test]
846 async fn loop_stops_at_max_iterations() {
847 let provider = Arc::new(MockProvider::new(vec![
848 MockProvider::text_response("iter1", 10, 5),
849 MockProvider::text_response("iter2", 10, 5),
850 MockProvider::text_response("iter3", 10, 5),
851 ]));
852 let agent = make_agent(provider, "worker");
853
854 let loop_agent = LoopAgent::builder()
855 .agent(agent)
856 .max_iterations(3)
857 .should_stop(|_| false) .build()
859 .unwrap();
860
861 let output = loop_agent.execute("start").await.unwrap();
862 assert_eq!(output.result, "iter3");
863 assert_eq!(output.tokens_used.input_tokens, 30);
864 assert_eq!(output.tokens_used.output_tokens, 15);
865 }
866
867 #[tokio::test]
868 async fn loop_single_iteration() {
869 let provider = Arc::new(MockProvider::new(vec![MockProvider::text_response(
870 "once", 50, 25,
871 )]));
872 let agent = make_agent(provider, "worker");
873
874 let loop_agent = LoopAgent::builder()
875 .agent(agent)
876 .max_iterations(1)
877 .should_stop(|_| false)
878 .build()
879 .unwrap();
880
881 let output = loop_agent.execute("go").await.unwrap();
882 assert_eq!(output.result, "once");
883 assert_eq!(output.tokens_used.input_tokens, 50);
884 }
885
886 #[tokio::test]
887 async fn loop_error_carries_partial_usage() {
888 let provider = Arc::new(MockProvider::new(vec![MockProvider::text_response(
890 "ok", 100, 50,
891 )]));
892 let agent = make_agent(provider, "worker");
893
894 let loop_agent = LoopAgent::builder()
895 .agent(agent)
896 .max_iterations(5)
897 .should_stop(|_| false) .build()
899 .unwrap();
900
901 let err = loop_agent.execute("go").await.unwrap_err();
902 let partial = err.partial_usage();
903 assert!(partial.input_tokens >= 100);
904 }
905
906 #[test]
911 fn sequential_builder_agents_method() {
912 let p1 = Arc::new(MockProvider::new(vec![MockProvider::text_response(
913 "a", 1, 1,
914 )]));
915 let p2 = Arc::new(MockProvider::new(vec![MockProvider::text_response(
916 "b", 1, 1,
917 )]));
918 let agents = vec![make_agent(p1, "x"), make_agent(p2, "y")];
919 let seq = SequentialAgent::builder().agents(agents).build();
920 assert!(seq.is_ok());
921 }
922
923 #[test]
928 fn parallel_builder_agents_method() {
929 let p1 = Arc::new(MockProvider::new(vec![MockProvider::text_response(
930 "a", 1, 1,
931 )]));
932 let p2 = Arc::new(MockProvider::new(vec![MockProvider::text_response(
933 "b", 1, 1,
934 )]));
935 let agents = vec![make_agent(p1, "x"), make_agent(p2, "y")];
936 let par = ParallelAgent::builder().agents(agents).build();
937 assert!(par.is_ok());
938 }
939
940 #[test]
945 fn agent_runner_name_getter() {
946 let provider = Arc::new(MockProvider::new(vec![]));
947 let agent = make_agent(provider, "test-agent");
948 assert_eq!(agent.name(), "test-agent");
949 }
950
951 #[test]
956 fn workflow_type_serde_roundtrip() {
957 for wt in [
958 WorkflowType::Sequential,
959 WorkflowType::Parallel,
960 WorkflowType::Loop,
961 WorkflowType::Dag,
962 WorkflowType::Debate,
963 WorkflowType::Voting,
964 WorkflowType::Mixture,
965 ] {
966 let json = serde_json::to_string(&wt).unwrap();
967 let back: WorkflowType = serde_json::from_str(&json).unwrap();
968 assert_eq!(wt, back);
969 }
970 }
971
972 #[test]
973 fn workflow_type_snake_case() {
974 assert_eq!(
975 serde_json::to_string(&WorkflowType::Sequential).unwrap(),
976 "\"sequential\""
977 );
978 assert_eq!(
979 serde_json::to_string(&WorkflowType::Parallel).unwrap(),
980 "\"parallel\""
981 );
982 assert_eq!(
983 serde_json::to_string(&WorkflowType::Loop).unwrap(),
984 "\"loop\""
985 );
986 assert_eq!(
987 serde_json::to_string(&WorkflowType::Dag).unwrap(),
988 "\"dag\""
989 );
990 assert_eq!(
991 serde_json::to_string(&WorkflowType::Debate).unwrap(),
992 "\"debate\""
993 );
994 assert_eq!(
995 serde_json::to_string(&WorkflowType::Voting).unwrap(),
996 "\"voting\""
997 );
998 assert_eq!(
999 serde_json::to_string(&WorkflowType::Mixture).unwrap(),
1000 "\"mixture\""
1001 );
1002 }
1003
1004 #[tokio::test]
1009 async fn router_sequential() {
1010 let provider = Arc::new(MockProvider::new(vec![MockProvider::text_response(
1011 "seq-out", 10, 5,
1012 )]));
1013 let seq = SequentialAgent::builder()
1014 .agent(make_agent(provider, "s"))
1015 .build()
1016 .unwrap();
1017 let router = WorkflowRouter::Sequential(Box::new(seq));
1018 assert_eq!(router.workflow_type(), WorkflowType::Sequential);
1019 let output = router.execute("task").await.unwrap();
1020 assert_eq!(output.result, "seq-out");
1021 }
1022
1023 #[tokio::test]
1024 async fn router_parallel() {
1025 let provider = Arc::new(MockProvider::new(vec![MockProvider::text_response(
1026 "par-out", 10, 5,
1027 )]));
1028 let par = ParallelAgent::builder()
1029 .agent(make_agent(provider, "p"))
1030 .build()
1031 .unwrap();
1032 let router = WorkflowRouter::Parallel(Box::new(par));
1033 assert_eq!(router.workflow_type(), WorkflowType::Parallel);
1034 let output = router.execute("task").await.unwrap();
1035 assert!(output.result.contains("par-out"));
1036 }
1037
1038 #[tokio::test]
1039 async fn router_loop() {
1040 let provider = Arc::new(MockProvider::new(vec![MockProvider::text_response(
1041 "loop-out", 10, 5,
1042 )]));
1043 let lp = LoopAgent::builder()
1044 .agent(make_agent(provider, "l"))
1045 .max_iterations(1)
1046 .should_stop(|_| true)
1047 .build()
1048 .unwrap();
1049 let router = WorkflowRouter::Loop(Box::new(lp));
1050 assert_eq!(router.workflow_type(), WorkflowType::Loop);
1051 let output = router.execute("task").await.unwrap();
1052 assert_eq!(output.result, "loop-out");
1053 }
1054
1055 #[tokio::test]
1056 async fn router_dag() {
1057 use crate::agent::dag::DagAgent;
1058
1059 let provider = Arc::new(MockProvider::new(vec![MockProvider::text_response(
1060 "dag-out", 10, 5,
1061 )]));
1062 let dag = DagAgent::builder()
1063 .node("A", make_agent(provider, "A"))
1064 .build()
1065 .unwrap();
1066 let router = WorkflowRouter::Dag(Box::new(dag));
1067 assert_eq!(router.workflow_type(), WorkflowType::Dag);
1068 let output = router.execute("task").await.unwrap();
1069 assert_eq!(output.result, "dag-out");
1070 }
1071
1072 #[test]
1073 fn router_from_sequential() {
1074 let provider = Arc::new(MockProvider::new(vec![MockProvider::text_response(
1075 "x", 1, 1,
1076 )]));
1077 let seq = SequentialAgent::builder()
1078 .agent(make_agent(provider, "s"))
1079 .build()
1080 .unwrap();
1081 let router: WorkflowRouter<MockProvider> = seq.into();
1082 assert_eq!(router.workflow_type(), WorkflowType::Sequential);
1083 }
1084
1085 #[test]
1086 fn router_from_dag() {
1087 use crate::agent::dag::DagAgent;
1088
1089 let provider = Arc::new(MockProvider::new(vec![MockProvider::text_response(
1090 "x", 1, 1,
1091 )]));
1092 let dag = DagAgent::builder()
1093 .node("A", make_agent(provider, "A"))
1094 .build()
1095 .unwrap();
1096 let router: WorkflowRouter<MockProvider> = dag.into();
1097 assert_eq!(router.workflow_type(), WorkflowType::Dag);
1098 }
1099
1100 #[test]
1101 fn router_debug() {
1102 let provider = Arc::new(MockProvider::new(vec![MockProvider::text_response(
1103 "x", 1, 1,
1104 )]));
1105 let seq = SequentialAgent::builder()
1106 .agent(make_agent(provider, "s"))
1107 .build()
1108 .unwrap();
1109 let router = WorkflowRouter::Sequential(Box::new(seq));
1110 let debug = format!("{router:?}");
1111 assert!(debug.contains("WorkflowRouter"));
1112 assert!(debug.contains("Sequential"));
1113 }
1114}