spec_ai/spec_ai_core/agent/
function_calling.rs1use async_openai::types::chat::{ChatCompletionTool, FunctionObject};
7use serde_json::{Value, json};
8
9fn parameters_to_openai_schema(params: &Value) -> Value {
11 let properties = params
13 .get("properties")
14 .and_then(|p| p.as_object())
15 .cloned()
16 .unwrap_or_default();
17
18 let required: Vec<String> = params
19 .get("required")
20 .and_then(|r| r.as_array())
21 .map(|arr| {
22 arr.iter()
23 .filter_map(|v| v.as_str().map(String::from))
24 .collect()
25 })
26 .unwrap_or_default();
27
28 json!({
29 "type": "object",
30 "properties": properties,
31 "required": required
32 })
33}
34
35pub fn tool_to_openai_function(
37 name: &str,
38 description: &str,
39 parameters: &Value,
40) -> ChatCompletionTool {
41 let schema = parameters_to_openai_schema(parameters);
42
43 ChatCompletionTool {
44 function: FunctionObject {
45 name: name.to_string(),
46 description: Some(description.to_string()),
47 parameters: Some(schema),
48 strict: Some(false),
49 },
50 }
51}
52
53#[derive(Debug, Clone)]
55pub struct FunctionCall {
56 pub name: String,
58 pub arguments: Value,
60}
61
62pub fn parse_tool_call_from_message(
65 _tool_call_id: &str,
66 function_name: &str,
67 arguments_str: &str,
68) -> Option<(String, Value)> {
69 let args = serde_json::from_str(arguments_str).ok()?;
71 Some((function_name.to_string(), args))
72}
73
74#[cfg(test)]
75mod tests {
76 use super::*;
77
78 #[test]
79 fn test_tool_to_openai_function() {
80 let params = json!({
81 "type": "object",
82 "properties": {
83 "message": {
84 "type": "string",
85 "description": "The message to echo"
86 }
87 },
88 "required": ["message"]
89 });
90
91 let tool = tool_to_openai_function("echo", "Echo a message", ¶ms);
92
93 assert_eq!(tool.function.name, "echo");
94 assert_eq!(
95 tool.function.description,
96 Some("Echo a message".to_string())
97 );
98 assert!(tool.function.parameters.is_some());
99 }
100
101 #[test]
102 fn test_parse_tool_call_from_message() {
103 let result = parse_tool_call_from_message("call_123", "echo", r#"{"message": "hello"}"#);
104
105 assert!(result.is_some());
106 let (name, args) = result.unwrap();
107 assert_eq!(name, "echo");
108 assert_eq!(args["message"], "hello");
109 }
110
111 #[test]
112 fn test_parse_tool_call_invalid_json() {
113 let result = parse_tool_call_from_message("call_123", "echo", "invalid json");
114 assert!(result.is_none());
115 }
116}