use std::collections::HashMap;
use anyhow::Result;
use serde::Deserialize;
use surrealdb::types::SurrealValue;
use tracing::debug;
use post_cortex_embeddings::text::tokens;
use post_cortex_embeddings::{Corpus, MESSAGE_CONTENT_TYPE, SearchMatch, VectorMetadata};
use super::SurrealDBStorage;
const TEXT_SEARCH_TIMEOUT: &str = "500ms";
#[derive(Debug, Deserialize, SurrealValue)]
struct TextHit {
content_id: String,
session_id: String,
text: String,
content_type: String,
timestamp: String,
metadata: HashMap<String, String>,
#[allow(dead_code)]
score: f32,
similarity: f32,
}
impl SurrealDBStorage {
pub(super) async fn text_search(
&self,
query: &str,
query_vector: &[f32],
k: usize,
sources: &[String],
corpus: Corpus,
) -> Result<Vec<SearchMatch>> {
let terms = tokens(query).join(" ");
if terms.is_empty() || k == 0 {
return Ok(Vec::new());
}
let mut scope = String::new();
if !sources.is_empty() {
scope.push_str("AND session_id IN $sources ");
}
match corpus {
Corpus::All => {}
Corpus::Records => scope.push_str("AND content_type != $message "),
Corpus::Messages => scope.push_str("AND content_type = $message "),
}
let sql = format!(
"SELECT content_id, session_id, text, content_type, timestamp, metadata, \
search::score(1) AS score, \
IF array::len(vector) = array::len($qv) \
THEN vector::similarity::cosine(vector, $qv) ELSE 0 END AS similarity \
FROM embedding WHERE text @1,OR@ $terms {scope}\
ORDER BY score DESC LIMIT $k TIMEOUT {TEXT_SEARCH_TIMEOUT}"
);
let mut response = self
.db
.query(sql)
.bind(("terms", terms))
.bind(("qv", query_vector.to_vec()))
.bind(("sources", sources.to_vec()))
.bind(("message", MESSAGE_CONTENT_TYPE))
.bind(("k", k as i64))
.await?
.check()?;
let hits: Vec<TextHit> = response.take(0)?;
debug!("SurrealDBStorage: BM25 found {} matches", hits.len());
Ok(hits
.into_iter()
.map(|h| SearchMatch {
vector_id: 0,
similarity: h.similarity,
metadata: VectorMetadata {
id: h.content_id,
text: h.text,
source: h.session_id,
content_type: h.content_type,
timestamp: Self::parse_datetime(&h.timestamp),
metadata: h.metadata,
},
})
.collect())
}
}