use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use serde_json::{Map, Value};
pub(crate) fn deserialize_id<'de, D>(deserializer: D) -> Result<u64, D::Error>
where
D: serde::Deserializer<'de>,
{
use serde::de::Error;
let expected = "expected a u64 number or a decimal u64 string";
match Value::deserialize(deserializer)? {
Value::Number(number) => number
.as_u64()
.ok_or_else(|| Error::custom(format!("invalid id {number} ({expected})"))),
Value::String(text) => text
.trim()
.parse()
.map_err(|_| Error::custom(format!("invalid id '{text}' ({expected})"))),
other => Err(Error::custom(format!("invalid id {other} ({expected})"))),
}
}
#[cfg(feature = "mcp")]
pub(crate) fn deserialize_optional_id<'de, D>(deserializer: D) -> Result<Option<u64>, D::Error>
where
D: serde::Deserializer<'de>,
{
use serde::de::Error;
let expected = "expected a u64 number, a decimal u64 string, or null";
match Value::deserialize(deserializer)? {
Value::Null => Ok(None),
Value::Number(number) => number
.as_u64()
.map(Some)
.ok_or_else(|| Error::custom(format!("invalid id {number} ({expected})"))),
Value::String(text) => text
.trim()
.parse()
.map(Some)
.map_err(|_| Error::custom(format!("invalid id '{text}' ({expected})"))),
other => Err(Error::custom(format!("invalid id {other} ({expected})"))),
}
}
#[derive(Debug, Clone, Deserialize, JsonSchema)]
#[schemars(transform = crate::schema::strip_int_formats)]
pub struct Link {
#[serde(deserialize_with = "deserialize_id")]
pub target: u64,
pub relation: String,
}
#[derive(Debug, Clone, Serialize, JsonSchema)]
#[schemars(transform = crate::schema::strip_int_formats)]
pub struct Recollection {
pub id: u64,
pub score: f32,
pub content: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub metadata: Option<Map<String, Value>>,
}
#[derive(Debug, Clone, Copy, Deserialize, JsonSchema)]
#[serde(rename_all = "lowercase")]
#[non_exhaustive] pub enum ColumnOp {
Eq,
Ne,
Lt,
Le,
Gt,
Ge,
}
impl ColumnOp {
#[cfg(feature = "persistence")]
#[must_use]
pub(crate) fn as_sql(self) -> &'static str {
match self {
Self::Eq => "=",
Self::Ne => "!=",
Self::Lt => "<",
Self::Le => "<=",
Self::Gt => ">",
Self::Ge => ">=",
}
}
}
#[must_use]
pub fn column_value_matches(stored: &Value, op: ColumnOp, target: &Value) -> bool {
if stored.is_null() {
return false;
}
if let (Some(left), Some(right)) = (stored.as_f64(), target.as_f64()) {
return compare_f64(op, left, right);
}
if let (Some(left), Some(right)) = (stored.as_str(), target.as_str()) {
return compare_ordered(op, &left, &right);
}
match op {
ColumnOp::Eq => stored == target,
ColumnOp::Ne => stored != target,
ColumnOp::Lt | ColumnOp::Le | ColumnOp::Gt | ColumnOp::Ge => false,
}
}
fn compare_f64(op: ColumnOp, left: f64, right: f64) -> bool {
match op {
ColumnOp::Eq => (left - right).abs() < f64::EPSILON,
ColumnOp::Ne => (left - right).abs() >= f64::EPSILON,
ColumnOp::Lt => left < right,
ColumnOp::Le => left <= right,
ColumnOp::Gt => left > right,
ColumnOp::Ge => left >= right,
}
}
fn compare_ordered<T: PartialOrd>(op: ColumnOp, left: &T, right: &T) -> bool {
match op {
ColumnOp::Eq => left == right,
ColumnOp::Ne => left != right,
ColumnOp::Lt => left < right,
ColumnOp::Le => left <= right,
ColumnOp::Gt => left > right,
ColumnOp::Ge => left >= right,
}
}
#[derive(Debug, Clone, Deserialize, JsonSchema)]
pub struct ColumnFilter {
pub field: String,
pub op: ColumnOp,
#[schemars(schema_with = "comparable_json_value")]
pub value: Value,
}
fn comparable_json_value(_: &mut schemars::SchemaGenerator) -> schemars::Schema {
schemars::json_schema!({
"type": ["number", "string", "boolean"],
"description": "Value to compare against. TYPE-STRICT: the JSON type must match \
how the fact was stored (a number never matches a string), and a \
mismatch returns no results rather than an error.",
})
}
#[derive(Debug, Clone, Copy)]
pub struct FusionOptions {
pub hops: usize,
pub graph_boost: f64,
pub pool: Option<usize>,
}
impl Default for FusionOptions {
fn default() -> Self {
Self {
hops: 2,
graph_boost: 0.15,
pool: None,
}
}
}
impl FusionOptions {
#[must_use]
pub fn from_knobs(hops: Option<usize>, graph_boost: Option<f64>, pool: Option<usize>) -> Self {
let defaults = Self::default();
Self {
hops: crate::limits::clamp_hops(hops.unwrap_or(defaults.hops)),
graph_boost: graph_boost.unwrap_or(defaults.graph_boost),
pool: pool
.map(crate::limits::clamp_recall_limit)
.or(defaults.pool),
}
}
#[must_use]
pub fn sanitized(mut self) -> Self {
if !self.graph_boost.is_finite() {
self.graph_boost = Self::default().graph_boost;
}
self
}
}
#[derive(Debug, Clone, Serialize, JsonSchema)]
#[schemars(transform = crate::schema::strip_int_formats)]
pub struct MemoryNode {
pub id: u64,
pub content: String,
pub hop: usize,
}
#[derive(Debug, Clone, Serialize, JsonSchema)]
#[schemars(transform = crate::schema::strip_int_formats)]
pub struct MemoryEdge {
pub id: u64,
pub from: u64,
pub to: u64,
pub relation: String,
}
#[derive(Debug, Clone)]
pub struct BoundedMemoryEdges {
pub edges: Vec<MemoryEdge>,
pub truncated: bool,
}
#[derive(Debug, Clone, Copy, Serialize, JsonSchema)]
#[schemars(transform = crate::schema::strip_int_formats)]
pub struct UnrelateOutcome {
pub found: bool,
pub removed: usize,
}
#[derive(Debug, Clone, Serialize, JsonSchema)]
#[schemars(transform = crate::schema::strip_int_formats)]
pub struct EntityRelation {
pub predicate: String,
pub target_id: u64,
pub target: String,
}
#[derive(Debug, Clone)]
pub struct RememberedExtraction {
pub ids: Vec<u64>,
pub skipped_over_cap: usize,
}
#[derive(Debug, Clone, Serialize, JsonSchema)]
#[schemars(transform = crate::schema::strip_int_formats)]
pub struct EntityProfile {
pub id: u64,
pub name: String,
pub attributes: crate::service::Metadata,
pub relations: Vec<EntityRelation>,
pub relations_in: Vec<EntityRelation>,
pub relations_truncated: bool,
pub relations_in_truncated: bool,
}
#[derive(Debug, Clone, Default, Serialize, JsonSchema)]
pub struct Explanation {
pub nodes: Vec<MemoryNode>,
pub edges: Vec<MemoryEdge>,
pub truncated: bool,
}
#[cfg(test)]
#[path = "model_tests.rs"]
mod tests;
#[derive(Debug, Clone)]
pub struct ListedMemory {
pub id: u64,
pub content: String,
pub metadata: Option<crate::service::Metadata>,
}