ras_agent/application/
run_agent.rs1use std::sync::Arc;
2
3use ras_errors::AppError;
4use ras_llm::{ChatMessage, LlmClient};
5use ras_types::{AgentId, StepId};
6
7use crate::application::run_step::RunStep;
8use crate::domain::agent_history::{AgentHistory, AgentHistoryList, StepRecord};
9use crate::domain::loop_detector::ActionLoopDetector;
10
11pub struct RunAgent {
12 pub agent: AgentId,
13 pub task: String,
14 pub max_steps: u32,
15 pub primary_llm: Arc<dyn LlmClient>,
16 pub fallback_llm: Option<Arc<dyn LlmClient>>,
17}
18
19impl RunAgent {
20 pub fn new(task: impl Into<String>, llm: Arc<dyn LlmClient>) -> Self {
21 Self {
22 agent: AgentId::new(),
23 task: task.into(),
24 max_steps: 25,
25 primary_llm: llm,
26 fallback_llm: None,
27 }
28 }
29
30 #[must_use]
31 pub fn with_fallback(mut self, fallback: Arc<dyn LlmClient>) -> Self {
32 self.fallback_llm = Some(fallback);
33 self
34 }
35
36 #[must_use]
37 pub fn with_max_steps(mut self, max_steps: u32) -> Self {
38 self.max_steps = max_steps;
39 self
40 }
41
42 pub async fn execute(self) -> Result<AgentHistoryList, AppError> {
43 let runner = RunStep::new(self.primary_llm.clone(), self.fallback_llm.clone());
44 let mut detector = ActionLoopDetector::new();
45 let mut history = AgentHistory {
46 agent: self.agent,
47 task: self.task.clone(),
48 steps: Vec::new(),
49 };
50 let mut last_step_ms: Option<u64> = None;
51 for n in 0..self.max_steps {
52 let prompt = build_prompt(&self.task, &history.steps);
53 let mut record = runner
54 .execute(StepId(n), self.max_steps, prompt, &mut detector)
55 .await?;
56 record.metadata.step_interval_ms = last_step_ms;
57 last_step_ms = Some(record.metadata.duration_ms);
58 let done = record.results.iter().any(|r| r.is_done) || record.output.action.is_empty();
59 history.steps.push(record);
60 if done {
61 break;
62 }
63 }
64 let mut list = AgentHistoryList::default();
65 list.push(history);
66 Ok(list)
67 }
68}
69
70fn build_prompt(task: &str, history: &[StepRecord]) -> Vec<ChatMessage> {
71 let mut out = vec![ChatMessage::system(format!(
72 "You are a browsing agent. Task: {task}\nReturn JSON with current_state and action[]."
73 ))];
74 for step in history.iter().rev().take(4).rev() {
75 if let Ok(j) = serde_json::to_string(&step.output) {
76 out.push(ChatMessage::assistant_text(j));
77 }
78 }
79 out.push(ChatMessage::user_text(
80 "Decide the next action(s). Return JSON {current_state, action: [...]}",
81 ));
82 out
83}