use std::collections::BTreeMap;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use systemprompt_identifiers::{BrainFactId, BrainRecallId, UserId, WorkflowId};
use super::declarations::QuorumUnit;
use super::fact::BrainFact;
use super::scope::ScopeLevel;
use crate::auth::RequestActor;
use crate::origin::ClientKind;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum RetrievalTier {
Pinned,
Subject,
Linked,
Context,
FullText,
Extension,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum WithheldReason {
Scope,
Source,
AgentCapped,
Stale,
ReviewDue,
Disputed,
Quota,
MaxFacts,
Budget,
LowScore,
ContextCap,
Unavailable,
}
impl WithheldReason {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::Scope => "scope",
Self::Source => "source",
Self::AgentCapped => "agent_capped",
Self::Stale => "stale",
Self::ReviewDue => "review_due",
Self::Disputed => "disputed",
Self::Quota => "quota",
Self::MaxFacts => "max_facts",
Self::Budget => "budget",
Self::LowScore => "low_score",
Self::ContextCap => "context_cap",
Self::Unavailable => "unavailable",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct RecallCtx {
pub user_id: UserId,
pub actor: RequestActor,
pub client: ClientKind,
pub project: Option<String>,
pub workflow: Option<WorkflowId>,
pub scopes: Vec<ScopeLevel>,
pub now: DateTime<Utc>,
pub as_of: DateTime<Utc>,
#[serde(default)]
pub held_keys: Vec<String>,
}
impl RecallCtx {
#[must_use]
pub fn new(user_id: UserId, actor: RequestActor, now: DateTime<Utc>) -> Self {
Self {
user_id,
actor,
client: ClientKind::Other,
project: None,
workflow: None,
scopes: ScopeLevel::ALL.to_vec(),
now,
as_of: now,
held_keys: Vec::new(),
}
}
#[must_use]
pub fn with_as_of(mut self, as_of: DateTime<Utc>) -> Self {
self.as_of = as_of.min(self.now);
self
}
#[must_use]
pub fn with_held_keys(mut self, keys: Vec<String>) -> Self {
self.held_keys = keys;
self
}
#[must_use]
pub const fn with_client(mut self, client: ClientKind) -> Self {
self.client = client;
self
}
#[must_use]
pub fn with_project(mut self, project: impl Into<String>) -> Self {
self.project = Some(project.into());
self
}
#[must_use]
pub fn with_workflow(mut self, workflow: WorkflowId) -> Self {
self.workflow = Some(workflow);
self
}
#[must_use]
pub fn with_scopes(mut self, scopes: Vec<ScopeLevel>) -> Self {
self.scopes = scopes;
self
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct RecallQuery {
pub subject_hints: String,
pub limit: u32,
#[serde(default = "RecallQuery::default_links_weight")]
pub links_weight: f64,
#[serde(default)]
pub synonyms: Vec<Vec<String>>,
#[serde(default = "RecallQuery::default_search_max_df")]
pub search_max_df: f64,
#[serde(default = "RecallQuery::default_search_quorum")]
pub search_quorum: f64,
#[serde(default)]
pub search_retry_quorum: f64,
#[serde(default)]
pub search_quorum_over: QuorumUnit,
#[serde(default = "RecallQuery::default_alias_min_sim")]
pub alias_min_sim: f64,
#[serde(default = "RecallQuery::default_max_subjects")]
pub max_subjects: u32,
}
impl RecallQuery {
#[must_use]
pub const fn new(subject_hints: String, limit: u32) -> Self {
Self {
subject_hints,
limit,
links_weight: Self::default_links_weight(),
synonyms: Vec::new(),
search_max_df: Self::default_search_max_df(),
search_quorum: Self::default_search_quorum(),
search_retry_quorum: 0.0,
search_quorum_over: QuorumUnit::Groups,
alias_min_sim: Self::default_alias_min_sim(),
max_subjects: Self::default_max_subjects(),
}
}
#[must_use]
pub const fn default_alias_min_sim() -> f64 {
0.6
}
#[must_use]
pub const fn default_max_subjects() -> u32 {
4
}
const fn default_links_weight() -> f64 {
0.6
}
const fn default_search_max_df() -> f64 {
0.2
}
const fn default_search_quorum() -> f64 {
0.5
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct TierRank {
pub tier: RetrievalTier,
pub rank: u32,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ScoredFact {
pub fact: BrainFact,
pub score: f64,
pub ranks: Vec<TierRank>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct WithheldFact {
pub id: BrainFactId,
pub reason: WithheldReason,
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
pub struct InjectionPlan {
pub facts: Vec<ScoredFact>,
pub withheld: Vec<WithheldFact>,
pub tokens: u32,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct FactRef {
pub id: BrainFactId,
pub revision: i32,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct StoredBlock {
pub rendered: String,
pub as_of: DateTime<Utc>,
pub facts: Vec<FactRef>,
#[serde(default)]
pub late: bool,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct RecallAudit {
pub recall_id: BrainRecallId,
pub injected: Vec<FactRef>,
pub withheld: BTreeMap<WithheldReason, u32>,
pub tokens: u32,
pub as_of: DateTime<Utc>,
}
impl RecallAudit {
#[must_use]
pub fn withheld_total(&self) -> u32 {
self.withheld.values().sum()
}
}