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}