Skip to main content

khive_runtime/
note_search_ann.rs

1//! Pack-owned ANN candidate source for the note-substrate search vector leg.
2//!
3//! The runtime owns this neutral seam so its note search does not depend on a
4//! higher-layer pack. The memory pack installs the provider for its backend.
5
6use std::sync::atomic::{AtomicU64, Ordering};
7
8use async_trait::async_trait;
9use khive_storage::types::VectorSearchHit;
10
11use crate::{KhiveRuntime, NamespaceToken, RuntimeResult};
12
13static ANN_ROUTE_TOTAL: AtomicU64 = AtomicU64::new(0);
14static FALLBACK_ROUTE_TOTAL: AtomicU64 = AtomicU64::new(0);
15
16/// An installed graph candidate source for one database backend.
17///
18/// `None` means this model has no graph this consumer may serve and licenses the
19/// existing exact sqlite-vec fallback. Errors propagate rather than silently
20/// changing route. Implementations merge the registered consumer's fresh tail
21/// before returning candidates.
22#[async_trait]
23pub trait NoteSearchAnnProvider: Send + Sync {
24    /// Backend identity whose note vectors the graph indexes.
25    fn backend_id(&self) -> &str;
26
27    /// Match the actual opened backend, not only its logical name. Two
28    /// independently opened databases may both have the default `main` ID.
29    fn serves_backend(&self, runtime: &KhiveRuntime) -> bool;
30
31    /// Return ANN plus fresh-tail candidates in the same score and ordering
32    /// contract as `VectorStore::search`.
33    async fn search(
34        &self,
35        token: &NamespaceToken,
36        model: &str,
37        query_embedding: &[f32],
38        top_k: u32,
39    ) -> RuntimeResult<Option<Vec<VectorSearchHit>>>;
40}
41
42fn increment(counter: &AtomicU64) {
43    let _ = counter.fetch_update(Ordering::Relaxed, Ordering::Relaxed, |value| {
44        Some(value.saturating_add(1))
45    });
46}
47
48pub(crate) fn record_ann_route() {
49    increment(&ANN_ROUTE_TOTAL);
50}
51
52pub(crate) fn record_fallback_route() {
53    increment(&FALLBACK_ROUTE_TOTAL);
54}
55
56/// Process-lifetime note-search vector route counts. The pair does not reset
57/// when a database pool is reopened or diagnostics is collected.
58pub fn route_totals() -> (u64, u64) {
59    (
60        ANN_ROUTE_TOTAL.load(Ordering::Relaxed),
61        FALLBACK_ROUTE_TOTAL.load(Ordering::Relaxed),
62    )
63}