rune-chain-core 0.1.2

Core traits and types for the rune-chain LLM orchestration framework
Documentation
use serde::{Deserialize, Serialize};

/// A single tool/function call requested by the model in a generation response.
///
/// When an LLM decides to call a tool, it emits one or more `ToolCall` values
/// in [`GenerateResult::tool_calls`](crate::GenerateResult::tool_calls). The
/// caller is responsible for executing each tool and feeding the results back
/// as [`Role::Tool`](crate::Role::Tool) messages.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ToolCall {
    /// Provider-issued ID correlating this call with its result message.
    pub id: String,
    /// Name of the function/tool to invoke (matches [`Tool::name`](crate::Tool::name)).
    pub name: String,
    /// JSON-encoded argument object the model wants passed to the tool.
    pub arguments: String,
}

impl ToolCall {
    /// Create a new `ToolCall`.
    pub fn new(
        id: impl Into<String>,
        name: impl Into<String>,
        arguments: impl Into<String>,
    ) -> Self {
        Self {
            id: id.into(),
            name: name.into(),
            arguments: arguments.into(),
        }
    }

    /// Parse [`arguments`](Self::arguments) as a JSON object and return the value of `key`.
    ///
    /// Returns `None` if the JSON is invalid or the key is absent.
    pub fn arg(&self, key: &str) -> Option<serde_json::Value> {
        serde_json::from_str::<serde_json::Value>(&self.arguments)
            .ok()
            .and_then(|v| v.get(key).cloned())
    }

    /// Parse [`arguments`](Self::arguments) as a JSON object and extract the
    /// `"input"` key as a string — the standard single-arg tool convention.
    pub fn input(&self) -> Option<String> {
        self.arg("input").and_then(|v| match v {
            serde_json::Value::String(s) => Some(s),
            other => Some(other.to_string()),
        })
    }
}

/// JSON-Schema-based description of a tool the LLM may call.
///
/// Pass a slice of `FunctionDefinition`s to
/// [`Llm::generate_with_tools`](crate::Llm::generate_with_tools) to enable
/// native function calling instead of text-based ReAct parsing.
///
/// # Example
///
/// ```rust
/// use rune_chain_core::FunctionDefinition;
/// use serde_json::json;
///
/// let def = FunctionDefinition::new(
///     "get_weather",
///     "Return the current weather for a city.",
///     json!({
///         "type": "object",
///         "properties": {
///             "city": { "type": "string", "description": "City name" }
///         },
///         "required": ["city"]
///     }),
/// );
/// ```
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FunctionDefinition {
    /// Unique tool name (no spaces; matches [`Tool::name`](crate::Tool::name)).
    pub name: String,
    /// One-sentence description the LLM uses to decide when to call this tool.
    pub description: String,
    /// JSON Schema object describing the tool's parameter object.
    pub parameters: serde_json::Value,
}

impl FunctionDefinition {
    /// Create a new function definition.
    pub fn new(
        name: impl Into<String>,
        description: impl Into<String>,
        parameters: serde_json::Value,
    ) -> Self {
        Self {
            name: name.into(),
            description: description.into(),
            parameters,
        }
    }

    /// Build a definition with the standard single-string `"input"` parameter.
    ///
    /// Use this as a quick wrapper for tools that accept a plain string.
    ///
    /// # Example
    ///
    /// ```rust
    /// use rune_chain_core::FunctionDefinition;
    ///
    /// let def = FunctionDefinition::single_input(
    ///     "upper_case",
    ///     "Convert a string to upper case.",
    /// );
    /// ```
    pub fn single_input(name: impl Into<String>, description: impl Into<String>) -> Self {
        Self::new(
            name,
            description,
            serde_json::json!({
                "type": "object",
                "properties": {
                    "input": {
                        "type": "string",
                        "description": "The input string"
                    }
                },
                "required": ["input"]
            }),
        )
    }
}

/// How the model should choose whether to call a tool.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum ToolChoice {
    /// The model decides (default).
    Auto,
    /// The model must not call any tool.
    None,
    /// The model must call at least one tool.
    Required,
    /// The model must call the named tool specifically.
    Tool(String),
}