Skip to main content

rune_chain_core/
function_call.rs

1use serde::{Deserialize, Serialize};
2
3/// A single tool/function call requested by the model in a generation response.
4///
5/// When an LLM decides to call a tool, it emits one or more `ToolCall` values
6/// in [`GenerateResult::tool_calls`](crate::GenerateResult::tool_calls). The
7/// caller is responsible for executing each tool and feeding the results back
8/// as [`Role::Tool`](crate::Role::Tool) messages.
9#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
10pub struct ToolCall {
11    /// Provider-issued ID correlating this call with its result message.
12    pub id: String,
13    /// Name of the function/tool to invoke (matches [`Tool::name`](crate::Tool::name)).
14    pub name: String,
15    /// JSON-encoded argument object the model wants passed to the tool.
16    pub arguments: String,
17}
18
19impl ToolCall {
20    /// Create a new `ToolCall`.
21    pub fn new(
22        id: impl Into<String>,
23        name: impl Into<String>,
24        arguments: impl Into<String>,
25    ) -> Self {
26        Self {
27            id: id.into(),
28            name: name.into(),
29            arguments: arguments.into(),
30        }
31    }
32
33    /// Parse [`arguments`](Self::arguments) as a JSON object and return the value of `key`.
34    ///
35    /// Returns `None` if the JSON is invalid or the key is absent.
36    pub fn arg(&self, key: &str) -> Option<serde_json::Value> {
37        serde_json::from_str::<serde_json::Value>(&self.arguments)
38            .ok()
39            .and_then(|v| v.get(key).cloned())
40    }
41
42    /// Parse [`arguments`](Self::arguments) as a JSON object and extract the
43    /// `"input"` key as a string — the standard single-arg tool convention.
44    pub fn input(&self) -> Option<String> {
45        self.arg("input").and_then(|v| match v {
46            serde_json::Value::String(s) => Some(s),
47            other => Some(other.to_string()),
48        })
49    }
50}
51
52/// JSON-Schema-based description of a tool the LLM may call.
53///
54/// Pass a slice of `FunctionDefinition`s to
55/// [`Llm::generate_with_tools`](crate::Llm::generate_with_tools) to enable
56/// native function calling instead of text-based ReAct parsing.
57///
58/// # Example
59///
60/// ```rust
61/// use rune_chain_core::FunctionDefinition;
62/// use serde_json::json;
63///
64/// let def = FunctionDefinition::new(
65///     "get_weather",
66///     "Return the current weather for a city.",
67///     json!({
68///         "type": "object",
69///         "properties": {
70///             "city": { "type": "string", "description": "City name" }
71///         },
72///         "required": ["city"]
73///     }),
74/// );
75/// ```
76#[derive(Debug, Clone, Serialize, Deserialize)]
77pub struct FunctionDefinition {
78    /// Unique tool name (no spaces; matches [`Tool::name`](crate::Tool::name)).
79    pub name: String,
80    /// One-sentence description the LLM uses to decide when to call this tool.
81    pub description: String,
82    /// JSON Schema object describing the tool's parameter object.
83    pub parameters: serde_json::Value,
84}
85
86impl FunctionDefinition {
87    /// Create a new function definition.
88    pub fn new(
89        name: impl Into<String>,
90        description: impl Into<String>,
91        parameters: serde_json::Value,
92    ) -> Self {
93        Self {
94            name: name.into(),
95            description: description.into(),
96            parameters,
97        }
98    }
99
100    /// Build a definition with the standard single-string `"input"` parameter.
101    ///
102    /// Use this as a quick wrapper for tools that accept a plain string.
103    ///
104    /// # Example
105    ///
106    /// ```rust
107    /// use rune_chain_core::FunctionDefinition;
108    ///
109    /// let def = FunctionDefinition::single_input(
110    ///     "upper_case",
111    ///     "Convert a string to upper case.",
112    /// );
113    /// ```
114    pub fn single_input(name: impl Into<String>, description: impl Into<String>) -> Self {
115        Self::new(
116            name,
117            description,
118            serde_json::json!({
119                "type": "object",
120                "properties": {
121                    "input": {
122                        "type": "string",
123                        "description": "The input string"
124                    }
125                },
126                "required": ["input"]
127            }),
128        )
129    }
130}
131
132/// How the model should choose whether to call a tool.
133#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
134#[serde(rename_all = "lowercase")]
135pub enum ToolChoice {
136    /// The model decides (default).
137    Auto,
138    /// The model must not call any tool.
139    None,
140    /// The model must call at least one tool.
141    Required,
142    /// The model must call the named tool specifically.
143    Tool(String),
144}