Skip to main content

lc_shared/
tool_types.rs

1// lc-shared/src/tool_types.rs
2//! Tool call types shared across crates.
3//!
4//! These types are needed by both `lc-schema` (Message uses ToolCall)
5//! and `lc-core` (tool definitions), so they live here to break the
6//! circular dependency between schema and core.
7
8use serde::{de::DeserializeOwned, Deserialize, Serialize};
9
10/// Tool call from LLM response
11///
12/// When an LLM decides to call a tool, it returns a ToolCall structure.
13#[derive(Debug, Clone, Serialize, Deserialize)]
14pub struct ToolCall {
15    /// Tool call ID (used to reference the call result)
16    pub id: String,
17
18    /// Tool type (always "function")
19    #[serde(rename = "type")]
20    pub tool_type: String,
21
22    /// Function call details
23    pub function: FunctionCall,
24}
25
26impl ToolCall {
27    /// Create a new tool call
28    pub fn new(
29        id: impl Into<String>,
30        name: impl Into<String>,
31        arguments: impl Into<String>,
32    ) -> Self {
33        Self {
34            id: id.into(),
35            tool_type: "function".to_string(),
36            function: FunctionCall {
37                name: name.into(),
38                arguments: arguments.into(),
39            },
40        }
41    }
42
43    /// Get the function name
44    pub fn name(&self) -> &str {
45        &self.function.name
46    }
47
48    /// Get the arguments as string
49    pub fn arguments(&self) -> &str {
50        &self.function.arguments
51    }
52
53    /// Parse arguments as JSON
54    pub fn parse_arguments<T: DeserializeOwned>(&self) -> Result<T, serde_json::Error> {
55        serde_json::from_str(&self.function.arguments)
56    }
57}
58
59/// Function call inside a ToolCall
60#[derive(Debug, Clone, Serialize, Deserialize)]
61pub struct FunctionCall {
62    /// Function name
63    pub name: String,
64
65    /// Arguments as JSON string
66    pub arguments: String,
67}
68
69/// Tool call result to send back to LLM
70#[derive(Debug, Clone, Serialize, Deserialize)]
71pub struct ToolCallResult {
72    /// Tool call ID (must match the ToolCall.id)
73    pub tool_call_id: String,
74
75    /// Role (always "tool")
76    pub role: String,
77
78    /// Tool output content
79    pub content: String,
80}
81
82impl ToolCallResult {
83    /// Create a new tool result
84    pub fn new(tool_call_id: impl Into<String>, content: impl Into<String>) -> Self {
85        Self {
86            tool_call_id: tool_call_id.into(),
87            role: "tool".to_string(),
88            content: content.into(),
89        }
90    }
91}
92
93#[cfg(test)]
94mod tests {
95    use super::*;
96    use serde_json::json;
97    use std::collections::HashMap;
98
99    #[test]
100    fn test_tool_call() {
101        let call = ToolCall::new(
102            "call_123",
103            "calculator",
104            json!({"expression": "2 + 3"}).to_string(),
105        );
106
107        assert_eq!(call.id, "call_123");
108        assert_eq!(call.name(), "calculator");
109
110        let args: HashMap<String, String> = call.parse_arguments().unwrap();
111        assert_eq!(args.get("expression").unwrap(), "2 + 3");
112    }
113
114    #[test]
115    fn test_tool_call_result() {
116        let result = ToolCallResult::new("call_123", "5");
117
118        assert_eq!(result.tool_call_id, "call_123");
119        assert_eq!(result.role, "tool");
120        assert_eq!(result.content, "5");
121    }
122}