1#![allow(unreachable_code)]
38
39use crate::agents::agent::AgentGPT;
40#[cfg(feature = "net")]
41use crate::collaboration::Collaborator;
42#[allow(unused_imports)]
43use crate::common::utils::{
44 Capability, ClientType, Communication, ContextManager, GenerationOutput, Goal, Knowledge,
45 OutputKind, Persona, Planner, Reflection, Scope, Status, Task, TaskScheduler, Tool,
46 extract_array, extract_json_string, strip_code_blocks,
47};
48use crate::prompts::architect::{
49 ARCHITECT_DIAGRAM_PROMPT, ARCHITECT_ENDPOINTS_PROMPT, ARCHITECT_SCOPE_PROMPT,
50};
51use crate::traits::agent::Agent;
52use crate::traits::functions::{AsyncFunctions, Executor, Functions};
53use anyhow::{Result, anyhow};
54use async_trait::async_trait;
55use colored::*;
56use reqwest::Client as ReqClient;
59use std::borrow::Cow;
60use std::env::var;
61use std::process::Stdio;
62use std::time::Duration;
63use tokio::fs;
64use tokio::fs::OpenOptions;
65use tokio::io::AsyncWriteExt;
66use tokio::process::Command;
67use tracing::{debug, error, info, warn};
68
69#[cfg(feature = "mem")]
70use {
71 crate::common::memory::load_long_term_memory, crate::common::memory::long_term_memory_context,
72 crate::common::memory::save_long_term_memory,
73};
74
75#[cfg(feature = "oai")]
76use {openai_dive::v1::models::FlagshipModel, openai_dive::v1::resources::chat::*};
77
78#[cfg(feature = "cld")]
79use anthropic_ai_sdk::types::message::{
80 ContentBlock, CreateMessageParams, Message as AnthMessage, MessageClient,
81 RequiredMessageParams, Role,
82};
83
84#[cfg(feature = "gem")]
85use gems::{
86 chat::ChatBuilder,
87 imagen::ImageGenBuilder,
88 messages::{Content, Message},
89 models::Model,
90 stream::StreamBuilder,
91 traits::CTrait,
92};
93
94#[cfg(any(feature = "oai", feature = "gem", feature = "cld", feature = "xai"))]
95use crate::traits::functions::ReqResponse;
96
97#[cfg(feature = "xai")]
98use x_ai::{
99 chat_compl::{ChatCompletionsRequestBuilder, Message as XaiMessage},
100 traits::ChatCompletionsFetcher,
101};
102
103use auto_derive::Auto;
104
105#[derive(Debug, Clone, Default, Auto)]
107#[allow(dead_code)]
108pub struct ArchitectGPT {
109 workspace: Cow<'static, str>,
111 agent: AgentGPT,
113 client: ClientType,
115 req_client: ReqClient,
117}
118
119impl ArchitectGPT {
120 #[allow(unused)]
138 pub async fn new(objective: &'static str, position: &'static str) -> Self {
139 let workspace = var("AUTOGPT_WORKSPACE")
140 .unwrap_or("workspace/".to_string())
141 .to_owned()
142 + "architect";
143
144 if !fs::try_exists(&workspace).await.unwrap_or(false) {
145 match fs::create_dir_all(&workspace).await {
146 Ok(_) => debug!("Directory '{}' created successfully!", workspace),
147 Err(e) => error!("Error creating directory '{}': {}", workspace, e),
148 }
149 } else {
150 debug!("Directory '{}' already exists.", workspace);
151 }
152 let file_path = format!("{workspace}/diagram.py");
153 match OpenOptions::new()
154 .write(true)
155 .create_new(true)
156 .open(&file_path)
157 .await
158 {
159 Ok(mut file) => {
160 if let Err(e) = file.write_all(b"").await {
161 error!("Failed to write to 'diagram.py': {}", e);
162 } else {
163 debug!("File 'diagram.py' created successfully!");
164 }
165 }
166 Err(e) => {
167 if e.kind() == std::io::ErrorKind::AlreadyExists {
168 debug!("File 'diagram.py' already exists, skipping creation.");
169 } else {
170 error!("Error creating file 'diagram.py': {}", e);
171 }
172 }
173 }
174 let create_venv = Command::new("python3")
175 .arg("-m")
176 .arg("venv")
177 .arg(workspace.clone() + "/.venv")
178 .status();
179
180 if let Ok(status) = create_venv.await {
181 if status.success() {
182 let pip_path = format!("{}/bin/pip", workspace.clone() + "/.venv");
183 let pip_install = Command::new(pip_path)
184 .arg("install")
185 .arg("diagrams")
186 .stdout(Stdio::null())
187 .stderr(Stdio::null())
188 .spawn();
189
190 match pip_install {
191 Ok(_) => info!(
192 "{}",
193 format!("[*] {position:?}: Diagrams installed successfully!")
194 .bright_white()
195 .bold()
196 ),
197 Err(e) => error!(
198 "{}",
199 format!("[*] {position:?}: Error installing Diagrams: {e}!")
200 .bright_red()
201 .bold()
202 ),
203 }
204 }
205 }
206
207 let mut agent: AgentGPT = AgentGPT::new_borrowed(objective, position);
208 agent.id = agent.position().to_string().into();
209
210 let client = ClientType::from_env();
211
212 info!(
213 "{}",
214 format!("[*] {:?}: 🛠️ Getting ready!", agent.position())
215 .bright_white()
216 .bold()
217 );
218
219 let req_client: ReqClient = ReqClient::builder()
220 .timeout(Duration::from_secs(3))
221 .build()
222 .unwrap();
223
224 Self {
225 workspace: workspace.into(),
226 agent,
227 client,
228 req_client,
229 }
230 }
231
232 pub async fn build_request(
233 &mut self,
234 prompt: &str,
235 tasks: &mut Task,
236 output_type: OutputKind,
237 ) -> Result<GenerationOutput> {
238 #[cfg(feature = "mem")]
239 {
240 self.agent.memory = self.get_ltm().await?;
241 }
242
243 let current_code = fs::read_to_string(&format!("{}/diagram.py", self.workspace)).await?;
244 let request: String = format!(
245 "{}\n\nTask Description: {}\nPrevious Conversation: {:?}\nCurrent Architecture: {:?}",
246 prompt,
247 tasks.description,
248 self.agent.memory(),
249 current_code
250 );
251
252 self.agent.add_communication(Communication {
253 role: Cow::Borrowed("user"),
254 content: Cow::Owned(request.clone()),
255 });
256
257 #[cfg(feature = "mem")]
258 {
259 let _ = self
260 .save_ltm(Communication {
261 role: Cow::Borrowed("user"),
262 content: Cow::Owned(request.clone()),
263 })
264 .await;
265 }
266
267 #[allow(unused)]
268 let mut response_text = String::new();
269
270 #[cfg(any(feature = "oai", feature = "gem", feature = "cld", feature = "xai"))]
271 {
272 response_text = self.generate(&request).await?;
273 }
274
275 self.agent.add_communication(Communication {
276 role: Cow::Borrowed("assistant"),
277 content: Cow::Owned(response_text.clone()),
278 });
279
280 #[cfg(feature = "mem")]
281 {
282 let _ = self
283 .save_ltm(Communication {
284 role: Cow::Borrowed("assistant"),
285 content: Cow::Owned(response_text.clone()),
286 })
287 .await;
288 }
289
290 debug!("[*] {:?}: {:?}", self.agent.position(), self.agent);
291
292 match output_type {
293 OutputKind::Text => Ok(GenerationOutput::Text(strip_code_blocks(&response_text))),
294 OutputKind::UrlList => {
295 let urls: Vec<Cow<'static, str>> =
296 serde_json::from_str(&extract_array(&response_text).unwrap_or_default())?;
297 tasks.urls = Some(urls.clone());
298 self.agent.update(Status::InUnitTesting);
299 Ok(GenerationOutput::UrlList(urls))
300 }
301 OutputKind::Scope => {
302 let scope: Scope = serde_json::from_str(&strip_code_blocks(&response_text))?;
303 Ok(GenerationOutput::Scope(scope))
304 }
305 }
306 }
307
308 pub async fn get_scope(&mut self, tasks: &mut Task) -> Result<Scope> {
333 match self
334 .build_request(ARCHITECT_SCOPE_PROMPT, tasks, OutputKind::Scope)
335 .await?
336 {
337 GenerationOutput::Scope(scope) => {
338 self.agent.update(Status::Completed);
339 debug!("[*] {:?}: {:?}", self.agent.position(), self.agent);
340 Ok(scope)
341 }
342 _ => Err(anyhow::anyhow!("Expected scope from generation.")),
343 }
344 }
345
346 pub async fn get_urls(&mut self, tasks: &mut Task) -> Result<()> {
371 match self
372 .build_request(ARCHITECT_ENDPOINTS_PROMPT, tasks, OutputKind::UrlList)
373 .await?
374 {
375 GenerationOutput::UrlList(urls) => {
376 tasks.urls = Some(urls.clone());
377 self.agent.update(Status::InUnitTesting);
378 debug!("[*] {:?}: {:?}", self.agent.position(), self.agent);
379 Ok(())
380 }
381 _ => Err(anyhow::anyhow!("Expected URL list from generation.")),
382 }
383 }
384
385 pub async fn generate_diagram(&mut self, tasks: &mut Task) -> Result<String> {
408 match self
409 .build_request(ARCHITECT_DIAGRAM_PROMPT, tasks, OutputKind::Text)
410 .await?
411 {
412 GenerationOutput::Text(diagram) => Ok(diagram),
413 _ => Err(anyhow::anyhow!("Expected diagram text from generation.")),
414 }
415 }
416
417 pub fn think(&self) -> String {
418 let objective = self.agent.objective();
419 format!("What steps should I take to achieve '{objective}'")
420 }
421
422 pub fn plan(&mut self, context: String) -> Goal {
423 let mut goals = vec![
424 Goal {
425 description: "Identify system components".into(),
426 priority: 1,
427 completed: false,
428 },
429 Goal {
430 description: "Determine communication between components".into(),
431 priority: 2,
432 completed: false,
433 },
434 Goal {
435 description: "Generate diagram for architecture".into(),
436 priority: 3,
437 completed: false,
438 },
439 ];
440
441 goals.sort_by_key(|g| g.priority);
442
443 if let Some(planner) = self.agent.planner_mut() {
444 if planner.current_plan.is_empty() {
445 for g in goals.into_iter().rev() {
446 planner.current_plan.push(g);
447 }
448 }
449
450 if let Some(next_goal) = planner.current_plan.iter().rev().find(|g| !g.completed) {
451 return next_goal.clone();
452 }
453 }
454
455 Goal {
456 description: format!("Default task from context: {context}"),
457 priority: 1,
458 completed: false,
459 }
460 }
461
462 pub fn act(&mut self, goal: Goal) {
463 info!(
464 "{}",
465 format!(
466 "[*] {:?}: Executing goal: {}",
467 self.agent.position(),
468 goal.description
469 )
470 .cyan()
471 .bold()
472 );
473
474 for tool in self.agent.tools() {
475 if goal
476 .description
477 .to_lowercase()
478 .contains(&format!("{:?}", tool.name).to_lowercase())
479 {
480 let result = (tool.invoke)(&goal.description);
481 info!(
482 "{}",
483 format!(
484 "[*] {:?}: Tool [{:?}] executed: {}",
485 self.agent.position(),
486 tool.name,
487 result
488 )
489 .green()
490 );
491 self.agent.memory_mut().push(Communication {
492 role: goal.description.into(),
493 content: result.into(),
494 });
495 return;
496 }
497 }
498
499 warn!(
500 "{}",
501 format!(
502 "[*] {:?}: No tool matched for goal: {}",
503 self.agent.position(),
504 goal.description
505 )
506 .yellow()
507 );
508 }
509
510 pub fn reflect(&mut self) {
511 let entry = format!("Reflection on step toward '{}'", self.agent.objective());
512
513 self.agent.memory_mut().push(Communication {
514 role: Cow::Borrowed("assistant"),
515 content: entry.clone().into(),
516 });
517
518 self.agent
519 .context_mut()
520 .recent_messages
521 .push(Communication {
522 role: Cow::Borrowed("assistant"),
523 content: entry.into(),
524 });
525
526 if let Some(reflection) = self.agent.reflection() {
527 let feedback = (reflection.evaluation_fn)(&self.agent);
528 info!(
529 "{}",
530 format!(
531 "[*] {:?}: Self Reflection: {}",
532 self.agent.position(),
533 feedback
534 )
535 .blue()
536 );
537 }
538 }
539 pub fn has_completed_objective(&self) -> bool {
540 self.planner()
541 .map(|p| p.current_plan.iter().all(|g| g.completed))
542 .unwrap_or(false)
543 }
544 pub fn mark_goal_complete(&mut self, goal: Goal) {
545 if let Some(planner) = self.planner_mut() {
546 for g in &mut planner.current_plan {
547 if g.description == goal.description {
548 g.completed = true;
549 }
550 }
551 }
552 }
553
554 fn display_task_info(&self, tasks: &Task) {
555 for task in tasks.clone().description.clone().split("- ") {
556 if !task.trim().is_empty() {
557 info!("{} {}", "•".bright_white().bold(), task.trim().cyan());
558 }
559 }
560 }
561
562 async fn idle(&mut self, tasks: &mut Task) -> Result<()> {
563 debug!(
564 "{}",
565 format!("[*] {:?}: Idle", self.agent.position())
566 .bright_white()
567 .bold()
568 );
569
570 let scope = self.get_scope(tasks).await?;
571 if scope.external {
572 let _ = self.get_urls(tasks).await;
573 }
574
575 self.agent.update(Status::InUnitTesting);
576 Ok(())
577 }
578
579 async fn unit_test_and_generate(
580 &mut self,
581 path: &str,
582 tasks: &mut Task,
583 max_tries: u64,
584 ) -> Result<()> {
585 self.filter_urls(tasks).await;
586
587 let mut python_code = self.generate_diagram(tasks).await?;
588
589 self.write_code_to_file(path, &python_code).await?;
590
591 for attempt in 1..=max_tries {
592 let run_result = self.run_python_script().await;
593
594 match run_result {
595 Ok(_) => {
596 info!(
597 "{}",
598 format!(
599 "[*] {:?}: Diagram generated successfully!",
600 self.agent.position()
601 )
602 .green()
603 .bold()
604 );
605 self.agent.update(Status::Completed);
606 break;
607 }
608 Err(e) => {
609 error!(
610 "{}",
611 format!(
612 "[*] {:?}: Error generating diagram: {}",
613 self.agent.position(),
614 e
615 )
616 .bright_red()
617 .bold()
618 );
619
620 if attempt < max_tries {
621 info!(
622 "{}",
623 format!(
624 "[*] {:?}: Retrying... ({}/{})",
625 self.agent.position(),
626 attempt,
627 max_tries
628 )
629 .yellow()
630 .bold()
631 );
632
633 tasks.description =
634 (tasks.description.to_string() + " Got an error: " + &e.to_string())
635 .into();
636
637 python_code = self.search_solution_and_regenerate(tasks).await?;
638 self.write_code_to_file(path, &python_code).await?;
639 } else {
640 error!(
641 "{}",
642 format!(
643 "[*] {:?}: Maximum retries reached. Exiting...",
644 self.agent.position()
645 )
646 .bright_red()
647 .bold()
648 );
649 break;
650 }
651 }
652 }
653 }
654 Ok(())
655 }
656
657 async fn filter_urls(&self, tasks: &mut Task) {
658 let mut exclude = Vec::new();
659
660 let urls = tasks
661 .urls
662 .as_ref()
663 .map_or_else(Vec::new, |url| url.to_vec());
664
665 for url in &urls {
666 info!(
667 "{}",
668 format!(
669 "[*] {:?}: Testing URL Endpoint: {}",
670 self.agent.position(),
671 url
672 )
673 .bright_white()
674 .bold()
675 );
676
677 match self.req_client.get(url.to_string()).send().await {
678 Ok(response) if response.status() != reqwest::StatusCode::OK => {
679 exclude.push(url.clone());
680 }
681 Err(err) => {
682 let url = err
683 .url()
684 .map(|u| u.to_string())
685 .unwrap_or_else(|| "unknown URL".to_string());
686
687 error!(
688 "{}",
689 format!(
690 "[*] {:?}: Failed to request URL {}. Check connection.",
691 self.agent.position(),
692 url
693 )
694 .bright_red()
695 .bold()
696 );
697 }
698 _ => {}
699 }
700 }
701
702 if !exclude.is_empty() {
703 let filtered: Vec<Cow<'static, str>> = tasks
704 .urls
705 .as_ref()
706 .unwrap()
707 .iter()
708 .filter(|url| !exclude.contains(url))
709 .cloned()
710 .collect();
711 tasks.urls = Some(filtered);
712 }
713 }
714
715 async fn write_code_to_file(&self, path: &str, code: &str) -> Result<()> {
716 match fs::write(path, code).await {
717 Ok(_) => {
718 debug!(
719 "{}",
720 format!(
721 "[*] {:?}: Wrote diagram.py successfully!",
722 self.agent.position()
723 )
724 .green()
725 );
726 Ok(())
727 }
728 Err(e) => {
729 error!(
730 "{}",
731 format!(
732 "[*] {:?}: Failed writing diagram.py: {}",
733 self.agent.position(),
734 e
735 )
736 .bright_red()
737 );
738 Err(anyhow!("File write error"))
739 }
740 }
741 }
742
743 async fn run_python_script(&self) -> Result<()> {
744 let result = Command::new("sh")
745 .arg("-c")
746 .arg(format!("timeout {} .venv/bin/python ./diagram.py", 10))
747 .current_dir(self.workspace.to_string())
748 .stdout(Stdio::piped())
749 .stderr(Stdio::piped())
750 .output();
751
752 match result.await {
753 Ok(output) if output.status.success() => Ok(()),
754 Ok(output) => {
755 let stderr = String::from_utf8_lossy(&output.stderr);
756 Err(anyhow!("Python error: {}", stderr))
757 }
758 Err(e) => Err(anyhow!("Execution error: {}", e)),
759 }
760 }
761
762 async fn search_solution_and_regenerate(&mut self, tasks: &mut Task) -> Result<String> {
763 let query = format!("Python error handling for: {}", tasks.description);
768 info!(
769 "{}",
770 format!("[*] {:?}: Searching: {}", self.agent.position(), query)
771 .blue()
772 .bold()
773 );
774
775 let results = vec!["".to_string()];
779
780 for result in &results {
781 info!(
782 "{}",
783 format!(
784 "[*] {:?}: DuckDuckGo result: {}",
785 self.agent.position(),
786 result
788 )
789 .bright_cyan()
790 );
791 }
792
793 self.generate_diagram(tasks).await
794 }
795}
796
797#[async_trait]
811impl Executor for ArchitectGPT {
812 async fn execute<'a>(
831 &'a mut self,
832 tasks: &'a mut Task,
833 execute: bool,
834 browse: bool,
835 max_tries: u64,
836 ) -> Result<()> {
837 self.agent.update(Status::Idle);
838 info!(
839 "{}",
840 format!("[*] {:?}: Executing task:", self.agent.position())
841 .bright_white()
842 .bold()
843 );
844
845 self.display_task_info(tasks);
846 let path = &(self.workspace.to_string() + "/diagram.py");
847
848 while self.agent.status() != &Status::Completed {
849 let context = self.think();
850 let goal = self.plan(context);
851
852 if browse {
853 self.idle(tasks).await?;
855 } else {
856 self.agent.update(Status::InUnitTesting);
857 }
858
859 if execute {
860 self.unit_test_and_generate(path, tasks, max_tries).await?;
861 } else {
862 self.unit_test_and_generate(path, tasks, 1).await?;
864 }
865
866 self.mark_goal_complete(goal);
867
868 self.reflect();
869
870 if self.has_completed_objective() {
871 info!(
872 "{}",
873 format!("[*] {:?}: Objective complete!", self.agent.position())
874 .green()
875 .bold()
876 );
877 self.agent.update(Status::Completed);
878 break;
879 }
880 }
881
882 Ok(())
883 }
884}