aether_evals/evals/
run_eval.rs1use super::report::EvalReport;
2use crate::agents::{Agent, AgentConfig, is_terminal};
3use crate::{EvalRunError, Workspace};
4use aether_core::events::AgentMessage;
5use tokio::sync::mpsc;
6
7#[tracing::instrument(skip(agent, prompt, workspace))]
8pub async fn run_eval<T: Agent>(
9 agent: &T,
10 prompt: impl Into<String>,
11 workspace: Workspace,
12) -> Result<EvalReport, EvalRunError> {
13 let prompt = prompt.into();
14 let messages = run_agent(agent, &workspace, &prompt).await?;
15 let (agent_diff, reference_diff) = workspace.capture_git_diffs();
16 Ok(EvalReport::new(prompt, workspace, messages, agent_diff, reference_diff))
17}
18
19async fn run_agent<T: Agent>(
20 agent: &T,
21 workspace: &Workspace,
22 task_prompt: &str,
23) -> Result<Vec<AgentMessage>, EvalRunError> {
24 let (tx, mut rx) = mpsc::channel(100);
25 let agent_task = agent.run(
26 AgentConfig { workspace_root: workspace.root_path(), relative_cwd: workspace.relative_cwd(), task_prompt },
27 tx,
28 );
29
30 let message_task = async {
31 let mut messages = Vec::new();
32 while let Some(message) = rx.recv().await {
33 let is_done = is_terminal(&message);
34 messages.push(message);
35 if is_done {
36 break;
37 }
38 }
39 messages
40 };
41
42 let (run_result, messages) = tokio::join!(agent_task, message_task);
43 run_result?;
44 Ok(messages)
45}
46
47#[cfg(test)]
48mod tests {
49 use super::*;
50 use crate::{FakeAgent, RunError, Workspace};
51 use std::sync::{Arc, Mutex};
52 use tokio::sync::mpsc::Sender;
53
54 #[tokio::test]
55 async fn run_eval_returns_report_with_prompt_workspace_and_messages() {
56 let report =
57 run_eval(&FakeAgent::with_tool_call("bash", "success"), "do the thing", Workspace::empty().unwrap())
58 .await
59 .unwrap();
60
61 assert_eq!(report.prompt(), "do the thing");
62 assert!(report.workspace().path().exists());
63 assert!(report.tool_called("bash"));
64 assert!(matches!(report.messages().last(), Some(AgentMessage::Done)));
65 }
66
67 #[tokio::test]
68 async fn run_eval_passes_raw_prompt_without_scaffolding() {
69 let agent = CapturingAgent::default();
70 run_eval(&agent, "do the thing", Workspace::empty().unwrap()).await.unwrap();
71
72 assert_eq!(agent.captured_task_prompt(), Some("do the thing".to_string()));
73 }
74
75 #[derive(Default)]
76 struct CapturingAgent {
77 task_prompt: Arc<Mutex<Option<String>>>,
78 }
79
80 impl CapturingAgent {
81 fn captured_task_prompt(&self) -> Option<String> {
82 self.task_prompt.lock().unwrap().clone()
83 }
84 }
85
86 impl Agent for CapturingAgent {
87 async fn run(&self, config: AgentConfig<'_>, tx: Sender<AgentMessage>) -> Result<(), RunError> {
88 *self.task_prompt.lock().unwrap() = Some(config.task_prompt.to_string());
89 tx.send(AgentMessage::Done).await.map_err(|e| RunError::ChannelSendFailed(e.to_string()))
90 }
91 }
92}