#![allow(deprecated)]
use crate::error::MemoryError;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use stack_ids::{
ClaimId, ClaimVersionId, EntityId, EnvelopeId, EpisodeId, RelationVersionId, ScopeKey,
};
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(transparent)]
pub struct CompatTraceId(pub String);
#[deprecated(since = "0.5.0", note = "Use stack_ids::TraceCtx instead")]
pub type TraceId = CompatTraceId;
impl CompatTraceId {
pub fn new(value: impl Into<String>) -> Self {
Self(value.into())
}
pub fn as_str(&self) -> &str {
&self.0
}
}
impl std::fmt::Display for CompatTraceId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&self.0)
}
}
impl From<String> for CompatTraceId {
fn from(value: String) -> Self {
Self(value)
}
}
impl From<&str> for CompatTraceId {
fn from(value: &str) -> Self {
Self(value.to_string())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum Role {
System,
User,
Assistant,
Tool,
}
impl Role {
pub fn as_str(&self) -> &'static str {
match self {
Role::System => "system",
Role::User => "user",
Role::Assistant => "assistant",
Role::Tool => "tool",
}
}
pub fn from_str_value(s: &str) -> Option<Self> {
match s {
"system" => Some(Role::System),
"user" => Some(Role::User),
"assistant" => Some(Role::Assistant),
"tool" => Some(Role::Tool),
_ => None,
}
}
}
impl std::fmt::Display for Role {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_str())
}
}
impl std::str::FromStr for Role {
type Err = MemoryError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::from_str_value(s).ok_or_else(|| MemoryError::Other(format!("Unknown role: '{}'", s)))
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SearchSourceType {
Facts,
Chunks,
Messages,
Episodes,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ReceiptMode {
#[default]
Disabled,
ExplainOnly,
ReturnReceipt,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ExactnessProfile {
#[default]
Default,
PreferExact,
AllowApproximate,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SearchContext {
pub evaluation_time: DateTime<Utc>,
pub receipt_mode: ReceiptMode,
pub exactness_profile: ExactnessProfile,
pub request_id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub trace_id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub attempt_family_id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub attempt_id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub replay_of: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub query_text_digest: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub query_input_digest: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub filter_digest: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub redaction_state: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub budget_id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub deadline_at: Option<DateTime<Utc>>,
}
impl SearchContext {
pub fn default_now() -> Self {
Self {
evaluation_time: Utc::now(),
receipt_mode: ReceiptMode::Disabled,
exactness_profile: ExactnessProfile::Default,
request_id: None,
trace_id: None,
attempt_family_id: None,
attempt_id: None,
replay_of: None,
query_text_digest: None,
query_input_digest: None,
filter_digest: None,
redaction_state: None,
budget_id: None,
deadline_at: None,
}
}
pub fn at(evaluation_time: DateTime<Utc>) -> Self {
Self {
evaluation_time,
..Self::default_now()
}
}
pub fn receipts_enabled(&self) -> bool {
self.receipt_mode != ReceiptMode::Disabled
}
}
impl Default for SearchContext {
fn default() -> Self {
Self::default_now()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VectorSearchReceiptV1 {
#[serde(default = "default_vector_search_receipt_schema")]
pub schema_version: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub receipt_digest: Option<String>,
pub receipt_id: String,
pub evaluation_time: DateTime<Utc>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub trace_id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub attempt_family_id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub attempt_id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub replay_of: Option<String>,
pub query_embedding_digest: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub query_text_digest: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub query_input_digest: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub filter_digest: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub redaction_state: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub budget_id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub deadline_at: Option<DateTime<Utc>>,
pub search_profile: String,
pub candidate_backend: String,
pub codec_family: Option<String>,
pub codec_profile_digest: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub artifact_profile_digest: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub artifact_count: Option<usize>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub artifact_corruption_count: Option<usize>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub artifact_missing_count: Option<usize>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub vector_artifact_manifest_digest: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub artifact_generation_id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub approximate_scanned_count: Option<usize>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub approximate_returned_count: Option<usize>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub raw_rows_loaded_count: Option<usize>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub filter_strategy: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub vector_artifact_count: Option<usize>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub vector_artifact_missing_count: Option<usize>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub vector_artifact_stale_count: Option<usize>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub exact_rerank_count: Option<usize>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub approximate_candidate_count: Option<usize>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub fallback_reason: Option<String>,
pub approximate: bool,
pub requested_candidates: usize,
pub returned_candidates: usize,
pub post_filter_candidates: usize,
pub fallback: Option<String>,
pub exact_rerank: bool,
pub result_ids: Vec<String>,
pub degradations: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DerivedVectorArtifactGenerationV1 {
pub schema_version: String,
pub generation_id: String,
pub codec_family: String,
pub codec_profile_digest: String,
pub source_snapshot_digest: String,
pub source_row_count: usize,
pub artifact_count: usize,
pub source_tables: Vec<String>,
pub dim: usize,
pub encoding: String,
pub created_at: DateTime<Utc>,
pub build_receipt_id: Option<String>,
pub artifact_manifest_digest: String,
pub status: String,
pub degradations: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VectorArtifactBuildReceiptV1 {
pub schema_version: String,
pub codec_family: String,
pub codec_profile_digest: String,
pub source_row_count: usize,
pub artifact_count: usize,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub generation_id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub source_snapshot_digest: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub artifact_manifest_digest: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub build_receipt_id: Option<String>,
pub skipped_row_count: usize,
pub elapsed_ms: u128,
pub created_at: DateTime<Utc>,
pub degradations: Vec<String>,
}
fn default_vector_search_receipt_schema() -> String {
"vector_search_receipt_v1".to_string()
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SearchReceiptAnswersV1 {
pub receipt_id: String,
pub replay_receipt_id: String,
pub evaluation_time: DateTime<Utc>,
pub search_profile: String,
pub candidate_backend: String,
pub codec_family: Option<String>,
pub codec_profile_digest: Option<String>,
pub exactness: String,
pub approximate: bool,
pub exact_rerank: bool,
pub fallback: Option<String>,
pub degraded: bool,
pub replay_ready: bool,
pub rebuild_ready: bool,
pub result_ids: Vec<String>,
pub result_count: usize,
pub degradations: Vec<String>,
pub why_results_appeared: Vec<String>,
}
impl VectorSearchReceiptV1 {
pub fn answers(&self) -> SearchReceiptAnswersV1 {
let exactness = match (self.approximate, self.exact_rerank) {
(true, true) => "approximate_candidate_generation_with_exact_rerank",
(true, false) => "approximate",
(false, true) => "exact_reference_with_rerank",
(false, false) => "exact_reference",
}
.to_string();
let mut why_results_appeared = Vec::new();
why_results_appeared.push(format!(
"retrieval used candidate backend '{}'",
self.candidate_backend
));
if self.exact_rerank {
why_results_appeared.push("final vector ordering used exact f32 scoring".to_string());
}
if let Some(fallback) = &self.fallback {
why_results_appeared.push(format!("fallback path '{}' was used", fallback));
}
if let Some(codec_profile_digest) = &self.codec_profile_digest {
why_results_appeared.push(format!(
"derived vector artifacts used codec profile '{}'",
codec_profile_digest
));
} else {
why_results_appeared.push("no derived codec profile was used".to_string());
}
if let Some(query_embedding_digest) = &self.query_embedding_digest {
why_results_appeared.push(format!(
"query embedding digest '{}' is recorded for replay checks",
query_embedding_digest
));
}
SearchReceiptAnswersV1 {
receipt_id: self.receipt_id.clone(),
replay_receipt_id: self.receipt_id.clone(),
evaluation_time: self.evaluation_time,
search_profile: self.search_profile.clone(),
candidate_backend: self.candidate_backend.clone(),
codec_family: self.codec_family.clone(),
codec_profile_digest: self.codec_profile_digest.clone(),
exactness,
approximate: self.approximate,
exact_rerank: self.exact_rerank,
fallback: self.fallback.clone(),
degraded: self.fallback.is_some() || !self.degradations.is_empty(),
replay_ready: self.query_embedding_digest.is_some(),
rebuild_ready: self.query_embedding_digest.is_some()
&& self.exact_rerank
&& self.fallback.is_none()
&& (self
.vector_artifact_count
.or(self.artifact_count)
.is_some_and(|count| count > 0)
|| (self.codec_family.is_none()
&& self.candidate_backend.contains("brute_force_f32")
&& !self.result_ids.is_empty())),
result_ids: self.result_ids.clone(),
result_count: self.result_ids.len(),
degradations: self.degradations.clone(),
why_results_appeared,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SearchResponse {
pub results: Vec<SearchResult>,
pub receipt: Option<VectorSearchReceiptV1>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ChunkManifestEntry {
pub external_chunk_id: String,
pub content: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub token_count_estimate: Option<usize>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub content_digest: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub metadata: Option<serde_json::Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ChunkManifestIngestOptions {
pub title: String,
pub namespace: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub source_path: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub metadata: Option<serde_json::Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ChunkManifestChunkMapping {
pub external_chunk_id: String,
pub sm_document_id: String,
pub sm_chunk_id: String,
pub chunk_index: usize,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub content_digest: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub metadata: Option<serde_json::Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ChunkManifestIngestResult {
pub sm_document_id: String,
pub namespace: String,
pub receipt_id: String,
pub chunks: Vec<ChunkManifestChunkMapping>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ExplainedSearchResponse {
pub results: Vec<ExplainedResult>,
pub receipt: Option<VectorSearchReceiptV1>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SearchReplayReportV1 {
pub receipt_id: String,
pub replay_receipt_id: String,
pub original_receipt: VectorSearchReceiptV1,
pub replay_receipt: VectorSearchReceiptV1,
pub query_embedding_digest_matches: bool,
pub result_ids_match: bool,
pub missing_result_ids: Vec<String>,
pub added_result_ids: Vec<String>,
pub vector_only: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProjectionQuery {
pub scope: ScopeKey,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub text_query: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub valid_at: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub recorded_at_or_before: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub subject_entity_id: Option<EntityId>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub canonical_entity_id: Option<EntityId>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub claim_state: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub claim_id: Option<ClaimId>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub claim_version_id: Option<ClaimVersionId>,
pub limit: usize,
}
impl ProjectionQuery {
pub fn new(scope: ScopeKey) -> Self {
Self {
scope,
text_query: None,
valid_at: None,
recorded_at_or_before: None,
subject_entity_id: None,
canonical_entity_id: None,
claim_state: None,
claim_id: None,
claim_version_id: None,
limit: 10,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProjectionClaimVersion {
pub claim_version_id: ClaimVersionId,
pub claim_id: ClaimId,
pub claim_state: String,
pub projection_family: String,
pub subject_entity_id: EntityId,
pub predicate: String,
pub object_anchor: serde_json::Value,
pub scope_key: ScopeKey,
pub valid_from: Option<String>,
pub valid_to: Option<String>,
pub recorded_at: String,
pub preferred_open: bool,
pub source_envelope_id: EnvelopeId,
pub source_authority: String,
pub trace_id: Option<String>,
pub freshness: String,
pub contradiction_status: String,
pub supersedes_claim_version_id: Option<ClaimVersionId>,
pub content: String,
pub confidence: f32,
pub metadata: Option<serde_json::Value>,
pub source_exported_at: Option<String>,
pub transformed_at: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProjectionRelationVersion {
pub relation_version_id: RelationVersionId,
pub subject_entity_id: EntityId,
pub predicate: String,
pub object_anchor: serde_json::Value,
pub scope_key: ScopeKey,
pub claim_id: Option<ClaimId>,
pub source_episode_id: Option<EpisodeId>,
pub valid_from: Option<String>,
pub valid_to: Option<String>,
pub recorded_at: String,
pub preferred_open: bool,
pub supersedes_relation_version_id: Option<RelationVersionId>,
pub contradiction_status: String,
pub source_confidence: f32,
pub projection_family: String,
pub source_envelope_id: EnvelopeId,
pub source_authority: String,
pub trace_id: Option<String>,
pub freshness: String,
pub metadata: Option<serde_json::Value>,
pub source_exported_at: Option<String>,
pub transformed_at: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProjectionEpisode {
pub episode_id: EpisodeId,
pub document_id: String,
pub cause_ids: Vec<String>,
pub effect_type: String,
pub outcome: String,
pub confidence: f32,
pub experiment_id: Option<String>,
pub scope_key: ScopeKey,
pub source_envelope_id: EnvelopeId,
pub source_authority: String,
pub trace_id: Option<String>,
pub recorded_at: String,
pub metadata: Option<serde_json::Value>,
pub source_exported_at: Option<String>,
pub transformed_at: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProjectionEntityAlias {
pub canonical_entity_id: EntityId,
pub alias_text: String,
pub alias_source: String,
pub match_evidence: Option<serde_json::Value>,
pub confidence: f32,
pub merge_decision: String,
pub scope_key: ScopeKey,
pub review_state: String,
pub is_human_confirmed: bool,
pub is_human_confirmed_final: bool,
pub superseded_by_entity_id: Option<EntityId>,
pub split_from_entity_id: Option<EntityId>,
pub source_envelope_id: EnvelopeId,
pub recorded_at: String,
pub source_exported_at: Option<String>,
pub transformed_at: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProjectionEvidenceRef {
pub claim_id: ClaimId,
pub claim_version_id: Option<ClaimVersionId>,
pub fetch_handle: String,
pub source_authority: String,
pub source_envelope_id: EnvelopeId,
pub scope_key: ScopeKey,
pub recorded_at: String,
pub metadata: Option<serde_json::Value>,
pub source_exported_at: Option<String>,
pub transformed_at: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Session {
pub id: String,
pub channel: String,
pub created_at: String,
pub updated_at: String,
pub metadata: Option<serde_json::Value>,
pub message_count: u32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Message {
pub id: i64,
pub session_id: String,
pub role: Role,
pub content: String,
pub token_count: Option<u32>,
pub created_at: String,
pub metadata: Option<serde_json::Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Fact {
pub id: String,
pub namespace: String,
pub content: String,
pub source: Option<String>,
pub created_at: String,
pub updated_at: String,
pub metadata: Option<serde_json::Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Document {
pub id: String,
pub title: String,
pub source_path: Option<String>,
pub namespace: String,
pub created_at: String,
pub metadata: Option<serde_json::Value>,
pub chunk_count: u32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TextChunk {
pub index: usize,
pub content: String,
pub token_count_estimate: usize,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SearchResult {
pub content: String,
pub source: SearchSource,
pub score: f64,
pub bm25_rank: Option<usize>,
pub vector_rank: Option<usize>,
pub cosine_similarity: Option<f64>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum SearchSource {
Fact {
fact_id: String,
namespace: String,
},
Chunk {
chunk_id: String,
document_id: String,
document_title: String,
chunk_index: usize,
},
Message {
message_id: i64,
session_id: String,
role: String,
},
Episode {
episode_id: String,
document_id: String,
effect_type: String,
outcome: String,
},
Projection {
projection_kind: String,
projection_id: String,
scope_key: ScopeKey,
valid_from: Option<String>,
valid_to: Option<String>,
recorded_at: String,
source_envelope_id: String,
source_authority: String,
},
}
impl SearchSource {
pub fn result_id(&self) -> String {
match self {
Self::Fact { fact_id, .. } => format!("fact:{fact_id}"),
Self::Chunk { chunk_id, .. } => format!("chunk:{chunk_id}"),
Self::Message { message_id, .. } => format!("msg:{message_id}"),
Self::Episode { episode_id, .. } => format!("episode:{episode_id}"),
Self::Projection { projection_id, .. } => format!("projection:{projection_id}"),
}
}
pub fn source_kind(&self) -> &'static str {
match self {
Self::Fact { .. } => "fact",
Self::Chunk { .. } => "chunk",
Self::Message { .. } => "message",
Self::Episode { .. } => "episode",
Self::Projection { .. } => "projection",
}
}
pub fn source_id(&self) -> String {
match self {
Self::Fact { fact_id, .. } => fact_id.clone(),
Self::Chunk { chunk_id, .. } => chunk_id.clone(),
Self::Message { message_id, .. } => message_id.to_string(),
Self::Episode { episode_id, .. } => episode_id.clone(),
Self::Projection { projection_id, .. } => projection_id.clone(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EpisodeMeta {
pub cause_ids: Vec<String>,
pub effect_type: String,
pub outcome: EpisodeOutcome,
pub confidence: f32,
pub verification_status: VerificationStatus,
pub experiment_id: Option<String>,
pub valid_time: Option<chrono::DateTime<chrono::Utc>>,
pub fact_digest: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EpisodeAsOfReceiptV1 {
pub query_id: String,
pub as_of_valid: chrono::DateTime<chrono::Utc>,
pub as_of_recorded: chrono::DateTime<chrono::Utc>,
pub episode_count: usize,
pub episode_ids: Vec<String>,
pub excluded_superseded: usize,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum EpisodeOutcome {
Confirmed,
Refuted,
Inconclusive,
Pending,
}
impl EpisodeOutcome {
pub fn as_str(&self) -> &'static str {
match self {
Self::Confirmed => "confirmed",
Self::Refuted => "refuted",
Self::Inconclusive => "inconclusive",
Self::Pending => "pending",
}
}
pub fn from_str_value(s: &str) -> Option<Self> {
match s {
"confirmed" => Some(Self::Confirmed),
"refuted" => Some(Self::Refuted),
"inconclusive" => Some(Self::Inconclusive),
"pending" => Some(Self::Pending),
_ => None,
}
}
}
impl std::fmt::Display for EpisodeOutcome {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "status", rename_all = "lowercase")]
pub enum VerificationStatus {
Unverified,
Verified {
method: String,
at: String,
},
Failed {
reason: String,
at: String,
},
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ScoreBreakdown {
pub rrf_score: f64,
pub bm25_score: Option<f64>,
pub vector_score: Option<f64>,
pub recency_score: Option<f64>,
pub bm25_rank: Option<usize>,
pub vector_rank: Option<usize>,
pub vector_source_rank: Option<usize>,
pub vector_source_score: Option<f64>,
pub bm25_contribution: Option<f64>,
pub vector_contribution: Option<f64>,
pub vector_reranked_from_f32: bool,
pub bm25_weight: f64,
pub vector_weight: f64,
pub recency_weight: Option<f64>,
pub rrf_k: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ExplainedResult {
pub result: SearchResult,
pub breakdown: ScoreBreakdown,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ExplainedResultAnswerV1 {
pub result_id: String,
pub source_kind: String,
pub source_id: String,
pub why_this_result: Vec<String>,
pub text_match: bool,
pub vector_match: bool,
pub recency_applied: bool,
pub exact_vector_rerank: bool,
pub final_score: f64,
}
impl ExplainedResult {
pub fn answer(&self) -> ExplainedResultAnswerV1 {
let text_match = self.breakdown.bm25_rank.is_some();
let vector_match = self.breakdown.vector_rank.is_some();
let recency_applied = self.breakdown.recency_score.is_some();
let mut why_this_result = Vec::new();
if let Some(rank) = self.breakdown.bm25_rank {
why_this_result.push(format!("text match rank {rank} contributed to fusion"));
}
if let Some(rank) = self.breakdown.vector_rank {
why_this_result.push(format!("vector match rank {rank} contributed to fusion"));
}
if recency_applied {
why_this_result.push("recency contributed to the fused score".to_string());
}
if self.breakdown.vector_reranked_from_f32 {
why_this_result.push("vector score was checked with exact f32 rerank".to_string());
}
if why_this_result.is_empty() {
why_this_result.push("result survived filtering and deterministic ranking".to_string());
}
ExplainedResultAnswerV1 {
result_id: self.result.source.result_id(),
source_kind: self.result.source.source_kind().to_string(),
source_id: self.result.source.source_id(),
why_this_result,
text_match,
vector_match,
recency_applied,
exact_vector_rerank: self.breakdown.vector_reranked_from_f32,
final_score: self.result.score,
}
}
}
pub trait GraphView: Send + Sync {
fn neighbors(
&self,
node_id: &str,
direction: GraphDirection,
max_depth: usize,
) -> Result<Vec<GraphEdge>, MemoryError>;
fn path(
&self,
from: &str,
to: &str,
max_depth: usize,
) -> Result<Option<Vec<String>>, MemoryError>;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum GraphDirection {
Outgoing,
Incoming,
Both,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GraphEdge {
pub source: String,
pub target: String,
pub edge_type: GraphEdgeType,
pub weight: f64,
pub metadata: Option<serde_json::Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum GraphEdgeType {
Semantic {
cosine_similarity: f32,
},
Temporal {
delta_secs: u64,
},
Causal {
confidence: f32,
evidence_ids: Vec<String>,
},
Entity {
relation: String,
},
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EmbeddingDisplacement {
pub cosine_similarity: f32,
pub euclidean_distance: f32,
pub magnitude_a: f32,
pub magnitude_b: f32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MemoryStats {
pub total_facts: u64,
pub total_documents: u64,
pub total_chunks: u64,
pub total_sessions: u64,
pub total_messages: u64,
pub database_size_bytes: u64,
pub embedding_model: Option<String>,
pub embedding_dimensions: Option<usize>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
pub struct NamespaceDeleteReport {
pub facts: usize,
pub documents: usize,
pub chunks: usize,
pub messages: usize,
pub sessions: usize,
pub episodes: usize,
pub projection_rows: usize,
pub hnsw_ops: usize,
}