From 598b134aefe368e9a35f5ccfde2aa6eda3a6fbc7 Mon Sep 17 00:00:00 2001 From: Dens Sumesh Date: Sat, 7 Dec 2024 18:08:15 -0800 Subject: [PATCH] feature: add hallucination detection to server --- .../components/SingleRagInfo/index.tsx | 22 +- .../pages/tablePages/RAGAnalyticsPage.tsx | 4 + frontends/shared/types.ts | 2 + hallucination-detection/Cargo.lock | 2 +- hallucination-detection/Cargo.toml | 16 +- hallucination-detection/README.md | 23 +- .../examples/rag_truth_test.rs | 2 +- .../examples/vectara_test.rs | 3 +- hallucination-detection/src/lib.rs | 83 +- pdf2md/server/Cargo.lock | 8 +- server/Cargo.lock | 754 +++++++++++++++--- server/Cargo.toml | 16 +- server/Dockerfile.server | 10 +- server/build.rs | 126 +++ .../down.sql | 5 + .../1733613844_add_hallucination_score/up.sql | 3 + server/src/data/models.rs | 20 + server/src/handlers/chunk_handler.rs | 63 ++ server/src/handlers/message_handler.rs | 24 + server/src/lib.rs | 15 + server/src/operators/message_operator.rs | 107 ++- 21 files changed, 1165 insertions(+), 143 deletions(-) create mode 100644 server/ch_migrations/1733613844_add_hallucination_score/down.sql create mode 100644 server/ch_migrations/1733613844_add_hallucination_score/up.sql diff --git a/frontends/dashboard/src/analytics/components/SingleRagInfo/index.tsx b/frontends/dashboard/src/analytics/components/SingleRagInfo/index.tsx index 3fc5cb1e5e..d4ab3cc7d8 100644 --- a/frontends/dashboard/src/analytics/components/SingleRagInfo/index.tsx +++ b/frontends/dashboard/src/analytics/components/SingleRagInfo/index.tsx @@ -78,7 +78,7 @@ export const SingleRAGQuery = (props: SingleRAGQueryProps) => { "M/d/yy h:mm a", )} -
+
{ value={props.search_data?.top_score.toPrecision(4) ?? "N/A"} /> + + + { + 0 + } + > + +
    +
  • {props.rag_data.detected_hallucinations?.join(",")}
  • +
+
+
{ ); }, }, + { + accessorKey: "hallucination_score", + header: "Hallucination Score", + }, { accessorKey: "query_rating", header: "Query Rating", diff --git a/frontends/shared/types.ts b/frontends/shared/types.ts index 175e5a882a..be10e68d70 100644 --- a/frontends/shared/types.ts +++ b/frontends/shared/types.ts @@ -634,6 +634,8 @@ export interface RagQueryEvent { note?: string; rating: number; }; + hallucination_score?: number; + detected_hallucinations?: string[]; } export interface EventData { diff --git a/hallucination-detection/Cargo.lock b/hallucination-detection/Cargo.lock index 81d0526ff3..1f07f95e0c 100644 --- a/hallucination-detection/Cargo.lock +++ b/hallucination-detection/Cargo.lock @@ -739,7 +739,7 @@ dependencies = [ [[package]] name = "hallucination-detection" -version = "0.1.3" +version = "0.1.5" dependencies = [ "csv", "dotenvy", diff --git a/hallucination-detection/Cargo.toml b/hallucination-detection/Cargo.toml index 7266b3a597..4587762a1d 100644 --- a/hallucination-detection/Cargo.toml +++ b/hallucination-detection/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "hallucination-detection" -version = "0.1.3" +version = "0.1.5" edition = "2021" license = "MIT" repository = "https://github.com/devflowinc/trieve" @@ -11,9 +11,10 @@ description = "Extremely fast Hallucination Detection for RAG using BERT NER, no inherits = "release" [features] -default = [] +default = ["ner"] ner = ["rust-bert"] -onnx = ["ort"] +download-onnx = ["ort?/download-binaries"] +onnx = ["ort", "rust-bert?/onnx"] [dependencies] # Core dependencies @@ -22,15 +23,14 @@ regex = "1.11.1" serde = { version = "1.0.215", features = ["derive"] } tokio = { version = "1.42.0", features = ["full"] } once_cell = "1.18" - -# Optional dependencies for NER feature -rust-bert = { version = "0.23.0", features = ["onnx"], optional = true } +rust-bert = { version = "0.23.0", optional = true } ort = { version = "1.16.3", features = [ - "download-binaries", "load-dynamic", -], optional = true } +], optional = true, default-features = false } + [dev-dependencies] +ort = { version = "1.16.3", features = ["download-binaries", "load-dynamic"] } csv = "1.3.1" dotenvy = "0.15.7" openai_dive = "0.7.0" diff --git a/hallucination-detection/README.md b/hallucination-detection/README.md index dd47bee0e7..ffb7e4b6ba 100644 --- a/hallucination-detection/README.md +++ b/hallucination-detection/README.md @@ -25,11 +25,32 @@ Add this to your `Cargo.toml`: hallucination-detection = "^0.1.3" ``` -If you want to use NER and ONNX features: +If you want to use NER: + +1. Download `libtorch` from . This package requires `v2.4`: if this version is no longer available on the "get started" page, the file should be accessible by modifying the target link, for example `https://download.pytorch.org/libtorch/cpu/libtorch-cxx11-abi-shared-with-deps-2.4.0%2Bcpu.zip` for a Linux version with CPU. +2. Extract the library to a location of your choice +3. Set the following environment variables +##### Linux: +```bash +export LIBTORCH=/path/to/libtorch +export LD_LIBRARY_PATH=${LIBTORCH}/lib:$LD_LIBRARY_PATH +``` +##### Windows +```powershell +$Env:LIBTORCH = "X:\path\to\libtorch" +$Env:Path += ";X:\path\to\libtorch\lib" +``` ```toml [dependencies] +hallucination-detection = { version = "^0.1.3", features = ["ner"] } +``` + +If you want to use ONNX for the NER models, you need to either [install the ort runtime](https://docs.rs/ort/1.16.3/ort/#how-to-get-binaries) or include it in your dependencies: + +```toml hallucination-detection = { version = "^0.1.3", features = ["ner", "onnx"] } +ort = { version = "...", features = [ "download-binaries" ] } ``` ## Quick Start diff --git a/hallucination-detection/examples/rag_truth_test.rs b/hallucination-detection/examples/rag_truth_test.rs index dfcea04159..2edf79d130 100644 --- a/hallucination-detection/examples/rag_truth_test.rs +++ b/hallucination-detection/examples/rag_truth_test.rs @@ -131,7 +131,7 @@ async fn run_hallucination_test() -> Result<(), Box> { let start = std::time::Instant::now(); let hallucination_score = detector .detect_hallucinations(&record.response, &[source_info.clone()]) - .await; + .await.unwrap(); let elapsed = start.elapsed(); println!("Hallucination detection took: {:?}", elapsed); diff --git a/hallucination-detection/examples/vectara_test.rs b/hallucination-detection/examples/vectara_test.rs index 241b02be40..9c2ecc0a1a 100644 --- a/hallucination-detection/examples/vectara_test.rs +++ b/hallucination-detection/examples/vectara_test.rs @@ -88,7 +88,8 @@ async fn run_hallucination_test() -> Result<(), Box> { let start = std::time::Instant::now(); let hallucination_score = detector .detect_hallucinations(&record.og_sum, &references) - .await; + .await + .unwrap(); let elapsed = start.elapsed(); println!("Hallucination detection took: {:?}", elapsed); diff --git a/hallucination-detection/src/lib.rs b/hallucination-detection/src/lib.rs index 595f80f2cf..09f487df9f 100644 --- a/hallucination-detection/src/lib.rs +++ b/hallucination-detection/src/lib.rs @@ -7,21 +7,32 @@ use std::{ }; use tokio::sync::OnceCell; +#[cfg(not(feature = "ner"))] +#[cfg(feature = "onnx")] +compile_error!("NER feature must be enabled to use ONNX model"); + #[cfg(feature = "ner")] use { rust_bert::{ - pipelines::{ - common::{ModelResource, ModelType, ONNXModelResources}, - ner::{Entity, NERModel}, - token_classification::{LabelAggregationOption, TokenClassificationConfig}, - }, - resources::RemoteResource, + pipelines::ner::{Entity, NERModel}, + pipelines::token_classification::TokenClassificationConfig, RustBertError, }, + std::error::Error, std::sync::mpsc, tokio::{sync::oneshot, task::JoinHandle}, }; +#[cfg(feature = "onnx")] +use rust_bert::{ + pipelines::{ + common::ONNXModelResources, + common::{ModelResource, ModelType}, + token_classification::LabelAggregationOption, + }, + resources::RemoteResource, +}; + const WORDS_URL: &str = "https://raw.githubusercontent.com/dwyl/english-words/refs/heads/master/words.txt"; const CACHE_FILE: &str = "~/.cache/hallucination-detection/english_words_cache.txt"; @@ -72,6 +83,18 @@ pub struct HallucinationScore { pub detected_hallucinations: Vec, } +#[derive(Debug)] +#[allow(dead_code)] +pub struct DetectorError { + message: String, +} + +impl std::fmt::Display for DetectorError { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "Detector Error: {}", self.message) + } +} + #[derive(Debug, Clone)] pub struct ScoreWeights { pub proper_noun_weight: f64, @@ -201,13 +224,20 @@ impl HallucinationDetector { &self, llm_output: &String, references: &[String], - ) -> HallucinationScore { + ) -> Result { let mut all_texts = vec![llm_output.to_string()]; all_texts.extend(references.iter().cloned()); - let all_analyses = self.analyze_text(&all_texts).await; + let all_analyses = self.analyze_text(&all_texts).await?; - let (output_analysis, ref_analyses) = all_analyses.split_first().unwrap(); + let (output_analysis, ref_analyses) = match all_analyses.split_first() { + Some((output_analysis, ref_analyses)) => (output_analysis, ref_analyses), + None => { + return Err(DetectorError { + message: "Failed to analyze text".to_string(), + }); + } + }; let all_ref_proper_nouns: HashSet<_> = ref_analyses .iter() @@ -250,7 +280,7 @@ impl HallucinationDetector { + number_mismatch_score * self.options.weights.number_mismatch_weight) .clamp(0.0, 1.0); - HallucinationScore { + Ok(HallucinationScore { proper_noun_score, unknown_word_score, number_mismatch_score, @@ -261,14 +291,19 @@ impl HallucinationDetector { number_diff.iter().map(|n| n.to_string()).collect(), ] .concat(), - } + }) } #[allow(unused_variables)] - async fn analyze_text(&self, texts: &[String]) -> Vec { + async fn analyze_text(&self, texts: &[String]) -> Result, DetectorError> { #[cfg(feature = "ner")] let entities = if let Some(ner_model) = &self.ner_model { - ner_model.predict(texts.to_vec()).await.unwrap() + ner_model + .predict(texts.to_vec()) + .await + .map_err(|e| DetectorError { + message: format!("Failed to predict entities: {:?}", e), + })? } else { vec![Vec::new(); texts.len()] }; @@ -322,11 +357,11 @@ impl HallucinationDetector { true }); } - TextAnalysis { + Ok(TextAnalysis { proper_nouns, unknown_words, numbers, - } + }) }) .collect() } @@ -381,7 +416,8 @@ mod tests { let score = detector .detect_hallucinations(&llm_output, &references) - .await; + .await + .unwrap(); println!("Zero Hallucination Score: {:?}", score); assert_eq!(score.proper_noun_score, 0.0); @@ -402,7 +438,8 @@ mod tests { let score = detector .detect_hallucinations(&llm_output, &references) - .await; + .await + .unwrap(); println!("Multiple References Score: {:?}", score); assert_eq!(score.proper_noun_score, 0.0); // Both companies are in references assert_eq!(score.number_mismatch_score, 0.0); // Number matches reference @@ -415,13 +452,15 @@ mod tests { // Empty input let score_empty = detector .detect_hallucinations(&String::from(""), &[String::from("")]) - .await; + .await + .unwrap(); assert_eq!(score_empty.total_score, 0.0); // Only numbers let score_numbers = detector .detect_hallucinations(&String::from("123 456.789"), &[String::from("123 456.789")]) - .await; + .await + .unwrap(); assert_eq!(score_numbers.number_mismatch_score, 0.0); // Only proper nouns @@ -430,7 +469,8 @@ mod tests { &String::from("John Smith"), &[String::from("Different Person")], ) - .await; + .await + .unwrap(); assert!(score_nouns.proper_noun_score > 0.0); } @@ -508,7 +548,8 @@ mod tests { &String::from(llm_output), &references.into_iter().map(String::from).collect::>(), ) - .await; + .await + .unwrap(); println!("Test '{}' Score: {:?}", test_name, score); diff --git a/pdf2md/server/Cargo.lock b/pdf2md/server/Cargo.lock index 9265c0b9a3..2d991e77ce 100644 --- a/pdf2md/server/Cargo.lock +++ b/pdf2md/server/Cargo.lock @@ -1178,9 +1178,9 @@ dependencies = [ [[package]] name = "flate2" -version = "1.0.34" +version = "1.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1b589b4dc103969ad3cf85c950899926ec64300a1a46d76c03a6072957036f0" +checksum = "c936bfdafb507ebbf50b8074c54fa31c5be9a1e7e5f467dd659697041407d07c" dependencies = [ "crc32fast", "miniz_oxide", @@ -2838,9 +2838,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.16" +version = "0.23.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eee87ff5d9b36712a58574e12e9f0ea80f915a5b0ac518d322b24a465617925e" +checksum = "934b404430bb06b3fae2cba809eb45a1ab1aecd64491213d7c3301b88393f8d1" dependencies = [ "once_cell", "ring", diff --git a/server/Cargo.lock b/server/Cargo.lock index 9e60b50224..d9376e6b2f 100644 --- a/server/Cargo.lock +++ b/server/Cargo.lock @@ -118,7 +118,7 @@ dependencies = [ "tokio", "tokio-util", "tracing", - "zstd", + "zstd 0.13.2", ] [[package]] @@ -144,7 +144,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e01ed3140b2f8d422c68afa1ed2e85d996ea619c988ac834d255db32138655cb" dependencies = [ "quote", - "syn 2.0.85", + "syn 2.0.90", ] [[package]] @@ -183,7 +183,7 @@ dependencies = [ "actix-utils", "futures-core", "futures-util", - "mio", + "mio 0.8.11", "socket2", "tokio", "tracing", @@ -279,7 +279,7 @@ dependencies = [ "actix-router", "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.90", ] [[package]] @@ -290,7 +290,7 @@ checksum = "7c7db3d5a9718568e4cf4a537cfd7070e6e6ff7481510d0237fb529ac850f6d3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.90", ] [[package]] @@ -308,6 +308,12 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "adler2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" + [[package]] name = "aead" version = "0.5.2" @@ -513,7 +519,7 @@ checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" dependencies = [ "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.90", ] [[package]] @@ -536,7 +542,7 @@ dependencies = [ "sha2", "smart-default", "smol_str", - "thiserror", + "thiserror 1.0.65", "tokio", "uuid 0.8.2", ] @@ -549,7 +555,7 @@ checksum = "6e0c28dcc82d7c8ead5cb13beb15405b57b8546e93215673ff8ca0349a028107" dependencies = [ "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.90", ] [[package]] @@ -590,7 +596,7 @@ dependencies = [ "quick-xml", "rust-ini", "serde", - "thiserror", + "thiserror 1.0.65", "time", "url", ] @@ -601,7 +607,7 @@ version = "0.25.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42fed2b9fca70f2908268d057a607f2a906f47edbf856ea8587de9038d264e22" dependencies = [ - "thiserror", + "thiserror 1.0.65", ] [[package]] @@ -661,7 +667,7 @@ dependencies = [ "cc", "cfg-if", "libc", - "miniz_oxide", + "miniz_oxide 0.7.4", "object", "rustc-demangle", ] @@ -763,7 +769,7 @@ checksum = "23285ad32269793932e830392f2fe2f83e26488fd3ec778883a93c8323735780" dependencies = [ "arrayref", "arrayvec", - "constant_time_eq", + "constant_time_eq 0.3.1", ] [[package]] @@ -776,7 +782,7 @@ dependencies = [ "arrayvec", "cc", "cfg-if", - "constant_time_eq", + "constant_time_eq 0.3.1", ] [[package]] @@ -848,6 +854,49 @@ dependencies = [ "bytes", ] +[[package]] +name = "bzip2" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdb116a6ef3f6c3698828873ad02c3014b3c85cadb88496095628e3ef1e347f8" +dependencies = [ + "bzip2-sys", + "libc", +] + +[[package]] +name = "bzip2-sys" +version = "0.1.11+1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "736a955f3fa7875102d57c82b8cac37ec45224a07fd32d58f9f7a186b6cd4cdc" +dependencies = [ + "cc", + "libc", + "pkg-config", +] + +[[package]] +name = "cached-path" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "097968e38f1319207f057d0f4d76452e4f4f847a5de61c5215379f297fa034f3" +dependencies = [ + "flate2", + "fs2", + "glob", + "indicatif", + "log", + "rand 0.8.5", + "reqwest 0.11.27", + "serde", + "serde_json", + "sha2", + "tar", + "tempfile", + "thiserror 1.0.65", + "zip 0.6.6", +] + [[package]] name = "cc" version = "1.1.0" @@ -954,7 +1003,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.90", ] [[package]] @@ -980,7 +1029,7 @@ dependencies = [ "sealed 0.4.0", "serde", "static_assertions", - "thiserror", + "thiserror 1.0.65", "time", "tokio", "url", @@ -1005,7 +1054,7 @@ dependencies = [ "sealed 0.5.0", "serde", "static_assertions", - "thiserror", + "thiserror 1.0.65", "time", "tokio", "url", @@ -1033,7 +1082,7 @@ dependencies = [ "proc-macro2", "quote", "serde_derive_internals 0.29.1", - "syn 2.0.85", + "syn 2.0.90", ] [[package]] @@ -1074,6 +1123,18 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "console" +version = "0.15.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e1f83fc076bd6dd27517eacdf25fef6c4dfe5f1d7448bafaaf3a26f13b5e4eb" +dependencies = [ + "encode_unicode", + "lazy_static", + "libc", + "windows-sys 0.52.0", +] + [[package]] name = "const-oid" version = "0.9.6" @@ -1102,9 +1163,15 @@ dependencies = [ [[package]] name = "constant_time_eq" -version = "0.3.0" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" + +[[package]] +name = "constant_time_eq" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7144d30dcf0fafbce74250a3963025d8d52177934239851c917d29f1df280c2" +checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" [[package]] name = "convert_case" @@ -1155,6 +1222,21 @@ dependencies = [ "libc", ] +[[package]] +name = "crc" +version = "3.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69e6e4d7b33a94f0991c26729976b10ebde1d34c3ee82408fb536164fa10d636" +dependencies = [ + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" + [[package]] name = "crc32fast" version = "1.4.2" @@ -1269,7 +1351,28 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" dependencies = [ "quote", - "syn 2.0.85", + "syn 2.0.90", +] + +[[package]] +name = "csv" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acdc4883a9c96732e4733212c01447ebd805833b7275a73ca3ee080fd77afdaf" +dependencies = [ + "csv-core", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "csv-core" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5efa2b3d7902f4b634a20cae3c9c4e6209dc4779feb6863329607560143efa70" +dependencies = [ + "memchr", ] [[package]] @@ -1305,7 +1408,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.90", ] [[package]] @@ -1329,7 +1432,7 @@ dependencies = [ "proc-macro2", "quote", "strsim 0.11.1", - "syn 2.0.85", + "syn 2.0.90", ] [[package]] @@ -1340,7 +1443,7 @@ checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" dependencies = [ "darling_core", "quote", - "syn 2.0.85", + "syn 2.0.90", ] [[package]] @@ -1388,6 +1491,12 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "092966b41edc516079bdf31ec78a2e0588d1d0c08f78b91d8307215928642b2b" +[[package]] +name = "deflate64" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da692b8d1080ea3045efaab14434d40468c3d8657e42abddfffca87b428f4c1b" + [[package]] name = "der" version = "0.7.9" @@ -1417,7 +1526,7 @@ checksum = "67e77553c4162a157adbf834ebae5b415acbecbeafc7a74b0e886657506a7611" dependencies = [ "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.90", ] [[package]] @@ -1438,7 +1547,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.90", ] [[package]] @@ -1448,7 +1557,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" dependencies = [ "derive_builder_core", - "syn 2.0.85", + "syn 2.0.90", ] [[package]] @@ -1461,7 +1570,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version", - "syn 2.0.85", + "syn 2.0.90", ] [[package]] @@ -1481,7 +1590,7 @@ checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" dependencies = [ "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.90", "unicode-xid", ] @@ -1525,7 +1634,7 @@ dependencies = [ "diesel_table_macro_syntax", "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.90", ] [[package]] @@ -1545,7 +1654,7 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc5557efc453706fed5e4fa85006fe9817c224c3f480a34c7e5959fd700921c5" dependencies = [ - "syn 2.0.85", + "syn 2.0.90", ] [[package]] @@ -1560,6 +1669,27 @@ dependencies = [ "subtle", ] +[[package]] +name = "dirs" +version = "5.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" +dependencies = [ + "libc", + "option-ext", + "redox_users", + "windows-sys 0.48.0", +] + [[package]] name = "displaydoc" version = "0.2.5" @@ -1568,7 +1698,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.90", ] [[package]] @@ -1700,6 +1830,12 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1019fa28f600f5b581b7a603d515c3f1635da041ca211b5055804788673abfe" +[[package]] +name = "encode_unicode" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" + [[package]] name = "encoding_rs" version = "0.8.34" @@ -1808,14 +1944,26 @@ version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" +[[package]] +name = "filetime" +version = "0.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35c0522e981e68cbfa8c3f978441a5f34b30b96e146b33cd3359176b50fe8586" +dependencies = [ + "cfg-if", + "libc", + "libredox", + "windows-sys 0.59.0", +] + [[package]] name = "flate2" -version = "1.0.31" +version = "1.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f211bbe8e69bbd0cfdea405084f128ae8b4aaa6b0b522fc8f2b009084797920" +checksum = "c936bfdafb507ebbf50b8074c54fa31c5be9a1e7e5f467dd659697041407d07c" dependencies = [ "crc32fast", - "miniz_oxide", + "miniz_oxide 0.8.0", ] [[package]] @@ -1848,6 +1996,16 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "fs2" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "fs4" version = "0.8.4" @@ -1939,7 +2097,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.90", ] [[package]] @@ -2102,6 +2260,30 @@ dependencies = [ "tracing", ] +[[package]] +name = "half" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888" +dependencies = [ + "cfg-if", + "crunchy", +] + +[[package]] +name = "hallucination-detection" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecbfea53ec460bc6bd4a73562604e301c41db9f818cf32aaf84921a36d8f1e8c" +dependencies = [ + "once_cell", + "regex", + "reqwest 0.12.9", + "rust-bert", + "serde", + "tokio", +] + [[package]] name = "hashbrown" version = "0.12.3" @@ -2214,7 +2396,7 @@ dependencies = [ "markup5ever 0.12.1", "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.90", ] [[package]] @@ -2530,6 +2712,18 @@ dependencies = [ "serde", ] +[[package]] +name = "indicatif" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d207dc617c7a380ab07ff572a6e52fa202a2a8f355860ac9c38e23f8196be1b" +dependencies = [ + "console", + "lazy_static", + "number_prefix", + "regex", +] + [[package]] name = "infer" version = "0.2.3" @@ -2578,6 +2772,15 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" +dependencies = [ + "either", +] + [[package]] name = "itertools" version = "0.12.1" @@ -2668,9 +2871,9 @@ checksum = "0c2cdeb66e45e9f36bfad5bbdb4d2384e70936afbee843c6f6543f0c551ebb25" [[package]] name = "libc" -version = "0.2.155" +version = "0.2.167" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" +checksum = "09d6582e104315a817dff97f75133544b2e094ee22447d2acf4a74e189ba06fc" [[package]] name = "libm" @@ -2678,6 +2881,17 @@ version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" +[[package]] +name = "libredox" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" +dependencies = [ + "bitflags 2.6.0", + "libc", + "redox_syscall 0.5.2", +] + [[package]] name = "libyml" version = "0.0.5" @@ -2721,6 +2935,12 @@ dependencies = [ "scopeguard", ] +[[package]] +name = "lockfree-object-pool" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9374ef4228402d4b7e403e5838cb880d9ee663314b0a900d5a6aabf0c213552e" + [[package]] name = "log" version = "0.4.22" @@ -2762,6 +2982,16 @@ version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75761162ae2b0e580d7e7c390558127e5f01b4194debd6221fd8c207fc80e3f5" +[[package]] +name = "lzma-rs" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "297e814c836ae64db86b36cf2a557ba54368d03f6afcd7d947c266692f71115e" +dependencies = [ + "byteorder", + "crc", +] + [[package]] name = "mac" version = "0.1.1" @@ -2832,7 +3062,7 @@ checksum = "5cf92c10c7e361d6b99666ec1c6f9805b0bea2c3bd8c78dc6fe98ac5bd78db11" dependencies = [ "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.90", ] [[package]] @@ -2961,6 +3191,15 @@ dependencies = [ "adler", ] +[[package]] +name = "miniz_oxide" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" +dependencies = [ + "adler2", +] + [[package]] name = "mio" version = "0.8.11" @@ -2973,6 +3212,17 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "mio" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" +dependencies = [ + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", + "windows-sys 0.52.0", +] + [[package]] name = "murmur3" version = "0.5.2" @@ -3121,9 +3371,15 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.90", ] +[[package]] +name = "number_prefix" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" + [[package]] name = "oas3" version = "0.10.0" @@ -3160,7 +3416,7 @@ dependencies = [ "serde_json", "serde_path_to_error", "sha2", - "thiserror", + "thiserror 1.0.65", "url", ] @@ -3236,7 +3492,7 @@ dependencies = [ "serde_with", "sha2", "subtle", - "thiserror", + "thiserror 1.0.65", "url", ] @@ -3263,7 +3519,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.90", ] [[package]] @@ -3284,6 +3540,12 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + [[package]] name = "ordered-float" version = "2.10.1" @@ -3293,6 +3555,15 @@ dependencies = [ "num-traits", ] +[[package]] +name = "ordered-float" +version = "4.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c65ee1f9701bf938026630b455d5315f490640234259037edb259798b3bcf85e" +dependencies = [ + "num-traits", +] + [[package]] name = "ordered-multimap" version = "0.7.3" @@ -3365,12 +3636,45 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "password-hash" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7676374caaee8a325c9e7a2ae557f216c5563a171d6997b0ef8a65af35147700" +dependencies = [ + "base64ct", + "rand_core 0.6.4", + "subtle", +] + [[package]] name = "paste" version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" +[[package]] +name = "pbkdf2" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917" +dependencies = [ + "digest", + "hmac", + "password-hash", + "sha2", +] + +[[package]] +name = "pbkdf2" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" +dependencies = [ + "digest", + "hmac", +] + [[package]] name = "pem-rfc7468" version = "0.7.0" @@ -3455,7 +3759,7 @@ dependencies = [ "phf_shared 0.11.2", "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.90", ] [[package]] @@ -3493,7 +3797,7 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" dependencies = [ "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.90", ] [[package]] @@ -3660,9 +3964,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.86" +version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" dependencies = [ "unicode-ident", ] @@ -3679,7 +3983,7 @@ dependencies = [ "memchr", "parking_lot", "protobuf", - "thiserror", + "thiserror 1.0.65", ] [[package]] @@ -3702,7 +4006,7 @@ dependencies = [ "itertools 0.13.0", "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.90", ] [[package]] @@ -3743,7 +4047,7 @@ dependencies = [ "reqwest 0.12.9", "serde", "serde_json", - "thiserror", + "thiserror 1.0.65", "tonic", ] @@ -3769,7 +4073,7 @@ dependencies = [ "quinn-udp", "rustc-hash", "rustls 0.23.11", - "thiserror", + "thiserror 1.0.65", "tokio", "tracing", ] @@ -3786,7 +4090,7 @@ dependencies = [ "rustc-hash", "rustls 0.23.11", "slab", - "thiserror", + "thiserror 1.0.65", "tinyvec", "tracing", ] @@ -3994,11 +4298,22 @@ dependencies = [ "bitflags 2.6.0", ] +[[package]] +name = "redox_users" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" +dependencies = [ + "getrandom 0.2.15", + "libredox", + "thiserror 1.0.65", +] + [[package]] name = "regex" -version = "1.10.5" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "aho-corasick", "memchr", @@ -4008,9 +4323,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.7" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" dependencies = [ "aho-corasick", "memchr", @@ -4034,9 +4349,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.4" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "reqwest" @@ -4054,10 +4369,12 @@ dependencies = [ "http-body 0.4.6", "hyper 0.14.30", "hyper-rustls 0.24.2", + "hyper-tls 0.5.0", "ipnet", "js-sys", "log", "mime", + "native-tls", "once_cell", "percent-encoding", "pin-project-lite", @@ -4069,6 +4386,7 @@ dependencies = [ "sync_wrapper 0.1.2", "system-configuration 0.5.1", "tokio", + "tokio-native-tls", "tokio-rustls 0.24.1", "tower-service", "url", @@ -4144,7 +4462,7 @@ dependencies = [ "nom", "pin-project-lite", "reqwest 0.12.9", - "thiserror", + "thiserror 1.0.65", ] [[package]] @@ -4206,7 +4524,27 @@ checksum = "9d9848531d60c9cbbcf9d166c885316c24bc0e2a9d3eba0956bb6cbbd79bc6e8" dependencies = [ "base64 0.21.7", "blake2b_simd", - "constant_time_eq", + "constant_time_eq 0.3.1", +] + +[[package]] +name = "rust-bert" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d42c5e4175577f25c58a4be357f09fc2aeb701093e861c41b7f60d1cbf7e61a3" +dependencies = [ + "cached-path", + "dirs", + "half", + "lazy_static", + "ordered-float 4.5.0", + "regex", + "rust_tokenizers", + "serde", + "serde_json", + "tch", + "thiserror 1.0.65", + "uuid 1.10.0", ] [[package]] @@ -4229,7 +4567,7 @@ dependencies = [ "proc-macro2", "quote", "rust-embed-utils", - "syn 2.0.85", + "syn 2.0.90", "walkdir", ] @@ -4283,7 +4621,7 @@ dependencies = [ "serde_derive", "serde_json", "sha2", - "thiserror", + "thiserror 1.0.65", "time", "tokio", "tokio-native-tls", @@ -4301,6 +4639,26 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "rust_tokenizers" +version = "8.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19599f60a688b5160247ee9c37a6af8b0c742ee8b160c5b44acc0f0eb265a59f" +dependencies = [ + "csv", + "hashbrown 0.14.5", + "itertools 0.11.0", + "lazy_static", + "protobuf", + "rayon", + "regex", + "serde", + "serde_json", + "thiserror 1.0.65", + "unicode-normalization", + "unicode-normalization-alignments", +] + [[package]] name = "rustc-demangle" version = "0.1.24" @@ -4477,6 +4835,16 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" +[[package]] +name = "safetensors" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d93279b86b3de76f820a8854dd06cbc33cfa57a417b19c47f6a25280112fb1df" +dependencies = [ + "serde", + "serde_json", +] + [[package]] name = "same-file" version = "1.0.6" @@ -4570,7 +4938,7 @@ dependencies = [ "heck 0.4.1", "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.90", ] [[package]] @@ -4643,9 +5011,9 @@ checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" [[package]] name = "serde" -version = "1.0.214" +version = "1.0.215" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f55c3193aca71c12ad7890f1785d2b73e1b9f63a0bbc353c08ef26fe03fc56b5" +checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f" dependencies = [ "serde_derive", ] @@ -4656,19 +5024,19 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3a1a3341211875ef120e117ea7fd5228530ae7e7036a779fdc9117be6b3282c" dependencies = [ - "ordered-float", + "ordered-float 2.10.1", "serde", ] [[package]] name = "serde_derive" -version = "1.0.214" +version = "1.0.215" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de523f781f095e28fa605cdce0f8307e451cc0fd14e2eb4cd2e98a355b147766" +checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.90", ] [[package]] @@ -4690,7 +5058,7 @@ checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" dependencies = [ "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.90", ] [[package]] @@ -4732,7 +5100,7 @@ checksum = "c7715380eec75f029a4ef7de39a9200e0a63823176b759d055b613f5a87df6a6" dependencies = [ "percent-encoding", "serde", - "thiserror", + "thiserror 1.0.65", ] [[package]] @@ -4743,7 +5111,7 @@ checksum = "8cac3f1e2ca2fe333923a1ae72caca910b98ed0630bb35ef6f8c8517d6e81afa" dependencies = [ "percent-encoding", "serde", - "thiserror", + "thiserror 1.0.65", ] [[package]] @@ -4794,7 +5162,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.90", ] [[package]] @@ -4878,6 +5246,12 @@ dependencies = [ "rand_core 0.6.4", ] +[[package]] +name = "simd-adler32" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" + [[package]] name = "simple-server-timing-header" version = "0.1.1" @@ -5074,9 +5448,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.85" +version = "2.0.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5023162dfcd14ef8f32034d8bcd4cc5ddc61ef7a247c024a33e24e1f24d21b56" +checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31" dependencies = [ "proc-macro2", "quote", @@ -5185,7 +5559,7 @@ dependencies = [ "tantivy-stacker", "tantivy-tokenizer-api", "tempfile", - "thiserror", + "thiserror 1.0.65", "time", "uuid 1.10.0", "winapi", @@ -5258,7 +5632,7 @@ dependencies = [ "tantivy-bitpacker", "tantivy-common", "tantivy-fst", - "zstd", + "zstd 0.13.2", ] [[package]] @@ -5281,6 +5655,34 @@ dependencies = [ "serde", ] +[[package]] +name = "tar" +version = "0.4.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c65998313f8e17d0d553d28f91a0df93e4dbbbf770279c7bc21ca0f09ea1a1f6" +dependencies = [ + "filetime", + "libc", + "xattr", +] + +[[package]] +name = "tch" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3585f5bbf1ddf2498d7586bf870c7bb785a0bf1be09c54d0f93fce51d5f3c7fc" +dependencies = [ + "half", + "lazy_static", + "libc", + "ndarray", + "rand 0.8.5", + "safetensors", + "thiserror 1.0.65", + "torch-sys", + "zip 0.6.6", +] + [[package]] name = "tempfile" version = "3.10.1" @@ -5310,7 +5712,16 @@ version = "1.0.65" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d11abd9594d9b38965ef50805c5e469ca9cc6f197f883f717e0269a3057b3d5" dependencies = [ - "thiserror-impl", + "thiserror-impl 1.0.65", +] + +[[package]] +name = "thiserror" +version = "2.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fec2a1820ebd077e2b90c4df007bebf344cd394098a13c563957d0afc83ea47" +dependencies = [ + "thiserror-impl 2.0.6", ] [[package]] @@ -5321,7 +5732,18 @@ checksum = "ae71770322cbd277e69d762a16c444af02aa0575ac0d174f0b9562d3b37f8602" dependencies = [ "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.90", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d65750cab40f4ff1929fb1ba509e9914eb756131cef4210da8d5d700d26f6312" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.90", ] [[package]] @@ -5381,32 +5803,31 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.38.0" +version = "1.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba4f4a02a7a80d6f274636f0aa95c7e383b912d41fe721a31f29e29698585a4a" +checksum = "5cec9b21b0450273377fc97bd4c33a8acffc8c996c987a7c5b319a0083707551" dependencies = [ "backtrace", "bytes", "libc", - "mio", - "num_cpus", + "mio 1.0.3", "parking_lot", "pin-project-lite", "signal-hook-registry", "socket2", "tokio-macros", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] name = "tokio-macros" -version = "2.3.0" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f5ae998a069d4b5aba8ee9dad856af7d520c3699e6159b185c2acd48155d39a" +checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.90", ] [[package]] @@ -5628,6 +6049,21 @@ dependencies = [ "tracing", ] +[[package]] +name = "torch-sys" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef116d446d79bb2447748550baee86850d2d32d366cc9bdd4b217bdbe10cac63" +dependencies = [ + "anyhow", + "cc", + "libc", + "serde", + "serde_json", + "ureq", + "zip 0.6.6", +] + [[package]] name = "tower" version = "0.4.13" @@ -5680,7 +6116,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.90", ] [[package]] @@ -5727,6 +6163,7 @@ dependencies = [ "futures", "futures-util", "glob", + "hallucination-detection", "itertools 0.13.0", "lazy_static", "lettre", @@ -5762,6 +6199,7 @@ dependencies = [ "simsearch", "strsim 0.11.1", "tantivy", + "tar", "time", "tokio", "tokio-postgres", @@ -5771,6 +6209,7 @@ dependencies = [ "utoipa-redoc", "utoipa-swagger-ui", "uuid 1.10.0", + "zip 2.2.1", ] [[package]] @@ -5827,6 +6266,15 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "unicode-normalization-alignments" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43f613e4fa046e69818dd287fdc4bc78175ff20331479dab6e1b0f98d57062de" +dependencies = [ + "smallvec", +] + [[package]] name = "unicode-properties" version = "0.1.1" @@ -5937,7 +6385,7 @@ dependencies = [ "proc-macro2", "quote", "regex", - "syn 2.0.85", + "syn 2.0.90", "uuid 1.10.0", ] @@ -5968,7 +6416,7 @@ dependencies = [ "serde_json", "url", "utoipa", - "zip", + "zip 1.1.4", ] [[package]] @@ -6073,7 +6521,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.90", "wasm-bindgen-shared", ] @@ -6107,7 +6555,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.90", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -6265,6 +6713,15 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + [[package]] name = "windows-targets" version = "0.48.5" @@ -6414,6 +6871,17 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "xattr" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8da84f1a25939b27f6820d92aed108f83ff920fdf11a7b19366c27c4cda81d4f" +dependencies = [ + "libc", + "linux-raw-sys", + "rustix", +] + [[package]] name = "xml5ever" version = "0.18.1" @@ -6442,7 +6910,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.90", ] [[package]] @@ -6450,6 +6918,40 @@ name = "zeroize" version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.90", +] + +[[package]] +name = "zip" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "760394e246e4c28189f19d488c058bf16f564016aefac5d32bb1f3b51d5e9261" +dependencies = [ + "aes", + "byteorder", + "bzip2", + "constant_time_eq 0.1.5", + "crc32fast", + "crossbeam-utils", + "flate2", + "hmac", + "pbkdf2 0.11.0", + "sha1", + "time", + "zstd 0.11.2+zstd.1.5.2", +] [[package]] name = "zip" @@ -6464,7 +6966,59 @@ dependencies = [ "flate2", "indexmap 2.2.6", "num_enum", - "thiserror", + "thiserror 1.0.65", +] + +[[package]] +name = "zip" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d52293fc86ea7cf13971b3bb81eb21683636e7ae24c729cdaf1b7c4157a352" +dependencies = [ + "aes", + "arbitrary", + "bzip2", + "constant_time_eq 0.3.1", + "crc32fast", + "crossbeam-utils", + "deflate64", + "displaydoc", + "flate2", + "hmac", + "indexmap 2.2.6", + "lzma-rs", + "memchr", + "pbkdf2 0.12.2", + "rand 0.8.5", + "sha1", + "thiserror 2.0.6", + "time", + "zeroize", + "zopfli", + "zstd 0.13.2", +] + +[[package]] +name = "zopfli" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5019f391bac5cf252e93bbcc53d039ffd62c7bfb7c150414d61369afe57e946" +dependencies = [ + "bumpalo", + "crc32fast", + "lockfree-object-pool", + "log", + "once_cell", + "simd-adler32", +] + +[[package]] +name = "zstd" +version = "0.11.2+zstd.1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20cc960326ece64f010d2d2107537f26dc589a6573a316bd5b1dba685fa5fde4" +dependencies = [ + "zstd-safe 5.0.2+zstd.1.5.2", ] [[package]] @@ -6473,7 +7027,17 @@ version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fcf2b778a664581e31e389454a7072dab1647606d44f7feea22cd5abb9c9f3f9" dependencies = [ - "zstd-safe", + "zstd-safe 7.2.0", +] + +[[package]] +name = "zstd-safe" +version = "5.0.2+zstd.1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d2a5585e04f9eea4b2a3d1eca508c4dee9592a89ef6f450c11719da0726f4db" +dependencies = [ + "libc", + "zstd-sys", ] [[package]] diff --git a/server/Cargo.toml b/server/Cargo.toml index de36d7f8c7..03b990971b 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -77,6 +77,12 @@ path = "src/bin/crawl-worker.rs" name = "crawl-cron-job" path = "src/bin/crawl-cron-job.rs" +[features] +default = [] +runtime-env = [] +hallucination-detection = ["dep:hallucination-detection"] +ner = ["hallucination-detection?/ner"] + [dependencies] actix-identity = { version = "0.7.1" } actix-session = { version = "0.9.0", features = [ @@ -177,12 +183,12 @@ oas3 = "0.10.0" sanitize_html = "0.8.1" minijinja-embed = "2.2.0" minijinja = { version = "2.2.0", features = ["loader", "json"] } - +hallucination-detection = { version = "0.1.5", default-features = false, optional = true } [build-dependencies] dotenvy = "0.15.7" minijinja-embed = "2.2.0" - -[features] -default = [] -runtime-env = [] +flate2 = "1.0.26" +tar = "0.4.38" +ureq = "2.6.2" +zip = "2.2.1" diff --git a/server/Dockerfile.server b/server/Dockerfile.server index 1823917cfb..67bdcb1e68 100644 --- a/server/Dockerfile.server +++ b/server/Dockerfile.server @@ -15,7 +15,7 @@ COPY --from=planner /app/recipe.json recipe.json RUN cargo chef cook --release --recipe-path recipe.json --bin "trieve-server" # Build application COPY . . -RUN cargo build --release --features "runtime-env" --bin "trieve-server" +RUN cargo build --release --features "runtime-env","hallucination-detection" --bin "trieve-server" FROM debian:bookworm-slim AS runtime WORKDIR /app @@ -29,11 +29,19 @@ RUN apt-get update -y; \ ca-certificates \ curl \ redis-tools \ + unzip \ ; RUN curl -fsSLO https://github.com/subtrace/subtrace/releases/download/b143/subtrace-linux-amd64 \ && chmod +x ./subtrace-linux-amd64 +RUN curl -L -o libtorch-cxx11-abi-shared-with-deps-2.4.0+cpu.zip https://download.pytorch.org/libtorch/cpu/libtorch-cxx11-abi-shared-with-deps-2.4.0%2Bcpu.zip \ + && unzip libtorch-cxx11-abi-shared-with-deps-2.4.0+cpu.zip \ + && rm libtorch-cxx11-abi-shared-with-deps-2.4.0+cpu.zip && mv libtorch /usr/lib/ + +ENV LIBTORCH=/usr/lib/libtorch +ENV LD_LIBRARY_PATH=${LIBTORCH}/lib: + COPY ./migrations/ /app/migrations COPY ./ch_migrations /app/ch_migrations COPY ./src/public/ /app/src/public diff --git a/server/build.rs b/server/build.rs index 0258188653..192e9013b0 100644 --- a/server/build.rs +++ b/server/build.rs @@ -1,10 +1,30 @@ use std::error::Error; +#[cfg(feature = "hallucination-detection")] +#[cfg(feature = "ner")] +use std::{ + env, fs, + io::{self, Write}, + path::{Path, PathBuf}, +}; + +#[cfg(feature = "hallucination-detection")] +#[cfg(feature = "ner")] +const ONNX_RELEASE_URL: &str = "https://github.com/microsoft/onnxruntime/releases/download/v1.16.3/onnxruntime-linux-x64-1.16.3.tgz"; +#[cfg(feature = "hallucination-detection")] +#[cfg(feature = "ner")] +const LIBTORCH_RELEASE_URL: &str = + "https://download.pytorch.org/libtorch/cpu/libtorch-cxx11-abi-shared-with-deps-2.4.0%2Bcpu.zip"; + #[cfg(not(feature = "runtime-env"))] fn main() -> Result<(), Box> { use std::{env, process::Command}; dotenvy::dotenv().expect("Failed to read .env file. Did you `cp .env.dist .env` ?"); + #[cfg(feature = "hallucination-detection")] + #[cfg(feature = "ner")] + copy_shared_objects()?; + let output = Command::new("npx") .arg("tailwindcss") .arg("-i") @@ -29,5 +49,111 @@ fn main() -> Result<(), Box> { #[cfg(feature = "runtime-env")] fn main() -> Result<(), Box> { minijinja_embed::embed_templates!("src/public"); + + #[cfg(feature = "hallucination-detection")] + #[cfg(feature = "ner")] + copy_shared_objects()?; + + Ok(()) +} + +#[cfg(feature = "hallucination-detection")] +#[cfg(feature = "ner")] +fn download_file

(url: String, target_file: &P) +where + P: AsRef, +{ + let resp = ureq::get(&url) + .timeout(std::time::Duration::from_secs(300)) + .call() + .unwrap_or_else(|err| panic!("ERROR: Failed to download {}: {:?}", url, err)); + + let len = resp + .header("Content-Length") + .and_then(|s| s.parse::().ok()) + .unwrap(); + let mut reader = resp.into_reader(); + // FIXME: Save directly to the file + let mut buffer = vec![]; + let read_len = reader.read_to_end(&mut buffer).unwrap(); + assert_eq!(buffer.len(), len); + assert_eq!(buffer.len(), read_len); + + let f = fs::File::create(target_file).unwrap(); + let mut writer = io::BufWriter::new(f); + writer.write_all(&buffer).unwrap(); +} + +#[cfg(feature = "hallucination-detection")] +#[cfg(feature = "ner")] +fn copy_shared_objects() -> io::Result<()> { + let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap()); + println!("cargo:rustc-link-search={}", out_dir.display()); + + let libonnxfilezip = out_dir.join("libonnxruntime.tgz"); + let libonnxfile = out_dir.join("onnxruntime-linux-x64-1.16.3/lib/libonnxruntime.so"); + let libtorchfilezip = out_dir.join("libtorch.zip"); + let libtorchfile = out_dir.join("libtorch"); + + if !libonnxfilezip.as_path().exists() { + download_file(ONNX_RELEASE_URL.to_string(), &libonnxfilezip); + } + + if !libtorchfilezip.as_path().exists() { + download_file(LIBTORCH_RELEASE_URL.to_string(), &libtorchfilezip); + } + + if !libonnxfile.as_path().exists() { + extract_tgz(&libonnxfilezip, out_dir.as_path()); + } + + if !libtorchfile.as_path().exists() { + extract_zip(&libtorchfilezip, out_dir.as_path()); + } + + env::set_var("ORT_DYLIB_PATH", libonnxfile.display().to_string()); + + env::set_var("LIBTORCH", libtorchfile.display().to_string()); + env::set_var( + "LD_LIBRARY_PATH", + libtorchfile.join("lib").display().to_string(), + ); + Ok(()) } + +#[cfg(feature = "hallucination-detection")] +#[cfg(feature = "ner")] +fn extract_tgz(filename: &Path, output: &Path) { + let file = fs::File::open(filename).unwrap(); + let buf = io::BufReader::new(file); + let tar = flate2::read::GzDecoder::new(buf); + let mut archive = tar::Archive::new(tar); + archive.unpack(output).unwrap(); +} + +#[cfg(feature = "hallucination-detection")] +#[cfg(feature = "ner")] +fn extract_zip(filename: &Path, output: &Path) { + let file = fs::File::open(filename).unwrap_or_else(|err| { + panic!( + "ERROR: Failed to open file {}: {:?}", + filename.display(), + err + ) + }); + let mut archive = zip::ZipArchive::new(file).unwrap_or_else(|err| { + panic!( + "ERROR: Failed to open zip archive {}: {:?}", + filename.display(), + err + ) + }); + archive.extract(output).unwrap_or_else(|err| { + panic!( + "ERROR: Failed to extract zip archive {}: {:?}", + filename.display(), + err + ) + }); +} diff --git a/server/ch_migrations/1733613844_add_hallucination_score/down.sql b/server/ch_migrations/1733613844_add_hallucination_score/down.sql new file mode 100644 index 0000000000..a5b4d1399a --- /dev/null +++ b/server/ch_migrations/1733613844_add_hallucination_score/down.sql @@ -0,0 +1,5 @@ +ALTER TABLE rag_queries +DROP COLUMN hallucination_score; + +ALTER TABLE rag_queries +DROP COLUMN detected_hallucinations; \ No newline at end of file diff --git a/server/ch_migrations/1733613844_add_hallucination_score/up.sql b/server/ch_migrations/1733613844_add_hallucination_score/up.sql new file mode 100644 index 0000000000..793f6572d4 --- /dev/null +++ b/server/ch_migrations/1733613844_add_hallucination_score/up.sql @@ -0,0 +1,3 @@ +ALTER TABLE rag_queries ADD COLUMN IF NOT EXISTS hallucination_score Float64 DEFAULT 0.0; +ALTER TABLE rag_queries ADD COLUMN IF NOT EXISTS detected_hallucinations Array(String) DEFAULT []; + diff --git a/server/src/data/models.rs b/server/src/data/models.rs index cafc2943de..a2ed2f8a52 100644 --- a/server/src/data/models.rs +++ b/server/src/data/models.rs @@ -5038,6 +5038,8 @@ pub struct RagQueryEvent { pub dataset_id: uuid::Uuid, pub llm_response: String, pub query_rating: Option, + pub hallucination_score: f64, + pub detected_hallucinations: Vec, pub created_at: String, pub user_id: String, } @@ -5094,6 +5096,8 @@ impl RagQueryEventClickhouse { query_rating, dataset_id: uuid::Uuid::from_bytes(*self.dataset_id.as_bytes()), llm_response: self.llm_response, + hallucination_score: self.hallucination_score, + detected_hallucinations: self.detected_hallucinations, created_at: self.created_at.to_string(), user_id: self.user_id, } @@ -5117,6 +5121,8 @@ pub struct RagQueryEventClickhouse { #[serde(with = "clickhouse::serde::time::datetime")] pub created_at: OffsetDateTime, pub user_id: String, + pub hallucination_score: f64, + pub detected_hallucinations: Vec, } #[derive(Debug, Row, Serialize, Deserialize, ToSchema)] @@ -5895,6 +5901,8 @@ impl EventTypes { query_rating, llm_response, user_id, + hallucination_score, + detected_hallucinations, } => EventDataTypes::RagQueryEventClickhouse(RagQueryEventClickhouse { id: uuid::Uuid::new_v4(), rag_type: rag_type @@ -5913,6 +5921,8 @@ impl EventTypes { dataset_id, created_at: OffsetDateTime::now_utc(), user_id: user_id.unwrap_or_default(), + hallucination_score: hallucination_score.unwrap_or(0.0), + detected_hallucinations: detected_hallucinations.unwrap_or_default(), }), EventTypes::Recommendation { recommendation_type, @@ -6775,6 +6785,10 @@ pub enum EventTypes { llm_response: Option, /// The user id of the user who made the RAG event user_id: Option, + /// The hallucination score of the RAG event + hallucination_score: Option, + /// The detected hallucinations of the RAG event + detected_hallucinations: Option>, }, #[display(fmt = "recommendation")] #[schema(title = "Recommendation")] @@ -7798,3 +7812,9 @@ impl From for FirecrawlCrawlRequest { } } } + +#[cfg(not(feature = "hallucination-detection"))] +pub struct DummyHallucinationScore { + pub total_score: f64, + pub detected_hallucinations: Vec, +} diff --git a/server/src/handlers/chunk_handler.rs b/server/src/handlers/chunk_handler.rs index a392e8d9db..1b9971cbfa 100644 --- a/server/src/handlers/chunk_handler.rs +++ b/server/src/handlers/chunk_handler.rs @@ -1,4 +1,6 @@ use super::auth_handler::{AdminOnly, LoggedUser}; +#[cfg(not(feature = "hallucination-detection"))] +use crate::data::models::DummyHallucinationScore; use crate::data::models::{ escape_quotes, ChatMessageProxy, ChunkMetadata, ChunkMetadataStringTagSet, ChunkMetadataTypes, ChunkMetadataWithScore, ConditionType, ContextOptions, CountSearchMethod, @@ -44,6 +46,11 @@ use serde_json::json; use simple_server_timing_header::Timer; use tokio_stream::StreamExt; use utoipa::ToSchema; +#[cfg(feature = "hallucination-detection")] +use { + crate::operators::message_operator::clean_markdown, + hallucination_detection::{HallucinationDetector, HallucinationScore}, +}; /// Boost the presence of certain tokens for fulltext (SPLADE) and keyword (BM25) search. I.e. boosting title phrases to priortize title matches or making sure that the listing for AirBNB itself ranks higher than companies who make software for AirBNB hosts by boosting the in-document-frequency of the AirBNB token (AKA word) for its official listing. Conceptually it multiples the in-document-importance second value in the tuples of the SPLADE or BM25 sparse vector of the chunk_html innerText for all tokens present in the boost phrase by the boost factor like so: (token, in-document-importance) -> (token, in-document-importance*boost_factor). #[derive(Serialize, Deserialize, Debug, Clone, ToSchema)] @@ -2444,6 +2451,9 @@ pub async fn generate_off_chunks( data: web::Json, pool: web::Data, event_queue: web::Data, + #[cfg(feature = "hallucination-detection")] hallucination_detector: web::Data< + HallucinationDetector, + >, _user: LoggedUser, dataset_org_plan_sub: DatasetAndOrgWithSubAndPlan, ) -> Result { @@ -2681,6 +2691,29 @@ pub async fn generate_off_chunks( } }; if !dataset_config.DISABLE_ANALYTICS { + #[cfg(feature = "hallucination-detection")] + let score = { + let docs = chunks + .iter() + .map(|x| x.chunk_html.clone().unwrap_or_default()) + .collect::>(); + hallucination_detector + .detect_hallucinations(&clean_markdown(&completion_content), &docs) + .await + .map_err(|err| { + ServiceError::BadRequest(format!( + "Failed to detect hallucinations: {}", + err + )) + })? + }; + + #[cfg(not(feature = "hallucination-detection"))] + let score = DummyHallucinationScore { + total_score: 0.0, + detected_hallucinations: vec![], + }; + let clickhouse_rag_event = RagQueryEventClickhouse { id: query_id, created_at: time::OffsetDateTime::now_utc(), @@ -2701,12 +2734,15 @@ pub async fn generate_off_chunks( rag_type: "chosen_chunks".to_string(), llm_response: completion_content.clone(), user_id: data.user_id.clone().unwrap_or_default(), + hallucination_score: score.total_score, + detected_hallucinations: score.detected_hallucinations, }; event_queue .send(ClickHouseEvent::RagQueryEvent(clickhouse_rag_event)) .await; } + return Ok(HttpResponse::Ok() .insert_header(("TR-QueryID", query_id.to_string())) .json(completion_content)); @@ -2723,6 +2759,31 @@ pub async fn generate_off_chunks( let chunk_v: Vec = r.iter().collect(); let completion = chunk_v.join(""); if !dataset_config.DISABLE_ANALYTICS { + #[cfg(feature = "hallucination-detection")] + let score = { + let docs = chunks + .iter() + .map(|x| x.chunk_html.clone().unwrap_or_default()) + .collect::>(); + + hallucination_detector + .detect_hallucinations(&clean_markdown(&completion), &docs) + .await + .unwrap_or(HallucinationScore { + total_score: 0.0, + proper_noun_score: 0.0, + number_mismatch_score: 0.0, + unknown_word_score: 0.0, + detected_hallucinations: vec![], + }) + }; + + #[cfg(not(feature = "hallucination-detection"))] + let score = DummyHallucinationScore { + total_score: 0.0, + detected_hallucinations: vec![], + }; + let clickhouse_rag_event = RagQueryEventClickhouse { id: uuid::Uuid::new_v4(), created_at: time::OffsetDateTime::now_utc(), @@ -2743,6 +2804,8 @@ pub async fn generate_off_chunks( query_rating: String::new(), llm_response: completion, user_id: data.user_id.clone().unwrap_or_default(), + hallucination_score: score.total_score, + detected_hallucinations: score.detected_hallucinations, }; event_queue diff --git a/server/src/handlers/message_handler.rs b/server/src/handlers/message_handler.rs index f4f44ee606..22b8e2968f 100644 --- a/server/src/handlers/message_handler.rs +++ b/server/src/handlers/message_handler.rs @@ -25,6 +25,8 @@ use crate::{ }, }; use actix_web::{web, HttpResponse}; +#[cfg(feature = "hallucination-detection")] +use hallucination_detection::HallucinationDetector; use itertools::Itertools; use openai_dive::v1::{ api::Client, @@ -148,6 +150,9 @@ pub async fn create_message( event_queue: web::Data, pool: web::Data, redis_pool: web::Data, + #[cfg(feature = "hallucination-detection")] hallucination_detector: web::Data< + HallucinationDetector, + >, ) -> Result { let message_count_pool = pool.clone(); let message_count_org_id = dataset_org_plan_sub.organization.organization.id; @@ -246,6 +251,8 @@ pub async fn create_message( redis_pool, dataset_config, create_message_data, + #[cfg(feature = "hallucination-detection")] + hallucination_detector, ) .await } @@ -478,6 +485,9 @@ pub async fn edit_message( pool: web::Data, event_queue: web::Data, redis_pool: web::Data, + #[cfg(feature = "hallucination-detection")] hallucination_detector: web::Data< + HallucinationDetector, + >, ) -> Result { let topic_id: uuid::Uuid = data.topic_id; let message_sort_order = data.message_sort_order; @@ -511,6 +521,8 @@ pub async fn edit_message( event_queue, third_pool, redis_pool, + #[cfg(feature = "hallucination-detection")] + hallucination_detector, ) .await } @@ -551,6 +563,9 @@ pub async fn regenerate_message_patch( pool: web::Data, event_queue: web::Data, redis_pool: web::Data, + #[cfg(feature = "hallucination-detection")] hallucination_detector: web::Data< + HallucinationDetector, + >, ) -> Result { let topic_id = data.topic_id; let dataset_config = @@ -581,6 +596,8 @@ pub async fn regenerate_message_patch( redis_pool.clone(), dataset_config, data.into_inner().into(), + #[cfg(feature = "hallucination-detection")] + hallucination_detector, ) .await; } @@ -654,6 +671,8 @@ pub async fn regenerate_message_patch( redis_pool.clone(), dataset_config, data.into_inner().into(), + #[cfg(feature = "hallucination-detection")] + hallucination_detector, ) .await } @@ -696,6 +715,9 @@ pub async fn regenerate_message( pool: web::Data, event_queue: web::Data, redis_pool: web::Data, + #[cfg(feature = "hallucination-detection")] hallucination_detector: web::Data< + HallucinationDetector, + >, ) -> Result { regenerate_message_patch( data, @@ -704,6 +726,8 @@ pub async fn regenerate_message( pool, event_queue, redis_pool, + #[cfg(feature = "hallucination-detection")] + hallucination_detector, ) .await } diff --git a/server/src/lib.rs b/server/src/lib.rs index d29a23c12c..a5ecfa173c 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -28,6 +28,8 @@ use diesel_async::pooled_connection::ManagerConfig; use diesel_migrations::{embed_migrations, EmbeddedMigrations, MigrationHarness}; use futures_util::future::BoxFuture; use futures_util::FutureExt; +#[cfg(feature = "hallucination-detection")] +use hallucination_detection::HallucinationDetector; use minijinja::Environment; use once_cell::sync::Lazy; use openssl::ssl::SslVerifyMode; @@ -697,6 +699,18 @@ pub fn main() -> std::io::Result<()> { std::io::Error::new(std::io::ErrorKind::Other, format!("Failed to create metrics {:?}", e)) })?; + #[cfg(feature = "hallucination-detection")] + let detector = { + let detector = HallucinationDetector::new(Default::default()).map_err(|e| { + std::io::Error::new(std::io::ErrorKind::Other, format!("Failed to create hallucination detector {:?}", e)) + })?; + web::Data::new(detector) + }; + + #[cfg(not(feature = "hallucination-detection"))] + let detector = web::Data::new(()); + + HttpServer::new(move || { let mut env = Environment::new(); minijinja_embed::load_templates!(&mut env); @@ -719,6 +733,7 @@ pub fn main() -> std::io::Result<()> { .app_data(web::Data::new(event_queue.clone())) .app_data(web::Data::new(clickhouse_client.clone())) .app_data(web::Data::new(metrics.clone())) + .app_data(detector.clone()) .wrap(from_fn(middleware::timeout_middleware::timeout_15secs)) .wrap(from_fn(middleware::metrics_middleware::error_logging_middleware)) .wrap(middleware::api_version::ApiVersionCheckFactory) diff --git a/server/src/operators/message_operator.rs b/server/src/operators/message_operator.rs index 927f62ea9e..717ea93157 100644 --- a/server/src/operators/message_operator.rs +++ b/server/src/operators/message_operator.rs @@ -1,3 +1,5 @@ +#[cfg(not(feature = "hallucination-detection"))] +use crate::data::models::DummyHallucinationScore; use crate::data::models::{ self, escape_quotes, ChunkMetadataStringTagSet, ChunkMetadataTypes, Dataset, DatasetConfiguration, LLMOptions, QueryTypes, RagQueryEventClickhouse, RedisPool, SearchMethod, @@ -20,6 +22,8 @@ use crossbeam_channel::unbounded; use diesel_async::RunQueryDsl; use futures::StreamExt; use futures_util::stream; +#[cfg(feature = "hallucination-detection")] +use hallucination_detection::{HallucinationDetector, HallucinationScore}; use openai_dive::v1::resources::chat::{DeltaChatMessage, ImageUrl, ImageUrlType}; use openai_dive::v1::{ api::Client, @@ -28,6 +32,7 @@ use openai_dive::v1::{ shared::StopToken, }, }; +use regex::Regex; use serde::{Deserialize, Serialize}; use simple_server_timing_header::Timer; use ureq::json; @@ -559,6 +564,50 @@ pub async fn get_rag_chunks_query( } } +pub fn clean_markdown(markdown_text: &str) -> String { + let mut text = markdown_text.to_string(); + + let patterns = [ + // Code blocks (both ``` and indented) + (r"```[\s\S]*?```", ""), + (r"(?m)^( {4,}|\t+)[^\n]+", ""), + // Headers + (r"(?m)^#{1,6}\s+", ""), + // Emphasis (bold, italic) + (r"\*\*(.+?)\*\*", "$1"), // Bold + (r"__(.+?)__", "$1"), // Bold + (r"\*(.+?)\*", "$1"), // Italic + (r"_(.+?)_", "$1"), // Italic + // Inline code + (r"`([^`]+)`", "$1"), + // Blockquotes + (r"(?m)^\s*>\s+", ""), + // Horizontal rules + (r"\n[\*\-_]{3,}\n", "\n"), + // Lists + (r"(?m)^\s*[\*\-+]\s+", ""), // Unordered lists + (r"(?m)^\s*\d+\.\s+", ""), // Ordered lists + // Links and images + (r"\[([^\]]+)\]\([^\)]+\)", "$1"), // [text](url) + (r"\[([^\]]+)\]\[[^\]]*\]", "$1"), // [text][reference] + (r"!\[([^\]]*)\]\([^\)]+\)", ""), // Images + // Reference-style links + (r"(?m)^\s*\[[^\]]+\]:\s+[^\s]+\s*$", ""), + // Clean up whitespace + (r"\n\s*\n", "\n\n"), + ]; + + // Apply all patterns + for (pattern, replacement) in patterns.iter() { + if let Ok(regex) = Regex::new(pattern) { + text = regex.replace_all(&text, *replacement).to_string(); + } + } + + // Final cleanup + text.trim().to_string() +} + #[allow(clippy::too_many_arguments)] pub async fn stream_response( @@ -568,11 +617,12 @@ pub async fn stream_response( pool: web::Data, event_queue: web::Data, redis_pool: web::Data, - _dataset_config: DatasetConfiguration, + dataset_config: DatasetConfiguration, create_message_req_payload: CreateMessageReqPayload, + #[cfg(feature = "hallucination-detection")] hallucination_detector: web::Data< + HallucinationDetector, + >, ) -> Result { - let dataset_config = DatasetConfiguration::from_json(dataset.server_configuration.clone()); - let user_message_query = match create_message_req_payload.concat_user_messages_query { Some(true) => messages .iter() @@ -880,6 +930,26 @@ pub async fn stream_response( query_id, ); + #[cfg(feature = "hallucination-detection")] + let score = { + let docs = chunk_metadatas + .iter() + .map(|x| x.chunk_html.clone().unwrap_or_default()) + .collect::>(); + hallucination_detector + .detect_hallucinations(&clean_markdown(&completion_content), &docs) + .await + .map_err(|err| { + ServiceError::BadRequest(format!("Failed to detect hallucinations: {}", err)) + })? + }; + + #[cfg(not(feature = "hallucination-detection"))] + let score = DummyHallucinationScore { + total_score: 0.0, + detected_hallucinations: vec![], + }; + let clickhouse_rag_event = RagQueryEventClickhouse { id: query_id, created_at: time::OffsetDateTime::now_utc(), @@ -889,12 +959,14 @@ pub async fn stream_response( json_results: chunk_data, user_message: user_message_query.clone(), query_rating: String::new(), - rag_type: "chosen_chunks".to_string(), + rag_type: "all_chunks".to_string(), llm_response: completion_content.clone(), user_id: create_message_req_payload .user_id .clone() .unwrap_or_default(), + hallucination_score: score.total_score, + detected_hallucinations: score.detected_hallucinations, }; let response_string = if create_message_req_payload @@ -950,6 +1022,31 @@ pub async fn stream_response( query_id_arb, ); if !dataset_config.DISABLE_ANALYTICS { + #[cfg(feature = "hallucination-detection")] + let score = { + let docs = chunk_metadatas + .iter() + .map(|x| x.chunk_html.clone().unwrap_or_default()) + .collect::>(); + + hallucination_detector + .detect_hallucinations(&clean_markdown(&completion), &docs) + .await + .unwrap_or(HallucinationScore { + total_score: 0.0, + proper_noun_score: 0.0, + number_mismatch_score: 0.0, + unknown_word_score: 0.0, + detected_hallucinations: vec![], + }) + }; + + #[cfg(not(feature = "hallucination-detection"))] + let score = DummyHallucinationScore { + total_score: 0.0, + detected_hallucinations: vec![], + }; + let clickhouse_rag_event = RagQueryEventClickhouse { id: query_id_arb, created_at: time::OffsetDateTime::now_utc(), @@ -965,6 +1062,8 @@ pub async fn stream_response( .user_id .clone() .unwrap_or_default(), + hallucination_score: score.total_score, + detected_hallucinations: score.detected_hallucinations, }; event_queue