agentforge_parser/formats/
anthropic.rs1use agentforge_core::{
2 AgentFile, AgentForgeError, EvalHints, ModelConfig, ModelProvider, Result, ToolDefinition,
3};
4
5pub fn normalize(value: &serde_json::Value) -> Result<AgentFile> {
7 let name = value
8 .get("name")
9 .and_then(|v| v.as_str())
10 .unwrap_or("anthropic-agent")
11 .to_string();
12
13 let system_prompt = value
15 .get("system")
16 .or_else(|| value.get("system_prompt"))
17 .and_then(|v| v.as_str())
18 .ok_or_else(|| {
19 AgentForgeError::ValidationError("Anthropic: missing 'system' field".to_string())
20 })?
21 .to_string();
22
23 let model_id = value
24 .get("model")
25 .and_then(|v| v.as_str())
26 .ok_or_else(|| {
27 AgentForgeError::ValidationError("Anthropic: missing 'model' field".to_string())
28 })?
29 .to_string();
30
31 let max_tokens = value
32 .get("max_tokens")
33 .and_then(|t| t.as_u64())
34 .map(|v| v as u32);
35
36 let temperature = value.get("temperature").and_then(|t| t.as_f64());
37
38 let model = ModelConfig {
39 provider: ModelProvider::Anthropic,
40 model_id,
41 temperature,
42 max_tokens,
43 top_p: None,
44 };
45
46 let tools = parse_anthropic_tools(value)?;
47
48 Ok(AgentFile {
49 agentforge_schema_version: "1".to_string(),
50 name,
51 version: "1.0.0".to_string(),
52 model,
53 system_prompt,
54 tools,
55 output_schema: None,
56 constraints: vec![],
57 eval_hints: Some(EvalHints::default()),
58 metadata: None,
59 })
60}
61
62fn parse_anthropic_tools(value: &serde_json::Value) -> Result<Vec<ToolDefinition>> {
63 let tools_val = match value.get("tools") {
64 Some(t) => t,
65 None => return Ok(vec![]),
66 };
67
68 let arr = tools_val
69 .as_array()
70 .ok_or_else(|| AgentForgeError::ValidationError("tools must be an array".to_string()))?;
71
72 arr.iter()
73 .map(|t| {
74 let name = t
76 .get("name")
77 .and_then(|n| n.as_str())
78 .ok_or_else(|| {
79 AgentForgeError::ValidationError("Anthropic tool missing name".to_string())
80 })?
81 .to_string();
82
83 let description = t
84 .get("description")
85 .and_then(|d| d.as_str())
86 .unwrap_or("")
87 .to_string();
88
89 let parameters = t
91 .get("input_schema")
92 .cloned()
93 .unwrap_or_else(|| serde_json::json!({"type": "object", "properties": {}}));
94
95 Ok(ToolDefinition {
96 name,
97 description,
98 parameters,
99 })
100 })
101 .collect()
102}
103
104#[cfg(test)]
105mod tests {
106 use super::*;
107 use serde_json::json;
108
109 #[test]
110 fn normalizes_anthropic_agent() {
111 let v = json!({
112 "model": "claude-3-5-sonnet-20241022",
113 "system": "You are a helpful support agent.",
114 "max_tokens": 1024,
115 "tools": [
116 {
117 "name": "get_order",
118 "description": "Get order details",
119 "input_schema": {
120 "type": "object",
121 "properties": {"order_id": {"type": "string"}},
122 "required": ["order_id"]
123 }
124 }
125 ]
126 });
127 let agent = normalize(&v).unwrap();
128 assert_eq!(agent.model.provider, ModelProvider::Anthropic);
129 assert_eq!(agent.tools.len(), 1);
130 assert_eq!(agent.tools[0].name, "get_order");
131 assert_eq!(agent.model.max_tokens, Some(1024));
132 }
133
134 #[test]
135 fn accepts_system_prompt_key() {
136 let v = json!({
137 "model": "claude-3-opus-20240229",
138 "system_prompt": "You are helpful."
139 });
140 let agent = normalize(&v).unwrap();
141 assert_eq!(agent.system_prompt, "You are helpful.");
142 }
143}