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 { 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> { 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, ) -> Result> { 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> { 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}"); } } }