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    /// Creates a new function definition with the given name.
79    pub fn new(name: impl Into<String>) -> Self {
80        Self {
81            name: name.into(),
82            description: None,
83            parameters: None,
84            strict: None,
85        }
86    }
87
88    /// Sets the function description (builder style).
89    pub fn with_description(mut self, description: impl Into<String>) -> Self {
90        self.description = Some(description.into());
91        self
92    }
93
94    /// Sets the parameters JSON schema (builder style).
95    pub fn with_parameters(mut self, parameters: serde_json::Value) -> Self {
96        self.parameters = Some(parameters);
97        self
98    }
99}
100
101// Re-export shared tool call types from lc-shared
102pub use lc_shared::tools::{FunctionCall, ToolCall, ToolCallBuilder, ToolCallResult};
103
104#[cfg(test)]
105mod tests {
106    use super::*;
107    use serde_json::json;
108
109    #[test]
110    fn test_tool_definition() {
111        let tool = ToolDefinition::new("calculator", "Calculate mathematical expressions")
112            .with_parameters(json!({
113                "type": "object",
114                "properties": {
115                    "expression": {
116                        "type": "string",
117                        "description": "Mathematical expression to calculate"
118                    }
119                },
120                "required": ["expression"]
121            }));
122
123        assert_eq!(tool.tool_type, "function");
124        assert_eq!(tool.function.name, "calculator");
125        assert!(tool.function.parameters.is_some());
126    }
127}
128
129#[cfg(test)]
130mod tool_call_tests {
131    use super::*;
132    use serde_json::json;
133    use std::collections::HashMap;
134
135    #[test]
136    fn test_tool_call() {
137        let call = ToolCall::builder("call_123")
138            .name("calculator")
139            .arguments(json!({"expression": "2 + 3"}).to_string())
140            .build();
141
142        assert_eq!(call.id, "call_123");
143        assert_eq!(call.name(), "calculator");
144
145        let args: HashMap<String, String> = call.parse_arguments().unwrap();
146        assert_eq!(args.get("expression").unwrap(), "2 + 3");
147    }
148
149    #[test]
150    fn test_tool_call_result() {
151        let result = ToolCallResult::new("call_123", "5");
152
153        assert_eq!(result.tool_call_id, "call_123");
154        assert_eq!(result.role, "tool");
155        assert_eq!(result.content, "5");
156    }
157}