Files
searchi/fixtures/mixed/sources/crates__searchi-service__src__routing.rs
T

219 lines
6.5 KiB
Rust

use super::*;
/// Explicit semantic source channel, independent of filename/type filters.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SemanticOrigin {
All,
Text,
Image,
Video,
Audio,
}
impl SemanticOrigin {
fn source_type(self) -> Option<&'static str> {
match self {
Self::All => None,
Self::Text => Some("text"),
Self::Image => Some("image"),
Self::Video => Some("video"),
Self::Audio => Some("audio"),
}
}
}
/// A conservative intent hint, not a language understanding model. Explicit
/// document/visual cues veto it; ordinary topic and instrument queries do not
/// silently restrict modality. Word boundaries avoid filename substrings.
fn preferred_origin(query: &str) -> Option<SemanticOrigin> {
if query.split_whitespace().count() == 1 && Path::new(query).extension().is_some() {
return None;
}
let lower = query.to_lowercase();
let words: Vec<_> = lower
.split(|c: char| !c.is_alphanumeric())
.filter(|s| !s.is_empty())
.collect();
let has = |values: &[&str]| words.iter().any(|word| values.contains(word));
if has(&[
"image",
"photo",
"picture",
"screenshot",
"diagram",
"document",
"pdf",
"text",
"paper",
"bild",
"foto",
"screenshot",
"dokument",
"textdatei",
]) {
return None;
}
if has(&[
"audio",
"spoken",
"speech",
"recording",
"conversation",
"interview",
"podcast",
"sound",
"music",
"gesprochen",
"gespräch",
"gespraech",
"geräusch",
"geraeusch",
"musik",
"hörbar",
"hörbarer",
"hörbare",
"hoerbar",
"hoerbarer",
"hoerbare",
"tonaufnahme",
]) {
return Some(SemanticOrigin::Audio);
}
if (has(&["someone", "speaker"])
&& has(&[
"mentions",
"mentioned",
"says",
"said",
"speaks",
"speaking",
]))
|| (has(&["jemand", "sprecher", "sprecherin"])
&& has(&["erwähnt", "erwaehnt", "sagt", "gesagt", "spricht"]))
{
return Some(SemanticOrigin::Audio);
}
None
}
impl HybridSearch {
pub(super) fn routed_semantic(
&mut self,
query: &str,
vector: &[f32],
limit: usize,
options: &SearchOptions,
) -> Result<Vec<SearchResult>> {
let mut trace = TraceCollector::new(MetricsMode::Basic);
self.routed_semantic_traced(query, vector, limit, options, &mut trace, None)
}
pub(super) fn routed_semantic_traced(
&mut self,
query: &str,
vector: &[f32],
limit: usize,
options: &SearchOptions,
trace: &mut TraceCollector,
parent: Option<SpanToken>,
) -> Result<Vec<SearchResult>> {
trace.timed("semantic.routing", parent, |trace, parent| {
if let Some(origin) = if options.code_only {
None
} else {
preferred_origin(query)
} {
trace.counter(parent, "audio_preference", 1);
let preferred = self.store.search_origin_traced(
vector,
&self.generation_fingerprint,
limit,
options,
origin.source_type(),
trace,
parent,
)?;
if !preferred.is_empty() {
return Ok(preferred);
}
}
trace.counter(parent, "general_lane", 1);
self.store.search_origin_traced(
vector,
&self.generation_fingerprint,
limit,
options,
None,
trace,
parent,
)
})
}
/// Query one actual source channel, or All to bypass automatic spoken/audio
/// preference. This never embeds files again or changes an active policy.
pub fn search_semantic_with_origin(
&mut self,
query: &str,
limit: usize,
options: &SearchOptions,
origin: SemanticOrigin,
) -> Result<Vec<SearchResult>> {
let _ = self.lexical.search_with_options(query, 0, options)?;
if query.trim().is_empty() || limit == 0 {
return Ok(Vec::new());
}
let mut trace = TraceCollector::new(MetricsMode::Basic);
let vector = self.query_vector(query, options.code_only, &mut trace, None)?;
self.store.search_origin(
&vector,
&self.generation_fingerprint,
limit.min(1000),
options,
origin.source_type(),
)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn persisted_custom_limits_require_identical_model_and_preprocessing() {
let expected = "pinned-model:pinned-media:preprocessing-v1";
assert!(reusable_policy(
expected,
&format!("{expected}:custom-240-2000-120-10000-2000-1800-14400000-17179869184")
));
for active in ["other-model:pinned-media:preprocessing-v1:custom-240-2000-120-10000-2000-1800-14400000-17179869184","pinned-model:pinned-media:preprocessing-v2:custom-240-2000-120-10000-2000-1800-14400000-17179869184","pinned-model:pinned-media:preprocessing-v1:custom-unknown"] {assert!(!reusable_policy(expected,active));}
}
#[test]
fn explicit_spoken_intent_is_conservative_and_bilingual() {
for query in [
"where someone mentions a contract",
"a speaker says the date",
"spoken discussion of ecology",
"audio of a waterfall",
"jemand erwähnt das Budget",
"Wo spricht jemand über Kündigung?",
"Gespräch über die neue Wohnung",
"hörbarer Regen",
] {
assert_eq!(
preferred_origin(query),
Some(SemanticOrigin::Audio),
"{query}"
);
}
for query in [
"contract deadline",
"violin solo",
"photo of someone speaking",
"Bild von einem Sprecher",
"a document that someone mentioned",
"speech.png",
"a screenshot of an audio waveform",
] {
assert_eq!(preferred_origin(query), None, "{query}");
}
}
}