Skip to main content

lc_core/tools/
tool_definition.rs

1// src/core/tools/tool_definition.rs
2//! Tool definition for function calling
3
4use schemars::{schema_for, JsonSchema};
5use serde::{Deserialize, Serialize};
6
7/// Tool definition for LLM function calling
8///
9/// This structure defines a tool that can be bound to an LLM
10/// and invoked during generation.
11#[derive(Debug, Clone, Serialize, Deserialize)]
12pub struct ToolDefinition {
13    /// Tool type (always "function" for now)
14    #[serde(rename = "type")]
15    pub tool_type: String,
16
17    /// Function definition
18    pub function: FunctionDefinition,
19}
20
21impl ToolDefinition {
22    /// Create a new tool definition
23    pub fn new(name: impl Into<String>, description: impl Into<String>) -> Self {
24        Self {
25            tool_type: "function".to_string(),
26            function: FunctionDefinition {
27                name: name.into(),
28                description: Some(description.into()),
29                parameters: None,
30                strict: None,
31            },
32        }
33    }
34
35    /// Create with JSON Schema parameters
36    pub fn with_parameters(mut self, parameters: serde_json::Value) -> Self {
37        self.function.parameters = Some(parameters);
38        self
39    }
40
41    /// Create from a type that implements JsonSchema
42    pub fn from_type<T: JsonSchema>(
43        name: impl Into<String>,
44        description: impl Into<String>,
45    ) -> Self {
46        let schema = schema_for!(T);
47        let parameters = serde_json::to_value(schema).unwrap_or(serde_json::Value::Null);
48        Self::new(name, description).with_parameters(parameters)
49    }
50
51    /// Enable strict mode (OpenAI specific)
52    pub fn with_strict(mut self, strict: bool) -> Self {
53        self.function.strict = Some(strict);
54        self
55    }
56}
57
58/// Function definition inside a tool
59#[derive(Debug, Clone, Serialize, Deserialize)]
60pub struct FunctionDefinition {
61    /// Function name
62    pub name: String,
63
64    /// Function description
65    #[serde(skip_serializing_if = "Option::is_none")]
66    pub description: Option<String>,
67
68    /// Parameters JSON Schema
69    #[serde(skip_serializing_if = "Option::is_none")]
70    pub parameters: Option<serde_json::Value>,
71
72    /// Strict mode (OpenAI specific)
73    #[serde(skip_serializing_if = "Option::is_none")]
74    pub strict: Option<bool>,
75}
76
77impl FunctionDefinition {
78    pub fn new(name: impl Into<String>) -> Self {
79        Self {
80            name: name.into(),
81            description: None,
82            parameters: None,
83            strict: None,
84        }
85    }
86
87    pub fn with_description(mut self, description: impl Into<String>) -> Self {
88        self.description = Some(description.into());
89        self
90    }
91
92    pub fn with_parameters(mut self, parameters: serde_json::Value) -> Self {
93        self.parameters = Some(parameters);
94        self
95    }
96}
97
98// Re-export shared tool call types from lc-shared
99pub use lc_shared::tools::{FunctionCall, ToolCall, ToolCallResult};
100
101#[cfg(test)]
102mod tests {
103    use super::*;
104    use serde_json::json;
105
106    #[test]
107    fn test_tool_definition() {
108        let tool = ToolDefinition::new("calculator", "Calculate mathematical expressions")
109            .with_parameters(json!({
110                "type": "object",
111                "properties": {
112                    "expression": {
113                        "type": "string",
114                        "description": "Mathematical expression to calculate"
115                    }
116                },
117                "required": ["expression"]
118            }));
119
120        assert_eq!(tool.tool_type, "function");
121        assert_eq!(tool.function.name, "calculator");
122        assert!(tool.function.parameters.is_some());
123    }
124}
125
126#[cfg(test)]
127mod tool_call_tests {
128    use super::*;
129    use serde_json::json;
130    use std::collections::HashMap;
131
132    #[test]
133    fn test_tool_call() {
134        let call = ToolCall::new(
135            "call_123",
136            "calculator",
137            json!({"expression": "2 + 3"}).to_string(),
138        );
139
140        assert_eq!(call.id, "call_123");
141        assert_eq!(call.name(), "calculator");
142
143        let args: HashMap<String, String> = call.parse_arguments().unwrap();
144        assert_eq!(args.get("expression").unwrap(), "2 + 3");
145    }
146
147    #[test]
148    fn test_tool_call_result() {
149        let result = ToolCallResult::new("call_123", "5");
150
151        assert_eq!(result.tool_call_id, "call_123");
152        assert_eq!(result.role, "tool");
153        assert_eq!(result.content, "5");
154    }
155}