Skip to main content

agentforge_parser/formats/
anthropic.rs

1use agentforge_core::{
2    AgentFile, AgentForgeError, EvalHints, ModelConfig, ModelProvider, Result, ToolDefinition,
3};
4
5/// Normalize Anthropic system-prompt + tool block JSON into `AgentFile`.
6pub 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    // Anthropic uses "system" for the system prompt
14    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            // Anthropic tool format: {"name": "...", "description": "...", "input_schema": {...}}
75            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            // Anthropic uses "input_schema" where OpenAI uses "parameters"
90            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}