Skip to main content

somatize_core/
value.rs

1//! Typed values flowing between filters in a pipeline.
2//!
3//! [`Value`] variants: Tensor (f64 array with shape), Text, JSON, Bytes,
4//! Object, Empty.
5//! Values are serializable and content-addressable via [`crate::cache::CacheKey`].
6
7use serde::{Deserialize, Serialize};
8use std::fmt;
9use std::sync::Arc;
10
11/// Typed values flowing between filters in a pipeline.
12#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
13#[serde(tag = "type", content = "data")]
14#[non_exhaustive]
15pub enum Value {
16    /// Numeric tensor data (shape + flat data).
17    /// `values` is wrapped in [`Arc`] so that cloning a `Value` is O(1).
18    Tensor {
19        /// Flat data in row-major order; `Arc`-shared, never mutated in place.
20        values: Arc<Vec<f64>>,
21        /// Dimension sizes; the product must equal `values.len()`.
22        shape: Vec<usize>,
23    },
24
25    /// UTF-8 text (Arc-wrapped for cheap cloning).
26    ///
27    /// Distinct from `Json(String)`: a prompt or completion is text, not a
28    /// JSON document that happens to be a string. Keeping them apart means
29    /// no round-trip through quoting/escaping on every hop, and lets a
30    /// schema say "this edge carries text" — see [`crate::schema::DataType`].
31    Text(Arc<str>),
32
33    /// Structured JSON data (Arc-wrapped for cheap cloning).
34    Json(Arc<serde_json::Value>),
35
36    /// Raw bytes (Arc-wrapped for cheap cloning).
37    Bytes(Arc<Vec<u8>>),
38
39    /// Opaque serialized object (e.g. Python pickle).
40    /// Soma passes it through without interpreting the contents.
41    /// Used for efficient inter-filter data transfer when the producing
42    /// and consuming runtimes share a serialization format.
43    Object(Arc<Vec<u8>>),
44
45    /// Empty / void value
46    Empty,
47}
48
49impl Value {
50    /// Create a tensor from flat row-major data and a shape.
51    pub fn tensor(values: Vec<f64>, shape: Vec<usize>) -> Self {
52        Self::Tensor {
53            values: Arc::new(values),
54            shape,
55        }
56    }
57
58    /// Create a text value.
59    pub fn text(s: impl AsRef<str>) -> Self {
60        Self::Text(Arc::from(s.as_ref()))
61    }
62
63    /// Create a JSON value.
64    pub fn json(val: serde_json::Value) -> Self {
65        Self::Json(Arc::new(val))
66    }
67
68    /// Create a raw bytes value.
69    pub fn bytes(data: Vec<u8>) -> Self {
70        Self::Bytes(Arc::new(data))
71    }
72
73    /// Create an opaque serialized object (e.g. a Python pickle).
74    pub fn object(data: Vec<u8>) -> Self {
75        Self::Object(Arc::new(data))
76    }
77
78    /// Is this the [`Value::Empty`] variant?
79    pub fn is_empty(&self) -> bool {
80        matches!(self, Self::Empty)
81    }
82
83    /// Try to extract tensor data.
84    pub fn as_tensor(&self) -> Option<(&[f64], &[usize])> {
85        match self {
86            Self::Tensor { values, shape } => Some((values, shape)),
87            _ => None,
88        }
89    }
90
91    /// Try to extract text.
92    ///
93    /// A `Json` string counts: a filter that returns `"hello"` as JSON and one
94    /// that returns it as text should both satisfy a consumer wanting text.
95    pub fn as_text(&self) -> Option<&str> {
96        match self {
97            Self::Text(s) => Some(s),
98            Self::Json(v) => v.as_str(),
99            _ => None,
100        }
101    }
102
103    /// Try to extract JSON value.
104    pub fn as_json(&self) -> Option<&serde_json::Value> {
105        match self {
106            Self::Json(v) => Some(v),
107            _ => None,
108        }
109    }
110
111    /// Natural JSON form for user-facing fan-in: tensors become
112    /// (nested) number arrays, Json unwraps, Empty is null. This is what
113    /// a multi-predecessor node receives per upstream branch — never the
114    /// internal serde-tagged encoding.
115    pub fn to_plain_json(&self) -> serde_json::Value {
116        fn nest(values: &[f64], shape: &[usize]) -> serde_json::Value {
117            if shape.len() <= 1 {
118                return serde_json::Value::Array(
119                    values.iter().map(|v| serde_json::json!(v)).collect(),
120                );
121            }
122            let rows = shape[0];
123            let row_len: usize = shape[1..].iter().product::<usize>().max(1);
124            serde_json::Value::Array(
125                (0..rows)
126                    .map(|r| {
127                        let start = r * row_len;
128                        let end = (start + row_len).min(values.len());
129                        nest(&values[start..end.max(start)], &shape[1..])
130                    })
131                    .collect(),
132            )
133        }
134        match self {
135            Self::Tensor { values, shape } => nest(values, shape),
136            Self::Text(s) => serde_json::Value::String(s.to_string()),
137            Self::Json(v) => (**v).clone(),
138            Self::Empty => serde_json::Value::Null,
139            other => serde_json::to_value(other).unwrap_or(serde_json::Value::Null),
140        }
141    }
142
143    /// Short name of the variant, for error messages.
144    ///
145    /// Unlike `Display` this never renders the payload, so it is safe to put
146    /// in an error that may reach a log — a JSON value can hold a prompt.
147    pub fn type_name(&self) -> &'static str {
148        match self {
149            Self::Tensor { .. } => "Tensor",
150            Self::Text(_) => "Text",
151            Self::Json(_) => "Json",
152            Self::Bytes(_) => "Bytes",
153            Self::Object(_) => "Object",
154            Self::Empty => "Empty",
155        }
156    }
157
158    /// Number of elements (for tensors) or bytes.
159    pub fn size(&self) -> usize {
160        match self {
161            Self::Tensor { values, .. } => values.len(),
162            Self::Text(s) => s.len(),
163            Self::Json(v) => v.to_string().len(),
164            Self::Bytes(b) | Self::Object(b) => b.len(),
165            Self::Empty => 0,
166        }
167    }
168}
169
170impl fmt::Display for Value {
171    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
172        match self {
173            Self::Tensor { shape, values } => {
174                write!(f, "Tensor(shape={shape:?}, len={})", values.len())
175            }
176            Self::Text(s) => write!(f, "Text(len={})", s.len()),
177            Self::Json(v) => write!(f, "Json({v})"),
178            Self::Bytes(b) => write!(f, "Bytes(len={})", b.len()),
179            Self::Object(b) => write!(f, "Object(len={})", b.len()),
180            Self::Empty => write!(f, "Empty"),
181        }
182    }
183}
184
185impl From<Vec<f64>> for Value {
186    fn from(values: Vec<f64>) -> Self {
187        let len = values.len();
188        Self::Tensor {
189            values: Arc::new(values),
190            shape: vec![len],
191        }
192    }
193}
194
195impl From<serde_json::Value> for Value {
196    fn from(v: serde_json::Value) -> Self {
197        Self::Json(Arc::new(v))
198    }
199}
200
201#[cfg(test)]
202mod tests {
203    use super::*;
204    use serde_json::json;
205
206    #[test]
207    fn tensor_creation_and_access() {
208        let v = Value::tensor(vec![1.0, 2.0, 3.0, 4.0], vec![2, 2]);
209        let (data, shape) = v.as_tensor().unwrap();
210        assert_eq!(data, &[1.0, 2.0, 3.0, 4.0]);
211        assert_eq!(shape, &[2, 2]);
212    }
213
214    #[test]
215    fn json_value() {
216        let v = Value::json(json!({"key": "value"}));
217        let j = v.as_json().unwrap();
218        assert_eq!(j["key"], "value");
219    }
220
221    #[test]
222    fn empty_value() {
223        let v = Value::Empty;
224        assert!(v.is_empty());
225        assert_eq!(v.size(), 0);
226    }
227
228    #[test]
229    fn from_vec_f64() {
230        let v: Value = vec![1.0, 2.0, 3.0].into();
231        let (data, shape) = v.as_tensor().unwrap();
232        assert_eq!(data, &[1.0, 2.0, 3.0]);
233        assert_eq!(shape, &[3]);
234    }
235
236    #[test]
237    fn display_formatting() {
238        let t = Value::tensor(vec![1.0, 2.0], vec![2]);
239        assert_eq!(t.to_string(), "Tensor(shape=[2], len=2)");
240
241        let e = Value::Empty;
242        assert_eq!(e.to_string(), "Empty");
243    }
244
245    #[test]
246    fn serde_roundtrip() {
247        let values = vec![
248            Value::tensor(vec![1.0, 2.0, 3.0], vec![3]),
249            Value::json(json!({"a": 1})),
250            Value::bytes(vec![0xDE, 0xAD]),
251            Value::Empty,
252        ];
253
254        for v in values {
255            let serialized = serde_json::to_string(&v).unwrap();
256            let deserialized: Value = serde_json::from_str(&serialized).unwrap();
257            assert_eq!(v, deserialized);
258        }
259    }
260
261    #[test]
262    fn size_returns_correct_values() {
263        assert_eq!(Value::tensor(vec![1.0; 100], vec![10, 10]).size(), 100);
264        assert_eq!(Value::bytes(vec![0; 50]).size(), 50);
265        assert!(Value::json(json!({"key": "val"})).size() > 0);
266    }
267}