From 5bed58d349dc046103c5aae6a2086e62cfc7b38d Mon Sep 17 00:00:00 2001 From: jhlee525 Date: Mon, 13 Oct 2025 17:38:57 +0900 Subject: [PATCH 1/5] docstring for value module --- v2/src/value/message.rs | 78 +++++++++++++++++++++- v2/src/value/part.rs | 141 ++++++++++++++++++++++++++++++++++++++-- 2 files changed, 211 insertions(+), 8 deletions(-) diff --git a/v2/src/value/message.rs b/v2/src/value/message.rs index b2576af5..32dd8388 100644 --- a/v2/src/value/message.rs +++ b/v2/src/value/message.rs @@ -21,21 +21,48 @@ pub enum Role { Tool, } +/// A chat message generated by a user, model, or tool. +/// +/// `Message` is the concrete, non-streaming container used by the application to store, transmit, or feed structured content into models or tools. +/// It can represent various kinds of messages, including user input, assistant responses, tool-call outputs, or signed *thinking* metadata. +/// +/// Note that many different kinds of messages can be produced. +/// For example, a language model may internally generate a `thinking` trace before emitting its final output, in order to improve reasoning accuracy. +/// In other cases, a model may produce *function calls* — structured outputs that instruct external tools to perform specific actions. +/// +/// This struct is designed to handle all of these situations in a unified way. +/// +/// # Example +/// +/// ## Rust +/// ```rust +/// let msg = Message::new(Role::User).with_contents([Part::text("hello")]); +/// assert_eq!(msg.role, Role::User); +/// assert_eq!(msg.contents.len(), 1); +/// ``` #[derive(Clone, Debug, Serialize, Deserialize)] #[cfg_attr(feature = "python", pyo3_stub_gen_derive::gen_stub_pyclass)] #[cfg_attr(feature = "python", pyo3::pyclass(get_all, set_all))] #[cfg_attr(feature = "nodejs", napi_derive::napi(object))] pub struct Message { + /// Author of the message. pub role: Role, + /// Optional stable identifier for deduplication or threading. pub id: Option, + /// Internal “thinking” text used by some models before producing final output. pub thinking: String, + /// Primary message parts (e.g., text, image, value, or function). pub contents: Vec, + /// Tool-call parts emitted alongside the main contents. pub tool_calls: Vec, + /// Optional signature for the `thinking` field. + /// + /// This is only applicable to certain LLM APIs that require a signature as part of the `thinking` payload. pub signature: Option, } @@ -85,6 +112,30 @@ impl Message { } } +/// A streaming, incremental update to a [`Message`]. +/// +/// `MessageDelta` accumulates partial outputs (text chunks, tool-call fragments, IDs, signatures, etc.) until they can be materialized as a full [`Message`]. +/// It implements [`Delta`] to support associative aggregation. +/// +/// # Aggregation Rules +/// - `role`: merging two distinct roles fails. +/// - `thinking`: concatenated in arrival order. +/// - `contents`/`tool_calls`: last element is aggregated with the incoming delta when both are compatible (e.g., Text+Text, Function+Function with matching ID policy), otherwise appended as a new fragment. +/// - `id`/`signature`: last-writer-wins. +/// +/// # Finalization +/// - `finish()` converts the accumulated deltas into a fully-formed [`Message`]. +/// Fails if required fields (e.g., `role`) are missing or inner deltas cannot be finalized. +/// +/// # Examples +/// ```rust +/// let d1 = MessageDelta::new().with_role(Role::Assistant).with_contents([PartDelta::Text { text: "Hel".into() }]); +/// let d2 = MessageDelta::new().with_contents([PartDelta::Text { text: "lo".into() }]); +/// +/// let merged = d1.aggregate(d2).unwrap(); +/// let msg = merged.finish().unwrap(); +/// assert_eq!(msg.contents[0].as_text().unwrap(), "Hello"); +/// ``` #[derive(Clone, Debug, Default)] #[cfg_attr(feature = "python", pyo3_stub_gen_derive::gen_stub_pyclass)] #[cfg_attr(feature = "python", pyo3::pyclass(get_all, set_all))] @@ -284,17 +335,40 @@ impl Delta for MessageDelta { } } +/// Explains why a language model's streamed generation finished. #[derive(Clone, Debug, PartialEq, Eq)] #[cfg_attr(feature = "python", pyo3_stub_gen_derive::gen_stub_pyclass_enum)] #[cfg_attr(feature = "python", pyo3::pyclass(eq))] #[cfg_attr(feature = "nodejs", napi_derive::napi)] pub enum FinishReason { + /// The model stopped naturally (e.g., EOS token or stop sequence). Stop(), - Length(), // max_output_tokens + + /// Hit the maximum token/length limit. + Length(), + + /// Stopped because a tool call was produced, waiting for it's execution. ToolCall(), - Refusal(String), // content_filter, refusal + + /// Content was refused/filtered; string provides reason. + Refusal(String), } +/// A container for a streamed message delta and its termination signal. +/// +/// During streaming, `delta` carries the incremental payload; once a terminal +/// condition is reached, `finish_reason` may be populated to explain why. +/// +/// # Examples +/// ```rust +/// let mut out = MessageOutput::new(); +/// out.delta = MessageDelta::new().with_role(Role::Assistant).with_contents([PartDelta::Text { text: "Hi".into() }]); +/// assert!(out.finish_reason.is_none()); +/// ``` +/// +/// # Lifecycle +/// - While streaming: `finish_reason` is typically `None`. +/// - On completion: `finish_reason` is set; callers can then `finish()` the delta to obtain a concrete [`Message`]. #[derive(Clone, Debug, Default)] #[cfg_attr(feature = "python", pyo3_stub_gen_derive::gen_stub_pyclass)] #[cfg_attr(feature = "python", pyo3::pyclass(get_all, set_all))] diff --git a/v2/src/value/part.rs b/v2/src/value/part.rs index 1dc16c09..435933b7 100644 --- a/v2/src/value/part.rs +++ b/v2/src/value/part.rs @@ -3,26 +3,61 @@ use serde::{Deserialize, Serialize}; use crate::value::{Value, delta::Delta}; +/// Represents a function call contained within a message part. +/// +/// Many language models (LLMs) use a **function calling** mechanism to extend their capabilities. +/// When an LLM decides to use external *tools*, it produces a structured output called a `function`. +/// A function conventionally consists of two fields: a `name`, and an `arguments` field formatted as JSON. +/// This is conceptually similar to making an HTTP POST request, where the request body carries a single JSON object. +/// +/// This struct models that convention, representing a function invocation request +/// from an LLM to an external tool or API. +/// +/// # Examples +/// ```rust +/// let f = PartFunction { +/// name: "translate".to_string(), +/// args: Value::from_json(r#"{"text": "hello", "lang": "cn"}"#).unwrap(), +/// }; +/// ``` #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] #[cfg_attr(feature = "nodejs", napi_derive::napi(object))] pub struct PartFunction { + /// The name of the function pub name: String, + + /// The arguments of the function, usually represented as a JSON object. #[serde(rename = "arguments")] pub args: Value, } +/// Represents the color space of an image part. +/// +/// This enum defines the supported pixel formats of image data. It determines +/// how many channels each pixel has and how the image should be interpreted. +/// +/// # Examples +/// ```rust +/// let c = PartImageColorspace::RGB; +/// assert_eq!(c.channel(), 3); +/// ``` #[derive( Clone, Debug, PartialEq, Eq, Serialize, Deserialize, strum::EnumString, strum::Display, )] #[serde(untagged)] #[cfg_attr(feature = "nodejs", napi_derive::napi(string_enum))] pub enum PartImageColorspace { + /// Single-channel grayscale image #[strum(serialize = "grayscale")] #[serde(rename = "grayscale")] Grayscale, + + /// Three-channel color image without alpha #[strum(serialize = "rgb")] #[serde(rename = "rgb")] RGB, + + /// Four-channel color image with alpha #[strum(serialize = "rgba")] #[serde(rename = "rgba")] RGBA, @@ -46,6 +81,20 @@ impl TryFrom for PartImageColorspace { } } +/// Represents the image data contained in a [`Part`]. +/// +/// `PartImage` provides structured access to image data. +/// Currently, it only implments "binary" types. +/// +/// # Example +/// ```rust +/// let part = Part::image_binary(640, 480, "rgb", (0..640*480*3).map(|i| (i % 255) as u8)).unwrap(); +/// +/// if let Some(img) = part.as_image() { +/// assert_eq!(img.height(), 640); +/// assert_eq!(img.width(), 480); +/// } +/// ``` #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] #[serde(tag = "media-type")] #[cfg_attr( @@ -68,6 +117,26 @@ pub enum PartImage { }, } +/// Represents a semantically meaningful content unit exchanged between the model and the user. +/// +/// Conceptually, each `Part` encapsulates a piece of **data** that contributes +/// to a chat message — such as text, a function invocation, or an image. +/// +/// For example, a single message consisting of a sequence like +/// `(text..., image, text...)` is represented as a `Message` containing +/// an array of three `Part` elements. +/// +/// Note that a `Part` does **not** carry "intent", such as "reasoning" or "tool call". +/// These higher-level semantics are determined by the context of a [`Message`]. +/// +/// # Example +/// +/// ## Rust +/// ```rust +/// let part = Part::text("Hello, world!"); +/// assert!(part.is_text()); +/// ``` +/// #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] #[serde(tag = "type")] #[cfg_attr( @@ -77,16 +146,23 @@ pub enum PartImage { #[cfg_attr(feature = "python", pyo3::pyclass(eq))] #[cfg_attr(feature = "nodejs", napi_derive::napi)] pub enum Part { + /// Plain text content. #[serde(rename = "text")] Text { text: String }, + + /// Represents a structured function call to an external tool. #[serde(rename = "function")] Function { id: Option, #[serde(rename = "function")] f: PartFunction, }, + + /// Holds a structured data value, typically a JSON object. #[serde(rename = "value")] Value { value: Value }, + + /// Contains image data or references with optional metadata. #[serde(rename = "image")] Image { image: PartImage }, } @@ -96,6 +172,17 @@ impl Part { Self::Text { text: v.into() } } + /// Create a new image pixel map. + /// + /// # Encoding Notes + /// * The `data` field is expected to contain pixel data in **row-major order**. + /// * The bytes per channel depend on the color depth (e.g., 1 byte for 8-bit, 2 bytes for 16-bit). + /// * The total size of `data` must be equal to: `height × width × colorspace.channel() × bytes_per_channel`. + /// + /// # Errors + /// Image construction fails if: + /// * The colorspace cannot be parsed from the provided input. + /// * The data length does not match the expected size given dimensions and channels. pub fn image_binary( height: u32, width: u32, @@ -296,6 +383,26 @@ impl Part { } } +/// Represents an incremental update (delta) of a function part. +/// +/// This type is used during streaming or partial message generation, when function calls are being streamed as text chunks or partial JSON fragments. +/// +/// # Variants +/// * `Verbatim(String)` — Raw text content, typically a partial JSON fragment. +/// * `WithStringArgs { name, args }` — Function name and its serialized arguments as strings. +/// * `WithParsedArgs { name, args }` — Function name and parsed arguments as a `Value`. +/// +/// # Use Case +/// When the model streams out a function call response (e.g., `"function_call":{"name":...}`), +/// the incremental deltas can be aggregated until the full function payload is formed. +/// +/// # Example +/// ```rust +/// let delta = PartDeltaFunction::WithStringArgs { +/// name: "translate".into(), +/// args: r#"{"text":"hi"}"#.into(), +/// }; +/// ` #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] #[serde(untagged)] #[cfg_attr( @@ -318,6 +425,25 @@ pub enum PartDeltaFunction { }, } +/// Represents a partial or incremental update (delta) of a [`Part`]. +/// +/// This type enables composable, streaming updates to message parts. +/// For example, text may be produced token-by-token, or a function call +/// may be emitted gradually as its arguments stream in. +/// +/// # Example +/// +/// ## Rust +/// ```rust +/// let d1 = PartDelta::Text { text: "Hel".into() }; +/// let d2 = PartDelta::Text { text: "lo".into() }; +/// let merged = d1.aggregate(d2).unwrap(); +/// assert_eq!(merged.to_text().unwrap(), "Hello"); +/// ``` +/// +/// # Error Handling +/// Aggregation or finalization may return an error if incompatible deltas +/// (e.g. mismatched function IDs) are combined or invalid JSON arguments are given. #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] #[serde(untagged)] #[cfg_attr( @@ -327,17 +453,20 @@ pub enum PartDeltaFunction { #[cfg_attr(feature = "python", pyo3::pyclass(eq))] #[cfg_attr(feature = "nodejs", napi_derive::napi)] pub enum PartDelta { - Text { - text: String, - }, + /// Incremental text fragment. + Text { text: String }, + + /// Incremental function call fragment. Function { id: Option, #[serde(rename = "function")] f: PartDeltaFunction, }, - Value { - value: Value, - }, + + /// JSON-like value update. + Value { value: Value }, + + /// Placeholder representing no data yet. Null(), } From 0de8619b90be451cab65f9a7663106e1b30911dc Mon Sep 17 00:00:00 2001 From: jhlee525 Date: Thu, 16 Oct 2025 12:42:51 +0900 Subject: [PATCH 2/5] update the image explaination --- v2/src/value/part.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/v2/src/value/part.rs b/v2/src/value/part.rs index 435933b7..c0db849a 100644 --- a/v2/src/value/part.rs +++ b/v2/src/value/part.rs @@ -162,7 +162,9 @@ pub enum Part { #[serde(rename = "value")] Value { value: Value }, - /// Contains image data or references with optional metadata. + /// Contains an image payload or reference used within a message part. + /// The image may be provided as raw binary data or an encoded format (e.g., PNG, JPEG), + /// or as a reference via a URL. Optional metadata can be included alongside the image. #[serde(rename = "image")] Image { image: PartImage }, } From dd4a54fd20197cd0521cc513342538d0e9981791 Mon Sep 17 00:00:00 2001 From: jhlee525 Date: Thu, 16 Oct 2025 12:51:09 +0900 Subject: [PATCH 3/5] change args to arguments --- v2/src/value/part.rs | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/v2/src/value/part.rs b/v2/src/value/part.rs index b9c851c5..ae99b3b7 100644 --- a/v2/src/value/part.rs +++ b/v2/src/value/part.rs @@ -17,7 +17,7 @@ use crate::value::{Value, delta::Delta}; /// ```rust /// let f = PartFunction { /// name: "translate".to_string(), -/// args: Value::from_json(r#"{"text": "hello", "lang": "cn"}"#).unwrap(), +/// arguments: Value::from_json(r#"{"text": "hello", "lang": "cn"}"#).unwrap(), /// }; /// ``` #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] @@ -202,12 +202,12 @@ impl Part { }) } - pub fn function(name: impl Into, args: impl Into) -> Self { + pub fn function(name: impl Into, arguments: impl Into) -> Self { Self::Function { id: None, function: PartFunction { name: name.into(), - arguments: args.into(), + arguments: arguments.into(), }, } } @@ -215,13 +215,13 @@ impl Part { pub fn function_with_id( id: impl Into, name: impl Into, - args: impl Into, + arguments: impl Into, ) -> Self { Self::Function { id: Some(id.into()), function: PartFunction { name: name.into(), - arguments: args.into(), + arguments: arguments.into(), }, } } @@ -383,8 +383,8 @@ impl Part { /// /// # Variants /// * `Verbatim(String)` — Raw text content, typically a partial JSON fragment. -/// * `WithStringArgs { name, args }` — Function name and its serialized arguments as strings. -/// * `WithParsedArgs { name, args }` — Function name and parsed arguments as a `Value`. +/// * `WithStringArgs { name, arguments }` — Function name and its serialized arguments as strings. +/// * `WithParsedArgs { name, arguments }` — Function name and parsed arguments as a `Value`. /// /// # Use Case /// When the model streams out a function call response (e.g., `"function_call":{"name":...}`), @@ -394,7 +394,7 @@ impl Part { /// ```rust /// let delta = PartDeltaFunction::WithStringArgs { /// name: "translate".into(), -/// args: r#"{"text":"hi"}"#.into(), +/// arguments: r#"{"text":"hi"}"#.into(), /// }; /// ` #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] @@ -708,12 +708,12 @@ mod py { if let Ok(pydict) = ob.downcast::() { let name_any = pydict.get_item("name")?; let name: String = name_any.extract()?; - let args_any = pydict.get_item("args")?; + let args_any = pydict.get_item("arguments")?; let arguments: Value = args_any.extract()?; Ok(Self { name, arguments }) } else { Err(PyTypeError::new_err( - "PartFunction must be a dict with keys 'name' and 'args'", + "PartFunction must be a dict with keys 'name' and 'arguments'", )) } } @@ -728,7 +728,7 @@ mod py { let d = PyDict::new(py); d.set_item("name", self.name)?; let py_args = self.arguments.into_pyobject(py)?; - d.set_item("args", py_args)?; + d.set_item("arguments", py_args)?; Ok(d) } } @@ -744,7 +744,7 @@ mod py { TypeInfo { name: format!( - "dict[typing.Literal[\"name\", \"args\"], typing.Union[str, {}]]", + "dict[typing.Literal[\"name\", \"arguments\"], typing.Union[str, {}]]", value_name ), import: imports, From 4b4ad16471c7e16952d7422ba4f8aadfaae95386 Mon Sep 17 00:00:00 2001 From: jhlee525 Date: Thu, 16 Oct 2025 12:57:03 +0900 Subject: [PATCH 4/5] update part docstring --- v2/src/value/part.rs | 37 +++++++++++++++++++------------------ 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/v2/src/value/part.rs b/v2/src/value/part.rs index ae99b3b7..dc09a2b8 100644 --- a/v2/src/value/part.rs +++ b/v2/src/value/part.rs @@ -4,22 +4,6 @@ use serde::{Deserialize, Serialize}; use crate::value::{Value, delta::Delta}; /// Represents a function call contained within a message part. -/// -/// Many language models (LLMs) use a **function calling** mechanism to extend their capabilities. -/// When an LLM decides to use external *tools*, it produces a structured output called a `function`. -/// A function conventionally consists of two fields: a `name`, and an `arguments` field formatted as JSON. -/// This is conceptually similar to making an HTTP POST request, where the request body carries a single JSON object. -/// -/// This struct models that convention, representing a function invocation request -/// from an LLM to an external tool or API. -/// -/// # Examples -/// ```rust -/// let f = PartFunction { -/// name: "translate".to_string(), -/// arguments: Value::from_json(r#"{"text": "hello", "lang": "cn"}"#).unwrap(), -/// }; -/// ``` #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] #[cfg_attr(feature = "nodejs", napi_derive::napi(object))] #[cfg_attr(feature = "wasm", derive(tsify::Tsify))] @@ -144,15 +128,32 @@ pub enum PartImage { #[cfg_attr(feature = "wasm", derive(tsify::Tsify))] #[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))] pub enum Part { - /// Represents a structured function call to an external tool. + /// Plain utf-8 encoded text. Text { text: String }, + /// Represents a structured function call to an external tool. + /// + /// Many language models (LLMs) use a **function calling** mechanism to extend their capabilities. + /// When an LLM decides to use external *tools*, it produces a structured output called a `function`. + /// A function conventionally consists of two fields: a `name`, and an `arguments` field formatted as JSON. + /// This is conceptually similar to making an HTTP POST request, where the request body carries a single JSON object. + /// + /// This struct models that convention, representing a function invocation request + /// from an LLM to an external tool or API. + /// + /// # Examples + /// ```rust + /// let f = PartFunction { + /// name: "translate".to_string(), + /// arguments: Value::from_json(r#"{"source": "hello", "lang": "cn"}"#).unwrap(), + /// }; + /// ``` Function { id: Option, function: PartFunction, }, - /// Holds a structured data value, typically a JSON object. + /// Holds a structured data value, typically considered as a JSON structure. Value { value: Value }, /// Contains an image payload or reference used within a message part. From 87f2d30dac0ba7109fef6d2e9f69ef142889d9eb Mon Sep 17 00:00:00 2001 From: jhlee525 Date: Thu, 16 Oct 2025 13:30:40 +0900 Subject: [PATCH 5/5] add tool description docs --- v2/src/value/tool_desc.rs | 69 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/v2/src/value/tool_desc.rs b/v2/src/value/tool_desc.rs index 2c1efbf6..e1a4eb18 100644 --- a/v2/src/value/tool_desc.rs +++ b/v2/src/value/tool_desc.rs @@ -2,6 +2,49 @@ use serde::{Deserialize, Serialize}; use crate::value::Value; +/// Describes a **tool** (or function) that a language model can invoke. +/// +/// `ToolDesc` defines the schema, behavior, and input/output specification of a callable +/// external function, allowing an LLM to understand how to use it. +/// +/// The primary role of this struct is to describe to the LLM what a *tool* does, +/// how it can be invoked, and what input (`parameters`) and output (`returns`) schemas it expects. +/// +/// The format follows the same **schema conventions** used by Hugging Face’s +/// `transformers` library, as well as APIs such as *OpenAI* and *Anthropic*. +/// The `parameters` and `returns` fields are typically defined using **JSON Schema**. +/// +/// We provide a builder [`ToolDescBuilder`] helper for convenient and fluent construction. +/// Please refer to [`ToolDescBuilder`]. +/// +/// # Example +/// ```rust +/// use crate::value::{ToolDescBuilder, to_value}; +/// +/// let desc = ToolDescBuilder::new("temperature") +/// .description("Get the current temperature for a given city") +/// .parameters(to_value!({ +/// "type": "object", +/// "properties": { +/// "location": { +/// "type": "string", +/// "description": "The city name" +/// }, +/// "unit": { +/// "type": "string", +/// "description": "Temperature unit (default: Celsius)", +/// "enum": ["Celsius", "Fahrenheit"] +/// } +/// }, +/// "required": ["location"] +/// })) +/// .returns(to_value!({ +/// "type": "number" +/// })) +/// .build(); +/// +/// assert_eq!(desc.name, "temperature"); +/// ``` #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] #[cfg_attr(feature = "python", pyo3_stub_gen_derive::gen_stub_pyclass)] #[cfg_attr(feature = "python", pyo3::pyclass)] @@ -9,16 +52,42 @@ use crate::value::Value; #[cfg_attr(feature = "wasm", derive(tsify::Tsify))] #[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))] pub struct ToolDesc { + /// The unique name of the tool or function. pub name: String, + + /// A natural-language description of what the tool does. #[serde(skip_serializing_if = "Option::is_none")] pub description: Option, + + /// A [`Value`] describing the JSON Schema of the expected parameters. + /// Typically an object schema such as `{ "type": "object", "properties": ... }`. pub parameters: Value, + + /// An optional [`Value`] that defines the return value schema. + /// If omitted, the tool is assumed to return free-form text or JSON. #[serde(skip_serializing_if = "Option::is_none")] pub returns: Option, } impl ToolDesc {} +/// A builder for constructing [`ToolDesc`] objects. +/// +/// Provides a fluent, chainable API for creating a tool description safely and clearly. +/// If no `parameters` are provided, it defaults to [`Value::Null`]. +/// +/// # Example +/// ```rust +/// use crate::value::{ToolDescBuilder, Value}; +/// +/// let tool = ToolDescBuilder::new("weather") +/// .description("Fetch current weather information") +/// .parameters(Value::Null) +/// .returns(Value::Null) +/// .build(); +/// +/// assert_eq!(tool.name, "weather"); +/// ``` #[derive(Clone, Debug)] pub struct ToolDescBuilder { pub name: String,