swarm_engine_eval/
runtime.rs1use crate::scenario::{
21 AgentsConfig, AppConfigTemplate, EnvironmentConfig, EvalConditions, EvalScenario, LlmConfig,
22 ManagerConfig, ScenarioActions, ScenarioMeta, TaskConfig,
23};
24use std::path::PathBuf;
25
26#[derive(Debug, Clone)]
31pub struct RuntimeTaskSpec {
32 pub meta: ScenarioMeta,
34
35 pub task: TaskConfig,
37
38 pub llm: LlmConfig,
40
41 pub manager: ManagerConfig,
43
44 pub actions: ScenarioActions,
46
47 pub app_config: AppConfigTemplate,
49
50 pub environment: EnvironmentConfig,
52
53 pub agents: AgentsConfig,
55
56 pub conditions: EvalConditions,
58
59 pub working_dir: Option<PathBuf>,
61}
62
63impl RuntimeTaskSpec {
64 pub fn with_task(mut self, goal: impl Into<String>) -> Self {
66 self.task.goal = goal.into();
67 self
68 }
69
70 pub fn with_env_type(mut self, env_type: impl Into<String>) -> Self {
72 self.environment.env_type = env_type.into();
73 self
74 }
75
76 pub fn with_working_dir(mut self, dir: impl Into<PathBuf>) -> Self {
78 self.working_dir = Some(dir.into());
79 self
80 }
81
82 pub fn with_max_ticks(mut self, max_ticks: u64) -> Self {
84 self.app_config.max_ticks = max_ticks;
85 self
86 }
87
88 pub fn into_eval_scenario(mut self) -> EvalScenario {
95 if let Some(ref dir) = self.working_dir {
97 let params = self.environment.params.as_object_mut();
98 if let Some(obj) = params {
99 obj.insert(
100 "working_dir".to_string(),
101 serde_json::Value::String(dir.display().to_string()),
102 );
103 } else {
104 let mut obj = serde_json::Map::new();
106 obj.insert(
107 "working_dir".to_string(),
108 serde_json::Value::String(dir.display().to_string()),
109 );
110 self.environment.params = serde_json::Value::Object(obj);
111 }
112 }
113
114 EvalScenario {
115 meta: self.meta,
116 task: self.task,
117 llm: self.llm,
118 manager: self.manager,
119 batch_processor: Default::default(),
120 dependency_graph: None,
121 actions: self.actions,
122 app_config: self.app_config,
123 environment: self.environment,
124 agents: self.agents,
125 conditions: self.conditions,
126 milestones: vec![],
127 variants: vec![],
128 }
129 }
130
131 pub fn resolved_working_dir(&self) -> PathBuf {
133 self.working_dir
134 .clone()
135 .unwrap_or_else(|| std::env::current_dir().unwrap_or_else(|_| PathBuf::from(".")))
136 }
137}
138
139impl From<EvalScenario> for RuntimeTaskSpec {
140 fn from(scenario: EvalScenario) -> Self {
141 Self {
142 meta: scenario.meta,
143 task: scenario.task,
144 llm: scenario.llm,
145 manager: scenario.manager,
146 actions: scenario.actions,
147 app_config: scenario.app_config,
148 environment: scenario.environment,
149 agents: scenario.agents,
150 conditions: scenario.conditions,
151 working_dir: None,
152 }
153 }
154}
155
156#[cfg(test)]
157mod tests {
158 use super::*;
159
160 fn minimal_scenario() -> EvalScenario {
161 let toml_str = r#"
162 [meta]
163 name = "Test"
164 id = "test:minimal:v1"
165
166 [task]
167 goal = "Original goal"
168
169 [llm]
170 provider = "llama-server"
171 model = "test-model"
172
173 [app_config]
174 max_ticks = 100
175
176 [environment]
177 env_type = "default"
178
179 [agents]
180 [[agents.workers]]
181 id_pattern = "worker_{i}"
182 count = 1
183
184 [conditions]
185 on_timeout = "fail"
186 "#;
187 toml::from_str(toml_str).unwrap()
188 }
189
190 #[test]
191 fn test_from_eval_scenario() {
192 let scenario = minimal_scenario();
193 let spec = RuntimeTaskSpec::from(scenario);
194
195 assert_eq!(spec.task.goal, "Original goal");
196 assert_eq!(spec.environment.env_type, "default");
197 }
198
199 #[test]
200 fn test_with_task() {
201 let scenario = minimal_scenario();
202 let spec = RuntimeTaskSpec::from(scenario).with_task("New custom goal");
203
204 assert_eq!(spec.task.goal, "New custom goal");
205 }
206
207 #[test]
208 fn test_with_env_type() {
209 let scenario = minimal_scenario();
210 let spec = RuntimeTaskSpec::from(scenario).with_env_type("realworld");
211
212 assert_eq!(spec.environment.env_type, "realworld");
213 }
214
215 #[test]
216 fn test_into_eval_scenario() {
217 let scenario = minimal_scenario();
218 let spec = RuntimeTaskSpec::from(scenario)
219 .with_task("Modified goal")
220 .with_max_ticks(50);
221
222 let converted = spec.into_eval_scenario();
223
224 assert_eq!(converted.task.goal, "Modified goal");
225 assert_eq!(converted.app_config.max_ticks, 50);
226 assert!(converted.milestones.is_empty());
227 assert!(converted.variants.is_empty());
228 }
229
230 #[test]
231 fn test_working_dir_propagates_to_env_params() {
232 let scenario = minimal_scenario();
233 let spec = RuntimeTaskSpec::from(scenario).with_working_dir("/tmp/test_workspace");
234
235 let converted = spec.into_eval_scenario();
236
237 let params = converted.environment.params.as_object().unwrap();
239 assert_eq!(
240 params.get("working_dir").and_then(|v| v.as_str()),
241 Some("/tmp/test_workspace")
242 );
243 }
244
245 #[test]
246 fn test_working_dir_without_override() {
247 let scenario = minimal_scenario();
248 let spec = RuntimeTaskSpec::from(scenario);
249
250 assert!(spec.working_dir.is_none());
252
253 let converted = spec.into_eval_scenario();
254
255 let params = converted.environment.params.as_object();
257 assert!(params.is_none() || params.unwrap().get("working_dir").is_none());
258 }
259}