agentforge_parser/formats/
crewai.rs1use agentforge_core::{
2 AgentFile, AgentForgeError, EvalHints, ModelConfig, ModelProvider, Result, ToolDefinition,
3};
4
5pub fn normalize(value: &serde_json::Value) -> Result<AgentFile> {
8 let agent = if value.get("agents").is_some() {
10 value
11 .get("agents")
12 .and_then(|a| a.as_array())
13 .and_then(|a| a.first())
14 .ok_or_else(|| {
15 AgentForgeError::ValidationError("CrewAI: 'agents' array is empty".to_string())
16 })?
17 } else {
18 value
19 };
20
21 let role = agent
22 .get("role")
23 .and_then(|r| r.as_str())
24 .ok_or_else(|| AgentForgeError::ValidationError("CrewAI: missing 'role'".to_string()))?;
25
26 let goal = agent.get("goal").and_then(|g| g.as_str()).unwrap_or("");
27
28 let backstory = agent
29 .get("backstory")
30 .and_then(|b| b.as_str())
31 .unwrap_or("");
32
33 let name = agent
34 .get("name")
35 .and_then(|n| n.as_str())
36 .unwrap_or(role)
37 .to_string();
38
39 let system_prompt = format!("You are {role}.\n\nGoal: {goal}\n\nBackstory: {backstory}");
41
42 let model_id = agent
43 .get("llm")
44 .and_then(|l| l.as_str())
45 .or_else(|| value.get("llm").and_then(|l| l.as_str()))
46 .unwrap_or("gpt-4o")
47 .to_string();
48
49 let model = ModelConfig {
50 provider: if model_id.contains("claude") {
51 ModelProvider::Anthropic
52 } else {
53 ModelProvider::Openai
54 },
55 model_id,
56 temperature: None,
57 max_tokens: None,
58 top_p: None,
59 };
60
61 let tools: Vec<ToolDefinition> = agent
63 .get("tools")
64 .and_then(|t| t.as_array())
65 .map(|arr| {
66 arr.iter()
67 .filter_map(|t| {
68 t.as_str().map(|name| ToolDefinition {
69 name: name.to_string(),
70 description: format!("Tool: {name}"),
71 parameters: serde_json::json!({"type": "object", "properties": {}}),
72 })
73 })
74 .collect()
75 })
76 .unwrap_or_default();
77
78 Ok(AgentFile {
79 agentforge_schema_version: "1".to_string(),
80 name,
81 version: "1.0.0".to_string(),
82 model,
83 system_prompt,
84 tools,
85 output_schema: None,
86 constraints: vec![],
87 eval_hints: Some(EvalHints::default()),
88 metadata: None,
89 })
90}
91
92#[cfg(test)]
93mod tests {
94 use super::*;
95 use serde_json::json;
96
97 #[test]
98 fn normalizes_crewai_agent() {
99 let v = json!({
100 "role": "Support Specialist",
101 "goal": "Help customers resolve issues",
102 "backstory": "You are an expert support agent with 10 years of experience.",
103 "llm": "gpt-4o",
104 "tools": ["search_tool", "order_lookup_tool"]
105 });
106 let agent = normalize(&v).unwrap();
107 assert!(agent.system_prompt.contains("Support Specialist"));
108 assert!(agent.system_prompt.contains("Help customers"));
109 assert_eq!(agent.tools.len(), 2);
110 }
111
112 #[test]
113 fn normalizes_crewai_agents_array() {
114 let v = json!({
115 "agents": [
116 {
117 "role": "Researcher",
118 "goal": "Research topics",
119 "backstory": "Expert researcher"
120 }
121 ]
122 });
123 let agent = normalize(&v).unwrap();
124 assert!(agent.system_prompt.contains("Researcher"));
125 }
126
127 #[test]
128 fn rejects_missing_role() {
129 let v = json!({"goal": "Help", "backstory": "Expert"});
130 assert!(normalize(&v).is_err());
131 }
132}