From 5400f3941a3a22457ced8362767dafb53c7da511 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Renault?= Date: Tue, 13 May 2025 11:19:32 +0200 Subject: [PATCH 001/103] Introduce the first version of the /chat route that mimics the OpenAI API --- Cargo.lock | 211 +++++++++++++++++++++++++- crates/benchmarks/Cargo.toml | 2 +- crates/meilisearch-types/src/keys.rs | 6 + crates/meilisearch/Cargo.toml | 1 + crates/meilisearch/src/routes/chat.rs | 32 ++++ crates/meilisearch/src/routes/mod.rs | 4 +- 6 files changed, 251 insertions(+), 5 deletions(-) create mode 100644 crates/meilisearch/src/routes/chat.rs diff --git a/Cargo.lock b/Cargo.lock index 0e9a072d3..cd314a025 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -429,6 +429,43 @@ dependencies = [ "serde_json", ] +[[package]] +name = "async-openai" +version = "0.28.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ac0334b0fef1ddaf141154a3ef6d55a95b5b1a52c720753554b0a1ca670e68" +dependencies = [ + "async-openai-macros", + "backoff", + "base64 0.22.1", + "bytes", + "derive_builder 0.20.2", + "eventsource-stream", + "futures", + "rand 0.8.5", + "reqwest", + "reqwest-eventsource", + "secrecy", + "serde", + "serde_json", + "thiserror 2.0.12", + "tokio", + "tokio-stream", + "tokio-util", + "tracing", +] + +[[package]] +name = "async-openai-macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0289cba6d5143bfe8251d57b4a8cac036adf158525a76533a7082ba65ec76398" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] + [[package]] name = "async-trait" version = "0.1.85" @@ -452,6 +489,20 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80" +[[package]] +name = "backoff" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b62ddb9cb1ec0a098ad4bbf9344d0713fa193ae1a80af55febcff2627b6a00c1" +dependencies = [ + "futures-core", + "getrandom 0.2.15", + "instant", + "pin-project-lite", + "rand 0.8.5", + "tokio", +] + [[package]] name = "backtrace" version = "0.3.68" @@ -1184,6 +1235,26 @@ dependencies = [ "version_check", ] +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation-sys" version = "0.8.7" @@ -1856,6 +1927,17 @@ version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d817e038c30374a4bcb22f94d0a8a0e216958d4c3dcde369b1439fec4bdda6e6" +[[package]] +name = "eventsource-stream" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74fef4569247a5f429d9156b9d0a2599914385dd189c539334c625d8099d90ab" +dependencies = [ + "futures-core", + "nom", + "pin-project-lite", +] + [[package]] name = "fancy-regex" version = "0.13.0" @@ -2036,6 +2118,12 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" +[[package]] +name = "futures-timer" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" + [[package]] name = "futures-util" version = "0.3.31" @@ -2580,6 +2668,7 @@ dependencies = [ "hyper", "hyper-util", "rustls", + "rustls-native-certs 0.7.3", "rustls-pki-types", "tokio", "tokio-rustls", @@ -3505,6 +3594,7 @@ dependencies = [ "actix-utils", "actix-web", "anyhow", + "async-openai", "async-trait", "brotli", "bstr", @@ -4080,6 +4170,12 @@ version = "11.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" +[[package]] +name = "openssl-probe" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" + [[package]] name = "option-ext" version = "0.2.0" @@ -4788,11 +4884,13 @@ dependencies = [ "js-sys", "log", "mime", + "mime_guess", "once_cell", "percent-encoding", "pin-project-lite", "quinn", "rustls", + "rustls-native-certs 0.8.1", "rustls-pki-types", "serde", "serde_json", @@ -4812,6 +4910,22 @@ dependencies = [ "webpki-roots 1.0.0", ] +[[package]] +name = "reqwest-eventsource" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "632c55746dbb44275691640e7b40c907c16a2dc1a5842aa98aaec90da6ec6bde" +dependencies = [ + "eventsource-stream", + "futures-core", + "futures-timer", + "mime", + "nom", + "pin-project-lite", + "reqwest", + "thiserror 1.0.69", +] + [[package]] name = "rhai" version = "1.20.0" @@ -4982,6 +5096,31 @@ dependencies = [ "zeroize", ] +[[package]] +name = "rustls-native-certs" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5bfb394eeed242e909609f56089eecfe5fda225042e8b171791b9c95f5931e5" +dependencies = [ + "openssl-probe", + "rustls-pemfile", + "rustls-pki-types", + "schannel", + "security-framework 2.11.1", +] + +[[package]] +name = "rustls-native-certs" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fcff2dd52b58a8d98a70243663a0d234c4e2b79235637849d15913394a247d3" +dependencies = [ + "openssl-probe", + "rustls-pki-types", + "schannel", + "security-framework 3.2.0", +] + [[package]] name = "rustls-pemfile" version = "2.2.0" @@ -5039,6 +5178,15 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "schannel" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" +dependencies = [ + "windows-sys 0.59.0", +] + [[package]] name = "scopeguard" version = "1.2.0" @@ -5051,6 +5199,52 @@ version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" +[[package]] +name = "secrecy" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e891af845473308773346dc847b2c23ee78fe442e0472ac50e22a18a93d3ae5a" +dependencies = [ + "serde", + "zeroize", +] + +[[package]] +name = "security-framework" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +dependencies = [ + "bitflags 2.9.0", + "core-foundation 0.9.4", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271720403f46ca04f7ba6f55d438f8bd878d6b8ca0a1046e8228c4145bcbb316" +dependencies = [ + "bitflags 2.9.0", + "core-foundation 0.10.1", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "segment" version = "0.2.5" @@ -5788,10 +5982,21 @@ dependencies = [ ] [[package]] -name = "tokio-util" -version = "0.7.11" +name = "tokio-stream" +version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cf6b47b3771c49ac75ad09a6162f53ad4b8088b76ac60e8ec1455b31a189fe1" +checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66a539a9ad6d5d281510d5bd368c973d636c02dbf8a67300bfb6b950696ad7df" dependencies = [ "bytes", "futures-core", diff --git a/crates/benchmarks/Cargo.toml b/crates/benchmarks/Cargo.toml index a2cddd554..a5b3a39e1 100644 --- a/crates/benchmarks/Cargo.toml +++ b/crates/benchmarks/Cargo.toml @@ -31,7 +31,7 @@ anyhow = "1.0.95" bytes = "1.9.0" convert_case = "0.6.0" flate2 = "1.0.35" -reqwest = { version = "0.12.12", features = ["blocking", "rustls-tls"], default-features = false } +reqwest = { version = "0.12.15", features = ["blocking", "rustls-tls"], default-features = false } [features] default = ["milli/all-tokenizations"] diff --git a/crates/meilisearch-types/src/keys.rs b/crates/meilisearch-types/src/keys.rs index 27f2047ee..805394781 100644 --- a/crates/meilisearch-types/src/keys.rs +++ b/crates/meilisearch-types/src/keys.rs @@ -308,6 +308,9 @@ pub enum Action { #[serde(rename = "network.update")] #[deserr(rename = "network.update")] NetworkUpdate, + #[serde(rename = "chat.get")] + #[deserr(rename = "chat.get")] + ChatGet, } impl Action { @@ -349,6 +352,7 @@ impl Action { EXPERIMENTAL_FEATURES_UPDATE => Some(Self::ExperimentalFeaturesUpdate), NETWORK_GET => Some(Self::NetworkGet), NETWORK_UPDATE => Some(Self::NetworkUpdate), + CHAT_GET => Some(Self::ChatGet), _otherwise => None, } } @@ -397,4 +401,6 @@ pub mod actions { pub const NETWORK_GET: u8 = NetworkGet.repr(); pub const NETWORK_UPDATE: u8 = NetworkUpdate.repr(); + + pub const CHAT_GET: u8 = ChatGet.repr(); } diff --git a/crates/meilisearch/Cargo.toml b/crates/meilisearch/Cargo.toml index 40c0d98b5..ff6e5ceeb 100644 --- a/crates/meilisearch/Cargo.toml +++ b/crates/meilisearch/Cargo.toml @@ -111,6 +111,7 @@ utoipa = { version = "5.3.1", features = [ "openapi_extensions", ] } utoipa-scalar = { version = "0.3.0", optional = true, features = ["actix-web"] } +async-openai = "0.28.1" [dev-dependencies] actix-rt = "2.10.0" diff --git a/crates/meilisearch/src/routes/chat.rs b/crates/meilisearch/src/routes/chat.rs new file mode 100644 index 000000000..1cb813acd --- /dev/null +++ b/crates/meilisearch/src/routes/chat.rs @@ -0,0 +1,32 @@ +use actix_web::web::{self, Data}; +use actix_web::HttpResponse; +use async_openai::config::OpenAIConfig; +use async_openai::types::CreateChatCompletionRequest; +use async_openai::Client; +use index_scheduler::IndexScheduler; +use meilisearch_types::error::ResponseError; +use meilisearch_types::keys::actions; + +use crate::extractors::authentication::policies::ActionPolicy; +use crate::extractors::authentication::GuardedData; + +pub fn configure(cfg: &mut web::ServiceConfig) { + cfg.service(web::resource("").route(web::post().to(chat))); +} + +/// Get a chat completion +async fn chat( + _index_scheduler: GuardedData, Data>, + web::Json(chat_completion): web::Json, +) -> Result { + // To enable later on, when the feature will be experimental + // index_scheduler.features().check_chat("Using the /chat route")?; + + let api_key = std::env::var("MEILI_OPENAI_API_KEY") + .expect("cannot find OpenAI API Key (MEILI_OPENAI_API_KEY)"); + let config = OpenAIConfig::default().with_api_key(&api_key); // we can also change the API base + let client = Client::with_config(config); + let response = client.chat().create(chat_completion).await.unwrap(); + + Ok(HttpResponse::Ok().json(response)) +} diff --git a/crates/meilisearch/src/routes/mod.rs b/crates/meilisearch/src/routes/mod.rs index 2c71fa68b..602fb6b40 100644 --- a/crates/meilisearch/src/routes/mod.rs +++ b/crates/meilisearch/src/routes/mod.rs @@ -52,6 +52,7 @@ const PAGINATION_DEFAULT_LIMIT_FN: fn() -> usize = || 20; mod api_key; pub mod batches; +pub mod chat; mod dump; pub mod features; pub mod indexes; @@ -113,7 +114,8 @@ pub fn configure(cfg: &mut web::ServiceConfig) { .service(web::scope("/swap-indexes").configure(swap_indexes::configure)) .service(web::scope("/metrics").configure(metrics::configure)) .service(web::scope("/experimental-features").configure(features::configure)) - .service(web::scope("/network").configure(network::configure)); + .service(web::scope("/network").configure(network::configure)) + .service(web::scope("/chat").configure(chat::configure)); #[cfg(feature = "swagger")] { From 951be6706008de597f58f9e8000ff54640f566b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Renault?= Date: Tue, 13 May 2025 15:26:24 +0200 Subject: [PATCH 002/103] Support querying the index named main --- Cargo.lock | 1 + crates/meilisearch/Cargo.toml | 1 + crates/meilisearch/src/routes/chat.rs | 209 +++++++++++++++++++++++++- 3 files changed, 207 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index cd314a025..c205bbb3d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3620,6 +3620,7 @@ dependencies = [ "itertools 0.14.0", "jsonwebtoken", "lazy_static", + "liquid", "manifest-dir-macros", "maplit", "meili-snap", diff --git a/crates/meilisearch/Cargo.toml b/crates/meilisearch/Cargo.toml index ff6e5ceeb..a0ce49193 100644 --- a/crates/meilisearch/Cargo.toml +++ b/crates/meilisearch/Cargo.toml @@ -48,6 +48,7 @@ is-terminal = "0.4.13" itertools = "0.14.0" jsonwebtoken = "9.3.0" lazy_static = "1.5.0" +liquid = "0.26.9" meilisearch-auth = { path = "../meilisearch-auth" } meilisearch-types = { path = "../meilisearch-types" } mimalloc = { version = "0.1.43", default-features = false } diff --git a/crates/meilisearch/src/routes/chat.rs b/crates/meilisearch/src/routes/chat.rs index 1cb813acd..335d5bf43 100644 --- a/crates/meilisearch/src/routes/chat.rs +++ b/crates/meilisearch/src/routes/chat.rs @@ -1,14 +1,48 @@ +use std::mem; + use actix_web::web::{self, Data}; use actix_web::HttpResponse; use async_openai::config::OpenAIConfig; -use async_openai::types::CreateChatCompletionRequest; +use async_openai::types::{ + ChatCompletionRequestAssistantMessageArgs, ChatCompletionRequestMessage, + ChatCompletionRequestToolMessage, ChatCompletionRequestToolMessageContent, + ChatCompletionToolArgs, ChatCompletionToolType, CreateChatCompletionRequest, FinishReason, + FunctionObjectArgs, +}; use async_openai::Client; use index_scheduler::IndexScheduler; use meilisearch_types::error::ResponseError; use meilisearch_types::keys::actions; +use meilisearch_types::milli::index::IndexEmbeddingConfig; +use meilisearch_types::milli::prompt::PromptData; +use meilisearch_types::milli::vector::EmbeddingConfig; +use meilisearch_types::{Document, Index}; +use serde::Deserialize; +use serde_json::json; use crate::extractors::authentication::policies::ActionPolicy; use crate::extractors::authentication::GuardedData; +use crate::metrics::MEILISEARCH_DEGRADED_SEARCH_REQUESTS; +use crate::routes::indexes::search::search_kind; +use crate::search::{ + add_search_rules, perform_search, HybridQuery, RetrieveVectors, SearchQuery, SemanticRatio, +}; +use crate::search_queue::SearchQueue; + +/// The default description of the searchInIndex tool provided to OpenAI. +const DEFAULT_SEARCH_IN_INDEX_TOOL_DESCRIPTION: &str = + "Search the database for relevant JSON documents using an optional query."; +/// The default description of the searchInIndex `q` parameter tool provided to OpenAI. +const DEFAULT_SEARCH_IN_INDEX_Q_PARAMETER_TOOL_DESCRIPTION: &str = + "The search query string used to find relevant documents in the index. \ +This should contain keywords or phrases that best represent what the user is looking for. \ +More specific queries will yield more precise results."; +/// The default description of the searchInIndex `index` parameter tool provided to OpenAI. +const DEFAULT_SEARCH_IN_INDEX_INDEX_PARAMETER_TOOL_DESCRIPTION: &str = +"The name of the index to search within. An index is a collection of documents organized for search. \ +Selecting the right index ensures the most relevant results for the user query"; + +const EMBEDDER_NAME: &str = "openai"; pub fn configure(cfg: &mut web::ServiceConfig) { cfg.service(web::resource("").route(web::post().to(chat))); @@ -16,8 +50,9 @@ pub fn configure(cfg: &mut web::ServiceConfig) { /// Get a chat completion async fn chat( - _index_scheduler: GuardedData, Data>, - web::Json(chat_completion): web::Json, + index_scheduler: GuardedData, Data>, + search_queue: web::Data, + web::Json(mut chat_completion): web::Json, ) -> Result { // To enable later on, when the feature will be experimental // index_scheduler.features().check_chat("Using the /chat route")?; @@ -26,7 +61,173 @@ async fn chat( .expect("cannot find OpenAI API Key (MEILI_OPENAI_API_KEY)"); let config = OpenAIConfig::default().with_api_key(&api_key); // we can also change the API base let client = Client::with_config(config); - let response = client.chat().create(chat_completion).await.unwrap(); + + assert_eq!( + chat_completion.n.unwrap_or(1), + 1, + "Meilisearch /chat only support one completion at a time (n = 1, n = null)" + ); + + let mut response; + loop { + let mut tools = chat_completion.tools.get_or_insert_default(); + tools.push(ChatCompletionToolArgs::default() + .r#type(ChatCompletionToolType::Function) + .function(FunctionObjectArgs::default() + .name("searchInIndex") + .description(DEFAULT_SEARCH_IN_INDEX_TOOL_DESCRIPTION) + .parameters(json!({ + "type": "object", + "properties": { + "index_uid": { + "type": "string", + "enum": ["main"], + "description": DEFAULT_SEARCH_IN_INDEX_INDEX_PARAMETER_TOOL_DESCRIPTION, + }, + "q": { + "type": ["string", "null"], + "description": DEFAULT_SEARCH_IN_INDEX_Q_PARAMETER_TOOL_DESCRIPTION, + } + }, + "required": ["index_uid", "q"], + "additionalProperties": false, + })) + .strict(true) + .build() + .unwrap(), + ) + .build() + .unwrap() + ); + response = dbg!(client.chat().create(chat_completion.clone()).await.unwrap()); + + let choice = &mut response.choices[0]; + match choice.finish_reason { + Some(FinishReason::ToolCalls) => { + let tool_calls = mem::take(&mut choice.message.tool_calls).unwrap_or_default(); + + let (meili_calls, other_calls): (Vec<_>, Vec<_>) = + tool_calls.into_iter().partition(|call| call.function.name == "searchInIndex"); + + chat_completion.messages.push( + ChatCompletionRequestAssistantMessageArgs::default() + .tool_calls(meili_calls.clone()) + .build() + .unwrap() + .into(), + ); + + for call in meili_calls { + let SearchInIndexParameters { index_uid, q } = + serde_json::from_str(dbg!(&call.function.arguments)).unwrap(); + + let mut query = SearchQuery { + q, + hybrid: Some(HybridQuery { + semantic_ratio: SemanticRatio::default(), + embedder: EMBEDDER_NAME.to_string(), + }), + ..Default::default() + }; + + // Tenant token search_rules. + if let Some(search_rules) = + index_scheduler.filters().get_index_search_rules(&index_uid) + { + add_search_rules(&mut query.filter, search_rules); + } + + // TBD + // let mut aggregate = SearchAggregator::::from_query(&query); + + let index = index_scheduler.index(&index_uid)?; + let search_kind = search_kind( + &query, + index_scheduler.get_ref(), + index_uid.to_string(), + &index, + )?; + + let permit = search_queue.try_get_search_permit().await?; + let features = index_scheduler.features(); + let index_cloned = index.clone(); + let search_result = tokio::task::spawn_blocking(move || { + perform_search( + index_uid.to_string(), + &index_cloned, + query, + search_kind, + RetrieveVectors::new(false), + features, + ) + }) + .await; + permit.drop().await; + + let search_result = search_result?; + if let Ok(ref search_result) = search_result { + // aggregate.succeed(search_result); + if search_result.degraded { + MEILISEARCH_DEGRADED_SEARCH_REQUESTS.inc(); + } + } + // analytics.publish(aggregate, &req); + + let search_result = search_result?; + let formatted = format_documents( + &index, + search_result.hits.into_iter().map(|doc| doc.document), + ); + let text = formatted.join("\n"); + chat_completion.messages.push(ChatCompletionRequestMessage::Tool( + ChatCompletionRequestToolMessage { + tool_call_id: call.id, + content: ChatCompletionRequestToolMessageContent::Text(text), + }, + )); + } + + // Let the client call other tools by themselves + if !other_calls.is_empty() { + response.choices[0].message.tool_calls = Some(other_calls); + break; + } + } + _ => break, + } + } Ok(HttpResponse::Ok().json(response)) } + +#[derive(Deserialize)] +struct SearchInIndexParameters { + /// The index uid to search in. + index_uid: String, + /// The query parameter to use. + q: Option, +} + +fn format_documents(index: &Index, documents: impl Iterator) -> Vec { + let rtxn = index.read_txn().unwrap(); + let IndexEmbeddingConfig { name: _, config, user_provided: _ } = index + .embedding_configs(&rtxn) + .unwrap() + .into_iter() + .find(|conf| conf.name == EMBEDDER_NAME) + .unwrap(); + + let EmbeddingConfig { + embedder_options: _, + prompt: PromptData { template, max_bytes }, + quantized: _, + } = config; + + let template = liquid::ParserBuilder::with_stdlib().build().unwrap().parse(&template).unwrap(); + documents + .map(|doc| { + let object = liquid::to_object(&doc).unwrap(); + template.render(&object).unwrap() + }) + .collect() +} From 82fa70da83ced99932ad8b03f3b9596c554852f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Renault?= Date: Tue, 13 May 2025 16:33:58 +0200 Subject: [PATCH 003/103] Support overwriten prompts of the search query --- crates/index-scheduler/src/lib.rs | 20 +++++++++++++++++--- crates/meilisearch/src/routes/chat.rs | 20 +++++++++++++++++++- 2 files changed, 36 insertions(+), 4 deletions(-) diff --git a/crates/index-scheduler/src/lib.rs b/crates/index-scheduler/src/lib.rs index 6ff274da7..11572cd06 100644 --- a/crates/index-scheduler/src/lib.rs +++ b/crates/index-scheduler/src/lib.rs @@ -53,8 +53,8 @@ use flate2::Compression; use meilisearch_types::batches::Batch; use meilisearch_types::features::{InstanceTogglableFeatures, Network, RuntimeTogglableFeatures}; use meilisearch_types::heed::byteorder::BE; -use meilisearch_types::heed::types::I128; -use meilisearch_types::heed::{self, Env, RoTxn, WithoutTls}; +use meilisearch_types::heed::types::{Str, I128}; +use meilisearch_types::heed::{self, Database, Env, RoTxn, WithoutTls}; use meilisearch_types::milli::index::IndexEmbeddingConfig; use meilisearch_types::milli::update::IndexerConfig; use meilisearch_types::milli::vector::{Embedder, EmbedderOptions, EmbeddingConfigs}; @@ -153,6 +153,9 @@ pub struct IndexScheduler { /// In charge of fetching and setting the status of experimental features. features: features::FeatureData, + /// Stores the custom prompts for the chat + chat_prompts: Database, + /// Everything related to the processing of the tasks pub scheduler: scheduler::Scheduler, @@ -211,11 +214,16 @@ impl IndexScheduler { #[cfg(test)] run_loop_iteration: self.run_loop_iteration.clone(), features: self.features.clone(), + chat_prompts: self.chat_prompts.clone(), } } pub(crate) const fn nb_db() -> u32 { - Versioning::nb_db() + Queue::nb_db() + IndexMapper::nb_db() + features::FeatureData::nb_db() + Versioning::nb_db() + + Queue::nb_db() + + IndexMapper::nb_db() + + features::FeatureData::nb_db() + + 1 // chat-prompts } /// Create an index scheduler and start its run loop. @@ -269,6 +277,7 @@ impl IndexScheduler { let features = features::FeatureData::new(&env, &mut wtxn, options.instance_features)?; let queue = Queue::new(&env, &mut wtxn, &options)?; let index_mapper = IndexMapper::new(&env, &mut wtxn, &options, budget)?; + let chat_prompts = env.create_database(&mut wtxn, Some("chat-prompts"))?; wtxn.commit()?; // allow unreachable_code to get rids of the warning in the case of a test build. @@ -292,6 +301,7 @@ impl IndexScheduler { #[cfg(test)] run_loop_iteration: Arc::new(RwLock::new(0)), features, + chat_prompts, }; this.run(); @@ -864,6 +874,10 @@ impl IndexScheduler { .collect(); res.map(EmbeddingConfigs::new) } + + pub fn chat_prompts<'t>(&self, rtxn: &'t RoTxn, name: &str) -> heed::Result> { + self.chat_prompts.get(rtxn, name) + } } /// The outcome of calling the [`IndexScheduler::tick`] function. diff --git a/crates/meilisearch/src/routes/chat.rs b/crates/meilisearch/src/routes/chat.rs index 335d5bf43..3e5bf6957 100644 --- a/crates/meilisearch/src/routes/chat.rs +++ b/crates/meilisearch/src/routes/chat.rs @@ -17,7 +17,7 @@ use meilisearch_types::milli::index::IndexEmbeddingConfig; use meilisearch_types::milli::prompt::PromptData; use meilisearch_types::milli::vector::EmbeddingConfig; use meilisearch_types::{Document, Index}; -use serde::Deserialize; +use serde::{Deserialize, Serialize}; use serde_json::json; use crate::extractors::authentication::policies::ActionPolicy; @@ -68,6 +68,24 @@ async fn chat( "Meilisearch /chat only support one completion at a time (n = 1, n = null)" ); + let rtxn = index_scheduler.read_txn().unwrap(); + let search_in_index_description = index_scheduler + .chat_prompts(&rtxn, "searchInIndex-description") + .unwrap() + .unwrap_or(DEFAULT_SEARCH_IN_INDEX_TOOL_DESCRIPTION) + .to_string(); + let search_in_index_q_param_description = index_scheduler + .chat_prompts(&rtxn, "searchInIndex-q-param-description") + .unwrap() + .unwrap_or(DEFAULT_SEARCH_IN_INDEX_Q_PARAMETER_TOOL_DESCRIPTION) + .to_string(); + let search_in_index_index_description = index_scheduler + .chat_prompts(&rtxn, "searchInIndex-index-param-description") + .unwrap() + .unwrap_or(DEFAULT_SEARCH_IN_INDEX_INDEX_PARAMETER_TOOL_DESCRIPTION) + .to_string(); + drop(rtxn); + let mut response; loop { let mut tools = chat_completion.tools.get_or_insert_default(); From 2cd85c732a1d0981eb446663d20a208f817e4f7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Renault?= Date: Tue, 13 May 2025 16:35:46 +0200 Subject: [PATCH 004/103] Make it work by retrieving content from the index --- crates/meilisearch/src/routes/chat.rs | 64 +++++++++++++++------------ 1 file changed, 36 insertions(+), 28 deletions(-) diff --git a/crates/meilisearch/src/routes/chat.rs b/crates/meilisearch/src/routes/chat.rs index 3e5bf6957..8f0552561 100644 --- a/crates/meilisearch/src/routes/chat.rs +++ b/crates/meilisearch/src/routes/chat.rs @@ -88,34 +88,36 @@ async fn chat( let mut response; loop { - let mut tools = chat_completion.tools.get_or_insert_default(); - tools.push(ChatCompletionToolArgs::default() - .r#type(ChatCompletionToolType::Function) - .function(FunctionObjectArgs::default() - .name("searchInIndex") - .description(DEFAULT_SEARCH_IN_INDEX_TOOL_DESCRIPTION) - .parameters(json!({ - "type": "object", - "properties": { - "index_uid": { - "type": "string", - "enum": ["main"], - "description": DEFAULT_SEARCH_IN_INDEX_INDEX_PARAMETER_TOOL_DESCRIPTION, - }, - "q": { - "type": ["string", "null"], - "description": DEFAULT_SEARCH_IN_INDEX_Q_PARAMETER_TOOL_DESCRIPTION, - } - }, - "required": ["index_uid", "q"], - "additionalProperties": false, - })) - .strict(true) + let tools = chat_completion.tools.get_or_insert_default(); + tools.push( + ChatCompletionToolArgs::default() + .r#type(ChatCompletionToolType::Function) + .function( + FunctionObjectArgs::default() + .name("searchInIndex") + .description(&search_in_index_description) + .parameters(json!({ + "type": "object", + "properties": { + "index_uid": { + "type": "string", + "enum": ["main"], + "description": search_in_index_index_description, + }, + "q": { + "type": ["string", "null"], + "description": search_in_index_q_param_description, + } + }, + "required": ["index_uid", "q"], + "additionalProperties": false, + })) + .strict(true) + .build() + .unwrap(), + ) .build() .unwrap(), - ) - .build() - .unwrap() ); response = dbg!(client.chat().create(chat_completion.clone()).await.unwrap()); @@ -137,7 +139,7 @@ async fn chat( for call in meili_calls { let SearchInIndexParameters { index_uid, q } = - serde_json::from_str(dbg!(&call.function.arguments)).unwrap(); + serde_json::from_str(&call.function.arguments).unwrap(); let mut query = SearchQuery { q, @@ -145,6 +147,7 @@ async fn chat( semantic_ratio: SemanticRatio::default(), embedder: EMBEDDER_NAME.to_string(), }), + limit: 20, ..Default::default() }; @@ -241,10 +244,15 @@ fn format_documents(index: &Index, documents: impl Iterator) -> quantized: _, } = config; + #[derive(Serialize)] + struct Doc { + doc: T, + } + let template = liquid::ParserBuilder::with_stdlib().build().unwrap().parse(&template).unwrap(); documents .map(|doc| { - let object = liquid::to_object(&doc).unwrap(); + let object = liquid::to_object(&Doc { doc }).unwrap(); template.render(&object).unwrap() }) .collect() From 0f05c0eb6fbae67a2555a9f04c90b3e5fe5c2b83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Renault?= Date: Wed, 14 May 2025 11:18:21 +0200 Subject: [PATCH 005/103] Implement a first version of a streamed chat API --- Cargo.lock | 158 +++++++++++++++++++++++--- crates/meilisearch/Cargo.toml | 1 + crates/meilisearch/src/routes/chat.rs | 38 ++++++- 3 files changed, 180 insertions(+), 17 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c205bbb3d..6ce6b5186 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -27,7 +27,7 @@ checksum = "f9e772b3bcafe335042b5db010ab7c09013dad6eac4915c91d8d50902769f331" dependencies = [ "actix-utils", "actix-web", - "derive_more", + "derive_more 0.99.17", "futures-util", "log", "once_cell", @@ -36,24 +36,24 @@ dependencies = [ [[package]] name = "actix-http" -version = "3.9.0" +version = "3.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d48f96fc3003717aeb9856ca3d02a8c7de502667ad76eeacd830b48d2e91fac4" +checksum = "44dfe5c9e0004c623edc65391dfd51daa201e7e30ebd9c9bedf873048ec32bc2" dependencies = [ "actix-codec", "actix-rt", "actix-service", "actix-tls", "actix-utils", - "ahash 0.8.11", "base64 0.22.1", "bitflags 2.9.0", - "brotli", + "brotli 8.0.1", "bytes", "bytestring", - "derive_more", + "derive_more 2.0.1", "encoding_rs", "flate2", + "foldhash", "futures-core", "h2 0.3.26", "http 0.2.11", @@ -65,7 +65,7 @@ dependencies = [ "mime", "percent-encoding", "pin-project-lite", - "rand 0.8.5", + "rand 0.9.1", "sha1", "smallvec", "tokio", @@ -92,6 +92,7 @@ dependencies = [ "bytestring", "cfg-if", "http 0.2.11", + "regex", "regex-lite", "serde", "tracing", @@ -187,7 +188,7 @@ dependencies = [ "bytestring", "cfg-if", "cookie", - "derive_more", + "derive_more 0.99.17", "encoding_rs", "futures-core", "futures-util", @@ -220,6 +221,43 @@ dependencies = [ "syn 2.0.87", ] +[[package]] +name = "actix-web-lab" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a33034dd88446a5deb20e42156dbfe43d07e0499345db3ae65b3f51854190531" +dependencies = [ + "actix-http", + "actix-router", + "actix-service", + "actix-utils", + "actix-web", + "ahash 0.8.11", + "arc-swap", + "bytes", + "bytestring", + "csv", + "derive_more 2.0.1", + "form_urlencoded", + "futures-core", + "futures-util", + "http 0.2.11", + "impl-more", + "itertools 0.14.0", + "local-channel", + "mime", + "pin-project-lite", + "regex", + "serde", + "serde_html_form", + "serde_json", + "serde_path_to_error", + "tokio", + "tokio-stream", + "tracing", + "url", +] + [[package]] name = "addr2line" version = "0.20.0" @@ -391,6 +429,12 @@ dependencies = [ "derive_arbitrary", ] +[[package]] +name = "arc-swap" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" + [[package]] name = "arrayvec" version = "0.7.4" @@ -556,7 +600,7 @@ dependencies = [ "milli", "mimalloc", "rand 0.8.5", - "rand_chacha", + "rand_chacha 0.3.1", "reqwest", "roaring", "serde_json", @@ -708,7 +752,18 @@ checksum = "74f7971dbd9326d58187408ab83117d8ac1bb9c17b085fdacd1cf2f598719b6b" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", - "brotli-decompressor", + "brotli-decompressor 4.0.1", +] + +[[package]] +name = "brotli" +version = "8.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9991eea70ea4f293524138648e41ee89b0b2b12ddef3b255effa43c8056e0e0d" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", + "brotli-decompressor 5.0.0", ] [[package]] @@ -721,6 +776,16 @@ dependencies = [ "alloc-stdlib", ] +[[package]] +name = "brotli-decompressor" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "874bb8112abecc98cbd6d81ea4fa7e94fb9449648c93cc89aa40c81c24d7de03" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", +] + [[package]] name = "bstr" version = "1.11.3" @@ -1634,6 +1699,27 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "derive_more" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "093242cf7570c207c83073cf82f79706fe7b8317e98620a47d5be7c3d8497678" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", + "unicode-xid", +] + [[package]] name = "deserr" version = "0.6.3" @@ -2847,9 +2933,9 @@ dependencies = [ [[package]] name = "impl-more" -version = "0.1.6" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "206ca75c9c03ba3d4ace2460e57b189f39f43de612c2f85836e65c929701bb2d" +checksum = "e8a5a9a0ff0086c7a148acb942baaabeadf9504d10400b5a05645853729b9cd2" [[package]] name = "include-flate" @@ -3593,10 +3679,11 @@ dependencies = [ "actix-rt", "actix-utils", "actix-web", + "actix-web-lab", "anyhow", "async-openai", "async-trait", - "brotli", + "brotli 6.0.0", "bstr", "build-info", "byte-unit", @@ -4688,7 +4775,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", - "rand_chacha", + "rand_chacha 0.3.1", "rand_core 0.6.4", ] @@ -4698,6 +4785,7 @@ version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97" dependencies = [ + "rand_chacha 0.9.0", "rand_core 0.9.3", ] @@ -4711,6 +4799,16 @@ dependencies = [ "rand_core 0.6.4", ] +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.3", +] + [[package]] name = "rand_core" version = "0.6.4" @@ -4725,6 +4823,9 @@ name = "rand_core" version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +dependencies = [ + "getrandom 0.3.1", +] [[package]] name = "rand_distr" @@ -5304,6 +5405,19 @@ dependencies = [ "syn 2.0.87", ] +[[package]] +name = "serde_html_form" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d2de91cf02bbc07cde38891769ccd5d4f073d22a40683aa4bc7a95781aaa2c4" +dependencies = [ + "form_urlencoded", + "indexmap", + "itoa", + "ryu", + "serde", +] + [[package]] name = "serde_json" version = "1.0.140" @@ -5317,6 +5431,16 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_path_to_error" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59fab13f937fa393d08645bf3a84bdfe86e296747b506ada67bb15f10f218b2a" +dependencies = [ + "itoa", + "serde", +] + [[package]] name = "serde_plain" version = "1.0.2" @@ -6320,6 +6444,12 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + [[package]] name = "unicode_categories" version = "0.1.1" diff --git a/crates/meilisearch/Cargo.toml b/crates/meilisearch/Cargo.toml index a0ce49193..f7469e7ac 100644 --- a/crates/meilisearch/Cargo.toml +++ b/crates/meilisearch/Cargo.toml @@ -113,6 +113,7 @@ utoipa = { version = "5.3.1", features = [ ] } utoipa-scalar = { version = "0.3.0", optional = true, features = ["actix-web"] } async-openai = "0.28.1" +actix-web-lab = { version = "0.24.1", default-features = false } [dev-dependencies] actix-rt = "2.10.0" diff --git a/crates/meilisearch/src/routes/chat.rs b/crates/meilisearch/src/routes/chat.rs index 8f0552561..ad46d91c8 100644 --- a/crates/meilisearch/src/routes/chat.rs +++ b/crates/meilisearch/src/routes/chat.rs @@ -1,7 +1,8 @@ use std::mem; use actix_web::web::{self, Data}; -use actix_web::HttpResponse; +use actix_web::{Either, HttpResponse, Responder}; +use actix_web_lab::sse::{self, Event}; use async_openai::config::OpenAIConfig; use async_openai::types::{ ChatCompletionRequestAssistantMessageArgs, ChatCompletionRequestMessage, @@ -10,6 +11,7 @@ use async_openai::types::{ FunctionObjectArgs, }; use async_openai::Client; +use futures::StreamExt; use index_scheduler::IndexScheduler; use meilisearch_types::error::ResponseError; use meilisearch_types::keys::actions; @@ -53,10 +55,22 @@ async fn chat( index_scheduler: GuardedData, Data>, search_queue: web::Data, web::Json(mut chat_completion): web::Json, -) -> Result { +) -> impl Responder { // To enable later on, when the feature will be experimental // index_scheduler.features().check_chat("Using the /chat route")?; + if chat_completion.stream.unwrap_or(false) { + Either::Right(streamed_chat(index_scheduler, search_queue, chat_completion).await) + } else { + Either::Left(non_streamed_chat(index_scheduler, search_queue, chat_completion).await) + } +} + +async fn non_streamed_chat( + index_scheduler: GuardedData, Data>, + search_queue: web::Data, + mut chat_completion: CreateChatCompletionRequest, +) -> Result { let api_key = std::env::var("MEILI_OPENAI_API_KEY") .expect("cannot find OpenAI API Key (MEILI_OPENAI_API_KEY)"); let config = OpenAIConfig::default().with_api_key(&api_key); // we can also change the API base @@ -119,7 +133,7 @@ async fn chat( .build() .unwrap(), ); - response = dbg!(client.chat().create(chat_completion.clone()).await.unwrap()); + response = client.chat().create(chat_completion.clone()).await.unwrap(); let choice = &mut response.choices[0]; match choice.finish_reason { @@ -221,6 +235,24 @@ async fn chat( Ok(HttpResponse::Ok().json(response)) } +async fn streamed_chat( + index_scheduler: GuardedData, Data>, + search_queue: web::Data, + mut chat_completion: CreateChatCompletionRequest, +) -> impl Responder { + assert!(chat_completion.stream.unwrap_or(false)); + + let api_key = std::env::var("MEILI_OPENAI_API_KEY") + .expect("cannot find OpenAI API Key (MEILI_OPENAI_API_KEY)"); + let config = OpenAIConfig::default().with_api_key(&api_key); // we can also change the API base + let client = Client::with_config(config); + let response = client.chat().create_stream(chat_completion).await.unwrap(); + actix_web_lab::sse::Sse::from_stream(response.map(|response| { + response + .map(|mut r| Event::Data(sse::Data::new_json(r.choices.pop().unwrap().delta).unwrap())) + })) +} + #[derive(Deserialize)] struct SearchInIndexParameters { /// The index uid to search in. From d4a16f23496b270c22aaefbb71d054ed93f1d6ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Renault?= Date: Wed, 14 May 2025 11:53:03 +0200 Subject: [PATCH 006/103] Aggregate tool calls and display the calls to make. --- crates/meilisearch/src/routes/chat.rs | 127 +++++++++++++++++++++++--- 1 file changed, 113 insertions(+), 14 deletions(-) diff --git a/crates/meilisearch/src/routes/chat.rs b/crates/meilisearch/src/routes/chat.rs index ad46d91c8..4c9c9934b 100644 --- a/crates/meilisearch/src/routes/chat.rs +++ b/crates/meilisearch/src/routes/chat.rs @@ -1,3 +1,4 @@ +use std::collections::HashMap; use std::mem; use actix_web::web::{self, Data}; @@ -5,10 +6,11 @@ use actix_web::{Either, HttpResponse, Responder}; use actix_web_lab::sse::{self, Event}; use async_openai::config::OpenAIConfig; use async_openai::types::{ - ChatCompletionRequestAssistantMessageArgs, ChatCompletionRequestMessage, - ChatCompletionRequestToolMessage, ChatCompletionRequestToolMessageContent, + ChatCompletionMessageToolCallChunk, ChatCompletionRequestAssistantMessageArgs, + ChatCompletionRequestMessage, ChatCompletionRequestToolMessage, + ChatCompletionRequestToolMessageContent, ChatCompletionStreamResponseDelta, ChatCompletionToolArgs, ChatCompletionToolType, CreateChatCompletionRequest, FinishReason, - FunctionObjectArgs, + FunctionCallStream, FunctionObjectArgs, }; use async_openai::Client; use futures::StreamExt; @@ -59,6 +61,12 @@ async fn chat( // To enable later on, when the feature will be experimental // index_scheduler.features().check_chat("Using the /chat route")?; + assert_eq!( + chat_completion.n.unwrap_or(1), + 1, + "Meilisearch /chat only support one completion at a time (n = 1, n = null)" + ); + if chat_completion.stream.unwrap_or(false) { Either::Right(streamed_chat(index_scheduler, search_queue, chat_completion).await) } else { @@ -76,12 +84,6 @@ async fn non_streamed_chat( let config = OpenAIConfig::default().with_api_key(&api_key); // we can also change the API base let client = Client::with_config(config); - assert_eq!( - chat_completion.n.unwrap_or(1), - 1, - "Meilisearch /chat only support one completion at a time (n = 1, n = null)" - ); - let rtxn = index_scheduler.read_txn().unwrap(); let search_in_index_description = index_scheduler .chat_prompts(&rtxn, "searchInIndex-description") @@ -240,19 +242,116 @@ async fn streamed_chat( search_queue: web::Data, mut chat_completion: CreateChatCompletionRequest, ) -> impl Responder { - assert!(chat_completion.stream.unwrap_or(false)); - let api_key = std::env::var("MEILI_OPENAI_API_KEY") .expect("cannot find OpenAI API Key (MEILI_OPENAI_API_KEY)"); + + let rtxn = index_scheduler.read_txn().unwrap(); + let search_in_index_description = index_scheduler + .chat_prompts(&rtxn, "searchInIndex-description") + .unwrap() + .unwrap_or(DEFAULT_SEARCH_IN_INDEX_TOOL_DESCRIPTION) + .to_string(); + let search_in_index_q_param_description = index_scheduler + .chat_prompts(&rtxn, "searchInIndex-q-param-description") + .unwrap() + .unwrap_or(DEFAULT_SEARCH_IN_INDEX_Q_PARAMETER_TOOL_DESCRIPTION) + .to_string(); + let search_in_index_index_description = index_scheduler + .chat_prompts(&rtxn, "searchInIndex-index-param-description") + .unwrap() + .unwrap_or(DEFAULT_SEARCH_IN_INDEX_INDEX_PARAMETER_TOOL_DESCRIPTION) + .to_string(); + drop(rtxn); + + let tools = chat_completion.tools.get_or_insert_default(); + tools.push( + ChatCompletionToolArgs::default() + .r#type(ChatCompletionToolType::Function) + .function( + FunctionObjectArgs::default() + .name("searchInIndex") + .description(&search_in_index_description) + .parameters(json!({ + "type": "object", + "properties": { + "index_uid": { + "type": "string", + "enum": ["main"], + "description": search_in_index_index_description, + }, + "q": { + "type": ["string", "null"], + "description": search_in_index_q_param_description, + } + }, + "required": ["index_uid", "q"], + "additionalProperties": false, + })) + .strict(true) + .build() + .unwrap(), + ) + .build() + .unwrap(), + ); + let config = OpenAIConfig::default().with_api_key(&api_key); // we can also change the API base let client = Client::with_config(config); let response = client.chat().create_stream(chat_completion).await.unwrap(); - actix_web_lab::sse::Sse::from_stream(response.map(|response| { - response - .map(|mut r| Event::Data(sse::Data::new_json(r.choices.pop().unwrap().delta).unwrap())) + let mut global_tool_calls = HashMap::::new(); + actix_web_lab::sse::Sse::from_stream(response.map(move |response| { + response.map(|mut r| { + let delta = r.choices.pop().unwrap().delta; + let ChatCompletionStreamResponseDelta { + ref content, + ref function_call, + ref tool_calls, + ref role, + ref refusal, + } = delta; + + match tool_calls { + Some(tool_calls) => { + for chunk in tool_calls { + let ChatCompletionMessageToolCallChunk { index, id, r#type, function } = + chunk; + let FunctionCallStream { ref name, ref arguments } = + function.as_ref().unwrap(); + global_tool_calls + .entry(*index) + .or_insert_with(|| Call { + id: id.as_ref().unwrap().clone(), + function_name: name.as_ref().unwrap().clone(), + arguments: arguments.as_ref().unwrap().clone(), + }) + .append(arguments.as_ref().unwrap()); + } + } + None if !global_tool_calls.is_empty() => { + dbg!(&global_tool_calls); + } + _ => (), + } + + Event::Data(sse::Data::new_json(delta).unwrap()) + }) })) } +/// The structure used to aggregate the function calls to make. +#[derive(Debug)] +struct Call { + id: String, + function_name: String, + arguments: String, +} + +impl Call { + fn append(&mut self, arguments: &str) { + self.arguments.push_str(arguments); + } +} + #[derive(Deserialize)] struct SearchInIndexParameters { /// The index uid to search in. From 12355239188b9d2a6fd777f287736fc93536cb93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Renault?= Date: Wed, 14 May 2025 12:03:43 +0200 Subject: [PATCH 007/103] Return the right message format --- crates/meilisearch/src/routes/chat.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/meilisearch/src/routes/chat.rs b/crates/meilisearch/src/routes/chat.rs index 4c9c9934b..70d565b99 100644 --- a/crates/meilisearch/src/routes/chat.rs +++ b/crates/meilisearch/src/routes/chat.rs @@ -301,7 +301,7 @@ async fn streamed_chat( let mut global_tool_calls = HashMap::::new(); actix_web_lab::sse::Sse::from_stream(response.map(move |response| { response.map(|mut r| { - let delta = r.choices.pop().unwrap().delta; + let delta = &r.choices[0].delta; let ChatCompletionStreamResponseDelta { ref content, ref function_call, @@ -330,10 +330,10 @@ async fn streamed_chat( None if !global_tool_calls.is_empty() => { dbg!(&global_tool_calls); } - _ => (), + None => (), } - Event::Data(sse::Data::new_json(delta).unwrap()) + Event::Data(sse::Data::new_json(r).unwrap()) }) })) } From 5fab2aee512a9fecb9fd3a3d4d12a45cf35d16bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Renault?= Date: Wed, 14 May 2025 14:29:41 +0200 Subject: [PATCH 008/103] Nearly support tools on the streaming route --- crates/meilisearch/src/routes/chat.rs | 149 ++++++++++++++++++++++---- 1 file changed, 128 insertions(+), 21 deletions(-) diff --git a/crates/meilisearch/src/routes/chat.rs b/crates/meilisearch/src/routes/chat.rs index 70d565b99..207feb256 100644 --- a/crates/meilisearch/src/routes/chat.rs +++ b/crates/meilisearch/src/routes/chat.rs @@ -6,14 +6,16 @@ use actix_web::{Either, HttpResponse, Responder}; use actix_web_lab::sse::{self, Event}; use async_openai::config::OpenAIConfig; use async_openai::types::{ - ChatCompletionMessageToolCallChunk, ChatCompletionRequestAssistantMessageArgs, - ChatCompletionRequestMessage, ChatCompletionRequestToolMessage, - ChatCompletionRequestToolMessageContent, ChatCompletionStreamResponseDelta, - ChatCompletionToolArgs, ChatCompletionToolType, CreateChatCompletionRequest, FinishReason, - FunctionCallStream, FunctionObjectArgs, + ChatCompletionMessageToolCall, ChatCompletionMessageToolCallChunk, + ChatCompletionRequestAssistantMessageArgs, ChatCompletionRequestMessage, + ChatCompletionRequestToolMessage, ChatCompletionRequestToolMessageContent, + ChatCompletionStreamResponseDelta, ChatCompletionToolArgs, ChatCompletionToolType, + CreateChatCompletionRequest, FinishReason, FunctionCall, FunctionCallStream, + FunctionObjectArgs, }; use async_openai::Client; use futures::StreamExt; +use futures_util::stream; use index_scheduler::IndexScheduler; use meilisearch_types::error::ResponseError; use meilisearch_types::keys::actions; @@ -23,6 +25,7 @@ use meilisearch_types::milli::vector::EmbeddingConfig; use meilisearch_types::{Document, Index}; use serde::{Deserialize, Serialize}; use serde_json::json; +use tokio::runtime::Handle; use crate::extractors::authentication::policies::ActionPolicy; use crate::extractors::authentication::GuardedData; @@ -297,26 +300,25 @@ async fn streamed_chat( let config = OpenAIConfig::default().with_api_key(&api_key); // we can also change the API base let client = Client::with_config(config); - let response = client.chat().create_stream(chat_completion).await.unwrap(); + let response = client.chat().create_stream(chat_completion.clone()).await.unwrap(); let mut global_tool_calls = HashMap::::new(); - actix_web_lab::sse::Sse::from_stream(response.map(move |response| { - response.map(|mut r| { - let delta = &r.choices[0].delta; + actix_web_lab::sse::Sse::from_stream(response.flat_map(move |response| match response { + Ok(resp) => { + let delta = &resp.choices[0].delta; let ChatCompletionStreamResponseDelta { - ref content, - ref function_call, + content: _, + function_call: _, ref tool_calls, - ref role, - ref refusal, + role: _, + refusal: _, } = delta; match tool_calls { Some(tool_calls) => { for chunk in tool_calls { - let ChatCompletionMessageToolCallChunk { index, id, r#type, function } = + let ChatCompletionMessageToolCallChunk { index, id, r#type: _, function } = chunk; - let FunctionCallStream { ref name, ref arguments } = - function.as_ref().unwrap(); + let FunctionCallStream { name, arguments } = function.as_ref().unwrap(); global_tool_calls .entry(*index) .or_insert_with(|| Call { @@ -326,15 +328,120 @@ async fn streamed_chat( }) .append(arguments.as_ref().unwrap()); } + stream::iter(vec![Ok(Event::Data(sse::Data::new_json(resp).unwrap()))]) } None if !global_tool_calls.is_empty() => { dbg!(&global_tool_calls); - } - None => (), - } - Event::Data(sse::Data::new_json(r).unwrap()) - }) + let (meili_calls, other_calls): (Vec<_>, Vec<_>) = + mem::take(&mut global_tool_calls) + .into_iter() + .map(|(_, call)| ChatCompletionMessageToolCall { + id: call.id, + r#type: ChatCompletionToolType::Function, + function: FunctionCall { + name: call.function_name, + arguments: call.arguments, + }, + }) + .partition(|call| call.function.name == "searchInIndex"); + + chat_completion.messages.push( + ChatCompletionRequestAssistantMessageArgs::default() + .tool_calls(meili_calls.clone()) + .build() + .unwrap() + .into(), + ); + + for call in meili_calls { + let SearchInIndexParameters { index_uid, q } = + serde_json::from_str(&call.function.arguments).unwrap(); + + let mut query = SearchQuery { + q, + hybrid: Some(HybridQuery { + semantic_ratio: SemanticRatio::default(), + embedder: EMBEDDER_NAME.to_string(), + }), + limit: 20, + ..Default::default() + }; + + // Tenant token search_rules. + if let Some(search_rules) = + index_scheduler.filters().get_index_search_rules(&index_uid) + { + add_search_rules(&mut query.filter, search_rules); + } + + // TBD + // let mut aggregate = SearchAggregator::::from_query(&query); + + let index = index_scheduler.index(&index_uid).unwrap(); + let search_kind = search_kind( + &query, + index_scheduler.get_ref(), + index_uid.to_string(), + &index, + ) + .unwrap(); + + // let permit = search_queue.try_get_search_permit().await?; + let features = index_scheduler.features(); + let index_cloned = index.clone(); + // let search_result = tokio::task::spawn_blocking(move || { + let search_result = perform_search( + index_uid.to_string(), + &index_cloned, + query, + search_kind, + RetrieveVectors::new(false), + features, + ); + // }) + // .await; + // permit.drop().await; + + // let search_result = search_result.unwrap(); + if let Ok(ref search_result) = search_result { + // aggregate.succeed(search_result); + if search_result.degraded { + MEILISEARCH_DEGRADED_SEARCH_REQUESTS.inc(); + } + } + // analytics.publish(aggregate, &req); + + let search_result = search_result.unwrap(); + let formatted = format_documents( + &index, + search_result.hits.into_iter().map(|doc| doc.document), + ); + let text = formatted.join("\n"); + chat_completion.messages.push(ChatCompletionRequestMessage::Tool( + ChatCompletionRequestToolMessage { + tool_call_id: call.id, + content: ChatCompletionRequestToolMessageContent::Text(text), + }, + )); + } + + let response = Handle::current().block_on(async { + client.chat().create_stream(chat_completion.clone()).await.unwrap() + }); + + // stream::iter(vec![ + // Ok(Event::Data(sse::Data::new_json(json!({ "text": "Hello" })).unwrap())), + // Ok(Event::Data(sse::Data::new_json(json!({ "text": " world" })).unwrap())), + // Ok(Event::Data(sse::Data::new_json(json!({ "text": " !" })).unwrap())), + // ]) + + response + } + None => stream::iter(vec![Ok(Event::Data(sse::Data::new_json(resp).unwrap()))]), + } + } + Err(err) => stream::iter(vec![Err(err)]), })) } From aef8448fc62bb1e6905055d19cb451a971da20a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Renault?= Date: Wed, 14 May 2025 14:58:01 +0200 Subject: [PATCH 009/103] Streaming supports tool calling --- crates/meilisearch/src/routes/chat.rs | 293 ++++++++++++++------------ 1 file changed, 158 insertions(+), 135 deletions(-) diff --git a/crates/meilisearch/src/routes/chat.rs b/crates/meilisearch/src/routes/chat.rs index 207feb256..d2def9488 100644 --- a/crates/meilisearch/src/routes/chat.rs +++ b/crates/meilisearch/src/routes/chat.rs @@ -1,9 +1,10 @@ use std::collections::HashMap; use std::mem; +use std::time::Duration; use actix_web::web::{self, Data}; use actix_web::{Either, HttpResponse, Responder}; -use actix_web_lab::sse::{self, Event}; +use actix_web_lab::sse::{self, Event, Sse}; use async_openai::config::OpenAIConfig; use async_openai::types::{ ChatCompletionMessageToolCall, ChatCompletionMessageToolCallChunk, @@ -15,7 +16,6 @@ use async_openai::types::{ }; use async_openai::Client; use futures::StreamExt; -use futures_util::stream; use index_scheduler::IndexScheduler; use meilisearch_types::error::ResponseError; use meilisearch_types::keys::actions; @@ -59,7 +59,7 @@ pub fn configure(cfg: &mut web::ServiceConfig) { async fn chat( index_scheduler: GuardedData, Data>, search_queue: web::Data, - web::Json(mut chat_completion): web::Json, + web::Json(chat_completion): web::Json, ) -> impl Responder { // To enable later on, when the feature will be experimental // index_scheduler.features().check_chat("Using the /chat route")?; @@ -298,151 +298,174 @@ async fn streamed_chat( .unwrap(), ); - let config = OpenAIConfig::default().with_api_key(&api_key); // we can also change the API base - let client = Client::with_config(config); - let response = client.chat().create_stream(chat_completion.clone()).await.unwrap(); - let mut global_tool_calls = HashMap::::new(); - actix_web_lab::sse::Sse::from_stream(response.flat_map(move |response| match response { - Ok(resp) => { - let delta = &resp.choices[0].delta; - let ChatCompletionStreamResponseDelta { - content: _, - function_call: _, - ref tool_calls, - role: _, - refusal: _, - } = delta; + let (tx, rx) = tokio::sync::mpsc::channel(10); + let _join_handle = Handle::current().spawn(async move { + let config = OpenAIConfig::default().with_api_key(&api_key); // we can also change the API base + let client = Client::with_config(config); + let mut global_tool_calls = HashMap::::new(); - match tool_calls { - Some(tool_calls) => { - for chunk in tool_calls { - let ChatCompletionMessageToolCallChunk { index, id, r#type: _, function } = - chunk; - let FunctionCallStream { name, arguments } = function.as_ref().unwrap(); - global_tool_calls - .entry(*index) - .or_insert_with(|| Call { - id: id.as_ref().unwrap().clone(), - function_name: name.as_ref().unwrap().clone(), - arguments: arguments.as_ref().unwrap().clone(), - }) - .append(arguments.as_ref().unwrap()); - } - stream::iter(vec![Ok(Event::Data(sse::Data::new_json(resp).unwrap()))]) - } - None if !global_tool_calls.is_empty() => { - dbg!(&global_tool_calls); + 'main: loop { + let mut response = client.chat().create_stream(chat_completion.clone()).await.unwrap(); - let (meili_calls, other_calls): (Vec<_>, Vec<_>) = - mem::take(&mut global_tool_calls) - .into_iter() - .map(|(_, call)| ChatCompletionMessageToolCall { - id: call.id, - r#type: ChatCompletionToolType::Function, - function: FunctionCall { - name: call.function_name, - arguments: call.arguments, - }, - }) - .partition(|call| call.function.name == "searchInIndex"); + while let Some(result) = response.next().await { + match result { + Ok(resp) => { + let delta = &resp.choices[0].delta; + let ChatCompletionStreamResponseDelta { + content, + function_call: _, + ref tool_calls, + role: _, + refusal: _, + } = delta; - chat_completion.messages.push( - ChatCompletionRequestAssistantMessageArgs::default() - .tool_calls(meili_calls.clone()) - .build() - .unwrap() - .into(), - ); - - for call in meili_calls { - let SearchInIndexParameters { index_uid, q } = - serde_json::from_str(&call.function.arguments).unwrap(); - - let mut query = SearchQuery { - q, - hybrid: Some(HybridQuery { - semantic_ratio: SemanticRatio::default(), - embedder: EMBEDDER_NAME.to_string(), - }), - limit: 20, - ..Default::default() - }; - - // Tenant token search_rules. - if let Some(search_rules) = - index_scheduler.filters().get_index_search_rules(&index_uid) + if content.is_none() && tool_calls.is_none() && global_tool_calls.is_empty() { - add_search_rules(&mut query.filter, search_rules); + break 'main; } - // TBD - // let mut aggregate = SearchAggregator::::from_query(&query); + if let Some(text) = content { + tx.send(Event::Data(sse::Data::new_json(&resp).unwrap())).await.unwrap() + } - let index = index_scheduler.index(&index_uid).unwrap(); - let search_kind = search_kind( - &query, - index_scheduler.get_ref(), - index_uid.to_string(), - &index, - ) - .unwrap(); - - // let permit = search_queue.try_get_search_permit().await?; - let features = index_scheduler.features(); - let index_cloned = index.clone(); - // let search_result = tokio::task::spawn_blocking(move || { - let search_result = perform_search( - index_uid.to_string(), - &index_cloned, - query, - search_kind, - RetrieveVectors::new(false), - features, - ); - // }) - // .await; - // permit.drop().await; - - // let search_result = search_result.unwrap(); - if let Ok(ref search_result) = search_result { - // aggregate.succeed(search_result); - if search_result.degraded { - MEILISEARCH_DEGRADED_SEARCH_REQUESTS.inc(); + match tool_calls { + Some(tool_calls) => { + for chunk in tool_calls { + let ChatCompletionMessageToolCallChunk { + index, + id, + r#type: _, + function, + } = chunk; + let FunctionCallStream { name, arguments } = + function.as_ref().unwrap(); + global_tool_calls + .entry(*index) + .or_insert_with(|| Call { + id: id.as_ref().unwrap().clone(), + function_name: name.as_ref().unwrap().clone(), + arguments: arguments.as_ref().unwrap().clone(), + }) + .append(arguments.as_ref().unwrap()); + } + tx.send(Event::Data(sse::Data::new_json(&resp).unwrap())) + .await + .unwrap() } + None if !global_tool_calls.is_empty() => { + // dbg!(&global_tool_calls); + + let (meili_calls, other_calls): (Vec<_>, Vec<_>) = + mem::take(&mut global_tool_calls) + .into_iter() + .map(|(_, call)| ChatCompletionMessageToolCall { + id: call.id, + r#type: ChatCompletionToolType::Function, + function: FunctionCall { + name: call.function_name, + arguments: call.arguments, + }, + }) + .partition(|call| call.function.name == "searchInIndex"); + + chat_completion.messages.push( + ChatCompletionRequestAssistantMessageArgs::default() + .tool_calls(meili_calls.clone()) + .build() + .unwrap() + .into(), + ); + + for call in meili_calls { + let SearchInIndexParameters { index_uid, q } = + serde_json::from_str(&call.function.arguments).unwrap(); + + let mut query = SearchQuery { + q, + hybrid: Some(HybridQuery { + semantic_ratio: SemanticRatio::default(), + embedder: EMBEDDER_NAME.to_string(), + }), + limit: 20, + ..Default::default() + }; + + // Tenant token search_rules. + if let Some(search_rules) = + index_scheduler.filters().get_index_search_rules(&index_uid) + { + add_search_rules(&mut query.filter, search_rules); + } + + // TBD + // let mut aggregate = SearchAggregator::::from_query(&query); + + let index = index_scheduler.index(&index_uid).unwrap(); + let search_kind = search_kind( + &query, + index_scheduler.get_ref(), + index_uid.to_string(), + &index, + ) + .unwrap(); + + let permit = + search_queue.try_get_search_permit().await.unwrap(); + let features = index_scheduler.features(); + let index_cloned = index.clone(); + let search_result = tokio::task::spawn_blocking(move || { + perform_search( + index_uid.to_string(), + &index_cloned, + query, + search_kind, + RetrieveVectors::new(false), + features, + ) + }) + .await; + permit.drop().await; + + let search_result = search_result.unwrap(); + if let Ok(ref search_result) = search_result { + // aggregate.succeed(search_result); + if search_result.degraded { + MEILISEARCH_DEGRADED_SEARCH_REQUESTS.inc(); + } + } + // analytics.publish(aggregate, &req); + + let search_result = search_result.unwrap(); + let formatted = format_documents( + &index, + search_result.hits.into_iter().map(|doc| doc.document), + ); + let text = formatted.join("\n"); + chat_completion.messages.push( + ChatCompletionRequestMessage::Tool( + ChatCompletionRequestToolMessage { + tool_call_id: call.id, + content: + ChatCompletionRequestToolMessageContent::Text( + text, + ), + }, + ), + ); + } + } + None => (), } - // analytics.publish(aggregate, &req); - - let search_result = search_result.unwrap(); - let formatted = format_documents( - &index, - search_result.hits.into_iter().map(|doc| doc.document), - ); - let text = formatted.join("\n"); - chat_completion.messages.push(ChatCompletionRequestMessage::Tool( - ChatCompletionRequestToolMessage { - tool_call_id: call.id, - content: ChatCompletionRequestToolMessageContent::Text(text), - }, - )); } - - let response = Handle::current().block_on(async { - client.chat().create_stream(chat_completion.clone()).await.unwrap() - }); - - // stream::iter(vec![ - // Ok(Event::Data(sse::Data::new_json(json!({ "text": "Hello" })).unwrap())), - // Ok(Event::Data(sse::Data::new_json(json!({ "text": " world" })).unwrap())), - // Ok(Event::Data(sse::Data::new_json(json!({ "text": " !" })).unwrap())), - // ]) - - response + Err(_err) => { + // writeln!(lock, "error: {err}").unwrap(); + } } - None => stream::iter(vec![Ok(Event::Data(sse::Data::new_json(resp).unwrap()))]), } } - Err(err) => stream::iter(vec![Err(err)]), - })) + }); + + Sse::from_infallible_receiver(rx).with_retry_duration(Duration::from_secs(10)) } /// The structure used to aggregate the function calls to make. From 511eef87bf59a98e2ce6eaae52ebebd2352a6104 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Renault?= Date: Wed, 14 May 2025 17:15:32 +0200 Subject: [PATCH 010/103] Send an event with the content of the tool calling --- crates/meilisearch/src/routes/chat.rs | 32 +++++++++++++++------------ 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/crates/meilisearch/src/routes/chat.rs b/crates/meilisearch/src/routes/chat.rs index d2def9488..f340b3449 100644 --- a/crates/meilisearch/src/routes/chat.rs +++ b/crates/meilisearch/src/routes/chat.rs @@ -324,7 +324,7 @@ async fn streamed_chat( break 'main; } - if let Some(text) = content { + if let Some(_) = content { tx.send(Event::Data(sse::Data::new_json(&resp).unwrap())).await.unwrap() } @@ -348,9 +348,6 @@ async fn streamed_chat( }) .append(arguments.as_ref().unwrap()); } - tx.send(Event::Data(sse::Data::new_json(&resp).unwrap())) - .await - .unwrap() } None if !global_tool_calls.is_empty() => { // dbg!(&global_tool_calls); @@ -441,17 +438,24 @@ async fn streamed_chat( search_result.hits.into_iter().map(|doc| doc.document), ); let text = formatted.join("\n"); - chat_completion.messages.push( - ChatCompletionRequestMessage::Tool( - ChatCompletionRequestToolMessage { - tool_call_id: call.id, - content: - ChatCompletionRequestToolMessageContent::Text( - text, - ), - }, - ), + let tool = ChatCompletionRequestMessage::Tool( + ChatCompletionRequestToolMessage { + tool_call_id: call.id, + content: ChatCompletionRequestToolMessageContent::Text( + text, + ), + }, ); + tx.send(Event::Data( + sse::Data::new_json(&json!({ + "object": "chat.completion.tool.event", + "tool": tool, + })) + .unwrap(), + )) + .await + .unwrap(); + chat_completion.messages.push(tool); } } None => (), From 148816a3da0fdabaf4038a4b1d818700ba063d0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Renault?= Date: Thu, 15 May 2025 11:17:34 +0200 Subject: [PATCH 011/103] Display the different tool calls we need to do --- crates/meilisearch/src/routes/chat.rs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/crates/meilisearch/src/routes/chat.rs b/crates/meilisearch/src/routes/chat.rs index f340b3449..8db6a1dde 100644 --- a/crates/meilisearch/src/routes/chat.rs +++ b/crates/meilisearch/src/routes/chat.rs @@ -374,6 +374,16 @@ async fn streamed_chat( ); for call in meili_calls { + tx.send(Event::Data( + sse::Data::new_json(&json!({ + "object": "chat.completion.tool.call", + "tool": call, + })) + .unwrap(), + )) + .await + .unwrap(); + let SearchInIndexParameters { index_uid, q } = serde_json::from_str(&call.function.arguments).unwrap(); @@ -448,7 +458,7 @@ async fn streamed_chat( ); tx.send(Event::Data( sse::Data::new_json(&json!({ - "object": "chat.completion.tool.event", + "object": "chat.completion.tool.output", "tool": tool, })) .unwrap(), From 77e03e3f8cf04b2281081339bb110b2564745acf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Renault?= Date: Thu, 15 May 2025 15:39:38 +0200 Subject: [PATCH 012/103] Factorise a bit the code --- crates/meilisearch/src/routes/chat.rs | 376 ++++++++++++-------------- 1 file changed, 169 insertions(+), 207 deletions(-) diff --git a/crates/meilisearch/src/routes/chat.rs b/crates/meilisearch/src/routes/chat.rs index 8db6a1dde..e4a9b65e2 100644 --- a/crates/meilisearch/src/routes/chat.rs +++ b/crates/meilisearch/src/routes/chat.rs @@ -55,6 +55,14 @@ pub fn configure(cfg: &mut web::ServiceConfig) { cfg.service(web::resource("").route(web::post().to(chat))); } +/// Creates OpenAI client with API key +fn create_openai_client() -> Client { + let api_key = std::env::var("MEILI_OPENAI_API_KEY") + .expect("cannot find OpenAI API Key (MEILI_OPENAI_API_KEY)"); + let config = OpenAIConfig::default().with_api_key(&api_key); + Client::with_config(config) +} + /// Get a chat completion async fn chat( index_scheduler: GuardedData, Data>, @@ -77,16 +85,112 @@ async fn chat( } } -async fn non_streamed_chat( - index_scheduler: GuardedData, Data>, - search_queue: web::Data, - mut chat_completion: CreateChatCompletionRequest, -) -> Result { - let api_key = std::env::var("MEILI_OPENAI_API_KEY") - .expect("cannot find OpenAI API Key (MEILI_OPENAI_API_KEY)"); - let config = OpenAIConfig::default().with_api_key(&api_key); // we can also change the API base - let client = Client::with_config(config); +/// Setup search tool in chat completion request +fn setup_search_tool( + chat_completion: &mut CreateChatCompletionRequest, + search_in_index_description: &str, + search_in_index_q_param_description: &str, + search_in_index_index_description: &str, +) { + let tools = chat_completion.tools.get_or_insert_default(); + tools.push( + ChatCompletionToolArgs::default() + .r#type(ChatCompletionToolType::Function) + .function( + FunctionObjectArgs::default() + .name("searchInIndex") + .description(search_in_index_description) + .parameters(json!({ + "type": "object", + "properties": { + "index_uid": { + "type": "string", + "enum": ["main"], + "description": search_in_index_index_description, + }, + "q": { + "type": ["string", "null"], + "description": search_in_index_q_param_description, + } + }, + "required": ["index_uid", "q"], + "additionalProperties": false, + })) + .strict(true) + .build() + .unwrap(), + ) + .build() + .unwrap(), + ); +} +/// Process search request and return formatted results +async fn process_search_request( + index_scheduler: &GuardedData, Data>, + search_queue: &web::Data, + index_uid: String, + q: Option, +) -> Result<(Index, String), ResponseError> { + let mut query = SearchQuery { + q, + hybrid: Some(HybridQuery { + semantic_ratio: SemanticRatio::default(), + embedder: EMBEDDER_NAME.to_string(), + }), + limit: 20, + ..Default::default() + }; + + // Tenant token search_rules. + if let Some(search_rules) = index_scheduler.filters().get_index_search_rules(&index_uid) { + add_search_rules(&mut query.filter, search_rules); + } + + // TBD + // let mut aggregate = SearchAggregator::::from_query(&query); + + let index = index_scheduler.index(&index_uid)?; + let search_kind = + search_kind(&query, index_scheduler.get_ref(), index_uid.to_string(), &index)?; + + let permit = search_queue.try_get_search_permit().await?; + let features = index_scheduler.features(); + let index_cloned = index.clone(); + let search_result = tokio::task::spawn_blocking(move || { + perform_search( + index_uid.to_string(), + &index_cloned, + query, + search_kind, + RetrieveVectors::new(false), + features, + ) + }) + .await; + permit.drop().await; + + let search_result = search_result?; + if let Ok(ref search_result) = search_result { + // aggregate.succeed(search_result); + if search_result.degraded { + MEILISEARCH_DEGRADED_SEARCH_REQUESTS.inc(); + } + } + // analytics.publish(aggregate, &req); + + let search_result = search_result?; + let formatted = + format_documents(&index, search_result.hits.into_iter().map(|doc| doc.document)); + let text = formatted.join("\n"); + + Ok((index, text)) +} + +/// Get prompt descriptions from index scheduler +fn get_prompt_descriptions( + index_scheduler: &GuardedData, Data>, +) -> (String, String, String) { let rtxn = index_scheduler.read_txn().unwrap(); let search_in_index_description = index_scheduler .chat_prompts(&rtxn, "searchInIndex-description") @@ -105,39 +209,35 @@ async fn non_streamed_chat( .to_string(); drop(rtxn); + ( + search_in_index_description, + search_in_index_q_param_description, + search_in_index_index_description, + ) +} + +async fn non_streamed_chat( + index_scheduler: GuardedData, Data>, + search_queue: web::Data, + mut chat_completion: CreateChatCompletionRequest, +) -> Result { + let client = create_openai_client(); + + let ( + search_in_index_description, + search_in_index_q_param_description, + search_in_index_index_description, + ) = get_prompt_descriptions(&index_scheduler); + let mut response; loop { - let tools = chat_completion.tools.get_or_insert_default(); - tools.push( - ChatCompletionToolArgs::default() - .r#type(ChatCompletionToolType::Function) - .function( - FunctionObjectArgs::default() - .name("searchInIndex") - .description(&search_in_index_description) - .parameters(json!({ - "type": "object", - "properties": { - "index_uid": { - "type": "string", - "enum": ["main"], - "description": search_in_index_index_description, - }, - "q": { - "type": ["string", "null"], - "description": search_in_index_q_param_description, - } - }, - "required": ["index_uid", "q"], - "additionalProperties": false, - })) - .strict(true) - .build() - .unwrap(), - ) - .build() - .unwrap(), + setup_search_tool( + &mut chat_completion, + &search_in_index_description, + &search_in_index_q_param_description, + &search_in_index_index_description, ); + response = client.chat().create(chat_completion.clone()).await.unwrap(); let choice = &mut response.choices[0]; @@ -160,65 +260,10 @@ async fn non_streamed_chat( let SearchInIndexParameters { index_uid, q } = serde_json::from_str(&call.function.arguments).unwrap(); - let mut query = SearchQuery { - q, - hybrid: Some(HybridQuery { - semantic_ratio: SemanticRatio::default(), - embedder: EMBEDDER_NAME.to_string(), - }), - limit: 20, - ..Default::default() - }; + let (_, text) = + process_search_request(&index_scheduler, &search_queue, index_uid, q) + .await?; - // Tenant token search_rules. - if let Some(search_rules) = - index_scheduler.filters().get_index_search_rules(&index_uid) - { - add_search_rules(&mut query.filter, search_rules); - } - - // TBD - // let mut aggregate = SearchAggregator::::from_query(&query); - - let index = index_scheduler.index(&index_uid)?; - let search_kind = search_kind( - &query, - index_scheduler.get_ref(), - index_uid.to_string(), - &index, - )?; - - let permit = search_queue.try_get_search_permit().await?; - let features = index_scheduler.features(); - let index_cloned = index.clone(); - let search_result = tokio::task::spawn_blocking(move || { - perform_search( - index_uid.to_string(), - &index_cloned, - query, - search_kind, - RetrieveVectors::new(false), - features, - ) - }) - .await; - permit.drop().await; - - let search_result = search_result?; - if let Ok(ref search_result) = search_result { - // aggregate.succeed(search_result); - if search_result.degraded { - MEILISEARCH_DEGRADED_SEARCH_REQUESTS.inc(); - } - } - // analytics.publish(aggregate, &req); - - let search_result = search_result?; - let formatted = format_documents( - &index, - search_result.hits.into_iter().map(|doc| doc.document), - ); - let text = formatted.join("\n"); chat_completion.messages.push(ChatCompletionRequestMessage::Tool( ChatCompletionRequestToolMessage { tool_call_id: call.id, @@ -245,63 +290,22 @@ async fn streamed_chat( search_queue: web::Data, mut chat_completion: CreateChatCompletionRequest, ) -> impl Responder { - let api_key = std::env::var("MEILI_OPENAI_API_KEY") - .expect("cannot find OpenAI API Key (MEILI_OPENAI_API_KEY)"); + let ( + search_in_index_description, + search_in_index_q_param_description, + search_in_index_index_description, + ) = get_prompt_descriptions(&index_scheduler); - let rtxn = index_scheduler.read_txn().unwrap(); - let search_in_index_description = index_scheduler - .chat_prompts(&rtxn, "searchInIndex-description") - .unwrap() - .unwrap_or(DEFAULT_SEARCH_IN_INDEX_TOOL_DESCRIPTION) - .to_string(); - let search_in_index_q_param_description = index_scheduler - .chat_prompts(&rtxn, "searchInIndex-q-param-description") - .unwrap() - .unwrap_or(DEFAULT_SEARCH_IN_INDEX_Q_PARAMETER_TOOL_DESCRIPTION) - .to_string(); - let search_in_index_index_description = index_scheduler - .chat_prompts(&rtxn, "searchInIndex-index-param-description") - .unwrap() - .unwrap_or(DEFAULT_SEARCH_IN_INDEX_INDEX_PARAMETER_TOOL_DESCRIPTION) - .to_string(); - drop(rtxn); - - let tools = chat_completion.tools.get_or_insert_default(); - tools.push( - ChatCompletionToolArgs::default() - .r#type(ChatCompletionToolType::Function) - .function( - FunctionObjectArgs::default() - .name("searchInIndex") - .description(&search_in_index_description) - .parameters(json!({ - "type": "object", - "properties": { - "index_uid": { - "type": "string", - "enum": ["main"], - "description": search_in_index_index_description, - }, - "q": { - "type": ["string", "null"], - "description": search_in_index_q_param_description, - } - }, - "required": ["index_uid", "q"], - "additionalProperties": false, - })) - .strict(true) - .build() - .unwrap(), - ) - .build() - .unwrap(), + setup_search_tool( + &mut chat_completion, + &search_in_index_description, + &search_in_index_q_param_description, + &search_in_index_index_description, ); let (tx, rx) = tokio::sync::mpsc::channel(10); let _join_handle = Handle::current().spawn(async move { - let config = OpenAIConfig::default().with_api_key(&api_key); // we can also change the API base - let client = Client::with_config(config); + let client = create_openai_client(); let mut global_tool_calls = HashMap::::new(); 'main: loop { @@ -313,7 +317,9 @@ async fn streamed_chat( let delta = &resp.choices[0].delta; let ChatCompletionStreamResponseDelta { content, - function_call: _, + // Using deprecated field but keeping for compatibility + #[allow(deprecated)] + function_call: _, ref tool_calls, role: _, refusal: _, @@ -352,7 +358,7 @@ async fn streamed_chat( None if !global_tool_calls.is_empty() => { // dbg!(&global_tool_calls); - let (meili_calls, other_calls): (Vec<_>, Vec<_>) = + let (meili_calls, _other_calls): (Vec<_>, Vec<_>) = mem::take(&mut global_tool_calls) .into_iter() .map(|(_, call)| ChatCompletionMessageToolCall { @@ -387,67 +393,23 @@ async fn streamed_chat( let SearchInIndexParameters { index_uid, q } = serde_json::from_str(&call.function.arguments).unwrap(); - let mut query = SearchQuery { + let result = process_search_request( + &index_scheduler, + &search_queue, + index_uid, q, - hybrid: Some(HybridQuery { - semantic_ratio: SemanticRatio::default(), - embedder: EMBEDDER_NAME.to_string(), - }), - limit: 20, - ..Default::default() - }; - - // Tenant token search_rules. - if let Some(search_rules) = - index_scheduler.filters().get_index_search_rules(&index_uid) - { - add_search_rules(&mut query.filter, search_rules); - } - - // TBD - // let mut aggregate = SearchAggregator::::from_query(&query); - - let index = index_scheduler.index(&index_uid).unwrap(); - let search_kind = search_kind( - &query, - index_scheduler.get_ref(), - index_uid.to_string(), - &index, ) - .unwrap(); - - let permit = - search_queue.try_get_search_permit().await.unwrap(); - let features = index_scheduler.features(); - let index_cloned = index.clone(); - let search_result = tokio::task::spawn_blocking(move || { - perform_search( - index_uid.to_string(), - &index_cloned, - query, - search_kind, - RetrieveVectors::new(false), - features, - ) - }) .await; - permit.drop().await; - let search_result = search_result.unwrap(); - if let Ok(ref search_result) = search_result { - // aggregate.succeed(search_result); - if search_result.degraded { - MEILISEARCH_DEGRADED_SEARCH_REQUESTS.inc(); - } + // Handle potential errors more explicitly + if let Err(err) = &result { + // Log the error or handle it as needed + eprintln!("Error processing search request: {:?}", err); + continue; } - // analytics.publish(aggregate, &req); - let search_result = search_result.unwrap(); - let formatted = format_documents( - &index, - search_result.hits.into_iter().map(|doc| doc.document), - ); - let text = formatted.join("\n"); + let (_, text) = result.unwrap(); + let tool = ChatCompletionRequestMessage::Tool( ChatCompletionRequestToolMessage { tool_call_id: call.id, @@ -515,7 +477,7 @@ fn format_documents(index: &Index, documents: impl Iterator) -> let EmbeddingConfig { embedder_options: _, - prompt: PromptData { template, max_bytes }, + prompt: PromptData { template, max_bytes: _ }, quantized: _, } = config; From a52b513023b2454eba701ec6a9de70d44d7d029e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Renault?= Date: Thu, 15 May 2025 17:40:55 +0200 Subject: [PATCH 013/103] Expose new chat settings routes --- crates/index-scheduler/src/lib.rs | 23 +-- crates/meilisearch-types/src/keys.rs | 8 ++ crates/meilisearch/src/routes/chat.rs | 136 +++++++----------- crates/meilisearch/src/routes/mod.rs | 4 +- .../meilisearch/src/routes/settings/chat.rs | 111 ++++++++++++++ crates/meilisearch/src/routes/settings/mod.rs | 1 + 6 files changed, 191 insertions(+), 92 deletions(-) create mode 100644 crates/meilisearch/src/routes/settings/chat.rs create mode 100644 crates/meilisearch/src/routes/settings/mod.rs diff --git a/crates/index-scheduler/src/lib.rs b/crates/index-scheduler/src/lib.rs index 11572cd06..15766c691 100644 --- a/crates/index-scheduler/src/lib.rs +++ b/crates/index-scheduler/src/lib.rs @@ -53,7 +53,7 @@ use flate2::Compression; use meilisearch_types::batches::Batch; use meilisearch_types::features::{InstanceTogglableFeatures, Network, RuntimeTogglableFeatures}; use meilisearch_types::heed::byteorder::BE; -use meilisearch_types::heed::types::{Str, I128}; +use meilisearch_types::heed::types::{SerdeJson, Str, I128}; use meilisearch_types::heed::{self, Database, Env, RoTxn, WithoutTls}; use meilisearch_types::milli::index::IndexEmbeddingConfig; use meilisearch_types::milli::update::IndexerConfig; @@ -153,8 +153,8 @@ pub struct IndexScheduler { /// In charge of fetching and setting the status of experimental features. features: features::FeatureData, - /// Stores the custom prompts for the chat - chat_prompts: Database, + /// Stores the custom chat prompts and other settings of the indexes. + chat_settings: Database>, /// Everything related to the processing of the tasks pub scheduler: scheduler::Scheduler, @@ -214,7 +214,7 @@ impl IndexScheduler { #[cfg(test)] run_loop_iteration: self.run_loop_iteration.clone(), features: self.features.clone(), - chat_prompts: self.chat_prompts.clone(), + chat_settings: self.chat_settings.clone(), } } @@ -277,7 +277,7 @@ impl IndexScheduler { let features = features::FeatureData::new(&env, &mut wtxn, options.instance_features)?; let queue = Queue::new(&env, &mut wtxn, &options)?; let index_mapper = IndexMapper::new(&env, &mut wtxn, &options, budget)?; - let chat_prompts = env.create_database(&mut wtxn, Some("chat-prompts"))?; + let chat_settings = env.create_database(&mut wtxn, Some("chat-settings"))?; wtxn.commit()?; // allow unreachable_code to get rids of the warning in the case of a test build. @@ -301,7 +301,7 @@ impl IndexScheduler { #[cfg(test)] run_loop_iteration: Arc::new(RwLock::new(0)), features, - chat_prompts, + chat_settings, }; this.run(); @@ -875,8 +875,15 @@ impl IndexScheduler { res.map(EmbeddingConfigs::new) } - pub fn chat_prompts<'t>(&self, rtxn: &'t RoTxn, name: &str) -> heed::Result> { - self.chat_prompts.get(rtxn, name) + pub fn chat_settings(&self) -> Result> { + let rtxn = self.env.read_txn().map_err(Error::HeedTransaction)?; + self.chat_settings.get(&rtxn, &"main").map_err(Into::into) + } + + pub fn put_chat_settings(&self, settings: &serde_json::Value) -> Result<()> { + let mut wtxn = self.env.write_txn().map_err(Error::HeedTransaction)?; + self.chat_settings.put(&mut wtxn, &"main", &settings)?; + Ok(()) } } diff --git a/crates/meilisearch-types/src/keys.rs b/crates/meilisearch-types/src/keys.rs index 805394781..ffa533be9 100644 --- a/crates/meilisearch-types/src/keys.rs +++ b/crates/meilisearch-types/src/keys.rs @@ -311,6 +311,12 @@ pub enum Action { #[serde(rename = "chat.get")] #[deserr(rename = "chat.get")] ChatGet, + #[serde(rename = "chatSettings.get")] + #[deserr(rename = "chatSettings.get")] + ChatSettingsGet, + #[serde(rename = "chatSettings.update")] + #[deserr(rename = "chatSettings.update")] + ChatSettingsUpdate, } impl Action { @@ -403,4 +409,6 @@ pub mod actions { pub const NETWORK_UPDATE: u8 = NetworkUpdate.repr(); pub const CHAT_GET: u8 = ChatGet.repr(); + pub const CHAT_SETTINGS_GET: u8 = ChatSettingsGet.repr(); + pub const CHAT_SETTINGS_UPDATE: u8 = ChatSettingsUpdate.repr(); } diff --git a/crates/meilisearch/src/routes/chat.rs b/crates/meilisearch/src/routes/chat.rs index e4a9b65e2..33cc06bce 100644 --- a/crates/meilisearch/src/routes/chat.rs +++ b/crates/meilisearch/src/routes/chat.rs @@ -9,6 +9,7 @@ use async_openai::config::OpenAIConfig; use async_openai::types::{ ChatCompletionMessageToolCall, ChatCompletionMessageToolCallChunk, ChatCompletionRequestAssistantMessageArgs, ChatCompletionRequestMessage, + ChatCompletionRequestSystemMessage, ChatCompletionRequestSystemMessageContent, ChatCompletionRequestToolMessage, ChatCompletionRequestToolMessageContent, ChatCompletionStreamResponseDelta, ChatCompletionToolArgs, ChatCompletionToolType, CreateChatCompletionRequest, FinishReason, FunctionCall, FunctionCallStream, @@ -27,6 +28,7 @@ use serde::{Deserialize, Serialize}; use serde_json::json; use tokio::runtime::Handle; +use super::settings::chat::{ChatPrompts, ChatSettings}; use crate::extractors::authentication::policies::ActionPolicy; use crate::extractors::authentication::GuardedData; use crate::metrics::MEILISEARCH_DEGRADED_SEARCH_REQUESTS; @@ -36,33 +38,12 @@ use crate::search::{ }; use crate::search_queue::SearchQueue; -/// The default description of the searchInIndex tool provided to OpenAI. -const DEFAULT_SEARCH_IN_INDEX_TOOL_DESCRIPTION: &str = - "Search the database for relevant JSON documents using an optional query."; -/// The default description of the searchInIndex `q` parameter tool provided to OpenAI. -const DEFAULT_SEARCH_IN_INDEX_Q_PARAMETER_TOOL_DESCRIPTION: &str = - "The search query string used to find relevant documents in the index. \ -This should contain keywords or phrases that best represent what the user is looking for. \ -More specific queries will yield more precise results."; -/// The default description of the searchInIndex `index` parameter tool provided to OpenAI. -const DEFAULT_SEARCH_IN_INDEX_INDEX_PARAMETER_TOOL_DESCRIPTION: &str = -"The name of the index to search within. An index is a collection of documents organized for search. \ -Selecting the right index ensures the most relevant results for the user query"; - const EMBEDDER_NAME: &str = "openai"; pub fn configure(cfg: &mut web::ServiceConfig) { cfg.service(web::resource("").route(web::post().to(chat))); } -/// Creates OpenAI client with API key -fn create_openai_client() -> Client { - let api_key = std::env::var("MEILI_OPENAI_API_KEY") - .expect("cannot find OpenAI API Key (MEILI_OPENAI_API_KEY)"); - let config = OpenAIConfig::default().with_api_key(&api_key); - Client::with_config(config) -} - /// Get a chat completion async fn chat( index_scheduler: GuardedData, Data>, @@ -86,12 +67,7 @@ async fn chat( } /// Setup search tool in chat completion request -fn setup_search_tool( - chat_completion: &mut CreateChatCompletionRequest, - search_in_index_description: &str, - search_in_index_q_param_description: &str, - search_in_index_index_description: &str, -) { +fn setup_search_tool(chat_completion: &mut CreateChatCompletionRequest, prompts: &ChatPrompts) { let tools = chat_completion.tools.get_or_insert_default(); tools.push( ChatCompletionToolArgs::default() @@ -99,18 +75,18 @@ fn setup_search_tool( .function( FunctionObjectArgs::default() .name("searchInIndex") - .description(search_in_index_description) + .description(&prompts.search_description) .parameters(json!({ "type": "object", "properties": { "index_uid": { "type": "string", "enum": ["main"], - "description": search_in_index_index_description, + "description": prompts.search_index_uid_param, }, "q": { "type": ["string", "null"], - "description": search_in_index_q_param_description, + "description": prompts.search_q_param, } }, "required": ["index_uid", "q"], @@ -125,6 +101,17 @@ fn setup_search_tool( ); } +/// Prepend system message to the conversation +fn prepend_system_message(chat_completion: &mut CreateChatCompletionRequest, system_prompt: &str) { + chat_completion.messages.insert( + 0, + ChatCompletionRequestMessage::System(ChatCompletionRequestSystemMessage { + content: ChatCompletionRequestSystemMessageContent::Text(system_prompt.to_string()), + name: None, + }), + ); +} + /// Process search request and return formatted results async fn process_search_request( index_scheduler: &GuardedData, Data>, @@ -187,56 +174,32 @@ async fn process_search_request( Ok((index, text)) } -/// Get prompt descriptions from index scheduler -fn get_prompt_descriptions( - index_scheduler: &GuardedData, Data>, -) -> (String, String, String) { - let rtxn = index_scheduler.read_txn().unwrap(); - let search_in_index_description = index_scheduler - .chat_prompts(&rtxn, "searchInIndex-description") - .unwrap() - .unwrap_or(DEFAULT_SEARCH_IN_INDEX_TOOL_DESCRIPTION) - .to_string(); - let search_in_index_q_param_description = index_scheduler - .chat_prompts(&rtxn, "searchInIndex-q-param-description") - .unwrap() - .unwrap_or(DEFAULT_SEARCH_IN_INDEX_Q_PARAMETER_TOOL_DESCRIPTION) - .to_string(); - let search_in_index_index_description = index_scheduler - .chat_prompts(&rtxn, "searchInIndex-index-param-description") - .unwrap() - .unwrap_or(DEFAULT_SEARCH_IN_INDEX_INDEX_PARAMETER_TOOL_DESCRIPTION) - .to_string(); - drop(rtxn); - - ( - search_in_index_description, - search_in_index_q_param_description, - search_in_index_index_description, - ) -} - async fn non_streamed_chat( index_scheduler: GuardedData, Data>, search_queue: web::Data, mut chat_completion: CreateChatCompletionRequest, ) -> Result { - let client = create_openai_client(); + let chat_settings = match index_scheduler.chat_settings().unwrap() { + Some(value) => serde_json::from_value(value).unwrap(), + None => ChatSettings::default(), + }; - let ( - search_in_index_description, - search_in_index_q_param_description, - search_in_index_index_description, - ) = get_prompt_descriptions(&index_scheduler); + let mut config = OpenAIConfig::default(); + if let Some(api_key) = chat_settings.api_key.as_ref() { + config = config.with_api_key(api_key); + } + // We cannot change the endpoint + // if let Some(endpoint) = chat_settings.endpoint.as_ref() { + // config.with_api_base(&endpoint); + // } + let client = Client::with_config(config); + + // Prepend system message to the conversation + prepend_system_message(&mut chat_completion, &chat_settings.prompts.system); let mut response; loop { - setup_search_tool( - &mut chat_completion, - &search_in_index_description, - &search_in_index_q_param_description, - &search_in_index_index_description, - ); + setup_search_tool(&mut chat_completion, &chat_settings.prompts); response = client.chat().create(chat_completion.clone()).await.unwrap(); @@ -290,22 +253,29 @@ async fn streamed_chat( search_queue: web::Data, mut chat_completion: CreateChatCompletionRequest, ) -> impl Responder { - let ( - search_in_index_description, - search_in_index_q_param_description, - search_in_index_index_description, - ) = get_prompt_descriptions(&index_scheduler); + let chat_settings = match index_scheduler.chat_settings().unwrap() { + Some(value) => serde_json::from_value(value).unwrap(), + None => ChatSettings::default(), + }; - setup_search_tool( - &mut chat_completion, - &search_in_index_description, - &search_in_index_q_param_description, - &search_in_index_index_description, - ); + let mut config = OpenAIConfig::default(); + if let Some(api_key) = chat_settings.api_key.as_ref() { + config = config.with_api_key(api_key); + } + // We cannot change the endpoint + // if let Some(endpoint) = chat_settings.endpoint.as_ref() { + // config.with_api_base(&endpoint); + // } + + // Prepend system message to the conversation + prepend_system_message(&mut chat_completion, &chat_settings.prompts.system); + + // Setup the search tool + setup_search_tool(&mut chat_completion, &chat_settings.prompts); let (tx, rx) = tokio::sync::mpsc::channel(10); let _join_handle = Handle::current().spawn(async move { - let client = create_openai_client(); + let client = Client::with_config(config.clone()); let mut global_tool_calls = HashMap::::new(); 'main: loop { diff --git a/crates/meilisearch/src/routes/mod.rs b/crates/meilisearch/src/routes/mod.rs index 602fb6b40..3d56ce8e8 100644 --- a/crates/meilisearch/src/routes/mod.rs +++ b/crates/meilisearch/src/routes/mod.rs @@ -62,6 +62,7 @@ mod multi_search; mod multi_search_analytics; pub mod network; mod open_api_utils; +pub mod settings; mod snapshot; mod swap_indexes; pub mod tasks; @@ -115,7 +116,8 @@ pub fn configure(cfg: &mut web::ServiceConfig) { .service(web::scope("/metrics").configure(metrics::configure)) .service(web::scope("/experimental-features").configure(features::configure)) .service(web::scope("/network").configure(network::configure)) - .service(web::scope("/chat").configure(chat::configure)); + .service(web::scope("/chat").configure(chat::configure)) + .service(web::scope("/settings/chat").configure(settings::chat::configure)); #[cfg(feature = "swagger")] { diff --git a/crates/meilisearch/src/routes/settings/chat.rs b/crates/meilisearch/src/routes/settings/chat.rs new file mode 100644 index 000000000..9708e1409 --- /dev/null +++ b/crates/meilisearch/src/routes/settings/chat.rs @@ -0,0 +1,111 @@ +use std::collections::BTreeMap; + +use actix_web::web::{self, Data}; +use actix_web::HttpResponse; +use index_scheduler::IndexScheduler; +use meilisearch_types::error::ResponseError; +use meilisearch_types::keys::actions; +use serde::{Deserialize, Serialize}; + +use crate::extractors::authentication::policies::ActionPolicy; +use crate::extractors::authentication::GuardedData; +use crate::extractors::sequential_extractor::SeqHandler; + +pub fn configure(cfg: &mut web::ServiceConfig) { + cfg.service( + web::resource("") + .route(web::get().to(get_settings)) + .route(web::patch().to(SeqHandler(patch_settings))), + ); +} + +async fn get_settings( + index_scheduler: GuardedData< + ActionPolicy<{ actions::CHAT_SETTINGS_GET }>, + Data, + >, +) -> Result { + let settings = match index_scheduler.chat_settings()? { + Some(value) => serde_json::from_value(value).unwrap(), + None => ChatSettings::default(), + }; + Ok(HttpResponse::Ok().json(settings)) +} + +async fn patch_settings( + index_scheduler: GuardedData< + ActionPolicy<{ actions::CHAT_SETTINGS_UPDATE }>, + Data, + >, + web::Json(chat_settings): web::Json, +) -> Result { + let chat_settings = serde_json::to_value(chat_settings).unwrap(); + index_scheduler.put_chat_settings(&chat_settings)?; + Ok(HttpResponse::Ok().finish()) +} + +#[derive(Debug, Serialize, Deserialize)] +#[serde(deny_unknown_fields, rename_all = "camelCase")] +pub struct ChatSettings { + pub source: String, + pub endpoint: Option, + pub api_key: Option, + pub prompts: ChatPrompts, + pub indexes: BTreeMap, +} + +#[derive(Debug, Serialize, Deserialize)] +#[serde(deny_unknown_fields, rename_all = "camelCase")] +pub struct ChatPrompts { + pub system: String, + pub search_description: String, + pub search_q_param: String, + pub search_index_uid_param: String, + pub pre_query: String, +} + +#[derive(Debug, Serialize, Deserialize)] +#[serde(deny_unknown_fields, rename_all = "camelCase")] +pub struct ChatIndexSettings { + pub description: String, + pub document_template: String, +} + +const DEFAULT_SYSTEM_MESSAGE: &str = "You are a highly capable research assistant with access to powerful search tools. IMPORTANT INSTRUCTIONS:\ + 1. When answering questions, you MUST make multiple tool calls (at least 2-3) to gather comprehensive information.\ + 2. Use different search queries for each tool call - vary keywords, rephrase questions, and explore different semantic angles to ensure broad coverage.\ + 3. Always explicitly announce BEFORE making each tool call by saying: \"I'll search for [specific information] now.\"\ + 4. Combine information from ALL tool calls to provide complete, nuanced answers rather than relying on a single source.\ + 5. For complex topics, break down your research into multiple targeted queries rather than using a single generic search."; + +/// The default description of the searchInIndex tool provided to OpenAI. +const DEFAULT_SEARCH_IN_INDEX_TOOL_DESCRIPTION: &str = + "Search the database for relevant JSON documents using an optional query."; +/// The default description of the searchInIndex `q` parameter tool provided to OpenAI. +const DEFAULT_SEARCH_IN_INDEX_Q_PARAMETER_TOOL_DESCRIPTION: &str = + "The search query string used to find relevant documents in the index. \ +This should contain keywords or phrases that best represent what the user is looking for. \ +More specific queries will yield more precise results."; +/// The default description of the searchInIndex `index` parameter tool provided to OpenAI. +const DEFAULT_SEARCH_IN_INDEX_INDEX_PARAMETER_TOOL_DESCRIPTION: &str = +"The name of the index to search within. An index is a collection of documents organized for search. \ +Selecting the right index ensures the most relevant results for the user query"; + +impl Default for ChatSettings { + fn default() -> Self { + ChatSettings { + source: "openai".to_string(), + endpoint: None, + api_key: None, + prompts: ChatPrompts { + system: DEFAULT_SYSTEM_MESSAGE.to_string(), + search_description: DEFAULT_SEARCH_IN_INDEX_TOOL_DESCRIPTION.to_string(), + search_q_param: DEFAULT_SEARCH_IN_INDEX_Q_PARAMETER_TOOL_DESCRIPTION.to_string(), + search_index_uid_param: DEFAULT_SEARCH_IN_INDEX_INDEX_PARAMETER_TOOL_DESCRIPTION + .to_string(), + pre_query: "".to_string(), + }, + indexes: BTreeMap::new(), + } + } +} diff --git a/crates/meilisearch/src/routes/settings/mod.rs b/crates/meilisearch/src/routes/settings/mod.rs new file mode 100644 index 000000000..30a62fc50 --- /dev/null +++ b/crates/meilisearch/src/routes/settings/mod.rs @@ -0,0 +1 @@ +pub mod chat; From c7839b5a8483f5f3190b9ad27c5097bbd940fc63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Renault?= Date: Thu, 15 May 2025 17:52:26 +0200 Subject: [PATCH 014/103] Remove useless function --- crates/meilisearch/src/routes/chat.rs | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/crates/meilisearch/src/routes/chat.rs b/crates/meilisearch/src/routes/chat.rs index 33cc06bce..3b85811c3 100644 --- a/crates/meilisearch/src/routes/chat.rs +++ b/crates/meilisearch/src/routes/chat.rs @@ -99,14 +99,11 @@ fn setup_search_tool(chat_completion: &mut CreateChatCompletionRequest, prompts: .build() .unwrap(), ); -} -/// Prepend system message to the conversation -fn prepend_system_message(chat_completion: &mut CreateChatCompletionRequest, system_prompt: &str) { chat_completion.messages.insert( 0, ChatCompletionRequestMessage::System(ChatCompletionRequestSystemMessage { - content: ChatCompletionRequestSystemMessageContent::Text(system_prompt.to_string()), + content: ChatCompletionRequestSystemMessageContent::Text(prompts.system.clone()), name: None, }), ); @@ -194,13 +191,10 @@ async fn non_streamed_chat( // } let client = Client::with_config(config); - // Prepend system message to the conversation - prepend_system_message(&mut chat_completion, &chat_settings.prompts.system); + setup_search_tool(&mut chat_completion, &chat_settings.prompts); let mut response; loop { - setup_search_tool(&mut chat_completion, &chat_settings.prompts); - response = client.chat().create(chat_completion.clone()).await.unwrap(); let choice = &mut response.choices[0]; @@ -267,10 +261,6 @@ async fn streamed_chat( // config.with_api_base(&endpoint); // } - // Prepend system message to the conversation - prepend_system_message(&mut chat_completion, &chat_settings.prompts.system); - - // Setup the search tool setup_search_tool(&mut chat_completion, &chat_settings.prompts); let (tx, rx) = tokio::sync::mpsc::channel(10); From 7d8415448c3ff76dd204bccb4ebb81e3893ea3f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Renault?= Date: Thu, 15 May 2025 18:03:26 +0200 Subject: [PATCH 015/103] Commit when putting stuff in LMDB --- crates/index-scheduler/src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/index-scheduler/src/lib.rs b/crates/index-scheduler/src/lib.rs index 15766c691..f96539454 100644 --- a/crates/index-scheduler/src/lib.rs +++ b/crates/index-scheduler/src/lib.rs @@ -883,6 +883,7 @@ impl IndexScheduler { pub fn put_chat_settings(&self, settings: &serde_json::Value) -> Result<()> { let mut wtxn = self.env.write_txn().map_err(Error::HeedTransaction)?; self.chat_settings.put(&mut wtxn, &"main", &settings)?; + wtxn.commit().map_err(Error::HeedTransaction)?; Ok(()) } } From 7fa74b49316847e451e4b7f5e9cda47f49196b57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Renault?= Date: Thu, 15 May 2025 18:10:09 +0200 Subject: [PATCH 016/103] Display pre-query prompt in search tool response --- crates/meilisearch/src/routes/chat.rs | 31 +++++++++++++++++---------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/crates/meilisearch/src/routes/chat.rs b/crates/meilisearch/src/routes/chat.rs index 3b85811c3..f8a3e8237 100644 --- a/crates/meilisearch/src/routes/chat.rs +++ b/crates/meilisearch/src/routes/chat.rs @@ -27,6 +27,7 @@ use meilisearch_types::{Document, Index}; use serde::{Deserialize, Serialize}; use serde_json::json; use tokio::runtime::Handle; +use tracing::error; use super::settings::chat::{ChatPrompts, ChatSettings}; use crate::extractors::authentication::policies::ActionPolicy; @@ -361,32 +362,40 @@ async fn streamed_chat( ) .await; - // Handle potential errors more explicitly - if let Err(err) = &result { - // Log the error or handle it as needed - eprintln!("Error processing search request: {:?}", err); - continue; - } - - let (_, text) = result.unwrap(); + let text = match result { + Ok((_, text)) => text, + Err(err) => { + error!("Error processing search request: {err:?}"); + continue; + } + }; let tool = ChatCompletionRequestMessage::Tool( ChatCompletionRequestToolMessage { - tool_call_id: call.id, + tool_call_id: call.id.clone(), content: ChatCompletionRequestToolMessageContent::Text( - text, + format!("{}\n\n{text}", chat_settings.prompts.pre_query), ), }, ); + tx.send(Event::Data( sse::Data::new_json(&json!({ "object": "chat.completion.tool.output", - "tool": tool, + "tool": ChatCompletionRequestMessage::Tool( + ChatCompletionRequestToolMessage { + tool_call_id: call.id, + content: ChatCompletionRequestToolMessageContent::Text( + text, + ), + }, + ), })) .unwrap(), )) .await .unwrap(); + chat_completion.messages.push(tool); } } From 564f85280c5d3962ff67f29bc48b786e36aca6f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Renault?= Date: Thu, 15 May 2025 18:16:06 +0200 Subject: [PATCH 017/103] Make clippy happy --- crates/index-scheduler/src/lib.rs | 6 +++--- crates/meilisearch/src/routes/chat.rs | 14 +++++++------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/crates/index-scheduler/src/lib.rs b/crates/index-scheduler/src/lib.rs index f96539454..9fb92f041 100644 --- a/crates/index-scheduler/src/lib.rs +++ b/crates/index-scheduler/src/lib.rs @@ -214,7 +214,7 @@ impl IndexScheduler { #[cfg(test)] run_loop_iteration: self.run_loop_iteration.clone(), features: self.features.clone(), - chat_settings: self.chat_settings.clone(), + chat_settings: self.chat_settings, } } @@ -877,12 +877,12 @@ impl IndexScheduler { pub fn chat_settings(&self) -> Result> { let rtxn = self.env.read_txn().map_err(Error::HeedTransaction)?; - self.chat_settings.get(&rtxn, &"main").map_err(Into::into) + self.chat_settings.get(&rtxn, "main").map_err(Into::into) } pub fn put_chat_settings(&self, settings: &serde_json::Value) -> Result<()> { let mut wtxn = self.env.write_txn().map_err(Error::HeedTransaction)?; - self.chat_settings.put(&mut wtxn, &"main", &settings)?; + self.chat_settings.put(&mut wtxn, "main", settings)?; wtxn.commit().map_err(Error::HeedTransaction)?; Ok(()) } diff --git a/crates/meilisearch/src/routes/chat.rs b/crates/meilisearch/src/routes/chat.rs index f8a3e8237..82ee4b435 100644 --- a/crates/meilisearch/src/routes/chat.rs +++ b/crates/meilisearch/src/routes/chat.rs @@ -276,11 +276,11 @@ async fn streamed_chat( match result { Ok(resp) => { let delta = &resp.choices[0].delta; + #[allow(deprecated)] let ChatCompletionStreamResponseDelta { content, // Using deprecated field but keeping for compatibility - #[allow(deprecated)] - function_call: _, + function_call: _, ref tool_calls, role: _, refusal: _, @@ -291,7 +291,7 @@ async fn streamed_chat( break 'main; } - if let Some(_) = content { + if content.is_some() { tx.send(Event::Data(sse::Data::new_json(&resp).unwrap())).await.unwrap() } @@ -321,8 +321,8 @@ async fn streamed_chat( let (meili_calls, _other_calls): (Vec<_>, Vec<_>) = mem::take(&mut global_tool_calls) - .into_iter() - .map(|(_, call)| ChatCompletionMessageToolCall { + .into_values() + .map(|call| ChatCompletionMessageToolCall { id: call.id, r#type: ChatCompletionToolType::Function, function: FunctionCall { @@ -342,7 +342,7 @@ async fn streamed_chat( for call in meili_calls { tx.send(Event::Data( - sse::Data::new_json(&json!({ + sse::Data::new_json(json!({ "object": "chat.completion.tool.call", "tool": call, })) @@ -380,7 +380,7 @@ async fn streamed_chat( ); tx.send(Event::Data( - sse::Data::new_json(&json!({ + sse::Data::new_json(json!({ "object": "chat.completion.tool.output", "tool": ChatCompletionRequestMessage::Tool( ChatCompletionRequestToolMessage { From b9716ec346cf64a7cb09c5a9c746576fbf149e43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Renault?= Date: Thu, 15 May 2025 18:28:02 +0200 Subject: [PATCH 018/103] Support base_api in the settings --- crates/meilisearch/src/routes/chat.rs | 14 ++++++-------- crates/meilisearch/src/routes/settings/chat.rs | 4 ++-- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/crates/meilisearch/src/routes/chat.rs b/crates/meilisearch/src/routes/chat.rs index 82ee4b435..8d07342c8 100644 --- a/crates/meilisearch/src/routes/chat.rs +++ b/crates/meilisearch/src/routes/chat.rs @@ -186,10 +186,9 @@ async fn non_streamed_chat( if let Some(api_key) = chat_settings.api_key.as_ref() { config = config.with_api_key(api_key); } - // We cannot change the endpoint - // if let Some(endpoint) = chat_settings.endpoint.as_ref() { - // config.with_api_base(&endpoint); - // } + if let Some(base_api) = chat_settings.base_api.as_ref() { + config = config.with_api_base(base_api); + } let client = Client::with_config(config); setup_search_tool(&mut chat_completion, &chat_settings.prompts); @@ -257,10 +256,9 @@ async fn streamed_chat( if let Some(api_key) = chat_settings.api_key.as_ref() { config = config.with_api_key(api_key); } - // We cannot change the endpoint - // if let Some(endpoint) = chat_settings.endpoint.as_ref() { - // config.with_api_base(&endpoint); - // } + if let Some(base_api) = chat_settings.base_api.as_ref() { + config = config.with_api_base(base_api); + } setup_search_tool(&mut chat_completion, &chat_settings.prompts); diff --git a/crates/meilisearch/src/routes/settings/chat.rs b/crates/meilisearch/src/routes/settings/chat.rs index 9708e1409..d8be27ab3 100644 --- a/crates/meilisearch/src/routes/settings/chat.rs +++ b/crates/meilisearch/src/routes/settings/chat.rs @@ -48,7 +48,7 @@ async fn patch_settings( #[serde(deny_unknown_fields, rename_all = "camelCase")] pub struct ChatSettings { pub source: String, - pub endpoint: Option, + pub base_api: Option, pub api_key: Option, pub prompts: ChatPrompts, pub indexes: BTreeMap, @@ -95,7 +95,7 @@ impl Default for ChatSettings { fn default() -> Self { ChatSettings { source: "openai".to_string(), - endpoint: None, + base_api: None, api_key: None, prompts: ChatPrompts { system: DEFAULT_SYSTEM_MESSAGE.to_string(), From 341183cd578a050ceb8916a0379435b0ade2fe20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Renault?= Date: Fri, 16 May 2025 14:33:53 +0200 Subject: [PATCH 019/103] Make it compatible with the Mistral API --- Cargo.lock | 8 +++----- crates/meilisearch/Cargo.toml | 2 +- crates/meilisearch/src/routes/chat.rs | 16 ++++++++++------ 3 files changed, 14 insertions(+), 12 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6ce6b5186..260df24ed 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -475,9 +475,8 @@ dependencies = [ [[package]] name = "async-openai" -version = "0.28.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ac0334b0fef1ddaf141154a3ef6d55a95b5b1a52c720753554b0a1ca670e68" +version = "0.28.1" +source = "git+https://github.com/meilisearch/async-openai?branch=optional-type-function#603f1d17bb4530c45fb9a6e93294ab715a7af869" dependencies = [ "async-openai-macros", "backoff", @@ -502,8 +501,7 @@ dependencies = [ [[package]] name = "async-openai-macros" version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0289cba6d5143bfe8251d57b4a8cac036adf158525a76533a7082ba65ec76398" +source = "git+https://github.com/meilisearch/async-openai?branch=optional-type-function#dd328d4c35ca24c30284c8aff616541ac82eb47a" dependencies = [ "proc-macro2", "quote", diff --git a/crates/meilisearch/Cargo.toml b/crates/meilisearch/Cargo.toml index f7469e7ac..398b62dad 100644 --- a/crates/meilisearch/Cargo.toml +++ b/crates/meilisearch/Cargo.toml @@ -112,7 +112,7 @@ utoipa = { version = "5.3.1", features = [ "openapi_extensions", ] } utoipa-scalar = { version = "0.3.0", optional = true, features = ["actix-web"] } -async-openai = "0.28.1" +async-openai = { git = "https://github.com/meilisearch/async-openai", branch = "optional-type-function" } actix-web-lab = { version = "0.24.1", default-features = false } [dev-dependencies] diff --git a/crates/meilisearch/src/routes/chat.rs b/crates/meilisearch/src/routes/chat.rs index 8d07342c8..7869b677d 100644 --- a/crates/meilisearch/src/routes/chat.rs +++ b/crates/meilisearch/src/routes/chat.rs @@ -86,7 +86,9 @@ fn setup_search_tool(chat_completion: &mut CreateChatCompletionRequest, prompts: "description": prompts.search_index_uid_param, }, "q": { - "type": ["string", "null"], + // Unfortunately, Mistral does not support an array of types, here. + // "type": ["string", "null"], + "type": "string", "description": prompts.search_q_param, } }, @@ -269,7 +271,6 @@ async fn streamed_chat( 'main: loop { let mut response = client.chat().create_stream(chat_completion.clone()).await.unwrap(); - while let Some(result) = response.next().await { match result { Ok(resp) => { @@ -306,12 +307,14 @@ async fn streamed_chat( function.as_ref().unwrap(); global_tool_calls .entry(*index) + .and_modify(|call| { + call.append(arguments.as_ref().unwrap()); + }) .or_insert_with(|| Call { id: id.as_ref().unwrap().clone(), function_name: name.as_ref().unwrap().clone(), arguments: arguments.as_ref().unwrap().clone(), - }) - .append(arguments.as_ref().unwrap()); + }); } } None if !global_tool_calls.is_empty() => { @@ -322,7 +325,7 @@ async fn streamed_chat( .into_values() .map(|call| ChatCompletionMessageToolCall { id: call.id, - r#type: ChatCompletionToolType::Function, + r#type: Some(ChatCompletionToolType::Function), function: FunctionCall { name: call.function_name, arguments: call.arguments, @@ -400,8 +403,9 @@ async fn streamed_chat( None => (), } } - Err(_err) => { + Err(err) => { // writeln!(lock, "error: {err}").unwrap(); + tracing::error!("{err:?}"); } } } From 1d2dbcb51fd6d71931a933aba637a075f45cea5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Renault?= Date: Fri, 16 May 2025 15:17:01 +0200 Subject: [PATCH 020/103] Update the streaming detection to work with Mistral --- crates/meilisearch/src/routes/chat.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/crates/meilisearch/src/routes/chat.rs b/crates/meilisearch/src/routes/chat.rs index 7869b677d..fe5fdd415 100644 --- a/crates/meilisearch/src/routes/chat.rs +++ b/crates/meilisearch/src/routes/chat.rs @@ -281,11 +281,14 @@ async fn streamed_chat( // Using deprecated field but keeping for compatibility function_call: _, ref tool_calls, - role: _, + role, refusal: _, } = delta; - if content.is_none() && tool_calls.is_none() && global_tool_calls.is_empty() + if content.as_ref().map_or(true, |s| s.is_empty()) + && tool_calls.is_none() + && global_tool_calls.is_empty() + && role.is_none() { break 'main; } From 39320a6fcef923a5dfb4f3b4e6a7507c701c13ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Renault?= Date: Fri, 16 May 2025 17:12:48 +0200 Subject: [PATCH 021/103] Better stop the stream --- Cargo.lock | 2 +- crates/meilisearch/src/routes/chat.rs | 20 ++++++++------------ 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 260df24ed..dd47ee94c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -501,7 +501,7 @@ dependencies = [ [[package]] name = "async-openai-macros" version = "0.1.0" -source = "git+https://github.com/meilisearch/async-openai?branch=optional-type-function#dd328d4c35ca24c30284c8aff616541ac82eb47a" +source = "git+https://github.com/meilisearch/async-openai?branch=optional-type-function#603f1d17bb4530c45fb9a6e93294ab715a7af869" dependencies = [ "proc-macro2", "quote", diff --git a/crates/meilisearch/src/routes/chat.rs b/crates/meilisearch/src/routes/chat.rs index fe5fdd415..04de94b88 100644 --- a/crates/meilisearch/src/routes/chat.rs +++ b/crates/meilisearch/src/routes/chat.rs @@ -268,30 +268,25 @@ async fn streamed_chat( let _join_handle = Handle::current().spawn(async move { let client = Client::with_config(config.clone()); let mut global_tool_calls = HashMap::::new(); + let mut finish_reason = None; - 'main: loop { + 'main: while finish_reason.map_or(true, |fr| fr == FinishReason::ToolCalls) { let mut response = client.chat().create_stream(chat_completion.clone()).await.unwrap(); while let Some(result) = response.next().await { match result { Ok(resp) => { - let delta = &resp.choices[0].delta; + let choice = &resp.choices[0]; + finish_reason = choice.finish_reason; + #[allow(deprecated)] let ChatCompletionStreamResponseDelta { content, // Using deprecated field but keeping for compatibility function_call: _, ref tool_calls, - role, + role: _, refusal: _, - } = delta; - - if content.as_ref().map_or(true, |s| s.is_empty()) - && tool_calls.is_none() - && global_tool_calls.is_empty() - && role.is_none() - { - break 'main; - } + } = &choice.delta; if content.is_some() { tx.send(Event::Data(sse::Data::new_json(&resp).unwrap())).await.unwrap() @@ -409,6 +404,7 @@ async fn streamed_chat( Err(err) => { // writeln!(lock, "error: {err}").unwrap(); tracing::error!("{err:?}"); + break 'main; } } } From 1a84f00fbf35bbb2e17256e7d34425bf45ab407f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Renault?= Date: Tue, 20 May 2025 10:14:56 +0200 Subject: [PATCH 022/103] Change the /chat route to /chat/completions to be OpenAI-compatible --- crates/meilisearch/src/routes/chat.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/meilisearch/src/routes/chat.rs b/crates/meilisearch/src/routes/chat.rs index 04de94b88..e58552d57 100644 --- a/crates/meilisearch/src/routes/chat.rs +++ b/crates/meilisearch/src/routes/chat.rs @@ -42,7 +42,7 @@ use crate::search_queue::SearchQueue; const EMBEDDER_NAME: &str = "openai"; pub fn configure(cfg: &mut web::ServiceConfig) { - cfg.service(web::resource("").route(web::post().to(chat))); + cfg.service(web::resource("/completions").route(web::post().to(chat))); } /// Get a chat completion From 56c1bd3afe377f75c114cca3681a774e4b4081d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Renault?= Date: Tue, 20 May 2025 11:00:19 +0200 Subject: [PATCH 023/103] Generate a new default chat API key --- crates/meilisearch-auth/src/lib.rs | 1 + crates/meilisearch-types/src/keys.rs | 21 ++++++++++++++++--- .../src/extractors/authentication/mod.rs | 4 ++-- crates/meilisearch/src/routes/chat.rs | 8 +++---- crates/meilisearch/tests/auth/api_keys.rs | 16 ++++++++++++++ 5 files changed, 41 insertions(+), 9 deletions(-) diff --git a/crates/meilisearch-auth/src/lib.rs b/crates/meilisearch-auth/src/lib.rs index 01c986d9f..d72ba386c 100644 --- a/crates/meilisearch-auth/src/lib.rs +++ b/crates/meilisearch-auth/src/lib.rs @@ -351,6 +351,7 @@ pub struct IndexSearchRules { fn generate_default_keys(store: &HeedAuthStore) -> Result<()> { store.put_api_key(Key::default_admin())?; store.put_api_key(Key::default_search())?; + store.put_api_key(Key::default_chat())?; Ok(()) } diff --git a/crates/meilisearch-types/src/keys.rs b/crates/meilisearch-types/src/keys.rs index ffa533be9..dfa50aa1e 100644 --- a/crates/meilisearch-types/src/keys.rs +++ b/crates/meilisearch-types/src/keys.rs @@ -158,6 +158,21 @@ impl Key { updated_at: now, } } + + pub fn default_chat() -> Self { + let now = OffsetDateTime::now_utc(); + let uid = Uuid::new_v4(); + Self { + name: Some("Default Chat API Key".to_string()), + description: Some("Use it to chat and search from the frontend".to_string()), + uid, + actions: vec![Action::Chat, Action::Search], + indexes: vec![IndexUidPattern::all()], + expires_at: None, + created_at: now, + updated_at: now, + } + } } fn parse_expiration_date( @@ -310,7 +325,7 @@ pub enum Action { NetworkUpdate, #[serde(rename = "chat.get")] #[deserr(rename = "chat.get")] - ChatGet, + Chat, #[serde(rename = "chatSettings.get")] #[deserr(rename = "chatSettings.get")] ChatSettingsGet, @@ -358,7 +373,7 @@ impl Action { EXPERIMENTAL_FEATURES_UPDATE => Some(Self::ExperimentalFeaturesUpdate), NETWORK_GET => Some(Self::NetworkGet), NETWORK_UPDATE => Some(Self::NetworkUpdate), - CHAT_GET => Some(Self::ChatGet), + CHAT => Some(Self::Chat), _otherwise => None, } } @@ -408,7 +423,7 @@ pub mod actions { pub const NETWORK_GET: u8 = NetworkGet.repr(); pub const NETWORK_UPDATE: u8 = NetworkUpdate.repr(); - pub const CHAT_GET: u8 = ChatGet.repr(); + pub const CHAT: u8 = Chat.repr(); pub const CHAT_SETTINGS_GET: u8 = ChatSettingsGet.repr(); pub const CHAT_SETTINGS_UPDATE: u8 = ChatSettingsUpdate.repr(); } diff --git a/crates/meilisearch/src/extractors/authentication/mod.rs b/crates/meilisearch/src/extractors/authentication/mod.rs index 28a6d770e..7c9f5892e 100644 --- a/crates/meilisearch/src/extractors/authentication/mod.rs +++ b/crates/meilisearch/src/extractors/authentication/mod.rs @@ -299,8 +299,8 @@ pub mod policies { auth: &AuthController, token: &str, ) -> Result { - // Only search action can be accessed by a tenant token. - if A != actions::SEARCH { + // Only search and chat actions can be accessed by a tenant token. + if A != actions::SEARCH && A != actions::CHAT { return Ok(TenantTokenOutcome::NotATenantToken); } diff --git a/crates/meilisearch/src/routes/chat.rs b/crates/meilisearch/src/routes/chat.rs index e58552d57..6c6c97761 100644 --- a/crates/meilisearch/src/routes/chat.rs +++ b/crates/meilisearch/src/routes/chat.rs @@ -47,7 +47,7 @@ pub fn configure(cfg: &mut web::ServiceConfig) { /// Get a chat completion async fn chat( - index_scheduler: GuardedData, Data>, + index_scheduler: GuardedData, Data>, search_queue: web::Data, web::Json(chat_completion): web::Json, ) -> impl Responder { @@ -114,7 +114,7 @@ fn setup_search_tool(chat_completion: &mut CreateChatCompletionRequest, prompts: /// Process search request and return formatted results async fn process_search_request( - index_scheduler: &GuardedData, Data>, + index_scheduler: &GuardedData, Data>, search_queue: &web::Data, index_uid: String, q: Option, @@ -175,7 +175,7 @@ async fn process_search_request( } async fn non_streamed_chat( - index_scheduler: GuardedData, Data>, + index_scheduler: GuardedData, Data>, search_queue: web::Data, mut chat_completion: CreateChatCompletionRequest, ) -> Result { @@ -245,7 +245,7 @@ async fn non_streamed_chat( } async fn streamed_chat( - index_scheduler: GuardedData, Data>, + index_scheduler: GuardedData, Data>, search_queue: web::Data, mut chat_completion: CreateChatCompletionRequest, ) -> impl Responder { diff --git a/crates/meilisearch/tests/auth/api_keys.rs b/crates/meilisearch/tests/auth/api_keys.rs index 0aea7d722..edfcd1c29 100644 --- a/crates/meilisearch/tests/auth/api_keys.rs +++ b/crates/meilisearch/tests/auth/api_keys.rs @@ -820,6 +820,22 @@ async fn list_api_keys() { "createdAt": "[ignored]", "updatedAt": "[ignored]" }, + { + "name": "Default Chat API Key", + "description": "Use it to chat and search from the frontend", + "key": "[ignored]", + "uid": "[ignored]", + "actions": [ + "search", + "chat.get" + ], + "indexes": [ + "*" + ], + "expiresAt": null, + "createdAt": "[ignored]", + "updatedAt": "[ignored]" + }, { "name": "Default Search API Key", "description": "Use it to search from the frontend", From bcec8d8984afebe4422e3408910c6a35e81f658c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Renault?= Date: Tue, 20 May 2025 12:05:51 +0200 Subject: [PATCH 024/103] Stop the stream when the connexion stops and chnage the events --- crates/meilisearch/src/routes/chat.rs | 68 +++++++++++++-------------- 1 file changed, 33 insertions(+), 35 deletions(-) diff --git a/crates/meilisearch/src/routes/chat.rs b/crates/meilisearch/src/routes/chat.rs index 6c6c97761..733b8ff65 100644 --- a/crates/meilisearch/src/routes/chat.rs +++ b/crates/meilisearch/src/routes/chat.rs @@ -27,7 +27,7 @@ use meilisearch_types::{Document, Index}; use serde::{Deserialize, Serialize}; use serde_json::json; use tokio::runtime::Handle; -use tracing::error; +use tokio::sync::mpsc::error::SendError; use super::settings::chat::{ChatPrompts, ChatSettings}; use crate::extractors::authentication::policies::ActionPolicy; @@ -289,7 +289,9 @@ async fn streamed_chat( } = &choice.delta; if content.is_some() { - tx.send(Event::Data(sse::Data::new_json(&resp).unwrap())).await.unwrap() + if let Err(SendError(_)) = tx.send(Event::Data(sse::Data::new_json(&resp).unwrap())).await { + return; + } } match tool_calls { @@ -305,9 +307,7 @@ async fn streamed_chat( function.as_ref().unwrap(); global_tool_calls .entry(*index) - .and_modify(|call| { - call.append(arguments.as_ref().unwrap()); - }) + .and_modify(|call| call.append(arguments.as_ref().unwrap())) .or_insert_with(|| Call { id: id.as_ref().unwrap().clone(), function_name: name.as_ref().unwrap().clone(), @@ -316,8 +316,6 @@ async fn streamed_chat( } } None if !global_tool_calls.is_empty() => { - // dbg!(&global_tool_calls); - let (meili_calls, _other_calls): (Vec<_>, Vec<_>) = mem::take(&mut global_tool_calls) .into_values() @@ -340,15 +338,16 @@ async fn streamed_chat( ); for call in meili_calls { - tx.send(Event::Data( + if let Err(SendError(_)) = tx.send(Event::Data( sse::Data::new_json(json!({ "object": "chat.completion.tool.call", "tool": call, })) .unwrap(), )) - .await - .unwrap(); + .await { + return; + } let SearchInIndexParameters { index_uid, q } = serde_json::from_str(&call.function.arguments).unwrap(); @@ -361,41 +360,40 @@ async fn streamed_chat( ) .await; + let is_error = result.is_err(); let text = match result { Ok((_, text)) => text, - Err(err) => { - error!("Error processing search request: {err:?}"); - continue; - } + Err(err) => err.to_string(), }; - let tool = ChatCompletionRequestMessage::Tool( - ChatCompletionRequestToolMessage { - tool_call_id: call.id.clone(), - content: ChatCompletionRequestToolMessageContent::Text( - format!("{}\n\n{text}", chat_settings.prompts.pre_query), - ), - }, - ); + let tool = ChatCompletionRequestToolMessage { + tool_call_id: call.id.clone(), + content: ChatCompletionRequestToolMessageContent::Text( + format!("{}\n\n{text}", chat_settings.prompts.pre_query), + ), + }; - tx.send(Event::Data( + if let Err(SendError(_)) = tx.send(Event::Data( sse::Data::new_json(json!({ - "object": "chat.completion.tool.output", - "tool": ChatCompletionRequestMessage::Tool( - ChatCompletionRequestToolMessage { - tool_call_id: call.id, - content: ChatCompletionRequestToolMessageContent::Text( - text, - ), - }, - ), + "object": if is_error { + "chat.completion.tool.error" + } else { + "chat.completion.tool.output" + }, + "tool": ChatCompletionRequestToolMessage { + tool_call_id: call.id, + content: ChatCompletionRequestToolMessageContent::Text( + text, + ), + }, })) .unwrap(), )) - .await - .unwrap(); + .await { + return; + } - chat_completion.messages.push(tool); + chat_completion.messages.push(ChatCompletionRequestMessage::Tool(tool)); } } None => (), From 46680585ae54f3ba979e9a0fa5107b1913eb6788 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Renault?= Date: Tue, 20 May 2025 12:23:22 +0200 Subject: [PATCH 025/103] Stream errors --- crates/meilisearch/src/routes/chat.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/crates/meilisearch/src/routes/chat.rs b/crates/meilisearch/src/routes/chat.rs index 733b8ff65..5ddcb6088 100644 --- a/crates/meilisearch/src/routes/chat.rs +++ b/crates/meilisearch/src/routes/chat.rs @@ -400,13 +400,21 @@ async fn streamed_chat( } } Err(err) => { - // writeln!(lock, "error: {err}").unwrap(); tracing::error!("{err:?}"); + if let Err(SendError(_)) = tx.send(Event::Data(sse::Data::new_json(&json!({ + "object": "chat.completion.error", + "tool": err.to_string(), + })).unwrap())).await { + return; + } + break 'main; } } } } + + let _ = tx.send(Event::Data(sse::Data::new("[DONE]"))); }); Sse::from_infallible_receiver(rx).with_retry_duration(Duration::from_secs(10)) From 7636365a650d46c37f12276838fc5f1a3a085f56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Renault?= Date: Tue, 20 May 2025 16:15:49 +0200 Subject: [PATCH 026/103] Correctly support tenant tokens and filters --- .../src/extractors/authentication/mod.rs | 57 +++++++++++-------- crates/meilisearch/src/routes/chat.rs | 49 ++++++++++++---- 2 files changed, 72 insertions(+), 34 deletions(-) diff --git a/crates/meilisearch/src/extractors/authentication/mod.rs b/crates/meilisearch/src/extractors/authentication/mod.rs index 7c9f5892e..eb250190d 100644 --- a/crates/meilisearch/src/extractors/authentication/mod.rs +++ b/crates/meilisearch/src/extractors/authentication/mod.rs @@ -4,6 +4,7 @@ use std::marker::PhantomData; use std::ops::Deref; use std::pin::Pin; +use actix_web::http::header::AUTHORIZATION; use actix_web::web::Data; use actix_web::FromRequest; pub use error::AuthenticationError; @@ -94,36 +95,44 @@ impl FromRequest for GuardedData _payload: &mut actix_web::dev::Payload, ) -> Self::Future { match req.app_data::>().cloned() { - Some(auth) => match req - .headers() - .get("Authorization") - .map(|type_token| type_token.to_str().unwrap_or_default().splitn(2, ' ')) - { - Some(mut type_token) => match type_token.next() { - Some("Bearer") => { - // TODO: find a less hardcoded way? - let index = req.match_info().get("index_uid"); - match type_token.next() { - Some(token) => Box::pin(Self::auth_bearer( - auth, - token.to_string(), - index.map(String::from), - req.app_data::().cloned(), - )), - None => Box::pin(err(AuthenticationError::InvalidToken.into())), - } - } - _otherwise => { - Box::pin(err(AuthenticationError::MissingAuthorizationHeader.into())) - } - }, - None => Box::pin(Self::auth_token(auth, req.app_data::().cloned())), + Some(auth) => match extract_token_from_request(req) { + Ok(Some(token)) => { + // TODO: find a less hardcoded way? + let index = req.match_info().get("index_uid"); + Box::pin(Self::auth_bearer( + auth, + token.to_string(), + index.map(String::from), + req.app_data::().cloned(), + )) + } + Ok(None) => Box::pin(Self::auth_token(auth, req.app_data::().cloned())), + Err(e) => Box::pin(err(e.into())), }, None => Box::pin(err(AuthenticationError::IrretrievableState.into())), } } } +pub fn extract_token_from_request( + req: &actix_web::HttpRequest, +) -> Result, AuthenticationError> { + match req + .headers() + .get(AUTHORIZATION) + .map(|type_token| type_token.to_str().unwrap_or_default().splitn(2, ' ')) + { + Some(mut type_token) => match type_token.next() { + Some("Bearer") => match type_token.next() { + Some(token) => Ok(Some(token)), + None => Err(AuthenticationError::InvalidToken), + }, + _otherwise => Err(AuthenticationError::MissingAuthorizationHeader), + }, + None => Ok(None), + } +} + pub trait Policy { fn authenticate( auth: Data, diff --git a/crates/meilisearch/src/routes/chat.rs b/crates/meilisearch/src/routes/chat.rs index 5ddcb6088..31e089231 100644 --- a/crates/meilisearch/src/routes/chat.rs +++ b/crates/meilisearch/src/routes/chat.rs @@ -3,7 +3,7 @@ use std::mem; use std::time::Duration; use actix_web::web::{self, Data}; -use actix_web::{Either, HttpResponse, Responder}; +use actix_web::{Either, HttpRequest, HttpResponse, Responder}; use actix_web_lab::sse::{self, Event, Sse}; use async_openai::config::OpenAIConfig; use async_openai::types::{ @@ -18,6 +18,7 @@ use async_openai::types::{ use async_openai::Client; use futures::StreamExt; use index_scheduler::IndexScheduler; +use meilisearch_auth::AuthController; use meilisearch_types::error::ResponseError; use meilisearch_types::keys::actions; use meilisearch_types::milli::index::IndexEmbeddingConfig; @@ -31,7 +32,7 @@ use tokio::sync::mpsc::error::SendError; use super::settings::chat::{ChatPrompts, ChatSettings}; use crate::extractors::authentication::policies::ActionPolicy; -use crate::extractors::authentication::GuardedData; +use crate::extractors::authentication::{extract_token_from_request, GuardedData, Policy as _}; use crate::metrics::MEILISEARCH_DEGRADED_SEARCH_REQUESTS; use crate::routes::indexes::search::search_kind; use crate::search::{ @@ -48,6 +49,8 @@ pub fn configure(cfg: &mut web::ServiceConfig) { /// Get a chat completion async fn chat( index_scheduler: GuardedData, Data>, + auth_ctrl: web::Data, + req: HttpRequest, search_queue: web::Data, web::Json(chat_completion): web::Json, ) -> impl Responder { @@ -61,9 +64,13 @@ async fn chat( ); if chat_completion.stream.unwrap_or(false) { - Either::Right(streamed_chat(index_scheduler, search_queue, chat_completion).await) + Either::Right( + streamed_chat(index_scheduler, auth_ctrl, req, search_queue, chat_completion).await, + ) } else { - Either::Left(non_streamed_chat(index_scheduler, search_queue, chat_completion).await) + Either::Left( + non_streamed_chat(index_scheduler, auth_ctrl, req, search_queue, chat_completion).await, + ) } } @@ -115,7 +122,9 @@ fn setup_search_tool(chat_completion: &mut CreateChatCompletionRequest, prompts: /// Process search request and return formatted results async fn process_search_request( index_scheduler: &GuardedData, Data>, + auth_ctrl: web::Data, search_queue: &web::Data, + auth_token: &str, index_uid: String, q: Option, ) -> Result<(Index, String), ResponseError> { @@ -129,8 +138,14 @@ async fn process_search_request( ..Default::default() }; + let auth_filter = ActionPolicy::<{ actions::SEARCH }>::authenticate( + auth_ctrl, + auth_token, + Some(index_uid.as_str()), + )?; + // Tenant token search_rules. - if let Some(search_rules) = index_scheduler.filters().get_index_search_rules(&index_uid) { + if let Some(search_rules) = auth_filter.get_index_search_rules(&index_uid) { add_search_rules(&mut query.filter, search_rules); } @@ -176,6 +191,8 @@ async fn process_search_request( async fn non_streamed_chat( index_scheduler: GuardedData, Data>, + auth_ctrl: web::Data, + req: HttpRequest, search_queue: web::Data, mut chat_completion: CreateChatCompletionRequest, ) -> Result { @@ -193,6 +210,7 @@ async fn non_streamed_chat( } let client = Client::with_config(config); + let auth_token = extract_token_from_request(&req)?.unwrap(); setup_search_tool(&mut chat_completion, &chat_settings.prompts); let mut response; @@ -219,9 +237,15 @@ async fn non_streamed_chat( let SearchInIndexParameters { index_uid, q } = serde_json::from_str(&call.function.arguments).unwrap(); - let (_, text) = - process_search_request(&index_scheduler, &search_queue, index_uid, q) - .await?; + let (_, text) = process_search_request( + &index_scheduler, + auth_ctrl.clone(), + &search_queue, + auth_token, + index_uid, + q, + ) + .await?; chat_completion.messages.push(ChatCompletionRequestMessage::Tool( ChatCompletionRequestToolMessage { @@ -246,9 +270,11 @@ async fn non_streamed_chat( async fn streamed_chat( index_scheduler: GuardedData, Data>, + auth_ctrl: web::Data, + req: HttpRequest, search_queue: web::Data, mut chat_completion: CreateChatCompletionRequest, -) -> impl Responder { +) -> Result { let chat_settings = match index_scheduler.chat_settings().unwrap() { Some(value) => serde_json::from_value(value).unwrap(), None => ChatSettings::default(), @@ -262,6 +288,7 @@ async fn streamed_chat( config = config.with_api_base(base_api); } + let auth_token = extract_token_from_request(&req)?.unwrap().to_string(); setup_search_tool(&mut chat_completion, &chat_settings.prompts); let (tx, rx) = tokio::sync::mpsc::channel(10); @@ -354,7 +381,9 @@ async fn streamed_chat( let result = process_search_request( &index_scheduler, + auth_ctrl.clone(), &search_queue, + &auth_token, index_uid, q, ) @@ -417,7 +446,7 @@ async fn streamed_chat( let _ = tx.send(Event::Data(sse::Data::new("[DONE]"))); }); - Sse::from_infallible_receiver(rx).with_retry_duration(Duration::from_secs(10)) + Ok(Sse::from_infallible_receiver(rx).with_retry_duration(Duration::from_secs(10))) } /// The structure used to aggregate the function calls to make. From 0b675bd530b9316f2e78d058a09389a293c4b638 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Renault?= Date: Tue, 20 May 2025 16:44:28 +0200 Subject: [PATCH 027/103] Limit the number of internal loop calls and change the function name --- crates/meilisearch/src/routes/chat.rs | 82 +++++++++++++++------------ 1 file changed, 46 insertions(+), 36 deletions(-) diff --git a/crates/meilisearch/src/routes/chat.rs b/crates/meilisearch/src/routes/chat.rs index 31e089231..2715cff72 100644 --- a/crates/meilisearch/src/routes/chat.rs +++ b/crates/meilisearch/src/routes/chat.rs @@ -41,6 +41,7 @@ use crate::search::{ use crate::search_queue::SearchQueue; const EMBEDDER_NAME: &str = "openai"; +const SEARCH_IN_INDEX_FUNCTION_NAME: &str = "_meiliSearchInIndex"; pub fn configure(cfg: &mut web::ServiceConfig) { cfg.service(web::resource("/completions").route(web::post().to(chat))); @@ -77,39 +78,41 @@ async fn chat( /// Setup search tool in chat completion request fn setup_search_tool(chat_completion: &mut CreateChatCompletionRequest, prompts: &ChatPrompts) { let tools = chat_completion.tools.get_or_insert_default(); - tools.push( - ChatCompletionToolArgs::default() - .r#type(ChatCompletionToolType::Function) - .function( - FunctionObjectArgs::default() - .name("searchInIndex") - .description(&prompts.search_description) - .parameters(json!({ - "type": "object", - "properties": { - "index_uid": { - "type": "string", - "enum": ["main"], - "description": prompts.search_index_uid_param, - }, - "q": { - // Unfortunately, Mistral does not support an array of types, here. - // "type": ["string", "null"], - "type": "string", - "description": prompts.search_q_param, - } - }, - "required": ["index_uid", "q"], - "additionalProperties": false, - })) - .strict(true) - .build() - .unwrap(), - ) - .build() - .unwrap(), - ); + if tools.iter().find(|t| t.function.name == SEARCH_IN_INDEX_FUNCTION_NAME).is_some() { + panic!("{SEARCH_IN_INDEX_FUNCTION_NAME} function already set"); + } + let tool = ChatCompletionToolArgs::default() + .r#type(ChatCompletionToolType::Function) + .function( + FunctionObjectArgs::default() + .name(SEARCH_IN_INDEX_FUNCTION_NAME) + .description(&prompts.search_description) + .parameters(json!({ + "type": "object", + "properties": { + "index_uid": { + "type": "string", + "enum": ["main"], + "description": prompts.search_index_uid_param, + }, + "q": { + // Unfortunately, Mistral does not support an array of types, here. + // "type": ["string", "null"], + "type": "string", + "description": prompts.search_q_param, + } + }, + "required": ["index_uid", "q"], + "additionalProperties": false, + })) + .strict(true) + .build() + .unwrap(), + ) + .build() + .unwrap(); + tools.push(tool); chat_completion.messages.insert( 0, ChatCompletionRequestMessage::System(ChatCompletionRequestSystemMessage { @@ -222,8 +225,9 @@ async fn non_streamed_chat( Some(FinishReason::ToolCalls) => { let tool_calls = mem::take(&mut choice.message.tool_calls).unwrap_or_default(); - let (meili_calls, other_calls): (Vec<_>, Vec<_>) = - tool_calls.into_iter().partition(|call| call.function.name == "searchInIndex"); + let (meili_calls, other_calls): (Vec<_>, Vec<_>) = tool_calls + .into_iter() + .partition(|call| call.function.name == SEARCH_IN_INDEX_FUNCTION_NAME); chat_completion.messages.push( ChatCompletionRequestAssistantMessageArgs::default() @@ -297,7 +301,8 @@ async fn streamed_chat( let mut global_tool_calls = HashMap::::new(); let mut finish_reason = None; - 'main: while finish_reason.map_or(true, |fr| fr == FinishReason::ToolCalls) { + // Limit the number of internal calls to satisfy the search requests of the LLM + 'main: for _ in 0..20 { let mut response = client.chat().create_stream(chat_completion.clone()).await.unwrap(); while let Some(result) = response.next().await { match result { @@ -354,7 +359,7 @@ async fn streamed_chat( arguments: call.arguments, }, }) - .partition(|call| call.function.name == "searchInIndex"); + .partition(|call| call.function.name == SEARCH_IN_INDEX_FUNCTION_NAME); chat_completion.messages.push( ChatCompletionRequestAssistantMessageArgs::default() @@ -441,6 +446,11 @@ async fn streamed_chat( } } } + + // We must stop if the finish reason is not something we can solve with Meilisearch + if finish_reason.map_or(true, |fr| fr != FinishReason::ToolCalls) { + break; + } } let _ = tx.send(Event::Data(sse::Data::new("[DONE]"))); From fcf694026d24de4b36fba75764fe1fc3e997b0ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Renault?= Date: Tue, 20 May 2025 17:15:23 +0200 Subject: [PATCH 028/103] Support multiple indexes and not only main --- crates/meilisearch/src/routes/chat.rs | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/crates/meilisearch/src/routes/chat.rs b/crates/meilisearch/src/routes/chat.rs index 2715cff72..f794ba19c 100644 --- a/crates/meilisearch/src/routes/chat.rs +++ b/crates/meilisearch/src/routes/chat.rs @@ -76,12 +76,23 @@ async fn chat( } /// Setup search tool in chat completion request -fn setup_search_tool(chat_completion: &mut CreateChatCompletionRequest, prompts: &ChatPrompts) { +fn setup_search_tool( + index_scheduler: &Data, + filters: &meilisearch_auth::AuthFilter, + chat_completion: &mut CreateChatCompletionRequest, + prompts: &ChatPrompts, +) -> Result<(), ResponseError> { let tools = chat_completion.tools.get_or_insert_default(); if tools.iter().find(|t| t.function.name == SEARCH_IN_INDEX_FUNCTION_NAME).is_some() { panic!("{SEARCH_IN_INDEX_FUNCTION_NAME} function already set"); } + let index_uids: Vec<_> = index_scheduler + .index_names()? + .into_iter() + .filter(|index_uid| filters.is_index_authorized(&index_uid)) + .collect(); + let tool = ChatCompletionToolArgs::default() .r#type(ChatCompletionToolType::Function) .function( @@ -93,7 +104,7 @@ fn setup_search_tool(chat_completion: &mut CreateChatCompletionRequest, prompts: "properties": { "index_uid": { "type": "string", - "enum": ["main"], + "enum": index_uids, "description": prompts.search_index_uid_param, }, "q": { @@ -120,6 +131,8 @@ fn setup_search_tool(chat_completion: &mut CreateChatCompletionRequest, prompts: name: None, }), ); + + Ok(()) } /// Process search request and return formatted results @@ -199,6 +212,8 @@ async fn non_streamed_chat( search_queue: web::Data, mut chat_completion: CreateChatCompletionRequest, ) -> Result { + let filters = index_scheduler.filters(); + let chat_settings = match index_scheduler.chat_settings().unwrap() { Some(value) => serde_json::from_value(value).unwrap(), None => ChatSettings::default(), @@ -214,7 +229,7 @@ async fn non_streamed_chat( let client = Client::with_config(config); let auth_token = extract_token_from_request(&req)?.unwrap(); - setup_search_tool(&mut chat_completion, &chat_settings.prompts); + setup_search_tool(&index_scheduler, filters, &mut chat_completion, &chat_settings.prompts)?; let mut response; loop { @@ -279,6 +294,8 @@ async fn streamed_chat( search_queue: web::Data, mut chat_completion: CreateChatCompletionRequest, ) -> Result { + let filters = index_scheduler.filters(); + let chat_settings = match index_scheduler.chat_settings().unwrap() { Some(value) => serde_json::from_value(value).unwrap(), None => ChatSettings::default(), @@ -293,7 +310,7 @@ async fn streamed_chat( } let auth_token = extract_token_from_request(&req)?.unwrap().to_string(); - setup_search_tool(&mut chat_completion, &chat_settings.prompts); + setup_search_tool(&index_scheduler, filters, &mut chat_completion, &chat_settings.prompts)?; let (tx, rx) = tokio::sync::mpsc::channel(10); let _join_handle = Handle::current().spawn(async move { From 6bf214bb1439c8b7b167c6b1bd70932aeb41164c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Renault?= Date: Tue, 20 May 2025 17:55:21 +0200 Subject: [PATCH 029/103] Catch invalid argument calls to search function --- crates/meilisearch/src/routes/chat.rs | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/crates/meilisearch/src/routes/chat.rs b/crates/meilisearch/src/routes/chat.rs index f794ba19c..0dc54b37d 100644 --- a/crates/meilisearch/src/routes/chat.rs +++ b/crates/meilisearch/src/routes/chat.rs @@ -398,18 +398,17 @@ async fn streamed_chat( return; } - let SearchInIndexParameters { index_uid, q } = - serde_json::from_str(&call.function.arguments).unwrap(); - - let result = process_search_request( - &index_scheduler, - auth_ctrl.clone(), - &search_queue, - &auth_token, - index_uid, - q, - ) - .await; + let result = match serde_json::from_str(&call.function.arguments) { + Ok(SearchInIndexParameters { index_uid, q }) => process_search_request( + &index_scheduler, + auth_ctrl.clone(), + &search_queue, + &auth_token, + index_uid, + q, + ).await.map_err(|e| e.to_string()), + Err(err) => Err(err.to_string()), + }; let is_error = result.is_err(); let text = match result { From 439146289ea5e8b6afdc96e2d485ac369b6f69f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Renault?= Date: Tue, 20 May 2025 18:01:08 +0200 Subject: [PATCH 030/103] Make sure errorneous calls are handled and forwarded to the LLM --- crates/meilisearch/src/routes/chat.rs | 37 +++++++++++++++++---------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/crates/meilisearch/src/routes/chat.rs b/crates/meilisearch/src/routes/chat.rs index 0dc54b37d..b3a67ff10 100644 --- a/crates/meilisearch/src/routes/chat.rs +++ b/crates/meilisearch/src/routes/chat.rs @@ -253,23 +253,32 @@ async fn non_streamed_chat( ); for call in meili_calls { - let SearchInIndexParameters { index_uid, q } = - serde_json::from_str(&call.function.arguments).unwrap(); + let result = match serde_json::from_str(&call.function.arguments) { + Ok(SearchInIndexParameters { index_uid, q }) => process_search_request( + &index_scheduler, + auth_ctrl.clone(), + &search_queue, + &auth_token, + index_uid, + q, + ) + .await + .map_err(|e| e.to_string()), + Err(err) => Err(err.to_string()), + }; - let (_, text) = process_search_request( - &index_scheduler, - auth_ctrl.clone(), - &search_queue, - auth_token, - index_uid, - q, - ) - .await?; + let text = match result { + Ok((_, text)) => text, + Err(err) => err, + }; chat_completion.messages.push(ChatCompletionRequestMessage::Tool( ChatCompletionRequestToolMessage { - tool_call_id: call.id, - content: ChatCompletionRequestToolMessageContent::Text(text), + tool_call_id: call.id.clone(), + content: ChatCompletionRequestToolMessageContent::Text(format!( + "{}\n\n{text}", + chat_settings.prompts.pre_query + )), }, )); } @@ -413,7 +422,7 @@ async fn streamed_chat( let is_error = result.is_err(); let text = match result { Ok((_, text)) => text, - Err(err) => err.to_string(), + Err(err) => err, }; let tool = ChatCompletionRequestToolMessage { From c6930c88193aae252bc83a25ce68525626880e83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Renault?= Date: Wed, 21 May 2025 11:07:06 +0200 Subject: [PATCH 031/103] Introduce the new index chat settings --- crates/dump/src/reader/compat/v5_to_v6.rs | 1 + crates/meilisearch-types/src/error.rs | 3 +- crates/meilisearch-types/src/settings.rs | 50 +++++++++++++++-- crates/meilisearch/src/routes/chat.rs | 6 +- .../src/routes/indexes/settings.rs | 14 ++++- .../src/routes/indexes/settings_analytics.rs | 21 ++++++- .../meilisearch/src/routes/settings/chat.rs | 10 ++-- crates/milli/src/index.rs | 28 ++++++++++ crates/milli/src/update/chat.rs | 45 +++++++++++++++ crates/milli/src/update/mod.rs | 2 + crates/milli/src/update/settings.rs | 56 ++++++++++++++++++- crates/milli/src/vector/settings.rs | 12 ++++ 12 files changed, 227 insertions(+), 21 deletions(-) create mode 100644 crates/milli/src/update/chat.rs diff --git a/crates/dump/src/reader/compat/v5_to_v6.rs b/crates/dump/src/reader/compat/v5_to_v6.rs index 14570c258..f7bda81c6 100644 --- a/crates/dump/src/reader/compat/v5_to_v6.rs +++ b/crates/dump/src/reader/compat/v5_to_v6.rs @@ -405,6 +405,7 @@ impl From> for v6::Settings { search_cutoff_ms: v6::Setting::NotSet, facet_search: v6::Setting::NotSet, prefix_search: v6::Setting::NotSet, + chat: v6::Setting::NotSet, _kind: std::marker::PhantomData, } } diff --git a/crates/meilisearch-types/src/error.rs b/crates/meilisearch-types/src/error.rs index 6c547d51e..172656237 100644 --- a/crates/meilisearch-types/src/error.rs +++ b/crates/meilisearch-types/src/error.rs @@ -387,7 +387,8 @@ VectorEmbeddingError , InvalidRequest , BAD_REQUEST ; NotFoundSimilarId , InvalidRequest , BAD_REQUEST ; InvalidDocumentEditionContext , InvalidRequest , BAD_REQUEST ; InvalidDocumentEditionFunctionFilter , InvalidRequest , BAD_REQUEST ; -EditDocumentsByFunctionError , InvalidRequest , BAD_REQUEST +EditDocumentsByFunctionError , InvalidRequest , BAD_REQUEST ; +InvalidSettingsIndexChat , InvalidRequest , BAD_REQUEST } impl ErrorCode for JoinError { diff --git a/crates/meilisearch-types/src/settings.rs b/crates/meilisearch-types/src/settings.rs index edb136567..b2f0c2f5b 100644 --- a/crates/meilisearch-types/src/settings.rs +++ b/crates/meilisearch-types/src/settings.rs @@ -11,6 +11,7 @@ use fst::IntoStreamer; use milli::disabled_typos_terms::DisabledTyposTerms; use milli::index::{IndexEmbeddingConfig, PrefixSearch}; use milli::proximity::ProximityPrecision; +pub use milli::update::ChatSettings; use milli::update::Setting; use milli::{Criterion, CriterionError, FilterableAttributesRule, Index, DEFAULT_VALUES_PER_FACET}; use serde::{Deserialize, Serialize, Serializer}; @@ -199,72 +200,86 @@ pub struct Settings { #[deserr(default, error = DeserrJsonError)] #[schema(value_type = Option>, example = json!(["id", "title", "description", "url"]))] pub displayed_attributes: WildcardSetting, + /// Fields in which to search for matching query words sorted by order of importance. #[serde(default, skip_serializing_if = "Setting::is_not_set")] #[deserr(default, error = DeserrJsonError)] #[schema(value_type = Option>, example = json!(["title", "description"]))] pub searchable_attributes: WildcardSetting, + /// Attributes to use for faceting and filtering. See [Filtering and Faceted Search](https://www.meilisearch.com/docs/learn/filtering_and_sorting/search_with_facet_filters). #[serde(default, skip_serializing_if = "Setting::is_not_set")] #[deserr(default, error = DeserrJsonError)] #[schema(value_type = Option>, example = json!(["release_date", "genre"]))] pub filterable_attributes: Setting>, + /// Attributes to use when sorting search results. #[serde(default, skip_serializing_if = "Setting::is_not_set")] #[deserr(default, error = DeserrJsonError)] #[schema(value_type = Option>, example = json!(["release_date"]))] pub sortable_attributes: Setting>, + /// List of ranking rules sorted by order of importance. The order is customizable. /// [A list of ordered built-in ranking rules](https://www.meilisearch.com/docs/learn/relevancy/relevancy). #[serde(default, skip_serializing_if = "Setting::is_not_set")] #[deserr(default, error = DeserrJsonError)] #[schema(value_type = Option>, example = json!([RankingRuleView::Words, RankingRuleView::Typo, RankingRuleView::Proximity, RankingRuleView::Attribute, RankingRuleView::Exactness]))] pub ranking_rules: Setting>, + /// List of words ignored when present in search queries. #[serde(default, skip_serializing_if = "Setting::is_not_set")] #[deserr(default, error = DeserrJsonError)] #[schema(value_type = Option>, example = json!(["the", "a", "them", "their"]))] pub stop_words: Setting>, + /// List of characters not delimiting where one term begins and ends. #[serde(default, skip_serializing_if = "Setting::is_not_set")] #[deserr(default, error = DeserrJsonError)] #[schema(value_type = Option>, example = json!([" ", "\n"]))] pub non_separator_tokens: Setting>, + /// List of characters delimiting where one term begins and ends. #[serde(default, skip_serializing_if = "Setting::is_not_set")] #[deserr(default, error = DeserrJsonError)] #[schema(value_type = Option>, example = json!(["S"]))] pub separator_tokens: Setting>, + /// List of strings Meilisearch should parse as a single term. #[serde(default, skip_serializing_if = "Setting::is_not_set")] #[deserr(default, error = DeserrJsonError)] #[schema(value_type = Option>, example = json!(["iPhone pro"]))] pub dictionary: Setting>, + /// List of associated words treated similarly. A word associated to an array of word as synonyms. #[serde(default, skip_serializing_if = "Setting::is_not_set")] #[deserr(default, error = DeserrJsonError)] #[schema(value_type = Option>>, example = json!({ "he": ["she", "they", "them"], "phone": ["iPhone", "android"]}))] pub synonyms: Setting>>, + /// Search returns documents with distinct (different) values of the given field. #[serde(default, skip_serializing_if = "Setting::is_not_set")] #[deserr(default, error = DeserrJsonError)] #[schema(value_type = Option, example = json!("sku"))] pub distinct_attribute: Setting, + /// Precision level when calculating the proximity ranking rule. #[serde(default, skip_serializing_if = "Setting::is_not_set")] #[deserr(default, error = DeserrJsonError)] #[schema(value_type = Option, example = json!(ProximityPrecisionView::ByAttribute))] pub proximity_precision: Setting, + /// Customize typo tolerance feature. #[serde(default, skip_serializing_if = "Setting::is_not_set")] #[deserr(default, error = DeserrJsonError)] #[schema(value_type = Option, example = json!({ "enabled": true, "disableOnAttributes": ["title"]}))] pub typo_tolerance: Setting, + /// Faceting settings. #[serde(default, skip_serializing_if = "Setting::is_not_set")] #[deserr(default, error = DeserrJsonError)] #[schema(value_type = Option, example = json!({ "maxValuesPerFacet": 10, "sortFacetValuesBy": { "genre": FacetValuesSort::Count }}))] pub faceting: Setting, + /// Pagination settings. #[serde(default, skip_serializing_if = "Setting::is_not_set")] #[deserr(default, error = DeserrJsonError)] @@ -276,24 +291,34 @@ pub struct Settings { #[deserr(default, error = DeserrJsonError)] #[schema(value_type = Option>)] pub embedders: Setting>, + /// Maximum duration of a search query. #[serde(default, skip_serializing_if = "Setting::is_not_set")] #[deserr(default, error = DeserrJsonError)] #[schema(value_type = Option, example = json!(50))] pub search_cutoff_ms: Setting, + #[serde(default, skip_serializing_if = "Setting::is_not_set")] #[deserr(default, error = DeserrJsonError)] #[schema(value_type = Option>, example = json!(50))] pub localized_attributes: Setting>, + #[serde(default, skip_serializing_if = "Setting::is_not_set")] #[deserr(default, error = DeserrJsonError)] #[schema(value_type = Option, example = json!(true))] pub facet_search: Setting, + #[serde(default, skip_serializing_if = "Setting::is_not_set")] #[deserr(default, error = DeserrJsonError)] #[schema(value_type = Option, example = json!("Hemlo"))] pub prefix_search: Setting, + /// Customize the chat prompting. + #[serde(default, skip_serializing_if = "Setting::is_not_set")] + #[deserr(default, error = DeserrJsonError)] + #[schema(value_type = Option)] + pub chat: Setting, + #[serde(skip)] #[deserr(skip)] pub _kind: PhantomData, @@ -359,6 +384,7 @@ impl Settings { localized_attributes: Setting::Reset, facet_search: Setting::Reset, prefix_search: Setting::Reset, + chat: Setting::Reset, _kind: PhantomData, } } @@ -385,6 +411,7 @@ impl Settings { localized_attributes: localized_attributes_rules, facet_search, prefix_search, + chat, _kind, } = self; @@ -409,6 +436,7 @@ impl Settings { localized_attributes: localized_attributes_rules, facet_search, prefix_search, + chat, _kind: PhantomData, } } @@ -459,6 +487,7 @@ impl Settings { localized_attributes: self.localized_attributes, facet_search: self.facet_search, prefix_search: self.prefix_search, + chat: self.chat, _kind: PhantomData, } } @@ -533,8 +562,9 @@ impl Settings { Setting::Set(this) } }, - prefix_search: other.prefix_search.or(self.prefix_search), facet_search: other.facet_search.or(self.facet_search), + prefix_search: other.prefix_search.or(self.prefix_search), + chat: other.chat.clone().or(self.chat.clone()), _kind: PhantomData, } } @@ -573,6 +603,7 @@ pub fn apply_settings_to_builder( localized_attributes: localized_attributes_rules, facet_search, prefix_search, + chat, _kind, } = settings; @@ -783,6 +814,12 @@ pub fn apply_settings_to_builder( Setting::Reset => builder.reset_facet_search(), Setting::NotSet => (), } + + match chat { + Setting::Set(chat) => builder.set_chat(chat.clone()), + Setting::Reset => builder.reset_chat(), + Setting::NotSet => (), + } } pub enum SecretPolicy { @@ -880,14 +917,11 @@ pub fn settings( }) .collect(); let embedders = Setting::Set(embedders); - let search_cutoff_ms = index.search_cutoff(rtxn)?; - let localized_attributes_rules = index.localized_attributes_rules(rtxn)?; - let prefix_search = index.prefix_search(rtxn)?.map(PrefixSearchSettings::from); - let facet_search = index.facet_search(rtxn)?; + let chat = index.chat_config(rtxn).map(ChatSettings::from)?; let mut settings = Settings { displayed_attributes: match displayed_attributes { @@ -925,8 +959,9 @@ pub fn settings( Some(rules) => Setting::Set(rules.into_iter().map(|r| r.into()).collect()), None => Setting::Reset, }, - prefix_search: Setting::Set(prefix_search.unwrap_or_default()), facet_search: Setting::Set(facet_search), + prefix_search: Setting::Set(prefix_search.unwrap_or_default()), + chat: Setting::Set(chat), _kind: PhantomData, }; @@ -1154,6 +1189,7 @@ pub(crate) mod test { search_cutoff_ms: Setting::NotSet, facet_search: Setting::NotSet, prefix_search: Setting::NotSet, + chat: Setting::NotSet, _kind: PhantomData::, }; @@ -1185,6 +1221,8 @@ pub(crate) mod test { search_cutoff_ms: Setting::NotSet, facet_search: Setting::NotSet, prefix_search: Setting::NotSet, + chat: Setting::NotSet, + _kind: PhantomData::, }; diff --git a/crates/meilisearch/src/routes/chat.rs b/crates/meilisearch/src/routes/chat.rs index b3a67ff10..d85c14c36 100644 --- a/crates/meilisearch/src/routes/chat.rs +++ b/crates/meilisearch/src/routes/chat.rs @@ -30,7 +30,7 @@ use serde_json::json; use tokio::runtime::Handle; use tokio::sync::mpsc::error::SendError; -use super::settings::chat::{ChatPrompts, ChatSettings}; +use super::settings::chat::{ChatPrompts, GlobalChatSettings}; use crate::extractors::authentication::policies::ActionPolicy; use crate::extractors::authentication::{extract_token_from_request, GuardedData, Policy as _}; use crate::metrics::MEILISEARCH_DEGRADED_SEARCH_REQUESTS; @@ -216,7 +216,7 @@ async fn non_streamed_chat( let chat_settings = match index_scheduler.chat_settings().unwrap() { Some(value) => serde_json::from_value(value).unwrap(), - None => ChatSettings::default(), + None => GlobalChatSettings::default(), }; let mut config = OpenAIConfig::default(); @@ -307,7 +307,7 @@ async fn streamed_chat( let chat_settings = match index_scheduler.chat_settings().unwrap() { Some(value) => serde_json::from_value(value).unwrap(), - None => ChatSettings::default(), + None => GlobalChatSettings::default(), }; let mut config = OpenAIConfig::default(); diff --git a/crates/meilisearch/src/routes/indexes/settings.rs b/crates/meilisearch/src/routes/indexes/settings.rs index 92b018c8c..a35ae5136 100644 --- a/crates/meilisearch/src/routes/indexes/settings.rs +++ b/crates/meilisearch/src/routes/indexes/settings.rs @@ -6,7 +6,7 @@ use meilisearch_types::deserr::DeserrJsonError; use meilisearch_types::error::ResponseError; use meilisearch_types::index_uid::IndexUid; use meilisearch_types::settings::{ - settings, SecretPolicy, SettingEmbeddingSettings, Settings, Unchecked, + settings, ChatSettings, SecretPolicy, SettingEmbeddingSettings, Settings, Unchecked, }; use meilisearch_types::tasks::KindWithContent; use tracing::debug; @@ -508,6 +508,17 @@ make_setting_routes!( camelcase_attr: "prefixSearch", analytics: PrefixSearchAnalytics }, + { + route: "/chat", + update_verb: put, + value_type: ChatSettings, + err_type: meilisearch_types::deserr::DeserrJsonError< + meilisearch_types::error::deserr_codes::InvalidSettingsIndexChat, + >, + attr: chat, + camelcase_attr: "chat", + analytics: ChatAnalytics + }, ); #[utoipa::path( @@ -597,6 +608,7 @@ pub async fn update_all( ), facet_search: FacetSearchAnalytics::new(new_settings.facet_search.as_ref().set()), prefix_search: PrefixSearchAnalytics::new(new_settings.prefix_search.as_ref().set()), + chat: ChatAnalytics::new(new_settings.chat.as_ref().set()), }, &req, ); diff --git a/crates/meilisearch/src/routes/indexes/settings_analytics.rs b/crates/meilisearch/src/routes/indexes/settings_analytics.rs index 41df91966..1b8d0e244 100644 --- a/crates/meilisearch/src/routes/indexes/settings_analytics.rs +++ b/crates/meilisearch/src/routes/indexes/settings_analytics.rs @@ -10,8 +10,8 @@ use meilisearch_types::locales::{Locale, LocalizedAttributesRuleView}; use meilisearch_types::milli::update::Setting; use meilisearch_types::milli::FilterableAttributesRule; use meilisearch_types::settings::{ - FacetingSettings, PaginationSettings, PrefixSearchSettings, ProximityPrecisionView, - RankingRuleView, SettingEmbeddingSettings, TypoSettings, + ChatSettings, FacetingSettings, PaginationSettings, PrefixSearchSettings, + ProximityPrecisionView, RankingRuleView, SettingEmbeddingSettings, TypoSettings, }; use serde::Serialize; @@ -39,6 +39,7 @@ pub struct SettingsAnalytics { pub non_separator_tokens: NonSeparatorTokensAnalytics, pub facet_search: FacetSearchAnalytics, pub prefix_search: PrefixSearchAnalytics, + pub chat: ChatAnalytics, } impl Aggregate for SettingsAnalytics { @@ -198,6 +199,7 @@ impl Aggregate for SettingsAnalytics { set: new.prefix_search.set | self.prefix_search.set, value: new.prefix_search.value.or(self.prefix_search.value), }, + chat: ChatAnalytics { set: new.chat.set | self.chat.set }, }) } @@ -676,3 +678,18 @@ impl PrefixSearchAnalytics { SettingsAnalytics { prefix_search: self, ..Default::default() } } } + +#[derive(Serialize, Default)] +pub struct ChatAnalytics { + pub set: bool, +} + +impl ChatAnalytics { + pub fn new(settings: Option<&ChatSettings>) -> Self { + Self { set: settings.is_some() } + } + + pub fn into_settings(self) -> SettingsAnalytics { + SettingsAnalytics { chat: self, ..Default::default() } + } +} diff --git a/crates/meilisearch/src/routes/settings/chat.rs b/crates/meilisearch/src/routes/settings/chat.rs index d8be27ab3..a971ad102 100644 --- a/crates/meilisearch/src/routes/settings/chat.rs +++ b/crates/meilisearch/src/routes/settings/chat.rs @@ -27,7 +27,7 @@ async fn get_settings( ) -> Result { let settings = match index_scheduler.chat_settings()? { Some(value) => serde_json::from_value(value).unwrap(), - None => ChatSettings::default(), + None => GlobalChatSettings::default(), }; Ok(HttpResponse::Ok().json(settings)) } @@ -37,7 +37,7 @@ async fn patch_settings( ActionPolicy<{ actions::CHAT_SETTINGS_UPDATE }>, Data, >, - web::Json(chat_settings): web::Json, + web::Json(chat_settings): web::Json, ) -> Result { let chat_settings = serde_json::to_value(chat_settings).unwrap(); index_scheduler.put_chat_settings(&chat_settings)?; @@ -46,7 +46,7 @@ async fn patch_settings( #[derive(Debug, Serialize, Deserialize)] #[serde(deny_unknown_fields, rename_all = "camelCase")] -pub struct ChatSettings { +pub struct GlobalChatSettings { pub source: String, pub base_api: Option, pub api_key: Option, @@ -91,9 +91,9 @@ const DEFAULT_SEARCH_IN_INDEX_INDEX_PARAMETER_TOOL_DESCRIPTION: &str = "The name of the index to search within. An index is a collection of documents organized for search. \ Selecting the right index ensures the most relevant results for the user query"; -impl Default for ChatSettings { +impl Default for GlobalChatSettings { fn default() -> Self { - ChatSettings { + GlobalChatSettings { source: "openai".to_string(), base_api: None, api_key: None, diff --git a/crates/milli/src/index.rs b/crates/milli/src/index.rs index d0cd5c862..a5145cb0b 100644 --- a/crates/milli/src/index.rs +++ b/crates/milli/src/index.rs @@ -23,6 +23,7 @@ use crate::heed_codec::facet::{ use crate::heed_codec::version::VersionCodec; use crate::heed_codec::{BEU16StrCodec, FstSetCodec, StrBEU16Codec, StrRefCodec}; use crate::order_by_map::OrderByMap; +use crate::prompt::PromptData; use crate::proximity::ProximityPrecision; use crate::vector::{ArroyStats, ArroyWrapper, Embedding, EmbeddingConfig}; use crate::{ @@ -79,6 +80,7 @@ pub mod main_key { pub const PREFIX_SEARCH: &str = "prefix_search"; pub const DOCUMENTS_STATS: &str = "documents_stats"; pub const DISABLED_TYPOS_TERMS: &str = "disabled_typos_terms"; + pub const CHAT: &str = "chat"; } pub mod db_name { @@ -1691,6 +1693,25 @@ impl Index { self.main.remap_key_type::().delete(txn, main_key::FACET_SEARCH) } + pub fn chat_config(&self, txn: &RoTxn<'_>) -> heed::Result { + self.main + .remap_types::>() + .get(txn, main_key::CHAT) + .map(|o| o.unwrap_or_default()) + } + + pub(crate) fn put_chat_config( + &self, + txn: &mut RwTxn<'_>, + val: &ChatConfig, + ) -> heed::Result<()> { + self.main.remap_types::>().put(txn, main_key::CHAT, &val) + } + + pub(crate) fn delete_chat_config(&self, txn: &mut RwTxn<'_>) -> heed::Result { + self.main.remap_key_type::().delete(txn, main_key::CHAT) + } + pub fn localized_attributes_rules( &self, rtxn: &RoTxn<'_>, @@ -1917,6 +1938,13 @@ pub struct IndexEmbeddingConfig { pub user_provided: RoaringBitmap, } +#[derive(Debug, Default, Deserialize, Serialize)] +pub struct ChatConfig { + pub description: String, + /// Contains the document template and max template length. + pub prompt: PromptData, +} + #[derive(Debug, Deserialize, Serialize)] pub struct PrefixSettings { pub prefix_count_threshold: usize, diff --git a/crates/milli/src/update/chat.rs b/crates/milli/src/update/chat.rs new file mode 100644 index 000000000..44e646f6d --- /dev/null +++ b/crates/milli/src/update/chat.rs @@ -0,0 +1,45 @@ +use deserr::Deserr; +use serde::{Deserialize, Serialize}; +use utoipa::ToSchema; + +use crate::index::ChatConfig; +use crate::prompt::{default_max_bytes, PromptData}; +use crate::update::Setting; + +#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq, Deserr, ToSchema)] +#[serde(deny_unknown_fields, rename_all = "camelCase")] +#[deserr(deny_unknown_fields, rename_all = camelCase)] +pub struct ChatSettings { + #[serde(default, skip_serializing_if = "Setting::is_not_set")] + #[deserr(default)] + #[schema(value_type = Option)] + pub description: Setting, + + /// A liquid template used to render documents to a text that can be embedded. + /// + /// Meillisearch interpolates the template for each document and sends the resulting text to the embedder. + /// The embedder then generates document vectors based on this text. + #[serde(default, skip_serializing_if = "Setting::is_not_set")] + #[deserr(default)] + #[schema(value_type = Option)] + pub document_template: Setting, + + /// Rendered texts are truncated to this size. Defaults to 400. + #[serde(default, skip_serializing_if = "Setting::is_not_set")] + #[deserr(default)] + #[schema(value_type = Option)] + pub document_template_max_bytes: Setting, +} + +impl From for ChatSettings { + fn from(config: ChatConfig) -> Self { + let ChatConfig { description, prompt: PromptData { template, max_bytes } } = config; + ChatSettings { + description: Setting::Set(description), + document_template: Setting::Set(template), + document_template_max_bytes: Setting::Set( + max_bytes.unwrap_or(default_max_bytes()).get(), + ), + } + } +} diff --git a/crates/milli/src/update/mod.rs b/crates/milli/src/update/mod.rs index 9a783ffd2..5acb00113 100644 --- a/crates/milli/src/update/mod.rs +++ b/crates/milli/src/update/mod.rs @@ -1,4 +1,5 @@ pub use self::available_ids::AvailableIds; +pub use self::chat::ChatSettings; pub use self::clear_documents::ClearDocuments; pub use self::concurrent_available_ids::ConcurrentAvailableIds; pub use self::facet::bulk::FacetsUpdateBulk; @@ -13,6 +14,7 @@ pub use self::words_prefix_integer_docids::WordPrefixIntegerDocids; pub use self::words_prefixes_fst::WordsPrefixesFst; mod available_ids; +mod chat; mod clear_documents; mod concurrent_available_ids; pub(crate) mod del_add; diff --git a/crates/milli/src/update/settings.rs b/crates/milli/src/update/settings.rs index fce2989b1..697bf8168 100644 --- a/crates/milli/src/update/settings.rs +++ b/crates/milli/src/update/settings.rs @@ -13,7 +13,7 @@ use time::OffsetDateTime; use super::del_add::{DelAdd, DelAddOperation}; use super::index_documents::{IndexDocumentsConfig, Transform}; -use super::IndexerConfig; +use super::{ChatSettings, IndexerConfig}; use crate::attribute_patterns::PatternMatch; use crate::constants::RESERVED_GEO_FIELD_NAME; use crate::criterion::Criterion; @@ -22,11 +22,11 @@ use crate::error::UserError; use crate::fields_ids_map::metadata::{FieldIdMapWithMetadata, MetadataBuilder}; use crate::filterable_attributes_rules::match_faceted_field; use crate::index::{ - IndexEmbeddingConfig, PrefixSearch, DEFAULT_MIN_WORD_LEN_ONE_TYPO, + ChatConfig, IndexEmbeddingConfig, PrefixSearch, DEFAULT_MIN_WORD_LEN_ONE_TYPO, DEFAULT_MIN_WORD_LEN_TWO_TYPOS, }; use crate::order_by_map::OrderByMap; -use crate::prompt::default_max_bytes; +use crate::prompt::{default_max_bytes, PromptData}; use crate::proximity::ProximityPrecision; use crate::update::index_documents::IndexDocumentsMethod; use crate::update::{IndexDocuments, UpdateIndexingStep}; @@ -185,6 +185,7 @@ pub struct Settings<'a, 't, 'i> { localized_attributes_rules: Setting>, prefix_search: Setting, facet_search: Setting, + chat: Setting, } impl<'a, 't, 'i> Settings<'a, 't, 'i> { @@ -223,6 +224,7 @@ impl<'a, 't, 'i> Settings<'a, 't, 'i> { localized_attributes_rules: Setting::NotSet, prefix_search: Setting::NotSet, facet_search: Setting::NotSet, + chat: Setting::NotSet, indexer_config, } } @@ -453,6 +455,14 @@ impl<'a, 't, 'i> Settings<'a, 't, 'i> { self.facet_search = Setting::Reset; } + pub fn set_chat(&mut self, value: ChatSettings) { + self.chat = Setting::Set(value); + } + + pub fn reset_chat(&mut self) { + self.chat = Setting::Reset; + } + #[tracing::instrument( level = "trace" skip(self, progress_callback, should_abort, settings_diff), @@ -1238,6 +1248,45 @@ impl<'a, 't, 'i> Settings<'a, 't, 'i> { Ok(()) } + fn update_chat_config(&mut self) -> heed::Result { + match &mut self.chat { + Setting::Set(ChatSettings { + description: new_description, + document_template: new_document_template, + document_template_max_bytes: new_document_template_max_bytes, + }) => { + let mut old = self.index.chat_config(self.wtxn)?; + let ChatConfig { + ref mut description, + prompt: PromptData { ref mut template, ref mut max_bytes }, + } = old; + + match new_description { + Setting::Set(d) => *description = d.clone(), + Setting::Reset => *description = Default::default(), + Setting::NotSet => (), + } + + match new_document_template { + Setting::Set(dt) => *template = dt.clone(), + Setting::Reset => *template = Default::default(), + Setting::NotSet => (), + } + + match new_document_template_max_bytes { + Setting::Set(m) => *max_bytes = NonZeroUsize::new(*m), + Setting::Reset => *max_bytes = Some(default_max_bytes()), + Setting::NotSet => (), + } + + self.index.put_chat_config(self.wtxn, &old)?; + Ok(true) + } + Setting::Reset => self.index.delete_chat_config(self.wtxn), + Setting::NotSet => Ok(false), + } + } + pub fn execute(mut self, progress_callback: FP, should_abort: FA) -> Result<()> where FP: Fn(UpdateIndexingStep) + Sync, @@ -1275,6 +1324,7 @@ impl<'a, 't, 'i> Settings<'a, 't, 'i> { self.update_facet_search()?; self.update_localized_attributes_rules()?; self.update_disabled_typos_terms()?; + self.update_chat_config()?; let embedding_config_updates = self.update_embedding_configs()?; diff --git a/crates/milli/src/vector/settings.rs b/crates/milli/src/vector/settings.rs index 3948ad4d8..712c1faa5 100644 --- a/crates/milli/src/vector/settings.rs +++ b/crates/milli/src/vector/settings.rs @@ -33,6 +33,7 @@ pub struct EmbeddingSettings { /// /// - Defaults to `openAi` pub source: Setting, + #[serde(default, skip_serializing_if = "Setting::is_not_set")] #[deserr(default)] #[schema(value_type = Option)] @@ -55,6 +56,7 @@ pub struct EmbeddingSettings { /// - For source `openAi`, defaults to `text-embedding-3-small` /// - For source `huggingFace`, defaults to `BAAI/bge-base-en-v1.5` pub model: Setting, + #[serde(default, skip_serializing_if = "Setting::is_not_set")] #[deserr(default)] #[schema(value_type = Option)] @@ -75,6 +77,7 @@ pub struct EmbeddingSettings { /// - When `model` is set to default, defaults to `617ca489d9e86b49b8167676d8220688b99db36e` /// - Otherwise, defaults to `null` pub revision: Setting, + #[serde(default, skip_serializing_if = "Setting::is_not_set")] #[deserr(default)] #[schema(value_type = Option)] @@ -96,6 +99,7 @@ pub struct EmbeddingSettings { /// /// - Embedders created before this parameter was available default to `forceMean` to preserve the existing behavior. pub pooling: Setting, + #[serde(default, skip_serializing_if = "Setting::is_not_set")] #[deserr(default)] #[schema(value_type = Option)] @@ -118,6 +122,7 @@ pub struct EmbeddingSettings { /// /// - This setting is partially hidden when returned by the settings pub api_key: Setting, + #[serde(default, skip_serializing_if = "Setting::is_not_set")] #[deserr(default)] #[schema(value_type = Option)] @@ -141,6 +146,7 @@ pub struct EmbeddingSettings { /// - For source `openAi`, the dimensions is the maximum allowed by the model. /// - For sources `ollama` and `rest`, the dimensions are inferred by embedding a sample text. pub dimensions: Setting, + #[serde(default, skip_serializing_if = "Setting::is_not_set")] #[deserr(default)] #[schema(value_type = Option)] @@ -167,6 +173,7 @@ pub struct EmbeddingSettings { /// first enabling it. If you are unsure of whether the performance-relevancy tradeoff is right for you, /// we recommend to use this parameter on a test index first. pub binary_quantized: Setting, + #[serde(default, skip_serializing_if = "Setting::is_not_set")] #[deserr(default)] #[schema(value_type = Option)] @@ -183,6 +190,7 @@ pub struct EmbeddingSettings { /// /// - 🏗️ When modified, embeddings are regenerated for documents whose rendering through the template produces a different text. pub document_template: Setting, + #[serde(default, skip_serializing_if = "Setting::is_not_set")] #[deserr(default)] #[schema(value_type = Option)] @@ -201,6 +209,7 @@ pub struct EmbeddingSettings { /// /// - Defaults to 400 pub document_template_max_bytes: Setting, + #[serde(default, skip_serializing_if = "Setting::is_not_set")] #[deserr(default)] #[schema(value_type = Option)] @@ -219,6 +228,7 @@ pub struct EmbeddingSettings { /// - 🌱 When modified for source `openAi`, embeddings are never regenerated /// - 🏗️ When modified for sources `ollama` and `rest`, embeddings are always regenerated pub url: Setting, + #[serde(default, skip_serializing_if = "Setting::is_not_set")] #[deserr(default)] #[schema(value_type = Option)] @@ -236,6 +246,7 @@ pub struct EmbeddingSettings { /// /// - 🏗️ Changing the value of this parameter always regenerates embeddings pub request: Setting, + #[serde(default, skip_serializing_if = "Setting::is_not_set")] #[deserr(default)] #[schema(value_type = Option)] @@ -253,6 +264,7 @@ pub struct EmbeddingSettings { /// /// - 🏗️ Changing the value of this parameter always regenerates embeddings pub response: Setting, + #[serde(default, skip_serializing_if = "Setting::is_not_set")] #[deserr(default)] #[schema(value_type = Option>)] From 75c3f33478ca111243a7cb124e5dc91241d6d3fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Renault?= Date: Wed, 21 May 2025 15:32:34 +0200 Subject: [PATCH 032/103] Correctly support document templates on the chat API --- Cargo.lock | 1 + crates/meilisearch/Cargo.toml | 1 + crates/meilisearch/src/routes/chat.rs | 100 +++++++++++------- .../meilisearch/src/routes/settings/chat.rs | 4 - crates/meilisearch/src/search/mod.rs | 2 +- crates/milli/src/external_documents_ids.rs | 4 +- crates/milli/src/fields_ids_map.rs | 1 + crates/milli/src/lib.rs | 11 +- 8 files changed, 72 insertions(+), 52 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index dd47ee94c..4a21d2ab6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3684,6 +3684,7 @@ dependencies = [ "brotli 6.0.0", "bstr", "build-info", + "bumpalo", "byte-unit", "bytes", "cargo_toml", diff --git a/crates/meilisearch/Cargo.toml b/crates/meilisearch/Cargo.toml index 398b62dad..62f7cfa0a 100644 --- a/crates/meilisearch/Cargo.toml +++ b/crates/meilisearch/Cargo.toml @@ -32,6 +32,7 @@ async-trait = "0.1.85" bstr = "1.11.3" byte-unit = { version = "5.1.6", features = ["serde"] } bytes = "1.9.0" +bumpalo = "3.16.0" clap = { version = "4.5.24", features = ["derive", "env"] } crossbeam-channel = "0.5.15" deserr = { version = "0.6.3", features = ["actix-web"] } diff --git a/crates/meilisearch/src/routes/chat.rs b/crates/meilisearch/src/routes/chat.rs index d85c14c36..c34bbcaea 100644 --- a/crates/meilisearch/src/routes/chat.rs +++ b/crates/meilisearch/src/routes/chat.rs @@ -1,5 +1,7 @@ +use std::cell::RefCell; use std::collections::HashMap; use std::mem; +use std::sync::RwLock; use std::time::Duration; use actix_web::web::{self, Data}; @@ -16,27 +18,33 @@ use async_openai::types::{ FunctionObjectArgs, }; use async_openai::Client; +use bumpalo::Bump; use futures::StreamExt; use index_scheduler::IndexScheduler; use meilisearch_auth::AuthController; use meilisearch_types::error::ResponseError; +use meilisearch_types::heed::RoTxn; use meilisearch_types::keys::actions; -use meilisearch_types::milli::index::IndexEmbeddingConfig; -use meilisearch_types::milli::prompt::PromptData; -use meilisearch_types::milli::vector::EmbeddingConfig; -use meilisearch_types::{Document, Index}; -use serde::{Deserialize, Serialize}; +use meilisearch_types::milli::index::ChatConfig; +use meilisearch_types::milli::prompt::{Prompt, PromptData}; +use meilisearch_types::milli::update::new::document::DocumentFromDb; +use meilisearch_types::milli::{ + DocumentId, FieldIdMapWithMetadata, GlobalFieldsIdsMap, MetadataBuilder, TimeBudget, +}; +use meilisearch_types::Index; +use serde::Deserialize; use serde_json::json; use tokio::runtime::Handle; use tokio::sync::mpsc::error::SendError; use super::settings::chat::{ChatPrompts, GlobalChatSettings}; +use crate::error::MeilisearchHttpError; use crate::extractors::authentication::policies::ActionPolicy; use crate::extractors::authentication::{extract_token_from_request, GuardedData, Policy as _}; use crate::metrics::MEILISEARCH_DEGRADED_SEARCH_REQUESTS; use crate::routes::indexes::search::search_kind; use crate::search::{ - add_search_rules, perform_search, HybridQuery, RetrieveVectors, SearchQuery, SemanticRatio, + add_search_rules, prepare_search, search_from_kind, HybridQuery, SearchQuery, SemanticRatio, }; use crate::search_queue::SearchQueue; @@ -175,15 +183,22 @@ async fn process_search_request( let permit = search_queue.try_get_search_permit().await?; let features = index_scheduler.features(); let index_cloned = index.clone(); - let search_result = tokio::task::spawn_blocking(move || { - perform_search( - index_uid.to_string(), - &index_cloned, - query, - search_kind, - RetrieveVectors::new(false), - features, - ) + let search_result = tokio::task::spawn_blocking(move || -> Result<_, ResponseError> { + let rtxn = index_cloned.read_txn()?; + let time_budget = match index_cloned + .search_cutoff(&rtxn) + .map_err(|e| MeilisearchHttpError::from_milli(e, Some(index_uid.clone())))? + { + Some(cutoff) => TimeBudget::new(Duration::from_millis(cutoff)), + None => TimeBudget::default(), + }; + + let (search, _is_finite_pagination, _max_total_hits, _offset) = + prepare_search(&index_cloned, &rtxn, &query, &search_kind, time_budget, features)?; + + search_from_kind(index_uid, search_kind, search) + .map(|(search_results, _)| search_results) + .map_err(ResponseError::from) }) .await; permit.drop().await; @@ -198,9 +213,11 @@ async fn process_search_request( // analytics.publish(aggregate, &req); let search_result = search_result?; - let formatted = - format_documents(&index, search_result.hits.into_iter().map(|doc| doc.document)); + let rtxn = index.read_txn()?; + let render_alloc = Bump::new(); + let formatted = format_documents(&rtxn, &index, &render_alloc, search_result.documents_ids)?; let text = formatted.join("\n"); + drop(rtxn); Ok((index, text)) } @@ -506,31 +523,36 @@ struct SearchInIndexParameters { q: Option, } -fn format_documents(index: &Index, documents: impl Iterator) -> Vec { - let rtxn = index.read_txn().unwrap(); - let IndexEmbeddingConfig { name: _, config, user_provided: _ } = index - .embedding_configs(&rtxn) - .unwrap() +fn format_documents<'t, 'doc>( + rtxn: &RoTxn<'t>, + index: &Index, + doc_alloc: &'doc Bump, + internal_docids: Vec, +) -> Result, ResponseError> { + let ChatConfig { prompt: PromptData { template, max_bytes }, .. } = index.chat_config(rtxn)?; + + let prompt = Prompt::new(template, max_bytes).unwrap(); + let fid_map = index.fields_ids_map(rtxn)?; + let metadata_builder = MetadataBuilder::from_index(index, rtxn)?; + let fid_map_with_meta = FieldIdMapWithMetadata::new(fid_map.clone(), metadata_builder); + let global = RwLock::new(fid_map_with_meta); + let gfid_map = RefCell::new(GlobalFieldsIdsMap::new(&global)); + + let external_ids: Vec = index + .external_id_of(rtxn, internal_docids.iter().copied())? .into_iter() - .find(|conf| conf.name == EMBEDDER_NAME) - .unwrap(); + .collect::>()?; - let EmbeddingConfig { - embedder_options: _, - prompt: PromptData { template, max_bytes: _ }, - quantized: _, - } = config; + let mut renders = Vec::new(); + for (docid, external_docid) in internal_docids.into_iter().zip(external_ids) { + let document = match DocumentFromDb::new(docid, rtxn, index, &fid_map)? { + Some(doc) => doc, + None => continue, + }; - #[derive(Serialize)] - struct Doc { - doc: T, + let text = prompt.render_document(&external_docid, document, &gfid_map, doc_alloc).unwrap(); + renders.push(text); } - let template = liquid::ParserBuilder::with_stdlib().build().unwrap().parse(&template).unwrap(); - documents - .map(|doc| { - let object = liquid::to_object(&Doc { doc }).unwrap(); - template.render(&object).unwrap() - }) - .collect() + Ok(renders) } diff --git a/crates/meilisearch/src/routes/settings/chat.rs b/crates/meilisearch/src/routes/settings/chat.rs index a971ad102..586fa041e 100644 --- a/crates/meilisearch/src/routes/settings/chat.rs +++ b/crates/meilisearch/src/routes/settings/chat.rs @@ -1,5 +1,3 @@ -use std::collections::BTreeMap; - use actix_web::web::{self, Data}; use actix_web::HttpResponse; use index_scheduler::IndexScheduler; @@ -51,7 +49,6 @@ pub struct GlobalChatSettings { pub base_api: Option, pub api_key: Option, pub prompts: ChatPrompts, - pub indexes: BTreeMap, } #[derive(Debug, Serialize, Deserialize)] @@ -105,7 +102,6 @@ impl Default for GlobalChatSettings { .to_string(), pre_query: "".to_string(), }, - indexes: BTreeMap::new(), } } } diff --git a/crates/meilisearch/src/search/mod.rs b/crates/meilisearch/src/search/mod.rs index 1dd16c474..16d04cd58 100644 --- a/crates/meilisearch/src/search/mod.rs +++ b/crates/meilisearch/src/search/mod.rs @@ -882,7 +882,7 @@ pub fn add_search_rules(filter: &mut Option, rules: IndexSearchRules) { } } -fn prepare_search<'t>( +pub fn prepare_search<'t>( index: &'t Index, rtxn: &'t RoTxn, query: &'t SearchQuery, diff --git a/crates/milli/src/external_documents_ids.rs b/crates/milli/src/external_documents_ids.rs index 755b801ec..598465e5f 100644 --- a/crates/milli/src/external_documents_ids.rs +++ b/crates/milli/src/external_documents_ids.rs @@ -32,13 +32,13 @@ impl ExternalDocumentsIds { &self, rtxn: &RoTxn<'_>, external_id: A, - ) -> heed::Result> { + ) -> heed::Result> { self.0.get(rtxn, external_id.as_ref()) } /// An helper function to debug this type, returns an `HashMap` of both, /// soft and hard fst maps, combined. - pub fn to_hash_map(&self, rtxn: &RoTxn<'_>) -> heed::Result> { + pub fn to_hash_map(&self, rtxn: &RoTxn<'_>) -> heed::Result> { let mut map = HashMap::default(); for result in self.0.iter(rtxn)? { let (external, internal) = result?; diff --git a/crates/milli/src/fields_ids_map.rs b/crates/milli/src/fields_ids_map.rs index 9a016e7bd..d2abd840c 100644 --- a/crates/milli/src/fields_ids_map.rs +++ b/crates/milli/src/fields_ids_map.rs @@ -7,6 +7,7 @@ use crate::FieldId; mod global; pub mod metadata; pub use global::GlobalFieldsIdsMap; +pub use metadata::{FieldIdMapWithMetadata, MetadataBuilder}; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct FieldsIdsMap { diff --git a/crates/milli/src/lib.rs b/crates/milli/src/lib.rs index 47d3dc75c..504b4c68d 100644 --- a/crates/milli/src/lib.rs +++ b/crates/milli/src/lib.rs @@ -52,18 +52,19 @@ pub use search::new::{ }; use serde_json::Value; pub use thread_pool_no_abort::{PanicCatched, ThreadPoolNoAbort, ThreadPoolNoAbortBuilder}; -pub use {charabia as tokenizer, heed, rhai}; +pub use {arroy, charabia as tokenizer, heed, rhai}; pub use self::asc_desc::{AscDesc, AscDescError, Member, SortError}; -pub use self::attribute_patterns::AttributePatterns; -pub use self::attribute_patterns::PatternMatch; +pub use self::attribute_patterns::{AttributePatterns, PatternMatch}; pub use self::criterion::{default_criteria, Criterion, CriterionError}; pub use self::error::{ Error, FieldIdMapMissingEntry, InternalError, SerializationError, UserError, }; pub use self::external_documents_ids::ExternalDocumentsIds; pub use self::fieldids_weights_map::FieldidsWeightsMap; -pub use self::fields_ids_map::{FieldsIdsMap, GlobalFieldsIdsMap}; +pub use self::fields_ids_map::{ + FieldIdMapWithMetadata, FieldsIdsMap, GlobalFieldsIdsMap, MetadataBuilder, +}; pub use self::filterable_attributes_rules::{ FilterFeatures, FilterableAttributesFeatures, FilterableAttributesPatterns, FilterableAttributesRule, @@ -84,8 +85,6 @@ pub use self::search::{ }; pub use self::update::ChannelCongestion; -pub use arroy; - pub type Result = std::result::Result; pub type Attribute = u32; From 05828ff2c7bc5f7237ab599cb2d6c32557fd842b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Renault?= Date: Wed, 21 May 2025 16:18:37 +0200 Subject: [PATCH 033/103] Always use the frequency matching strategy --- crates/meilisearch/src/routes/chat.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/crates/meilisearch/src/routes/chat.rs b/crates/meilisearch/src/routes/chat.rs index c34bbcaea..2f434d706 100644 --- a/crates/meilisearch/src/routes/chat.rs +++ b/crates/meilisearch/src/routes/chat.rs @@ -44,7 +44,8 @@ use crate::extractors::authentication::{extract_token_from_request, GuardedData, use crate::metrics::MEILISEARCH_DEGRADED_SEARCH_REQUESTS; use crate::routes::indexes::search::search_kind; use crate::search::{ - add_search_rules, prepare_search, search_from_kind, HybridQuery, SearchQuery, SemanticRatio, + add_search_rules, prepare_search, search_from_kind, HybridQuery, MatchingStrategy, SearchQuery, + SemanticRatio, }; use crate::search_queue::SearchQueue; @@ -159,6 +160,7 @@ async fn process_search_request( embedder: EMBEDDER_NAME.to_string(), }), limit: 20, + matching_strategy: MatchingStrategy::Frequency, ..Default::default() }; From afb43d266e3a96d00ff91bd4544c024f73be9df1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Renault?= Date: Wed, 21 May 2025 16:24:51 +0200 Subject: [PATCH 034/103] Correctly list the chat settings key actions --- crates/meilisearch-types/src/keys.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/crates/meilisearch-types/src/keys.rs b/crates/meilisearch-types/src/keys.rs index dfa50aa1e..1c1ebad5b 100644 --- a/crates/meilisearch-types/src/keys.rs +++ b/crates/meilisearch-types/src/keys.rs @@ -326,6 +326,9 @@ pub enum Action { #[serde(rename = "chat.get")] #[deserr(rename = "chat.get")] Chat, + #[serde(rename = "chatSettings.*")] + #[deserr(rename = "chatSettings.*")] + ChatSettingsAll, #[serde(rename = "chatSettings.get")] #[deserr(rename = "chatSettings.get")] ChatSettingsGet, @@ -357,6 +360,9 @@ impl Action { SETTINGS_ALL => Some(Self::SettingsAll), SETTINGS_GET => Some(Self::SettingsGet), SETTINGS_UPDATE => Some(Self::SettingsUpdate), + CHAT_SETTINGS_ALL => Some(Self::ChatSettingsAll), + CHAT_SETTINGS_GET => Some(Self::ChatSettingsGet), + CHAT_SETTINGS_UPDATE => Some(Self::ChatSettingsUpdate), STATS_ALL => Some(Self::StatsAll), STATS_GET => Some(Self::StatsGet), METRICS_ALL => Some(Self::MetricsAll), @@ -424,6 +430,7 @@ pub mod actions { pub const NETWORK_UPDATE: u8 = NetworkUpdate.repr(); pub const CHAT: u8 = Chat.repr(); + pub const CHAT_SETTINGS_ALL: u8 = ChatSettingsAll.repr(); pub const CHAT_SETTINGS_GET: u8 = ChatSettingsGet.repr(); pub const CHAT_SETTINGS_UPDATE: u8 = ChatSettingsUpdate.repr(); } From 79298720917f0d3293fedab781e404fa813f62f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Renault?= Date: Wed, 21 May 2025 21:06:11 +0200 Subject: [PATCH 035/103] Better chat settings management --- crates/meilisearch/src/routes/chat.rs | 31 +++--- .../meilisearch/src/routes/settings/chat.rs | 100 ++++++++++++------ crates/milli/src/update/settings.rs | 9 ++ 3 files changed, 95 insertions(+), 45 deletions(-) diff --git a/crates/meilisearch/src/routes/chat.rs b/crates/meilisearch/src/routes/chat.rs index 2f434d706..05512bff3 100644 --- a/crates/meilisearch/src/routes/chat.rs +++ b/crates/meilisearch/src/routes/chat.rs @@ -28,6 +28,7 @@ use meilisearch_types::keys::actions; use meilisearch_types::milli::index::ChatConfig; use meilisearch_types::milli::prompt::{Prompt, PromptData}; use meilisearch_types::milli::update::new::document::DocumentFromDb; +use meilisearch_types::milli::update::Setting; use meilisearch_types::milli::{ DocumentId, FieldIdMapWithMetadata, GlobalFieldsIdsMap, MetadataBuilder, TimeBudget, }; @@ -107,20 +108,20 @@ fn setup_search_tool( .function( FunctionObjectArgs::default() .name(SEARCH_IN_INDEX_FUNCTION_NAME) - .description(&prompts.search_description) + .description(&prompts.search_description.clone().unwrap()) .parameters(json!({ "type": "object", "properties": { "index_uid": { "type": "string", "enum": index_uids, - "description": prompts.search_index_uid_param, + "description": prompts.search_index_uid_param.clone().unwrap(), }, "q": { // Unfortunately, Mistral does not support an array of types, here. // "type": ["string", "null"], "type": "string", - "description": prompts.search_q_param, + "description": prompts.search_q_param.clone().unwrap(), } }, "required": ["index_uid", "q"], @@ -136,7 +137,9 @@ fn setup_search_tool( chat_completion.messages.insert( 0, ChatCompletionRequestMessage::System(ChatCompletionRequestSystemMessage { - content: ChatCompletionRequestSystemMessageContent::Text(prompts.system.clone()), + content: ChatCompletionRequestSystemMessageContent::Text( + prompts.system.as_ref().unwrap().clone(), + ), name: None, }), ); @@ -239,16 +242,17 @@ async fn non_streamed_chat( }; let mut config = OpenAIConfig::default(); - if let Some(api_key) = chat_settings.api_key.as_ref() { + if let Setting::Set(api_key) = chat_settings.api_key.as_ref() { config = config.with_api_key(api_key); } - if let Some(base_api) = chat_settings.base_api.as_ref() { + if let Setting::Set(base_api) = chat_settings.base_api.as_ref() { config = config.with_api_base(base_api); } let client = Client::with_config(config); let auth_token = extract_token_from_request(&req)?.unwrap(); - setup_search_tool(&index_scheduler, filters, &mut chat_completion, &chat_settings.prompts)?; + let prompts = chat_settings.prompts.clone().or(Setting::Set(ChatPrompts::default())).unwrap(); + setup_search_tool(&index_scheduler, filters, &mut chat_completion, &prompts)?; let mut response; loop { @@ -296,7 +300,7 @@ async fn non_streamed_chat( tool_call_id: call.id.clone(), content: ChatCompletionRequestToolMessageContent::Text(format!( "{}\n\n{text}", - chat_settings.prompts.pre_query + chat_settings.prompts.clone().unwrap().pre_query.unwrap() )), }, )); @@ -325,20 +329,21 @@ async fn streamed_chat( let filters = index_scheduler.filters(); let chat_settings = match index_scheduler.chat_settings().unwrap() { - Some(value) => serde_json::from_value(value).unwrap(), + Some(value) => serde_json::from_value(value.clone()).unwrap(), None => GlobalChatSettings::default(), }; let mut config = OpenAIConfig::default(); - if let Some(api_key) = chat_settings.api_key.as_ref() { + if let Setting::Set(api_key) = chat_settings.api_key.as_ref() { config = config.with_api_key(api_key); } - if let Some(base_api) = chat_settings.base_api.as_ref() { + if let Setting::Set(base_api) = chat_settings.base_api.as_ref() { config = config.with_api_base(base_api); } let auth_token = extract_token_from_request(&req)?.unwrap().to_string(); - setup_search_tool(&index_scheduler, filters, &mut chat_completion, &chat_settings.prompts)?; + let prompts = chat_settings.prompts.clone().or(Setting::Set(ChatPrompts::default())).unwrap(); + setup_search_tool(&index_scheduler, filters, &mut chat_completion, &prompts)?; let (tx, rx) = tokio::sync::mpsc::channel(10); let _join_handle = Handle::current().spawn(async move { @@ -447,7 +452,7 @@ async fn streamed_chat( let tool = ChatCompletionRequestToolMessage { tool_call_id: call.id.clone(), content: ChatCompletionRequestToolMessageContent::Text( - format!("{}\n\n{text}", chat_settings.prompts.pre_query), + format!("{}\n\n{text}", chat_settings.prompts.as_ref().unwrap().pre_query.as_ref().unwrap()), ), }; diff --git a/crates/meilisearch/src/routes/settings/chat.rs b/crates/meilisearch/src/routes/settings/chat.rs index 586fa041e..42fb456b8 100644 --- a/crates/meilisearch/src/routes/settings/chat.rs +++ b/crates/meilisearch/src/routes/settings/chat.rs @@ -3,6 +3,7 @@ use actix_web::HttpResponse; use index_scheduler::IndexScheduler; use meilisearch_types::error::ResponseError; use meilisearch_types::keys::actions; +use meilisearch_types::milli::update::Setting; use serde::{Deserialize, Serialize}; use crate::extractors::authentication::policies::ActionPolicy; @@ -35,37 +36,63 @@ async fn patch_settings( ActionPolicy<{ actions::CHAT_SETTINGS_UPDATE }>, Data, >, - web::Json(chat_settings): web::Json, + web::Json(new): web::Json, ) -> Result { - let chat_settings = serde_json::to_value(chat_settings).unwrap(); - index_scheduler.put_chat_settings(&chat_settings)?; + let old = match index_scheduler.chat_settings()? { + Some(value) => serde_json::from_value(value).unwrap(), + None => GlobalChatSettings::default(), + }; + + let settings = GlobalChatSettings { + source: new.source.or(old.source), + base_api: new.base_api.clone().or(old.base_api), + api_key: new.api_key.clone().or(old.api_key), + prompts: match (new.prompts, old.prompts) { + (Setting::NotSet, set) | (set, Setting::NotSet) => set, + (Setting::Set(_) | Setting::Reset, Setting::Reset) => Setting::Reset, + (Setting::Reset, Setting::Set(set)) => Setting::Set(set), + // If both are set we must merge the prompts settings + (Setting::Set(new), Setting::Set(old)) => Setting::Set(ChatPrompts { + system: new.system.or(old.system), + search_description: new.search_description.or(old.search_description), + search_q_param: new.search_q_param.or(old.search_q_param), + search_index_uid_param: new.search_index_uid_param.or(old.search_index_uid_param), + pre_query: new.pre_query.or(old.pre_query), + }), + }, + }; + + let value = serde_json::to_value(settings).unwrap(); + index_scheduler.put_chat_settings(&value)?; Ok(HttpResponse::Ok().finish()) } -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize)] #[serde(deny_unknown_fields, rename_all = "camelCase")] pub struct GlobalChatSettings { - pub source: String, - pub base_api: Option, - pub api_key: Option, - pub prompts: ChatPrompts, + #[serde(default, skip_serializing_if = "Setting::is_not_set")] + pub source: Setting, + #[serde(default, skip_serializing_if = "Setting::is_not_set")] + pub base_api: Setting, + #[serde(default, skip_serializing_if = "Setting::is_not_set")] + pub api_key: Setting, + #[serde(default, skip_serializing_if = "Setting::is_not_set")] + pub prompts: Setting, } -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize)] #[serde(deny_unknown_fields, rename_all = "camelCase")] pub struct ChatPrompts { - pub system: String, - pub search_description: String, - pub search_q_param: String, - pub search_index_uid_param: String, - pub pre_query: String, -} - -#[derive(Debug, Serialize, Deserialize)] -#[serde(deny_unknown_fields, rename_all = "camelCase")] -pub struct ChatIndexSettings { - pub description: String, - pub document_template: String, + #[serde(default, skip_serializing_if = "Setting::is_not_set")] + pub system: Setting, + #[serde(default, skip_serializing_if = "Setting::is_not_set")] + pub search_description: Setting, + #[serde(default, skip_serializing_if = "Setting::is_not_set")] + pub search_q_param: Setting, + #[serde(default, skip_serializing_if = "Setting::is_not_set")] + pub search_index_uid_param: Setting, + #[serde(default, skip_serializing_if = "Setting::is_not_set")] + pub pre_query: Setting, } const DEFAULT_SYSTEM_MESSAGE: &str = "You are a highly capable research assistant with access to powerful search tools. IMPORTANT INSTRUCTIONS:\ @@ -91,17 +118,26 @@ Selecting the right index ensures the most relevant results for the user query"; impl Default for GlobalChatSettings { fn default() -> Self { GlobalChatSettings { - source: "openai".to_string(), - base_api: None, - api_key: None, - prompts: ChatPrompts { - system: DEFAULT_SYSTEM_MESSAGE.to_string(), - search_description: DEFAULT_SEARCH_IN_INDEX_TOOL_DESCRIPTION.to_string(), - search_q_param: DEFAULT_SEARCH_IN_INDEX_Q_PARAMETER_TOOL_DESCRIPTION.to_string(), - search_index_uid_param: DEFAULT_SEARCH_IN_INDEX_INDEX_PARAMETER_TOOL_DESCRIPTION - .to_string(), - pre_query: "".to_string(), - }, + source: Setting::Set("openAi".to_string()), + base_api: Setting::NotSet, + api_key: Setting::NotSet, + prompts: Setting::Set(ChatPrompts::default()), + } + } +} + +impl Default for ChatPrompts { + fn default() -> Self { + ChatPrompts { + system: Setting::Set(DEFAULT_SYSTEM_MESSAGE.to_string()), + search_description: Setting::Set(DEFAULT_SEARCH_IN_INDEX_TOOL_DESCRIPTION.to_string()), + search_q_param: Setting::Set( + DEFAULT_SEARCH_IN_INDEX_Q_PARAMETER_TOOL_DESCRIPTION.to_string(), + ), + search_index_uid_param: Setting::Set( + DEFAULT_SEARCH_IN_INDEX_INDEX_PARAMETER_TOOL_DESCRIPTION.to_string(), + ), + pre_query: Setting::Set(Default::default()), } } } diff --git a/crates/milli/src/update/settings.rs b/crates/milli/src/update/settings.rs index 697bf8168..bba8bc758 100644 --- a/crates/milli/src/update/settings.rs +++ b/crates/milli/src/update/settings.rs @@ -123,6 +123,15 @@ impl Setting { *self = new; true } + + #[track_caller] + pub fn unwrap(self) -> T { + match self { + Setting::Set(value) => value, + Setting::Reset => panic!("Setting::Reset unwrapped"), + Setting::NotSet => panic!("Setting::NotSet unwrapped"), + } + } } impl Serialize for Setting { From 3e53527bff022ab67c628b19c20adfccb3df2ec4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Renault?= Date: Wed, 21 May 2025 21:18:18 +0200 Subject: [PATCH 036/103] redact the chat settings API key --- .../meilisearch/src/routes/settings/chat.rs | 30 ++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/crates/meilisearch/src/routes/settings/chat.rs b/crates/meilisearch/src/routes/settings/chat.rs index 42fb456b8..09d476008 100644 --- a/crates/meilisearch/src/routes/settings/chat.rs +++ b/crates/meilisearch/src/routes/settings/chat.rs @@ -24,10 +24,11 @@ async fn get_settings( Data, >, ) -> Result { - let settings = match index_scheduler.chat_settings()? { + let mut settings = match index_scheduler.chat_settings()? { Some(value) => serde_json::from_value(value).unwrap(), None => GlobalChatSettings::default(), }; + settings.hide_secrets(); Ok(HttpResponse::Ok().json(settings)) } @@ -80,6 +81,33 @@ pub struct GlobalChatSettings { pub prompts: Setting, } +impl GlobalChatSettings { + pub fn hide_secrets(&mut self) { + match &mut self.api_key { + Setting::Set(key) => Self::hide_secret(key), + Setting::Reset => (), + Setting::NotSet => (), + } + } + + fn hide_secret(secret: &mut String) { + match secret.len() { + x if x < 10 => { + secret.replace_range(.., "XXX..."); + } + x if x < 20 => { + secret.replace_range(2.., "XXXX..."); + } + x if x < 30 => { + secret.replace_range(3.., "XXXXX..."); + } + _x => { + secret.replace_range(5.., "XXXXXX..."); + } + } + } +} + #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(deny_unknown_fields, rename_all = "camelCase")] pub struct ChatPrompts { From 7b74810b03fd86f5179f4ef8b3d1e2e438608f01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Renault?= Date: Thu, 22 May 2025 10:40:43 +0200 Subject: [PATCH 037/103] Add the index descriptions to the function description --- crates/meilisearch/src/routes/chat.rs | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/crates/meilisearch/src/routes/chat.rs b/crates/meilisearch/src/routes/chat.rs index 05512bff3..0f50aafac 100644 --- a/crates/meilisearch/src/routes/chat.rs +++ b/crates/meilisearch/src/routes/chat.rs @@ -1,5 +1,6 @@ use std::cell::RefCell; use std::collections::HashMap; +use std::fmt::Write as _; use std::mem; use std::sync::RwLock; use std::time::Duration; @@ -97,18 +98,29 @@ fn setup_search_tool( panic!("{SEARCH_IN_INDEX_FUNCTION_NAME} function already set"); } - let index_uids: Vec<_> = index_scheduler - .index_names()? - .into_iter() - .filter(|index_uid| filters.is_index_authorized(&index_uid)) - .collect(); + let mut index_uids = Vec::new(); + let mut function_description = prompts.search_description.clone().unwrap(); + index_scheduler.try_for_each_index::<_, ()>(|name, index| { + // Make sure to skip unauthorized indexes + if !filters.is_index_authorized(&name) { + return Ok(()); + } + + let rtxn = index.read_txn()?; + let chat_config = index.chat_config(&rtxn)?; + let index_description = chat_config.description; + let _ = writeln!(&mut function_description, "\n\n - {name}: {index_description}\n"); + index_uids.push(name.to_string()); + + Ok(()) + })?; let tool = ChatCompletionToolArgs::default() .r#type(ChatCompletionToolType::Function) .function( FunctionObjectArgs::default() .name(SEARCH_IN_INDEX_FUNCTION_NAME) - .description(&prompts.search_description.clone().unwrap()) + .description(&function_description) .parameters(json!({ "type": "object", "properties": { From 036a9d5dbc27a5664d781634c677be70b320758c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Renault?= Date: Thu, 22 May 2025 10:42:36 +0200 Subject: [PATCH 038/103] Expose a well defined set of sources --- crates/meilisearch/src/routes/settings/chat.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/crates/meilisearch/src/routes/settings/chat.rs b/crates/meilisearch/src/routes/settings/chat.rs index 09d476008..60fd01ab6 100644 --- a/crates/meilisearch/src/routes/settings/chat.rs +++ b/crates/meilisearch/src/routes/settings/chat.rs @@ -68,11 +68,17 @@ async fn patch_settings( Ok(HttpResponse::Ok().finish()) } +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(deny_unknown_fields, rename_all = "camelCase")] +pub enum ChatSource { + OpenAi, +} + #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(deny_unknown_fields, rename_all = "camelCase")] pub struct GlobalChatSettings { #[serde(default, skip_serializing_if = "Setting::is_not_set")] - pub source: Setting, + pub source: Setting, #[serde(default, skip_serializing_if = "Setting::is_not_set")] pub base_api: Setting, #[serde(default, skip_serializing_if = "Setting::is_not_set")] @@ -146,7 +152,7 @@ Selecting the right index ensures the most relevant results for the user query"; impl Default for GlobalChatSettings { fn default() -> Self { GlobalChatSettings { - source: Setting::Set("openAi".to_string()), + source: Setting::NotSet, base_api: Setting::NotSet, api_key: Setting::NotSet, prompts: Setting::Set(ChatPrompts::default()), From 33dfd422dbe6f0822b3c768e771dabb347562922 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Renault?= Date: Thu, 22 May 2025 15:34:49 +0200 Subject: [PATCH 039/103] Introduce a lot of search parameters and make Deserr happy --- crates/meilisearch-auth/src/lib.rs | 1 + crates/meilisearch-types/src/deserr/mod.rs | 18 +- crates/meilisearch-types/src/settings.rs | 2 +- crates/meilisearch-types/src/task_view.rs | 4 +- crates/meilisearch-types/src/tasks.rs | 2 +- crates/meilisearch/src/routes/chat.rs | 65 ++++-- crates/meilisearch/src/search/mod.rs | 5 +- crates/milli/src/index.rs | 43 +++- crates/milli/src/update/chat.rs | 222 ++++++++++++++++++++- crates/milli/src/update/settings.rs | 86 +++++++- 10 files changed, 411 insertions(+), 37 deletions(-) diff --git a/crates/meilisearch-auth/src/lib.rs b/crates/meilisearch-auth/src/lib.rs index d72ba386c..a19ad7b8c 100644 --- a/crates/meilisearch-auth/src/lib.rs +++ b/crates/meilisearch-auth/src/lib.rs @@ -165,6 +165,7 @@ impl AuthController { } } +#[derive(Debug)] pub struct AuthFilter { search_rules: Option, key_authorized_indexes: SearchRules, diff --git a/crates/meilisearch-types/src/deserr/mod.rs b/crates/meilisearch-types/src/deserr/mod.rs index f5ad18d5c..f1470c201 100644 --- a/crates/meilisearch-types/src/deserr/mod.rs +++ b/crates/meilisearch-types/src/deserr/mod.rs @@ -4,9 +4,12 @@ use std::marker::PhantomData; use std::ops::ControlFlow; use deserr::errors::{JsonError, QueryParamError}; -use deserr::{take_cf_content, DeserializeError, IntoValue, MergeWithError, ValuePointerRef}; +use deserr::{ + take_cf_content, DeserializeError, Deserr, IntoValue, MergeWithError, ValuePointerRef, +}; +use milli::update::ChatSettings; -use crate::error::deserr_codes::*; +use crate::error::deserr_codes::{self, *}; use crate::error::{ Code, DeserrParseBoolError, DeserrParseIntError, ErrorCode, InvalidTaskDateError, ParseOffsetDateTimeError, @@ -33,6 +36,7 @@ pub struct DeserrError { pub code: Code, _phantom: PhantomData<(Format, C)>, } + impl DeserrError { pub fn new(msg: String, code: Code) -> Self { Self { msg, code, _phantom: PhantomData } @@ -117,6 +121,16 @@ impl DeserializeError for DeserrQueryParamError { } } +impl Deserr> for ChatSettings { + fn deserialize_from_value( + value: deserr::Value, + location: ValuePointerRef, + ) -> Result> { + Deserr::::deserialize_from_value(value, location) + .map_err(|e| DeserrError::new(e.to_string(), InvalidSettingsIndexChat.error_code())) + } +} + pub fn immutable_field_error(field: &str, accepted: &[&str], code: Code) -> DeserrJsonError { let msg = format!( "Immutable field `{field}`: expected one of {}", diff --git a/crates/meilisearch-types/src/settings.rs b/crates/meilisearch-types/src/settings.rs index b2f0c2f5b..d26c575d6 100644 --- a/crates/meilisearch-types/src/settings.rs +++ b/crates/meilisearch-types/src/settings.rs @@ -186,7 +186,7 @@ impl Deserr for SettingEmbeddingSettings { /// Holds all the settings for an index. `T` can either be `Checked` if they represents settings /// whose validity is guaranteed, or `Unchecked` if they need to be validated. In the later case, a /// call to `check` will return a `Settings` from a `Settings`. -#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq, Deserr, ToSchema)] +#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Deserr, ToSchema)] #[serde( deny_unknown_fields, rename_all = "camelCase", diff --git a/crates/meilisearch-types/src/task_view.rs b/crates/meilisearch-types/src/task_view.rs index 7a6faee39..86a00426b 100644 --- a/crates/meilisearch-types/src/task_view.rs +++ b/crates/meilisearch-types/src/task_view.rs @@ -8,7 +8,7 @@ use crate::error::ResponseError; use crate::settings::{Settings, Unchecked}; use crate::tasks::{serialize_duration, Details, IndexSwap, Kind, Status, Task, TaskId}; -#[derive(Debug, Clone, PartialEq, Eq, Serialize, ToSchema)] +#[derive(Debug, Clone, PartialEq, Serialize, ToSchema)] #[serde(rename_all = "camelCase")] #[schema(rename_all = "camelCase")] pub struct TaskView { @@ -67,7 +67,7 @@ impl TaskView { } } -#[derive(Default, Debug, PartialEq, Eq, Clone, Serialize, Deserialize, ToSchema)] +#[derive(Default, Debug, PartialEq, Clone, Serialize, Deserialize, ToSchema)] #[serde(rename_all = "camelCase")] #[schema(rename_all = "camelCase")] pub struct DetailsView { diff --git a/crates/meilisearch-types/src/tasks.rs b/crates/meilisearch-types/src/tasks.rs index 24254d36e..95c52d9a6 100644 --- a/crates/meilisearch-types/src/tasks.rs +++ b/crates/meilisearch-types/src/tasks.rs @@ -597,7 +597,7 @@ impl fmt::Display for ParseTaskKindError { } impl std::error::Error for ParseTaskKindError {} -#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] +#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] pub enum Details { DocumentAdditionOrUpdate { received_documents: u64, diff --git a/crates/meilisearch/src/routes/chat.rs b/crates/meilisearch/src/routes/chat.rs index 0f50aafac..b8c3eee29 100644 --- a/crates/meilisearch/src/routes/chat.rs +++ b/crates/meilisearch/src/routes/chat.rs @@ -26,7 +26,7 @@ use meilisearch_auth::AuthController; use meilisearch_types::error::ResponseError; use meilisearch_types::heed::RoTxn; use meilisearch_types::keys::actions; -use meilisearch_types::milli::index::ChatConfig; +use meilisearch_types::milli::index::{self, ChatConfig, SearchParameters}; use meilisearch_types::milli::prompt::{Prompt, PromptData}; use meilisearch_types::milli::update::new::document::DocumentFromDb; use meilisearch_types::milli::update::Setting; @@ -46,12 +46,12 @@ use crate::extractors::authentication::{extract_token_from_request, GuardedData, use crate::metrics::MEILISEARCH_DEGRADED_SEARCH_REQUESTS; use crate::routes::indexes::search::search_kind; use crate::search::{ - add_search_rules, prepare_search, search_from_kind, HybridQuery, MatchingStrategy, SearchQuery, - SemanticRatio, + add_search_rules, prepare_search, search_from_kind, HybridQuery, MatchingStrategy, + RankingScoreThreshold, SearchQuery, SemanticRatio, DEFAULT_SEARCH_LIMIT, + DEFAULT_SEMANTIC_RATIO, }; use crate::search_queue::SearchQueue; -const EMBEDDER_NAME: &str = "openai"; const SEARCH_IN_INDEX_FUNCTION_NAME: &str = "_meiliSearchInIndex"; pub fn configure(cfg: &mut web::ServiceConfig) { @@ -168,14 +168,43 @@ async fn process_search_request( index_uid: String, q: Option, ) -> Result<(Index, String), ResponseError> { + // TBD + // let mut aggregate = SearchAggregator::::from_query(&query); + + let index = index_scheduler.index(&index_uid)?; + let rtxn = index.static_read_txn()?; + let ChatConfig { description: _, prompt: _, search_parameters } = index.chat_config(&rtxn)?; + let SearchParameters { + hybrid, + limit, + sort, + distinct, + matching_strategy, + attributes_to_search_on, + ranking_score_threshold, + } = search_parameters; + let mut query = SearchQuery { q, - hybrid: Some(HybridQuery { - semantic_ratio: SemanticRatio::default(), - embedder: EMBEDDER_NAME.to_string(), + hybrid: hybrid.map(|index::HybridQuery { semantic_ratio, embedder }| HybridQuery { + semantic_ratio: SemanticRatio::try_from(semantic_ratio) + .ok() + .unwrap_or_else(DEFAULT_SEMANTIC_RATIO), + embedder, }), - limit: 20, - matching_strategy: MatchingStrategy::Frequency, + limit: limit.unwrap_or_else(DEFAULT_SEARCH_LIMIT), + sort: sort, + distinct: distinct, + matching_strategy: matching_strategy + .map(|ms| match ms { + index::MatchingStrategy::Last => MatchingStrategy::Last, + index::MatchingStrategy::All => MatchingStrategy::All, + index::MatchingStrategy::Frequency => MatchingStrategy::Frequency, + }) + .unwrap_or(MatchingStrategy::Frequency), + attributes_to_search_on: attributes_to_search_on, + ranking_score_threshold: ranking_score_threshold + .and_then(|rst| RankingScoreThreshold::try_from(rst).ok()), ..Default::default() }; @@ -189,19 +218,13 @@ async fn process_search_request( if let Some(search_rules) = auth_filter.get_index_search_rules(&index_uid) { add_search_rules(&mut query.filter, search_rules); } - - // TBD - // let mut aggregate = SearchAggregator::::from_query(&query); - - let index = index_scheduler.index(&index_uid)?; let search_kind = search_kind(&query, index_scheduler.get_ref(), index_uid.to_string(), &index)?; let permit = search_queue.try_get_search_permit().await?; let features = index_scheduler.features(); let index_cloned = index.clone(); - let search_result = tokio::task::spawn_blocking(move || -> Result<_, ResponseError> { - let rtxn = index_cloned.read_txn()?; + let output = tokio::task::spawn_blocking(move || -> Result<_, ResponseError> { let time_budget = match index_cloned .search_cutoff(&rtxn) .map_err(|e| MeilisearchHttpError::from_milli(e, Some(index_uid.clone())))? @@ -214,14 +237,14 @@ async fn process_search_request( prepare_search(&index_cloned, &rtxn, &query, &search_kind, time_budget, features)?; search_from_kind(index_uid, search_kind, search) - .map(|(search_results, _)| search_results) + .map(|(search_results, _)| (rtxn, search_results)) .map_err(ResponseError::from) }) .await; permit.drop().await; - let search_result = search_result?; - if let Ok(ref search_result) = search_result { + let output = output?; + if let Ok((_, ref search_result)) = output { // aggregate.succeed(search_result); if search_result.degraded { MEILISEARCH_DEGRADED_SEARCH_REQUESTS.inc(); @@ -229,8 +252,8 @@ async fn process_search_request( } // analytics.publish(aggregate, &req); - let search_result = search_result?; - let rtxn = index.read_txn()?; + let (rtxn, search_result) = output?; + // let rtxn = index.read_txn()?; let render_alloc = Bump::new(); let formatted = format_documents(&rtxn, &index, &render_alloc, search_result.documents_ids)?; let text = formatted.join("\n"); diff --git a/crates/meilisearch/src/search/mod.rs b/crates/meilisearch/src/search/mod.rs index 16d04cd58..848591a4f 100644 --- a/crates/meilisearch/src/search/mod.rs +++ b/crates/meilisearch/src/search/mod.rs @@ -122,6 +122,7 @@ pub struct SearchQuery { #[derive(Debug, Clone, Copy, PartialEq, Deserr, ToSchema, Serialize)] #[deserr(try_from(f64) = TryFrom::try_from -> InvalidSearchRankingScoreThreshold)] pub struct RankingScoreThreshold(f64); + impl std::convert::TryFrom for RankingScoreThreshold { type Error = InvalidSearchRankingScoreThreshold; @@ -279,8 +280,8 @@ impl fmt::Debug for SearchQuery { #[deserr(error = DeserrJsonError, rename_all = camelCase, deny_unknown_fields)] #[serde(rename_all = "camelCase")] pub struct HybridQuery { - #[deserr(default, error = DeserrJsonError, default)] - #[schema(value_type = f32, default)] + #[deserr(default, error = DeserrJsonError)] + #[schema(default, value_type = f32)] #[serde(default)] pub semantic_ratio: SemanticRatio, #[deserr(error = DeserrJsonError)] diff --git a/crates/milli/src/index.rs b/crates/milli/src/index.rs index a5145cb0b..e6f28d02e 100644 --- a/crates/milli/src/index.rs +++ b/crates/milli/src/index.rs @@ -1695,7 +1695,7 @@ impl Index { pub fn chat_config(&self, txn: &RoTxn<'_>) -> heed::Result { self.main - .remap_types::>() + .remap_types::>() .get(txn, main_key::CHAT) .map(|o| o.unwrap_or_default()) } @@ -1705,7 +1705,7 @@ impl Index { txn: &mut RwTxn<'_>, val: &ChatConfig, ) -> heed::Result<()> { - self.main.remap_types::>().put(txn, main_key::CHAT, &val) + self.main.remap_types::>().put(txn, main_key::CHAT, &val) } pub(crate) fn delete_chat_config(&self, txn: &mut RwTxn<'_>) -> heed::Result { @@ -1943,15 +1943,54 @@ pub struct ChatConfig { pub description: String, /// Contains the document template and max template length. pub prompt: PromptData, + pub search_parameters: SearchParameters, +} + +#[derive(Debug, Default, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct SearchParameters { + #[serde(skip_serializing_if = "Option::is_none")] + pub hybrid: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub limit: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub sort: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub distinct: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub matching_strategy: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub attributes_to_search_on: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub ranking_score_threshold: Option, +} + +#[derive(Debug, Default, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct HybridQuery { + pub semantic_ratio: f32, + pub embedder: String, } #[derive(Debug, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] pub struct PrefixSettings { pub prefix_count_threshold: usize, pub max_prefix_length: usize, pub compute_prefixes: PrefixSearch, } +#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub enum MatchingStrategy { + /// Remove query words from last to first + Last, + /// All query words are mandatory + All, + /// Remove query words from the most frequent to the least + Frequency, +} + #[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default)] #[serde(rename_all = "camelCase")] pub enum PrefixSearch { diff --git a/crates/milli/src/update/chat.rs b/crates/milli/src/update/chat.rs index 44e646f6d..b8fbc582d 100644 --- a/crates/milli/src/update/chat.rs +++ b/crates/milli/src/update/chat.rs @@ -1,14 +1,19 @@ +use std::error::Error; +use std::fmt; + +use deserr::errors::JsonError; use deserr::Deserr; use serde::{Deserialize, Serialize}; use utoipa::ToSchema; -use crate::index::ChatConfig; +use crate::index::{self, ChatConfig, SearchParameters}; use crate::prompt::{default_max_bytes, PromptData}; use crate::update::Setting; +use crate::TermsMatchingStrategy; -#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq, Deserr, ToSchema)] +#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Deserr, ToSchema)] #[serde(deny_unknown_fields, rename_all = "camelCase")] -#[deserr(deny_unknown_fields, rename_all = camelCase)] +#[deserr(error = JsonError, deny_unknown_fields, rename_all = camelCase)] pub struct ChatSettings { #[serde(default, skip_serializing_if = "Setting::is_not_set")] #[deserr(default)] @@ -29,17 +34,226 @@ pub struct ChatSettings { #[deserr(default)] #[schema(value_type = Option)] pub document_template_max_bytes: Setting, + + /// The search parameters to use for the LLM. + #[serde(default, skip_serializing_if = "Setting::is_not_set")] + #[deserr(default)] + #[schema(value_type = Option)] + pub search_parameters: Setting, } impl From for ChatSettings { fn from(config: ChatConfig) -> Self { - let ChatConfig { description, prompt: PromptData { template, max_bytes } } = config; + let ChatConfig { + description, + prompt: PromptData { template, max_bytes }, + search_parameters, + } = config; ChatSettings { description: Setting::Set(description), document_template: Setting::Set(template), document_template_max_bytes: Setting::Set( max_bytes.unwrap_or(default_max_bytes()).get(), ), + search_parameters: Setting::Set({ + let SearchParameters { + hybrid, + limit, + sort, + distinct, + matching_strategy, + attributes_to_search_on, + ranking_score_threshold, + } = search_parameters; + + let hybrid = hybrid.map(|index::HybridQuery { semantic_ratio, embedder }| { + HybridQuery { semantic_ratio: SemanticRatio(semantic_ratio), embedder } + }); + + let matching_strategy = matching_strategy.map(|ms| match ms { + index::MatchingStrategy::Last => MatchingStrategy::Last, + index::MatchingStrategy::All => MatchingStrategy::All, + index::MatchingStrategy::Frequency => MatchingStrategy::Frequency, + }); + + let ranking_score_threshold = ranking_score_threshold.map(RankingScoreThreshold); + + ChatSearchParams { + hybrid: Setting::some_or_not_set(hybrid), + limit: Setting::some_or_not_set(limit), + sort: Setting::some_or_not_set(sort), + distinct: Setting::some_or_not_set(distinct), + matching_strategy: Setting::some_or_not_set(matching_strategy), + attributes_to_search_on: Setting::some_or_not_set(attributes_to_search_on), + ranking_score_threshold: Setting::some_or_not_set(ranking_score_threshold), + } + }), } } } + +#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Deserr, ToSchema)] +#[serde(deny_unknown_fields, rename_all = "camelCase")] +#[deserr(error = JsonError, deny_unknown_fields, rename_all = camelCase)] +pub struct ChatSearchParams { + #[serde(default, skip_serializing_if = "Setting::is_not_set")] + #[deserr(default)] + #[schema(value_type = Option)] + pub hybrid: Setting, + + #[serde(default, skip_serializing_if = "Setting::is_not_set")] + #[deserr(default = Setting::Set(20))] + #[schema(value_type = Option)] + pub limit: Setting, + + // #[serde(default, skip_serializing_if = "Setting::is_not_set")] + // #[deserr(default)] + // pub attributes_to_retrieve: Option>, + + // #[serde(default, skip_serializing_if = "Setting::is_not_set")] + // #[deserr(default)] + // pub filter: Option, + // + #[serde(default, skip_serializing_if = "Setting::is_not_set")] + #[deserr(default)] + #[schema(value_type = Option>)] + pub sort: Setting>, + + #[serde(default, skip_serializing_if = "Setting::is_not_set")] + #[deserr(default)] + #[schema(value_type = Option)] + pub distinct: Setting, + + #[serde(default, skip_serializing_if = "Setting::is_not_set")] + #[deserr(default)] + #[schema(value_type = Option)] + pub matching_strategy: Setting, + + #[serde(default, skip_serializing_if = "Setting::is_not_set")] + #[deserr(default)] + #[schema(value_type = Option>)] + pub attributes_to_search_on: Setting>, + + #[serde(default, skip_serializing_if = "Setting::is_not_set")] + #[deserr(default)] + #[schema(value_type = Option)] + pub ranking_score_threshold: Setting, +} + +#[derive(Debug, Clone, Default, Deserr, ToSchema, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +#[deserr(error = JsonError, rename_all = camelCase, deny_unknown_fields)] +pub struct HybridQuery { + #[deserr(default)] + #[serde(default)] + #[schema(default, value_type = f32)] + pub semantic_ratio: SemanticRatio, + #[schema(value_type = String)] + pub embedder: String, +} + +#[derive(Debug, Clone, Copy, Deserr, ToSchema, PartialEq, Serialize, Deserialize)] +#[deserr(try_from(f32) = TryFrom::try_from -> InvalidSearchSemanticRatio)] +pub struct SemanticRatio(f32); + +impl Default for SemanticRatio { + fn default() -> Self { + SemanticRatio(0.5) + } +} + +impl std::convert::TryFrom for SemanticRatio { + type Error = InvalidSearchSemanticRatio; + + fn try_from(f: f32) -> Result { + // the suggested "fix" is: `!(0.0..=1.0).contains(&f)`` which is allegedly less readable + #[allow(clippy::manual_range_contains)] + if f > 1.0 || f < 0.0 { + Err(InvalidSearchSemanticRatio) + } else { + Ok(SemanticRatio(f)) + } + } +} + +#[derive(Debug)] +pub struct InvalidSearchSemanticRatio; + +impl Error for InvalidSearchSemanticRatio {} + +impl fmt::Display for InvalidSearchSemanticRatio { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "the value of `semanticRatio` is invalid, expected a float between `0.0` and `1.0`." + ) + } +} + +impl std::ops::Deref for SemanticRatio { + type Target = f32; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, Deserr, ToSchema, Serialize, Deserialize)] +#[deserr(rename_all = camelCase)] +#[serde(rename_all = "camelCase")] +pub enum MatchingStrategy { + /// Remove query words from last to first + Last, + /// All query words are mandatory + All, + /// Remove query words from the most frequent to the least + Frequency, +} + +impl Default for MatchingStrategy { + fn default() -> Self { + Self::Last + } +} + +impl From for TermsMatchingStrategy { + fn from(other: MatchingStrategy) -> Self { + match other { + MatchingStrategy::Last => Self::Last, + MatchingStrategy::All => Self::All, + MatchingStrategy::Frequency => Self::Frequency, + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Deserr, ToSchema, Serialize, Deserialize)] +#[deserr(try_from(f64) = TryFrom::try_from -> InvalidSearchRankingScoreThreshold)] +pub struct RankingScoreThreshold(pub f64); + +impl std::convert::TryFrom for RankingScoreThreshold { + type Error = InvalidSearchRankingScoreThreshold; + + fn try_from(f: f64) -> Result { + // the suggested "fix" is: `!(0.0..=1.0).contains(&f)`` which is allegedly less readable + #[allow(clippy::manual_range_contains)] + if f > 1.0 || f < 0.0 { + Err(InvalidSearchRankingScoreThreshold) + } else { + Ok(RankingScoreThreshold(f)) + } + } +} + +#[derive(Debug)] +pub struct InvalidSearchRankingScoreThreshold; + +impl Error for InvalidSearchRankingScoreThreshold {} + +impl fmt::Display for InvalidSearchRankingScoreThreshold { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "the value of `rankingScoreThreshold` is invalid, expected a float between `0.0` and `1.0`." + ) + } +} diff --git a/crates/milli/src/update/settings.rs b/crates/milli/src/update/settings.rs index bba8bc758..bb589c5ee 100644 --- a/crates/milli/src/update/settings.rs +++ b/crates/milli/src/update/settings.rs @@ -11,6 +11,7 @@ use roaring::RoaringBitmap; use serde::{Deserialize, Deserializer, Serialize, Serializer}; use time::OffsetDateTime; +use super::chat::{ChatSearchParams, RankingScoreThreshold}; use super::del_add::{DelAdd, DelAddOperation}; use super::index_documents::{IndexDocumentsConfig, Transform}; use super::{ChatSettings, IndexerConfig}; @@ -22,8 +23,8 @@ use crate::error::UserError; use crate::fields_ids_map::metadata::{FieldIdMapWithMetadata, MetadataBuilder}; use crate::filterable_attributes_rules::match_faceted_field; use crate::index::{ - ChatConfig, IndexEmbeddingConfig, PrefixSearch, DEFAULT_MIN_WORD_LEN_ONE_TYPO, - DEFAULT_MIN_WORD_LEN_TWO_TYPOS, + ChatConfig, IndexEmbeddingConfig, MatchingStrategy, PrefixSearch, + DEFAULT_MIN_WORD_LEN_ONE_TYPO, DEFAULT_MIN_WORD_LEN_TWO_TYPOS, }; use crate::order_by_map::OrderByMap; use crate::prompt::{default_max_bytes, PromptData}; @@ -1263,11 +1264,13 @@ impl<'a, 't, 'i> Settings<'a, 't, 'i> { description: new_description, document_template: new_document_template, document_template_max_bytes: new_document_template_max_bytes, + search_parameters: new_search_parameters, }) => { let mut old = self.index.chat_config(self.wtxn)?; let ChatConfig { ref mut description, prompt: PromptData { ref mut template, ref mut max_bytes }, + ref mut search_parameters, } = old; match new_description { @@ -1288,6 +1291,85 @@ impl<'a, 't, 'i> Settings<'a, 't, 'i> { Setting::NotSet => (), } + match new_search_parameters { + Setting::Set(sp) => { + let ChatSearchParams { + hybrid, + limit, + sort, + distinct, + matching_strategy, + attributes_to_search_on, + ranking_score_threshold, + } = sp; + + match hybrid { + Setting::Set(hybrid) => { + search_parameters.hybrid = Some(crate::index::HybridQuery { + semantic_ratio: *hybrid.semantic_ratio, + embedder: hybrid.embedder.clone(), + }) + } + Setting::Reset => search_parameters.hybrid = None, + Setting::NotSet => (), + } + + match limit { + Setting::Set(limit) => search_parameters.limit = Some(*limit), + Setting::Reset => search_parameters.limit = None, + Setting::NotSet => (), + } + + match sort { + Setting::Set(sort) => search_parameters.sort = Some(sort.clone()), + Setting::Reset => search_parameters.sort = None, + Setting::NotSet => (), + } + + match distinct { + Setting::Set(distinct) => { + search_parameters.distinct = Some(distinct.clone()) + } + Setting::Reset => search_parameters.distinct = None, + Setting::NotSet => (), + } + + match matching_strategy { + Setting::Set(matching_strategy) => { + let strategy = match matching_strategy { + super::chat::MatchingStrategy::Last => MatchingStrategy::Last, + super::chat::MatchingStrategy::All => MatchingStrategy::All, + super::chat::MatchingStrategy::Frequency => { + MatchingStrategy::Frequency + } + }; + search_parameters.matching_strategy = Some(strategy) + } + Setting::Reset => search_parameters.matching_strategy = None, + Setting::NotSet => (), + } + + match attributes_to_search_on { + Setting::Set(attributes_to_search_on) => { + search_parameters.attributes_to_search_on = + Some(attributes_to_search_on.clone()) + } + Setting::Reset => search_parameters.attributes_to_search_on = None, + Setting::NotSet => (), + } + + match ranking_score_threshold { + Setting::Set(RankingScoreThreshold(score)) => { + search_parameters.ranking_score_threshold = Some(*score) + } + Setting::Reset => search_parameters.ranking_score_threshold = None, + Setting::NotSet => (), + } + } + Setting::Reset => *search_parameters = Default::default(), + Setting::NotSet => (), + } + self.index.put_chat_config(self.wtxn, &old)?; Ok(true) } From 564cad116380b273662173d6dd2f80a9a08e0df5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Renault?= Date: Fri, 23 May 2025 17:25:09 +0200 Subject: [PATCH 040/103] Call specific tools to show progression and results. --- crates/meilisearch/src/routes/chat.rs | 458 ++++++++++++++++++-------- 1 file changed, 327 insertions(+), 131 deletions(-) diff --git a/crates/meilisearch/src/routes/chat.rs b/crates/meilisearch/src/routes/chat.rs index b8c3eee29..3db948eb8 100644 --- a/crates/meilisearch/src/routes/chat.rs +++ b/crates/meilisearch/src/routes/chat.rs @@ -10,13 +10,14 @@ use actix_web::{Either, HttpRequest, HttpResponse, Responder}; use actix_web_lab::sse::{self, Event, Sse}; use async_openai::config::OpenAIConfig; use async_openai::types::{ - ChatCompletionMessageToolCall, ChatCompletionMessageToolCallChunk, - ChatCompletionRequestAssistantMessageArgs, ChatCompletionRequestMessage, - ChatCompletionRequestSystemMessage, ChatCompletionRequestSystemMessageContent, - ChatCompletionRequestToolMessage, ChatCompletionRequestToolMessageContent, - ChatCompletionStreamResponseDelta, ChatCompletionToolArgs, ChatCompletionToolType, - CreateChatCompletionRequest, FinishReason, FunctionCall, FunctionCallStream, - FunctionObjectArgs, + ChatChoiceStream, ChatCompletionMessageToolCall, ChatCompletionMessageToolCallChunk, + ChatCompletionRequestAssistantMessage, ChatCompletionRequestAssistantMessageArgs, + ChatCompletionRequestMessage, ChatCompletionRequestSystemMessage, + ChatCompletionRequestSystemMessageContent, ChatCompletionRequestToolMessage, + ChatCompletionRequestToolMessageContent, ChatCompletionStreamResponseDelta, + ChatCompletionToolArgs, ChatCompletionToolType, CreateChatCompletionRequest, + CreateChatCompletionStreamResponse, FinishReason, FunctionCall, FunctionCallStream, + FunctionObjectArgs, Role, }; use async_openai::Client; use bumpalo::Bump; @@ -34,7 +35,7 @@ use meilisearch_types::milli::{ DocumentId, FieldIdMapWithMetadata, GlobalFieldsIdsMap, MetadataBuilder, TimeBudget, }; use meilisearch_types::Index; -use serde::Deserialize; +use serde::{Deserialize, Serialize}; use serde_json::json; use tokio::runtime::Handle; use tokio::sync::mpsc::error::SendError; @@ -52,7 +53,9 @@ use crate::search::{ }; use crate::search_queue::SearchQueue; -const SEARCH_IN_INDEX_FUNCTION_NAME: &str = "_meiliSearchInIndex"; +const MEILI_SEARCH_PROGRESS_NAME: &str = "_meiliSearchProgress"; +const MEILI_APPEND_CONVERSATION_MESSAGE_NAME: &str = "_meiliAppendConversationMessage"; +const MEILI_SEARCH_IN_INDEX_FUNCTION_NAME: &str = "_meiliSearchInIndex"; pub fn configure(cfg: &mut web::ServiceConfig) { cfg.service(web::resource("/completions").route(web::post().to(chat))); @@ -86,18 +89,45 @@ async fn chat( } } +#[derive(Default, Debug, Clone, Copy)] +pub struct FunctionSupport { + /// Defines if we can call the _meiliSearchProgress function + /// to inform the front-end about what we are searching for. + progress: bool, + /// Defines if we can call the _meiliAppendConversationMessage + /// function to provide the messages to append into the conversation. + append_to_conversation: bool, +} + /// Setup search tool in chat completion request fn setup_search_tool( index_scheduler: &Data, filters: &meilisearch_auth::AuthFilter, chat_completion: &mut CreateChatCompletionRequest, prompts: &ChatPrompts, -) -> Result<(), ResponseError> { +) -> Result { let tools = chat_completion.tools.get_or_insert_default(); - if tools.iter().find(|t| t.function.name == SEARCH_IN_INDEX_FUNCTION_NAME).is_some() { - panic!("{SEARCH_IN_INDEX_FUNCTION_NAME} function already set"); + if tools.iter().find(|t| t.function.name == MEILI_SEARCH_IN_INDEX_FUNCTION_NAME).is_some() { + panic!("{MEILI_SEARCH_IN_INDEX_FUNCTION_NAME} function already set"); } + // Remove internal tools used for front-end notifications as they should be hidden from the LLM. + let mut progress = false; + let mut append_to_conversation = false; + tools.retain(|tool| { + match tool.function.name.as_str() { + MEILI_SEARCH_PROGRESS_NAME => { + progress = true; + false + } + MEILI_APPEND_CONVERSATION_MESSAGE_NAME => { + append_to_conversation = true; + false + } + _ => true, // keep other tools + } + }); + let mut index_uids = Vec::new(); let mut function_description = prompts.search_description.clone().unwrap(); index_scheduler.try_for_each_index::<_, ()>(|name, index| { @@ -119,7 +149,7 @@ fn setup_search_tool( .r#type(ChatCompletionToolType::Function) .function( FunctionObjectArgs::default() - .name(SEARCH_IN_INDEX_FUNCTION_NAME) + .name(MEILI_SEARCH_IN_INDEX_FUNCTION_NAME) .description(&function_description) .parameters(json!({ "type": "object", @@ -145,7 +175,9 @@ fn setup_search_tool( ) .build() .unwrap(); + tools.push(tool); + chat_completion.messages.insert( 0, ChatCompletionRequestMessage::System(ChatCompletionRequestSystemMessage { @@ -156,7 +188,7 @@ fn setup_search_tool( }), ); - Ok(()) + Ok(FunctionSupport { progress, append_to_conversation }) } /// Process search request and return formatted results @@ -287,7 +319,8 @@ async fn non_streamed_chat( let auth_token = extract_token_from_request(&req)?.unwrap(); let prompts = chat_settings.prompts.clone().or(Setting::Set(ChatPrompts::default())).unwrap(); - setup_search_tool(&index_scheduler, filters, &mut chat_completion, &prompts)?; + let FunctionSupport { progress, append_to_conversation } = + setup_search_tool(&index_scheduler, filters, &mut chat_completion, &prompts)?; let mut response; loop { @@ -300,7 +333,7 @@ async fn non_streamed_chat( let (meili_calls, other_calls): (Vec<_>, Vec<_>) = tool_calls .into_iter() - .partition(|call| call.function.name == SEARCH_IN_INDEX_FUNCTION_NAME); + .partition(|call| call.function.name == MEILI_SEARCH_IN_INDEX_FUNCTION_NAME); chat_completion.messages.push( ChatCompletionRequestAssistantMessageArgs::default() @@ -378,7 +411,8 @@ async fn streamed_chat( let auth_token = extract_token_from_request(&req)?.unwrap().to_string(); let prompts = chat_settings.prompts.clone().or(Setting::Set(ChatPrompts::default())).unwrap(); - setup_search_tool(&index_scheduler, filters, &mut chat_completion, &prompts)?; + let FunctionSupport { progress, append_to_conversation } = + setup_search_tool(&index_scheduler, filters, &mut chat_completion, &prompts)?; let (tx, rx) = tokio::sync::mpsc::channel(10); let _join_handle = Handle::current().spawn(async move { @@ -395,21 +429,8 @@ async fn streamed_chat( let choice = &resp.choices[0]; finish_reason = choice.finish_reason; - #[allow(deprecated)] - let ChatCompletionStreamResponseDelta { - content, - // Using deprecated field but keeping for compatibility - function_call: _, - ref tool_calls, - role: _, - refusal: _, - } = &choice.delta; - - if content.is_some() { - if let Err(SendError(_)) = tx.send(Event::Data(sse::Data::new_json(&resp).unwrap())).await { - return; - } - } + let ChatCompletionStreamResponseDelta { ref tool_calls, .. } = + &choice.delta; match tool_calls { Some(tool_calls) => { @@ -422,109 +443,195 @@ async fn streamed_chat( } = chunk; let FunctionCallStream { name, arguments } = function.as_ref().unwrap(); + global_tool_calls .entry(*index) - .and_modify(|call| call.append(arguments.as_ref().unwrap())) - .or_insert_with(|| Call { - id: id.as_ref().unwrap().clone(), - function_name: name.as_ref().unwrap().clone(), - arguments: arguments.as_ref().unwrap().clone(), - }); - } - } - None if !global_tool_calls.is_empty() => { - let (meili_calls, _other_calls): (Vec<_>, Vec<_>) = - mem::take(&mut global_tool_calls) - .into_values() - .map(|call| ChatCompletionMessageToolCall { - id: call.id, - r#type: Some(ChatCompletionToolType::Function), - function: FunctionCall { - name: call.function_name, - arguments: call.arguments, - }, + .and_modify(|call| { + if call.is_internal() { + call.append(arguments.as_ref().unwrap()) + } }) - .partition(|call| call.function.name == SEARCH_IN_INDEX_FUNCTION_NAME); - - chat_completion.messages.push( - ChatCompletionRequestAssistantMessageArgs::default() - .tool_calls(meili_calls.clone()) - .build() - .unwrap() - .into(), - ); - - for call in meili_calls { - if let Err(SendError(_)) = tx.send(Event::Data( - sse::Data::new_json(json!({ - "object": "chat.completion.tool.call", - "tool": call, - })) - .unwrap(), - )) - .await { - return; - } - - let result = match serde_json::from_str(&call.function.arguments) { - Ok(SearchInIndexParameters { index_uid, q }) => process_search_request( - &index_scheduler, - auth_ctrl.clone(), - &search_queue, - &auth_token, - index_uid, - q, - ).await.map_err(|e| e.to_string()), - Err(err) => Err(err.to_string()), - }; - - let is_error = result.is_err(); - let text = match result { - Ok((_, text)) => text, - Err(err) => err, - }; - - let tool = ChatCompletionRequestToolMessage { - tool_call_id: call.id.clone(), - content: ChatCompletionRequestToolMessageContent::Text( - format!("{}\n\n{text}", chat_settings.prompts.as_ref().unwrap().pre_query.as_ref().unwrap()), - ), - }; - - if let Err(SendError(_)) = tx.send(Event::Data( - sse::Data::new_json(json!({ - "object": if is_error { - "chat.completion.tool.error" + .or_insert_with(|| { + if name.as_ref().map_or(false, |n| { + n == MEILI_SEARCH_IN_INDEX_FUNCTION_NAME + }) { + Call::Internal { + id: id.as_ref().unwrap().clone(), + function_name: name.as_ref().unwrap().clone(), + arguments: arguments.as_ref().unwrap().clone(), + } } else { - "chat.completion.tool.output" - }, - "tool": ChatCompletionRequestToolMessage { - tool_call_id: call.id, - content: ChatCompletionRequestToolMessageContent::Text( - text, - ), - }, - })) - .unwrap(), - )) - .await { - return; - } + Call::External { _id: id.as_ref().unwrap().clone() } + } + }); - chat_completion.messages.push(ChatCompletionRequestMessage::Tool(tool)); + if global_tool_calls.get(index).map_or(false, Call::is_external) + { + todo!("Support forwarding external tool calls"); + } + } + } + None => { + if !global_tool_calls.is_empty() { + let (meili_calls, other_calls): (Vec<_>, Vec<_>) = + mem::take(&mut global_tool_calls) + .into_values() + .flat_map(|call| match call { + Call::Internal { + id, + function_name: name, + arguments, + } => Some(ChatCompletionMessageToolCall { + id, + r#type: Some(ChatCompletionToolType::Function), + function: FunctionCall { name, arguments }, + }), + Call::External { _id: _ } => None, + }) + .partition(|call| { + call.function.name + == MEILI_SEARCH_IN_INDEX_FUNCTION_NAME + }); + + chat_completion.messages.push( + ChatCompletionRequestAssistantMessageArgs::default() + .tool_calls(meili_calls.clone()) + .build() + .unwrap() + .into(), + ); + + assert!( + other_calls.is_empty(), + "We do not support external tool forwarding for now" + ); + + for call in meili_calls { + if progress { + let call = MeiliSearchProgress { + function_name: call.function.name.clone(), + function_arguments: call + .function + .arguments + .clone(), + }; + let resp = call.create_response(resp.clone()); + // Send the event of "we are doing a search" + if let Err(SendError(_)) = tx + .send(Event::Data(sse::Data::new_json(&resp).unwrap())) + .await + { + return; + } + } + + if append_to_conversation { + // Ask the front-end user to append this tool *call* to the conversation + let call = MeiliAppendConversationMessage(ChatCompletionRequestMessage::Assistant( + ChatCompletionRequestAssistantMessage { + content: None, + refusal: None, + name: None, + audio: None, + tool_calls: Some(vec![ + ChatCompletionMessageToolCall { + id: call.id.clone(), + r#type: Some(ChatCompletionToolType::Function), + function: FunctionCall { + name: call.function.name.clone(), + arguments: call.function.arguments.clone(), + }, + }, + ]), + function_call: None, + } + )); + let resp = call.create_response(resp.clone()); + if let Err(SendError(_)) = tx + .send(Event::Data(sse::Data::new_json(&resp).unwrap())) + .await + { + return; + } + } + + let result = + match serde_json::from_str(&call.function.arguments) { + Ok(SearchInIndexParameters { index_uid, q }) => { + process_search_request( + &index_scheduler, + auth_ctrl.clone(), + &search_queue, + &auth_token, + index_uid, + q, + ) + .await + .map_err(|e| e.to_string()) + } + Err(err) => Err(err.to_string()), + }; + + let text = match result { + Ok((_, text)) => text, + Err(err) => err, + }; + + let tool = ChatCompletionRequestMessage::Tool(ChatCompletionRequestToolMessage { + tool_call_id: call.id.clone(), + content: ChatCompletionRequestToolMessageContent::Text( + format!( + "{}\n\n{text}", + chat_settings + .prompts + .as_ref() + .unwrap() + .pre_query + .as_ref() + .unwrap() + ), + ), + }); + + if append_to_conversation { + // Ask the front-end user to append this tool *output* to the conversation + let tool = MeiliAppendConversationMessage(tool.clone()); + let resp = tool.create_response(resp.clone()); + if let Err(SendError(_)) = tx + .send(Event::Data(sse::Data::new_json(&resp).unwrap())) + .await + { + return; + } + } + + chat_completion.messages.push(tool); + } + } else { + if let Err(SendError(_)) = tx + .send(Event::Data(sse::Data::new_json(&resp).unwrap())) + .await + { + return; + } } } - None => (), } } Err(err) => { - tracing::error!("{err:?}"); - if let Err(SendError(_)) = tx.send(Event::Data(sse::Data::new_json(&json!({ - "object": "chat.completion.error", - "tool": err.to_string(), - })).unwrap())).await { - return; - } + // tracing::error!("{err:?}"); + // if let Err(SendError(_)) = tx + // .send(Event::Data( + // sse::Data::new_json(&json!({ + // "object": "chat.completion.error", + // "tool": err.to_string(), + // })) + // .unwrap(), + // )) + // .await + // { + // return; + // } break 'main; } @@ -543,17 +650,106 @@ async fn streamed_chat( Ok(Sse::from_infallible_receiver(rx).with_retry_duration(Duration::from_secs(10))) } +#[derive(Debug, Clone, Serialize)] +/// Give context about what Meilisearch is doing. +struct MeiliSearchProgress { + /// The name of the function we are executing. + pub function_name: String, + /// The arguments of the function we are executing, encoded in JSON. + pub function_arguments: String, +} + +impl MeiliSearchProgress { + fn create_response( + &self, + mut resp: CreateChatCompletionStreamResponse, + ) -> CreateChatCompletionStreamResponse { + let call_text = serde_json::to_string(self).unwrap(); + let tool_call = ChatCompletionMessageToolCallChunk { + index: 0, + id: Some(uuid::Uuid::new_v4().to_string()), + r#type: Some(ChatCompletionToolType::Function), + function: Some(FunctionCallStream { + name: Some(MEILI_SEARCH_PROGRESS_NAME.to_string()), + arguments: Some(call_text), + }), + }; + resp.choices[0] = ChatChoiceStream { + index: 0, + delta: ChatCompletionStreamResponseDelta { + content: None, + function_call: None, + tool_calls: Some(vec![tool_call]), + role: Some(Role::Assistant), + refusal: None, + }, + finish_reason: None, + logprobs: None, + }; + resp + } +} + +struct MeiliAppendConversationMessage(pub ChatCompletionRequestMessage); + +impl MeiliAppendConversationMessage { + fn create_response( + &self, + mut resp: CreateChatCompletionStreamResponse, + ) -> CreateChatCompletionStreamResponse { + let call_text = serde_json::to_string(&self.0).unwrap(); + let tool_call = ChatCompletionMessageToolCallChunk { + index: 0, + id: Some(uuid::Uuid::new_v4().to_string()), + r#type: Some(ChatCompletionToolType::Function), + function: Some(FunctionCallStream { + name: Some(MEILI_APPEND_CONVERSATION_MESSAGE_NAME.to_string()), + arguments: Some(call_text), + }), + }; + resp.choices[0] = ChatChoiceStream { + index: 0, + delta: ChatCompletionStreamResponseDelta { + content: None, + function_call: None, + tool_calls: Some(vec![tool_call]), + role: Some(Role::Assistant), + refusal: None, + }, + finish_reason: None, + logprobs: None, + }; + resp + } +} + /// The structure used to aggregate the function calls to make. #[derive(Debug)] -struct Call { - id: String, - function_name: String, - arguments: String, +enum Call { + /// Tool calls to tools that must be managed by Meilisearch internally. + /// Typically the search functions. + Internal { id: String, function_name: String, arguments: String }, + /// Tool calls that we track but only to know that its not our functions. + /// We return the function calls as-is to the end-user. + External { _id: String }, } impl Call { - fn append(&mut self, arguments: &str) { - self.arguments.push_str(arguments); + fn is_internal(&self) -> bool { + matches!(self, Call::Internal { .. }) + } + + fn is_external(&self) -> bool { + matches!(self, Call::External { .. }) + } + + fn append(&mut self, more: &str) { + match self { + Call::Internal { arguments, .. } => arguments.push_str(more), + Call::External { .. } => { + panic!("Cannot append argument chunks to an external function") + } + } } } From 2a067d3327482061718684cda51e2cdb72fa626e Mon Sep 17 00:00:00 2001 From: Kerollmops Date: Mon, 26 May 2025 14:04:53 +0200 Subject: [PATCH 041/103] Fix compilation error in test --- crates/dump/src/lib.rs | 1 + crates/index-scheduler/src/insta_snapshot.rs | 1 + crates/milli/src/update/test_settings.rs | 1 + 3 files changed, 3 insertions(+) diff --git a/crates/dump/src/lib.rs b/crates/dump/src/lib.rs index 95d75700e..285818a87 100644 --- a/crates/dump/src/lib.rs +++ b/crates/dump/src/lib.rs @@ -305,6 +305,7 @@ pub(crate) mod test { localized_attributes: Setting::NotSet, facet_search: Setting::NotSet, prefix_search: Setting::NotSet, + chat: Setting::NotSet, _kind: std::marker::PhantomData, }; settings.check() diff --git a/crates/index-scheduler/src/insta_snapshot.rs b/crates/index-scheduler/src/insta_snapshot.rs index 89e615132..d01548319 100644 --- a/crates/index-scheduler/src/insta_snapshot.rs +++ b/crates/index-scheduler/src/insta_snapshot.rs @@ -34,6 +34,7 @@ pub fn snapshot_index_scheduler(scheduler: &IndexScheduler) -> String { planned_failures: _, run_loop_iteration: _, embedders: _, + chat_settings: _, } = scheduler; let rtxn = env.read_txn().unwrap(); diff --git a/crates/milli/src/update/test_settings.rs b/crates/milli/src/update/test_settings.rs index 2b9ee3a5e..e775c226f 100644 --- a/crates/milli/src/update/test_settings.rs +++ b/crates/milli/src/update/test_settings.rs @@ -897,6 +897,7 @@ fn test_correct_settings_init() { prefix_search, facet_search, disable_on_numbers, + chat, } = settings; assert!(matches!(searchable_fields, Setting::NotSet)); assert!(matches!(displayed_fields, Setting::NotSet)); From 420c6e1932876ef3c59a2e26bc46766f96600a55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Renault?= Date: Tue, 27 May 2025 11:48:12 +0200 Subject: [PATCH 042/103] Report the sources --- crates/meilisearch/src/routes/chat.rs | 123 ++++++++++++++++++++++---- 1 file changed, 107 insertions(+), 16 deletions(-) diff --git a/crates/meilisearch/src/routes/chat.rs b/crates/meilisearch/src/routes/chat.rs index 3db948eb8..5de5a9367 100644 --- a/crates/meilisearch/src/routes/chat.rs +++ b/crates/meilisearch/src/routes/chat.rs @@ -32,9 +32,10 @@ use meilisearch_types::milli::prompt::{Prompt, PromptData}; use meilisearch_types::milli::update::new::document::DocumentFromDb; use meilisearch_types::milli::update::Setting; use meilisearch_types::milli::{ - DocumentId, FieldIdMapWithMetadata, GlobalFieldsIdsMap, MetadataBuilder, TimeBudget, + all_obkv_to_json, obkv_to_json, DocumentId, FieldIdMapWithMetadata, GlobalFieldsIdsMap, + MetadataBuilder, TimeBudget, }; -use meilisearch_types::Index; +use meilisearch_types::{Document, Index}; use serde::{Deserialize, Serialize}; use serde_json::json; use tokio::runtime::Handle; @@ -55,6 +56,8 @@ use crate::search_queue::SearchQueue; const MEILI_SEARCH_PROGRESS_NAME: &str = "_meiliSearchProgress"; const MEILI_APPEND_CONVERSATION_MESSAGE_NAME: &str = "_meiliAppendConversationMessage"; +const MEILI_SEARCH_SOURCES_NAME: &str = "_meiliSearchSources"; +const MEILI_REPORT_ERRORS_NAME: &str = "_meiliReportErrors"; const MEILI_SEARCH_IN_INDEX_FUNCTION_NAME: &str = "_meiliSearchInIndex"; pub fn configure(cfg: &mut web::ServiceConfig) { @@ -93,10 +96,16 @@ async fn chat( pub struct FunctionSupport { /// Defines if we can call the _meiliSearchProgress function /// to inform the front-end about what we are searching for. - progress: bool, + report_progress: bool, + /// Defines if we can call the _meiliSearchSources function + /// to inform the front-end about the sources of the search. + report_sources: bool, /// Defines if we can call the _meiliAppendConversationMessage /// function to provide the messages to append into the conversation. append_to_conversation: bool, + /// Defines if we can call the _meiliReportErrors function + /// to inform the front-end about potential errors. + report_errors: bool, } /// Setup search tool in chat completion request @@ -112,18 +121,28 @@ fn setup_search_tool( } // Remove internal tools used for front-end notifications as they should be hidden from the LLM. - let mut progress = false; + let mut report_progress = false; + let mut report_sources = false; let mut append_to_conversation = false; + let mut report_errors = false; tools.retain(|tool| { match tool.function.name.as_str() { MEILI_SEARCH_PROGRESS_NAME => { - progress = true; + report_progress = true; + false + } + MEILI_SEARCH_SOURCES_NAME => { + report_sources = true; false } MEILI_APPEND_CONVERSATION_MESSAGE_NAME => { append_to_conversation = true; false } + MEILI_REPORT_ERRORS_NAME => { + report_errors = true; + false + } _ => true, // keep other tools } }); @@ -188,7 +207,7 @@ fn setup_search_tool( }), ); - Ok(FunctionSupport { progress, append_to_conversation }) + Ok(FunctionSupport { report_progress, report_sources, append_to_conversation, report_errors }) } /// Process search request and return formatted results @@ -199,7 +218,7 @@ async fn process_search_request( auth_token: &str, index_uid: String, q: Option, -) -> Result<(Index, String), ResponseError> { +) -> Result<(Index, Vec, String), ResponseError> { // TBD // let mut aggregate = SearchAggregator::::from_query(&query); @@ -276,22 +295,33 @@ async fn process_search_request( permit.drop().await; let output = output?; - if let Ok((_, ref search_result)) = output { + let mut documents = Vec::new(); + if let Ok((ref rtxn, ref search_result)) = output { // aggregate.succeed(search_result); if search_result.degraded { MEILISEARCH_DEGRADED_SEARCH_REQUESTS.inc(); } + + let fields_ids_map = index.fields_ids_map(rtxn)?; + let displayed_fields = index.displayed_fields_ids(rtxn)?; + for &document_id in &search_result.documents_ids { + let obkv = index.document(rtxn, document_id)?; + let document = match displayed_fields { + Some(ref fields) => obkv_to_json(fields, &fields_ids_map, obkv)?, + None => all_obkv_to_json(obkv, &fields_ids_map)?, + }; + documents.push(document); + } } // analytics.publish(aggregate, &req); let (rtxn, search_result) = output?; - // let rtxn = index.read_txn()?; let render_alloc = Bump::new(); let formatted = format_documents(&rtxn, &index, &render_alloc, search_result.documents_ids)?; let text = formatted.join("\n"); drop(rtxn); - Ok((index, text)) + Ok((index, documents, text)) } async fn non_streamed_chat( @@ -319,7 +349,7 @@ async fn non_streamed_chat( let auth_token = extract_token_from_request(&req)?.unwrap(); let prompts = chat_settings.prompts.clone().or(Setting::Set(ChatPrompts::default())).unwrap(); - let FunctionSupport { progress, append_to_conversation } = + let FunctionSupport { report_progress, report_sources, append_to_conversation, report_errors } = setup_search_tool(&index_scheduler, filters, &mut chat_completion, &prompts)?; let mut response; @@ -359,7 +389,7 @@ async fn non_streamed_chat( }; let text = match result { - Ok((_, text)) => text, + Ok((_, documents, text)) => text, Err(err) => err, }; @@ -411,7 +441,7 @@ async fn streamed_chat( let auth_token = extract_token_from_request(&req)?.unwrap().to_string(); let prompts = chat_settings.prompts.clone().or(Setting::Set(ChatPrompts::default())).unwrap(); - let FunctionSupport { progress, append_to_conversation } = + let FunctionSupport { report_progress, report_sources, append_to_conversation, report_errors } = setup_search_tool(&index_scheduler, filters, &mut chat_completion, &prompts)?; let (tx, rx) = tokio::sync::mpsc::channel(10); @@ -507,8 +537,9 @@ async fn streamed_chat( ); for call in meili_calls { - if progress { + if report_progress { let call = MeiliSearchProgress { + call_id: call.id.to_string(), function_name: call.function.name.clone(), function_arguments: call .function @@ -573,7 +604,24 @@ async fn streamed_chat( }; let text = match result { - Ok((_, text)) => text, + Ok((_index, documents, text)) => { + if report_sources { + let call = MeiliSearchSources { + call_id: call.id.to_string(), + sources: documents, + }; + let resp = call.create_response(resp.clone()); + // Send the event of "we are doing a search" + if let Err(SendError(_)) = tx + .send(Event::Data(sse::Data::new_json(&resp).unwrap())) + .await + { + return; + } + } + + text + }, Err(err) => err, }; @@ -651,8 +699,10 @@ async fn streamed_chat( } #[derive(Debug, Clone, Serialize)] -/// Give context about what Meilisearch is doing. +/// Provides information about the current Meilisearch search operation. struct MeiliSearchProgress { + /// The call ID to track the sources of the search. + pub call_id: String, /// The name of the function we are executing. pub function_name: String, /// The arguments of the function we are executing, encoded in JSON. @@ -690,6 +740,47 @@ impl MeiliSearchProgress { } } +#[derive(Debug, Clone, Serialize)] +/// Provides sources of the search. +struct MeiliSearchSources { + /// The call ID to track the original search associated to those sources. + pub call_id: String, + /// The documents associated with the search (call_id). + /// Only the displayed attributes of the documents are returned. + pub sources: Vec, +} + +impl MeiliSearchSources { + fn create_response( + &self, + mut resp: CreateChatCompletionStreamResponse, + ) -> CreateChatCompletionStreamResponse { + let call_text = serde_json::to_string(self).unwrap(); + let tool_call = ChatCompletionMessageToolCallChunk { + index: 0, + id: Some(uuid::Uuid::new_v4().to_string()), + r#type: Some(ChatCompletionToolType::Function), + function: Some(FunctionCallStream { + name: Some(MEILI_SEARCH_SOURCES_NAME.to_string()), + arguments: Some(call_text), + }), + }; + resp.choices[0] = ChatChoiceStream { + index: 0, + delta: ChatCompletionStreamResponseDelta { + content: None, + function_call: None, + tool_calls: Some(vec![tool_call]), + role: Some(Role::Assistant), + refusal: None, + }, + finish_reason: None, + logprobs: None, + }; + resp + } +} + struct MeiliAppendConversationMessage(pub ChatCompletionRequestMessage); impl MeiliAppendConversationMessage { From 2da64e835e524a2e94dd7f15311a56f451fc44a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Renault?= Date: Tue, 27 May 2025 18:07:29 +0200 Subject: [PATCH 043/103] Factorize the code a bit more and support reporting errors --- crates/meilisearch/src/routes/chat.rs | 710 ++++++++++++++------------ 1 file changed, 385 insertions(+), 325 deletions(-) diff --git a/crates/meilisearch/src/routes/chat.rs b/crates/meilisearch/src/routes/chat.rs index 5de5a9367..9de15f751 100644 --- a/crates/meilisearch/src/routes/chat.rs +++ b/crates/meilisearch/src/routes/chat.rs @@ -2,13 +2,15 @@ use std::cell::RefCell; use std::collections::HashMap; use std::fmt::Write as _; use std::mem; +use std::ops::ControlFlow; use std::sync::RwLock; use std::time::Duration; use actix_web::web::{self, Data}; use actix_web::{Either, HttpRequest, HttpResponse, Responder}; use actix_web_lab::sse::{self, Event, Sse}; -use async_openai::config::OpenAIConfig; +use async_openai::config::{Config, OpenAIConfig}; +use async_openai::error::OpenAIError; use async_openai::types::{ ChatChoiceStream, ChatCompletionMessageToolCall, ChatCompletionMessageToolCallChunk, ChatCompletionRequestAssistantMessage, ChatCompletionRequestAssistantMessageArgs, @@ -40,6 +42,7 @@ use serde::{Deserialize, Serialize}; use serde_json::json; use tokio::runtime::Handle; use tokio::sync::mpsc::error::SendError; +use tokio::sync::mpsc::Sender; use super::settings::chat::{ChatPrompts, GlobalChatSettings}; use crate::error::MeilisearchHttpError; @@ -83,11 +86,11 @@ async fn chat( if chat_completion.stream.unwrap_or(false) { Either::Right( - streamed_chat(index_scheduler, auth_ctrl, req, search_queue, chat_completion).await, + streamed_chat(index_scheduler, auth_ctrl, search_queue, req, chat_completion).await, ) } else { Either::Left( - non_streamed_chat(index_scheduler, auth_ctrl, req, search_queue, chat_completion).await, + non_streamed_chat(index_scheduler, auth_ctrl, search_queue, req, chat_completion).await, ) } } @@ -327,8 +330,8 @@ async fn process_search_request( async fn non_streamed_chat( index_scheduler: GuardedData, Data>, auth_ctrl: web::Data, - req: HttpRequest, search_queue: web::Data, + req: HttpRequest, mut chat_completion: CreateChatCompletionRequest, ) -> Result { let filters = index_scheduler.filters(); @@ -420,8 +423,8 @@ async fn non_streamed_chat( async fn streamed_chat( index_scheduler: GuardedData, Data>, auth_ctrl: web::Data, - req: HttpRequest, search_queue: web::Data, + req: HttpRequest, mut chat_completion: CreateChatCompletionRequest, ) -> Result { let filters = index_scheduler.filters(); @@ -441,354 +444,285 @@ async fn streamed_chat( let auth_token = extract_token_from_request(&req)?.unwrap().to_string(); let prompts = chat_settings.prompts.clone().or(Setting::Set(ChatPrompts::default())).unwrap(); - let FunctionSupport { report_progress, report_sources, append_to_conversation, report_errors } = + let function_support = setup_search_tool(&index_scheduler, filters, &mut chat_completion, &prompts)?; let (tx, rx) = tokio::sync::mpsc::channel(10); + let tx = SseEventSender(tx); let _join_handle = Handle::current().spawn(async move { let client = Client::with_config(config.clone()); let mut global_tool_calls = HashMap::::new(); - let mut finish_reason = None; // Limit the number of internal calls to satisfy the search requests of the LLM - 'main: for _ in 0..20 { - let mut response = client.chat().create_stream(chat_completion.clone()).await.unwrap(); - while let Some(result) = response.next().await { - match result { - Ok(resp) => { - let choice = &resp.choices[0]; - finish_reason = choice.finish_reason; + for _ in 0..20 { + let output = run_conversation( + &index_scheduler, + &auth_ctrl, + &search_queue, + &auth_token, + &client, + &chat_settings, + &mut chat_completion, + &tx, + &mut global_tool_calls, + function_support, + ); - let ChatCompletionStreamResponseDelta { ref tool_calls, .. } = - &choice.delta; - - match tool_calls { - Some(tool_calls) => { - for chunk in tool_calls { - let ChatCompletionMessageToolCallChunk { - index, - id, - r#type: _, - function, - } = chunk; - let FunctionCallStream { name, arguments } = - function.as_ref().unwrap(); - - global_tool_calls - .entry(*index) - .and_modify(|call| { - if call.is_internal() { - call.append(arguments.as_ref().unwrap()) - } - }) - .or_insert_with(|| { - if name.as_ref().map_or(false, |n| { - n == MEILI_SEARCH_IN_INDEX_FUNCTION_NAME - }) { - Call::Internal { - id: id.as_ref().unwrap().clone(), - function_name: name.as_ref().unwrap().clone(), - arguments: arguments.as_ref().unwrap().clone(), - } - } else { - Call::External { _id: id.as_ref().unwrap().clone() } - } - }); - - if global_tool_calls.get(index).map_or(false, Call::is_external) - { - todo!("Support forwarding external tool calls"); - } - } - } - None => { - if !global_tool_calls.is_empty() { - let (meili_calls, other_calls): (Vec<_>, Vec<_>) = - mem::take(&mut global_tool_calls) - .into_values() - .flat_map(|call| match call { - Call::Internal { - id, - function_name: name, - arguments, - } => Some(ChatCompletionMessageToolCall { - id, - r#type: Some(ChatCompletionToolType::Function), - function: FunctionCall { name, arguments }, - }), - Call::External { _id: _ } => None, - }) - .partition(|call| { - call.function.name - == MEILI_SEARCH_IN_INDEX_FUNCTION_NAME - }); - - chat_completion.messages.push( - ChatCompletionRequestAssistantMessageArgs::default() - .tool_calls(meili_calls.clone()) - .build() - .unwrap() - .into(), - ); - - assert!( - other_calls.is_empty(), - "We do not support external tool forwarding for now" - ); - - for call in meili_calls { - if report_progress { - let call = MeiliSearchProgress { - call_id: call.id.to_string(), - function_name: call.function.name.clone(), - function_arguments: call - .function - .arguments - .clone(), - }; - let resp = call.create_response(resp.clone()); - // Send the event of "we are doing a search" - if let Err(SendError(_)) = tx - .send(Event::Data(sse::Data::new_json(&resp).unwrap())) - .await - { - return; - } - } - - if append_to_conversation { - // Ask the front-end user to append this tool *call* to the conversation - let call = MeiliAppendConversationMessage(ChatCompletionRequestMessage::Assistant( - ChatCompletionRequestAssistantMessage { - content: None, - refusal: None, - name: None, - audio: None, - tool_calls: Some(vec![ - ChatCompletionMessageToolCall { - id: call.id.clone(), - r#type: Some(ChatCompletionToolType::Function), - function: FunctionCall { - name: call.function.name.clone(), - arguments: call.function.arguments.clone(), - }, - }, - ]), - function_call: None, - } - )); - let resp = call.create_response(resp.clone()); - if let Err(SendError(_)) = tx - .send(Event::Data(sse::Data::new_json(&resp).unwrap())) - .await - { - return; - } - } - - let result = - match serde_json::from_str(&call.function.arguments) { - Ok(SearchInIndexParameters { index_uid, q }) => { - process_search_request( - &index_scheduler, - auth_ctrl.clone(), - &search_queue, - &auth_token, - index_uid, - q, - ) - .await - .map_err(|e| e.to_string()) - } - Err(err) => Err(err.to_string()), - }; - - let text = match result { - Ok((_index, documents, text)) => { - if report_sources { - let call = MeiliSearchSources { - call_id: call.id.to_string(), - sources: documents, - }; - let resp = call.create_response(resp.clone()); - // Send the event of "we are doing a search" - if let Err(SendError(_)) = tx - .send(Event::Data(sse::Data::new_json(&resp).unwrap())) - .await - { - return; - } - } - - text - }, - Err(err) => err, - }; - - let tool = ChatCompletionRequestMessage::Tool(ChatCompletionRequestToolMessage { - tool_call_id: call.id.clone(), - content: ChatCompletionRequestToolMessageContent::Text( - format!( - "{}\n\n{text}", - chat_settings - .prompts - .as_ref() - .unwrap() - .pre_query - .as_ref() - .unwrap() - ), - ), - }); - - if append_to_conversation { - // Ask the front-end user to append this tool *output* to the conversation - let tool = MeiliAppendConversationMessage(tool.clone()); - let resp = tool.create_response(resp.clone()); - if let Err(SendError(_)) = tx - .send(Event::Data(sse::Data::new_json(&resp).unwrap())) - .await - { - return; - } - } - - chat_completion.messages.push(tool); - } - } else { - if let Err(SendError(_)) = tx - .send(Event::Data(sse::Data::new_json(&resp).unwrap())) - .await - { - return; - } - } - } - } - } - Err(err) => { - // tracing::error!("{err:?}"); - // if let Err(SendError(_)) = tx - // .send(Event::Data( - // sse::Data::new_json(&json!({ - // "object": "chat.completion.error", - // "tool": err.to_string(), - // })) - // .unwrap(), - // )) - // .await - // { - // return; - // } - - break 'main; - } - } - } - - // We must stop if the finish reason is not something we can solve with Meilisearch - if finish_reason.map_or(true, |fr| fr != FinishReason::ToolCalls) { - break; + match output.await { + Ok(ControlFlow::Continue(())) => (), + Ok(ControlFlow::Break(_finish_reason)) => break, + // If the connection is closed we must stop + Err(SendError(_)) => return, } } - let _ = tx.send(Event::Data(sse::Data::new("[DONE]"))); + let _ = tx.stop().await; }); Ok(Sse::from_infallible_receiver(rx).with_retry_duration(Duration::from_secs(10))) } -#[derive(Debug, Clone, Serialize)] -/// Provides information about the current Meilisearch search operation. -struct MeiliSearchProgress { - /// The call ID to track the sources of the search. - pub call_id: String, - /// The name of the function we are executing. - pub function_name: String, - /// The arguments of the function we are executing, encoded in JSON. - pub function_arguments: String, -} +/// Updates the chat completion with the new messages, streams the LLM tokens, +/// and report progress and errors. +async fn run_conversation( + index_scheduler: &GuardedData, Data>, + auth_ctrl: &web::Data, + search_queue: &web::Data, + auth_token: &str, + client: &Client, + chat_settings: &GlobalChatSettings, + chat_completion: &mut CreateChatCompletionRequest, + tx: &SseEventSender, + global_tool_calls: &mut HashMap, + function_support: FunctionSupport, +) -> Result, ()>, SendError> { + let mut finish_reason = None; -impl MeiliSearchProgress { - fn create_response( - &self, - mut resp: CreateChatCompletionStreamResponse, - ) -> CreateChatCompletionStreamResponse { - let call_text = serde_json::to_string(self).unwrap(); - let tool_call = ChatCompletionMessageToolCallChunk { - index: 0, - id: Some(uuid::Uuid::new_v4().to_string()), - r#type: Some(ChatCompletionToolType::Function), - function: Some(FunctionCallStream { - name: Some(MEILI_SEARCH_PROGRESS_NAME.to_string()), - arguments: Some(call_text), - }), - }; - resp.choices[0] = ChatChoiceStream { - index: 0, - delta: ChatCompletionStreamResponseDelta { - content: None, - function_call: None, - tool_calls: Some(vec![tool_call]), - role: Some(Role::Assistant), - refusal: None, - }, - finish_reason: None, - logprobs: None, - }; - resp + let mut response = client.chat().create_stream(chat_completion.clone()).await.unwrap(); + while let Some(result) = response.next().await { + match result { + Ok(resp) => { + let choice = &resp.choices[0]; + finish_reason = choice.finish_reason; + + let ChatCompletionStreamResponseDelta { ref tool_calls, .. } = &choice.delta; + + match tool_calls { + Some(tool_calls) => { + for chunk in tool_calls { + let ChatCompletionMessageToolCallChunk { + index, + id, + r#type: _, + function, + } = chunk; + let FunctionCallStream { name, arguments } = function.as_ref().unwrap(); + + global_tool_calls + .entry(*index) + .and_modify(|call| { + if call.is_internal() { + call.append(arguments.as_ref().unwrap()) + } + }) + .or_insert_with(|| { + if name + .as_ref() + .map_or(false, |n| n == MEILI_SEARCH_IN_INDEX_FUNCTION_NAME) + { + Call::Internal { + id: id.as_ref().unwrap().clone(), + function_name: name.as_ref().unwrap().clone(), + arguments: arguments.as_ref().unwrap().clone(), + } + } else { + Call::External + } + }); + + if global_tool_calls.get(index).map_or(false, Call::is_external) { + todo!("Support forwarding external tool calls"); + } + } + } + None => { + if !global_tool_calls.is_empty() { + let (meili_calls, other_calls): (Vec<_>, Vec<_>) = + mem::take(global_tool_calls) + .into_values() + .flat_map(|call| match call { + Call::Internal { id, function_name: name, arguments } => { + Some(ChatCompletionMessageToolCall { + id, + r#type: Some(ChatCompletionToolType::Function), + function: FunctionCall { name, arguments }, + }) + } + Call::External => None, + }) + .partition(|call| { + call.function.name == MEILI_SEARCH_IN_INDEX_FUNCTION_NAME + }); + + chat_completion.messages.push( + ChatCompletionRequestAssistantMessageArgs::default() + .tool_calls(meili_calls.clone()) + .build() + .unwrap() + .into(), + ); + + assert!( + other_calls.is_empty(), + "We do not support external tool forwarding for now" + ); + + handle_meili_tools( + &index_scheduler, + &auth_ctrl, + &search_queue, + &auth_token, + chat_settings, + tx, + meili_calls, + chat_completion, + &resp, + function_support, + ) + .await?; + } else { + tx.forward_response(&resp).await?; + } + } + } + } + Err(err) => { + if function_support.report_errors { + tx.report_error(err).await?; + } + return Ok(ControlFlow::Break(None)); + } + } + } + + // We must stop if the finish reason is not something we can solve with Meilisearch + match finish_reason { + Some(FinishReason::ToolCalls) => Ok(ControlFlow::Continue(())), + otherwise => Ok(ControlFlow::Break(otherwise)), } } -#[derive(Debug, Clone, Serialize)] -/// Provides sources of the search. -struct MeiliSearchSources { - /// The call ID to track the original search associated to those sources. - pub call_id: String, - /// The documents associated with the search (call_id). - /// Only the displayed attributes of the documents are returned. - pub sources: Vec, -} +async fn handle_meili_tools( + index_scheduler: &GuardedData, Data>, + auth_ctrl: &web::Data, + search_queue: &web::Data, + auth_token: &str, + chat_settings: &GlobalChatSettings, + tx: &SseEventSender, + meili_calls: Vec, + chat_completion: &mut CreateChatCompletionRequest, + resp: &CreateChatCompletionStreamResponse, + FunctionSupport { report_progress, report_sources, append_to_conversation, .. }: FunctionSupport, +) -> Result<(), SendError> { + for call in meili_calls { + if report_progress { + tx.report_search_progress( + resp.clone(), + &call.id, + &call.function.name, + &call.function.arguments, + ) + .await?; + } -impl MeiliSearchSources { - fn create_response( - &self, - mut resp: CreateChatCompletionStreamResponse, - ) -> CreateChatCompletionStreamResponse { - let call_text = serde_json::to_string(self).unwrap(); - let tool_call = ChatCompletionMessageToolCallChunk { - index: 0, - id: Some(uuid::Uuid::new_v4().to_string()), - r#type: Some(ChatCompletionToolType::Function), - function: Some(FunctionCallStream { - name: Some(MEILI_SEARCH_SOURCES_NAME.to_string()), - arguments: Some(call_text), - }), + if append_to_conversation { + tx.append_tool_call_conversation_message( + resp.clone(), + call.id.clone(), + call.function.name.clone(), + call.function.arguments.clone(), + ) + .await?; + } + + let result = match serde_json::from_str(&call.function.arguments) { + Ok(SearchInIndexParameters { index_uid, q }) => process_search_request( + &index_scheduler, + auth_ctrl.clone(), + &search_queue, + &auth_token, + index_uid, + q, + ) + .await + .map_err(|e| e.to_string()), + Err(err) => Err(err.to_string()), }; - resp.choices[0] = ChatChoiceStream { - index: 0, - delta: ChatCompletionStreamResponseDelta { - content: None, - function_call: None, - tool_calls: Some(vec![tool_call]), - role: Some(Role::Assistant), - refusal: None, - }, - finish_reason: None, - logprobs: None, + + let text = match result { + Ok((_index, documents, text)) => { + if report_sources { + tx.report_sources(resp.clone(), &call.id, &documents).await?; + } + + text + } + Err(err) => err, }; - resp + + let tool = ChatCompletionRequestMessage::Tool(ChatCompletionRequestToolMessage { + tool_call_id: call.id.clone(), + content: ChatCompletionRequestToolMessageContent::Text(format!( + "{}\n\n{text}", + chat_settings.prompts.as_ref().unwrap().pre_query.as_ref().unwrap() + )), + }); + + if append_to_conversation { + tx.append_conversation_message(resp.clone(), &tool).await?; + } + + chat_completion.messages.push(tool); } + + Ok(()) } -struct MeiliAppendConversationMessage(pub ChatCompletionRequestMessage); +pub struct SseEventSender(Sender); -impl MeiliAppendConversationMessage { - fn create_response( +impl SseEventSender { + /// Ask the front-end user to append this tool *call* to the conversation + pub async fn append_tool_call_conversation_message( + &self, + resp: CreateChatCompletionStreamResponse, + call_id: String, + function_name: String, + function_arguments: String, + ) -> Result<(), SendError> { + let message = + ChatCompletionRequestMessage::Assistant(ChatCompletionRequestAssistantMessage { + content: None, + refusal: None, + name: None, + audio: None, + tool_calls: Some(vec![ChatCompletionMessageToolCall { + id: call_id, + r#type: Some(ChatCompletionToolType::Function), + function: FunctionCall { name: function_name, arguments: function_arguments }, + }]), + function_call: None, + }); + + self.append_conversation_message(resp, &message).await + } + + /// Ask the front-end user to append this tool to the conversation + pub async fn append_conversation_message( &self, mut resp: CreateChatCompletionStreamResponse, - ) -> CreateChatCompletionStreamResponse { - let call_text = serde_json::to_string(&self.0).unwrap(); + message: &ChatCompletionRequestMessage, + ) -> Result<(), SendError> { + let call_text = serde_json::to_string(message).unwrap(); let tool_call = ChatCompletionMessageToolCallChunk { index: 0, id: Some(uuid::Uuid::new_v4().to_string()), @@ -798,6 +732,7 @@ impl MeiliAppendConversationMessage { arguments: Some(call_text), }), }; + resp.choices[0] = ChatChoiceStream { index: 0, delta: ChatCompletionStreamResponseDelta { @@ -810,7 +745,132 @@ impl MeiliAppendConversationMessage { finish_reason: None, logprobs: None, }; - resp + + self.send_json(&resp).await + } + + pub async fn report_search_progress( + &self, + mut resp: CreateChatCompletionStreamResponse, + call_id: &str, + function_name: &str, + function_arguments: &str, + ) -> Result<(), SendError> { + #[derive(Debug, Clone, Serialize)] + /// Provides information about the current Meilisearch search operation. + struct MeiliSearchProgress<'a> { + /// The call ID to track the sources of the search. + call_id: &'a str, + /// The name of the function we are executing. + function_name: &'a str, + /// The arguments of the function we are executing, encoded in JSON. + function_arguments: &'a str, + } + + let progress = MeiliSearchProgress { call_id, function_name, function_arguments }; + let call_text = serde_json::to_string(&progress).unwrap(); + let tool_call = ChatCompletionMessageToolCallChunk { + index: 0, + id: Some(uuid::Uuid::new_v4().to_string()), + r#type: Some(ChatCompletionToolType::Function), + function: Some(FunctionCallStream { + name: Some(MEILI_SEARCH_PROGRESS_NAME.to_string()), + arguments: Some(call_text), + }), + }; + + resp.choices[0] = ChatChoiceStream { + index: 0, + delta: ChatCompletionStreamResponseDelta { + content: None, + function_call: None, + tool_calls: Some(vec![tool_call]), + role: Some(Role::Assistant), + refusal: None, + }, + finish_reason: None, + logprobs: None, + }; + + self.send_json(&resp).await + } + + pub async fn report_sources( + &self, + mut resp: CreateChatCompletionStreamResponse, + call_id: &str, + documents: &[Document], + ) -> Result<(), SendError> { + #[derive(Debug, Clone, Serialize)] + /// Provides sources of the search. + struct MeiliSearchSources<'a> { + /// The call ID to track the original search associated to those sources. + call_id: &'a str, + /// The documents associated with the search (call_id). + /// Only the displayed attributes of the documents are returned. + sources: &'a [Document], + } + + let sources = MeiliSearchSources { call_id, sources: documents }; + let call_text = serde_json::to_string(&sources).unwrap(); + let tool_call = ChatCompletionMessageToolCallChunk { + index: 0, + id: Some(uuid::Uuid::new_v4().to_string()), + r#type: Some(ChatCompletionToolType::Function), + function: Some(FunctionCallStream { + name: Some(MEILI_SEARCH_SOURCES_NAME.to_string()), + arguments: Some(call_text), + }), + }; + + resp.choices[0] = ChatChoiceStream { + index: 0, + delta: ChatCompletionStreamResponseDelta { + content: None, + function_call: None, + tool_calls: Some(vec![tool_call]), + role: Some(Role::Assistant), + refusal: None, + }, + finish_reason: None, + logprobs: None, + }; + + self.send_json(&resp).await + } + + pub async fn report_error(&self, error: OpenAIError) -> Result<(), SendError> { + tracing::error!("OpenAI Error: {}", error); + + let (error_code, message) = match error { + OpenAIError::Reqwest(e) => ("internal_reqwest_error", e.to_string()), + OpenAIError::ApiError(api_error) => ("llm_api_issue", api_error.to_string()), + OpenAIError::JSONDeserialize(error) => ("internal_json_deserialize", error.to_string()), + OpenAIError::FileSaveError(_) | OpenAIError::FileReadError(_) => unreachable!(), + OpenAIError::StreamError(error) => ("llm_api_stream_error", error.to_string()), + OpenAIError::InvalidArgument(error) => ("internal_invalid_argument", error.to_string()), + }; + + self.send_json(&json!({ + "error_code": error_code, + "message": message, + })) + .await + } + + pub async fn forward_response( + &self, + resp: &CreateChatCompletionStreamResponse, + ) -> Result<(), SendError> { + self.send_json(resp).await + } + + pub async fn stop(self) -> Result<(), SendError> { + self.0.send(Event::Data(sse::Data::new("[DONE]"))).await + } + + async fn send_json(&self, data: &S) -> Result<(), SendError> { + self.0.send(Event::Data(sse::Data::new_json(data).unwrap())).await } } @@ -822,7 +882,7 @@ enum Call { Internal { id: String, function_name: String, arguments: String }, /// Tool calls that we track but only to know that its not our functions. /// We return the function calls as-is to the end-user. - External { _id: String }, + External, } impl Call { From 2821163b9557a01113cb0b0c1565d9e90ac33e37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Renault?= Date: Wed, 28 May 2025 14:22:53 +0200 Subject: [PATCH 044/103] Clean up the code a bit --- crates/meilisearch/src/routes/chat.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/meilisearch/src/routes/chat.rs b/crates/meilisearch/src/routes/chat.rs index 9de15f751..d2fe4ee3a 100644 --- a/crates/meilisearch/src/routes/chat.rs +++ b/crates/meilisearch/src/routes/chat.rs @@ -247,8 +247,8 @@ async fn process_search_request( embedder, }), limit: limit.unwrap_or_else(DEFAULT_SEARCH_LIMIT), - sort: sort, - distinct: distinct, + sort, + distinct, matching_strategy: matching_strategy .map(|ms| match ms { index::MatchingStrategy::Last => MatchingStrategy::Last, @@ -256,7 +256,7 @@ async fn process_search_request( index::MatchingStrategy::Frequency => MatchingStrategy::Frequency, }) .unwrap_or(MatchingStrategy::Frequency), - attributes_to_search_on: attributes_to_search_on, + attributes_to_search_on, ranking_score_threshold: ranking_score_threshold .and_then(|rst| RankingScoreThreshold::try_from(rst).ok()), ..Default::default() From 50fafbbc8ba3caaee150957b3bdcaddf2d2cb22e Mon Sep 17 00:00:00 2001 From: Kerollmops Date: Fri, 30 May 2025 10:54:32 +0200 Subject: [PATCH 045/103] Implement useful conversion strategies and clean up the code --- crates/meilisearch/src/routes/chat.rs | 43 +----- crates/meilisearch/src/search/mod.rs | 86 +++++++++++- crates/milli/src/index.rs | 49 ++++++- crates/milli/src/prompt/mod.rs | 2 +- crates/milli/src/search/mod.rs | 11 ++ crates/milli/src/update/chat.rs | 72 +--------- crates/milli/src/update/settings.rs | 163 ++++++++++------------- crates/milli/src/update/test_settings.rs | 1 + 8 files changed, 224 insertions(+), 203 deletions(-) diff --git a/crates/meilisearch/src/routes/chat.rs b/crates/meilisearch/src/routes/chat.rs index d2fe4ee3a..1cc5f1012 100644 --- a/crates/meilisearch/src/routes/chat.rs +++ b/crates/meilisearch/src/routes/chat.rs @@ -29,7 +29,7 @@ use meilisearch_auth::AuthController; use meilisearch_types::error::ResponseError; use meilisearch_types::heed::RoTxn; use meilisearch_types::keys::actions; -use meilisearch_types::milli::index::{self, ChatConfig, SearchParameters}; +use meilisearch_types::milli::index::ChatConfig; use meilisearch_types::milli::prompt::{Prompt, PromptData}; use meilisearch_types::milli::update::new::document::DocumentFromDb; use meilisearch_types::milli::update::Setting; @@ -50,11 +50,7 @@ use crate::extractors::authentication::policies::ActionPolicy; use crate::extractors::authentication::{extract_token_from_request, GuardedData, Policy as _}; use crate::metrics::MEILISEARCH_DEGRADED_SEARCH_REQUESTS; use crate::routes::indexes::search::search_kind; -use crate::search::{ - add_search_rules, prepare_search, search_from_kind, HybridQuery, MatchingStrategy, - RankingScoreThreshold, SearchQuery, SemanticRatio, DEFAULT_SEARCH_LIMIT, - DEFAULT_SEMANTIC_RATIO, -}; +use crate::search::{add_search_rules, prepare_search, search_from_kind, SearchQuery}; use crate::search_queue::SearchQueue; const MEILI_SEARCH_PROGRESS_NAME: &str = "_meiliSearchProgress"; @@ -228,40 +224,7 @@ async fn process_search_request( let index = index_scheduler.index(&index_uid)?; let rtxn = index.static_read_txn()?; let ChatConfig { description: _, prompt: _, search_parameters } = index.chat_config(&rtxn)?; - let SearchParameters { - hybrid, - limit, - sort, - distinct, - matching_strategy, - attributes_to_search_on, - ranking_score_threshold, - } = search_parameters; - - let mut query = SearchQuery { - q, - hybrid: hybrid.map(|index::HybridQuery { semantic_ratio, embedder }| HybridQuery { - semantic_ratio: SemanticRatio::try_from(semantic_ratio) - .ok() - .unwrap_or_else(DEFAULT_SEMANTIC_RATIO), - embedder, - }), - limit: limit.unwrap_or_else(DEFAULT_SEARCH_LIMIT), - sort, - distinct, - matching_strategy: matching_strategy - .map(|ms| match ms { - index::MatchingStrategy::Last => MatchingStrategy::Last, - index::MatchingStrategy::All => MatchingStrategy::All, - index::MatchingStrategy::Frequency => MatchingStrategy::Frequency, - }) - .unwrap_or(MatchingStrategy::Frequency), - attributes_to_search_on, - ranking_score_threshold: ranking_score_threshold - .and_then(|rst| RankingScoreThreshold::try_from(rst).ok()), - ..Default::default() - }; - + let mut query = SearchQuery { q, ..SearchQuery::from(search_parameters) }; let auth_filter = ActionPolicy::<{ actions::SEARCH }>::authenticate( auth_ctrl, auth_token, diff --git a/crates/meilisearch/src/search/mod.rs b/crates/meilisearch/src/search/mod.rs index 848591a4f..037083b2d 100644 --- a/crates/meilisearch/src/search/mod.rs +++ b/crates/meilisearch/src/search/mod.rs @@ -16,6 +16,7 @@ use meilisearch_types::error::{Code, ResponseError}; use meilisearch_types::heed::RoTxn; use meilisearch_types::index_uid::IndexUid; use meilisearch_types::locales::Locale; +use meilisearch_types::milli::index::{self, SearchParameters}; use meilisearch_types::milli::score_details::{ScoreDetails, ScoringStrategy}; use meilisearch_types::milli::vector::parsed_vectors::ExplicitVectors; use meilisearch_types::milli::vector::Embedder; @@ -56,7 +57,7 @@ pub const DEFAULT_HIGHLIGHT_PRE_TAG: fn() -> String = || "".to_string(); pub const DEFAULT_HIGHLIGHT_POST_TAG: fn() -> String = || "".to_string(); pub const DEFAULT_SEMANTIC_RATIO: fn() -> SemanticRatio = || SemanticRatio(0.5); -#[derive(Clone, Default, PartialEq, Deserr, ToSchema)] +#[derive(Clone, PartialEq, Deserr, ToSchema)] #[deserr(error = DeserrJsonError, rename_all = camelCase, deny_unknown_fields)] pub struct SearchQuery { #[deserr(default, error = DeserrJsonError)] @@ -119,6 +120,69 @@ pub struct SearchQuery { pub locales: Option>, } +impl From for SearchQuery { + fn from(parameters: SearchParameters) -> Self { + let SearchParameters { + hybrid, + limit, + sort, + distinct, + matching_strategy, + attributes_to_search_on, + ranking_score_threshold, + } = parameters; + + SearchQuery { + hybrid: hybrid.map(|index::HybridQuery { semantic_ratio, embedder }| HybridQuery { + semantic_ratio: SemanticRatio::try_from(semantic_ratio) + .ok() + .unwrap_or_else(DEFAULT_SEMANTIC_RATIO), + embedder, + }), + limit: limit.unwrap_or_else(DEFAULT_SEARCH_LIMIT), + sort, + distinct, + matching_strategy: matching_strategy.map(MatchingStrategy::from).unwrap_or_default(), + attributes_to_search_on, + ranking_score_threshold: ranking_score_threshold.map(RankingScoreThreshold::from), + ..Default::default() + } + } +} + +impl Default for SearchQuery { + fn default() -> Self { + SearchQuery { + q: None, + vector: None, + hybrid: None, + offset: DEFAULT_SEARCH_OFFSET(), + limit: DEFAULT_SEARCH_LIMIT(), + page: None, + hits_per_page: None, + attributes_to_retrieve: None, + retrieve_vectors: false, + attributes_to_crop: None, + crop_length: DEFAULT_CROP_LENGTH(), + attributes_to_highlight: None, + show_matches_position: false, + show_ranking_score: false, + show_ranking_score_details: false, + filter: None, + sort: None, + distinct: None, + facets: None, + highlight_pre_tag: DEFAULT_HIGHLIGHT_PRE_TAG(), + highlight_post_tag: DEFAULT_HIGHLIGHT_POST_TAG(), + crop_marker: DEFAULT_CROP_MARKER(), + matching_strategy: Default::default(), + attributes_to_search_on: None, + ranking_score_threshold: None, + locales: None, + } + } +} + #[derive(Debug, Clone, Copy, PartialEq, Deserr, ToSchema, Serialize)] #[deserr(try_from(f64) = TryFrom::try_from -> InvalidSearchRankingScoreThreshold)] pub struct RankingScoreThreshold(f64); @@ -137,6 +201,14 @@ impl std::convert::TryFrom for RankingScoreThreshold { } } +impl From for RankingScoreThreshold { + fn from(threshold: index::RankingScoreThreshold) -> Self { + let threshold = threshold.as_f64(); + assert!(threshold >= 0.0 && threshold <= 1.0); + RankingScoreThreshold(threshold) + } +} + #[derive(Debug, Clone, Copy, PartialEq, Deserr)] #[deserr(try_from(f64) = TryFrom::try_from -> InvalidSimilarRankingScoreThreshold)] pub struct RankingScoreThresholdSimilar(f64); @@ -718,6 +790,16 @@ impl From for TermsMatchingStrategy { } } +impl From for MatchingStrategy { + fn from(other: index::MatchingStrategy) -> Self { + match other { + index::MatchingStrategy::Last => Self::Last, + index::MatchingStrategy::All => Self::All, + index::MatchingStrategy::Frequency => Self::Frequency, + } + } +} + #[derive(Debug, Default, Clone, PartialEq, Eq, Deserr)] #[deserr(rename_all = camelCase)] pub enum FacetValuesSort { @@ -1261,7 +1343,7 @@ struct HitMaker<'a> { vectors_fid: Option, retrieve_vectors: RetrieveVectors, to_retrieve_ids: BTreeSet, - embedding_configs: Vec, + embedding_configs: Vec, formatter_builder: MatcherBuilder<'a>, formatted_options: BTreeMap, show_ranking_score: bool, diff --git a/crates/milli/src/index.rs b/crates/milli/src/index.rs index e6f28d02e..b2df46af3 100644 --- a/crates/milli/src/index.rs +++ b/crates/milli/src/index.rs @@ -1,14 +1,18 @@ use std::borrow::Cow; use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet}; +use std::error::Error; +use std::fmt; use std::fs::File; use std::path::Path; +use deserr::Deserr; use heed::types::*; use heed::{CompactionOption, Database, DatabaseStat, RoTxn, RwTxn, Unspecified, WithoutTls}; use indexmap::IndexMap; use roaring::RoaringBitmap; use rstar::RTree; use serde::{Deserialize, Serialize}; +use utoipa::ToSchema; use crate::constants::{self, RESERVED_GEO_FIELD_NAME, RESERVED_VECTORS_FIELD_NAME}; use crate::database_stats::DatabaseStats; @@ -25,6 +29,7 @@ use crate::heed_codec::{BEU16StrCodec, FstSetCodec, StrBEU16Codec, StrRefCodec}; use crate::order_by_map::OrderByMap; use crate::prompt::PromptData; use crate::proximity::ProximityPrecision; +use crate::update::new::StdResult; use crate::vector::{ArroyStats, ArroyWrapper, Embedding, EmbeddingConfig}; use crate::{ default_criteria, CboRoaringBitmapCodec, Criterion, DocumentId, ExternalDocumentsIds, @@ -1962,10 +1967,46 @@ pub struct SearchParameters { #[serde(skip_serializing_if = "Option::is_none")] pub attributes_to_search_on: Option>, #[serde(skip_serializing_if = "Option::is_none")] - pub ranking_score_threshold: Option, + pub ranking_score_threshold: Option, } -#[derive(Debug, Default, Deserialize, Serialize)] +#[derive(Debug, Clone, Copy, Default, Deserialize, Serialize, PartialEq, Deserr, ToSchema)] +#[deserr(try_from(f64) = TryFrom::try_from -> InvalidSearchRankingScoreThreshold)] +pub struct RankingScoreThreshold(f64); + +impl RankingScoreThreshold { + pub fn as_f64(&self) -> f64 { + self.0 + } +} + +impl TryFrom for RankingScoreThreshold { + type Error = InvalidSearchRankingScoreThreshold; + + fn try_from(value: f64) -> StdResult { + if value < 0.0 || value > 1.0 { + Err(InvalidSearchRankingScoreThreshold) + } else { + Ok(RankingScoreThreshold(value)) + } + } +} + +#[derive(Debug)] +pub struct InvalidSearchRankingScoreThreshold; + +impl Error for InvalidSearchRankingScoreThreshold {} + +impl fmt::Display for InvalidSearchRankingScoreThreshold { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "the value of `rankingScoreThreshold` is invalid, expected a float between `0.0` and `1.0`." + ) + } +} + +#[derive(Debug, Clone, Default, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct HybridQuery { pub semantic_ratio: f32, @@ -1980,10 +2021,12 @@ pub struct PrefixSettings { pub compute_prefixes: PrefixSearch, } -#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Deserr, ToSchema, Serialize, Deserialize)] +#[deserr(rename_all = camelCase)] #[serde(rename_all = "camelCase")] pub enum MatchingStrategy { /// Remove query words from last to first + #[default] Last, /// All query words are mandatory All, diff --git a/crates/milli/src/prompt/mod.rs b/crates/milli/src/prompt/mod.rs index d40fcd27f..a8288f83d 100644 --- a/crates/milli/src/prompt/mod.rs +++ b/crates/milli/src/prompt/mod.rs @@ -64,7 +64,7 @@ fn default_template() -> liquid::Template { new_template(default_template_text()).unwrap() } -fn default_template_text() -> &'static str { +pub fn default_template_text() -> &'static str { "{% for field in fields %}\ {% if field.is_searchable and field.value != nil %}\ {{ field.name }}: {{ field.value }}\n\ diff --git a/crates/milli/src/search/mod.rs b/crates/milli/src/search/mod.rs index 37b1aaf09..62183afc3 100644 --- a/crates/milli/src/search/mod.rs +++ b/crates/milli/src/search/mod.rs @@ -10,6 +10,7 @@ pub use self::facet::{FacetDistribution, Filter, OrderBy, DEFAULT_VALUES_PER_FAC pub use self::new::matches::{FormatOptions, MatchBounds, MatcherBuilder, MatchingWords}; use self::new::{execute_vector_search, PartialSearchResult, VectorStoreStats}; use crate::filterable_attributes_rules::{filtered_matching_patterns, matching_features}; +use crate::index::MatchingStrategy; use crate::score_details::{ScoreDetails, ScoringStrategy}; use crate::vector::Embedder; use crate::{ @@ -364,6 +365,16 @@ impl Default for TermsMatchingStrategy { } } +impl From for TermsMatchingStrategy { + fn from(other: MatchingStrategy) -> Self { + match other { + MatchingStrategy::Last => Self::Last, + MatchingStrategy::All => Self::All, + MatchingStrategy::Frequency => Self::Frequency, + } + } +} + fn get_first(s: &str) -> &str { match s.chars().next() { Some(c) => &s[..c.len_utf8()], diff --git a/crates/milli/src/update/chat.rs b/crates/milli/src/update/chat.rs index b8fbc582d..ae95ddfd9 100644 --- a/crates/milli/src/update/chat.rs +++ b/crates/milli/src/update/chat.rs @@ -6,10 +6,9 @@ use deserr::Deserr; use serde::{Deserialize, Serialize}; use utoipa::ToSchema; -use crate::index::{self, ChatConfig, SearchParameters}; +use crate::index::{self, ChatConfig, MatchingStrategy, RankingScoreThreshold, SearchParameters}; use crate::prompt::{default_max_bytes, PromptData}; use crate::update::Setting; -use crate::TermsMatchingStrategy; #[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Deserr, ToSchema)] #[serde(deny_unknown_fields, rename_all = "camelCase")] @@ -70,13 +69,10 @@ impl From for ChatSettings { HybridQuery { semantic_ratio: SemanticRatio(semantic_ratio), embedder } }); - let matching_strategy = matching_strategy.map(|ms| match ms { - index::MatchingStrategy::Last => MatchingStrategy::Last, - index::MatchingStrategy::All => MatchingStrategy::All, - index::MatchingStrategy::Frequency => MatchingStrategy::Frequency, - }); + let matching_strategy = matching_strategy.map(MatchingStrategy::from); - let ranking_score_threshold = ranking_score_threshold.map(RankingScoreThreshold); + let ranking_score_threshold = + ranking_score_threshold.map(RankingScoreThreshold::from); ChatSearchParams { hybrid: Setting::some_or_not_set(hybrid), @@ -197,63 +193,3 @@ impl std::ops::Deref for SemanticRatio { &self.0 } } - -#[derive(Debug, Copy, Clone, PartialEq, Eq, Deserr, ToSchema, Serialize, Deserialize)] -#[deserr(rename_all = camelCase)] -#[serde(rename_all = "camelCase")] -pub enum MatchingStrategy { - /// Remove query words from last to first - Last, - /// All query words are mandatory - All, - /// Remove query words from the most frequent to the least - Frequency, -} - -impl Default for MatchingStrategy { - fn default() -> Self { - Self::Last - } -} - -impl From for TermsMatchingStrategy { - fn from(other: MatchingStrategy) -> Self { - match other { - MatchingStrategy::Last => Self::Last, - MatchingStrategy::All => Self::All, - MatchingStrategy::Frequency => Self::Frequency, - } - } -} - -#[derive(Debug, Clone, Copy, PartialEq, Deserr, ToSchema, Serialize, Deserialize)] -#[deserr(try_from(f64) = TryFrom::try_from -> InvalidSearchRankingScoreThreshold)] -pub struct RankingScoreThreshold(pub f64); - -impl std::convert::TryFrom for RankingScoreThreshold { - type Error = InvalidSearchRankingScoreThreshold; - - fn try_from(f: f64) -> Result { - // the suggested "fix" is: `!(0.0..=1.0).contains(&f)`` which is allegedly less readable - #[allow(clippy::manual_range_contains)] - if f > 1.0 || f < 0.0 { - Err(InvalidSearchRankingScoreThreshold) - } else { - Ok(RankingScoreThreshold(f)) - } - } -} - -#[derive(Debug)] -pub struct InvalidSearchRankingScoreThreshold; - -impl Error for InvalidSearchRankingScoreThreshold {} - -impl fmt::Display for InvalidSearchRankingScoreThreshold { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - f, - "the value of `rankingScoreThreshold` is invalid, expected a float between `0.0` and `1.0`." - ) - } -} diff --git a/crates/milli/src/update/settings.rs b/crates/milli/src/update/settings.rs index bb589c5ee..9f152710a 100644 --- a/crates/milli/src/update/settings.rs +++ b/crates/milli/src/update/settings.rs @@ -11,7 +11,7 @@ use roaring::RoaringBitmap; use serde::{Deserialize, Deserializer, Serialize, Serializer}; use time::OffsetDateTime; -use super::chat::{ChatSearchParams, RankingScoreThreshold}; +use super::chat::ChatSearchParams; use super::del_add::{DelAdd, DelAddOperation}; use super::index_documents::{IndexDocumentsConfig, Transform}; use super::{ChatSettings, IndexerConfig}; @@ -23,11 +23,11 @@ use crate::error::UserError; use crate::fields_ids_map::metadata::{FieldIdMapWithMetadata, MetadataBuilder}; use crate::filterable_attributes_rules::match_faceted_field; use crate::index::{ - ChatConfig, IndexEmbeddingConfig, MatchingStrategy, PrefixSearch, - DEFAULT_MIN_WORD_LEN_ONE_TYPO, DEFAULT_MIN_WORD_LEN_TWO_TYPOS, + ChatConfig, IndexEmbeddingConfig, MatchingStrategy, PrefixSearch, RankingScoreThreshold, + SearchParameters, DEFAULT_MIN_WORD_LEN_ONE_TYPO, DEFAULT_MIN_WORD_LEN_TWO_TYPOS, }; use crate::order_by_map::OrderByMap; -use crate::prompt::{default_max_bytes, PromptData}; +use crate::prompt::{default_max_bytes, default_template_text, PromptData}; use crate::proximity::ProximityPrecision; use crate::update::index_documents::IndexDocumentsMethod; use crate::update::{IndexDocuments, UpdateIndexingStep}; @@ -1266,32 +1266,29 @@ impl<'a, 't, 'i> Settings<'a, 't, 'i> { document_template_max_bytes: new_document_template_max_bytes, search_parameters: new_search_parameters, }) => { - let mut old = self.index.chat_config(self.wtxn)?; - let ChatConfig { - ref mut description, - prompt: PromptData { ref mut template, ref mut max_bytes }, - ref mut search_parameters, - } = old; + let ChatConfig { description, prompt, search_parameters } = + self.index.chat_config(self.wtxn)?; - match new_description { - Setting::Set(d) => *description = d.clone(), - Setting::Reset => *description = Default::default(), - Setting::NotSet => (), - } + let description = match new_description { + Setting::Set(new) => new.clone(), + Setting::Reset => Default::default(), + Setting::NotSet => description, + }; - match new_document_template { - Setting::Set(dt) => *template = dt.clone(), - Setting::Reset => *template = Default::default(), - Setting::NotSet => (), - } + let prompt = PromptData { + template: match new_document_template { + Setting::Set(new) => new.clone(), + Setting::Reset => default_template_text().to_string(), + Setting::NotSet => prompt.template.clone(), + }, + max_bytes: match new_document_template_max_bytes { + Setting::Set(m) => NonZeroUsize::new(*m), + Setting::Reset => Some(default_max_bytes()), + Setting::NotSet => prompt.max_bytes, + }, + }; - match new_document_template_max_bytes { - Setting::Set(m) => *max_bytes = NonZeroUsize::new(*m), - Setting::Reset => *max_bytes = Some(default_max_bytes()), - Setting::NotSet => (), - } - - match new_search_parameters { + let search_parameters = match new_search_parameters { Setting::Set(sp) => { let ChatSearchParams { hybrid, @@ -1303,74 +1300,62 @@ impl<'a, 't, 'i> Settings<'a, 't, 'i> { ranking_score_threshold, } = sp; - match hybrid { - Setting::Set(hybrid) => { - search_parameters.hybrid = Some(crate::index::HybridQuery { + SearchParameters { + hybrid: match hybrid { + Setting::Set(hybrid) => Some(crate::index::HybridQuery { semantic_ratio: *hybrid.semantic_ratio, embedder: hybrid.embedder.clone(), - }) - } - Setting::Reset => search_parameters.hybrid = None, - Setting::NotSet => (), - } - - match limit { - Setting::Set(limit) => search_parameters.limit = Some(*limit), - Setting::Reset => search_parameters.limit = None, - Setting::NotSet => (), - } - - match sort { - Setting::Set(sort) => search_parameters.sort = Some(sort.clone()), - Setting::Reset => search_parameters.sort = None, - Setting::NotSet => (), - } - - match distinct { - Setting::Set(distinct) => { - search_parameters.distinct = Some(distinct.clone()) - } - Setting::Reset => search_parameters.distinct = None, - Setting::NotSet => (), - } - - match matching_strategy { - Setting::Set(matching_strategy) => { - let strategy = match matching_strategy { - super::chat::MatchingStrategy::Last => MatchingStrategy::Last, - super::chat::MatchingStrategy::All => MatchingStrategy::All, - super::chat::MatchingStrategy::Frequency => { - MatchingStrategy::Frequency - } - }; - search_parameters.matching_strategy = Some(strategy) - } - Setting::Reset => search_parameters.matching_strategy = None, - Setting::NotSet => (), - } - - match attributes_to_search_on { - Setting::Set(attributes_to_search_on) => { - search_parameters.attributes_to_search_on = + }), + Setting::Reset => None, + Setting::NotSet => search_parameters.hybrid.clone(), + }, + limit: match limit { + Setting::Set(limit) => Some(*limit), + Setting::Reset => None, + Setting::NotSet => search_parameters.limit, + }, + sort: match sort { + Setting::Set(sort) => Some(sort.clone()), + Setting::Reset => None, + Setting::NotSet => search_parameters.sort.clone(), + }, + distinct: match distinct { + Setting::Set(distinct) => Some(distinct.clone()), + Setting::Reset => None, + Setting::NotSet => search_parameters.distinct.clone(), + }, + matching_strategy: match matching_strategy { + Setting::Set(matching_strategy) => { + Some(MatchingStrategy::from(*matching_strategy)) + } + Setting::Reset => None, + Setting::NotSet => search_parameters.matching_strategy, + }, + attributes_to_search_on: match attributes_to_search_on { + Setting::Set(attributes_to_search_on) => { Some(attributes_to_search_on.clone()) - } - Setting::Reset => search_parameters.attributes_to_search_on = None, - Setting::NotSet => (), - } - - match ranking_score_threshold { - Setting::Set(RankingScoreThreshold(score)) => { - search_parameters.ranking_score_threshold = Some(*score) - } - Setting::Reset => search_parameters.ranking_score_threshold = None, - Setting::NotSet => (), + } + Setting::Reset => None, + Setting::NotSet => { + search_parameters.attributes_to_search_on.clone() + } + }, + ranking_score_threshold: match ranking_score_threshold { + Setting::Set(rst) => Some(RankingScoreThreshold::from(*rst)), + Setting::Reset => None, + Setting::NotSet => search_parameters.ranking_score_threshold, + }, } } - Setting::Reset => *search_parameters = Default::default(), - Setting::NotSet => (), - } + Setting::Reset => Default::default(), + Setting::NotSet => search_parameters, + }; + + self.index.put_chat_config( + self.wtxn, + &ChatConfig { description, prompt, search_parameters }, + )?; - self.index.put_chat_config(self.wtxn, &old)?; Ok(true) } Setting::Reset => self.index.delete_chat_config(self.wtxn), diff --git a/crates/milli/src/update/test_settings.rs b/crates/milli/src/update/test_settings.rs index e775c226f..e78765cfb 100644 --- a/crates/milli/src/update/test_settings.rs +++ b/crates/milli/src/update/test_settings.rs @@ -926,6 +926,7 @@ fn test_correct_settings_init() { assert!(matches!(prefix_search, Setting::NotSet)); assert!(matches!(facet_search, Setting::NotSet)); assert!(matches!(disable_on_numbers, Setting::NotSet)); + assert!(matches!(chat, Setting::NotSet)); }) .unwrap(); } From 0f7f5fa10495a7463818b9fb46139871b5e95bf2 Mon Sep 17 00:00:00 2001 From: Kerollmops Date: Fri, 30 May 2025 12:12:47 +0200 Subject: [PATCH 046/103] Introduce listing/getting/deleting/updating chat workspace settings --- crates/index-scheduler/src/lib.rs | 58 +++++++++---- crates/index-scheduler/src/scheduler/test.rs | 4 +- crates/meilisearch-types/src/keys.rs | 43 +++++---- .../{chat.rs => chats/chat_completions.rs} | 41 +++++++-- crates/meilisearch/src/routes/chats/mod.rs | 87 +++++++++++++++++++ .../{settings/chat.rs => chats/settings.rs} | 50 +++++++++-- crates/meilisearch/src/routes/indexes/mod.rs | 2 +- crates/meilisearch/src/routes/mod.rs | 6 +- crates/meilisearch/src/routes/settings/mod.rs | 1 - 9 files changed, 241 insertions(+), 51 deletions(-) rename crates/meilisearch/src/routes/{chat.rs => chats/chat_completions.rs} (96%) create mode 100644 crates/meilisearch/src/routes/chats/mod.rs rename crates/meilisearch/src/routes/{settings/chat.rs => chats/settings.rs} (80%) delete mode 100644 crates/meilisearch/src/routes/settings/mod.rs diff --git a/crates/index-scheduler/src/lib.rs b/crates/index-scheduler/src/lib.rs index 9fb92f041..261fe030c 100644 --- a/crates/index-scheduler/src/lib.rs +++ b/crates/index-scheduler/src/lib.rs @@ -54,7 +54,7 @@ use meilisearch_types::batches::Batch; use meilisearch_types::features::{InstanceTogglableFeatures, Network, RuntimeTogglableFeatures}; use meilisearch_types::heed::byteorder::BE; use meilisearch_types::heed::types::{SerdeJson, Str, I128}; -use meilisearch_types::heed::{self, Database, Env, RoTxn, WithoutTls}; +use meilisearch_types::heed::{self, Database, Env, RoTxn, RwTxn, WithoutTls}; use meilisearch_types::milli::index::IndexEmbeddingConfig; use meilisearch_types::milli::update::IndexerConfig; use meilisearch_types::milli::vector::{Embedder, EmbedderOptions, EmbeddingConfigs}; @@ -154,7 +154,7 @@ pub struct IndexScheduler { features: features::FeatureData, /// Stores the custom chat prompts and other settings of the indexes. - chat_settings: Database>, + pub(crate) chat_settings: Database>, /// Everything related to the processing of the tasks pub scheduler: scheduler::Scheduler, @@ -308,6 +308,14 @@ impl IndexScheduler { Ok(this) } + pub fn write_txn(&self) -> Result { + self.env.write_txn().map_err(|e| e.into()) + } + + pub fn read_txn(&self) -> Result> { + self.env.read_txn().map_err(|e| e.into()) + } + /// Return `Ok(())` if the index scheduler is able to access one of its database. pub fn health(&self) -> Result<()> { let rtxn = self.env.read_txn()?; @@ -384,10 +392,6 @@ impl IndexScheduler { } } - pub fn read_txn(&self) -> Result> { - self.env.read_txn().map_err(|e| e.into()) - } - /// Start the run loop for the given index scheduler. /// /// This function will execute in a different thread and must be called @@ -505,7 +509,7 @@ impl IndexScheduler { /// Returns the total number of indexes available for the specified filter. /// And a `Vec` of the index_uid + its stats - pub fn get_paginated_indexes_stats( + pub fn paginated_indexes_stats( &self, filters: &meilisearch_auth::AuthFilter, from: usize, @@ -546,6 +550,25 @@ impl IndexScheduler { ret.map(|ret| (total, ret)) } + /// Returns the total number of chat workspaces available ~~for the specified filter~~. + /// And a `Vec` of the workspace_uids + pub fn paginated_chat_workspace_uids( + &self, + _filters: &meilisearch_auth::AuthFilter, + from: usize, + limit: usize, + ) -> Result<(usize, Vec)> { + let rtxn = self.read_txn()?; + let total = self.chat_settings.len(&rtxn)?; + let mut iter = self.chat_settings.iter(&rtxn)?.skip(from); + iter.by_ref() + .take(limit) + .map(|ret| ret.map_err(Error::from)) + .map(|ret| ret.map(|(uid, _)| uid.to_string())) + .collect::, Error>>() + .map(|ret| (total as usize, ret)) + } + /// The returned structure contains: /// 1. The name of the property being observed can be `statuses`, `types`, or `indexes`. /// 2. The name of the specific data related to the property can be `enqueued` for the `statuses`, `settingsUpdate` for the `types`, or the name of the index for the `indexes`, for example. @@ -875,16 +898,21 @@ impl IndexScheduler { res.map(EmbeddingConfigs::new) } - pub fn chat_settings(&self) -> Result> { - let rtxn = self.env.read_txn().map_err(Error::HeedTransaction)?; - self.chat_settings.get(&rtxn, "main").map_err(Into::into) + pub fn chat_settings(&self, rtxn: &RoTxn, uid: &str) -> Result> { + self.chat_settings.get(rtxn, uid).map_err(Into::into) } - pub fn put_chat_settings(&self, settings: &serde_json::Value) -> Result<()> { - let mut wtxn = self.env.write_txn().map_err(Error::HeedTransaction)?; - self.chat_settings.put(&mut wtxn, "main", settings)?; - wtxn.commit().map_err(Error::HeedTransaction)?; - Ok(()) + pub fn put_chat_settings( + &self, + wtxn: &mut RwTxn, + uid: &str, + settings: &serde_json::Value, + ) -> Result<()> { + self.chat_settings.put(wtxn, uid, settings).map_err(Into::into) + } + + pub fn delete_chat_settings(&self, wtxn: &mut RwTxn, uid: &str) -> Result { + self.chat_settings.delete(wtxn, uid).map_err(Into::into) } } diff --git a/crates/index-scheduler/src/scheduler/test.rs b/crates/index-scheduler/src/scheduler/test.rs index f13af9f87..06bc14051 100644 --- a/crates/index-scheduler/src/scheduler/test.rs +++ b/crates/index-scheduler/src/scheduler/test.rs @@ -894,7 +894,7 @@ fn create_and_list_index() { let err = index_scheduler.index("kefir").map(|_| ()).unwrap_err(); snapshot!(err, @"Index `kefir` not found."); - let empty = index_scheduler.get_paginated_indexes_stats(&AuthFilter::default(), 0, 20).unwrap(); + let empty = index_scheduler.paginated_indexes_stats(&AuthFilter::default(), 0, 20).unwrap(); snapshot!(format!("{empty:?}"), @"(0, [])"); // After advancing just once the index should've been created, the wtxn has been released and commited @@ -902,7 +902,7 @@ fn create_and_list_index() { handle.advance_till([InsideProcessBatch]); index_scheduler.index("kefir").unwrap(); - let list = index_scheduler.get_paginated_indexes_stats(&AuthFilter::default(), 0, 20).unwrap(); + let list = index_scheduler.paginated_indexes_stats(&AuthFilter::default(), 0, 20).unwrap(); snapshot!(json_string!(list, { "[1][0][1].created_at" => "[date]", "[1][0][1].updated_at" => "[date]", "[1][0][1].used_database_size" => "[bytes]", "[1][0][1].database_size" => "[bytes]" }), @r###" [ 1, diff --git a/crates/meilisearch-types/src/keys.rs b/crates/meilisearch-types/src/keys.rs index 1c1ebad5b..e30ef1008 100644 --- a/crates/meilisearch-types/src/keys.rs +++ b/crates/meilisearch-types/src/keys.rs @@ -323,18 +323,25 @@ pub enum Action { #[serde(rename = "network.update")] #[deserr(rename = "network.update")] NetworkUpdate, + // TODO should we rename it chatCompletions.get ? #[serde(rename = "chat.get")] #[deserr(rename = "chat.get")] Chat, - #[serde(rename = "chatSettings.*")] - #[deserr(rename = "chatSettings.*")] - ChatSettingsAll, - #[serde(rename = "chatSettings.get")] - #[deserr(rename = "chatSettings.get")] - ChatSettingsGet, - #[serde(rename = "chatSettings.update")] - #[deserr(rename = "chatSettings.update")] - ChatSettingsUpdate, + #[serde(rename = "chats.get")] + #[deserr(rename = "chats.get")] + Chats, + #[serde(rename = "chatsSettings.*")] + #[deserr(rename = "chatsSettings.*")] + ChatsSettingsAll, + #[serde(rename = "chatsSettings.get")] + #[deserr(rename = "chatsSettings.get")] + ChatsSettingsGet, + #[serde(rename = "chatsSettings.update")] + #[deserr(rename = "chatsSettings.update")] + ChatsSettingsUpdate, + #[serde(rename = "chatsSettings.delete")] + #[deserr(rename = "chatsSettings.delete")] + ChatsSettingsDelete, } impl Action { @@ -360,9 +367,12 @@ impl Action { SETTINGS_ALL => Some(Self::SettingsAll), SETTINGS_GET => Some(Self::SettingsGet), SETTINGS_UPDATE => Some(Self::SettingsUpdate), - CHAT_SETTINGS_ALL => Some(Self::ChatSettingsAll), - CHAT_SETTINGS_GET => Some(Self::ChatSettingsGet), - CHAT_SETTINGS_UPDATE => Some(Self::ChatSettingsUpdate), + CHAT => Some(Self::Chat), + CHATS_GET => Some(Self::Chats), + CHATS_SETTINGS_ALL => Some(Self::ChatsSettingsAll), + CHATS_SETTINGS_GET => Some(Self::ChatsSettingsGet), + CHATS_SETTINGS_UPDATE => Some(Self::ChatsSettingsUpdate), + CHATS_SETTINGS_DELETE => Some(Self::ChatsSettingsDelete), STATS_ALL => Some(Self::StatsAll), STATS_GET => Some(Self::StatsGet), METRICS_ALL => Some(Self::MetricsAll), @@ -379,7 +389,6 @@ impl Action { EXPERIMENTAL_FEATURES_UPDATE => Some(Self::ExperimentalFeaturesUpdate), NETWORK_GET => Some(Self::NetworkGet), NETWORK_UPDATE => Some(Self::NetworkUpdate), - CHAT => Some(Self::Chat), _otherwise => None, } } @@ -430,7 +439,9 @@ pub mod actions { pub const NETWORK_UPDATE: u8 = NetworkUpdate.repr(); pub const CHAT: u8 = Chat.repr(); - pub const CHAT_SETTINGS_ALL: u8 = ChatSettingsAll.repr(); - pub const CHAT_SETTINGS_GET: u8 = ChatSettingsGet.repr(); - pub const CHAT_SETTINGS_UPDATE: u8 = ChatSettingsUpdate.repr(); + pub const CHATS_GET: u8 = Chats.repr(); + pub const CHATS_SETTINGS_ALL: u8 = ChatsSettingsAll.repr(); + pub const CHATS_SETTINGS_GET: u8 = ChatsSettingsGet.repr(); + pub const CHATS_SETTINGS_UPDATE: u8 = ChatsSettingsUpdate.repr(); + pub const CHATS_SETTINGS_DELETE: u8 = ChatsSettingsDelete.repr(); } diff --git a/crates/meilisearch/src/routes/chat.rs b/crates/meilisearch/src/routes/chats/chat_completions.rs similarity index 96% rename from crates/meilisearch/src/routes/chat.rs rename to crates/meilisearch/src/routes/chats/chat_completions.rs index 1cc5f1012..ee16a33cd 100644 --- a/crates/meilisearch/src/routes/chat.rs +++ b/crates/meilisearch/src/routes/chats/chat_completions.rs @@ -44,7 +44,8 @@ use tokio::runtime::Handle; use tokio::sync::mpsc::error::SendError; use tokio::sync::mpsc::Sender; -use super::settings::chat::{ChatPrompts, GlobalChatSettings}; +use super::settings::{ChatPrompts, GlobalChatSettings}; +use super::ChatsParam; use crate::error::MeilisearchHttpError; use crate::extractors::authentication::policies::ActionPolicy; use crate::extractors::authentication::{extract_token_from_request, GuardedData, Policy as _}; @@ -60,13 +61,14 @@ const MEILI_REPORT_ERRORS_NAME: &str = "_meiliReportErrors"; const MEILI_SEARCH_IN_INDEX_FUNCTION_NAME: &str = "_meiliSearchInIndex"; pub fn configure(cfg: &mut web::ServiceConfig) { - cfg.service(web::resource("/completions").route(web::post().to(chat))); + cfg.service(web::resource("").route(web::post().to(chat))); } /// Get a chat completion async fn chat( index_scheduler: GuardedData, Data>, auth_ctrl: web::Data, + chats_param: web::Path, req: HttpRequest, search_queue: web::Data, web::Json(chat_completion): web::Json, @@ -74,6 +76,8 @@ async fn chat( // To enable later on, when the feature will be experimental // index_scheduler.features().check_chat("Using the /chat route")?; + let ChatsParam { workspace_uid } = chats_param.into_inner(); + assert_eq!( chat_completion.n.unwrap_or(1), 1, @@ -82,11 +86,27 @@ async fn chat( if chat_completion.stream.unwrap_or(false) { Either::Right( - streamed_chat(index_scheduler, auth_ctrl, search_queue, req, chat_completion).await, + streamed_chat( + index_scheduler, + auth_ctrl, + search_queue, + &workspace_uid, + req, + chat_completion, + ) + .await, ) } else { Either::Left( - non_streamed_chat(index_scheduler, auth_ctrl, search_queue, req, chat_completion).await, + non_streamed_chat( + index_scheduler, + auth_ctrl, + search_queue, + &workspace_uid, + req, + chat_completion, + ) + .await, ) } } @@ -294,12 +314,14 @@ async fn non_streamed_chat( index_scheduler: GuardedData, Data>, auth_ctrl: web::Data, search_queue: web::Data, + workspace_uid: &str, req: HttpRequest, mut chat_completion: CreateChatCompletionRequest, ) -> Result { let filters = index_scheduler.filters(); - let chat_settings = match index_scheduler.chat_settings().unwrap() { + let rtxn = index_scheduler.read_txn()?; + let chat_settings = match index_scheduler.chat_settings(&rtxn, workspace_uid).unwrap() { Some(value) => serde_json::from_value(value).unwrap(), None => GlobalChatSettings::default(), }; @@ -387,15 +409,18 @@ async fn streamed_chat( index_scheduler: GuardedData, Data>, auth_ctrl: web::Data, search_queue: web::Data, + workspace_uid: &str, req: HttpRequest, mut chat_completion: CreateChatCompletionRequest, ) -> Result { let filters = index_scheduler.filters(); - let chat_settings = match index_scheduler.chat_settings().unwrap() { + let rtxn = index_scheduler.read_txn()?; + let chat_settings = match index_scheduler.chat_settings(&rtxn, workspace_uid).unwrap() { Some(value) => serde_json::from_value(value.clone()).unwrap(), None => GlobalChatSettings::default(), }; + drop(rtxn); let mut config = OpenAIConfig::default(); if let Setting::Set(api_key) = chat_settings.api_key.as_ref() { @@ -662,6 +687,7 @@ impl SseEventSender { function_name: String, function_arguments: String, ) -> Result<(), SendError> { + #[allow(deprecated)] let message = ChatCompletionRequestMessage::Assistant(ChatCompletionRequestAssistantMessage { content: None, @@ -698,6 +724,7 @@ impl SseEventSender { resp.choices[0] = ChatChoiceStream { index: 0, + #[allow(deprecated)] delta: ChatCompletionStreamResponseDelta { content: None, function_call: None, @@ -744,6 +771,7 @@ impl SseEventSender { resp.choices[0] = ChatChoiceStream { index: 0, + #[allow(deprecated)] delta: ChatCompletionStreamResponseDelta { content: None, function_call: None, @@ -788,6 +816,7 @@ impl SseEventSender { resp.choices[0] = ChatChoiceStream { index: 0, + #[allow(deprecated)] delta: ChatCompletionStreamResponseDelta { content: None, function_call: None, diff --git a/crates/meilisearch/src/routes/chats/mod.rs b/crates/meilisearch/src/routes/chats/mod.rs new file mode 100644 index 000000000..00e3b28b5 --- /dev/null +++ b/crates/meilisearch/src/routes/chats/mod.rs @@ -0,0 +1,87 @@ +use actix_web::{ + web::{self, Data}, + HttpResponse, +}; +use deserr::{actix_web::AwebQueryParameter, Deserr}; +use index_scheduler::IndexScheduler; +use meilisearch_types::{ + deserr::{query_params::Param, DeserrQueryParamError}, + error::{ + deserr_codes::{InvalidIndexLimit, InvalidIndexOffset}, + ResponseError, + }, + keys::actions, +}; +use serde::{Deserialize, Serialize}; +use tracing::debug; +use utoipa::{IntoParams, ToSchema}; + +use crate::{ + extractors::authentication::{policies::ActionPolicy, GuardedData}, + routes::PAGINATION_DEFAULT_LIMIT, +}; + +use super::Pagination; + +// TODO supports chats/$workspace/settings + /chats/$workspace/chat/completions +pub mod chat_completions; +pub mod settings; + +#[derive(Deserialize)] +pub struct ChatsParam { + workspace_uid: String, +} + +pub fn configure(cfg: &mut web::ServiceConfig) { + cfg.service(web::resource("").route(web::get().to(list_workspaces))).service( + web::scope("/{workspace_uid}") + .service(web::scope("/chat/completions").configure(chat_completions::configure)) + .service(web::scope("/settings").configure(settings::configure)), + ); +} + +#[derive(Deserr, Debug, Clone, Copy, IntoParams)] +#[deserr(error = DeserrQueryParamError, rename_all = camelCase, deny_unknown_fields)] +#[into_params(rename_all = "camelCase", parameter_in = Query)] +pub struct ListChats { + /// The number of chat workspaces to skip before starting to retrieve anything + #[param(value_type = Option, default, example = 100)] + #[deserr(default, error = DeserrQueryParamError)] + pub offset: Param, + /// The number of chat workspaces to retrieve + #[param(value_type = Option, default = 20, example = 1)] + #[deserr(default = Param(PAGINATION_DEFAULT_LIMIT), error = DeserrQueryParamError)] + pub limit: Param, +} + +impl ListChats { + fn as_pagination(self) -> Pagination { + Pagination { offset: self.offset.0, limit: self.limit.0 } + } +} + +#[derive(Debug, Serialize, Clone, ToSchema)] +#[serde(rename_all = "camelCase")] +pub struct ChatWorkspaceView { + /// Unique identifier for the index + pub uid: String, +} + +pub async fn list_workspaces( + index_scheduler: GuardedData, Data>, + paginate: AwebQueryParameter, +) -> Result { + debug!(parameters = ?paginate, "List chat workspaces"); + let filters = index_scheduler.filters(); + let (total, workspaces) = index_scheduler.paginated_chat_workspace_uids( + filters, + *paginate.offset, + *paginate.limit, + )?; + let workspaces = + workspaces.into_iter().map(|uid| ChatWorkspaceView { uid }).collect::>(); + let ret = paginate.as_pagination().format_with(total, workspaces); + + debug!(returns = ?ret, "List chat workspaces"); + Ok(HttpResponse::Ok().json(ret)) +} diff --git a/crates/meilisearch/src/routes/settings/chat.rs b/crates/meilisearch/src/routes/chats/settings.rs similarity index 80% rename from crates/meilisearch/src/routes/settings/chat.rs rename to crates/meilisearch/src/routes/chats/settings.rs index 60fd01ab6..0d3f88938 100644 --- a/crates/meilisearch/src/routes/settings/chat.rs +++ b/crates/meilisearch/src/routes/chats/settings.rs @@ -10,21 +10,29 @@ use crate::extractors::authentication::policies::ActionPolicy; use crate::extractors::authentication::GuardedData; use crate::extractors::sequential_extractor::SeqHandler; +use super::ChatsParam; + pub fn configure(cfg: &mut web::ServiceConfig) { cfg.service( web::resource("") .route(web::get().to(get_settings)) - .route(web::patch().to(SeqHandler(patch_settings))), + .route(web::patch().to(SeqHandler(patch_settings))) + .route(web::delete().to(SeqHandler(delete_settings))), ); } async fn get_settings( index_scheduler: GuardedData< - ActionPolicy<{ actions::CHAT_SETTINGS_GET }>, + ActionPolicy<{ actions::CHATS_SETTINGS_GET }>, Data, >, + chats_param: web::Path, ) -> Result { - let mut settings = match index_scheduler.chat_settings()? { + let ChatsParam { workspace_uid } = chats_param.into_inner(); + + // TODO do a spawn_blocking here ??? + let rtxn = index_scheduler.read_txn()?; + let mut settings = match index_scheduler.chat_settings(&rtxn, &workspace_uid)? { Some(value) => serde_json::from_value(value).unwrap(), None => GlobalChatSettings::default(), }; @@ -34,12 +42,17 @@ async fn get_settings( async fn patch_settings( index_scheduler: GuardedData< - ActionPolicy<{ actions::CHAT_SETTINGS_UPDATE }>, + ActionPolicy<{ actions::CHATS_SETTINGS_UPDATE }>, Data, >, + chats_param: web::Path, web::Json(new): web::Json, ) -> Result { - let old = match index_scheduler.chat_settings()? { + let ChatsParam { workspace_uid } = chats_param.into_inner(); + + // TODO do a spawn_blocking here + let mut wtxn = index_scheduler.write_txn()?; + let old = match index_scheduler.chat_settings(&mut wtxn, &workspace_uid)? { Some(value) => serde_json::from_value(value).unwrap(), None => GlobalChatSettings::default(), }; @@ -64,16 +77,39 @@ async fn patch_settings( }; let value = serde_json::to_value(settings).unwrap(); - index_scheduler.put_chat_settings(&value)?; + index_scheduler.put_chat_settings(&mut wtxn, &workspace_uid, &value)?; + wtxn.commit()?; + Ok(HttpResponse::Ok().finish()) } +async fn delete_settings( + index_scheduler: GuardedData< + ActionPolicy<{ actions::CHATS_SETTINGS_DELETE }>, + Data, + >, + chats_param: web::Path, +) -> Result { + let ChatsParam { workspace_uid } = chats_param.into_inner(); + + // TODO do a spawn_blocking here + let mut wtxn = index_scheduler.write_txn()?; + if index_scheduler.delete_chat_settings(&mut wtxn, &workspace_uid)? { + wtxn.commit()?; + Ok(HttpResponse::Ok().finish()) + } else { + Ok(HttpResponse::NotFound().finish()) + } +} + #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(deny_unknown_fields, rename_all = "camelCase")] pub enum ChatSource { OpenAi, } +// TODO Implement Deserr on that. +// TODO Declare DbGlobalChatSettings (alias it). #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(deny_unknown_fields, rename_all = "camelCase")] pub struct GlobalChatSettings { @@ -114,6 +150,8 @@ impl GlobalChatSettings { } } +// TODO Implement Deserr on that. +// TODO Declare DbChatPrompts (alias it). #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(deny_unknown_fields, rename_all = "camelCase")] pub struct ChatPrompts { diff --git a/crates/meilisearch/src/routes/indexes/mod.rs b/crates/meilisearch/src/routes/indexes/mod.rs index 48ed1cfb1..04b3e12c4 100644 --- a/crates/meilisearch/src/routes/indexes/mod.rs +++ b/crates/meilisearch/src/routes/indexes/mod.rs @@ -172,7 +172,7 @@ pub async fn list_indexes( debug!(parameters = ?paginate, "List indexes"); let filters = index_scheduler.filters(); let (total, indexes) = - index_scheduler.get_paginated_indexes_stats(filters, *paginate.offset, *paginate.limit)?; + index_scheduler.paginated_indexes_stats(filters, *paginate.offset, *paginate.limit)?; let indexes = indexes .into_iter() .map(|(name, stats)| IndexView { diff --git a/crates/meilisearch/src/routes/mod.rs b/crates/meilisearch/src/routes/mod.rs index 3d56ce8e8..572092fea 100644 --- a/crates/meilisearch/src/routes/mod.rs +++ b/crates/meilisearch/src/routes/mod.rs @@ -52,7 +52,7 @@ const PAGINATION_DEFAULT_LIMIT_FN: fn() -> usize = || 20; mod api_key; pub mod batches; -pub mod chat; +pub mod chats; mod dump; pub mod features; pub mod indexes; @@ -62,7 +62,6 @@ mod multi_search; mod multi_search_analytics; pub mod network; mod open_api_utils; -pub mod settings; mod snapshot; mod swap_indexes; pub mod tasks; @@ -116,8 +115,7 @@ pub fn configure(cfg: &mut web::ServiceConfig) { .service(web::scope("/metrics").configure(metrics::configure)) .service(web::scope("/experimental-features").configure(features::configure)) .service(web::scope("/network").configure(network::configure)) - .service(web::scope("/chat").configure(chat::configure)) - .service(web::scope("/settings/chat").configure(settings::chat::configure)); + .service(web::scope("/chats").configure(chats::settings::configure)); #[cfg(feature = "swagger")] { diff --git a/crates/meilisearch/src/routes/settings/mod.rs b/crates/meilisearch/src/routes/settings/mod.rs deleted file mode 100644 index 30a62fc50..000000000 --- a/crates/meilisearch/src/routes/settings/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod chat; From 02cbcea3db2507136960e55e5fcc38854f0dfa6e Mon Sep 17 00:00:00 2001 From: Kerollmops Date: Fri, 30 May 2025 15:02:24 +0200 Subject: [PATCH 047/103] Better chat completions settings management --- crates/index-scheduler/src/lib.rs | 10 +- crates/meilisearch-types/src/error.rs | 1 + crates/meilisearch-types/src/features.rs | 73 ++++++++ .../src/routes/chats/chat_completions.rs | 69 +++---- .../meilisearch/src/routes/chats/settings.rs | 171 +++++++++++------- crates/meilisearch/src/routes/mod.rs | 2 +- 6 files changed, 219 insertions(+), 107 deletions(-) diff --git a/crates/index-scheduler/src/lib.rs b/crates/index-scheduler/src/lib.rs index 261fe030c..3860ef5cb 100644 --- a/crates/index-scheduler/src/lib.rs +++ b/crates/index-scheduler/src/lib.rs @@ -51,7 +51,9 @@ pub use features::RoFeatures; use flate2::bufread::GzEncoder; use flate2::Compression; use meilisearch_types::batches::Batch; -use meilisearch_types::features::{InstanceTogglableFeatures, Network, RuntimeTogglableFeatures}; +use meilisearch_types::features::{ + ChatCompletionSettings, InstanceTogglableFeatures, Network, RuntimeTogglableFeatures, +}; use meilisearch_types::heed::byteorder::BE; use meilisearch_types::heed::types::{SerdeJson, Str, I128}; use meilisearch_types::heed::{self, Database, Env, RoTxn, RwTxn, WithoutTls}; @@ -154,7 +156,7 @@ pub struct IndexScheduler { features: features::FeatureData, /// Stores the custom chat prompts and other settings of the indexes. - pub(crate) chat_settings: Database>, + pub(crate) chat_settings: Database>, /// Everything related to the processing of the tasks pub scheduler: scheduler::Scheduler, @@ -898,7 +900,7 @@ impl IndexScheduler { res.map(EmbeddingConfigs::new) } - pub fn chat_settings(&self, rtxn: &RoTxn, uid: &str) -> Result> { + pub fn chat_settings(&self, rtxn: &RoTxn, uid: &str) -> Result> { self.chat_settings.get(rtxn, uid).map_err(Into::into) } @@ -906,7 +908,7 @@ impl IndexScheduler { &self, wtxn: &mut RwTxn, uid: &str, - settings: &serde_json::Value, + settings: &ChatCompletionSettings, ) -> Result<()> { self.chat_settings.put(wtxn, uid, settings).map_err(Into::into) } diff --git a/crates/meilisearch-types/src/error.rs b/crates/meilisearch-types/src/error.rs index 172656237..d7b6f56d4 100644 --- a/crates/meilisearch-types/src/error.rs +++ b/crates/meilisearch-types/src/error.rs @@ -217,6 +217,7 @@ ImmutableIndexUpdatedAt , InvalidRequest , BAD_REQUEST; IndexAlreadyExists , InvalidRequest , CONFLICT ; IndexCreationFailed , Internal , INTERNAL_SERVER_ERROR; IndexNotFound , InvalidRequest , NOT_FOUND; +ChatWorkspaceNotFound , InvalidRequest , NOT_FOUND; IndexPrimaryKeyAlreadyExists , InvalidRequest , BAD_REQUEST ; IndexPrimaryKeyMultipleCandidatesFound, InvalidRequest , BAD_REQUEST; IndexPrimaryKeyNoCandidateFound , InvalidRequest , BAD_REQUEST ; diff --git a/crates/meilisearch-types/src/features.rs b/crates/meilisearch-types/src/features.rs index 5db8775b6..f12cbcfc9 100644 --- a/crates/meilisearch-types/src/features.rs +++ b/crates/meilisearch-types/src/features.rs @@ -2,6 +2,13 @@ use std::collections::BTreeMap; use serde::{Deserialize, Serialize}; +pub const DEFAULT_CHAT_SYSTEM_PROMPT: &str = "You are a highly capable research assistant with access to powerful search tools. IMPORTANT INSTRUCTIONS:1. When answering questions, you MUST make multiple tool calls (at least 2-3) to gather comprehensive information.2. Use different search queries for each tool call - vary keywords, rephrase questions, and explore different semantic angles to ensure broad coverage.3. Always explicitly announce BEFORE making each tool call by saying: \"I'll search for [specific information] now.\"4. Combine information from ALL tool calls to provide complete, nuanced answers rather than relying on a single source.5. For complex topics, break down your research into multiple targeted queries rather than using a single generic search."; +pub const DEFAULT_CHAT_SEARCH_DESCRIPTION_PROMPT: &str = + "Search the database for relevant JSON documents using an optional query."; +pub const DEFAULT_CHAT_SEARCH_Q_PARAM_PROMPT: &str = "The search query string used to find relevant documents in the index. This should contain keywords or phrases that best represent what the user is looking for. More specific queries will yield more precise results."; +pub const DEFAULT_CHAT_SEARCH_INDEX_UID_PARAM_PROMPT: &str = "The name of the index to search within. An index is a collection of documents organized for search. Selecting the right index ensures the most relevant results for the user query. You can access to two indexes: movies, steam. The movies index contains movies with overviews. The steam index contains steam games from the Steam platform with their prices"; +pub const DEFAULT_CHAT_PRE_QUERY_PROMPT: &str = ""; + #[derive(Serialize, Deserialize, Debug, Clone, Copy, Default, PartialEq, Eq)] #[serde(rename_all = "camelCase", default)] pub struct RuntimeTogglableFeatures { @@ -37,3 +44,69 @@ pub struct Network { #[serde(default)] pub remotes: BTreeMap, } + +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Default)] +#[serde(rename_all = "camelCase")] +pub struct ChatCompletionSettings { + pub source: ChatCompletionSource, + #[serde(default)] + pub base_api: Option, + #[serde(default)] + pub api_key: Option, + #[serde(default)] + pub prompts: ChatCompletionPrompts, +} + +impl ChatCompletionSettings { + pub fn hide_secrets(&mut self) { + if let Some(api_key) = &mut self.api_key { + Self::hide_secret(api_key); + } + } + + fn hide_secret(secret: &mut String) { + match secret.len() { + x if x < 10 => { + secret.replace_range(.., "XXX..."); + } + x if x < 20 => { + secret.replace_range(2.., "XXXX..."); + } + x if x < 30 => { + secret.replace_range(3.., "XXXXX..."); + } + _x => { + secret.replace_range(5.., "XXXXXX..."); + } + } + } +} + +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Default)] +#[serde(rename_all = "camelCase")] +pub enum ChatCompletionSource { + #[default] + OpenAi, +} + +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub struct ChatCompletionPrompts { + pub system: String, + pub search_description: String, + pub search_q_param: String, + pub search_index_uid_param: String, + pub pre_query: String, +} + +impl Default for ChatCompletionPrompts { + fn default() -> Self { + Self { + system: DEFAULT_CHAT_SYSTEM_PROMPT.to_string(), + search_description: DEFAULT_CHAT_SEARCH_DESCRIPTION_PROMPT.to_string(), + search_q_param: DEFAULT_CHAT_SEARCH_Q_PARAM_PROMPT.to_string(), + search_index_uid_param: DEFAULT_CHAT_SEARCH_INDEX_UID_PARAM_PROMPT.to_string(), + pre_query: DEFAULT_CHAT_PRE_QUERY_PROMPT.to_string(), + } + } +} diff --git a/crates/meilisearch/src/routes/chats/chat_completions.rs b/crates/meilisearch/src/routes/chats/chat_completions.rs index ee16a33cd..a923d2ff9 100644 --- a/crates/meilisearch/src/routes/chats/chat_completions.rs +++ b/crates/meilisearch/src/routes/chats/chat_completions.rs @@ -26,13 +26,15 @@ use bumpalo::Bump; use futures::StreamExt; use index_scheduler::IndexScheduler; use meilisearch_auth::AuthController; -use meilisearch_types::error::ResponseError; +use meilisearch_types::error::{Code, ResponseError}; +use meilisearch_types::features::{ + ChatCompletionPrompts as DbChatCompletionPrompts, ChatCompletionSettings as DbChatSettings, +}; use meilisearch_types::heed::RoTxn; use meilisearch_types::keys::actions; use meilisearch_types::milli::index::ChatConfig; use meilisearch_types::milli::prompt::{Prompt, PromptData}; use meilisearch_types::milli::update::new::document::DocumentFromDb; -use meilisearch_types::milli::update::Setting; use meilisearch_types::milli::{ all_obkv_to_json, obkv_to_json, DocumentId, FieldIdMapWithMetadata, GlobalFieldsIdsMap, MetadataBuilder, TimeBudget, @@ -44,7 +46,6 @@ use tokio::runtime::Handle; use tokio::sync::mpsc::error::SendError; use tokio::sync::mpsc::Sender; -use super::settings::{ChatPrompts, GlobalChatSettings}; use super::ChatsParam; use crate::error::MeilisearchHttpError; use crate::extractors::authentication::policies::ActionPolicy; @@ -132,7 +133,7 @@ fn setup_search_tool( index_scheduler: &Data, filters: &meilisearch_auth::AuthFilter, chat_completion: &mut CreateChatCompletionRequest, - prompts: &ChatPrompts, + prompts: &DbChatCompletionPrompts, ) -> Result { let tools = chat_completion.tools.get_or_insert_default(); if tools.iter().find(|t| t.function.name == MEILI_SEARCH_IN_INDEX_FUNCTION_NAME).is_some() { @@ -167,7 +168,7 @@ fn setup_search_tool( }); let mut index_uids = Vec::new(); - let mut function_description = prompts.search_description.clone().unwrap(); + let mut function_description = prompts.search_description.clone(); index_scheduler.try_for_each_index::<_, ()>(|name, index| { // Make sure to skip unauthorized indexes if !filters.is_index_authorized(&name) { @@ -195,13 +196,13 @@ fn setup_search_tool( "index_uid": { "type": "string", "enum": index_uids, - "description": prompts.search_index_uid_param.clone().unwrap(), + "description": prompts.search_index_uid_param, }, "q": { // Unfortunately, Mistral does not support an array of types, here. // "type": ["string", "null"], "type": "string", - "description": prompts.search_q_param.clone().unwrap(), + "description": prompts.search_q_param, } }, "required": ["index_uid", "q"], @@ -219,9 +220,7 @@ fn setup_search_tool( chat_completion.messages.insert( 0, ChatCompletionRequestMessage::System(ChatCompletionRequestSystemMessage { - content: ChatCompletionRequestSystemMessageContent::Text( - prompts.system.as_ref().unwrap().clone(), - ), + content: ChatCompletionRequestSystemMessageContent::Text(prompts.system.clone()), name: None, }), ); @@ -322,23 +321,27 @@ async fn non_streamed_chat( let rtxn = index_scheduler.read_txn()?; let chat_settings = match index_scheduler.chat_settings(&rtxn, workspace_uid).unwrap() { - Some(value) => serde_json::from_value(value).unwrap(), - None => GlobalChatSettings::default(), + Some(settings) => settings, + None => { + return Err(ResponseError::from_msg( + format!("Chat `{workspace_uid}` not found"), + Code::ChatWorkspaceNotFound, + )) + } }; let mut config = OpenAIConfig::default(); - if let Setting::Set(api_key) = chat_settings.api_key.as_ref() { + if let Some(api_key) = chat_settings.api_key.as_ref() { config = config.with_api_key(api_key); } - if let Setting::Set(base_api) = chat_settings.base_api.as_ref() { + if let Some(base_api) = chat_settings.base_api.as_ref() { config = config.with_api_base(base_api); } let client = Client::with_config(config); let auth_token = extract_token_from_request(&req)?.unwrap(); - let prompts = chat_settings.prompts.clone().or(Setting::Set(ChatPrompts::default())).unwrap(); let FunctionSupport { report_progress, report_sources, append_to_conversation, report_errors } = - setup_search_tool(&index_scheduler, filters, &mut chat_completion, &prompts)?; + setup_search_tool(&index_scheduler, filters, &mut chat_completion, &chat_settings.prompts)?; let mut response; loop { @@ -381,13 +384,11 @@ async fn non_streamed_chat( Err(err) => err, }; + let answer = format!("{}\n\n{text}", chat_settings.prompts.pre_query); chat_completion.messages.push(ChatCompletionRequestMessage::Tool( ChatCompletionRequestToolMessage { tool_call_id: call.id.clone(), - content: ChatCompletionRequestToolMessageContent::Text(format!( - "{}\n\n{text}", - chat_settings.prompts.clone().unwrap().pre_query.unwrap() - )), + content: ChatCompletionRequestToolMessageContent::Text(answer), }, )); } @@ -416,24 +417,28 @@ async fn streamed_chat( let filters = index_scheduler.filters(); let rtxn = index_scheduler.read_txn()?; - let chat_settings = match index_scheduler.chat_settings(&rtxn, workspace_uid).unwrap() { - Some(value) => serde_json::from_value(value.clone()).unwrap(), - None => GlobalChatSettings::default(), + let chat_settings = match index_scheduler.chat_settings(&rtxn, workspace_uid)? { + Some(settings) => settings, + None => { + return Err(ResponseError::from_msg( + format!("Chat `{workspace_uid}` not found"), + Code::ChatWorkspaceNotFound, + )) + } }; drop(rtxn); let mut config = OpenAIConfig::default(); - if let Setting::Set(api_key) = chat_settings.api_key.as_ref() { + if let Some(api_key) = chat_settings.api_key.as_ref() { config = config.with_api_key(api_key); } - if let Setting::Set(base_api) = chat_settings.base_api.as_ref() { + if let Some(base_api) = chat_settings.base_api.as_ref() { config = config.with_api_base(base_api); } let auth_token = extract_token_from_request(&req)?.unwrap().to_string(); - let prompts = chat_settings.prompts.clone().or(Setting::Set(ChatPrompts::default())).unwrap(); let function_support = - setup_search_tool(&index_scheduler, filters, &mut chat_completion, &prompts)?; + setup_search_tool(&index_scheduler, filters, &mut chat_completion, &chat_settings.prompts)?; let (tx, rx) = tokio::sync::mpsc::channel(10); let tx = SseEventSender(tx); @@ -478,7 +483,7 @@ async fn run_conversation( search_queue: &web::Data, auth_token: &str, client: &Client, - chat_settings: &GlobalChatSettings, + chat_settings: &DbChatSettings, chat_completion: &mut CreateChatCompletionRequest, tx: &SseEventSender, global_tool_calls: &mut HashMap, @@ -605,7 +610,7 @@ async fn handle_meili_tools( auth_ctrl: &web::Data, search_queue: &web::Data, auth_token: &str, - chat_settings: &GlobalChatSettings, + chat_settings: &DbChatSettings, tx: &SseEventSender, meili_calls: Vec, chat_completion: &mut CreateChatCompletionRequest, @@ -658,12 +663,10 @@ async fn handle_meili_tools( Err(err) => err, }; + let answer = format!("{}\n\n{text}", chat_settings.prompts.pre_query); let tool = ChatCompletionRequestMessage::Tool(ChatCompletionRequestToolMessage { tool_call_id: call.id.clone(), - content: ChatCompletionRequestToolMessageContent::Text(format!( - "{}\n\n{text}", - chat_settings.prompts.as_ref().unwrap().pre_query.as_ref().unwrap() - )), + content: ChatCompletionRequestToolMessageContent::Text(answer), }); if append_to_conversation { diff --git a/crates/meilisearch/src/routes/chats/settings.rs b/crates/meilisearch/src/routes/chats/settings.rs index 0d3f88938..6354066be 100644 --- a/crates/meilisearch/src/routes/chats/settings.rs +++ b/crates/meilisearch/src/routes/chats/settings.rs @@ -1,7 +1,13 @@ use actix_web::web::{self, Data}; use actix_web::HttpResponse; use index_scheduler::IndexScheduler; -use meilisearch_types::error::ResponseError; +use meilisearch_types::error::{Code, ResponseError}; +use meilisearch_types::features::{ + ChatCompletionPrompts as DbChatCompletionPrompts, ChatCompletionSettings, + ChatCompletionSource as DbChatCompletionSource, DEFAULT_CHAT_PRE_QUERY_PROMPT, + DEFAULT_CHAT_SEARCH_DESCRIPTION_PROMPT, DEFAULT_CHAT_SEARCH_INDEX_UID_PARAM_PROMPT, + DEFAULT_CHAT_SEARCH_Q_PARAM_PROMPT, DEFAULT_CHAT_SYSTEM_PROMPT, +}; use meilisearch_types::keys::actions; use meilisearch_types::milli::update::Setting; use serde::{Deserialize, Serialize}; @@ -15,7 +21,7 @@ use super::ChatsParam; pub fn configure(cfg: &mut web::ServiceConfig) { cfg.service( web::resource("") - .route(web::get().to(get_settings)) + .route(web::get().to(SeqHandler(get_settings))) .route(web::patch().to(SeqHandler(patch_settings))) .route(web::delete().to(SeqHandler(delete_settings))), ); @@ -33,8 +39,13 @@ async fn get_settings( // TODO do a spawn_blocking here ??? let rtxn = index_scheduler.read_txn()?; let mut settings = match index_scheduler.chat_settings(&rtxn, &workspace_uid)? { - Some(value) => serde_json::from_value(value).unwrap(), - None => GlobalChatSettings::default(), + Some(settings) => settings, + None => { + return Err(ResponseError::from_msg( + format!("Chat `{workspace_uid}` not found"), + Code::ChatWorkspaceNotFound, + )) + } }; settings.hide_secrets(); Ok(HttpResponse::Ok().json(settings)) @@ -52,35 +63,73 @@ async fn patch_settings( // TODO do a spawn_blocking here let mut wtxn = index_scheduler.write_txn()?; - let old = match index_scheduler.chat_settings(&mut wtxn, &workspace_uid)? { - Some(value) => serde_json::from_value(value).unwrap(), - None => GlobalChatSettings::default(), - }; + let old_settings = + index_scheduler.chat_settings(&mut wtxn, &workspace_uid)?.unwrap_or_default(); - let settings = GlobalChatSettings { - source: new.source.or(old.source), - base_api: new.base_api.clone().or(old.base_api), - api_key: new.api_key.clone().or(old.api_key), - prompts: match (new.prompts, old.prompts) { - (Setting::NotSet, set) | (set, Setting::NotSet) => set, - (Setting::Set(_) | Setting::Reset, Setting::Reset) => Setting::Reset, - (Setting::Reset, Setting::Set(set)) => Setting::Set(set), - // If both are set we must merge the prompts settings - (Setting::Set(new), Setting::Set(old)) => Setting::Set(ChatPrompts { - system: new.system.or(old.system), - search_description: new.search_description.or(old.search_description), - search_q_param: new.search_q_param.or(old.search_q_param), - search_index_uid_param: new.search_index_uid_param.or(old.search_index_uid_param), - pre_query: new.pre_query.or(old.pre_query), - }), + let prompts = match new.prompts { + Setting::Set(new_prompts) => DbChatCompletionPrompts { + system: match new_prompts.system { + Setting::Set(new_system) => new_system, + Setting::Reset => DEFAULT_CHAT_SYSTEM_PROMPT.to_string(), + Setting::NotSet => old_settings.prompts.system, + }, + search_description: match new_prompts.search_description { + Setting::Set(new_description) => new_description, + Setting::Reset => DEFAULT_CHAT_SEARCH_DESCRIPTION_PROMPT.to_string(), + Setting::NotSet => old_settings.prompts.search_description, + }, + search_q_param: match new_prompts.search_q_param { + Setting::Set(new_description) => new_description, + Setting::Reset => DEFAULT_CHAT_SEARCH_Q_PARAM_PROMPT.to_string(), + Setting::NotSet => old_settings.prompts.search_q_param, + }, + search_index_uid_param: match new_prompts.search_index_uid_param { + Setting::Set(new_description) => new_description, + Setting::Reset => DEFAULT_CHAT_SEARCH_INDEX_UID_PARAM_PROMPT.to_string(), + Setting::NotSet => old_settings.prompts.search_index_uid_param, + }, + pre_query: match new_prompts.pre_query { + Setting::Set(new_description) => new_description, + Setting::Reset => DEFAULT_CHAT_PRE_QUERY_PROMPT.to_string(), + Setting::NotSet => old_settings.prompts.pre_query, + }, }, + Setting::Reset => DbChatCompletionPrompts::default(), + Setting::NotSet => old_settings.prompts, }; - let value = serde_json::to_value(settings).unwrap(); - index_scheduler.put_chat_settings(&mut wtxn, &workspace_uid, &value)?; + let settings = ChatCompletionSettings { + source: match new.source { + Setting::Set(new_source) => new_source.into(), + Setting::Reset => DbChatCompletionSource::default(), + Setting::NotSet => old_settings.source, + }, + base_api: match new.base_api { + Setting::Set(new_base_api) => Some(new_base_api), + Setting::Reset => None, + Setting::NotSet => old_settings.base_api, + }, + api_key: match new.api_key { + Setting::Set(new_api_key) => Some(new_api_key), + Setting::Reset => None, + Setting::NotSet => old_settings.api_key, + }, + prompts, + }; + + // TODO send analytics + // analytics.publish( + // PatchNetworkAnalytics { + // network_size: merged_remotes.len(), + // network_has_self: merged_self.is_some(), + // }, + // &req, + // ); + + index_scheduler.put_chat_settings(&mut wtxn, &workspace_uid, &settings)?; wtxn.commit()?; - Ok(HttpResponse::Ok().finish()) + Ok(HttpResponse::Ok().json(settings)) } async fn delete_settings( @@ -96,58 +145,42 @@ async fn delete_settings( let mut wtxn = index_scheduler.write_txn()?; if index_scheduler.delete_chat_settings(&mut wtxn, &workspace_uid)? { wtxn.commit()?; - Ok(HttpResponse::Ok().finish()) + Ok(HttpResponse::NoContent().finish()) } else { - Ok(HttpResponse::NotFound().finish()) + Err(ResponseError::from_msg( + format!("Chat `{workspace_uid}` not found"), + Code::ChatWorkspaceNotFound, + )) } } -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Default, Debug, Clone, Copy, Serialize, Deserialize)] #[serde(deny_unknown_fields, rename_all = "camelCase")] -pub enum ChatSource { +pub enum ChatCompletionSource { + #[default] OpenAi, } -// TODO Implement Deserr on that. -// TODO Declare DbGlobalChatSettings (alias it). -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(deny_unknown_fields, rename_all = "camelCase")] -pub struct GlobalChatSettings { - #[serde(default, skip_serializing_if = "Setting::is_not_set")] - pub source: Setting, - #[serde(default, skip_serializing_if = "Setting::is_not_set")] - pub base_api: Setting, - #[serde(default, skip_serializing_if = "Setting::is_not_set")] - pub api_key: Setting, - #[serde(default, skip_serializing_if = "Setting::is_not_set")] - pub prompts: Setting, +impl From for DbChatCompletionSource { + fn from(source: ChatCompletionSource) -> Self { + match source { + ChatCompletionSource::OpenAi => DbChatCompletionSource::OpenAi, + } + } } -impl GlobalChatSettings { - pub fn hide_secrets(&mut self) { - match &mut self.api_key { - Setting::Set(key) => Self::hide_secret(key), - Setting::Reset => (), - Setting::NotSet => (), - } - } - - fn hide_secret(secret: &mut String) { - match secret.len() { - x if x < 10 => { - secret.replace_range(.., "XXX..."); - } - x if x < 20 => { - secret.replace_range(2.., "XXXX..."); - } - x if x < 30 => { - secret.replace_range(3.., "XXXXX..."); - } - _x => { - secret.replace_range(5.., "XXXXXX..."); - } - } - } +// TODO Implement Deserr on that. +#[derive(Debug, Clone, Deserialize)] +#[serde(deny_unknown_fields, rename_all = "camelCase")] +pub struct GlobalChatSettings { + #[serde(default)] + pub source: Setting, + #[serde(default)] + pub base_api: Setting, + #[serde(default)] + pub api_key: Setting, + #[serde(default)] + pub prompts: Setting, } // TODO Implement Deserr on that. diff --git a/crates/meilisearch/src/routes/mod.rs b/crates/meilisearch/src/routes/mod.rs index 572092fea..cc62e43c3 100644 --- a/crates/meilisearch/src/routes/mod.rs +++ b/crates/meilisearch/src/routes/mod.rs @@ -115,7 +115,7 @@ pub fn configure(cfg: &mut web::ServiceConfig) { .service(web::scope("/metrics").configure(metrics::configure)) .service(web::scope("/experimental-features").configure(features::configure)) .service(web::scope("/network").configure(network::configure)) - .service(web::scope("/chats").configure(chats::settings::configure)); + .service(web::scope("/chats").configure(chats::configure)); #[cfg(feature = "swagger")] { From 496685fa265407d2eca6cbdaa0e83bd1bb469c52 Mon Sep 17 00:00:00 2001 From: Kerollmops Date: Fri, 30 May 2025 15:30:16 +0200 Subject: [PATCH 048/103] Implement deserr on ChatCompletions settings structs --- crates/meilisearch-types/src/error.rs | 398 +++++++++--------- crates/meilisearch/src/routes/chats/mod.rs | 1 - .../meilisearch/src/routes/chats/settings.rs | 117 +++-- 3 files changed, 251 insertions(+), 265 deletions(-) diff --git a/crates/meilisearch-types/src/error.rs b/crates/meilisearch-types/src/error.rs index d7b6f56d4..11bad977d 100644 --- a/crates/meilisearch-types/src/error.rs +++ b/crates/meilisearch-types/src/error.rs @@ -194,202 +194,212 @@ macro_rules! make_error_codes { // An exhaustive list of all the error codes used by meilisearch. make_error_codes! { -ApiKeyAlreadyExists , InvalidRequest , CONFLICT ; -ApiKeyNotFound , InvalidRequest , NOT_FOUND ; -BadParameter , InvalidRequest , BAD_REQUEST; -BadRequest , InvalidRequest , BAD_REQUEST; -DatabaseSizeLimitReached , Internal , INTERNAL_SERVER_ERROR; -DocumentNotFound , InvalidRequest , NOT_FOUND; -DumpAlreadyProcessing , InvalidRequest , CONFLICT; -DumpNotFound , InvalidRequest , NOT_FOUND; -DumpProcessFailed , Internal , INTERNAL_SERVER_ERROR; -DuplicateIndexFound , InvalidRequest , BAD_REQUEST; -ImmutableApiKeyActions , InvalidRequest , BAD_REQUEST; -ImmutableApiKeyCreatedAt , InvalidRequest , BAD_REQUEST; -ImmutableApiKeyExpiresAt , InvalidRequest , BAD_REQUEST; -ImmutableApiKeyIndexes , InvalidRequest , BAD_REQUEST; -ImmutableApiKeyKey , InvalidRequest , BAD_REQUEST; -ImmutableApiKeyUid , InvalidRequest , BAD_REQUEST; -ImmutableApiKeyUpdatedAt , InvalidRequest , BAD_REQUEST; -ImmutableIndexCreatedAt , InvalidRequest , BAD_REQUEST; -ImmutableIndexUid , InvalidRequest , BAD_REQUEST; -ImmutableIndexUpdatedAt , InvalidRequest , BAD_REQUEST; -IndexAlreadyExists , InvalidRequest , CONFLICT ; -IndexCreationFailed , Internal , INTERNAL_SERVER_ERROR; -IndexNotFound , InvalidRequest , NOT_FOUND; -ChatWorkspaceNotFound , InvalidRequest , NOT_FOUND; -IndexPrimaryKeyAlreadyExists , InvalidRequest , BAD_REQUEST ; -IndexPrimaryKeyMultipleCandidatesFound, InvalidRequest , BAD_REQUEST; -IndexPrimaryKeyNoCandidateFound , InvalidRequest , BAD_REQUEST ; -Internal , Internal , INTERNAL_SERVER_ERROR ; -InvalidApiKey , Auth , FORBIDDEN ; -InvalidApiKeyActions , InvalidRequest , BAD_REQUEST ; -InvalidApiKeyDescription , InvalidRequest , BAD_REQUEST ; -InvalidApiKeyExpiresAt , InvalidRequest , BAD_REQUEST ; -InvalidApiKeyIndexes , InvalidRequest , BAD_REQUEST ; -InvalidApiKeyLimit , InvalidRequest , BAD_REQUEST ; -InvalidApiKeyName , InvalidRequest , BAD_REQUEST ; -InvalidApiKeyOffset , InvalidRequest , BAD_REQUEST ; -InvalidApiKeyUid , InvalidRequest , BAD_REQUEST ; -InvalidContentType , InvalidRequest , UNSUPPORTED_MEDIA_TYPE ; -InvalidDocumentCsvDelimiter , InvalidRequest , BAD_REQUEST ; -InvalidDocumentFields , InvalidRequest , BAD_REQUEST ; -InvalidDocumentRetrieveVectors , InvalidRequest , BAD_REQUEST ; -MissingDocumentFilter , InvalidRequest , BAD_REQUEST ; -MissingDocumentEditionFunction , InvalidRequest , BAD_REQUEST ; -InvalidDocumentFilter , InvalidRequest , BAD_REQUEST ; -InvalidDocumentGeoField , InvalidRequest , BAD_REQUEST ; -InvalidVectorDimensions , InvalidRequest , BAD_REQUEST ; -InvalidVectorsType , InvalidRequest , BAD_REQUEST ; -InvalidDocumentId , InvalidRequest , BAD_REQUEST ; -InvalidDocumentIds , InvalidRequest , BAD_REQUEST ; -InvalidDocumentLimit , InvalidRequest , BAD_REQUEST ; -InvalidDocumentOffset , InvalidRequest , BAD_REQUEST ; -InvalidSearchEmbedder , InvalidRequest , BAD_REQUEST ; -InvalidSimilarEmbedder , InvalidRequest , BAD_REQUEST ; -InvalidSearchHybridQuery , InvalidRequest , BAD_REQUEST ; -InvalidIndexLimit , InvalidRequest , BAD_REQUEST ; -InvalidIndexOffset , InvalidRequest , BAD_REQUEST ; -InvalidIndexPrimaryKey , InvalidRequest , BAD_REQUEST ; -InvalidIndexUid , InvalidRequest , BAD_REQUEST ; -InvalidMultiSearchFacets , InvalidRequest , BAD_REQUEST ; -InvalidMultiSearchFacetsByIndex , InvalidRequest , BAD_REQUEST ; -InvalidMultiSearchFacetOrder , InvalidRequest , BAD_REQUEST ; -InvalidMultiSearchFederated , InvalidRequest , BAD_REQUEST ; -InvalidMultiSearchFederationOptions , InvalidRequest , BAD_REQUEST ; -InvalidMultiSearchMaxValuesPerFacet , InvalidRequest , BAD_REQUEST ; -InvalidMultiSearchMergeFacets , InvalidRequest , BAD_REQUEST ; -InvalidMultiSearchQueryFacets , InvalidRequest , BAD_REQUEST ; -InvalidMultiSearchQueryPagination , InvalidRequest , BAD_REQUEST ; -InvalidMultiSearchQueryRankingRules , InvalidRequest , BAD_REQUEST ; -InvalidMultiSearchQueryPosition , InvalidRequest , BAD_REQUEST ; -InvalidMultiSearchRemote , InvalidRequest , BAD_REQUEST ; -InvalidMultiSearchWeight , InvalidRequest , BAD_REQUEST ; -InvalidNetworkRemotes , InvalidRequest , BAD_REQUEST ; -InvalidNetworkSelf , InvalidRequest , BAD_REQUEST ; -InvalidNetworkSearchApiKey , InvalidRequest , BAD_REQUEST ; -InvalidNetworkUrl , InvalidRequest , BAD_REQUEST ; -InvalidSearchAttributesToSearchOn , InvalidRequest , BAD_REQUEST ; -InvalidSearchAttributesToCrop , InvalidRequest , BAD_REQUEST ; -InvalidSearchAttributesToHighlight , InvalidRequest , BAD_REQUEST ; -InvalidSimilarAttributesToRetrieve , InvalidRequest , BAD_REQUEST ; -InvalidSimilarRetrieveVectors , InvalidRequest , BAD_REQUEST ; -InvalidSearchAttributesToRetrieve , InvalidRequest , BAD_REQUEST ; -InvalidSearchRankingScoreThreshold , InvalidRequest , BAD_REQUEST ; -InvalidSimilarRankingScoreThreshold , InvalidRequest , BAD_REQUEST ; -InvalidSearchRetrieveVectors , InvalidRequest , BAD_REQUEST ; -InvalidSearchCropLength , InvalidRequest , BAD_REQUEST ; -InvalidSearchCropMarker , InvalidRequest , BAD_REQUEST ; -InvalidSearchFacets , InvalidRequest , BAD_REQUEST ; -InvalidSearchSemanticRatio , InvalidRequest , BAD_REQUEST ; -InvalidSearchLocales , InvalidRequest , BAD_REQUEST ; -InvalidFacetSearchExhaustiveFacetCount, InvalidRequest , BAD_REQUEST ; -InvalidFacetSearchFacetName , InvalidRequest , BAD_REQUEST ; -InvalidSimilarId , InvalidRequest , BAD_REQUEST ; -InvalidSearchFilter , InvalidRequest , BAD_REQUEST ; -InvalidSimilarFilter , InvalidRequest , BAD_REQUEST ; -InvalidSearchHighlightPostTag , InvalidRequest , BAD_REQUEST ; -InvalidSearchHighlightPreTag , InvalidRequest , BAD_REQUEST ; -InvalidSearchHitsPerPage , InvalidRequest , BAD_REQUEST ; -InvalidSimilarLimit , InvalidRequest , BAD_REQUEST ; -InvalidSearchLimit , InvalidRequest , BAD_REQUEST ; -InvalidSearchMatchingStrategy , InvalidRequest , BAD_REQUEST ; -InvalidSimilarOffset , InvalidRequest , BAD_REQUEST ; -InvalidSearchOffset , InvalidRequest , BAD_REQUEST ; -InvalidSearchPage , InvalidRequest , BAD_REQUEST ; -InvalidSearchQ , InvalidRequest , BAD_REQUEST ; -InvalidFacetSearchQuery , InvalidRequest , BAD_REQUEST ; -InvalidFacetSearchName , InvalidRequest , BAD_REQUEST ; -FacetSearchDisabled , InvalidRequest , BAD_REQUEST ; -InvalidSearchVector , InvalidRequest , BAD_REQUEST ; -InvalidSearchShowMatchesPosition , InvalidRequest , BAD_REQUEST ; -InvalidSearchShowRankingScore , InvalidRequest , BAD_REQUEST ; -InvalidSimilarShowRankingScore , InvalidRequest , BAD_REQUEST ; -InvalidSearchShowRankingScoreDetails , InvalidRequest , BAD_REQUEST ; -InvalidSimilarShowRankingScoreDetails , InvalidRequest , BAD_REQUEST ; -InvalidSearchSort , InvalidRequest , BAD_REQUEST ; -InvalidSearchDistinct , InvalidRequest , BAD_REQUEST ; -InvalidSettingsDisplayedAttributes , InvalidRequest , BAD_REQUEST ; -InvalidSettingsDistinctAttribute , InvalidRequest , BAD_REQUEST ; -InvalidSettingsProximityPrecision , InvalidRequest , BAD_REQUEST ; -InvalidSettingsFacetSearch , InvalidRequest , BAD_REQUEST ; -InvalidSettingsPrefixSearch , InvalidRequest , BAD_REQUEST ; -InvalidSettingsFaceting , InvalidRequest , BAD_REQUEST ; -InvalidSettingsFilterableAttributes , InvalidRequest , BAD_REQUEST ; -InvalidSettingsPagination , InvalidRequest , BAD_REQUEST ; -InvalidSettingsSearchCutoffMs , InvalidRequest , BAD_REQUEST ; -InvalidSettingsEmbedders , InvalidRequest , BAD_REQUEST ; -InvalidSettingsRankingRules , InvalidRequest , BAD_REQUEST ; -InvalidSettingsSearchableAttributes , InvalidRequest , BAD_REQUEST ; -InvalidSettingsSortableAttributes , InvalidRequest , BAD_REQUEST ; -InvalidSettingsStopWords , InvalidRequest , BAD_REQUEST ; -InvalidSettingsNonSeparatorTokens , InvalidRequest , BAD_REQUEST ; -InvalidSettingsSeparatorTokens , InvalidRequest , BAD_REQUEST ; -InvalidSettingsDictionary , InvalidRequest , BAD_REQUEST ; -InvalidSettingsSynonyms , InvalidRequest , BAD_REQUEST ; -InvalidSettingsTypoTolerance , InvalidRequest , BAD_REQUEST ; -InvalidSettingsLocalizedAttributes , InvalidRequest , BAD_REQUEST ; -InvalidState , Internal , INTERNAL_SERVER_ERROR ; -InvalidStoreFile , Internal , INTERNAL_SERVER_ERROR ; -InvalidSwapDuplicateIndexFound , InvalidRequest , BAD_REQUEST ; -InvalidSwapIndexes , InvalidRequest , BAD_REQUEST ; -InvalidTaskAfterEnqueuedAt , InvalidRequest , BAD_REQUEST ; -InvalidTaskAfterFinishedAt , InvalidRequest , BAD_REQUEST ; -InvalidTaskAfterStartedAt , InvalidRequest , BAD_REQUEST ; -InvalidTaskBeforeEnqueuedAt , InvalidRequest , BAD_REQUEST ; -InvalidTaskBeforeFinishedAt , InvalidRequest , BAD_REQUEST ; -InvalidTaskBeforeStartedAt , InvalidRequest , BAD_REQUEST ; -InvalidTaskCanceledBy , InvalidRequest , BAD_REQUEST ; -InvalidTaskFrom , InvalidRequest , BAD_REQUEST ; -InvalidTaskLimit , InvalidRequest , BAD_REQUEST ; -InvalidTaskReverse , InvalidRequest , BAD_REQUEST ; -InvalidTaskStatuses , InvalidRequest , BAD_REQUEST ; -InvalidTaskTypes , InvalidRequest , BAD_REQUEST ; -InvalidTaskUids , InvalidRequest , BAD_REQUEST ; -InvalidBatchUids , InvalidRequest , BAD_REQUEST ; -IoError , System , UNPROCESSABLE_ENTITY; -FeatureNotEnabled , InvalidRequest , BAD_REQUEST ; -MalformedPayload , InvalidRequest , BAD_REQUEST ; -MaxFieldsLimitExceeded , InvalidRequest , BAD_REQUEST ; -MissingApiKeyActions , InvalidRequest , BAD_REQUEST ; -MissingApiKeyExpiresAt , InvalidRequest , BAD_REQUEST ; -MissingApiKeyIndexes , InvalidRequest , BAD_REQUEST ; -MissingAuthorizationHeader , Auth , UNAUTHORIZED ; -MissingContentType , InvalidRequest , UNSUPPORTED_MEDIA_TYPE ; -MissingDocumentId , InvalidRequest , BAD_REQUEST ; -MissingFacetSearchFacetName , InvalidRequest , BAD_REQUEST ; -MissingIndexUid , InvalidRequest , BAD_REQUEST ; -MissingMasterKey , Auth , UNAUTHORIZED ; -MissingNetworkUrl , InvalidRequest , BAD_REQUEST ; -MissingPayload , InvalidRequest , BAD_REQUEST ; -MissingSearchHybrid , InvalidRequest , BAD_REQUEST ; -MissingSwapIndexes , InvalidRequest , BAD_REQUEST ; -MissingTaskFilters , InvalidRequest , BAD_REQUEST ; -NoSpaceLeftOnDevice , System , UNPROCESSABLE_ENTITY; -PayloadTooLarge , InvalidRequest , PAYLOAD_TOO_LARGE ; -RemoteBadResponse , System , BAD_GATEWAY ; -RemoteBadRequest , InvalidRequest , BAD_REQUEST ; -RemoteCouldNotSendRequest , System , BAD_GATEWAY ; -RemoteInvalidApiKey , Auth , FORBIDDEN ; -RemoteRemoteError , System , BAD_GATEWAY ; -RemoteTimeout , System , BAD_GATEWAY ; -TooManySearchRequests , System , SERVICE_UNAVAILABLE ; -TaskNotFound , InvalidRequest , NOT_FOUND ; -TaskFileNotFound , InvalidRequest , NOT_FOUND ; -BatchNotFound , InvalidRequest , NOT_FOUND ; -TooManyOpenFiles , System , UNPROCESSABLE_ENTITY ; -TooManyVectors , InvalidRequest , BAD_REQUEST ; -UnretrievableDocument , Internal , BAD_REQUEST ; -UnretrievableErrorCode , InvalidRequest , BAD_REQUEST ; -UnsupportedMediaType , InvalidRequest , UNSUPPORTED_MEDIA_TYPE ; +ApiKeyAlreadyExists , InvalidRequest , CONFLICT ; +ApiKeyNotFound , InvalidRequest , NOT_FOUND ; +BadParameter , InvalidRequest , BAD_REQUEST; +BadRequest , InvalidRequest , BAD_REQUEST; +DatabaseSizeLimitReached , Internal , INTERNAL_SERVER_ERROR; +DocumentNotFound , InvalidRequest , NOT_FOUND; +DumpAlreadyProcessing , InvalidRequest , CONFLICT; +DumpNotFound , InvalidRequest , NOT_FOUND; +DumpProcessFailed , Internal , INTERNAL_SERVER_ERROR; +DuplicateIndexFound , InvalidRequest , BAD_REQUEST; +ImmutableApiKeyActions , InvalidRequest , BAD_REQUEST; +ImmutableApiKeyCreatedAt , InvalidRequest , BAD_REQUEST; +ImmutableApiKeyExpiresAt , InvalidRequest , BAD_REQUEST; +ImmutableApiKeyIndexes , InvalidRequest , BAD_REQUEST; +ImmutableApiKeyKey , InvalidRequest , BAD_REQUEST; +ImmutableApiKeyUid , InvalidRequest , BAD_REQUEST; +ImmutableApiKeyUpdatedAt , InvalidRequest , BAD_REQUEST; +ImmutableIndexCreatedAt , InvalidRequest , BAD_REQUEST; +ImmutableIndexUid , InvalidRequest , BAD_REQUEST; +ImmutableIndexUpdatedAt , InvalidRequest , BAD_REQUEST; +IndexAlreadyExists , InvalidRequest , CONFLICT ; +IndexCreationFailed , Internal , INTERNAL_SERVER_ERROR; +IndexNotFound , InvalidRequest , NOT_FOUND; +IndexPrimaryKeyAlreadyExists , InvalidRequest , BAD_REQUEST ; +IndexPrimaryKeyMultipleCandidatesFound , InvalidRequest , BAD_REQUEST; +IndexPrimaryKeyNoCandidateFound , InvalidRequest , BAD_REQUEST ; +Internal , Internal , INTERNAL_SERVER_ERROR ; +InvalidApiKey , Auth , FORBIDDEN ; +InvalidApiKeyActions , InvalidRequest , BAD_REQUEST ; +InvalidApiKeyDescription , InvalidRequest , BAD_REQUEST ; +InvalidApiKeyExpiresAt , InvalidRequest , BAD_REQUEST ; +InvalidApiKeyIndexes , InvalidRequest , BAD_REQUEST ; +InvalidApiKeyLimit , InvalidRequest , BAD_REQUEST ; +InvalidApiKeyName , InvalidRequest , BAD_REQUEST ; +InvalidApiKeyOffset , InvalidRequest , BAD_REQUEST ; +InvalidApiKeyUid , InvalidRequest , BAD_REQUEST ; +InvalidContentType , InvalidRequest , UNSUPPORTED_MEDIA_TYPE ; +InvalidDocumentCsvDelimiter , InvalidRequest , BAD_REQUEST ; +InvalidDocumentFields , InvalidRequest , BAD_REQUEST ; +InvalidDocumentRetrieveVectors , InvalidRequest , BAD_REQUEST ; +MissingDocumentFilter , InvalidRequest , BAD_REQUEST ; +MissingDocumentEditionFunction , InvalidRequest , BAD_REQUEST ; +InvalidDocumentFilter , InvalidRequest , BAD_REQUEST ; +InvalidDocumentGeoField , InvalidRequest , BAD_REQUEST ; +InvalidVectorDimensions , InvalidRequest , BAD_REQUEST ; +InvalidVectorsType , InvalidRequest , BAD_REQUEST ; +InvalidDocumentId , InvalidRequest , BAD_REQUEST ; +InvalidDocumentIds , InvalidRequest , BAD_REQUEST ; +InvalidDocumentLimit , InvalidRequest , BAD_REQUEST ; +InvalidDocumentOffset , InvalidRequest , BAD_REQUEST ; +InvalidSearchEmbedder , InvalidRequest , BAD_REQUEST ; +InvalidSimilarEmbedder , InvalidRequest , BAD_REQUEST ; +InvalidSearchHybridQuery , InvalidRequest , BAD_REQUEST ; +InvalidIndexLimit , InvalidRequest , BAD_REQUEST ; +InvalidIndexOffset , InvalidRequest , BAD_REQUEST ; +InvalidIndexPrimaryKey , InvalidRequest , BAD_REQUEST ; +InvalidIndexUid , InvalidRequest , BAD_REQUEST ; +InvalidMultiSearchFacets , InvalidRequest , BAD_REQUEST ; +InvalidMultiSearchFacetsByIndex , InvalidRequest , BAD_REQUEST ; +InvalidMultiSearchFacetOrder , InvalidRequest , BAD_REQUEST ; +InvalidMultiSearchFederated , InvalidRequest , BAD_REQUEST ; +InvalidMultiSearchFederationOptions , InvalidRequest , BAD_REQUEST ; +InvalidMultiSearchMaxValuesPerFacet , InvalidRequest , BAD_REQUEST ; +InvalidMultiSearchMergeFacets , InvalidRequest , BAD_REQUEST ; +InvalidMultiSearchQueryFacets , InvalidRequest , BAD_REQUEST ; +InvalidMultiSearchQueryPagination , InvalidRequest , BAD_REQUEST ; +InvalidMultiSearchQueryRankingRules , InvalidRequest , BAD_REQUEST ; +InvalidMultiSearchQueryPosition , InvalidRequest , BAD_REQUEST ; +InvalidMultiSearchRemote , InvalidRequest , BAD_REQUEST ; +InvalidMultiSearchWeight , InvalidRequest , BAD_REQUEST ; +InvalidNetworkRemotes , InvalidRequest , BAD_REQUEST ; +InvalidNetworkSelf , InvalidRequest , BAD_REQUEST ; +InvalidNetworkSearchApiKey , InvalidRequest , BAD_REQUEST ; +InvalidNetworkUrl , InvalidRequest , BAD_REQUEST ; +InvalidSearchAttributesToSearchOn , InvalidRequest , BAD_REQUEST ; +InvalidSearchAttributesToCrop , InvalidRequest , BAD_REQUEST ; +InvalidSearchAttributesToHighlight , InvalidRequest , BAD_REQUEST ; +InvalidSimilarAttributesToRetrieve , InvalidRequest , BAD_REQUEST ; +InvalidSimilarRetrieveVectors , InvalidRequest , BAD_REQUEST ; +InvalidSearchAttributesToRetrieve , InvalidRequest , BAD_REQUEST ; +InvalidSearchRankingScoreThreshold , InvalidRequest , BAD_REQUEST ; +InvalidSimilarRankingScoreThreshold , InvalidRequest , BAD_REQUEST ; +InvalidSearchRetrieveVectors , InvalidRequest , BAD_REQUEST ; +InvalidSearchCropLength , InvalidRequest , BAD_REQUEST ; +InvalidSearchCropMarker , InvalidRequest , BAD_REQUEST ; +InvalidSearchFacets , InvalidRequest , BAD_REQUEST ; +InvalidSearchSemanticRatio , InvalidRequest , BAD_REQUEST ; +InvalidSearchLocales , InvalidRequest , BAD_REQUEST ; +InvalidFacetSearchExhaustiveFacetCount , InvalidRequest , BAD_REQUEST ; +InvalidFacetSearchFacetName , InvalidRequest , BAD_REQUEST ; +InvalidSimilarId , InvalidRequest , BAD_REQUEST ; +InvalidSearchFilter , InvalidRequest , BAD_REQUEST ; +InvalidSimilarFilter , InvalidRequest , BAD_REQUEST ; +InvalidSearchHighlightPostTag , InvalidRequest , BAD_REQUEST ; +InvalidSearchHighlightPreTag , InvalidRequest , BAD_REQUEST ; +InvalidSearchHitsPerPage , InvalidRequest , BAD_REQUEST ; +InvalidSimilarLimit , InvalidRequest , BAD_REQUEST ; +InvalidSearchLimit , InvalidRequest , BAD_REQUEST ; +InvalidSearchMatchingStrategy , InvalidRequest , BAD_REQUEST ; +InvalidSimilarOffset , InvalidRequest , BAD_REQUEST ; +InvalidSearchOffset , InvalidRequest , BAD_REQUEST ; +InvalidSearchPage , InvalidRequest , BAD_REQUEST ; +InvalidSearchQ , InvalidRequest , BAD_REQUEST ; +InvalidFacetSearchQuery , InvalidRequest , BAD_REQUEST ; +InvalidFacetSearchName , InvalidRequest , BAD_REQUEST ; +FacetSearchDisabled , InvalidRequest , BAD_REQUEST ; +InvalidSearchVector , InvalidRequest , BAD_REQUEST ; +InvalidSearchShowMatchesPosition , InvalidRequest , BAD_REQUEST ; +InvalidSearchShowRankingScore , InvalidRequest , BAD_REQUEST ; +InvalidSimilarShowRankingScore , InvalidRequest , BAD_REQUEST ; +InvalidSearchShowRankingScoreDetails , InvalidRequest , BAD_REQUEST ; +InvalidSimilarShowRankingScoreDetails , InvalidRequest , BAD_REQUEST ; +InvalidSearchSort , InvalidRequest , BAD_REQUEST ; +InvalidSearchDistinct , InvalidRequest , BAD_REQUEST ; +InvalidSettingsDisplayedAttributes , InvalidRequest , BAD_REQUEST ; +InvalidSettingsDistinctAttribute , InvalidRequest , BAD_REQUEST ; +InvalidSettingsProximityPrecision , InvalidRequest , BAD_REQUEST ; +InvalidSettingsFacetSearch , InvalidRequest , BAD_REQUEST ; +InvalidSettingsPrefixSearch , InvalidRequest , BAD_REQUEST ; +InvalidSettingsFaceting , InvalidRequest , BAD_REQUEST ; +InvalidSettingsFilterableAttributes , InvalidRequest , BAD_REQUEST ; +InvalidSettingsPagination , InvalidRequest , BAD_REQUEST ; +InvalidSettingsSearchCutoffMs , InvalidRequest , BAD_REQUEST ; +InvalidSettingsEmbedders , InvalidRequest , BAD_REQUEST ; +InvalidSettingsRankingRules , InvalidRequest , BAD_REQUEST ; +InvalidSettingsSearchableAttributes , InvalidRequest , BAD_REQUEST ; +InvalidSettingsSortableAttributes , InvalidRequest , BAD_REQUEST ; +InvalidSettingsStopWords , InvalidRequest , BAD_REQUEST ; +InvalidSettingsNonSeparatorTokens , InvalidRequest , BAD_REQUEST ; +InvalidSettingsSeparatorTokens , InvalidRequest , BAD_REQUEST ; +InvalidSettingsDictionary , InvalidRequest , BAD_REQUEST ; +InvalidSettingsSynonyms , InvalidRequest , BAD_REQUEST ; +InvalidSettingsTypoTolerance , InvalidRequest , BAD_REQUEST ; +InvalidSettingsLocalizedAttributes , InvalidRequest , BAD_REQUEST ; +InvalidState , Internal , INTERNAL_SERVER_ERROR ; +InvalidStoreFile , Internal , INTERNAL_SERVER_ERROR ; +InvalidSwapDuplicateIndexFound , InvalidRequest , BAD_REQUEST ; +InvalidSwapIndexes , InvalidRequest , BAD_REQUEST ; +InvalidTaskAfterEnqueuedAt , InvalidRequest , BAD_REQUEST ; +InvalidTaskAfterFinishedAt , InvalidRequest , BAD_REQUEST ; +InvalidTaskAfterStartedAt , InvalidRequest , BAD_REQUEST ; +InvalidTaskBeforeEnqueuedAt , InvalidRequest , BAD_REQUEST ; +InvalidTaskBeforeFinishedAt , InvalidRequest , BAD_REQUEST ; +InvalidTaskBeforeStartedAt , InvalidRequest , BAD_REQUEST ; +InvalidTaskCanceledBy , InvalidRequest , BAD_REQUEST ; +InvalidTaskFrom , InvalidRequest , BAD_REQUEST ; +InvalidTaskLimit , InvalidRequest , BAD_REQUEST ; +InvalidTaskReverse , InvalidRequest , BAD_REQUEST ; +InvalidTaskStatuses , InvalidRequest , BAD_REQUEST ; +InvalidTaskTypes , InvalidRequest , BAD_REQUEST ; +InvalidTaskUids , InvalidRequest , BAD_REQUEST ; +InvalidBatchUids , InvalidRequest , BAD_REQUEST ; +IoError , System , UNPROCESSABLE_ENTITY; +FeatureNotEnabled , InvalidRequest , BAD_REQUEST ; +MalformedPayload , InvalidRequest , BAD_REQUEST ; +MaxFieldsLimitExceeded , InvalidRequest , BAD_REQUEST ; +MissingApiKeyActions , InvalidRequest , BAD_REQUEST ; +MissingApiKeyExpiresAt , InvalidRequest , BAD_REQUEST ; +MissingApiKeyIndexes , InvalidRequest , BAD_REQUEST ; +MissingAuthorizationHeader , Auth , UNAUTHORIZED ; +MissingContentType , InvalidRequest , UNSUPPORTED_MEDIA_TYPE ; +MissingDocumentId , InvalidRequest , BAD_REQUEST ; +MissingFacetSearchFacetName , InvalidRequest , BAD_REQUEST ; +MissingIndexUid , InvalidRequest , BAD_REQUEST ; +MissingMasterKey , Auth , UNAUTHORIZED ; +MissingNetworkUrl , InvalidRequest , BAD_REQUEST ; +MissingPayload , InvalidRequest , BAD_REQUEST ; +MissingSearchHybrid , InvalidRequest , BAD_REQUEST ; +MissingSwapIndexes , InvalidRequest , BAD_REQUEST ; +MissingTaskFilters , InvalidRequest , BAD_REQUEST ; +NoSpaceLeftOnDevice , System , UNPROCESSABLE_ENTITY; +PayloadTooLarge , InvalidRequest , PAYLOAD_TOO_LARGE ; +RemoteBadResponse , System , BAD_GATEWAY ; +RemoteBadRequest , InvalidRequest , BAD_REQUEST ; +RemoteCouldNotSendRequest , System , BAD_GATEWAY ; +RemoteInvalidApiKey , Auth , FORBIDDEN ; +RemoteRemoteError , System , BAD_GATEWAY ; +RemoteTimeout , System , BAD_GATEWAY ; +TooManySearchRequests , System , SERVICE_UNAVAILABLE ; +TaskNotFound , InvalidRequest , NOT_FOUND ; +TaskFileNotFound , InvalidRequest , NOT_FOUND ; +BatchNotFound , InvalidRequest , NOT_FOUND ; +TooManyOpenFiles , System , UNPROCESSABLE_ENTITY ; +TooManyVectors , InvalidRequest , BAD_REQUEST ; +UnretrievableDocument , Internal , BAD_REQUEST ; +UnretrievableErrorCode , InvalidRequest , BAD_REQUEST ; +UnsupportedMediaType , InvalidRequest , UNSUPPORTED_MEDIA_TYPE ; // Experimental features -VectorEmbeddingError , InvalidRequest , BAD_REQUEST ; -NotFoundSimilarId , InvalidRequest , BAD_REQUEST ; -InvalidDocumentEditionContext , InvalidRequest , BAD_REQUEST ; -InvalidDocumentEditionFunctionFilter , InvalidRequest , BAD_REQUEST ; -EditDocumentsByFunctionError , InvalidRequest , BAD_REQUEST ; -InvalidSettingsIndexChat , InvalidRequest , BAD_REQUEST +VectorEmbeddingError , InvalidRequest , BAD_REQUEST ; +NotFoundSimilarId , InvalidRequest , BAD_REQUEST ; +InvalidDocumentEditionContext , InvalidRequest , BAD_REQUEST ; +InvalidDocumentEditionFunctionFilter , InvalidRequest , BAD_REQUEST ; +EditDocumentsByFunctionError , InvalidRequest , BAD_REQUEST ; +InvalidSettingsIndexChat , InvalidRequest , BAD_REQUEST ; +// Experimental features - Chat Completions +ChatWorkspaceNotFound , InvalidRequest , NOT_FOUND ; +InvalidChatCompletionSource , InvalidRequest , BAD_REQUEST ; +InvalidChatCompletionBaseApi , InvalidRequest , BAD_REQUEST ; +InvalidChatCompletionApiKey , InvalidRequest , BAD_REQUEST ; +InvalidChatCompletionPrompts , InvalidRequest , BAD_REQUEST ; +InvalidChatCompletionSystemPrompt , InvalidRequest , BAD_REQUEST ; +InvalidChatCompletionSearchDescriptionPrompt , InvalidRequest , BAD_REQUEST ; +InvalidChatCompletionSearchQueryParamPrompt , InvalidRequest , BAD_REQUEST ; +InvalidChatCompletionSearchIndexUidParamPrompt , InvalidRequest , BAD_REQUEST ; +InvalidChatCompletionPreQueryPrompt , InvalidRequest , BAD_REQUEST } impl ErrorCode for JoinError { diff --git a/crates/meilisearch/src/routes/chats/mod.rs b/crates/meilisearch/src/routes/chats/mod.rs index 00e3b28b5..86feb14aa 100644 --- a/crates/meilisearch/src/routes/chats/mod.rs +++ b/crates/meilisearch/src/routes/chats/mod.rs @@ -23,7 +23,6 @@ use crate::{ use super::Pagination; -// TODO supports chats/$workspace/settings + /chats/$workspace/chat/completions pub mod chat_completions; pub mod settings; diff --git a/crates/meilisearch/src/routes/chats/settings.rs b/crates/meilisearch/src/routes/chats/settings.rs index 6354066be..68ad2d45e 100644 --- a/crates/meilisearch/src/routes/chats/settings.rs +++ b/crates/meilisearch/src/routes/chats/settings.rs @@ -1,6 +1,9 @@ use actix_web::web::{self, Data}; use actix_web::HttpResponse; +use deserr::Deserr; use index_scheduler::IndexScheduler; +use meilisearch_types::deserr::DeserrJsonError; +use meilisearch_types::error::deserr_codes::*; use meilisearch_types::error::{Code, ResponseError}; use meilisearch_types::features::{ ChatCompletionPrompts as DbChatCompletionPrompts, ChatCompletionSettings, @@ -11,6 +14,7 @@ use meilisearch_types::features::{ use meilisearch_types::keys::actions; use meilisearch_types::milli::update::Setting; use serde::{Deserialize, Serialize}; +use utoipa::ToSchema; use crate::extractors::authentication::policies::ActionPolicy; use crate::extractors::authentication::GuardedData; @@ -154,8 +158,32 @@ async fn delete_settings( } } -#[derive(Default, Debug, Clone, Copy, Serialize, Deserialize)] +#[derive(Debug, Clone, Deserialize, Deserr, ToSchema)] +#[deserr(error = DeserrJsonError, rename_all = camelCase, deny_unknown_fields)] #[serde(deny_unknown_fields, rename_all = "camelCase")] +#[schema(rename_all = "camelCase")] +pub struct GlobalChatSettings { + #[serde(default)] + #[deserr(default)] + #[schema(value_type = Option)] + pub source: Setting, + #[serde(default)] + #[deserr(default, error = DeserrJsonError)] + #[schema(value_type = Option, example = json!("https://api.mistral.ai/v1"))] + pub base_api: Setting, + #[serde(default)] + #[deserr(default, error = DeserrJsonError)] + #[schema(value_type = Option, example = json!("abcd1234..."))] + pub api_key: Setting, + #[serde(default)] + #[deserr(default)] + #[schema(inline, value_type = Option)] + pub prompts: Setting, +} + +#[derive(Default, Debug, Clone, Copy, Serialize, Deserialize, Deserr, ToSchema)] +#[serde(deny_unknown_fields, rename_all = "camelCase")] +#[deserr(error = DeserrJsonError, rename_all = camelCase, deny_unknown_fields)] pub enum ChatCompletionSource { #[default] OpenAi, @@ -169,80 +197,29 @@ impl From for DbChatCompletionSource { } } -// TODO Implement Deserr on that. -#[derive(Debug, Clone, Deserialize)] -#[serde(deny_unknown_fields, rename_all = "camelCase")] -pub struct GlobalChatSettings { - #[serde(default)] - pub source: Setting, - #[serde(default)] - pub base_api: Setting, - #[serde(default)] - pub api_key: Setting, - #[serde(default)] - pub prompts: Setting, -} - -// TODO Implement Deserr on that. -// TODO Declare DbChatPrompts (alias it). -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Deserialize, Deserr, ToSchema)] +#[deserr(error = DeserrJsonError, rename_all = camelCase, deny_unknown_fields)] #[serde(deny_unknown_fields, rename_all = "camelCase")] +#[schema(rename_all = "camelCase")] pub struct ChatPrompts { - #[serde(default, skip_serializing_if = "Setting::is_not_set")] + #[serde(default)] + #[deserr(default, error = DeserrJsonError)] + #[schema(value_type = Option, example = json!("You are a helpful assistant..."))] pub system: Setting, - #[serde(default, skip_serializing_if = "Setting::is_not_set")] + #[serde(default)] + #[deserr(default, error = DeserrJsonError)] + #[schema(value_type = Option, example = json!("This is the search function..."))] pub search_description: Setting, - #[serde(default, skip_serializing_if = "Setting::is_not_set")] + #[serde(default)] + #[deserr(default, error = DeserrJsonError)] + #[schema(value_type = Option, example = json!("This is query parameter..."))] pub search_q_param: Setting, - #[serde(default, skip_serializing_if = "Setting::is_not_set")] + #[serde(default)] + #[deserr(default, error = DeserrJsonError)] + #[schema(value_type = Option, example = json!("This is index you want to search in..."))] pub search_index_uid_param: Setting, - #[serde(default, skip_serializing_if = "Setting::is_not_set")] + #[serde(default)] + #[deserr(default, error = DeserrJsonError)] + #[schema(value_type = Option)] pub pre_query: Setting, } - -const DEFAULT_SYSTEM_MESSAGE: &str = "You are a highly capable research assistant with access to powerful search tools. IMPORTANT INSTRUCTIONS:\ - 1. When answering questions, you MUST make multiple tool calls (at least 2-3) to gather comprehensive information.\ - 2. Use different search queries for each tool call - vary keywords, rephrase questions, and explore different semantic angles to ensure broad coverage.\ - 3. Always explicitly announce BEFORE making each tool call by saying: \"I'll search for [specific information] now.\"\ - 4. Combine information from ALL tool calls to provide complete, nuanced answers rather than relying on a single source.\ - 5. For complex topics, break down your research into multiple targeted queries rather than using a single generic search."; - -/// The default description of the searchInIndex tool provided to OpenAI. -const DEFAULT_SEARCH_IN_INDEX_TOOL_DESCRIPTION: &str = - "Search the database for relevant JSON documents using an optional query."; -/// The default description of the searchInIndex `q` parameter tool provided to OpenAI. -const DEFAULT_SEARCH_IN_INDEX_Q_PARAMETER_TOOL_DESCRIPTION: &str = - "The search query string used to find relevant documents in the index. \ -This should contain keywords or phrases that best represent what the user is looking for. \ -More specific queries will yield more precise results."; -/// The default description of the searchInIndex `index` parameter tool provided to OpenAI. -const DEFAULT_SEARCH_IN_INDEX_INDEX_PARAMETER_TOOL_DESCRIPTION: &str = -"The name of the index to search within. An index is a collection of documents organized for search. \ -Selecting the right index ensures the most relevant results for the user query"; - -impl Default for GlobalChatSettings { - fn default() -> Self { - GlobalChatSettings { - source: Setting::NotSet, - base_api: Setting::NotSet, - api_key: Setting::NotSet, - prompts: Setting::Set(ChatPrompts::default()), - } - } -} - -impl Default for ChatPrompts { - fn default() -> Self { - ChatPrompts { - system: Setting::Set(DEFAULT_SYSTEM_MESSAGE.to_string()), - search_description: Setting::Set(DEFAULT_SEARCH_IN_INDEX_TOOL_DESCRIPTION.to_string()), - search_q_param: Setting::Set( - DEFAULT_SEARCH_IN_INDEX_Q_PARAMETER_TOOL_DESCRIPTION.to_string(), - ), - search_index_uid_param: Setting::Set( - DEFAULT_SEARCH_IN_INDEX_INDEX_PARAMETER_TOOL_DESCRIPTION.to_string(), - ), - pre_query: Setting::Set(Default::default()), - } - } -} From bed442528f554d162f243d6d4124711c9d7cbf6f Mon Sep 17 00:00:00 2001 From: ManyTheFish Date: Tue, 13 May 2025 16:22:33 +0200 Subject: [PATCH 049/103] Update charabia v0.9.4 --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4a21d2ab6..a7bebe2c6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6067,9 +6067,9 @@ dependencies = [ [[package]] name = "tokio" -version = "1.45.1" +version = "1.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75ef51a33ef1da925cea3e4eb122833cb377c61439ca401b770f54902b806779" +checksum = "2513ca694ef9ede0fb23fe71a4ee4107cb102b9dc1930f6d0fd77aae068ae165" dependencies = [ "backtrace", "bytes", From 0efb72fe66f41505d10a88a70d0f8a19f49154e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Renault?= Date: Tue, 13 May 2025 11:19:32 +0200 Subject: [PATCH 050/103] Introduce the first version of the /chat route that mimics the OpenAI API --- Cargo.lock | 4 ++-- crates/meilisearch-types/src/keys.rs | 1 + crates/meilisearch/src/routes/chat.rs | 32 +++++++++++++++++++++++++++ 3 files changed, 35 insertions(+), 2 deletions(-) create mode 100644 crates/meilisearch/src/routes/chat.rs diff --git a/Cargo.lock b/Cargo.lock index a7bebe2c6..4a21d2ab6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6067,9 +6067,9 @@ dependencies = [ [[package]] name = "tokio" -version = "1.45.0" +version = "1.45.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2513ca694ef9ede0fb23fe71a4ee4107cb102b9dc1930f6d0fd77aae068ae165" +checksum = "75ef51a33ef1da925cea3e4eb122833cb377c61439ca401b770f54902b806779" dependencies = [ "backtrace", "bytes", diff --git a/crates/meilisearch-types/src/keys.rs b/crates/meilisearch-types/src/keys.rs index e30ef1008..4fd1842ad 100644 --- a/crates/meilisearch-types/src/keys.rs +++ b/crates/meilisearch-types/src/keys.rs @@ -389,6 +389,7 @@ impl Action { EXPERIMENTAL_FEATURES_UPDATE => Some(Self::ExperimentalFeaturesUpdate), NETWORK_GET => Some(Self::NetworkGet), NETWORK_UPDATE => Some(Self::NetworkUpdate), + CHAT_GET => Some(Self::ChatGet), _otherwise => None, } } diff --git a/crates/meilisearch/src/routes/chat.rs b/crates/meilisearch/src/routes/chat.rs new file mode 100644 index 000000000..1cb813acd --- /dev/null +++ b/crates/meilisearch/src/routes/chat.rs @@ -0,0 +1,32 @@ +use actix_web::web::{self, Data}; +use actix_web::HttpResponse; +use async_openai::config::OpenAIConfig; +use async_openai::types::CreateChatCompletionRequest; +use async_openai::Client; +use index_scheduler::IndexScheduler; +use meilisearch_types::error::ResponseError; +use meilisearch_types::keys::actions; + +use crate::extractors::authentication::policies::ActionPolicy; +use crate::extractors::authentication::GuardedData; + +pub fn configure(cfg: &mut web::ServiceConfig) { + cfg.service(web::resource("").route(web::post().to(chat))); +} + +/// Get a chat completion +async fn chat( + _index_scheduler: GuardedData, Data>, + web::Json(chat_completion): web::Json, +) -> Result { + // To enable later on, when the feature will be experimental + // index_scheduler.features().check_chat("Using the /chat route")?; + + let api_key = std::env::var("MEILI_OPENAI_API_KEY") + .expect("cannot find OpenAI API Key (MEILI_OPENAI_API_KEY)"); + let config = OpenAIConfig::default().with_api_key(&api_key); // we can also change the API base + let client = Client::with_config(config); + let response = client.chat().create(chat_completion).await.unwrap(); + + Ok(HttpResponse::Ok().json(response)) +} From ae135d1d46b12f3745941828b7e18e6a81d27301 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Renault?= Date: Wed, 14 May 2025 11:18:21 +0200 Subject: [PATCH 051/103] Implement a first version of a streamed chat API --- crates/meilisearch/src/routes/chat.rs | 275 +++++++++++++++++++++++++- 1 file changed, 267 insertions(+), 8 deletions(-) diff --git a/crates/meilisearch/src/routes/chat.rs b/crates/meilisearch/src/routes/chat.rs index 1cb813acd..ad46d91c8 100644 --- a/crates/meilisearch/src/routes/chat.rs +++ b/crates/meilisearch/src/routes/chat.rs @@ -1,14 +1,50 @@ +use std::mem; + use actix_web::web::{self, Data}; -use actix_web::HttpResponse; +use actix_web::{Either, HttpResponse, Responder}; +use actix_web_lab::sse::{self, Event}; use async_openai::config::OpenAIConfig; -use async_openai::types::CreateChatCompletionRequest; +use async_openai::types::{ + ChatCompletionRequestAssistantMessageArgs, ChatCompletionRequestMessage, + ChatCompletionRequestToolMessage, ChatCompletionRequestToolMessageContent, + ChatCompletionToolArgs, ChatCompletionToolType, CreateChatCompletionRequest, FinishReason, + FunctionObjectArgs, +}; use async_openai::Client; +use futures::StreamExt; use index_scheduler::IndexScheduler; use meilisearch_types::error::ResponseError; use meilisearch_types::keys::actions; +use meilisearch_types::milli::index::IndexEmbeddingConfig; +use meilisearch_types::milli::prompt::PromptData; +use meilisearch_types::milli::vector::EmbeddingConfig; +use meilisearch_types::{Document, Index}; +use serde::{Deserialize, Serialize}; +use serde_json::json; use crate::extractors::authentication::policies::ActionPolicy; use crate::extractors::authentication::GuardedData; +use crate::metrics::MEILISEARCH_DEGRADED_SEARCH_REQUESTS; +use crate::routes::indexes::search::search_kind; +use crate::search::{ + add_search_rules, perform_search, HybridQuery, RetrieveVectors, SearchQuery, SemanticRatio, +}; +use crate::search_queue::SearchQueue; + +/// The default description of the searchInIndex tool provided to OpenAI. +const DEFAULT_SEARCH_IN_INDEX_TOOL_DESCRIPTION: &str = + "Search the database for relevant JSON documents using an optional query."; +/// The default description of the searchInIndex `q` parameter tool provided to OpenAI. +const DEFAULT_SEARCH_IN_INDEX_Q_PARAMETER_TOOL_DESCRIPTION: &str = + "The search query string used to find relevant documents in the index. \ +This should contain keywords or phrases that best represent what the user is looking for. \ +More specific queries will yield more precise results."; +/// The default description of the searchInIndex `index` parameter tool provided to OpenAI. +const DEFAULT_SEARCH_IN_INDEX_INDEX_PARAMETER_TOOL_DESCRIPTION: &str = +"The name of the index to search within. An index is a collection of documents organized for search. \ +Selecting the right index ensures the most relevant results for the user query"; + +const EMBEDDER_NAME: &str = "openai"; pub fn configure(cfg: &mut web::ServiceConfig) { cfg.service(web::resource("").route(web::post().to(chat))); @@ -16,17 +52,240 @@ pub fn configure(cfg: &mut web::ServiceConfig) { /// Get a chat completion async fn chat( - _index_scheduler: GuardedData, Data>, - web::Json(chat_completion): web::Json, -) -> Result { + index_scheduler: GuardedData, Data>, + search_queue: web::Data, + web::Json(mut chat_completion): web::Json, +) -> impl Responder { // To enable later on, when the feature will be experimental // index_scheduler.features().check_chat("Using the /chat route")?; + if chat_completion.stream.unwrap_or(false) { + Either::Right(streamed_chat(index_scheduler, search_queue, chat_completion).await) + } else { + Either::Left(non_streamed_chat(index_scheduler, search_queue, chat_completion).await) + } +} + +async fn non_streamed_chat( + index_scheduler: GuardedData, Data>, + search_queue: web::Data, + mut chat_completion: CreateChatCompletionRequest, +) -> Result { + let api_key = std::env::var("MEILI_OPENAI_API_KEY") + .expect("cannot find OpenAI API Key (MEILI_OPENAI_API_KEY)"); + let config = OpenAIConfig::default().with_api_key(&api_key); // we can also change the API base + let client = Client::with_config(config); + + assert_eq!( + chat_completion.n.unwrap_or(1), + 1, + "Meilisearch /chat only support one completion at a time (n = 1, n = null)" + ); + + let rtxn = index_scheduler.read_txn().unwrap(); + let search_in_index_description = index_scheduler + .chat_prompts(&rtxn, "searchInIndex-description") + .unwrap() + .unwrap_or(DEFAULT_SEARCH_IN_INDEX_TOOL_DESCRIPTION) + .to_string(); + let search_in_index_q_param_description = index_scheduler + .chat_prompts(&rtxn, "searchInIndex-q-param-description") + .unwrap() + .unwrap_or(DEFAULT_SEARCH_IN_INDEX_Q_PARAMETER_TOOL_DESCRIPTION) + .to_string(); + let search_in_index_index_description = index_scheduler + .chat_prompts(&rtxn, "searchInIndex-index-param-description") + .unwrap() + .unwrap_or(DEFAULT_SEARCH_IN_INDEX_INDEX_PARAMETER_TOOL_DESCRIPTION) + .to_string(); + drop(rtxn); + + let mut response; + loop { + let tools = chat_completion.tools.get_or_insert_default(); + tools.push( + ChatCompletionToolArgs::default() + .r#type(ChatCompletionToolType::Function) + .function( + FunctionObjectArgs::default() + .name("searchInIndex") + .description(&search_in_index_description) + .parameters(json!({ + "type": "object", + "properties": { + "index_uid": { + "type": "string", + "enum": ["main"], + "description": search_in_index_index_description, + }, + "q": { + "type": ["string", "null"], + "description": search_in_index_q_param_description, + } + }, + "required": ["index_uid", "q"], + "additionalProperties": false, + })) + .strict(true) + .build() + .unwrap(), + ) + .build() + .unwrap(), + ); + response = client.chat().create(chat_completion.clone()).await.unwrap(); + + let choice = &mut response.choices[0]; + match choice.finish_reason { + Some(FinishReason::ToolCalls) => { + let tool_calls = mem::take(&mut choice.message.tool_calls).unwrap_or_default(); + + let (meili_calls, other_calls): (Vec<_>, Vec<_>) = + tool_calls.into_iter().partition(|call| call.function.name == "searchInIndex"); + + chat_completion.messages.push( + ChatCompletionRequestAssistantMessageArgs::default() + .tool_calls(meili_calls.clone()) + .build() + .unwrap() + .into(), + ); + + for call in meili_calls { + let SearchInIndexParameters { index_uid, q } = + serde_json::from_str(&call.function.arguments).unwrap(); + + let mut query = SearchQuery { + q, + hybrid: Some(HybridQuery { + semantic_ratio: SemanticRatio::default(), + embedder: EMBEDDER_NAME.to_string(), + }), + limit: 20, + ..Default::default() + }; + + // Tenant token search_rules. + if let Some(search_rules) = + index_scheduler.filters().get_index_search_rules(&index_uid) + { + add_search_rules(&mut query.filter, search_rules); + } + + // TBD + // let mut aggregate = SearchAggregator::::from_query(&query); + + let index = index_scheduler.index(&index_uid)?; + let search_kind = search_kind( + &query, + index_scheduler.get_ref(), + index_uid.to_string(), + &index, + )?; + + let permit = search_queue.try_get_search_permit().await?; + let features = index_scheduler.features(); + let index_cloned = index.clone(); + let search_result = tokio::task::spawn_blocking(move || { + perform_search( + index_uid.to_string(), + &index_cloned, + query, + search_kind, + RetrieveVectors::new(false), + features, + ) + }) + .await; + permit.drop().await; + + let search_result = search_result?; + if let Ok(ref search_result) = search_result { + // aggregate.succeed(search_result); + if search_result.degraded { + MEILISEARCH_DEGRADED_SEARCH_REQUESTS.inc(); + } + } + // analytics.publish(aggregate, &req); + + let search_result = search_result?; + let formatted = format_documents( + &index, + search_result.hits.into_iter().map(|doc| doc.document), + ); + let text = formatted.join("\n"); + chat_completion.messages.push(ChatCompletionRequestMessage::Tool( + ChatCompletionRequestToolMessage { + tool_call_id: call.id, + content: ChatCompletionRequestToolMessageContent::Text(text), + }, + )); + } + + // Let the client call other tools by themselves + if !other_calls.is_empty() { + response.choices[0].message.tool_calls = Some(other_calls); + break; + } + } + _ => break, + } + } + + Ok(HttpResponse::Ok().json(response)) +} + +async fn streamed_chat( + index_scheduler: GuardedData, Data>, + search_queue: web::Data, + mut chat_completion: CreateChatCompletionRequest, +) -> impl Responder { + assert!(chat_completion.stream.unwrap_or(false)); + let api_key = std::env::var("MEILI_OPENAI_API_KEY") .expect("cannot find OpenAI API Key (MEILI_OPENAI_API_KEY)"); let config = OpenAIConfig::default().with_api_key(&api_key); // we can also change the API base let client = Client::with_config(config); - let response = client.chat().create(chat_completion).await.unwrap(); - - Ok(HttpResponse::Ok().json(response)) + let response = client.chat().create_stream(chat_completion).await.unwrap(); + actix_web_lab::sse::Sse::from_stream(response.map(|response| { + response + .map(|mut r| Event::Data(sse::Data::new_json(r.choices.pop().unwrap().delta).unwrap())) + })) +} + +#[derive(Deserialize)] +struct SearchInIndexParameters { + /// The index uid to search in. + index_uid: String, + /// The query parameter to use. + q: Option, +} + +fn format_documents(index: &Index, documents: impl Iterator) -> Vec { + let rtxn = index.read_txn().unwrap(); + let IndexEmbeddingConfig { name: _, config, user_provided: _ } = index + .embedding_configs(&rtxn) + .unwrap() + .into_iter() + .find(|conf| conf.name == EMBEDDER_NAME) + .unwrap(); + + let EmbeddingConfig { + embedder_options: _, + prompt: PromptData { template, max_bytes }, + quantized: _, + } = config; + + #[derive(Serialize)] + struct Doc { + doc: T, + } + + let template = liquid::ParserBuilder::with_stdlib().build().unwrap().parse(&template).unwrap(); + documents + .map(|doc| { + let object = liquid::to_object(&Doc { doc }).unwrap(); + template.render(&object).unwrap() + }) + .collect() } From 3b931e75d93364a73046722127ecbc76d35b608c Mon Sep 17 00:00:00 2001 From: Kerollmops Date: Fri, 30 May 2025 15:56:57 +0200 Subject: [PATCH 052/103] Make the chats settings and chat completions route experimental --- crates/index-scheduler/src/features.rs | 13 +++++++++++++ crates/meilisearch-types/src/features.rs | 1 + .../meilisearch/src/analytics/segment_analytics.rs | 3 +++ .../src/routes/chats/chat_completions.rs | 5 ++--- crates/meilisearch/src/routes/chats/mod.rs | 2 ++ crates/meilisearch/src/routes/chats/settings.rs | 5 +++++ crates/meilisearch/src/routes/features.rs | 11 +++++++++++ 7 files changed, 37 insertions(+), 3 deletions(-) diff --git a/crates/index-scheduler/src/features.rs b/crates/index-scheduler/src/features.rs index 109e6b867..78ffc0766 100644 --- a/crates/index-scheduler/src/features.rs +++ b/crates/index-scheduler/src/features.rs @@ -131,6 +131,19 @@ impl RoFeatures { .into()) } } + + pub fn check_chat_completions(&self, disabled_action: &'static str) -> Result<()> { + if self.runtime.chat_completions { + Ok(()) + } else { + Err(FeatureNotEnabledError { + disabled_action, + feature: "chat completions", + issue_link: "https://github.com/orgs/meilisearch/discussions/835", + } + .into()) + } + } } impl FeatureData { diff --git a/crates/meilisearch-types/src/features.rs b/crates/meilisearch-types/src/features.rs index f12cbcfc9..10c39e09c 100644 --- a/crates/meilisearch-types/src/features.rs +++ b/crates/meilisearch-types/src/features.rs @@ -19,6 +19,7 @@ pub struct RuntimeTogglableFeatures { pub network: bool, pub get_task_documents_route: bool, pub composite_embedders: bool, + pub chat_completions: bool, } #[derive(Default, Debug, Clone, Copy)] diff --git a/crates/meilisearch/src/analytics/segment_analytics.rs b/crates/meilisearch/src/analytics/segment_analytics.rs index ee8a9ee20..c7e0634f4 100644 --- a/crates/meilisearch/src/analytics/segment_analytics.rs +++ b/crates/meilisearch/src/analytics/segment_analytics.rs @@ -197,6 +197,7 @@ struct Infos { experimental_max_number_of_batched_tasks: usize, experimental_limit_batched_tasks_total_size: u64, experimental_network: bool, + experimental_chat_completions: bool, experimental_get_task_documents_route: bool, experimental_composite_embedders: bool, experimental_embedding_cache_entries: usize, @@ -296,6 +297,7 @@ impl Infos { network, get_task_documents_route, composite_embedders, + chat_completions, } = features; // We're going to override every sensible information. @@ -314,6 +316,7 @@ impl Infos { experimental_enable_logs_route: experimental_enable_logs_route | logs_route, experimental_reduce_indexing_memory_usage, experimental_network: network, + experimental_chat_completions: chat_completions, experimental_get_task_documents_route: get_task_documents_route, experimental_composite_embedders: composite_embedders, experimental_embedding_cache_entries, diff --git a/crates/meilisearch/src/routes/chats/chat_completions.rs b/crates/meilisearch/src/routes/chats/chat_completions.rs index a923d2ff9..c82e29524 100644 --- a/crates/meilisearch/src/routes/chats/chat_completions.rs +++ b/crates/meilisearch/src/routes/chats/chat_completions.rs @@ -74,9 +74,6 @@ async fn chat( search_queue: web::Data, web::Json(chat_completion): web::Json, ) -> impl Responder { - // To enable later on, when the feature will be experimental - // index_scheduler.features().check_chat("Using the /chat route")?; - let ChatsParam { workspace_uid } = chats_param.into_inner(); assert_eq!( @@ -317,6 +314,7 @@ async fn non_streamed_chat( req: HttpRequest, mut chat_completion: CreateChatCompletionRequest, ) -> Result { + index_scheduler.features().check_chat_completions("Using the /chats chat completions route")?; let filters = index_scheduler.filters(); let rtxn = index_scheduler.read_txn()?; @@ -414,6 +412,7 @@ async fn streamed_chat( req: HttpRequest, mut chat_completion: CreateChatCompletionRequest, ) -> Result { + index_scheduler.features().check_chat_completions("Using the /chats chat completions route")?; let filters = index_scheduler.filters(); let rtxn = index_scheduler.read_txn()?; diff --git a/crates/meilisearch/src/routes/chats/mod.rs b/crates/meilisearch/src/routes/chats/mod.rs index 86feb14aa..0fa0d54b4 100644 --- a/crates/meilisearch/src/routes/chats/mod.rs +++ b/crates/meilisearch/src/routes/chats/mod.rs @@ -70,6 +70,8 @@ pub async fn list_workspaces( index_scheduler: GuardedData, Data>, paginate: AwebQueryParameter, ) -> Result { + index_scheduler.features().check_chat_completions("Using the /chats settings route")?; + debug!(parameters = ?paginate, "List chat workspaces"); let filters = index_scheduler.filters(); let (total, workspaces) = index_scheduler.paginated_chat_workspace_uids( diff --git a/crates/meilisearch/src/routes/chats/settings.rs b/crates/meilisearch/src/routes/chats/settings.rs index 68ad2d45e..e118a3ef1 100644 --- a/crates/meilisearch/src/routes/chats/settings.rs +++ b/crates/meilisearch/src/routes/chats/settings.rs @@ -38,6 +38,8 @@ async fn get_settings( >, chats_param: web::Path, ) -> Result { + index_scheduler.features().check_chat_completions("Using the /chats settings route")?; + let ChatsParam { workspace_uid } = chats_param.into_inner(); // TODO do a spawn_blocking here ??? @@ -63,6 +65,7 @@ async fn patch_settings( chats_param: web::Path, web::Json(new): web::Json, ) -> Result { + index_scheduler.features().check_chat_completions("Using the /chats settings route")?; let ChatsParam { workspace_uid } = chats_param.into_inner(); // TODO do a spawn_blocking here @@ -143,6 +146,8 @@ async fn delete_settings( >, chats_param: web::Path, ) -> Result { + index_scheduler.features().check_chat_completions("Using the /chats settings route")?; + let ChatsParam { workspace_uid } = chats_param.into_inner(); // TODO do a spawn_blocking here diff --git a/crates/meilisearch/src/routes/features.rs b/crates/meilisearch/src/routes/features.rs index eb8e7ac04..179b9cf68 100644 --- a/crates/meilisearch/src/routes/features.rs +++ b/crates/meilisearch/src/routes/features.rs @@ -53,6 +53,7 @@ pub fn configure(cfg: &mut web::ServiceConfig) { network: Some(false), get_task_documents_route: Some(false), composite_embedders: Some(false), + chat_completions: Some(false), })), (status = 401, description = "The authorization header is missing", body = ResponseError, content_type = "application/json", example = json!( { @@ -97,6 +98,8 @@ pub struct RuntimeTogglableFeatures { pub get_task_documents_route: Option, #[deserr(default)] pub composite_embedders: Option, + #[deserr(default)] + pub chat_completions: Option, } impl From for RuntimeTogglableFeatures { @@ -109,6 +112,7 @@ impl From for RuntimeTogg network, get_task_documents_route, composite_embedders, + chat_completions, } = value; Self { @@ -119,6 +123,7 @@ impl From for RuntimeTogg network: Some(network), get_task_documents_route: Some(get_task_documents_route), composite_embedders: Some(composite_embedders), + chat_completions: Some(chat_completions), } } } @@ -132,6 +137,7 @@ pub struct PatchExperimentalFeatureAnalytics { network: bool, get_task_documents_route: bool, composite_embedders: bool, + chat_completions: bool, } impl Aggregate for PatchExperimentalFeatureAnalytics { @@ -148,6 +154,7 @@ impl Aggregate for PatchExperimentalFeatureAnalytics { network: new.network, get_task_documents_route: new.get_task_documents_route, composite_embedders: new.composite_embedders, + chat_completions: new.chat_completions, }) } @@ -173,6 +180,7 @@ impl Aggregate for PatchExperimentalFeatureAnalytics { network: Some(false), get_task_documents_route: Some(false), composite_embedders: Some(false), + chat_completions: Some(false), })), (status = 401, description = "The authorization header is missing", body = ResponseError, content_type = "application/json", example = json!( { @@ -214,6 +222,7 @@ async fn patch_features( .0 .composite_embedders .unwrap_or(old_features.composite_embedders), + chat_completions: new_features.0.chat_completions.unwrap_or(old_features.chat_completions), }; // explicitly destructure for analytics rather than using the `Serialize` implementation, because @@ -227,6 +236,7 @@ async fn patch_features( network, get_task_documents_route, composite_embedders, + chat_completions, } = new_features; analytics.publish( @@ -238,6 +248,7 @@ async fn patch_features( network, get_task_documents_route, composite_embedders, + chat_completions, }, &req, ); From 87d2e213f34383d29c1c344c635ef31ea5f511f1 Mon Sep 17 00:00:00 2001 From: Kerollmops Date: Fri, 30 May 2025 16:03:48 +0200 Subject: [PATCH 053/103] Update chat keys --- crates/meilisearch-types/src/keys.rs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/crates/meilisearch-types/src/keys.rs b/crates/meilisearch-types/src/keys.rs index 4fd1842ad..a3f6ff046 100644 --- a/crates/meilisearch-types/src/keys.rs +++ b/crates/meilisearch-types/src/keys.rs @@ -324,12 +324,12 @@ pub enum Action { #[deserr(rename = "network.update")] NetworkUpdate, // TODO should we rename it chatCompletions.get ? - #[serde(rename = "chat.get")] - #[deserr(rename = "chat.get")] + #[serde(rename = "chat")] + #[deserr(rename = "chat")] Chat, #[serde(rename = "chats.get")] #[deserr(rename = "chats.get")] - Chats, + ChatsGet, #[serde(rename = "chatsSettings.*")] #[deserr(rename = "chatsSettings.*")] ChatsSettingsAll, @@ -368,7 +368,7 @@ impl Action { SETTINGS_GET => Some(Self::SettingsGet), SETTINGS_UPDATE => Some(Self::SettingsUpdate), CHAT => Some(Self::Chat), - CHATS_GET => Some(Self::Chats), + CHATS_GET => Some(Self::ChatsGet), CHATS_SETTINGS_ALL => Some(Self::ChatsSettingsAll), CHATS_SETTINGS_GET => Some(Self::ChatsSettingsGet), CHATS_SETTINGS_UPDATE => Some(Self::ChatsSettingsUpdate), @@ -389,7 +389,6 @@ impl Action { EXPERIMENTAL_FEATURES_UPDATE => Some(Self::ExperimentalFeaturesUpdate), NETWORK_GET => Some(Self::NetworkGet), NETWORK_UPDATE => Some(Self::NetworkUpdate), - CHAT_GET => Some(Self::ChatGet), _otherwise => None, } } @@ -440,7 +439,7 @@ pub mod actions { pub const NETWORK_UPDATE: u8 = NetworkUpdate.repr(); pub const CHAT: u8 = Chat.repr(); - pub const CHATS_GET: u8 = Chats.repr(); + pub const CHATS_GET: u8 = ChatsGet.repr(); pub const CHATS_SETTINGS_ALL: u8 = ChatsSettingsAll.repr(); pub const CHATS_SETTINGS_GET: u8 = ChatsSettingsGet.repr(); pub const CHATS_SETTINGS_UPDATE: u8 = ChatsSettingsUpdate.repr(); From f827c2442c01dc2cc967774e52eb1197d2cdb127 Mon Sep 17 00:00:00 2001 From: Kerollmops Date: Fri, 30 May 2025 16:37:16 +0200 Subject: [PATCH 054/103] Mark tool calls to be implemented later for non-streaming --- crates/meilisearch/src/routes/chats/chat_completions.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/crates/meilisearch/src/routes/chats/chat_completions.rs b/crates/meilisearch/src/routes/chats/chat_completions.rs index c82e29524..66fa12abf 100644 --- a/crates/meilisearch/src/routes/chats/chat_completions.rs +++ b/crates/meilisearch/src/routes/chats/chat_completions.rs @@ -338,7 +338,8 @@ async fn non_streamed_chat( let client = Client::with_config(config); let auth_token = extract_token_from_request(&req)?.unwrap(); - let FunctionSupport { report_progress, report_sources, append_to_conversation, report_errors } = + // TODO do function support later + let _function_support = setup_search_tool(&index_scheduler, filters, &mut chat_completion, &chat_settings.prompts)?; let mut response; @@ -377,8 +378,9 @@ async fn non_streamed_chat( Err(err) => Err(err.to_string()), }; + // TODO report documents sources later let text = match result { - Ok((_, documents, text)) => text, + Ok((_, _documents, text)) => text, Err(err) => err, }; From 201a808fe20935c50fdb5f8fc552760731b6d017 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Renault?= Date: Tue, 3 Jun 2025 14:07:38 +0200 Subject: [PATCH 055/103] Better report errors happening with the underlying LLM --- Cargo.lock | 4 +- crates/meilisearch/Cargo.toml | 2 +- .../src/routes/chats/chat_completions.rs | 226 +++++++++++++++--- 3 files changed, 200 insertions(+), 32 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4a21d2ab6..e310c967d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -476,7 +476,7 @@ dependencies = [ [[package]] name = "async-openai" version = "0.28.1" -source = "git+https://github.com/meilisearch/async-openai?branch=optional-type-function#603f1d17bb4530c45fb9a6e93294ab715a7af869" +source = "git+https://github.com/meilisearch/async-openai?branch=better-error-handling#42d05e5f7dd7cdd46115c0855965f0b3f24754a2" dependencies = [ "async-openai-macros", "backoff", @@ -501,7 +501,7 @@ dependencies = [ [[package]] name = "async-openai-macros" version = "0.1.0" -source = "git+https://github.com/meilisearch/async-openai?branch=optional-type-function#603f1d17bb4530c45fb9a6e93294ab715a7af869" +source = "git+https://github.com/meilisearch/async-openai?branch=better-error-handling#42d05e5f7dd7cdd46115c0855965f0b3f24754a2" dependencies = [ "proc-macro2", "quote", diff --git a/crates/meilisearch/Cargo.toml b/crates/meilisearch/Cargo.toml index 62f7cfa0a..deea9f803 100644 --- a/crates/meilisearch/Cargo.toml +++ b/crates/meilisearch/Cargo.toml @@ -113,7 +113,7 @@ utoipa = { version = "5.3.1", features = [ "openapi_extensions", ] } utoipa-scalar = { version = "0.3.0", optional = true, features = ["actix-web"] } -async-openai = { git = "https://github.com/meilisearch/async-openai", branch = "optional-type-function" } +async-openai = { git = "https://github.com/meilisearch/async-openai", branch = "better-error-handling" } actix-web-lab = { version = "0.24.1", default-features = false } [dev-dependencies] diff --git a/crates/meilisearch/src/routes/chats/chat_completions.rs b/crates/meilisearch/src/routes/chats/chat_completions.rs index 66fa12abf..e14ce3c2c 100644 --- a/crates/meilisearch/src/routes/chats/chat_completions.rs +++ b/crates/meilisearch/src/routes/chats/chat_completions.rs @@ -10,7 +10,8 @@ use actix_web::web::{self, Data}; use actix_web::{Either, HttpRequest, HttpResponse, Responder}; use actix_web_lab::sse::{self, Event, Sse}; use async_openai::config::{Config, OpenAIConfig}; -use async_openai::error::OpenAIError; +use async_openai::error::{ApiError, OpenAIError}; +use async_openai::reqwest_eventsource::Error as EventSourceError; use async_openai::types::{ ChatChoiceStream, ChatCompletionMessageToolCall, ChatCompletionMessageToolCallChunk, ChatCompletionRequestAssistantMessage, ChatCompletionRequestAssistantMessageArgs, @@ -45,6 +46,7 @@ use serde_json::json; use tokio::runtime::Handle; use tokio::sync::mpsc::error::SendError; use tokio::sync::mpsc::Sender; +use uuid::Uuid; use super::ChatsParam; use crate::error::MeilisearchHttpError; @@ -120,9 +122,6 @@ pub struct FunctionSupport { /// Defines if we can call the _meiliAppendConversationMessage /// function to provide the messages to append into the conversation. append_to_conversation: bool, - /// Defines if we can call the _meiliReportErrors function - /// to inform the front-end about potential errors. - report_errors: bool, } /// Setup search tool in chat completion request @@ -222,7 +221,7 @@ fn setup_search_tool( }), ); - Ok(FunctionSupport { report_progress, report_sources, append_to_conversation, report_errors }) + Ok(FunctionSupport { report_progress, report_sources, append_to_conversation }) } /// Process search request and return formatted results @@ -441,6 +440,8 @@ async fn streamed_chat( let function_support = setup_search_tool(&index_scheduler, filters, &mut chat_completion, &chat_settings.prompts)?; + tracing::debug!("Conversation function support: {function_support:?}"); + let (tx, rx) = tokio::sync::mpsc::channel(10); let tx = SseEventSender(tx); let _join_handle = Handle::current().spawn(async move { @@ -491,7 +492,7 @@ async fn run_conversation( function_support: FunctionSupport, ) -> Result, ()>, SendError> { let mut finish_reason = None; - + // safety: The unwrap can only happen if the stream is not correctly configured. let mut response = client.chat().create_stream(chat_completion.clone()).await.unwrap(); while let Some(result) = response.next().await { match result { @@ -590,10 +591,9 @@ async fn run_conversation( } } } - Err(err) => { - if function_support.report_errors { - tx.report_error(err).await?; - } + Err(error) => { + let error = StreamErrorEvent::from_openai_error(error).await.unwrap(); + tx.send_error(&error).await?; return Ok(ControlFlow::Break(None)); } } @@ -835,25 +835,6 @@ impl SseEventSender { self.send_json(&resp).await } - pub async fn report_error(&self, error: OpenAIError) -> Result<(), SendError> { - tracing::error!("OpenAI Error: {}", error); - - let (error_code, message) = match error { - OpenAIError::Reqwest(e) => ("internal_reqwest_error", e.to_string()), - OpenAIError::ApiError(api_error) => ("llm_api_issue", api_error.to_string()), - OpenAIError::JSONDeserialize(error) => ("internal_json_deserialize", error.to_string()), - OpenAIError::FileSaveError(_) | OpenAIError::FileReadError(_) => unreachable!(), - OpenAIError::StreamError(error) => ("llm_api_stream_error", error.to_string()), - OpenAIError::InvalidArgument(error) => ("internal_invalid_argument", error.to_string()), - }; - - self.send_json(&json!({ - "error_code": error_code, - "message": message, - })) - .await - } - pub async fn forward_response( &self, resp: &CreateChatCompletionStreamResponse, @@ -861,6 +842,10 @@ impl SseEventSender { self.send_json(resp).await } + pub async fn send_error(&self, error: &StreamErrorEvent) -> Result<(), SendError> { + self.send_json(error).await + } + pub async fn stop(self) -> Result<(), SendError> { self.0.send(Event::Data(sse::Data::new("[DONE]"))).await } @@ -941,3 +926,186 @@ fn format_documents<'t, 'doc>( Ok(renders) } + +/// An error that occurs during the streaming process. +/// +/// It directly comes from the OpenAI API and you can +/// read more about error events on their website: +/// +#[derive(Debug, Serialize, Deserialize)] +pub struct StreamErrorEvent { + /// The unique ID of the server event. + event_id: String, + /// The event type, must be error. + r#type: String, + /// Details of the error. + error: StreamError, +} + +/// Details of the error. +#[derive(Debug, Serialize, Deserialize)] +pub struct StreamError { + /// The type of error (e.g., "invalid_request_error", "server_error"). + r#type: String, + /// Error code, if any. + code: Option, + /// A human-readable error message. + message: String, + /// Parameter related to the error, if any. + param: Option, + /// The event_id of the client event that caused the error, if applicable. + event_id: Option, +} + +impl StreamErrorEvent { + pub async fn from_openai_error(error: OpenAIError) -> Result { + let error_type = "error".to_string(); + match error { + OpenAIError::Reqwest(e) => Ok(StreamErrorEvent { + event_id: Uuid::new_v4().to_string(), + r#type: error_type, + error: StreamError { + r#type: "internal_reqwest_error".to_string(), + code: Some("internal".to_string()), + message: e.to_string(), + param: None, + event_id: None, + }, + }), + OpenAIError::ApiError(ApiError { message, r#type, param, code }) => { + Ok(StreamErrorEvent { + r#type: error_type, + event_id: Uuid::new_v4().to_string(), + error: StreamError { + r#type: r#type.unwrap_or_else(|| "unknown".to_string()), + code, + message, + param, + event_id: None, + }, + }) + } + OpenAIError::JSONDeserialize(error) => Ok(StreamErrorEvent { + event_id: Uuid::new_v4().to_string(), + r#type: error_type, + error: StreamError { + r#type: "json_deserialize_error".to_string(), + code: Some("internal".to_string()), + message: error.to_string(), + param: None, + event_id: None, + }, + }), + OpenAIError::FileSaveError(_) | OpenAIError::FileReadError(_) => unreachable!(), + OpenAIError::StreamError(error) => match error { + EventSourceError::InvalidStatusCode(_status_code, response) => { + let OpenAiOutsideError { + error: OpenAiInnerError { code, message, param, r#type }, + } = response.json().await?; + + Ok(StreamErrorEvent { + event_id: Uuid::new_v4().to_string(), + r#type: error_type, + error: StreamError { r#type, code, message, param, event_id: None }, + }) + } + EventSourceError::InvalidContentType(_header_value, response) => { + let OpenAiOutsideError { + error: OpenAiInnerError { code, message, param, r#type }, + } = response.json().await?; + + Ok(StreamErrorEvent { + event_id: Uuid::new_v4().to_string(), + r#type: error_type, + error: StreamError { r#type, code, message, param, event_id: None }, + }) + } + EventSourceError::Utf8(error) => Ok(StreamErrorEvent { + event_id: Uuid::new_v4().to_string(), + r#type: error_type, + error: StreamError { + r#type: "invalid_utf8_error".to_string(), + code: None, + message: error.to_string(), + param: None, + event_id: None, + }, + }), + EventSourceError::Parser(error) => Ok(StreamErrorEvent { + event_id: Uuid::new_v4().to_string(), + r#type: error_type, + error: StreamError { + r#type: "parser_error".to_string(), + code: None, + message: error.to_string(), + param: None, + event_id: None, + }, + }), + EventSourceError::Transport(error) => Ok(StreamErrorEvent { + event_id: Uuid::new_v4().to_string(), + r#type: error_type, + error: StreamError { + r#type: "transport_error".to_string(), + code: None, + message: error.to_string(), + param: None, + event_id: None, + }, + }), + EventSourceError::InvalidLastEventId(message) => Ok(StreamErrorEvent { + event_id: Uuid::new_v4().to_string(), + r#type: error_type, + error: StreamError { + r#type: "invalid_last_event_id".to_string(), + code: None, + message, + param: None, + event_id: None, + }, + }), + EventSourceError::StreamEnded => Ok(StreamErrorEvent { + event_id: Uuid::new_v4().to_string(), + r#type: error_type, + error: StreamError { + r#type: "stream_ended".to_string(), + code: None, + message: "Stream ended".to_string(), + param: None, + event_id: None, + }, + }), + }, + OpenAIError::InvalidArgument(message) => Ok(StreamErrorEvent { + event_id: Uuid::new_v4().to_string(), + r#type: error_type, + error: StreamError { + r#type: "invalid_argument".to_string(), + code: None, + message, + param: None, + event_id: None, + }, + }), + } + } +} + +#[derive(Debug, Clone, Deserialize)] +pub struct OpenAiOutsideError { + /// Emitted when an error occurs. + error: OpenAiInnerError, +} + +/// Emitted when an error occurs. +#[derive(Debug, Clone, Deserialize)] +pub struct OpenAiInnerError { + /// The error code. + code: Option, + /// The error message. + message: String, + /// The error parameter. + param: Option, + /// The type of the event. Always `error`. + r#type: String, +} From 7d574433b603a115fe3d1805a97a1321023cb0fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Renault?= Date: Tue, 3 Jun 2025 14:28:01 +0200 Subject: [PATCH 056/103] Clean up chat completions modules a bit --- .../src/routes/chats/chat_completions.rs | 450 +----------------- crates/meilisearch/src/routes/chats/errors.rs | 187 ++++++++ crates/meilisearch/src/routes/chats/mod.rs | 41 +- crates/meilisearch/src/routes/chats/utils.rs | 243 ++++++++++ 4 files changed, 472 insertions(+), 449 deletions(-) create mode 100644 crates/meilisearch/src/routes/chats/errors.rs create mode 100644 crates/meilisearch/src/routes/chats/utils.rs diff --git a/crates/meilisearch/src/routes/chats/chat_completions.rs b/crates/meilisearch/src/routes/chats/chat_completions.rs index e14ce3c2c..ed8df3c8b 100644 --- a/crates/meilisearch/src/routes/chats/chat_completions.rs +++ b/crates/meilisearch/src/routes/chats/chat_completions.rs @@ -1,26 +1,21 @@ -use std::cell::RefCell; use std::collections::HashMap; use std::fmt::Write as _; use std::mem; use std::ops::ControlFlow; -use std::sync::RwLock; use std::time::Duration; use actix_web::web::{self, Data}; use actix_web::{Either, HttpRequest, HttpResponse, Responder}; -use actix_web_lab::sse::{self, Event, Sse}; +use actix_web_lab::sse::{Event, Sse}; use async_openai::config::{Config, OpenAIConfig}; -use async_openai::error::{ApiError, OpenAIError}; -use async_openai::reqwest_eventsource::Error as EventSourceError; use async_openai::types::{ - ChatChoiceStream, ChatCompletionMessageToolCall, ChatCompletionMessageToolCallChunk, - ChatCompletionRequestAssistantMessage, ChatCompletionRequestAssistantMessageArgs, - ChatCompletionRequestMessage, ChatCompletionRequestSystemMessage, - ChatCompletionRequestSystemMessageContent, ChatCompletionRequestToolMessage, - ChatCompletionRequestToolMessageContent, ChatCompletionStreamResponseDelta, - ChatCompletionToolArgs, ChatCompletionToolType, CreateChatCompletionRequest, - CreateChatCompletionStreamResponse, FinishReason, FunctionCall, FunctionCallStream, - FunctionObjectArgs, Role, + ChatCompletionMessageToolCall, ChatCompletionMessageToolCallChunk, + ChatCompletionRequestAssistantMessageArgs, ChatCompletionRequestMessage, + ChatCompletionRequestSystemMessage, ChatCompletionRequestSystemMessageContent, + ChatCompletionRequestToolMessage, ChatCompletionRequestToolMessageContent, + ChatCompletionStreamResponseDelta, ChatCompletionToolArgs, ChatCompletionToolType, + CreateChatCompletionRequest, CreateChatCompletionStreamResponse, FinishReason, FunctionCall, + FunctionCallStream, FunctionObjectArgs, }; use async_openai::Client; use bumpalo::Bump; @@ -31,38 +26,30 @@ use meilisearch_types::error::{Code, ResponseError}; use meilisearch_types::features::{ ChatCompletionPrompts as DbChatCompletionPrompts, ChatCompletionSettings as DbChatSettings, }; -use meilisearch_types::heed::RoTxn; use meilisearch_types::keys::actions; use meilisearch_types::milli::index::ChatConfig; -use meilisearch_types::milli::prompt::{Prompt, PromptData}; -use meilisearch_types::milli::update::new::document::DocumentFromDb; -use meilisearch_types::milli::{ - all_obkv_to_json, obkv_to_json, DocumentId, FieldIdMapWithMetadata, GlobalFieldsIdsMap, - MetadataBuilder, TimeBudget, -}; +use meilisearch_types::milli::{all_obkv_to_json, obkv_to_json, TimeBudget}; use meilisearch_types::{Document, Index}; -use serde::{Deserialize, Serialize}; +use serde::Deserialize; use serde_json::json; use tokio::runtime::Handle; use tokio::sync::mpsc::error::SendError; -use tokio::sync::mpsc::Sender; -use uuid::Uuid; -use super::ChatsParam; +use super::errors::StreamErrorEvent; +use super::utils::format_documents; +use super::{ + ChatsParam, MEILI_APPEND_CONVERSATION_MESSAGE_NAME, MEILI_SEARCH_IN_INDEX_FUNCTION_NAME, + MEILI_SEARCH_PROGRESS_NAME, MEILI_SEARCH_SOURCES_NAME, +}; use crate::error::MeilisearchHttpError; use crate::extractors::authentication::policies::ActionPolicy; use crate::extractors::authentication::{extract_token_from_request, GuardedData, Policy as _}; use crate::metrics::MEILISEARCH_DEGRADED_SEARCH_REQUESTS; +use crate::routes::chats::utils::SseEventSender; use crate::routes::indexes::search::search_kind; use crate::search::{add_search_rules, prepare_search, search_from_kind, SearchQuery}; use crate::search_queue::SearchQueue; -const MEILI_SEARCH_PROGRESS_NAME: &str = "_meiliSearchProgress"; -const MEILI_APPEND_CONVERSATION_MESSAGE_NAME: &str = "_meiliAppendConversationMessage"; -const MEILI_SEARCH_SOURCES_NAME: &str = "_meiliSearchSources"; -const MEILI_REPORT_ERRORS_NAME: &str = "_meiliReportErrors"; -const MEILI_SEARCH_IN_INDEX_FUNCTION_NAME: &str = "_meiliSearchInIndex"; - pub fn configure(cfg: &mut web::ServiceConfig) { cfg.service(web::resource("").route(web::post().to(chat))); } @@ -140,7 +127,6 @@ fn setup_search_tool( let mut report_progress = false; let mut report_sources = false; let mut append_to_conversation = false; - let mut report_errors = false; tools.retain(|tool| { match tool.function.name.as_str() { MEILI_SEARCH_PROGRESS_NAME => { @@ -155,10 +141,6 @@ fn setup_search_tool( append_to_conversation = true; false } - MEILI_REPORT_ERRORS_NAME => { - report_errors = true; - false - } _ => true, // keep other tools } }); @@ -443,7 +425,7 @@ async fn streamed_chat( tracing::debug!("Conversation function support: {function_support:?}"); let (tx, rx) = tokio::sync::mpsc::channel(10); - let tx = SseEventSender(tx); + let tx = SseEventSender::new(tx); let _join_handle = Handle::current().spawn(async move { let client = Client::with_config(config.clone()); let mut global_tool_calls = HashMap::::new(); @@ -521,9 +503,7 @@ async fn run_conversation( } }) .or_insert_with(|| { - if name - .as_ref() - .map_or(false, |n| n == MEILI_SEARCH_IN_INDEX_FUNCTION_NAME) + if name.as_deref() == Some(MEILI_SEARCH_IN_INDEX_FUNCTION_NAME) { Call::Internal { id: id.as_ref().unwrap().clone(), @@ -680,181 +660,6 @@ async fn handle_meili_tools( Ok(()) } -pub struct SseEventSender(Sender); - -impl SseEventSender { - /// Ask the front-end user to append this tool *call* to the conversation - pub async fn append_tool_call_conversation_message( - &self, - resp: CreateChatCompletionStreamResponse, - call_id: String, - function_name: String, - function_arguments: String, - ) -> Result<(), SendError> { - #[allow(deprecated)] - let message = - ChatCompletionRequestMessage::Assistant(ChatCompletionRequestAssistantMessage { - content: None, - refusal: None, - name: None, - audio: None, - tool_calls: Some(vec![ChatCompletionMessageToolCall { - id: call_id, - r#type: Some(ChatCompletionToolType::Function), - function: FunctionCall { name: function_name, arguments: function_arguments }, - }]), - function_call: None, - }); - - self.append_conversation_message(resp, &message).await - } - - /// Ask the front-end user to append this tool to the conversation - pub async fn append_conversation_message( - &self, - mut resp: CreateChatCompletionStreamResponse, - message: &ChatCompletionRequestMessage, - ) -> Result<(), SendError> { - let call_text = serde_json::to_string(message).unwrap(); - let tool_call = ChatCompletionMessageToolCallChunk { - index: 0, - id: Some(uuid::Uuid::new_v4().to_string()), - r#type: Some(ChatCompletionToolType::Function), - function: Some(FunctionCallStream { - name: Some(MEILI_APPEND_CONVERSATION_MESSAGE_NAME.to_string()), - arguments: Some(call_text), - }), - }; - - resp.choices[0] = ChatChoiceStream { - index: 0, - #[allow(deprecated)] - delta: ChatCompletionStreamResponseDelta { - content: None, - function_call: None, - tool_calls: Some(vec![tool_call]), - role: Some(Role::Assistant), - refusal: None, - }, - finish_reason: None, - logprobs: None, - }; - - self.send_json(&resp).await - } - - pub async fn report_search_progress( - &self, - mut resp: CreateChatCompletionStreamResponse, - call_id: &str, - function_name: &str, - function_arguments: &str, - ) -> Result<(), SendError> { - #[derive(Debug, Clone, Serialize)] - /// Provides information about the current Meilisearch search operation. - struct MeiliSearchProgress<'a> { - /// The call ID to track the sources of the search. - call_id: &'a str, - /// The name of the function we are executing. - function_name: &'a str, - /// The arguments of the function we are executing, encoded in JSON. - function_arguments: &'a str, - } - - let progress = MeiliSearchProgress { call_id, function_name, function_arguments }; - let call_text = serde_json::to_string(&progress).unwrap(); - let tool_call = ChatCompletionMessageToolCallChunk { - index: 0, - id: Some(uuid::Uuid::new_v4().to_string()), - r#type: Some(ChatCompletionToolType::Function), - function: Some(FunctionCallStream { - name: Some(MEILI_SEARCH_PROGRESS_NAME.to_string()), - arguments: Some(call_text), - }), - }; - - resp.choices[0] = ChatChoiceStream { - index: 0, - #[allow(deprecated)] - delta: ChatCompletionStreamResponseDelta { - content: None, - function_call: None, - tool_calls: Some(vec![tool_call]), - role: Some(Role::Assistant), - refusal: None, - }, - finish_reason: None, - logprobs: None, - }; - - self.send_json(&resp).await - } - - pub async fn report_sources( - &self, - mut resp: CreateChatCompletionStreamResponse, - call_id: &str, - documents: &[Document], - ) -> Result<(), SendError> { - #[derive(Debug, Clone, Serialize)] - /// Provides sources of the search. - struct MeiliSearchSources<'a> { - /// The call ID to track the original search associated to those sources. - call_id: &'a str, - /// The documents associated with the search (call_id). - /// Only the displayed attributes of the documents are returned. - sources: &'a [Document], - } - - let sources = MeiliSearchSources { call_id, sources: documents }; - let call_text = serde_json::to_string(&sources).unwrap(); - let tool_call = ChatCompletionMessageToolCallChunk { - index: 0, - id: Some(uuid::Uuid::new_v4().to_string()), - r#type: Some(ChatCompletionToolType::Function), - function: Some(FunctionCallStream { - name: Some(MEILI_SEARCH_SOURCES_NAME.to_string()), - arguments: Some(call_text), - }), - }; - - resp.choices[0] = ChatChoiceStream { - index: 0, - #[allow(deprecated)] - delta: ChatCompletionStreamResponseDelta { - content: None, - function_call: None, - tool_calls: Some(vec![tool_call]), - role: Some(Role::Assistant), - refusal: None, - }, - finish_reason: None, - logprobs: None, - }; - - self.send_json(&resp).await - } - - pub async fn forward_response( - &self, - resp: &CreateChatCompletionStreamResponse, - ) -> Result<(), SendError> { - self.send_json(resp).await - } - - pub async fn send_error(&self, error: &StreamErrorEvent) -> Result<(), SendError> { - self.send_json(error).await - } - - pub async fn stop(self) -> Result<(), SendError> { - self.0.send(Event::Data(sse::Data::new("[DONE]"))).await - } - - async fn send_json(&self, data: &S) -> Result<(), SendError> { - self.0.send(Event::Data(sse::Data::new_json(data).unwrap())).await - } -} - /// The structure used to aggregate the function calls to make. #[derive(Debug)] enum Call { @@ -892,220 +697,3 @@ struct SearchInIndexParameters { /// The query parameter to use. q: Option, } - -fn format_documents<'t, 'doc>( - rtxn: &RoTxn<'t>, - index: &Index, - doc_alloc: &'doc Bump, - internal_docids: Vec, -) -> Result, ResponseError> { - let ChatConfig { prompt: PromptData { template, max_bytes }, .. } = index.chat_config(rtxn)?; - - let prompt = Prompt::new(template, max_bytes).unwrap(); - let fid_map = index.fields_ids_map(rtxn)?; - let metadata_builder = MetadataBuilder::from_index(index, rtxn)?; - let fid_map_with_meta = FieldIdMapWithMetadata::new(fid_map.clone(), metadata_builder); - let global = RwLock::new(fid_map_with_meta); - let gfid_map = RefCell::new(GlobalFieldsIdsMap::new(&global)); - - let external_ids: Vec = index - .external_id_of(rtxn, internal_docids.iter().copied())? - .into_iter() - .collect::>()?; - - let mut renders = Vec::new(); - for (docid, external_docid) in internal_docids.into_iter().zip(external_ids) { - let document = match DocumentFromDb::new(docid, rtxn, index, &fid_map)? { - Some(doc) => doc, - None => continue, - }; - - let text = prompt.render_document(&external_docid, document, &gfid_map, doc_alloc).unwrap(); - renders.push(text); - } - - Ok(renders) -} - -/// An error that occurs during the streaming process. -/// -/// It directly comes from the OpenAI API and you can -/// read more about error events on their website: -/// -#[derive(Debug, Serialize, Deserialize)] -pub struct StreamErrorEvent { - /// The unique ID of the server event. - event_id: String, - /// The event type, must be error. - r#type: String, - /// Details of the error. - error: StreamError, -} - -/// Details of the error. -#[derive(Debug, Serialize, Deserialize)] -pub struct StreamError { - /// The type of error (e.g., "invalid_request_error", "server_error"). - r#type: String, - /// Error code, if any. - code: Option, - /// A human-readable error message. - message: String, - /// Parameter related to the error, if any. - param: Option, - /// The event_id of the client event that caused the error, if applicable. - event_id: Option, -} - -impl StreamErrorEvent { - pub async fn from_openai_error(error: OpenAIError) -> Result { - let error_type = "error".to_string(); - match error { - OpenAIError::Reqwest(e) => Ok(StreamErrorEvent { - event_id: Uuid::new_v4().to_string(), - r#type: error_type, - error: StreamError { - r#type: "internal_reqwest_error".to_string(), - code: Some("internal".to_string()), - message: e.to_string(), - param: None, - event_id: None, - }, - }), - OpenAIError::ApiError(ApiError { message, r#type, param, code }) => { - Ok(StreamErrorEvent { - r#type: error_type, - event_id: Uuid::new_v4().to_string(), - error: StreamError { - r#type: r#type.unwrap_or_else(|| "unknown".to_string()), - code, - message, - param, - event_id: None, - }, - }) - } - OpenAIError::JSONDeserialize(error) => Ok(StreamErrorEvent { - event_id: Uuid::new_v4().to_string(), - r#type: error_type, - error: StreamError { - r#type: "json_deserialize_error".to_string(), - code: Some("internal".to_string()), - message: error.to_string(), - param: None, - event_id: None, - }, - }), - OpenAIError::FileSaveError(_) | OpenAIError::FileReadError(_) => unreachable!(), - OpenAIError::StreamError(error) => match error { - EventSourceError::InvalidStatusCode(_status_code, response) => { - let OpenAiOutsideError { - error: OpenAiInnerError { code, message, param, r#type }, - } = response.json().await?; - - Ok(StreamErrorEvent { - event_id: Uuid::new_v4().to_string(), - r#type: error_type, - error: StreamError { r#type, code, message, param, event_id: None }, - }) - } - EventSourceError::InvalidContentType(_header_value, response) => { - let OpenAiOutsideError { - error: OpenAiInnerError { code, message, param, r#type }, - } = response.json().await?; - - Ok(StreamErrorEvent { - event_id: Uuid::new_v4().to_string(), - r#type: error_type, - error: StreamError { r#type, code, message, param, event_id: None }, - }) - } - EventSourceError::Utf8(error) => Ok(StreamErrorEvent { - event_id: Uuid::new_v4().to_string(), - r#type: error_type, - error: StreamError { - r#type: "invalid_utf8_error".to_string(), - code: None, - message: error.to_string(), - param: None, - event_id: None, - }, - }), - EventSourceError::Parser(error) => Ok(StreamErrorEvent { - event_id: Uuid::new_v4().to_string(), - r#type: error_type, - error: StreamError { - r#type: "parser_error".to_string(), - code: None, - message: error.to_string(), - param: None, - event_id: None, - }, - }), - EventSourceError::Transport(error) => Ok(StreamErrorEvent { - event_id: Uuid::new_v4().to_string(), - r#type: error_type, - error: StreamError { - r#type: "transport_error".to_string(), - code: None, - message: error.to_string(), - param: None, - event_id: None, - }, - }), - EventSourceError::InvalidLastEventId(message) => Ok(StreamErrorEvent { - event_id: Uuid::new_v4().to_string(), - r#type: error_type, - error: StreamError { - r#type: "invalid_last_event_id".to_string(), - code: None, - message, - param: None, - event_id: None, - }, - }), - EventSourceError::StreamEnded => Ok(StreamErrorEvent { - event_id: Uuid::new_v4().to_string(), - r#type: error_type, - error: StreamError { - r#type: "stream_ended".to_string(), - code: None, - message: "Stream ended".to_string(), - param: None, - event_id: None, - }, - }), - }, - OpenAIError::InvalidArgument(message) => Ok(StreamErrorEvent { - event_id: Uuid::new_v4().to_string(), - r#type: error_type, - error: StreamError { - r#type: "invalid_argument".to_string(), - code: None, - message, - param: None, - event_id: None, - }, - }), - } - } -} - -#[derive(Debug, Clone, Deserialize)] -pub struct OpenAiOutsideError { - /// Emitted when an error occurs. - error: OpenAiInnerError, -} - -/// Emitted when an error occurs. -#[derive(Debug, Clone, Deserialize)] -pub struct OpenAiInnerError { - /// The error code. - code: Option, - /// The error message. - message: String, - /// The error parameter. - param: Option, - /// The type of the event. Always `error`. - r#type: String, -} diff --git a/crates/meilisearch/src/routes/chats/errors.rs b/crates/meilisearch/src/routes/chats/errors.rs new file mode 100644 index 000000000..f1aa9722b --- /dev/null +++ b/crates/meilisearch/src/routes/chats/errors.rs @@ -0,0 +1,187 @@ +use async_openai::error::{ApiError, OpenAIError}; +use async_openai::reqwest_eventsource::Error as EventSourceError; +use serde::{Deserialize, Serialize}; +use uuid::Uuid; + +#[derive(Debug, Clone, Deserialize)] +pub struct OpenAiOutsideError { + /// Emitted when an error occurs. + error: OpenAiInnerError, +} + +/// Emitted when an error occurs. +#[derive(Debug, Clone, Deserialize)] +pub struct OpenAiInnerError { + /// The error code. + code: Option, + /// The error message. + message: String, + /// The error parameter. + param: Option, + /// The type of the event. Always `error`. + r#type: String, +} + +/// An error that occurs during the streaming process. +/// +/// It directly comes from the OpenAI API and you can +/// read more about error events on their website: +/// +#[derive(Debug, Serialize, Deserialize)] +pub struct StreamErrorEvent { + /// The unique ID of the server event. + pub event_id: String, + /// The event type, must be error. + pub r#type: String, + /// Details of the error. + pub error: StreamError, +} + +/// Details of the error. +#[derive(Debug, Serialize, Deserialize)] +pub struct StreamError { + /// The type of error (e.g., "invalid_request_error", "server_error"). + pub r#type: String, + /// Error code, if any. + pub code: Option, + /// A human-readable error message. + pub message: String, + /// Parameter related to the error, if any. + pub param: Option, + /// The event_id of the client event that caused the error, if applicable. + pub event_id: Option, +} + +impl StreamErrorEvent { + pub async fn from_openai_error(error: OpenAIError) -> Result { + let error_type = "error".to_string(); + match error { + OpenAIError::Reqwest(e) => Ok(StreamErrorEvent { + event_id: Uuid::new_v4().to_string(), + r#type: error_type, + error: StreamError { + r#type: "internal_reqwest_error".to_string(), + code: Some("internal".to_string()), + message: e.to_string(), + param: None, + event_id: None, + }, + }), + OpenAIError::ApiError(ApiError { message, r#type, param, code }) => { + Ok(StreamErrorEvent { + r#type: error_type, + event_id: Uuid::new_v4().to_string(), + error: StreamError { + r#type: r#type.unwrap_or_else(|| "unknown".to_string()), + code, + message, + param, + event_id: None, + }, + }) + } + OpenAIError::JSONDeserialize(error) => Ok(StreamErrorEvent { + event_id: Uuid::new_v4().to_string(), + r#type: error_type, + error: StreamError { + r#type: "json_deserialize_error".to_string(), + code: Some("internal".to_string()), + message: error.to_string(), + param: None, + event_id: None, + }, + }), + OpenAIError::FileSaveError(_) | OpenAIError::FileReadError(_) => unreachable!(), + OpenAIError::StreamError(error) => match error { + EventSourceError::InvalidStatusCode(_status_code, response) => { + let OpenAiOutsideError { + error: OpenAiInnerError { code, message, param, r#type }, + } = response.json().await?; + + Ok(StreamErrorEvent { + event_id: Uuid::new_v4().to_string(), + r#type: error_type, + error: StreamError { r#type, code, message, param, event_id: None }, + }) + } + EventSourceError::InvalidContentType(_header_value, response) => { + let OpenAiOutsideError { + error: OpenAiInnerError { code, message, param, r#type }, + } = response.json().await?; + + Ok(StreamErrorEvent { + event_id: Uuid::new_v4().to_string(), + r#type: error_type, + error: StreamError { r#type, code, message, param, event_id: None }, + }) + } + EventSourceError::Utf8(error) => Ok(StreamErrorEvent { + event_id: Uuid::new_v4().to_string(), + r#type: error_type, + error: StreamError { + r#type: "invalid_utf8_error".to_string(), + code: None, + message: error.to_string(), + param: None, + event_id: None, + }, + }), + EventSourceError::Parser(error) => Ok(StreamErrorEvent { + event_id: Uuid::new_v4().to_string(), + r#type: error_type, + error: StreamError { + r#type: "parser_error".to_string(), + code: None, + message: error.to_string(), + param: None, + event_id: None, + }, + }), + EventSourceError::Transport(error) => Ok(StreamErrorEvent { + event_id: Uuid::new_v4().to_string(), + r#type: error_type, + error: StreamError { + r#type: "transport_error".to_string(), + code: None, + message: error.to_string(), + param: None, + event_id: None, + }, + }), + EventSourceError::InvalidLastEventId(message) => Ok(StreamErrorEvent { + event_id: Uuid::new_v4().to_string(), + r#type: error_type, + error: StreamError { + r#type: "invalid_last_event_id".to_string(), + code: None, + message, + param: None, + event_id: None, + }, + }), + EventSourceError::StreamEnded => Ok(StreamErrorEvent { + event_id: Uuid::new_v4().to_string(), + r#type: error_type, + error: StreamError { + r#type: "stream_ended".to_string(), + code: None, + message: "Stream ended".to_string(), + param: None, + event_id: None, + }, + }), + }, + OpenAIError::InvalidArgument(message) => Ok(StreamErrorEvent { + event_id: Uuid::new_v4().to_string(), + r#type: error_type, + error: StreamError { + r#type: "invalid_argument".to_string(), + code: None, + message, + param: None, + event_id: None, + }, + }), + } + } +} diff --git a/crates/meilisearch/src/routes/chats/mod.rs b/crates/meilisearch/src/routes/chats/mod.rs index 0fa0d54b4..bb0476ab8 100644 --- a/crates/meilisearch/src/routes/chats/mod.rs +++ b/crates/meilisearch/src/routes/chats/mod.rs @@ -1,30 +1,35 @@ -use actix_web::{ - web::{self, Data}, - HttpResponse, -}; -use deserr::{actix_web::AwebQueryParameter, Deserr}; +use actix_web::web::{self, Data}; +use actix_web::HttpResponse; +use deserr::actix_web::AwebQueryParameter; +use deserr::Deserr; use index_scheduler::IndexScheduler; -use meilisearch_types::{ - deserr::{query_params::Param, DeserrQueryParamError}, - error::{ - deserr_codes::{InvalidIndexLimit, InvalidIndexOffset}, - ResponseError, - }, - keys::actions, -}; +use meilisearch_types::deserr::query_params::Param; +use meilisearch_types::deserr::DeserrQueryParamError; +use meilisearch_types::error::deserr_codes::{InvalidIndexLimit, InvalidIndexOffset}; +use meilisearch_types::error::ResponseError; +use meilisearch_types::keys::actions; use serde::{Deserialize, Serialize}; use tracing::debug; use utoipa::{IntoParams, ToSchema}; -use crate::{ - extractors::authentication::{policies::ActionPolicy, GuardedData}, - routes::PAGINATION_DEFAULT_LIMIT, -}; - use super::Pagination; +use crate::extractors::authentication::policies::ActionPolicy; +use crate::extractors::authentication::GuardedData; +use crate::routes::PAGINATION_DEFAULT_LIMIT; pub mod chat_completions; +mod errors; pub mod settings; +mod utils; + +/// The function name to report search progress. +const MEILI_SEARCH_PROGRESS_NAME: &str = "_meiliSearchProgress"; +/// The function name to append a conversation message in the user conversation. +const MEILI_APPEND_CONVERSATION_MESSAGE_NAME: &str = "_meiliAppendConversationMessage"; +/// The function name to report sources to the frontend. +const MEILI_SEARCH_SOURCES_NAME: &str = "_meiliSearchSources"; +/// The *internal* function name to provide to the LLM to search in indexes. +const MEILI_SEARCH_IN_INDEX_FUNCTION_NAME: &str = "_meiliSearchInIndex"; #[derive(Deserialize)] pub struct ChatsParam { diff --git a/crates/meilisearch/src/routes/chats/utils.rs b/crates/meilisearch/src/routes/chats/utils.rs new file mode 100644 index 000000000..424b4ea64 --- /dev/null +++ b/crates/meilisearch/src/routes/chats/utils.rs @@ -0,0 +1,243 @@ +use std::cell::RefCell; +use std::sync::RwLock; + +use actix_web_lab::sse::{self, Event}; +use async_openai::types::{ + ChatChoiceStream, ChatCompletionMessageToolCall, ChatCompletionMessageToolCallChunk, + ChatCompletionRequestAssistantMessage, ChatCompletionRequestMessage, + ChatCompletionStreamResponseDelta, ChatCompletionToolType, CreateChatCompletionStreamResponse, + FunctionCall, FunctionCallStream, Role, +}; +use bumpalo::Bump; +use meilisearch_types::error::ResponseError; +use meilisearch_types::heed::RoTxn; +use meilisearch_types::milli::index::ChatConfig; +use meilisearch_types::milli::prompt::{Prompt, PromptData}; +use meilisearch_types::milli::update::new::document::DocumentFromDb; +use meilisearch_types::milli::{ + DocumentId, FieldIdMapWithMetadata, GlobalFieldsIdsMap, MetadataBuilder, +}; +use meilisearch_types::{Document, Index}; +use serde::Serialize; +use tokio::sync::mpsc::error::SendError; +use tokio::sync::mpsc::Sender; + +use super::errors::StreamErrorEvent; +use super::MEILI_APPEND_CONVERSATION_MESSAGE_NAME; +use crate::routes::chats::{MEILI_SEARCH_PROGRESS_NAME, MEILI_SEARCH_SOURCES_NAME}; + +pub struct SseEventSender(Sender); + +impl SseEventSender { + pub fn new(sender: Sender) -> Self { + Self(sender) + } + + /// Ask the front-end user to append this tool *call* to the conversation + pub async fn append_tool_call_conversation_message( + &self, + resp: CreateChatCompletionStreamResponse, + call_id: String, + function_name: String, + function_arguments: String, + ) -> Result<(), SendError> { + #[allow(deprecated)] + let message = + ChatCompletionRequestMessage::Assistant(ChatCompletionRequestAssistantMessage { + content: None, + refusal: None, + name: None, + audio: None, + tool_calls: Some(vec![ChatCompletionMessageToolCall { + id: call_id, + r#type: Some(ChatCompletionToolType::Function), + function: FunctionCall { name: function_name, arguments: function_arguments }, + }]), + function_call: None, + }); + + self.append_conversation_message(resp, &message).await + } + + /// Ask the front-end user to append this tool to the conversation + pub async fn append_conversation_message( + &self, + mut resp: CreateChatCompletionStreamResponse, + message: &ChatCompletionRequestMessage, + ) -> Result<(), SendError> { + let call_text = serde_json::to_string(message).unwrap(); + let tool_call = ChatCompletionMessageToolCallChunk { + index: 0, + id: Some(uuid::Uuid::new_v4().to_string()), + r#type: Some(ChatCompletionToolType::Function), + function: Some(FunctionCallStream { + name: Some(MEILI_APPEND_CONVERSATION_MESSAGE_NAME.to_string()), + arguments: Some(call_text), + }), + }; + + resp.choices[0] = ChatChoiceStream { + index: 0, + #[allow(deprecated)] + delta: ChatCompletionStreamResponseDelta { + content: None, + function_call: None, + tool_calls: Some(vec![tool_call]), + role: Some(Role::Assistant), + refusal: None, + }, + finish_reason: None, + logprobs: None, + }; + + self.send_json(&resp).await + } + + pub async fn report_search_progress( + &self, + mut resp: CreateChatCompletionStreamResponse, + call_id: &str, + function_name: &str, + function_arguments: &str, + ) -> Result<(), SendError> { + #[derive(Debug, Clone, Serialize)] + /// Provides information about the current Meilisearch search operation. + struct MeiliSearchProgress<'a> { + /// The call ID to track the sources of the search. + call_id: &'a str, + /// The name of the function we are executing. + function_name: &'a str, + /// The arguments of the function we are executing, encoded in JSON. + function_arguments: &'a str, + } + + let progress = MeiliSearchProgress { call_id, function_name, function_arguments }; + let call_text = serde_json::to_string(&progress).unwrap(); + let tool_call = ChatCompletionMessageToolCallChunk { + index: 0, + id: Some(uuid::Uuid::new_v4().to_string()), + r#type: Some(ChatCompletionToolType::Function), + function: Some(FunctionCallStream { + name: Some(MEILI_SEARCH_PROGRESS_NAME.to_string()), + arguments: Some(call_text), + }), + }; + + resp.choices[0] = ChatChoiceStream { + index: 0, + #[allow(deprecated)] + delta: ChatCompletionStreamResponseDelta { + content: None, + function_call: None, + tool_calls: Some(vec![tool_call]), + role: Some(Role::Assistant), + refusal: None, + }, + finish_reason: None, + logprobs: None, + }; + + self.send_json(&resp).await + } + + pub async fn report_sources( + &self, + mut resp: CreateChatCompletionStreamResponse, + call_id: &str, + documents: &[Document], + ) -> Result<(), SendError> { + #[derive(Debug, Clone, Serialize)] + /// Provides sources of the search. + struct MeiliSearchSources<'a> { + /// The call ID to track the original search associated to those sources. + call_id: &'a str, + /// The documents associated with the search (call_id). + /// Only the displayed attributes of the documents are returned. + sources: &'a [Document], + } + + let sources = MeiliSearchSources { call_id, sources: documents }; + let call_text = serde_json::to_string(&sources).unwrap(); + let tool_call = ChatCompletionMessageToolCallChunk { + index: 0, + id: Some(uuid::Uuid::new_v4().to_string()), + r#type: Some(ChatCompletionToolType::Function), + function: Some(FunctionCallStream { + name: Some(MEILI_SEARCH_SOURCES_NAME.to_string()), + arguments: Some(call_text), + }), + }; + + resp.choices[0] = ChatChoiceStream { + index: 0, + #[allow(deprecated)] + delta: ChatCompletionStreamResponseDelta { + content: None, + function_call: None, + tool_calls: Some(vec![tool_call]), + role: Some(Role::Assistant), + refusal: None, + }, + finish_reason: None, + logprobs: None, + }; + + self.send_json(&resp).await + } + + pub async fn forward_response( + &self, + resp: &CreateChatCompletionStreamResponse, + ) -> Result<(), SendError> { + self.send_json(resp).await + } + + pub async fn send_error(&self, error: &StreamErrorEvent) -> Result<(), SendError> { + self.send_json(error).await + } + + pub async fn stop(self) -> Result<(), SendError> { + self.0.send(Event::Data(sse::Data::new("[DONE]"))).await + } + + async fn send_json(&self, data: &S) -> Result<(), SendError> { + self.0.send(Event::Data(sse::Data::new_json(data).unwrap())).await + } +} + +/// Format documents based on the provided template and maximum bytes. +/// +/// This formatting function is usually used to generate a summary of the documents for LLMs. +pub fn format_documents<'t, 'doc>( + rtxn: &RoTxn<'t>, + index: &Index, + doc_alloc: &'doc Bump, + internal_docids: Vec, +) -> Result, ResponseError> { + let ChatConfig { prompt: PromptData { template, max_bytes }, .. } = index.chat_config(rtxn)?; + + let prompt = Prompt::new(template, max_bytes).unwrap(); + let fid_map = index.fields_ids_map(rtxn)?; + let metadata_builder = MetadataBuilder::from_index(index, rtxn)?; + let fid_map_with_meta = FieldIdMapWithMetadata::new(fid_map.clone(), metadata_builder); + let global = RwLock::new(fid_map_with_meta); + let gfid_map = RefCell::new(GlobalFieldsIdsMap::new(&global)); + + let external_ids: Vec = index + .external_id_of(rtxn, internal_docids.iter().copied())? + .into_iter() + .collect::>()?; + + let mut renders = Vec::new(); + for (docid, external_docid) in internal_docids.into_iter().zip(external_ids) { + let document = match DocumentFromDb::new(docid, rtxn, index, &fid_map)? { + Some(doc) => doc, + None => continue, + }; + + let text = prompt.render_document(&external_docid, document, &gfid_map, doc_alloc).unwrap(); + renders.push(text); + } + + Ok(renders) +} From 3c218cc3a03df5774598a4530406628f01fe1124 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Renault?= Date: Tue, 3 Jun 2025 14:34:03 +0200 Subject: [PATCH 057/103] Update the default chat completions prompt Co-authored-by: Martin Grigorov --- crates/meilisearch-types/src/features.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/meilisearch-types/src/features.rs b/crates/meilisearch-types/src/features.rs index 10c39e09c..95706fb46 100644 --- a/crates/meilisearch-types/src/features.rs +++ b/crates/meilisearch-types/src/features.rs @@ -6,7 +6,7 @@ pub const DEFAULT_CHAT_SYSTEM_PROMPT: &str = "You are a highly capable research pub const DEFAULT_CHAT_SEARCH_DESCRIPTION_PROMPT: &str = "Search the database for relevant JSON documents using an optional query."; pub const DEFAULT_CHAT_SEARCH_Q_PARAM_PROMPT: &str = "The search query string used to find relevant documents in the index. This should contain keywords or phrases that best represent what the user is looking for. More specific queries will yield more precise results."; -pub const DEFAULT_CHAT_SEARCH_INDEX_UID_PARAM_PROMPT: &str = "The name of the index to search within. An index is a collection of documents organized for search. Selecting the right index ensures the most relevant results for the user query. You can access to two indexes: movies, steam. The movies index contains movies with overviews. The steam index contains steam games from the Steam platform with their prices"; +pub const DEFAULT_CHAT_SEARCH_INDEX_UID_PARAM_PROMPT: &str = "The name of the index to search within. An index is a collection of documents organized for search. Selecting the right index ensures the most relevant results for the user query. You have access to two indexes: movies, steam. The movies index contains movies with overviews. The steam index contains steam games from the Steam platform with their prices"; pub const DEFAULT_CHAT_PRE_QUERY_PROMPT: &str = ""; #[derive(Serialize, Deserialize, Debug, Clone, Copy, Default, PartialEq, Eq)] From 8fdcdee0ccc5d97ec253fc726c67a769a3f0b148 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Renault?= Date: Tue, 3 Jun 2025 14:47:39 +0200 Subject: [PATCH 058/103] Do a first clippy pass --- .../src/routes/chats/chat_completions.rs | 24 ++++++++++--------- .../meilisearch/src/routes/chats/settings.rs | 6 ++--- crates/meilisearch/src/routes/chats/utils.rs | 4 ++-- crates/meilisearch/src/search/mod.rs | 2 +- crates/milli/src/index.rs | 2 +- crates/milli/src/update/chat.rs | 5 ---- crates/milli/src/update/settings.rs | 6 ++--- 7 files changed, 22 insertions(+), 27 deletions(-) diff --git a/crates/meilisearch/src/routes/chats/chat_completions.rs b/crates/meilisearch/src/routes/chats/chat_completions.rs index ed8df3c8b..01e22d6f8 100644 --- a/crates/meilisearch/src/routes/chats/chat_completions.rs +++ b/crates/meilisearch/src/routes/chats/chat_completions.rs @@ -119,7 +119,7 @@ fn setup_search_tool( prompts: &DbChatCompletionPrompts, ) -> Result { let tools = chat_completion.tools.get_or_insert_default(); - if tools.iter().find(|t| t.function.name == MEILI_SEARCH_IN_INDEX_FUNCTION_NAME).is_some() { + if tools.iter().any(|t| t.function.name == MEILI_SEARCH_IN_INDEX_FUNCTION_NAME) { panic!("{MEILI_SEARCH_IN_INDEX_FUNCTION_NAME} function already set"); } @@ -149,7 +149,7 @@ fn setup_search_tool( let mut function_description = prompts.search_description.clone(); index_scheduler.try_for_each_index::<_, ()>(|name, index| { // Make sure to skip unauthorized indexes - if !filters.is_index_authorized(&name) { + if !filters.is_index_authorized(name) { return Ok(()); } @@ -350,7 +350,7 @@ async fn non_streamed_chat( &index_scheduler, auth_ctrl.clone(), &search_queue, - &auth_token, + auth_token, index_uid, q, ) @@ -461,6 +461,7 @@ async fn streamed_chat( /// Updates the chat completion with the new messages, streams the LLM tokens, /// and report progress and errors. +#[allow(clippy::too_many_arguments)] async fn run_conversation( index_scheduler: &GuardedData, Data>, auth_ctrl: &web::Data, @@ -515,7 +516,7 @@ async fn run_conversation( } }); - if global_tool_calls.get(index).map_or(false, Call::is_external) { + if global_tool_calls.get(index).is_some_and(Call::is_external) { todo!("Support forwarding external tool calls"); } } @@ -553,10 +554,10 @@ async fn run_conversation( ); handle_meili_tools( - &index_scheduler, - &auth_ctrl, - &search_queue, - &auth_token, + index_scheduler, + auth_ctrl, + search_queue, + auth_token, chat_settings, tx, meili_calls, @@ -586,6 +587,7 @@ async fn run_conversation( } } +#[allow(clippy::too_many_arguments)] async fn handle_meili_tools( index_scheduler: &GuardedData, Data>, auth_ctrl: &web::Data, @@ -621,10 +623,10 @@ async fn handle_meili_tools( let result = match serde_json::from_str(&call.function.arguments) { Ok(SearchInIndexParameters { index_uid, q }) => process_search_request( - &index_scheduler, + index_scheduler, auth_ctrl.clone(), - &search_queue, - &auth_token, + search_queue, + auth_token, index_uid, q, ) diff --git a/crates/meilisearch/src/routes/chats/settings.rs b/crates/meilisearch/src/routes/chats/settings.rs index e118a3ef1..a87fbed70 100644 --- a/crates/meilisearch/src/routes/chats/settings.rs +++ b/crates/meilisearch/src/routes/chats/settings.rs @@ -16,12 +16,11 @@ use meilisearch_types::milli::update::Setting; use serde::{Deserialize, Serialize}; use utoipa::ToSchema; +use super::ChatsParam; use crate::extractors::authentication::policies::ActionPolicy; use crate::extractors::authentication::GuardedData; use crate::extractors::sequential_extractor::SeqHandler; -use super::ChatsParam; - pub fn configure(cfg: &mut web::ServiceConfig) { cfg.service( web::resource("") @@ -70,8 +69,7 @@ async fn patch_settings( // TODO do a spawn_blocking here let mut wtxn = index_scheduler.write_txn()?; - let old_settings = - index_scheduler.chat_settings(&mut wtxn, &workspace_uid)?.unwrap_or_default(); + let old_settings = index_scheduler.chat_settings(&wtxn, &workspace_uid)?.unwrap_or_default(); let prompts = match new.prompts { Setting::Set(new_prompts) => DbChatCompletionPrompts { diff --git a/crates/meilisearch/src/routes/chats/utils.rs b/crates/meilisearch/src/routes/chats/utils.rs index 424b4ea64..b29747bc9 100644 --- a/crates/meilisearch/src/routes/chats/utils.rs +++ b/crates/meilisearch/src/routes/chats/utils.rs @@ -208,8 +208,8 @@ impl SseEventSender { /// Format documents based on the provided template and maximum bytes. /// /// This formatting function is usually used to generate a summary of the documents for LLMs. -pub fn format_documents<'t, 'doc>( - rtxn: &RoTxn<'t>, +pub fn format_documents<'doc>( + rtxn: &RoTxn<'_>, index: &Index, doc_alloc: &'doc Bump, internal_docids: Vec, diff --git a/crates/meilisearch/src/search/mod.rs b/crates/meilisearch/src/search/mod.rs index 037083b2d..27369591b 100644 --- a/crates/meilisearch/src/search/mod.rs +++ b/crates/meilisearch/src/search/mod.rs @@ -204,7 +204,7 @@ impl std::convert::TryFrom for RankingScoreThreshold { impl From for RankingScoreThreshold { fn from(threshold: index::RankingScoreThreshold) -> Self { let threshold = threshold.as_f64(); - assert!(threshold >= 0.0 && threshold <= 1.0); + assert!((0.0..=1.0).contains(&threshold)); RankingScoreThreshold(threshold) } } diff --git a/crates/milli/src/index.rs b/crates/milli/src/index.rs index b2df46af3..6ff972ae0 100644 --- a/crates/milli/src/index.rs +++ b/crates/milli/src/index.rs @@ -1984,7 +1984,7 @@ impl TryFrom for RankingScoreThreshold { type Error = InvalidSearchRankingScoreThreshold; fn try_from(value: f64) -> StdResult { - if value < 0.0 || value > 1.0 { + if !(0.0..=1.0).contains(&value) { Err(InvalidSearchRankingScoreThreshold) } else { Ok(RankingScoreThreshold(value)) diff --git a/crates/milli/src/update/chat.rs b/crates/milli/src/update/chat.rs index ae95ddfd9..c52ede029 100644 --- a/crates/milli/src/update/chat.rs +++ b/crates/milli/src/update/chat.rs @@ -69,11 +69,6 @@ impl From for ChatSettings { HybridQuery { semantic_ratio: SemanticRatio(semantic_ratio), embedder } }); - let matching_strategy = matching_strategy.map(MatchingStrategy::from); - - let ranking_score_threshold = - ranking_score_threshold.map(RankingScoreThreshold::from); - ChatSearchParams { hybrid: Setting::some_or_not_set(hybrid), limit: Setting::some_or_not_set(limit), diff --git a/crates/milli/src/update/settings.rs b/crates/milli/src/update/settings.rs index 9f152710a..0e44654a5 100644 --- a/crates/milli/src/update/settings.rs +++ b/crates/milli/src/update/settings.rs @@ -23,7 +23,7 @@ use crate::error::UserError; use crate::fields_ids_map::metadata::{FieldIdMapWithMetadata, MetadataBuilder}; use crate::filterable_attributes_rules::match_faceted_field; use crate::index::{ - ChatConfig, IndexEmbeddingConfig, MatchingStrategy, PrefixSearch, RankingScoreThreshold, + ChatConfig, IndexEmbeddingConfig, PrefixSearch, SearchParameters, DEFAULT_MIN_WORD_LEN_ONE_TYPO, DEFAULT_MIN_WORD_LEN_TWO_TYPOS, }; use crate::order_by_map::OrderByMap; @@ -1326,7 +1326,7 @@ impl<'a, 't, 'i> Settings<'a, 't, 'i> { }, matching_strategy: match matching_strategy { Setting::Set(matching_strategy) => { - Some(MatchingStrategy::from(*matching_strategy)) + Some(*matching_strategy) } Setting::Reset => None, Setting::NotSet => search_parameters.matching_strategy, @@ -1341,7 +1341,7 @@ impl<'a, 't, 'i> Settings<'a, 't, 'i> { } }, ranking_score_threshold: match ranking_score_threshold { - Setting::Set(rst) => Some(RankingScoreThreshold::from(*rst)), + Setting::Set(rst) => Some(*rst), Setting::Reset => None, Setting::NotSet => search_parameters.ranking_score_threshold, }, From 82313a4444f09628bba063e57c8ea554b3e28d07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Renault?= Date: Tue, 3 Jun 2025 14:50:21 +0200 Subject: [PATCH 059/103] Cargo fmt --- crates/benchmarks/benches/search_geo.rs | 3 ++- crates/benchmarks/benches/search_songs.rs | 3 ++- crates/index-scheduler/src/scheduler/mod.rs | 4 +++- crates/index-scheduler/src/scheduler/test_failure.rs | 3 +-- crates/meilisearch/tests/index/get_index.rs | 2 +- crates/meilisearch/tests/search/errors.rs | 3 +-- crates/meilisearch/tests/search/filters.rs | 6 +++--- crates/meilisearch/tests/search/geo.rs | 3 +-- crates/milli/src/database_stats.rs | 5 +---- crates/milli/src/disabled_typos_terms.rs | 9 ++++----- crates/milli/src/error.rs | 3 +-- crates/milli/src/filterable_attributes_rules.rs | 11 +++++------ crates/milli/src/progress.rs | 2 +- crates/milli/src/search/new/geo_sort.rs | 3 ++- crates/milli/src/search/new/mod.rs | 3 +-- .../src/update/new/extract/faceted/facet_document.rs | 3 +-- crates/milli/src/update/new/extract/mod.rs | 3 ++- crates/milli/src/update/settings.rs | 8 +++----- 18 files changed, 35 insertions(+), 42 deletions(-) diff --git a/crates/benchmarks/benches/search_geo.rs b/crates/benchmarks/benches/search_geo.rs index d76929f99..b16eb41f1 100644 --- a/crates/benchmarks/benches/search_geo.rs +++ b/crates/benchmarks/benches/search_geo.rs @@ -2,7 +2,8 @@ mod datasets_paths; mod utils; use criterion::{criterion_group, criterion_main}; -use milli::{update::Settings, FilterableAttributesRule}; +use milli::update::Settings; +use milli::FilterableAttributesRule; use utils::Conf; #[cfg(not(windows))] diff --git a/crates/benchmarks/benches/search_songs.rs b/crates/benchmarks/benches/search_songs.rs index 680a675ef..e1cbb5730 100644 --- a/crates/benchmarks/benches/search_songs.rs +++ b/crates/benchmarks/benches/search_songs.rs @@ -2,7 +2,8 @@ mod datasets_paths; mod utils; use criterion::{criterion_group, criterion_main}; -use milli::{update::Settings, FilterableAttributesRule}; +use milli::update::Settings; +use milli::FilterableAttributesRule; use utils::Conf; #[cfg(not(windows))] diff --git a/crates/index-scheduler/src/scheduler/mod.rs b/crates/index-scheduler/src/scheduler/mod.rs index f1775a892..0e258e27b 100644 --- a/crates/index-scheduler/src/scheduler/mod.rs +++ b/crates/index-scheduler/src/scheduler/mod.rs @@ -375,9 +375,11 @@ impl IndexScheduler { post_commit_dabases_sizes .get(dbname) .map(|post_size| { - use byte_unit::{Byte, UnitType::Binary}; use std::cmp::Ordering::{Equal, Greater, Less}; + use byte_unit::Byte; + use byte_unit::UnitType::Binary; + let post = Byte::from_u64(*post_size as u64).get_appropriate_unit(Binary); let diff_size = post_size.abs_diff(*pre_size) as u64; let diff = Byte::from_u64(diff_size).get_appropriate_unit(Binary); diff --git a/crates/index-scheduler/src/scheduler/test_failure.rs b/crates/index-scheduler/src/scheduler/test_failure.rs index 191910d38..ad7f22bd8 100644 --- a/crates/index-scheduler/src/scheduler/test_failure.rs +++ b/crates/index-scheduler/src/scheduler/test_failure.rs @@ -2,10 +2,9 @@ use std::time::Instant; use big_s::S; use meili_snap::snapshot; -use meilisearch_types::milli::obkv_to_json; use meilisearch_types::milli::update::IndexDocumentsMethod::*; use meilisearch_types::milli::update::Setting; -use meilisearch_types::milli::FilterableAttributesRule; +use meilisearch_types::milli::{obkv_to_json, FilterableAttributesRule}; use meilisearch_types::tasks::{Kind, KindWithContent}; use crate::insta_snapshot::snapshot_index_scheduler; diff --git a/crates/meilisearch/tests/index/get_index.rs b/crates/meilisearch/tests/index/get_index.rs index a436b649b..b07b7821b 100644 --- a/crates/meilisearch/tests/index/get_index.rs +++ b/crates/meilisearch/tests/index/get_index.rs @@ -1,8 +1,8 @@ -use crate::json; use meili_snap::{json_string, snapshot}; use serde_json::Value; use crate::common::{shared_does_not_exists_index, Server}; +use crate::json; #[actix_rt::test] async fn create_and_get_index() { diff --git a/crates/meilisearch/tests/search/errors.rs b/crates/meilisearch/tests/search/errors.rs index 2b63a07b1..7490544bf 100644 --- a/crates/meilisearch/tests/search/errors.rs +++ b/crates/meilisearch/tests/search/errors.rs @@ -1,10 +1,9 @@ use meili_snap::*; +use super::test_settings_documents_indexing_swapping_and_search; use crate::common::{shared_does_not_exists_index, Server, DOCUMENTS, NESTED_DOCUMENTS}; use crate::json; -use super::test_settings_documents_indexing_swapping_and_search; - #[actix_rt::test] async fn search_unexisting_index() { let index = shared_does_not_exists_index().await; diff --git a/crates/meilisearch/tests/search/filters.rs b/crates/meilisearch/tests/search/filters.rs index 4219d2ec1..ec80eb63e 100644 --- a/crates/meilisearch/tests/search/filters.rs +++ b/crates/meilisearch/tests/search/filters.rs @@ -3,10 +3,10 @@ use meilisearch::Opt; use tempfile::TempDir; use super::test_settings_documents_indexing_swapping_and_search; -use crate::{ - common::{default_settings, shared_index_with_documents, Server, DOCUMENTS, NESTED_DOCUMENTS}, - json, +use crate::common::{ + default_settings, shared_index_with_documents, Server, DOCUMENTS, NESTED_DOCUMENTS, }; +use crate::json; #[actix_rt::test] async fn search_with_filter_string_notation() { diff --git a/crates/meilisearch/tests/search/geo.rs b/crates/meilisearch/tests/search/geo.rs index a314ca241..eda02e440 100644 --- a/crates/meilisearch/tests/search/geo.rs +++ b/crates/meilisearch/tests/search/geo.rs @@ -2,11 +2,10 @@ use meili_snap::{json_string, snapshot}; use meilisearch_types::milli::constants::RESERVED_GEO_FIELD_NAME; use once_cell::sync::Lazy; +use super::test_settings_documents_indexing_swapping_and_search; use crate::common::{Server, Value}; use crate::json; -use super::test_settings_documents_indexing_swapping_and_search; - static DOCUMENTS: Lazy = Lazy::new(|| { json!([ { diff --git a/crates/milli/src/database_stats.rs b/crates/milli/src/database_stats.rs index 7da1fbd2b..381408621 100644 --- a/crates/milli/src/database_stats.rs +++ b/crates/milli/src/database_stats.rs @@ -1,9 +1,6 @@ use std::mem; -use heed::Database; -use heed::DatabaseStat; -use heed::RoTxn; -use heed::Unspecified; +use heed::{Database, DatabaseStat, RoTxn, Unspecified}; use serde::{Deserialize, Serialize}; use crate::BEU32; diff --git a/crates/milli/src/disabled_typos_terms.rs b/crates/milli/src/disabled_typos_terms.rs index 7ae35f828..c5acad7cd 100644 --- a/crates/milli/src/disabled_typos_terms.rs +++ b/crates/milli/src/disabled_typos_terms.rs @@ -1,10 +1,9 @@ -use heed::{ - types::{SerdeJson, Str}, - RoTxn, RwTxn, -}; +use heed::types::{SerdeJson, Str}; +use heed::{RoTxn, RwTxn}; use serde::{Deserialize, Serialize}; -use crate::{index::main_key, Index}; +use crate::index::main_key; +use crate::Index; #[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default)] #[serde(rename_all = "camelCase")] diff --git a/crates/milli/src/error.rs b/crates/milli/src/error.rs index 237a895d3..e129a31a0 100644 --- a/crates/milli/src/error.rs +++ b/crates/milli/src/error.rs @@ -1,5 +1,4 @@ -use std::collections::BTreeSet; -use std::collections::HashMap; +use std::collections::{BTreeSet, HashMap}; use std::convert::Infallible; use std::fmt::Write; use std::{io, str}; diff --git a/crates/milli/src/filterable_attributes_rules.rs b/crates/milli/src/filterable_attributes_rules.rs index 53af30fd6..ae1a9755a 100644 --- a/crates/milli/src/filterable_attributes_rules.rs +++ b/crates/milli/src/filterable_attributes_rules.rs @@ -1,13 +1,12 @@ +use std::collections::{BTreeSet, HashSet}; + use deserr::{DeserializeError, Deserr, ValuePointerRef}; use serde::{Deserialize, Serialize}; -use std::collections::{BTreeSet, HashSet}; use utoipa::ToSchema; -use crate::{ - attribute_patterns::{match_distinct_field, match_field_legacy, PatternMatch}, - constants::RESERVED_GEO_FIELD_NAME, - AttributePatterns, -}; +use crate::attribute_patterns::{match_distinct_field, match_field_legacy, PatternMatch}; +use crate::constants::RESERVED_GEO_FIELD_NAME; +use crate::AttributePatterns; #[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug, ToSchema)] #[serde(untagged)] diff --git a/crates/milli/src/progress.rs b/crates/milli/src/progress.rs index 75dafa8ec..fa651e17f 100644 --- a/crates/milli/src/progress.rs +++ b/crates/milli/src/progress.rs @@ -1,4 +1,3 @@ -use enum_iterator::Sequence; use std::any::TypeId; use std::borrow::Cow; use std::marker::PhantomData; @@ -6,6 +5,7 @@ use std::sync::atomic::{AtomicU32, Ordering}; use std::sync::{Arc, RwLock}; use std::time::{Duration, Instant}; +use enum_iterator::Sequence; use indexmap::IndexMap; use itertools::Itertools; use serde::Serialize; diff --git a/crates/milli/src/search/new/geo_sort.rs b/crates/milli/src/search/new/geo_sort.rs index 663599553..3e7fe3458 100644 --- a/crates/milli/src/search/new/geo_sort.rs +++ b/crates/milli/src/search/new/geo_sort.rs @@ -1,8 +1,9 @@ +use std::collections::VecDeque; + use heed::types::{Bytes, Unit}; use heed::{RoPrefix, RoTxn}; use roaring::RoaringBitmap; use rstar::RTree; -use std::collections::VecDeque; use super::facet_string_values; use super::ranking_rules::{RankingRule, RankingRuleOutput, RankingRuleQueryTrait}; diff --git a/crates/milli/src/search/new/mod.rs b/crates/milli/src/search/new/mod.rs index 6e794ef53..86d0816ba 100644 --- a/crates/milli/src/search/new/mod.rs +++ b/crates/milli/src/search/new/mod.rs @@ -47,8 +47,7 @@ use sort::Sort; use self::distinct::facet_string_values; use self::geo_sort::GeoSort; -pub use self::geo_sort::Parameter as GeoSortParameter; -pub use self::geo_sort::Strategy as GeoSortStrategy; +pub use self::geo_sort::{Parameter as GeoSortParameter, Strategy as GeoSortStrategy}; use self::graph_based_ranking_rule::Words; use self::interner::Interned; use self::vector_sort::VectorSort; diff --git a/crates/milli/src/update/new/extract/faceted/facet_document.rs b/crates/milli/src/update/new/extract/faceted/facet_document.rs index e74131402..ff15f146d 100644 --- a/crates/milli/src/update/new/extract/faceted/facet_document.rs +++ b/crates/milli/src/update/new/extract/faceted/facet_document.rs @@ -4,6 +4,7 @@ use serde_json::Value; use crate::attribute_patterns::PatternMatch; use crate::fields_ids_map::metadata::Metadata; +use crate::filterable_attributes_rules::match_faceted_field; use crate::update::new::document::Document; use crate::update::new::extract::geo::extract_geo_coordinates; use crate::update::new::extract::perm_json_p; @@ -11,8 +12,6 @@ use crate::{ FieldId, FilterableAttributesRule, GlobalFieldsIdsMap, InternalError, Result, UserError, }; -use crate::filterable_attributes_rules::match_faceted_field; - #[allow(clippy::too_many_arguments)] pub fn extract_document_facets<'doc>( document: impl Document<'doc>, diff --git a/crates/milli/src/update/new/extract/mod.rs b/crates/milli/src/update/new/extract/mod.rs index a8264ba4a..2abefb098 100644 --- a/crates/milli/src/update/new/extract/mod.rs +++ b/crates/milli/src/update/new/extract/mod.rs @@ -18,7 +18,8 @@ pub use vectors::EmbeddingExtractor; pub mod perm_json_p { use serde_json::{Map, Value}; - use crate::{attribute_patterns::PatternMatch, Result}; + use crate::attribute_patterns::PatternMatch; + use crate::Result; const SPLIT_SYMBOL: char = '.'; /// Returns `true` if the `selector` match the `key`. diff --git a/crates/milli/src/update/settings.rs b/crates/milli/src/update/settings.rs index 0e44654a5..70a190b19 100644 --- a/crates/milli/src/update/settings.rs +++ b/crates/milli/src/update/settings.rs @@ -23,8 +23,8 @@ use crate::error::UserError; use crate::fields_ids_map::metadata::{FieldIdMapWithMetadata, MetadataBuilder}; use crate::filterable_attributes_rules::match_faceted_field; use crate::index::{ - ChatConfig, IndexEmbeddingConfig, PrefixSearch, - SearchParameters, DEFAULT_MIN_WORD_LEN_ONE_TYPO, DEFAULT_MIN_WORD_LEN_TWO_TYPOS, + ChatConfig, IndexEmbeddingConfig, PrefixSearch, SearchParameters, + DEFAULT_MIN_WORD_LEN_ONE_TYPO, DEFAULT_MIN_WORD_LEN_TWO_TYPOS, }; use crate::order_by_map::OrderByMap; use crate::prompt::{default_max_bytes, default_template_text, PromptData}; @@ -1325,9 +1325,7 @@ impl<'a, 't, 'i> Settings<'a, 't, 'i> { Setting::NotSet => search_parameters.distinct.clone(), }, matching_strategy: match matching_strategy { - Setting::Set(matching_strategy) => { - Some(*matching_strategy) - } + Setting::Set(matching_strategy) => Some(*matching_strategy), Setting::Reset => None, Setting::NotSet => search_parameters.matching_strategy, }, From c4e1407e77be9fdb34964725513b312e2627db3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Renault?= Date: Tue, 3 Jun 2025 15:13:42 +0200 Subject: [PATCH 060/103] Fix the chat, chats, and chatsSettings actions --- crates/meilisearch-auth/src/store.rs | 7 +++++++ crates/meilisearch-types/src/keys.rs | 14 ++++++------- .../src/extractors/authentication/mod.rs | 2 +- .../src/routes/chats/chat_completions.rs | 21 +++++++++++++------ 4 files changed, 30 insertions(+), 14 deletions(-) diff --git a/crates/meilisearch-auth/src/store.rs b/crates/meilisearch-auth/src/store.rs index 2fd380194..e20f259ee 100644 --- a/crates/meilisearch-auth/src/store.rs +++ b/crates/meilisearch-auth/src/store.rs @@ -125,6 +125,13 @@ impl HeedAuthStore { Action::MetricsAll => { actions.insert(Action::MetricsGet); } + Action::ChatsSettingsAll => { + actions.extend([ + Action::ChatsSettingsGet, + Action::ChatsSettingsUpdate, + Action::ChatsSettingsDelete, + ]); + } other => { actions.insert(*other); } diff --git a/crates/meilisearch-types/src/keys.rs b/crates/meilisearch-types/src/keys.rs index a3f6ff046..5634d585f 100644 --- a/crates/meilisearch-types/src/keys.rs +++ b/crates/meilisearch-types/src/keys.rs @@ -53,7 +53,7 @@ pub struct CreateApiKey { #[schema(example = json!(["documents.add"]))] #[deserr(error = DeserrJsonError, missing_field_error = DeserrJsonError::missing_api_key_actions)] pub actions: Vec, - /// A list of accesible indexes permitted for the key. `["*"]` for all indexes. The `*` character can be used as a wildcard when located at the last position. e.g. `products_*` to allow access to all indexes whose names start with `products_`. + /// A list of accessible indexes permitted for the key. `["*"]` for all indexes. The `*` character can be used as a wildcard when located at the last position. e.g. `products_*` to allow access to all indexes whose names start with `products_`. #[deserr(error = DeserrJsonError, missing_field_error = DeserrJsonError::missing_api_key_indexes)] #[schema(value_type = Vec, example = json!(["products"]))] pub indexes: Vec, @@ -166,7 +166,7 @@ impl Key { name: Some("Default Chat API Key".to_string()), description: Some("Use it to chat and search from the frontend".to_string()), uid, - actions: vec![Action::Chat, Action::Search], + actions: vec![Action::ChatCompletions, Action::Search], indexes: vec![IndexUidPattern::all()], expires_at: None, created_at: now, @@ -324,9 +324,9 @@ pub enum Action { #[deserr(rename = "network.update")] NetworkUpdate, // TODO should we rename it chatCompletions.get ? - #[serde(rename = "chat")] - #[deserr(rename = "chat")] - Chat, + #[serde(rename = "chatCompletion")] + #[deserr(rename = "chatCompletion")] + ChatCompletions, #[serde(rename = "chats.get")] #[deserr(rename = "chats.get")] ChatsGet, @@ -367,7 +367,7 @@ impl Action { SETTINGS_ALL => Some(Self::SettingsAll), SETTINGS_GET => Some(Self::SettingsGet), SETTINGS_UPDATE => Some(Self::SettingsUpdate), - CHAT => Some(Self::Chat), + CHAT_COMPLETIONS => Some(Self::ChatCompletions), CHATS_GET => Some(Self::ChatsGet), CHATS_SETTINGS_ALL => Some(Self::ChatsSettingsAll), CHATS_SETTINGS_GET => Some(Self::ChatsSettingsGet), @@ -438,7 +438,7 @@ pub mod actions { pub const NETWORK_GET: u8 = NetworkGet.repr(); pub const NETWORK_UPDATE: u8 = NetworkUpdate.repr(); - pub const CHAT: u8 = Chat.repr(); + pub const CHAT_COMPLETIONS: u8 = ChatCompletions.repr(); pub const CHATS_GET: u8 = ChatsGet.repr(); pub const CHATS_SETTINGS_ALL: u8 = ChatsSettingsAll.repr(); pub const CHATS_SETTINGS_GET: u8 = ChatsSettingsGet.repr(); diff --git a/crates/meilisearch/src/extractors/authentication/mod.rs b/crates/meilisearch/src/extractors/authentication/mod.rs index eb250190d..86614f153 100644 --- a/crates/meilisearch/src/extractors/authentication/mod.rs +++ b/crates/meilisearch/src/extractors/authentication/mod.rs @@ -309,7 +309,7 @@ pub mod policies { token: &str, ) -> Result { // Only search and chat actions can be accessed by a tenant token. - if A != actions::SEARCH && A != actions::CHAT { + if A != actions::SEARCH && A != actions::CHAT_COMPLETIONS { return Ok(TenantTokenOutcome::NotATenantToken); } diff --git a/crates/meilisearch/src/routes/chats/chat_completions.rs b/crates/meilisearch/src/routes/chats/chat_completions.rs index 01e22d6f8..8b7ff2689 100644 --- a/crates/meilisearch/src/routes/chats/chat_completions.rs +++ b/crates/meilisearch/src/routes/chats/chat_completions.rs @@ -56,7 +56,7 @@ pub fn configure(cfg: &mut web::ServiceConfig) { /// Get a chat completion async fn chat( - index_scheduler: GuardedData, Data>, + index_scheduler: GuardedData, Data>, auth_ctrl: web::Data, chats_param: web::Path, req: HttpRequest, @@ -208,7 +208,10 @@ fn setup_search_tool( /// Process search request and return formatted results async fn process_search_request( - index_scheduler: &GuardedData, Data>, + index_scheduler: &GuardedData< + ActionPolicy<{ actions::CHAT_COMPLETIONS }>, + Data, + >, auth_ctrl: web::Data, search_queue: &web::Data, auth_token: &str, @@ -288,7 +291,7 @@ async fn process_search_request( } async fn non_streamed_chat( - index_scheduler: GuardedData, Data>, + index_scheduler: GuardedData, Data>, auth_ctrl: web::Data, search_queue: web::Data, workspace_uid: &str, @@ -388,7 +391,7 @@ async fn non_streamed_chat( } async fn streamed_chat( - index_scheduler: GuardedData, Data>, + index_scheduler: GuardedData, Data>, auth_ctrl: web::Data, search_queue: web::Data, workspace_uid: &str, @@ -463,7 +466,10 @@ async fn streamed_chat( /// and report progress and errors. #[allow(clippy::too_many_arguments)] async fn run_conversation( - index_scheduler: &GuardedData, Data>, + index_scheduler: &GuardedData< + ActionPolicy<{ actions::CHAT_COMPLETIONS }>, + Data, + >, auth_ctrl: &web::Data, search_queue: &web::Data, auth_token: &str, @@ -589,7 +595,10 @@ async fn run_conversation( #[allow(clippy::too_many_arguments)] async fn handle_meili_tools( - index_scheduler: &GuardedData, Data>, + index_scheduler: &GuardedData< + ActionPolicy<{ actions::CHAT_COMPLETIONS }>, + Data, + >, auth_ctrl: &web::Data, search_queue: &web::Data, auth_token: &str, From 28dc7b836ba277ec07162ba0b70628d7164f73f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Renault?= Date: Tue, 3 Jun 2025 17:10:53 +0200 Subject: [PATCH 061/103] Fix the chat completions feature gate --- .../src/routes/chats/chat_completions.rs | 4 ++-- crates/meilisearch/src/routes/chats/mod.rs | 2 +- crates/meilisearch/src/routes/chats/settings.rs | 6 +++--- crates/meilisearch/src/routes/indexes/settings.rs | 15 ++++++++++++++- 4 files changed, 20 insertions(+), 7 deletions(-) diff --git a/crates/meilisearch/src/routes/chats/chat_completions.rs b/crates/meilisearch/src/routes/chats/chat_completions.rs index 8b7ff2689..6f6b50d1c 100644 --- a/crates/meilisearch/src/routes/chats/chat_completions.rs +++ b/crates/meilisearch/src/routes/chats/chat_completions.rs @@ -298,7 +298,7 @@ async fn non_streamed_chat( req: HttpRequest, mut chat_completion: CreateChatCompletionRequest, ) -> Result { - index_scheduler.features().check_chat_completions("Using the /chats chat completions route")?; + index_scheduler.features().check_chat_completions("using the /chats chat completions route")?; let filters = index_scheduler.filters(); let rtxn = index_scheduler.read_txn()?; @@ -398,7 +398,7 @@ async fn streamed_chat( req: HttpRequest, mut chat_completion: CreateChatCompletionRequest, ) -> Result { - index_scheduler.features().check_chat_completions("Using the /chats chat completions route")?; + index_scheduler.features().check_chat_completions("using the /chats chat completions route")?; let filters = index_scheduler.filters(); let rtxn = index_scheduler.read_txn()?; diff --git a/crates/meilisearch/src/routes/chats/mod.rs b/crates/meilisearch/src/routes/chats/mod.rs index bb0476ab8..35afd69c0 100644 --- a/crates/meilisearch/src/routes/chats/mod.rs +++ b/crates/meilisearch/src/routes/chats/mod.rs @@ -75,7 +75,7 @@ pub async fn list_workspaces( index_scheduler: GuardedData, Data>, paginate: AwebQueryParameter, ) -> Result { - index_scheduler.features().check_chat_completions("Using the /chats settings route")?; + index_scheduler.features().check_chat_completions("listing the chats")?; debug!(parameters = ?paginate, "List chat workspaces"); let filters = index_scheduler.filters(); diff --git a/crates/meilisearch/src/routes/chats/settings.rs b/crates/meilisearch/src/routes/chats/settings.rs index a87fbed70..0bb25f30d 100644 --- a/crates/meilisearch/src/routes/chats/settings.rs +++ b/crates/meilisearch/src/routes/chats/settings.rs @@ -37,7 +37,7 @@ async fn get_settings( >, chats_param: web::Path, ) -> Result { - index_scheduler.features().check_chat_completions("Using the /chats settings route")?; + index_scheduler.features().check_chat_completions("using the /chats/settings route")?; let ChatsParam { workspace_uid } = chats_param.into_inner(); @@ -64,7 +64,7 @@ async fn patch_settings( chats_param: web::Path, web::Json(new): web::Json, ) -> Result { - index_scheduler.features().check_chat_completions("Using the /chats settings route")?; + index_scheduler.features().check_chat_completions("using the /chats/settings route")?; let ChatsParam { workspace_uid } = chats_param.into_inner(); // TODO do a spawn_blocking here @@ -144,7 +144,7 @@ async fn delete_settings( >, chats_param: web::Path, ) -> Result { - index_scheduler.features().check_chat_completions("Using the /chats settings route")?; + index_scheduler.features().check_chat_completions("using the /chats/settings route")?; let ChatsParam { workspace_uid } = chats_param.into_inner(); diff --git a/crates/meilisearch/src/routes/indexes/settings.rs b/crates/meilisearch/src/routes/indexes/settings.rs index a35ae5136..d91c3cd35 100644 --- a/crates/meilisearch/src/routes/indexes/settings.rs +++ b/crates/meilisearch/src/routes/indexes/settings.rs @@ -5,6 +5,7 @@ use index_scheduler::IndexScheduler; use meilisearch_types::deserr::DeserrJsonError; use meilisearch_types::error::ResponseError; use meilisearch_types::index_uid::IndexUid; +use meilisearch_types::milli::update::Setting; use meilisearch_types::settings::{ settings, ChatSettings, SecretPolicy, SettingEmbeddingSettings, Settings, Unchecked, }; @@ -568,6 +569,10 @@ pub async fn update_all( debug!(parameters = ?new_settings, "Update all settings"); let new_settings = validate_settings(new_settings, &index_scheduler)?; + if !new_settings.chat.is_not_set() { + index_scheduler.features().check_chat_completions("setting `chat` in the index route")?; + } + analytics.publish( SettingsAnalytics { ranking_rules: RankingRulesAnalytics::new(new_settings.ranking_rules.as_ref().set()), @@ -663,7 +668,11 @@ pub async fn get_all( let index = index_scheduler.index(&index_uid)?; let rtxn = index.read_txn()?; - let new_settings = settings(&index, &rtxn, SecretPolicy::HideSecrets)?; + let mut new_settings = settings(&index, &rtxn, SecretPolicy::HideSecrets)?; + if index_scheduler.features().check_chat_completions("showing index `chat` settings").is_err() { + new_settings.chat = Setting::NotSet; + } + debug!(returns = ?new_settings, "Get all settings"); Ok(HttpResponse::Ok().json(new_settings)) } @@ -753,5 +762,9 @@ fn validate_settings( } } + if let Setting::Set(_chat) = &settings.chat { + features.check_chat_completions("setting `chat` in the index settings")?; + } + Ok(settings.validate()?) } From 352ac759b528e3662f0160353345a8ef202b13a8 Mon Sep 17 00:00:00 2001 From: Kerollmops Date: Wed, 4 Jun 2025 09:35:43 +0200 Subject: [PATCH 062/103] Update dependencies --- Cargo.lock | 2108 +++++++++++++++++++++++++++------------------------- 1 file changed, 1078 insertions(+), 1030 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e310c967d..4e897e580 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,11 +4,11 @@ version = 4 [[package]] name = "actix-codec" -version = "0.5.1" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "617a8268e3537fe1d8c9ead925fca49ef6400927ee7bc26750e90ecee14ce4b8" +checksum = "5f7b0a21988c1bf877cf4759ef5ddaac04c1c9fe808c9142ecb78ba97d97a28a" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.9.1", "bytes", "futures-core", "futures-sink", @@ -21,13 +21,13 @@ dependencies = [ [[package]] name = "actix-cors" -version = "0.7.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9e772b3bcafe335042b5db010ab7c09013dad6eac4915c91d8d50902769f331" +checksum = "daa239b93927be1ff123eebada5a3ff23e89f0124ccb8609234e5103d5a5ae6d" dependencies = [ "actix-utils", "actix-web", - "derive_more 0.99.17", + "derive_more", "futures-util", "log", "once_cell", @@ -46,17 +46,17 @@ dependencies = [ "actix-tls", "actix-utils", "base64 0.22.1", - "bitflags 2.9.0", + "bitflags 2.9.1", "brotli 8.0.1", "bytes", "bytestring", - "derive_more 2.0.1", + "derive_more", "encoding_rs", "flate2", "foldhash", "futures-core", "h2 0.3.26", - "http 0.2.11", + "http 0.2.12", "httparse", "httpdate", "itoa", @@ -80,7 +80,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e01ed3140b2f8d422c68afa1ed2e85d996ea619c988ac834d255db32138655cb" dependencies = [ "quote", - "syn 2.0.87", + "syn 2.0.101", ] [[package]] @@ -91,7 +91,7 @@ checksum = "13d324164c51f63867b57e73ba5936ea151b8a41a1d23d1031eeb9f70d0236f8" dependencies = [ "bytestring", "cfg-if", - "http 0.2.11", + "http 0.2.12", "regex", "regex-lite", "serde", @@ -111,30 +111,28 @@ dependencies = [ [[package]] name = "actix-server" -version = "2.2.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e8613a75dd50cc45f473cee3c34d59ed677c0f7b44480ce3b8247d7dc519327" +checksum = "a65064ea4a457eaf07f2fba30b4c695bf43b721790e9530d26cb6f9019ff7502" dependencies = [ "actix-rt", "actix-service", "actix-utils", "futures-core", "futures-util", - "mio 0.8.11", - "num_cpus", - "socket2 0.4.9", + "mio", + "socket2", "tokio", "tracing", ] [[package]] name = "actix-service" -version = "2.0.2" +version = "2.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b894941f818cfdc7ccc4b9e60fa7e53b5042a2e8567270f9147d5591893373a" +checksum = "9e46f36bf0e5af44bdc4bdb36fbbd421aa98c79a9bce724e1edeb3894e10dc7f" dependencies = [ "futures-core", - "paste", "pin-project-lite", ] @@ -169,9 +167,9 @@ dependencies = [ [[package]] name = "actix-web" -version = "4.9.0" +version = "4.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9180d76e5cc7ccbc4d60a506f2c727730b154010262df5b910eb17dbe4b8cb38" +checksum = "a597b77b5c6d6a1e1097fddde329a83665e25c5437c696a3a9a4aa514a614dea" dependencies = [ "actix-codec", "actix-http", @@ -183,13 +181,13 @@ dependencies = [ "actix-tls", "actix-utils", "actix-web-codegen", - "ahash 0.8.11", "bytes", "bytestring", "cfg-if", "cookie", - "derive_more 0.99.17", + "derive_more", "encoding_rs", + "foldhash", "futures-core", "futures-util", "impl-more", @@ -204,8 +202,9 @@ dependencies = [ "serde_json", "serde_urlencoded", "smallvec", - "socket2 0.5.10", + "socket2", "time", + "tracing", "url", ] @@ -218,7 +217,7 @@ dependencies = [ "actix-router", "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.101", ] [[package]] @@ -232,16 +231,16 @@ dependencies = [ "actix-service", "actix-utils", "actix-web", - "ahash 0.8.11", + "ahash 0.8.12", "arc-swap", "bytes", "bytestring", "csv", - "derive_more 2.0.1", + "derive_more", "form_urlencoded", "futures-core", "futures-util", - "http 0.2.11", + "http 0.2.12", "impl-more", "itertools 0.14.0", "local-channel", @@ -260,19 +259,13 @@ dependencies = [ [[package]] name = "addr2line" -version = "0.20.0" +version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4fa78e18c64fce05e902adecd7a5eed15a5e0a3439f7b0e169f0252214865e3" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" dependencies = [ "gimli", ] -[[package]] -name = "adler" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" - [[package]] name = "adler2" version = "2.0.0" @@ -302,20 +295,20 @@ version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" dependencies = [ - "getrandom 0.2.15", + "getrandom 0.2.16", "once_cell", "version_check", ] [[package]] name = "ahash" -version = "0.8.11" +version = "0.8.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" dependencies = [ "cfg-if", "const-random", - "getrandom 0.2.15", + "getrandom 0.3.3", "once_cell", "version_check", "zerocopy", @@ -359,15 +352,16 @@ checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" [[package]] name = "anstream" -version = "0.6.13" +version = "0.6.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d96bd03f33fe50a863e394ee9718a706f988b9079b20c3784fb726e7678b62fb" +checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" dependencies = [ "anstyle", "anstyle-parse", "anstyle-query", "anstyle-wincon", "colorchoice", + "is_terminal_polyfill", "utf8parse", ] @@ -379,30 +373,31 @@ checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" [[package]] name = "anstyle-parse" -version = "0.2.1" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "938874ff5980b03a87c5524b3ae5b59cf99b1d6bc836848df7bc5ada9643c333" +checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.0.0" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" +checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.59.0", ] [[package]] name = "anstyle-wincon" -version = "3.0.1" +version = "3.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0699d10d2f4d628a98ee7b57b289abbc98ff3bad977cb3152709d4bf2330628" +checksum = "6680de5231bd6ee4c6191b8a1325daa282b415391ec9d3a37bd34f2060dc73fa" dependencies = [ "anstyle", - "windows-sys 0.48.0", + "once_cell_polyfill", + "windows-sys 0.59.0", ] [[package]] @@ -437,9 +432,9 @@ checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" [[package]] name = "arrayvec" -version = "0.7.4" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" [[package]] name = "arroy" @@ -505,18 +500,18 @@ source = "git+https://github.com/meilisearch/async-openai?branch=better-error-ha dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.101", ] [[package]] name = "async-trait" -version = "0.1.85" +version = "0.1.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f934833b4b7233644e5848f235df3f57ed8c80f1528a26c3dfa13d2147fa056" +checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.101", ] [[package]] @@ -527,9 +522,9 @@ checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] name = "autocfg" -version = "1.2.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "backoff" @@ -538,7 +533,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b62ddb9cb1ec0a098ad4bbf9344d0713fa193ae1a80af55febcff2627b6a00c1" dependencies = [ "futures-core", - "getrandom 0.2.15", + "getrandom 0.2.16", "instant", "pin-project-lite", "rand 0.8.5", @@ -547,17 +542,17 @@ dependencies = [ [[package]] name = "backtrace" -version = "0.3.68" +version = "0.3.75" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4319208da049c43661739c5fade2ba182f09d1dc2299b32298d3a31692b17e12" +checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" dependencies = [ "addr2line", - "cc", "cfg-if", "libc", - "miniz_oxide 0.7.2", + "miniz_oxide", "object", "rustc-demangle", + "windows-targets 0.52.6", ] [[package]] @@ -590,7 +585,7 @@ dependencies = [ "anyhow", "bumpalo", "bytes", - "convert_case 0.6.0", + "convert_case", "criterion", "csv", "flate2", @@ -635,7 +630,7 @@ version = "0.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f49d8fed880d473ea71efb9bf597651e77201bdd4893efe54c9e5d65ae04ce6f" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "cexpr", "clang-sys", "itertools 0.13.0", @@ -644,7 +639,7 @@ dependencies = [ "regex", "rustc-hash 1.1.0", "shlex", - "syn 2.0.87", + "syn 2.0.101", ] [[package]] @@ -681,9 +676,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.9.0" +version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" +checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" dependencies = [ "serde", ] @@ -720,9 +715,9 @@ dependencies = [ [[package]] name = "borsh" -version = "1.5.1" +version = "1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6362ed55def622cddc70a4746a68554d7b687713770de539e59a739b249f8ed" +checksum = "ad8646f98db542e39fc66e68a20b2144f6a732636df7c2354e74645faaa433ce" dependencies = [ "borsh-derive", "cfg_aliases", @@ -730,16 +725,15 @@ dependencies = [ [[package]] name = "borsh-derive" -version = "1.5.1" +version = "1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3ef8005764f53cd4dca619f5bf64cafd4664dada50ece25e4d81de54c80cc0b" +checksum = "fdd1d3c0c2f5833f22386f252fe8ed005c7f59fdcddeef025c01b4c3b9fd9ac3" dependencies = [ "once_cell", "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.87", - "syn_derive", + "syn 2.0.101", ] [[package]] @@ -750,7 +744,7 @@ checksum = "74f7971dbd9326d58187408ab83117d8ac1bb9c17b085fdacd1cf2f598719b6b" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", - "brotli-decompressor 4.0.1", + "brotli-decompressor 4.0.3", ] [[package]] @@ -766,9 +760,9 @@ dependencies = [ [[package]] name = "brotli-decompressor" -version = "4.0.1" +version = "4.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a45bd2e4095a8b518033b128020dd4a55aab1c0a381ba4404a472630f4bc362" +checksum = "a334ef7c9e23abf0ce748e8cd309037da93e606ad52eb372e4ce327a0dcfbdfd" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", @@ -786,9 +780,9 @@ dependencies = [ [[package]] name = "bstr" -version = "1.11.3" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "531a9155a481e2ee699d4f98f43c0ca4ff8ee1bfd55c31e9e98fb29d2b176fe0" +checksum = "234113d19d0d7d613b40e86fb654acf958910802bcceab913a4f9e7cda03b1a4" dependencies = [ "memchr", "regex-automata", @@ -806,9 +800,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.16.0" +version = "3.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" +checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" dependencies = [ "allocator-api2", "serde", @@ -823,7 +817,7 @@ dependencies = [ "allocator-api2", "bitpacking", "bumpalo", - "hashbrown 0.15.2", + "hashbrown 0.15.3", "serde", "serde_json", ] @@ -863,15 +857,15 @@ dependencies = [ [[package]] name = "bytecount" -version = "0.6.3" +version = "0.6.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c676a478f63e9fa2dd5368a42f28bba0d6c560b775f38583c8bbaa7fcd67c9c" +checksum = "175812e0be2bccb6abe50bb8d566126198344f707e304f45c648fd8f2cc0365e" [[package]] name = "bytemuck" -version = "1.21.0" +version = "1.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef657dfab802224e671f5818e9a4935f9b1957ed18e58292690cc39e7a4092a3" +checksum = "9134a6ef01ce4b366b50689c94f82c14bc72bc5d0386829828a2e2752ef7958c" dependencies = [ "bytemuck_derive", ] @@ -884,7 +878,7 @@ checksum = "7ecc273b49b3205b83d648f0690daa588925572cc5063745bfe547fe7ec8e1a1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.101", ] [[package]] @@ -895,15 +889,15 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.9.0" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" +checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" [[package]] name = "bytestring" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "238e4886760d98c4f899360c834fa93e62cf7f721ac3c2da375cbdf4b8679aae" +checksum = "e465647ae23b2823b0753f50decb2d5a86d2bb2cac04788fafd1f80e45378e5f" dependencies = [ "bytes", ] @@ -929,55 +923,55 @@ dependencies = [ [[package]] name = "camino" -version = "1.1.6" +version = "1.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c59e92b5a388f549b863a7bea62612c09f24c8393560709a54558a9abdfb3b9c" +checksum = "0da45bc31171d8d6960122e222a67740df867c1dd53b4d51caa297084c185cab" dependencies = [ "serde", ] [[package]] name = "candle-core" -version = "0.8.2" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "855dfedff437d2681d68e1f34ae559d88b0dd84aa5a6b63f2c8e75ebdd875bbf" +checksum = "06ccf5ee3532e66868516d9b315f73aec9f34ea1a37ae98514534d458915dbf1" dependencies = [ "byteorder", "candle-kernels", "cudarc", - "gemm", - "half 2.4.1", + "gemm 0.17.1", + "half", "memmap2", "num-traits", "num_cpus", - "rand 0.8.5", + "rand 0.9.1", "rand_distr", "rayon", "safetensors", "thiserror 1.0.69", "ug", "ug-cuda", - "yoke", + "yoke 0.7.5", "zip 1.1.4", ] [[package]] name = "candle-kernels" -version = "0.8.2" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53343628fa470b7075c28c589b98735b4220b464e37ddbb8e117040e199f4787" +checksum = "a10885bd902fad1b8518ba2b22369aaed88a3d94e123533ad3ca73db33b1c8ca" dependencies = [ "bindgen_cuda", ] [[package]] name = "candle-nn" -version = "0.8.2" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddd3c6b2ee0dfd64af12ae5b07e4b7c517898981cdaeffcb10b71d7dd5c8f359" +checksum = "be1160c3b63f47d40d91110a3e1e1e566ae38edddbbf492a60b40ffc3bc1ff38" dependencies = [ "candle-core", - "half 2.4.1", + "half", "num-traits", "rayon", "safetensors", @@ -987,16 +981,16 @@ dependencies = [ [[package]] name = "candle-transformers" -version = "0.8.2" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4270cc692c4a3df2051c2e8c3c4da3a189746af7ca3a547b99ecd335582b92e1" +checksum = "94a0900d49f8605e0e7e6693a1f560e6271279de98e5fa369e7abf3aac245020" dependencies = [ "byteorder", "candle-core", "candle-nn", "fancy-regex", "num-traits", - "rand 0.8.5", + "rand 0.9.1", "rayon", "serde", "serde_json", @@ -1006,18 +1000,18 @@ dependencies = [ [[package]] name = "cargo-platform" -version = "0.1.6" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ceed8ef69d8518a5dda55c07425450b58a4e1946f4951eab6d7191ee86c2443d" +checksum = "e35af189006b9c0f00a064685c727031e3ed2d8020f7ba284d78cc2671bd36ea" dependencies = [ "serde", ] [[package]] name = "cargo_metadata" -version = "0.19.1" +version = "0.19.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8769706aad5d996120af43197bf46ef6ad0fda35216b4505f926a365a232d924" +checksum = "dd5eb614ed4c27c5d706420e4320fbe3216ab31fa1c33cd8246ac36dae4479ba" dependencies = [ "camino", "cargo-platform", @@ -1045,9 +1039,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.2.16" +version = "1.2.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be714c154be609ec7f5dad223a33bf1482fff90472de28f7362806e6d4832b8c" +checksum = "d0fc897dc1e865cc67c0e05a836d9d3f1df3cbe442aa4a9473b18e12624a4951" dependencies = [ "jobserver", "libc", @@ -1118,9 +1112,9 @@ dependencies = [ [[package]] name = "ciborium" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "effd91f6c78e5a4ace8a5d3c0b6bfaec9e2baaef55f3efc00e45fb2e477ee926" +checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" dependencies = [ "ciborium-io", "ciborium-ll", @@ -1129,18 +1123,18 @@ dependencies = [ [[package]] name = "ciborium-io" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdf919175532b369853f5d5e20b26b43112613fd6fe7aee757e35f7a44642656" +checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" [[package]] name = "ciborium-ll" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "defaa24ecc093c77630e6c15e17c51f5e187bf35ee514f4e2d67baaa96dae22b" +checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" dependencies = [ "ciborium-io", - "half 1.8.2", + "half", ] [[package]] @@ -1155,9 +1149,9 @@ dependencies = [ [[package]] name = "clang-sys" -version = "1.7.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67523a3b4be3ce1989d607a828d036249522dd9c1c8de7f4dd2dae43a37369d1" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" dependencies = [ "glob", "libc", @@ -1166,9 +1160,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.24" +version = "4.5.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9560b07a799281c7e0958b9296854d6fafd4c5f31444a7e5bb1ad6dde5ccf1bd" +checksum = "fd60e63e9be68e5fb56422e397cf9baddded06dae1d2e523401542383bc72a9f" dependencies = [ "clap_builder", "clap_derive", @@ -1176,9 +1170,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.24" +version = "4.5.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "874e0dd3eb68bf99058751ac9712f622e61e6f393a94f7128fa26e3f02f5c7cd" +checksum = "89cc6392a1f72bbeb820d71f32108f61fdaf18bc526e1d23954168a67759ef51" dependencies = [ "anstream", "anstyle", @@ -1188,14 +1182,14 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.24" +version = "4.5.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54b755194d6389280185988721fffba69495eed5ee9feeee9a599b53db80318c" +checksum = "09176aae279615badda0765c0c0b3f6ed53f4709118af73cf4655d85d1530cd7" dependencies = [ - "heck 0.5.0", + "heck", "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.101", ] [[package]] @@ -1218,9 +1212,9 @@ dependencies = [ [[package]] name = "colorchoice" -version = "1.0.0" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" +checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" [[package]] name = "concat-arrays" @@ -1235,15 +1229,15 @@ dependencies = [ [[package]] name = "console" -version = "0.15.7" +version = "0.15.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c926e00cc70edefdc64d3a5ff31cc65bb97a3460097762bd23afb4d8145fccf8" +checksum = "054ccb5b10f9f2cbf51eb355ca1d05c2d279ce1804688d0db74b4733a5aeafd8" dependencies = [ "encode_unicode", - "lazy_static", "libc", + "once_cell", "unicode-width", - "windows-sys 0.45.0", + "windows-sys 0.59.0", ] [[package]] @@ -1261,22 +1255,16 @@ version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e" dependencies = [ - "getrandom 0.2.15", + "getrandom 0.2.16", "once_cell", "tiny-keccak", ] [[package]] name = "constant_time_eq" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7144d30dcf0fafbce74250a3963025d8d52177934239851c917d29f1df280c2" - -[[package]] -name = "convert_case" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" +checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" [[package]] name = "convert_case" @@ -1298,16 +1286,6 @@ dependencies = [ "version_check", ] -[[package]] -name = "core-foundation" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" -dependencies = [ - "core-foundation-sys", - "libc", -] - [[package]] name = "core-foundation" version = "0.10.1" @@ -1335,18 +1313,18 @@ dependencies = [ [[package]] name = "cpufeatures" -version = "0.2.12" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" dependencies = [ "libc", ] [[package]] name = "crc" -version = "3.2.1" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69e6e4d7b33a94f0991c26729976b10ebde1d34c3ee82408fb536164fa10d636" +checksum = "9710d3b3739c2e349eb44fe848ad0b7c8cb1e42bd87ee49371df2f7acaf3e675" dependencies = [ "crc-catalog", ] @@ -1413,9 +1391,9 @@ dependencies = [ [[package]] name = "crossbeam-deque" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" dependencies = [ "crossbeam-epoch", "crossbeam-utils", @@ -1432,24 +1410,24 @@ dependencies = [ [[package]] name = "crossbeam-queue" -version = "0.3.11" +version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df0346b5d5e76ac2fe4e327c5fd1118d6be7c51dfb18f9b7922923f287471e35" +checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115" dependencies = [ "crossbeam-utils", ] [[package]] name = "crossbeam-utils" -version = "0.8.20" +version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] name = "crunchy" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" +checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929" [[package]] name = "crypto-common" @@ -1475,20 +1453,20 @@ dependencies = [ [[package]] name = "csv-core" -version = "0.1.11" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5efa2b3d7902f4b634a20cae3c9c4e6209dc4779feb6863329607560143efa70" +checksum = "7d02f3b0da4c6504f86e9cd789d8dbafab48c2321be74e9987593de5a894d93d" dependencies = [ "memchr", ] [[package]] name = "cudarc" -version = "0.12.2" +version = "0.13.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cd76de2aa3a7bdb9a65941ea5a3c688d941688f736a81b2fc5beb88747a7f25" +checksum = "486c221362668c63a1636cfa51463b09574433b39029326cff40864b3ba12b6e" dependencies = [ - "half 2.4.1", + "half", "libloading", ] @@ -1504,12 +1482,12 @@ dependencies = [ [[package]] name = "darling" -version = "0.20.10" +version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" +checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" dependencies = [ - "darling_core 0.20.10", - "darling_macro 0.20.10", + "darling_core 0.20.11", + "darling_macro 0.20.11", ] [[package]] @@ -1528,16 +1506,16 @@ dependencies = [ [[package]] name = "darling_core" -version = "0.20.10" +version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" +checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" dependencies = [ "fnv", "ident_case", "proc-macro2", "quote", "strsim 0.11.1", - "syn 2.0.87", + "syn 2.0.101", ] [[package]] @@ -1553,13 +1531,13 @@ dependencies = [ [[package]] name = "darling_macro" -version = "0.20.10" +version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" +checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ - "darling_core 0.20.10", + "darling_core 0.20.11", "quote", - "syn 2.0.87", + "syn 2.0.101", ] [[package]] @@ -1603,9 +1581,9 @@ checksum = "da692b8d1080ea3045efaab14434d40468c3d8657e42abddfffca87b428f4c1b" [[package]] name = "deranged" -version = "0.3.11" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e" dependencies = [ "powerfmt", "serde", @@ -1619,7 +1597,7 @@ checksum = "30542c1ad912e0e3d22a1935c290e12e8a29d704a420177a31faad4a601a0800" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.101", ] [[package]] @@ -1658,10 +1636,10 @@ version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2d5bcf7b024d6835cfb3d473887cd966994907effbe9227e8c8219824d06c4e8" dependencies = [ - "darling 0.20.10", + "darling 0.20.11", "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.101", ] [[package]] @@ -1681,20 +1659,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" dependencies = [ "derive_builder_core 0.20.2", - "syn 2.0.87", -] - -[[package]] -name = "derive_more" -version = "0.99.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" -dependencies = [ - "convert_case 0.4.0", - "proc-macro2", - "quote", - "rustc_version", - "syn 1.0.109", + "syn 2.0.101", ] [[package]] @@ -1714,7 +1679,7 @@ checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.101", "unicode-xid", ] @@ -1741,10 +1706,10 @@ version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aadef696fce456c704f10186def1bdc0a40e646c9f4f18cf091477acadb731d8" dependencies = [ - "convert_case 0.6.0", + "convert_case", "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.101", ] [[package]] @@ -1808,15 +1773,9 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.101", ] -[[package]] -name = "doc-comment" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" - [[package]] name = "doxygen-rs" version = "0.4.2" @@ -1833,7 +1792,7 @@ dependencies = [ "anyhow", "big_s", "flate2", - "http 1.2.0", + "http 1.3.1", "maplit", "meili-snap", "meilisearch-types", @@ -1861,19 +1820,28 @@ dependencies = [ ] [[package]] -name = "either" -version = "1.13.0" +name = "dyn-stack" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" +checksum = "490bd48eb68fffcfed519b4edbfd82c69cbe741d175b84f0e0cbe8c57cbe0bdd" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" dependencies = [ "serde", ] [[package]] name = "encode_unicode" -version = "0.3.6" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" +checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" [[package]] name = "encoding" @@ -1959,14 +1927,14 @@ dependencies = [ [[package]] name = "enum-as-inner" -version = "0.6.0" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ffccbb6966c05b32ef8fbac435df276c4ae4d3dc55a8cd0eb9745e6c12f546a" +checksum = "a1e6a265c649f3f5979b601d26f1d05ada116434c87741c9493cb56218f76cbc" dependencies = [ - "heck 0.4.1", + "heck", "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.101", ] [[package]] @@ -1986,23 +1954,23 @@ checksum = "a1ab991c1362ac86c61ab6f556cff143daa22e5a15e4e189df818b2fd19fe65b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.101", ] [[package]] name = "equivalent" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "errno" -version = "0.3.8" +version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" +checksum = "cea14ef9355e3beab063703aa9dab15afd25f0667c341310c1e5274bb1d0da18" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -2051,14 +2019,14 @@ dependencies = [ [[package]] name = "filetime" -version = "0.2.22" +version = "0.2.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4029edd3e734da6fe05b6cd7bd2960760a616bd2ddd0d59a0124746d6272af0" +checksum = "35c0522e981e68cbfa8c3f978441a5f34b30b96e146b33cd3359176b50fe8586" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.3.5", - "windows-sys 0.48.0", + "libredox", + "windows-sys 0.59.0", ] [[package]] @@ -2078,7 +2046,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ced92e76e966ca2fd84c8f7aa01a4aea65b0eb6648d72f7c8f3e2764a67fece" dependencies = [ "crc32fast", - "miniz_oxide 0.8.8", + "miniz_oxide", ] [[package]] @@ -2106,9 +2074,9 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "foldhash" -version = "0.1.3" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f81ec6369c545a7d40e4589b5597581fa1c441fe1cce96dd1de43159910a36a2" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" [[package]] name = "form_urlencoded" @@ -2187,7 +2155,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.101", ] [[package]] @@ -2256,7 +2224,7 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce20bbb48248608ba4908b45fe36e17e40f56f8c6bb385ecf5d3c4a1e8b05a22" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "debugid", "fxhash", "serde", @@ -2270,17 +2238,37 @@ version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ab24cc62135b40090e31a76a9b2766a501979f3070fa27f689c27ec04377d32" dependencies = [ - "dyn-stack", - "gemm-c32", - "gemm-c64", - "gemm-common", - "gemm-f16", - "gemm-f32", - "gemm-f64", + "dyn-stack 0.10.0", + "gemm-c32 0.17.1", + "gemm-c64 0.17.1", + "gemm-common 0.17.1", + "gemm-f16 0.17.1", + "gemm-f32 0.17.1", + "gemm-f64 0.17.1", "num-complex", "num-traits", "paste", - "raw-cpuid", + "raw-cpuid 10.7.0", + "seq-macro", +] + +[[package]] +name = "gemm" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab96b703d31950f1aeddded248bc95543c9efc7ac9c4a21fda8703a83ee35451" +dependencies = [ + "dyn-stack 0.13.0", + "gemm-c32 0.18.2", + "gemm-c64 0.18.2", + "gemm-common 0.18.2", + "gemm-f16 0.18.2", + "gemm-f32 0.18.2", + "gemm-f64 0.18.2", + "num-complex", + "num-traits", + "paste", + "raw-cpuid 11.5.0", "seq-macro", ] @@ -2290,12 +2278,27 @@ version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9c030d0b983d1e34a546b86e08f600c11696fde16199f971cd46c12e67512c0" dependencies = [ - "dyn-stack", - "gemm-common", + "dyn-stack 0.10.0", + "gemm-common 0.17.1", "num-complex", "num-traits", "paste", - "raw-cpuid", + "raw-cpuid 10.7.0", + "seq-macro", +] + +[[package]] +name = "gemm-c32" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6db9fd9f40421d00eea9dd0770045a5603b8d684654816637732463f4073847" +dependencies = [ + "dyn-stack 0.13.0", + "gemm-common 0.18.2", + "num-complex", + "num-traits", + "paste", + "raw-cpuid 11.5.0", "seq-macro", ] @@ -2305,12 +2308,27 @@ version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fbb5f2e79fefb9693d18e1066a557b4546cd334b226beadc68b11a8f9431852a" dependencies = [ - "dyn-stack", - "gemm-common", + "dyn-stack 0.10.0", + "gemm-common 0.17.1", "num-complex", "num-traits", "paste", - "raw-cpuid", + "raw-cpuid 10.7.0", + "seq-macro", +] + +[[package]] +name = "gemm-c64" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfcad8a3d35a43758330b635d02edad980c1e143dc2f21e6fd25f9e4eada8edf" +dependencies = [ + "dyn-stack 0.13.0", + "gemm-common 0.18.2", + "num-complex", + "num-traits", + "paste", + "raw-cpuid 11.5.0", "seq-macro", ] @@ -2321,17 +2339,38 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2e7ea062c987abcd8db95db917b4ffb4ecdfd0668471d8dc54734fdff2354e8" dependencies = [ "bytemuck", - "dyn-stack", - "half 2.4.1", + "dyn-stack 0.10.0", + "half", "num-complex", "num-traits", "once_cell", "paste", - "pulp", - "raw-cpuid", + "pulp 0.18.22", + "raw-cpuid 10.7.0", "rayon", "seq-macro", - "sysctl", + "sysctl 0.5.5", +] + +[[package]] +name = "gemm-common" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a352d4a69cbe938b9e2a9cb7a3a63b7e72f9349174a2752a558a8a563510d0f3" +dependencies = [ + "bytemuck", + "dyn-stack 0.13.0", + "half", + "libm", + "num-complex", + "num-traits", + "once_cell", + "paste", + "pulp 0.21.5", + "raw-cpuid 11.5.0", + "rayon", + "seq-macro", + "sysctl 0.6.0", ] [[package]] @@ -2340,14 +2379,32 @@ version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ca4c06b9b11952071d317604acb332e924e817bd891bec8dfb494168c7cedd4" dependencies = [ - "dyn-stack", - "gemm-common", - "gemm-f32", - "half 2.4.1", + "dyn-stack 0.10.0", + "gemm-common 0.17.1", + "gemm-f32 0.17.1", + "half", "num-complex", "num-traits", "paste", - "raw-cpuid", + "raw-cpuid 10.7.0", + "rayon", + "seq-macro", +] + +[[package]] +name = "gemm-f16" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cff95ae3259432f3c3410eaa919033cd03791d81cebd18018393dc147952e109" +dependencies = [ + "dyn-stack 0.13.0", + "gemm-common 0.18.2", + "gemm-f32 0.18.2", + "half", + "num-complex", + "num-traits", + "paste", + "raw-cpuid 11.5.0", "rayon", "seq-macro", ] @@ -2358,12 +2415,27 @@ version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e9a69f51aaefbd9cf12d18faf273d3e982d9d711f60775645ed5c8047b4ae113" dependencies = [ - "dyn-stack", - "gemm-common", + "dyn-stack 0.10.0", + "gemm-common 0.17.1", "num-complex", "num-traits", "paste", - "raw-cpuid", + "raw-cpuid 10.7.0", + "seq-macro", +] + +[[package]] +name = "gemm-f32" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc8d3d4385393304f407392f754cd2dc4b315d05063f62cf09f47b58de276864" +dependencies = [ + "dyn-stack 0.13.0", + "gemm-common 0.18.2", + "num-complex", + "num-traits", + "paste", + "raw-cpuid 11.5.0", "seq-macro", ] @@ -2373,12 +2445,27 @@ version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa397a48544fadf0b81ec8741e5c0fba0043008113f71f2034def1935645d2b0" dependencies = [ - "dyn-stack", - "gemm-common", + "dyn-stack 0.10.0", + "gemm-common 0.17.1", "num-complex", "num-traits", "paste", - "raw-cpuid", + "raw-cpuid 10.7.0", + "seq-macro", +] + +[[package]] +name = "gemm-f64" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35b2a4f76ce4b8b16eadc11ccf2e083252d8237c1b589558a49b0183545015bd" +dependencies = [ + "dyn-stack 0.13.0", + "gemm-common 0.18.2", + "num-complex", + "num-traits", + "paste", + "raw-cpuid 11.5.0", "seq-macro", ] @@ -2400,9 +2487,9 @@ checksum = "36d244a08113319b5ebcabad2b8b7925732d15eec46d7e7ac3c11734f3b7a6ad" [[package]] name = "getrandom" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" dependencies = [ "cfg-if", "js-sys", @@ -2413,31 +2500,31 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.3.1" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8" +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" dependencies = [ "cfg-if", "js-sys", "libc", - "wasi 0.13.3+wasi-0.2.2", + "r-efi", + "wasi 0.14.2+wasi-0.2.4", "wasm-bindgen", - "windows-targets 0.52.6", ] [[package]] name = "gimli" -version = "0.27.3" +version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6c80984affa11d98d1b88b66ac8853f143217b399d3c74116778ff8fdb4ed2e" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" [[package]] name = "git2" -version = "0.19.0" +version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b903b73e45dc0c6c596f2d37eccece7c1c8bb6e4407b001096387c63d0d93724" +checksum = "2deb07a133b1520dc1a5690e9bd08950108873d7ed5de38dcc74d3b5ebffa110" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "libc", "libgit2-sys", "log", @@ -2474,7 +2561,7 @@ dependencies = [ "futures-core", "futures-sink", "futures-util", - "http 0.2.11", + "http 0.2.12", "indexmap", "slab", "tokio", @@ -2484,16 +2571,16 @@ dependencies = [ [[package]] name = "h2" -version = "0.4.5" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa82e28a107a8cc405f0839610bdc9b15f1e25ec7d696aa5cf173edbcb1486ab" +checksum = "a9421a676d1b147b16b82c9225157dc629087ef8ec4d5e2960f9437a90dac0a5" dependencies = [ "atomic-waker", "bytes", "fnv", "futures-core", "futures-sink", - "http 1.2.0", + "http 1.3.1", "indexmap", "slab", "tokio", @@ -2503,21 +2590,15 @@ dependencies = [ [[package]] name = "half" -version = "1.8.2" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" - -[[package]] -name = "half" -version = "2.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888" +checksum = "459196ed295495a68f7d7fe1d84f6c4b7ff0e21fe3017b2f283c6fac3ad803c9" dependencies = [ "bytemuck", "cfg-if", "crunchy", "num-traits", - "rand 0.8.5", + "rand 0.9.1", "rand_distr", ] @@ -2541,19 +2622,19 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.14.3" +version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" dependencies = [ - "ahash 0.8.11", + "ahash 0.8.12", "allocator-api2", ] [[package]] name = "hashbrown" -version = "0.15.2" +version = "0.15.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" +checksum = "84b26c544d002229e640969970a2e74021aadf6e2f96372b9c58eff97de08eb3" dependencies = [ "allocator-api2", "equivalent", @@ -2571,12 +2652,6 @@ dependencies = [ "stable_deref_trait", ] -[[package]] -name = "heck" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" - [[package]] name = "heck" version = "0.5.0" @@ -2589,7 +2664,7 @@ version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a56c94661ddfb51aa9cdfbf102cfcc340aa69267f95ebccc4af08d7c530d393" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "byteorder", "heed-traits", "heed-types", @@ -2622,15 +2697,9 @@ dependencies = [ [[package]] name = "hermit-abi" -version = "0.3.9" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" - -[[package]] -name = "hermit-abi" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" +checksum = "f154ce46856750ed433c8649605bf7ed2de3bc35fd9d2a9f30cddd873c80cb08" [[package]] name = "hex" @@ -2644,7 +2713,7 @@ version = "0.3.2" source = "git+https://github.com/dureuill/hf-hub.git?branch=rust_tls#88d4f11cb9fa079f2912bacb96f5080b16825ce8" dependencies = [ "dirs", - "http 1.2.0", + "http 1.3.1", "indicatif", "log", "rand 0.8.5", @@ -2665,9 +2734,9 @@ dependencies = [ [[package]] name = "http" -version = "0.2.11" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8947b1a6fad4393052c7ba1f4cd97bed3e953a95c79c92ad9b051a04611d9fbb" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" dependencies = [ "bytes", "fnv", @@ -2676,9 +2745,9 @@ dependencies = [ [[package]] name = "http" -version = "1.2.0" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f16ca2af56261c99fba8bac40a10251ce8188205a4c448fbb745a2e4daa76fea" +checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" dependencies = [ "bytes", "fnv", @@ -2687,23 +2756,23 @@ dependencies = [ [[package]] name = "http-body" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cac85db508abc24a2e48553ba12a996e87244a0395ce011e62b37158745d643" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ "bytes", - "http 1.2.0", + "http 1.3.1", ] [[package]] name = "http-body-util" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" dependencies = [ "bytes", - "futures-util", - "http 1.2.0", + "futures-core", + "http 1.3.1", "http-body", "pin-project-lite", ] @@ -2716,9 +2785,9 @@ checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" [[package]] name = "httpdate" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "hyper" @@ -2729,8 +2798,8 @@ dependencies = [ "bytes", "futures-channel", "futures-util", - "h2 0.4.5", - "http 1.2.0", + "h2 0.4.10", + "http 1.3.1", "http-body", "httparse", "httpdate", @@ -2743,21 +2812,20 @@ dependencies = [ [[package]] name = "hyper-rustls" -version = "0.27.2" +version = "0.27.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ee4be2c948921a1a5320b629c4193916ed787a7f7f293fd3f7f5a6c9de74155" +checksum = "03a01595e11bdcec50946522c32dde3fc6914743000a68b93000965f2f02406d" dependencies = [ - "futures-util", - "http 1.2.0", + "http 1.3.1", "hyper", "hyper-util", "rustls", - "rustls-native-certs 0.7.3", + "rustls-native-certs", "rustls-pki-types", "tokio", "tokio-rustls", "tower-service", - "webpki-roots 0.26.1", + "webpki-roots 1.0.0", ] [[package]] @@ -2771,14 +2839,14 @@ dependencies = [ "futures-channel", "futures-core", "futures-util", - "http 1.2.0", + "http 1.3.1", "http-body", "hyper", "ipnet", "libc", "percent-encoding", "pin-project-lite", - "socket2 0.5.10", + "socket2", "tokio", "tower-service", "tracing", @@ -2786,21 +2854,22 @@ dependencies = [ [[package]] name = "icu_collections" -version = "1.5.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" +checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" dependencies = [ "displaydoc", - "yoke", + "potential_utf", + "yoke 0.8.0", "zerofrom", "zerovec", ] [[package]] -name = "icu_locid" -version = "1.5.0" +name = "icu_locale_core" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" +checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a" dependencies = [ "displaydoc", "litemap", @@ -2809,31 +2878,11 @@ dependencies = [ "zerovec", ] -[[package]] -name = "icu_locid_transform" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" -dependencies = [ - "displaydoc", - "icu_locid", - "icu_locid_transform_data", - "icu_provider", - "tinystr", - "zerovec", -] - -[[package]] -name = "icu_locid_transform_data" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" - [[package]] name = "icu_normalizer" -version = "1.5.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" +checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979" dependencies = [ "displaydoc", "icu_collections", @@ -2841,67 +2890,54 @@ dependencies = [ "icu_properties", "icu_provider", "smallvec", - "utf16_iter", - "utf8_iter", - "write16", "zerovec", ] [[package]] name = "icu_normalizer_data" -version = "1.5.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" +checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" [[package]] name = "icu_properties" -version = "1.5.1" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" +checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b" dependencies = [ "displaydoc", "icu_collections", - "icu_locid_transform", + "icu_locale_core", "icu_properties_data", "icu_provider", - "tinystr", + "potential_utf", + "zerotrie", "zerovec", ] [[package]] name = "icu_properties_data" -version = "1.5.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" +checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" [[package]] name = "icu_provider" -version = "1.5.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" +checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af" dependencies = [ "displaydoc", - "icu_locid", - "icu_provider_macros", + "icu_locale_core", "stable_deref_trait", "tinystr", "writeable", - "yoke", + "yoke 0.8.0", "zerofrom", + "zerotrie", "zerovec", ] -[[package]] -name = "icu_provider_macros" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.87", -] - [[package]] name = "ident_case" version = "1.0.1" @@ -2921,9 +2957,9 @@ dependencies = [ [[package]] name = "idna_adapter" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" dependencies = [ "icu_normalizer", "icu_properties", @@ -2955,7 +2991,7 @@ dependencies = [ "libflate", "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.101", ] [[package]] @@ -2968,7 +3004,7 @@ dependencies = [ "bumpalo", "bumparaw-collections", "byte-unit", - "convert_case 0.6.0", + "convert_case", "crossbeam-channel", "csv", "derive_builder 0.20.2", @@ -2999,33 +3035,33 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.7.0" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" +checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" dependencies = [ "equivalent", - "hashbrown 0.15.2", + "hashbrown 0.15.3", "serde", ] [[package]] name = "indicatif" -version = "0.17.7" +version = "0.17.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb28741c9db9a713d93deb3bb9515c20788cef5815265bee4980e87bde7e0f25" +checksum = "183b3088984b400f4cfac3620d5e076c84da5364016b4f49473de574b2586235" dependencies = [ "console", - "instant", "number_prefix", "portable-atomic", "unicode-width", + "web-time", ] [[package]] name = "inout" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" dependencies = [ "generic-array", ] @@ -3047,9 +3083,9 @@ dependencies = [ [[package]] name = "instant" -version = "0.1.12" +version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" dependencies = [ "cfg-if", ] @@ -3083,15 +3119,21 @@ dependencies = [ [[package]] name = "is-terminal" -version = "0.4.13" +version = "0.4.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "261f68e344040fbd0edea105bef17c66edf46f984ddb1115b775ce31be948f4b" +checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9" dependencies = [ - "hermit-abi 0.4.0", + "hermit-abi", "libc", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + [[package]] name = "itertools" version = "0.10.5" @@ -3139,9 +3181,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.11" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] name = "jieba-macros" @@ -3154,9 +3196,9 @@ dependencies = [ [[package]] name = "jieba-rs" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d1bcad6332969e4d48ee568d430e14ee6dea70740c2549d005d87677ebefb0c" +checksum = "b06096b4b61fb4bfdbf16c6a968ea2d6be1ac9617cf3db741c3b641e6c290a35" dependencies = [ "cedarwood", "fxhash", @@ -3169,10 +3211,11 @@ dependencies = [ [[package]] name = "jobserver" -version = "0.1.31" +version = "0.1.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2b099aaa34a9751c5bf0878add70444e1ed2dd73f347be99003d4577277de6e" +checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a" dependencies = [ + "getrandom 0.3.3", "libc", ] @@ -3196,11 +3239,11 @@ dependencies = [ [[package]] name = "jsonwebtoken" -version = "9.3.0" +version = "9.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9ae10193d25051e74945f1ea2d0b42e03cc3b890f7e4cc5faa44997d808193f" +checksum = "5a87cc7a48537badeae96744432de36f4be2b4a34a05a5ef32e9dd8a1c169dde" dependencies = [ - "base64 0.21.7", + "base64 0.22.1", "js-sys", "pem", "ring", @@ -3220,9 +3263,9 @@ dependencies = [ [[package]] name = "kstring" -version = "2.0.0" +version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec3066350882a1cd6d950d055997f379ac37fd39f81cd4d8ed186032eb3c5747" +checksum = "558bf9508a558512042d3095138b1f7b8fe90c5467d94f9f1da28b3731c5dbd1" dependencies = [ "serde", "static_assertions", @@ -3251,9 +3294,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.171" +version = "0.2.172" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6" +checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" [[package]] name = "libflate" @@ -3275,15 +3318,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6e0d73b369f386f1c44abd9c570d5318f55ccde816ff4b562fa452e5182863d" dependencies = [ "core2", - "hashbrown 0.14.3", + "hashbrown 0.14.5", "rle-decode-fast", ] [[package]] name = "libgit2-sys" -version = "0.17.0+1.8.1" +version = "0.18.1+1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10472326a8a6477c3c20a64547b0059e4b0d086869eee31e6d7da728a8eb7224" +checksum = "e1dcb20f84ffcdd825c7a311ae347cce604a6f084a767dec4a4929829645290e" dependencies = [ "cc", "libc", @@ -3293,25 +3336,25 @@ dependencies = [ [[package]] name = "libloading" -version = "0.8.6" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" +checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667" dependencies = [ "cfg-if", - "windows-targets 0.52.6", + "windows-targets 0.53.0", ] [[package]] name = "libm" -version = "0.2.8" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" +checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" [[package]] name = "libmimalloc-sys" -version = "0.1.39" +version = "0.1.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23aa6811d3bd4deb8a84dde645f943476d13b248d818edcf8ce0b2f37f036b44" +checksum = "ec9d6fac27761dabcd4ee73571cdb06b7022dc99089acbe5435691edffaac0f4" dependencies = [ "cc", "libc", @@ -3329,10 +3372,21 @@ dependencies = [ ] [[package]] -name = "libz-sys" -version = "1.1.15" +name = "libredox" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "037731f5d3aaa87a5675e895b63ddff1a87624bc29f77004ea829809654e48f6" +checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" +dependencies = [ + "bitflags 2.9.1", + "libc", + "redox_syscall", +] + +[[package]] +name = "libz-sys" +version = "1.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b70e7a7df205e92a1a4cd9aaae7898dac0aa555503cc0a649494d0d60e7651d" dependencies = [ "cc", "libc", @@ -3471,17 +3525,22 @@ checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" [[package]] name = "linux-raw-sys" -version = "0.4.14" +version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" + +[[package]] +name = "linux-raw-sys" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" [[package]] name = "liquid" -version = "0.26.9" +version = "0.26.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cdcc72b82748f47c2933c172313f5a9aea5b2c4eb3fa4c66b4ea55bb60bb4b1" +checksum = "2a494c3f9dad3cb7ed16f1c51812cbe4b29493d6c2e5cd1e2b87477263d9534d" dependencies = [ - "doc-comment", "liquid-core", "liquid-derive", "liquid-lib", @@ -3490,15 +3549,14 @@ dependencies = [ [[package]] name = "liquid-core" -version = "0.26.9" +version = "0.26.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2752e978ffc53670f3f2e8b3ef09f348d6f7b5474a3be3f8a5befe5382e4effb" +checksum = "fc623edee8a618b4543e8e8505584f4847a4e51b805db1af6d9af0a3395d0d57" dependencies = [ "anymap2", - "itertools 0.13.0", + "itertools 0.14.0", "kstring", "liquid-derive", - "num-traits", "pest", "pest_derive", "regex", @@ -3508,24 +3566,23 @@ dependencies = [ [[package]] name = "liquid-derive" -version = "0.26.8" +version = "0.26.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b51f1d220e3fa869e24cfd75915efe3164bd09bb11b3165db3f37f57bf673e3" +checksum = "de66c928222984aea59fcaed8ba627f388aaac3c1f57dcb05cc25495ef8faefe" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.101", ] [[package]] name = "liquid-lib" -version = "0.26.9" +version = "0.26.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59b1a298d3d2287ee5b1e43840d885b8fdfc37d3f4e90d82aacfd04d021618da" +checksum = "9befeedd61f5995bc128c571db65300aeb50d62e4f0542c88282dbcb5f72372a" dependencies = [ - "itertools 0.13.0", + "itertools 0.14.0", "liquid-core", - "once_cell", "percent-encoding", "regex", "time", @@ -3534,9 +3591,9 @@ dependencies = [ [[package]] name = "litemap" -version = "0.7.4" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104" +checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" [[package]] name = "lmdb-master-sys" @@ -3551,38 +3608,31 @@ dependencies = [ [[package]] name = "local-channel" -version = "0.1.3" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f303ec0e94c6c54447f84f3b0ef7af769858a9c4ef56ef2a986d3dcd4c3fc9c" +checksum = "b6cbc85e69b8df4b8bb8b89ec634e7189099cea8927a276b7384ce5488e53ec8" dependencies = [ "futures-core", "futures-sink", - "futures-util", "local-waker", ] [[package]] name = "local-waker" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e34f76eb3611940e0e7d53a9aaa4e6a3151f69541a282fd0dad5571420c53ff1" +checksum = "4d873d7c67ce09b42110d801813efbc9364414e356be9935700d368351657487" [[package]] name = "lock_api" -version = "0.4.11" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" +checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" dependencies = [ "autocfg", "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.27" @@ -3595,9 +3645,15 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "227748d55f2f0ab4735d87fd623798cb6b664512fe979705f829c9f81c934465" dependencies = [ - "hashbrown 0.15.2", + "hashbrown 0.15.3", ] +[[package]] +name = "lru-slab" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" + [[package]] name = "lzma-rs" version = "0.3.0" @@ -3621,9 +3677,9 @@ dependencies = [ [[package]] name = "macro_rules_attribute" -version = "0.2.0" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a82271f7bc033d84bbca59a3ce3e4159938cb08a9c3aebbe54d215131518a13" +checksum = "65049d7923698040cd0b1ddcced9b0eb14dd22c5f86ae59c3740eab64a676520" dependencies = [ "macro_rules_attribute-proc_macro", "paste", @@ -3631,9 +3687,9 @@ dependencies = [ [[package]] name = "macro_rules_attribute-proc_macro" -version = "0.2.0" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8dd856d451cc0da70e2ef2ce95a18e39a93b7558bedf10201ad28503f918568" +checksum = "670fdfda89751bc4a84ac13eaa63e205cf0fd22b4c9a5fbfa085b63c1f1d3a30" [[package]] name = "manifest-dir-macros" @@ -3644,7 +3700,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.101", ] [[package]] @@ -3738,7 +3794,7 @@ dependencies = [ "serde_urlencoded", "sha-1", "sha2", - "siphasher 1.0.1", + "siphasher", "slice-group-by", "static-files", "sysinfo", @@ -3761,7 +3817,7 @@ dependencies = [ "uuid", "wiremock", "yaup", - "zip 2.3.0", + "zip 2.4.2", ] [[package]] @@ -3791,7 +3847,7 @@ dependencies = [ "anyhow", "bumpalo", "bumparaw-collections", - "convert_case 0.6.0", + "convert_case", "csv", "deserr", "either", @@ -3804,7 +3860,7 @@ dependencies = [ "memmap2", "milli", "roaring", - "rustc-hash 2.1.0", + "rustc-hash 2.1.1", "serde", "serde-cs", "serde_json", @@ -3871,7 +3927,7 @@ dependencies = [ "candle-transformers", "charabia", "concat-arrays", - "convert_case 0.6.0", + "convert_case", "crossbeam-channel", "csv", "deserr", @@ -3884,7 +3940,7 @@ dependencies = [ "fxhash", "geoutils", "grenad", - "hashbrown 0.15.2", + "hashbrown 0.15.3", "heed", "hf-hub", "indexmap", @@ -3909,7 +3965,7 @@ dependencies = [ "rhai", "roaring", "rstar", - "rustc-hash 2.1.0", + "rustc-hash 2.1.1", "serde", "serde_json", "slice-group-by", @@ -3932,9 +3988,9 @@ dependencies = [ [[package]] name = "mimalloc" -version = "0.1.43" +version = "0.1.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68914350ae34959d83f732418d51e2427a794055d0b9529f48259ac07af65633" +checksum = "995942f432bbb4822a7e9c3faa87a695185b0d09273ba85f097b54f4e458f2af" dependencies = [ "libmimalloc-sys", ] @@ -3947,9 +4003,9 @@ checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" [[package]] name = "mime_guess" -version = "2.0.4" +version = "2.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef" +checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" dependencies = [ "mime", "unicase", @@ -3961,15 +4017,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" -[[package]] -name = "miniz_oxide" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" -dependencies = [ - "adler", -] - [[package]] name = "miniz_oxide" version = "0.8.8" @@ -3981,32 +4028,21 @@ dependencies = [ [[package]] name = "mio" -version = "0.8.11" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" +checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" dependencies = [ "libc", "log", "wasi 0.11.0+wasi-snapshot-preview1", - "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", + "windows-sys 0.59.0", ] [[package]] name = "monostate" -version = "0.1.9" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15f370ae88093ec6b11a710dec51321a61d420fafd1bad6e30d01bd9c920e8ee" +checksum = "aafe1be9d0c75642e3e50fedc7ecadf1ef1cbce6eb66462153fc44245343fbee" dependencies = [ "monostate-impl", "serde", @@ -4014,13 +4050,13 @@ dependencies = [ [[package]] name = "monostate-impl" -version = "0.1.9" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "371717c0a5543d6a800cac822eac735aa7d2d2fbb41002e9856a4089532dbdce" +checksum = "c402a4092d5e204f32c9e155431046831fa712637043c58cb73bc6bc6c9663b5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.101", ] [[package]] @@ -4164,33 +4200,33 @@ dependencies = [ [[package]] name = "num_cpus" -version = "1.16.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b" dependencies = [ - "hermit-abi 0.3.9", + "hermit-abi", "libc", ] [[package]] name = "num_enum" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02339744ee7253741199f897151b38e72257d13802d4ee837285cc2990a90845" +checksum = "4e613fc340b2220f734a8595782c551f1250e969d87d3be1ae0579e8d4065179" dependencies = [ "num_enum_derive", ] [[package]] name = "num_enum_derive" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "681030a937600a36906c185595136d26abfebb4aa9c65701cefcaf8578bb982b" +checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56" dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.101", ] [[package]] @@ -4210,9 +4246,9 @@ checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" [[package]] name = "object" -version = "0.31.1" +version = "0.36.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bda667d9f2b5051b8833f59f3bf748b28ef54f850f4fcb389a252aa383866d1" +checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" dependencies = [ "memchr", ] @@ -4230,12 +4266,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] -name = "onig" -version = "6.4.0" +name = "once_cell_polyfill" +version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c4b31c8722ad9171c6d77d3557db078cab2bd50afcc9d09c8b315c59df8ca4f" +checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" + +[[package]] +name = "onig" +version = "6.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "336b9c63443aceef14bea841b899035ae3abe89b7c486aaf4c5bd8aafedac3f0" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.9.1", "libc", "once_cell", "onig_sys", @@ -4243,9 +4285,9 @@ dependencies = [ [[package]] name = "onig_sys" -version = "69.8.1" +version = "69.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b829e3d7e9cc74c7e315ee8edb185bf4190da5acde74afd7fc59c35b1f086e7" +checksum = "c7f86c6eef3d6df15f23bcfb6af487cbd2fed4e5581d58d5bf1f5f8b7f6727dc" dependencies = [ "cc", "pkg-config", @@ -4253,9 +4295,9 @@ dependencies = [ [[package]] name = "oorandom" -version = "11.1.3" +version = "11.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" +checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" [[package]] name = "openssl-probe" @@ -4302,9 +4344,9 @@ dependencies = [ [[package]] name = "parking_lot" -version = "0.12.3" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" dependencies = [ "lock_api", "parking_lot_core", @@ -4312,22 +4354,22 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.8" +version = "0.9.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447" +checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.3.5", + "redox_syscall", "smallvec", - "windows-targets 0.48.1", + "windows-targets 0.52.6", ] [[package]] name = "paste" -version = "1.0.14" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" [[package]] name = "path-matchers" @@ -4356,11 +4398,11 @@ dependencies = [ [[package]] name = "pem" -version = "3.0.3" +version = "3.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b8fcc794035347fb64beda2d3b462595dd2753e3f268d89c5aae77e8cf2c310" +checksum = "38af38e8470ac9dee3ce1bae1af9c1671fffc44ddfd8bd1d0a3445bf349a8ef3" dependencies = [ - "base64 0.21.7", + "base64 0.22.1", "serde", ] @@ -4380,19 +4422,20 @@ dependencies = [ [[package]] name = "pest" -version = "2.7.2" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1acb4a4365a13f749a93f1a094a7805e5cfa0955373a9de860d962eaa3a5fe5a" +checksum = "198db74531d58c70a361c42201efde7e2591e976d518caf7662a47dc5720e7b6" dependencies = [ - "thiserror 1.0.69", + "memchr", + "thiserror 2.0.12", "ucd-trie", ] [[package]] name = "pest_derive" -version = "2.7.2" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "666d00490d4ac815001da55838c500eafb0320019bbaa44444137c48b443a853" +checksum = "d725d9cfd79e87dccc9341a2ef39d1b6f6353d68c4b33c177febbe1a402c97c5" dependencies = [ "pest", "pest_generator", @@ -4400,22 +4443,22 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.7.2" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68ca01446f50dbda87c1786af8770d535423fa8a53aec03b8f4e3d7eb10e0929" +checksum = "db7d01726be8ab66ab32f9df467ae8b1148906685bbe75c82d1e65d7f5b3f841" dependencies = [ "pest", "pest_meta", "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.101", ] [[package]] name = "pest_meta" -version = "2.7.2" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56af0a30af74d0445c0bf6d9d051c979b516a1a5af790d251daee76005420a48" +checksum = "7f9f832470494906d1fca5329f8ab5791cc60beb230c74815dff541cbd2b5ca0" dependencies = [ "once_cell", "pest", @@ -4424,9 +4467,9 @@ dependencies = [ [[package]] name = "phf" -version = "0.11.2" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc" +checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" dependencies = [ "phf_macros", "phf_shared", @@ -4434,9 +4477,9 @@ dependencies = [ [[package]] name = "phf_codegen" -version = "0.11.2" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8d39688d359e6b34654d328e262234662d16cc0f60ec8dcbe5e718709342a5a" +checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a" dependencies = [ "phf_generator", "phf_shared", @@ -4444,9 +4487,9 @@ dependencies = [ [[package]] name = "phf_generator" -version = "0.11.2" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0" +checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" dependencies = [ "phf_shared", "rand 0.8.5", @@ -4454,44 +4497,44 @@ dependencies = [ [[package]] name = "phf_macros" -version = "0.11.2" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3444646e286606587e49f3bcf1679b8cef1dc2c5ecc29ddacaffc305180d464b" +checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216" dependencies = [ "phf_generator", "phf_shared", "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.101", ] [[package]] name = "phf_shared" -version = "0.11.2" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b" +checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" dependencies = [ - "siphasher 0.3.11", + "siphasher", ] [[package]] name = "pin-project" -version = "1.1.4" +version = "1.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0302c4a0442c456bd56f841aee5c3bfd17967563f6fadc9ceb9f9c23cf3807e0" +checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.4" +version = "1.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "266c042b60c9c76b8d53061e52b2e0d1116abc57cefc8c5cd671619a56ac3690" +checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.101", ] [[package]] @@ -4514,9 +4557,9 @@ checksum = "16f2611cd06a1ac239a0cea4521de9eb068a6ca110324ee00631aa68daa74fc0" [[package]] name = "pkg-config" -version = "0.3.30" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" [[package]] name = "platform-dirs" @@ -4529,9 +4572,9 @@ dependencies = [ [[package]] name = "plotters" -version = "0.3.5" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2c224ba00d7cadd4d5c660deaf2098e5e80e07846537c51f9cfa4be50c1fd45" +checksum = "5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747" dependencies = [ "num-traits", "plotters-backend", @@ -4542,24 +4585,33 @@ dependencies = [ [[package]] name = "plotters-backend" -version = "0.3.5" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e76628b4d3a7581389a35d5b6e2139607ad7c75b17aed325f210aa91f4a9609" +checksum = "df42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172a" [[package]] name = "plotters-svg" -version = "0.3.5" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38f6d39893cca0701371e3c27294f09797214b86f1fb951b89ade8ec04e2abab" +checksum = "51bae2ac328883f7acdfea3d66a7c35751187f870bc81f94563733a154d7a670" dependencies = [ "plotters-backend", ] [[package]] name = "portable-atomic" -version = "1.5.1" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bccab0e7fd7cc19f820a1c8c91720af652d0c88dc9664dd72aef2614f04af3b" +checksum = "350e9b48cbc6b0e028b0473b114454c6316e57336ee184ceab6e53f72c178b3e" + +[[package]] +name = "potential_utf" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5a7c30837279ca13e7c867e9e40053bc68740f988cb07f7ca6df43cc734b585" +dependencies = [ + "zerovec", +] [[package]] name = "powerfmt" @@ -4569,47 +4621,27 @@ checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] name = "ppv-lite86" -version = "0.2.17" +version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] [[package]] name = "proc-macro-crate" -version = "3.1.0" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d37c51ca738a55da99dc0c4a34860fd675453b8b36209178c2249bb13651284" +checksum = "edce586971a4dfaa28950c6f18ed55e0406c1ab88bbce2c6f6293a7aaba73d35" dependencies = [ - "toml_edit 0.21.0", -] - -[[package]] -name = "proc-macro-error" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" -dependencies = [ - "proc-macro-error-attr", - "proc-macro2", - "quote", - "version_check", -] - -[[package]] -name = "proc-macro-error-attr" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" -dependencies = [ - "proc-macro2", - "quote", - "version_check", + "toml_edit", ] [[package]] name = "proc-macro2" -version = "1.0.89" +version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" dependencies = [ "unicode-ident", ] @@ -4620,10 +4652,10 @@ version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cc5b72d8145275d844d4b5f6d4e1eef00c8cd889edb6035c21675d1bb1f45c9f" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "hex", "procfs-core", - "rustix", + "rustix 0.38.44", ] [[package]] @@ -4632,7 +4664,7 @@ version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "239df02d8349b06fc07398a3a1697b06418223b1c7725085e801e7c0fc6a12ec" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "hex", ] @@ -4695,9 +4727,9 @@ dependencies = [ [[package]] name = "pulp" -version = "0.18.9" +version = "0.18.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03457ac216146f43f921500bac4e892d5cd32b0479b929cbfc90f95cd6c599c2" +checksum = "a0a01a0dc67cf4558d279f0c25b0962bd08fc6dec0137699eae304103e882fe6" dependencies = [ "bytemuck", "libm", @@ -4706,61 +4738,89 @@ dependencies = [ ] [[package]] -name = "quinn" -version = "0.11.2" +name = "pulp" +version = "0.21.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4ceeeeabace7857413798eb1ffa1e9c905a9946a57d81fb69b4b71c4d8eb3ad" +checksum = "96b86df24f0a7ddd5e4b95c94fc9ed8a98f1ca94d3b01bdce2824097e7835907" +dependencies = [ + "bytemuck", + "cfg-if", + "libm", + "num-complex", + "reborrow", + "version_check", +] + +[[package]] +name = "quinn" +version = "0.11.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "626214629cda6781b6dc1d316ba307189c85ba657213ce642d9c77670f8202c8" dependencies = [ "bytes", + "cfg_aliases", "pin-project-lite", "quinn-proto", "quinn-udp", - "rustc-hash 1.1.0", + "rustc-hash 2.1.1", "rustls", - "thiserror 1.0.69", + "socket2", + "thiserror 2.0.12", "tokio", "tracing", + "web-time", ] [[package]] name = "quinn-proto" -version = "0.11.8" +version = "0.11.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fadfaed2cd7f389d0161bb73eeb07b7b78f8691047a6f3e73caaeae55310a4a6" +checksum = "49df843a9161c85bb8aae55f101bc0bac8bcafd637a620d9122fd7e0b2f7422e" dependencies = [ "bytes", - "rand 0.8.5", + "getrandom 0.3.3", + "lru-slab", + "rand 0.9.1", "ring", - "rustc-hash 2.1.0", + "rustc-hash 2.1.1", "rustls", + "rustls-pki-types", "slab", - "thiserror 1.0.69", + "thiserror 2.0.12", "tinyvec", "tracing", + "web-time", ] [[package]] name = "quinn-udp" -version = "0.5.2" +version = "0.5.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9096629c45860fc7fb143e125eb826b5e721e10be3263160c7d60ca832cf8c46" +checksum = "ee4e529991f949c5e25755532370b8af5d114acae52326361d68d47af64aa842" dependencies = [ + "cfg_aliases", "libc", "once_cell", - "socket2 0.5.10", + "socket2", "tracing", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] name = "quote" -version = "1.0.36" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" dependencies = [ "proc-macro2", ] +[[package]] +name = "r-efi" +version = "5.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" + [[package]] name = "radium" version = "0.7.0" @@ -4814,7 +4874,7 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.15", + "getrandom 0.2.16", ] [[package]] @@ -4823,17 +4883,17 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" dependencies = [ - "getrandom 0.3.1", + "getrandom 0.3.3", ] [[package]] name = "rand_distr" -version = "0.4.3" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32cb0b9bc82b0a0876c2dd994a7e7a2683d3e7390ca40e6886785ef0c7e3ee31" +checksum = "6a8615d50dcf34fa31f7ab52692afec947c4dd0ab803cc87cb3b0b4570ff7463" dependencies = [ "num-traits", - "rand 0.8.5", + "rand 0.9.1", ] [[package]] @@ -4845,6 +4905,15 @@ dependencies = [ "bitflags 1.3.2", ] +[[package]] +name = "raw-cpuid" +version = "11.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6df7ab838ed27997ba19a4664507e6f82b41fe6e20be42929332156e5e85146" +dependencies = [ + "bitflags 2.9.1", +] + [[package]] name = "rayon" version = "1.10.0" @@ -4893,30 +4962,21 @@ checksum = "03251193000f4bd3b042892be858ee50e8b3719f2b08e5833ac4353724632430" [[package]] name = "redox_syscall" -version = "0.2.16" +version = "0.5.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +checksum = "928fca9cf2aa042393a8325b9ead81d2f0df4cb12e1e24cef072922ccd99c5af" dependencies = [ - "bitflags 1.3.2", -] - -[[package]] -name = "redox_syscall" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" -dependencies = [ - "bitflags 1.3.2", + "bitflags 2.9.1", ] [[package]] name = "redox_users" -version = "0.4.3" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" +checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" dependencies = [ - "getrandom 0.2.15", - "redox_syscall 0.2.16", + "getrandom 0.2.16", + "libredox", "thiserror 1.0.69", ] @@ -4975,7 +5035,7 @@ dependencies = [ "futures-channel", "futures-core", "futures-util", - "http 1.2.0", + "http 1.3.1", "http-body", "http-body-util", "hyper", @@ -4991,7 +5051,7 @@ dependencies = [ "pin-project-lite", "quinn", "rustls", - "rustls-native-certs 0.8.1", + "rustls-native-certs", "rustls-pki-types", "serde", "serde_json", @@ -5032,8 +5092,8 @@ name = "rhai" version = "1.20.0" source = "git+https://github.com/rhaiscript/rhai?rev=ef3df63121d27aacd838f366f2b83fd65f20a1e4#ef3df63121d27aacd838f366f2b83fd65f20a1e4" dependencies = [ - "ahash 0.8.11", - "bitflags 2.9.0", + "ahash 0.8.12", + "bitflags 2.9.1", "instant", "num-traits", "once_cell", @@ -5051,7 +5111,7 @@ source = "git+https://github.com/rhaiscript/rhai?rev=ef3df63121d27aacd838f366f2b dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.101", ] [[package]] @@ -5062,7 +5122,7 @@ checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" dependencies = [ "cc", "cfg-if", - "getrandom 0.2.15", + "getrandom 0.2.16", "libc", "untrusted", "windows-sys 0.52.0", @@ -5070,9 +5130,9 @@ dependencies = [ [[package]] name = "rkyv" -version = "0.7.44" +version = "0.7.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cba464629b3394fc4dbc6f940ff8f5b4ff5c7aef40f29166fd4ad12acbc99c0" +checksum = "9008cd6385b9e161d8229e1f6549dd23c3d022f132a2ea37ac3a10ac4935779b" dependencies = [ "bitvec", "bytecheck", @@ -5088,9 +5148,9 @@ dependencies = [ [[package]] name = "rkyv_derive" -version = "0.7.44" +version = "0.7.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7dddfff8de25e6f62b9d64e6e432bf1c6736c57d20323e15ee10435fbda7c65" +checksum = "503d1d27590a2b0a3a4ca4c94755aa2875657196ecbf401a42eff41d7de532c0" dependencies = [ "proc-macro2", "quote", @@ -5105,9 +5165,9 @@ checksum = "3582f63211428f83597b51b2ddb88e2a91a9d52d12831f9d08f5e624e8977422" [[package]] name = "roaring" -version = "0.10.10" +version = "0.10.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a652edd001c53df0b3f96a36a8dc93fce6866988efc16808235653c6bcac8bf2" +checksum = "19e8d2cfa184d94d0726d650a9f4a1be7f9b76ac9fdb954219878dc00c1c1e7b" dependencies = [ "bytemuck", "byteorder", @@ -5128,9 +5188,9 @@ dependencies = [ [[package]] name = "rust_decimal" -version = "1.35.0" +version = "1.37.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1790d1c4c0ca81211399e0e0af16333276f375209e71a37b67698a373db5b47a" +checksum = "faa7de2ba56ac291bd90c6b9bece784a52ae1411f9506544b3eae36dd2356d50" dependencies = [ "arrayvec", "borsh", @@ -5144,9 +5204,9 @@ dependencies = [ [[package]] name = "rustc-demangle" -version = "0.1.23" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" [[package]] name = "rustc-hash" @@ -5156,37 +5216,41 @@ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] name = "rustc-hash" -version = "2.1.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7fb8039b3032c191086b10f11f319a6e99e1e82889c5cc6046f515c9db1d497" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" [[package]] -name = "rustc_version" -version = "0.4.0" +name = "rustix" +version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ - "semver", + "bitflags 2.9.1", + "errno", + "libc", + "linux-raw-sys 0.4.15", + "windows-sys 0.59.0", ] [[package]] name = "rustix" -version = "0.38.41" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7f649912bc1495e167a6edee79151c84b1bad49748cb4f1f1167f459f6224f6" +checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "errno", "libc", - "linux-raw-sys", - "windows-sys 0.52.0", + "linux-raw-sys 0.9.4", + "windows-sys 0.59.0", ] [[package]] name = "rustls" -version = "0.23.20" +version = "0.23.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5065c3f250cbd332cd894be57c40fa52387247659b14a2d6041d121547903b1b" +checksum = "730944ca083c1c233a75c09f199e973ca499344a2b7ba9e755c457e86fb4a321" dependencies = [ "log", "once_cell", @@ -5197,19 +5261,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "rustls-native-certs" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5bfb394eeed242e909609f56089eecfe5fda225042e8b171791b9c95f5931e5" -dependencies = [ - "openssl-probe", - "rustls-pemfile", - "rustls-pki-types", - "schannel", - "security-framework 2.11.1", -] - [[package]] name = "rustls-native-certs" version = "0.8.1" @@ -5219,7 +5270,7 @@ dependencies = [ "openssl-probe", "rustls-pki-types", "schannel", - "security-framework 3.2.0", + "security-framework", ] [[package]] @@ -5233,15 +5284,19 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.10.1" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2bf47e6ff922db3825eb750c4e2ff784c6ff8fb9e13046ef6a1d1c5401b0b37" +checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" +dependencies = [ + "web-time", + "zeroize", +] [[package]] name = "rustls-webpki" -version = "0.102.8" +version = "0.103.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" +checksum = "e4a72fe2bcf7a6ac6fd7d0b9e5cb68aeb7d4c0a0271730218b3e92d43b4eb435" dependencies = [ "ring", "rustls-pki-types", @@ -5250,21 +5305,21 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.19" +version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4" +checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" [[package]] name = "ryu" -version = "1.0.17" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" [[package]] name = "safetensors" -version = "0.4.2" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d980e6bfb34436fb0a81e42bc41af43f11805bbbca443e7f68e9faaabe669ed" +checksum = "44560c11236a6130a46ce36c836a62936dc81ebf8c36a37947423571be0e55b6" dependencies = [ "serde", "serde_json", @@ -5310,27 +5365,14 @@ dependencies = [ "zeroize", ] -[[package]] -name = "security-framework" -version = "2.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" -dependencies = [ - "bitflags 2.9.0", - "core-foundation 0.9.4", - "core-foundation-sys", - "libc", - "security-framework-sys", -] - [[package]] name = "security-framework" version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "271720403f46ca04f7ba6f55d438f8bd878d6b8ca0a1046e8228c4145bcbb316" dependencies = [ - "bitflags 2.9.0", - "core-foundation 0.10.1", + "bitflags 2.9.1", + "core-foundation", "core-foundation-sys", "libc", "security-framework-sys", @@ -5348,9 +5390,9 @@ dependencies = [ [[package]] name = "segment" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dd0f21b6eb87a45a7cce06075a29ccdb42658a6eb84bf40c8fc179479630609" +checksum = "971369158e31ad10bd73b558625f99de39554a2f00c2ff886a6796d950e69664" dependencies = [ "async-trait", "reqwest", @@ -5362,18 +5404,18 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.18" +version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918" +checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" dependencies = [ "serde", ] [[package]] name = "seq-macro" -version = "0.3.5" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3f0bf26fd526d2a95683cd0f87bf103b8539e2ca1ef48ce002d67aad59aa0b4" +checksum = "1bc711410fbe7399f390ca1c3b60ad0f53f80e95c5eb935e52268a0e2cd49acc" [[package]] name = "serde" @@ -5401,7 +5443,7 @@ checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.101", ] [[package]] @@ -5507,9 +5549,9 @@ dependencies = [ [[package]] name = "sha2" -version = "0.10.8" +version = "0.10.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", "cpufeatures", @@ -5533,9 +5575,9 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "signal-hook-registry" -version = "1.4.1" +version = "1.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" +checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410" dependencies = [ "libc", ] @@ -5548,34 +5590,28 @@ checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" [[package]] name = "simdutf8" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f27f6278552951f1f2b8cf9da965d10969b2efdea95a6ec47987ab46edfe263a" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" [[package]] name = "similar" -version = "2.2.1" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "420acb44afdae038210c99e69aae24109f32f15500aa708e81d46c9f29d55fcf" +checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa" [[package]] name = "simple_asn1" -version = "0.6.2" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adc4e5204eb1910f40f9cfa375f6f05b68c3abac4b6fd879c8ff5e7ae8a0a085" +checksum = "297f631f50729c8c99b84667867963997ec0b50f32b2a7dbcab828ef0541e8bb" dependencies = [ "num-bigint", "num-traits", - "thiserror 1.0.69", + "thiserror 2.0.12", "time", ] -[[package]] -name = "siphasher" -version = "0.3.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" - [[package]] name = "siphasher" version = "1.0.1" @@ -5584,9 +5620,9 @@ checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" [[package]] name = "slab" -version = "0.4.8" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" dependencies = [ "autocfg", ] @@ -5609,9 +5645,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.13.2" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" +checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" dependencies = [ "serde", ] @@ -5628,16 +5664,6 @@ dependencies = [ "version_check", ] -[[package]] -name = "socket2" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" -dependencies = [ - "libc", - "winapi", -] - [[package]] name = "socket2" version = "0.5.10" @@ -5730,18 +5756,18 @@ version = "0.27.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c77a8c5abcaf0f9ce05d62342b7d298c346515365c36b673df4ebe3ced01fde8" dependencies = [ - "heck 0.5.0", + "heck", "proc-macro2", "quote", "rustversion", - "syn 2.0.87", + "syn 2.0.101", ] [[package]] name = "subtle" -version = "2.5.0" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" @@ -5756,32 +5782,20 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.87" +version = "2.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d" +checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] -[[package]] -name = "syn_derive" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1329189c02ff984e9736652b1631330da25eaa6bc639089ed4915d25446cbe7b" -dependencies = [ - "proc-macro-error", - "proc-macro2", - "quote", - "syn 2.0.87", -] - [[package]] name = "sync_wrapper" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" dependencies = [ "futures-core", ] @@ -5797,13 +5811,13 @@ dependencies = [ [[package]] name = "synstructure" -version = "0.13.1" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.101", ] [[package]] @@ -5812,7 +5826,21 @@ version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec7dddc5f0fee506baf8b9fdb989e242f17e4b11c61dfbb0635b705217199eea" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", + "byteorder", + "enum-as-inner", + "libc", + "thiserror 1.0.69", + "walkdir", +] + +[[package]] +name = "sysctl" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01198a2debb237c62b6826ec7081082d951f46dbb64b0e8c7649a452230d1dfc" +dependencies = [ + "bitflags 2.9.1", "byteorder", "enum-as-inner", "libc", @@ -5862,15 +5890,14 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.15.0" +version = "3.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a8a559c81686f576e8cd0290cd2a24a2a9ad80c98b3478856500fcbd7acd704" +checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1" dependencies = [ - "cfg-if", "fastrand", - "getrandom 0.2.15", + "getrandom 0.3.3", "once_cell", - "rustix", + "rustix 1.0.7", "windows-sys 0.59.0", ] @@ -5885,9 +5912,9 @@ dependencies = [ [[package]] name = "thin-vec" -version = "0.2.13" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a38c90d48152c236a3ab59271da4f4ae63d678c5d7ad6b7714d7cb9760be5e4b" +checksum = "144f754d318415ac792f9d69fc87abbbfc043ce2ef041c60f16ad828f638717d" dependencies = [ "serde", ] @@ -5918,7 +5945,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.101", ] [[package]] @@ -5929,7 +5956,7 @@ checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.101", ] [[package]] @@ -5960,9 +5987,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.37" +version = "0.3.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21" +checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40" dependencies = [ "deranged", "itoa", @@ -5977,15 +6004,15 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.2" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" +checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c" [[package]] name = "time-macros" -version = "0.2.19" +version = "0.2.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2834e6017e3e5e4b9834939793b282bc03b37a3336245fa820e35e233e2a85de" +checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49" dependencies = [ "num-conv", "time-core", @@ -6002,9 +6029,9 @@ dependencies = [ [[package]] name = "tinystr" -version = "0.7.6" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" +checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b" dependencies = [ "displaydoc", "zerovec", @@ -6022,9 +6049,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.6.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +checksum = "09b3661f17e86524eccd4371ab0429194e0d7c008abb45f7a7495b1719463c71" dependencies = [ "tinyvec_macros", ] @@ -6043,7 +6070,7 @@ dependencies = [ "aho-corasick", "derive_builder 0.12.0", "esaxx-rs", - "getrandom 0.2.15", + "getrandom 0.2.16", "itertools 0.12.1", "lazy_static", "log", @@ -6074,11 +6101,11 @@ dependencies = [ "backtrace", "bytes", "libc", - "mio 1.0.3", + "mio", "parking_lot", "pin-project-lite", "signal-hook-registry", - "socket2 0.5.10", + "socket2", "tokio-macros", "windows-sys 0.52.0", ] @@ -6091,17 +6118,16 @@ checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.101", ] [[package]] name = "tokio-rustls" -version = "0.26.0" +version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" +checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b" dependencies = [ "rustls", - "rustls-pki-types", "tokio", ] @@ -6131,49 +6157,45 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.19" +version = "0.8.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" +checksum = "05ae329d1f08c4d17a59bed7ff5b5a769d062e64a62d34a3261b219e62cd5aae" dependencies = [ "serde", "serde_spanned", "toml_datetime", - "toml_edit 0.22.22", + "toml_edit", ] [[package]] name = "toml_datetime" -version = "0.6.8" +version = "0.6.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" +checksum = "3da5db5a963e24bc68be8b17b6fa82814bb22ee8660f192bb182771d498f09a3" dependencies = [ "serde", ] [[package]] name = "toml_edit" -version = "0.21.0" +version = "0.22.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d34d383cd00a163b4a5b85053df514d45bc330f6de7737edfe0a93311d1eaa03" -dependencies = [ - "indexmap", - "toml_datetime", - "winnow 0.5.40", -] - -[[package]] -name = "toml_edit" -version = "0.22.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" +checksum = "310068873db2c5b3e7659d2cc35d21855dbafa50d1ce336397c666e3cb08137e" dependencies = [ "indexmap", "serde", "serde_spanned", "toml_datetime", - "winnow 0.6.22", + "toml_write", + "winnow", ] +[[package]] +name = "toml_write" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfb942dfe1d8e29a7ee7fcbde5bd2b9a25fb89aa70caea2eba3bee836ff41076" + [[package]] name = "tower" version = "0.5.2" @@ -6191,14 +6213,14 @@ dependencies = [ [[package]] name = "tower-http" -version = "0.6.5" +version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cc2d9e086a412a451384326f521c8123a99a466b329941a9403696bff9b0da2" +checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "bytes", "futures-util", - "http 1.2.0", + "http 1.3.1", "http-body", "iri-string", "pin-project-lite", @@ -6233,9 +6255,9 @@ dependencies = [ [[package]] name = "tracing-actix-web" -version = "0.7.15" +version = "0.7.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54a9f5c1aca50ebebf074ee665b9f99f2e84906dcf6b993a0d0090edb835166d" +checksum = "2340b7722695166c7fc9b3e3cd1166e7c74fedb9075b8f0c74d3822d2e41caf5" dependencies = [ "actix-web", "mutually_exclusive_features", @@ -6252,7 +6274,7 @@ checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.101", ] [[package]] @@ -6331,21 +6353,21 @@ dependencies = [ [[package]] name = "try-lock" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "typenum" -version = "1.17.0" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" +checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" [[package]] name = "ucd-trie" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" +checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" [[package]] name = "uell" @@ -6358,48 +6380,52 @@ dependencies = [ [[package]] name = "ug" -version = "0.0.2" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4eef2ebfc18c67a6dbcacd9d8a4d85e0568cc58c82515552382312c2730ea13" +checksum = "03719c61a91b51541f076dfdba45caacf750b230cefaa4b32d6f5411c3f7f437" dependencies = [ - "half 2.4.1", + "gemm 0.18.2", + "half", + "libloading", + "memmap2", "num", + "num-traits", + "num_cpus", + "rayon", + "safetensors", "serde", - "serde_json", "thiserror 1.0.69", + "tracing", + "yoke 0.7.5", ] [[package]] name = "ug-cuda" -version = "0.0.2" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c4dcab280ad0ef3957e153a82dcad608c954d02cf253b695322f502d1f8902e" +checksum = "50758486d7941f8b0a636ba7e29455c07071f41590beac1fd307ec893e8db69a" dependencies = [ "cudarc", - "half 2.4.1", + "half", "serde", - "serde_json", "thiserror 1.0.69", "ug", ] [[package]] name = "unescaper" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c878a167baa8afd137494101a688ef8c67125089ff2249284bd2b5f9bfedb815" +checksum = "c01d12e3a56a4432a8b436f293c25f4808bdf9e9f9f98f9260bba1f1bc5a1f26" dependencies = [ - "thiserror 1.0.69", + "thiserror 2.0.12", ] [[package]] name = "unicase" -version = "2.6.0" +version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" -dependencies = [ - "version_check", -] +checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" [[package]] name = "unicode-blocks" @@ -6409,9 +6435,9 @@ checksum = "6b12e05d9e06373163a9bb6bb8c263c261b396643a99445fe6b9811fd376581b" [[package]] name = "unicode-ident" -version = "1.0.12" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" [[package]] name = "unicode-normalization" @@ -6433,15 +6459,15 @@ dependencies = [ [[package]] name = "unicode-segmentation" -version = "1.11.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" [[package]] name = "unicode-width" -version = "0.1.11" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" +checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" [[package]] name = "unicode-xid" @@ -6483,7 +6509,7 @@ dependencies = [ "serde_json", "socks", "url", - "webpki-roots 0.26.1", + "webpki-roots 0.26.11", ] [[package]] @@ -6504,17 +6530,11 @@ version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" -[[package]] -name = "utf16_iter" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" - [[package]] name = "utf8-width" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5190c9442dcdaf0ddd50f37420417d219ae5261bbf5db120d0f9bab996c9cba1" +checksum = "86bd8d4e895da8537e5315b8254664e6b769c4ff3db18321b297a1e7004392e3" [[package]] name = "utf8_iter" @@ -6524,9 +6544,9 @@ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" [[package]] name = "utf8parse" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "utoipa" @@ -6549,7 +6569,7 @@ dependencies = [ "proc-macro2", "quote", "regex", - "syn 2.0.87", + "syn 2.0.101", "uuid", ] @@ -6567,19 +6587,21 @@ dependencies = [ [[package]] name = "uuid" -version = "1.11.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8c5f0a0af699448548ad1a2fbf920fb4bee257eae39953ba95cb84891a0446a" +checksum = "3cf4199d1e5d15ddd86a694e4d0dffa9c323ce759fea589f00fef9d81cc1931d" dependencies = [ - "getrandom 0.2.15", + "getrandom 0.3.3", + "js-sys", "serde", + "wasm-bindgen", ] [[package]] name = "valuable" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" [[package]] name = "vcpkg" @@ -6589,9 +6611,9 @@ checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" [[package]] name = "vergen" -version = "9.0.2" +version = "9.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31f25fc8f8f05df455c7941e87f093ad22522a9ff33d7a027774815acf6f0639" +checksum = "6b2bf58be11fc9414104c6d3a2e464163db5ef74b12296bda593cac37b6e4777" dependencies = [ "anyhow", "derive_builder 0.20.2", @@ -6601,9 +6623,9 @@ dependencies = [ [[package]] name = "vergen-git2" -version = "1.0.2" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e63e069d8749fead1e3bab7a9d79e8fb90516b2ec66fc2243a798ecdc1a31d7" +checksum = "4f6ee511ec45098eabade8a0750e76eec671e7fb2d9360c563911336bea9cac1" dependencies = [ "anyhow", "derive_builder 0.20.2", @@ -6616,9 +6638,9 @@ dependencies = [ [[package]] name = "vergen-lib" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0c767e6751c09fc85cde58722cf2f1007e80e4c8d5a4321fc90d83dc54ca147" +checksum = "9b07e6010c0f3e59fcb164e0163834597da68d1f864e2b8ca49f74de01e9c166" dependencies = [ "anyhow", "derive_builder 0.20.2", @@ -6627,9 +6649,9 @@ dependencies = [ [[package]] name = "version_check" -version = "0.9.4" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "walkdir" @@ -6669,9 +6691,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasi" -version = "0.13.3+wasi-0.2.2" +version = "0.14.2+wasi-0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2" +checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" dependencies = [ "wit-bindgen-rt", ] @@ -6698,18 +6720,19 @@ dependencies = [ "log", "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.101", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.37" +version = "0.4.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c02dbc21516f9f1f04f187958890d7e6026df8d16540b7ad9492bc34a67cea03" +checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" dependencies = [ "cfg-if", "js-sys", + "once_cell", "wasm-bindgen", "web-sys", ] @@ -6732,7 +6755,7 @@ checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.101", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -6748,9 +6771,9 @@ dependencies = [ [[package]] name = "wasm-streams" -version = "0.4.0" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b65dc4c90b63b118468cf747d8bf3566c1913ef60be765b5730ead9e0a3ba129" +checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" dependencies = [ "futures-util", "js-sys", @@ -6761,9 +6784,19 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.64" +version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b85cbef8c220a6abc02aefd892dfc0fc23afb1c6a426316ec33253a3877249b" +checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" dependencies = [ "js-sys", "wasm-bindgen", @@ -6771,11 +6804,11 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "0.26.1" +version = "0.26.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3de34ae270483955a94f4b21bdaaeb83d508bb84a01435f393818edb0012009" +checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" dependencies = [ - "rustls-pki-types", + "webpki-roots 1.0.0", ] [[package]] @@ -6793,7 +6826,7 @@ version = "0.16.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "471d1c1645d361eb782a1650b1786a8fb58dd625e681a04c09f5ff7c8764a7b0" dependencies = [ - "hashbrown 0.14.3", + "hashbrown 0.14.5", "once_cell", ] @@ -6815,11 +6848,11 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.6" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "winapi", + "windows-sys 0.59.0", ] [[package]] @@ -6858,7 +6891,7 @@ checksum = "9107ddc059d5b6fbfbffdfa7a7fe3e22a226def0b2608f72e9d552763d3e1ad7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.101", ] [[package]] @@ -6869,7 +6902,7 @@ checksum = "29bee4b38ea3cde66011baa44dba677c432a78593e202392d1e9070cf2a7fca7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.101", ] [[package]] @@ -6881,22 +6914,13 @@ dependencies = [ "windows-targets 0.52.6", ] -[[package]] -name = "windows-sys" -version = "0.45.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" -dependencies = [ - "windows-targets 0.42.2", -] - [[package]] name = "windows-sys" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ - "windows-targets 0.48.1", + "windows-targets 0.48.5", ] [[package]] @@ -6919,32 +6943,17 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.42.2" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" dependencies = [ - "windows_aarch64_gnullvm 0.42.2", - "windows_aarch64_msvc 0.42.2", - "windows_i686_gnu 0.42.2", - "windows_i686_msvc 0.42.2", - "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm 0.42.2", - "windows_x86_64_msvc 0.42.2", -] - -[[package]] -name = "windows-targets" -version = "0.48.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05d4b17490f70499f20b9e791dcf6a299785ce8af4d709018206dc5b4953e95f" -dependencies = [ - "windows_aarch64_gnullvm 0.48.0", - "windows_aarch64_msvc 0.48.0", - "windows_i686_gnu 0.48.0", - "windows_i686_msvc 0.48.0", - "windows_x86_64_gnu 0.48.0", - "windows_x86_64_gnullvm 0.48.0", - "windows_x86_64_msvc 0.48.0", + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", ] [[package]] @@ -6956,7 +6965,7 @@ dependencies = [ "windows_aarch64_gnullvm 0.52.6", "windows_aarch64_msvc 0.52.6", "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm", + "windows_i686_gnullvm 0.52.6", "windows_i686_msvc 0.52.6", "windows_x86_64_gnu 0.52.6", "windows_x86_64_gnullvm 0.52.6", @@ -6964,16 +6973,26 @@ dependencies = [ ] [[package]] -name = "windows_aarch64_gnullvm" -version = "0.42.2" +name = "windows-targets" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" +checksum = "b1e4c7e8ceaaf9cb7d7507c974735728ab453b67ef8f18febdd7c11fe59dca8b" +dependencies = [ + "windows_aarch64_gnullvm 0.53.0", + "windows_aarch64_msvc 0.53.0", + "windows_i686_gnu 0.53.0", + "windows_i686_gnullvm 0.53.0", + "windows_i686_msvc 0.53.0", + "windows_x86_64_gnu 0.53.0", + "windows_x86_64_gnullvm 0.53.0", + "windows_x86_64_msvc 0.53.0", +] [[package]] name = "windows_aarch64_gnullvm" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" @@ -6982,16 +7001,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] -name = "windows_aarch64_msvc" -version = "0.42.2" +name = "windows_aarch64_gnullvm" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" +checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" [[package]] name = "windows_aarch64_msvc" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" @@ -7000,16 +7019,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] -name = "windows_i686_gnu" -version = "0.42.2" +name = "windows_aarch64_msvc" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" +checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" [[package]] name = "windows_i686_gnu" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" @@ -7017,6 +7036,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" +[[package]] +name = "windows_i686_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" + [[package]] name = "windows_i686_gnullvm" version = "0.52.6" @@ -7024,16 +7049,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] -name = "windows_i686_msvc" -version = "0.42.2" +name = "windows_i686_gnullvm" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" +checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" [[package]] name = "windows_i686_msvc" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" @@ -7042,16 +7067,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] -name = "windows_x86_64_gnu" -version = "0.42.2" +name = "windows_i686_msvc" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" +checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" [[package]] name = "windows_x86_64_gnu" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" @@ -7060,16 +7085,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] -name = "windows_x86_64_gnullvm" -version = "0.42.2" +name = "windows_x86_64_gnu" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" +checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" [[package]] name = "windows_x86_64_gnullvm" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" @@ -7078,16 +7103,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] -name = "windows_x86_64_msvc" -version = "0.42.2" +name = "windows_x86_64_gnullvm" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" +checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" [[package]] name = "windows_x86_64_msvc" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" @@ -7096,35 +7121,32 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] -name = "winnow" -version = "0.5.40" +name = "windows_x86_64_msvc" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" -dependencies = [ - "memchr", -] +checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" [[package]] name = "winnow" -version = "0.6.22" +version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39281189af81c07ec09db316b302a3e67bf9bd7cbf6c820b50e35fee9c2fa980" +checksum = "c06928c8748d81b05c9be96aad92e1b6ff01833332f281e8cfca3be4b35fc9ec" dependencies = [ "memchr", ] [[package]] name = "wiremock" -version = "0.6.2" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fff469918e7ca034884c7fd8f93fe27bacb7fcb599fd879df6c7b429a29b646" +checksum = "101681b74cd87b5899e87bcf5a64e83334dd313fcd3053ea72e6dba18928e301" dependencies = [ "assert-json-diff", "async-trait", "base64 0.22.1", "deadpool", "futures", - "http 1.2.0", + "http 1.3.1", "http-body-util", "hyper", "hyper-util", @@ -7139,24 +7161,18 @@ dependencies = [ [[package]] name = "wit-bindgen-rt" -version = "0.33.0" +version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c" +checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", ] -[[package]] -name = "write16" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" - [[package]] name = "writeable" -version = "0.5.5" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" +checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" [[package]] name = "wyz" @@ -7169,13 +7185,12 @@ dependencies = [ [[package]] name = "xattr" -version = "1.3.1" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8da84f1a25939b27f6820d92aed108f83ff920fdf11a7b19366c27c4cda81d4f" +checksum = "0d65cbf2f12c15564212d48f4e3dfb87923d25d611f2aed18f4cb23f0413d89e" dependencies = [ "libc", - "linux-raw-sys", - "rustix", + "rustix 1.0.7", ] [[package]] @@ -7235,7 +7250,19 @@ checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" dependencies = [ "serde", "stable_deref_trait", - "yoke-derive", + "yoke-derive 0.7.5", + "zerofrom", +] + +[[package]] +name = "yoke" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive 0.8.0", "zerofrom", ] @@ -7247,48 +7274,60 @@ checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.101", + "synstructure", +] + +[[package]] +name = "yoke-derive" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", "synstructure", ] [[package]] name = "zerocopy" -version = "0.7.32" +version = "0.8.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be" +checksum = "a1702d9583232ddb9174e01bb7c15a2ab8fb1bc6f227aa1233858c351a3ba0cb" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.7.32" +version = "0.8.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" +checksum = "28a6e20d751156648aa063f3800b706ee209a32c0b4d9f24be3d980b01be55ef" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.101", ] [[package]] name = "zerofrom" -version = "0.1.3" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "655b0814c5c0b19ade497851070c640773304939a6c0fd5f5fb43da0696d05b7" +checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" dependencies = [ "zerofrom-derive", ] [[package]] name = "zerofrom-derive" -version = "0.1.3" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6a647510471d372f2e6c2e6b7219e44d8c574d24fdc11c610a61455782f18c3" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.101", "synstructure", ] @@ -7309,29 +7348,40 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.101", +] + +[[package]] +name = "zerotrie" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595" +dependencies = [ + "displaydoc", + "yoke 0.8.0", + "zerofrom", ] [[package]] name = "zerovec" -version = "0.10.4" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" +checksum = "4a05eb080e015ba39cc9e23bbe5e7fb04d5fb040350f99f34e338d5fdd294428" dependencies = [ - "yoke", + "yoke 0.8.0", "zerofrom", "zerovec-derive", ] [[package]] name = "zerovec-derive" -version = "0.10.3" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" +checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.101", ] [[package]] @@ -7351,9 +7401,9 @@ dependencies = [ [[package]] name = "zip" -version = "2.3.0" +version = "2.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84e9a772a54b54236b9b744aaaf8d7be01b4d6e99725523cb82cb32d1c81b1d7" +checksum = "fabe6324e908f85a1c52063ce7aa26b68dcb7eb6dbc83a2d148403c9bc3eba50" dependencies = [ "aes", "arbitrary", @@ -7364,7 +7414,7 @@ dependencies = [ "deflate64", "displaydoc", "flate2", - "getrandom 0.3.1", + "getrandom 0.3.3", "hmac", "indexmap", "lzma-rs", @@ -7381,41 +7431,39 @@ dependencies = [ [[package]] name = "zopfli" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5019f391bac5cf252e93bbcc53d039ffd62c7bfb7c150414d61369afe57e946" +checksum = "edfc5ee405f504cd4984ecc6f14d02d55cfda60fa4b689434ef4102aae150cd7" dependencies = [ "bumpalo", "crc32fast", - "lockfree-object-pool", "log", - "once_cell", "simd-adler32", ] [[package]] name = "zstd" -version = "0.13.2" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcf2b778a664581e31e389454a7072dab1647606d44f7feea22cd5abb9c9f3f9" +checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" dependencies = [ "zstd-safe", ] [[package]] name = "zstd-safe" -version = "7.2.0" +version = "7.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa556e971e7b568dc775c136fc9de8c779b1c2fc3a63defaafadffdbd3181afa" +checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" dependencies = [ "zstd-sys", ] [[package]] name = "zstd-sys" -version = "2.0.10+zstd.1.5.6" +version = "2.0.15+zstd.1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c253a4914af5bafc8fa8c86ee400827e83cf6ec01195ec1f1ed8441bf00d65aa" +checksum = "eb81183ddd97d0c74cedf1d50d85c8d08c1b8b68ee863bdee9e706eedba1a237" dependencies = [ "cc", "pkg-config", From 92d0d36ff63da54c77346cbfcf9c4d2c78eb138b Mon Sep 17 00:00:00 2001 From: Kerollmops Date: Wed, 4 Jun 2025 10:25:35 +0200 Subject: [PATCH 063/103] Fix a bunch of snapshot tests --- .../after_registering_settings_task.snap | 2 +- .../settings_update_processed.snap | 2 +- .../Intel to kefir succeeds.snap | 2 +- .../import_vectors/Intel to kefir.snap | 2 +- .../import_vectors/adding Intel succeeds.snap | 2 +- .../import_vectors/after adding Intel.snap | 2 +- ...ter_registering_settings_task_vectors.snap | 2 +- .../settings_update_processed_vectors.snap | 2 +- .../after_adding_the_documents.snap | 2 +- .../after_adding_the_settings.snap | 2 +- .../after_removing_the_documents.snap | 2 +- .../registered_the_document_deletions.snap | 2 +- ...red_the_setting_and_document_addition.snap | 2 +- crates/meilisearch/tests/auth/api_keys.rs | 8 ++++---- crates/meilisearch/tests/dumps/mod.rs | 9 ++++++--- crates/meilisearch/tests/features/mod.rs | 20 ++++++++++++------- 16 files changed, 36 insertions(+), 27 deletions(-) diff --git a/crates/index-scheduler/src/scheduler/snapshots/test.rs/test_settings_update/after_registering_settings_task.snap b/crates/index-scheduler/src/scheduler/snapshots/test.rs/test_settings_update/after_registering_settings_task.snap index d9d8b0724..c66a6b5b3 100644 --- a/crates/index-scheduler/src/scheduler/snapshots/test.rs/test_settings_update/after_registering_settings_task.snap +++ b/crates/index-scheduler/src/scheduler/snapshots/test.rs/test_settings_update/after_registering_settings_task.snap @@ -6,7 +6,7 @@ source: crates/index-scheduler/src/scheduler/test.rs [] ---------------------------------------------------------------------- ### All Tasks: -0 {uid: 0, status: enqueued, details: { settings: Settings { displayed_attributes: WildcardSetting(NotSet), searchable_attributes: WildcardSetting(NotSet), filterable_attributes: NotSet, sortable_attributes: NotSet, ranking_rules: NotSet, stop_words: NotSet, non_separator_tokens: NotSet, separator_tokens: NotSet, dictionary: NotSet, synonyms: NotSet, distinct_attribute: NotSet, proximity_precision: NotSet, typo_tolerance: NotSet, faceting: NotSet, pagination: NotSet, embedders: Set({"default": Set(EmbeddingSettings { source: Set(Rest), model: NotSet, revision: NotSet, pooling: NotSet, api_key: Set("My super secret"), dimensions: Set(4), binary_quantized: NotSet, document_template: NotSet, document_template_max_bytes: NotSet, url: Set("http://localhost:7777"), request: Set(String("{{text}}")), response: Set(String("{{embedding}}")), headers: NotSet, search_embedder: NotSet, indexing_embedder: NotSet, distribution: NotSet })}), search_cutoff_ms: NotSet, localized_attributes: NotSet, facet_search: NotSet, prefix_search: NotSet, _kind: PhantomData } }, kind: SettingsUpdate { index_uid: "doggos", new_settings: Settings { displayed_attributes: WildcardSetting(NotSet), searchable_attributes: WildcardSetting(NotSet), filterable_attributes: NotSet, sortable_attributes: NotSet, ranking_rules: NotSet, stop_words: NotSet, non_separator_tokens: NotSet, separator_tokens: NotSet, dictionary: NotSet, synonyms: NotSet, distinct_attribute: NotSet, proximity_precision: NotSet, typo_tolerance: NotSet, faceting: NotSet, pagination: NotSet, embedders: Set({"default": Set(EmbeddingSettings { source: Set(Rest), model: NotSet, revision: NotSet, pooling: NotSet, api_key: Set("My super secret"), dimensions: Set(4), binary_quantized: NotSet, document_template: NotSet, document_template_max_bytes: NotSet, url: Set("http://localhost:7777"), request: Set(String("{{text}}")), response: Set(String("{{embedding}}")), headers: NotSet, search_embedder: NotSet, indexing_embedder: NotSet, distribution: NotSet })}), search_cutoff_ms: NotSet, localized_attributes: NotSet, facet_search: NotSet, prefix_search: NotSet, _kind: PhantomData }, is_deletion: false, allow_index_creation: true }} +0 {uid: 0, status: enqueued, details: { settings: Settings { displayed_attributes: WildcardSetting(NotSet), searchable_attributes: WildcardSetting(NotSet), filterable_attributes: NotSet, sortable_attributes: NotSet, ranking_rules: NotSet, stop_words: NotSet, non_separator_tokens: NotSet, separator_tokens: NotSet, dictionary: NotSet, synonyms: NotSet, distinct_attribute: NotSet, proximity_precision: NotSet, typo_tolerance: NotSet, faceting: NotSet, pagination: NotSet, embedders: Set({"default": Set(EmbeddingSettings { source: Set(Rest), model: NotSet, revision: NotSet, pooling: NotSet, api_key: Set("My super secret"), dimensions: Set(4), binary_quantized: NotSet, document_template: NotSet, document_template_max_bytes: NotSet, url: Set("http://localhost:7777"), request: Set(String("{{text}}")), response: Set(String("{{embedding}}")), headers: NotSet, search_embedder: NotSet, indexing_embedder: NotSet, distribution: NotSet })}), search_cutoff_ms: NotSet, localized_attributes: NotSet, facet_search: NotSet, prefix_search: NotSet, chat: NotSet, _kind: PhantomData } }, kind: SettingsUpdate { index_uid: "doggos", new_settings: Settings { displayed_attributes: WildcardSetting(NotSet), searchable_attributes: WildcardSetting(NotSet), filterable_attributes: NotSet, sortable_attributes: NotSet, ranking_rules: NotSet, stop_words: NotSet, non_separator_tokens: NotSet, separator_tokens: NotSet, dictionary: NotSet, synonyms: NotSet, distinct_attribute: NotSet, proximity_precision: NotSet, typo_tolerance: NotSet, faceting: NotSet, pagination: NotSet, embedders: Set({"default": Set(EmbeddingSettings { source: Set(Rest), model: NotSet, revision: NotSet, pooling: NotSet, api_key: Set("My super secret"), dimensions: Set(4), binary_quantized: NotSet, document_template: NotSet, document_template_max_bytes: NotSet, url: Set("http://localhost:7777"), request: Set(String("{{text}}")), response: Set(String("{{embedding}}")), headers: NotSet, search_embedder: NotSet, indexing_embedder: NotSet, distribution: NotSet })}), search_cutoff_ms: NotSet, localized_attributes: NotSet, facet_search: NotSet, prefix_search: NotSet, chat: NotSet, _kind: PhantomData }, is_deletion: false, allow_index_creation: true }} ---------------------------------------------------------------------- ### Status: enqueued [0,] diff --git a/crates/index-scheduler/src/scheduler/snapshots/test.rs/test_settings_update/settings_update_processed.snap b/crates/index-scheduler/src/scheduler/snapshots/test.rs/test_settings_update/settings_update_processed.snap index 35eb3f162..b7faefa8a 100644 --- a/crates/index-scheduler/src/scheduler/snapshots/test.rs/test_settings_update/settings_update_processed.snap +++ b/crates/index-scheduler/src/scheduler/snapshots/test.rs/test_settings_update/settings_update_processed.snap @@ -6,7 +6,7 @@ source: crates/index-scheduler/src/scheduler/test.rs [] ---------------------------------------------------------------------- ### All Tasks: -0 {uid: 0, batch_uid: 0, status: succeeded, details: { settings: Settings { displayed_attributes: WildcardSetting(NotSet), searchable_attributes: WildcardSetting(NotSet), filterable_attributes: NotSet, sortable_attributes: NotSet, ranking_rules: NotSet, stop_words: NotSet, non_separator_tokens: NotSet, separator_tokens: NotSet, dictionary: NotSet, synonyms: NotSet, distinct_attribute: NotSet, proximity_precision: NotSet, typo_tolerance: NotSet, faceting: NotSet, pagination: NotSet, embedders: Set({"default": Set(EmbeddingSettings { source: Set(Rest), model: NotSet, revision: NotSet, pooling: NotSet, api_key: Set("My super secret"), dimensions: Set(4), binary_quantized: NotSet, document_template: NotSet, document_template_max_bytes: NotSet, url: Set("http://localhost:7777"), request: Set(String("{{text}}")), response: Set(String("{{embedding}}")), headers: NotSet, search_embedder: NotSet, indexing_embedder: NotSet, distribution: NotSet })}), search_cutoff_ms: NotSet, localized_attributes: NotSet, facet_search: NotSet, prefix_search: NotSet, _kind: PhantomData } }, kind: SettingsUpdate { index_uid: "doggos", new_settings: Settings { displayed_attributes: WildcardSetting(NotSet), searchable_attributes: WildcardSetting(NotSet), filterable_attributes: NotSet, sortable_attributes: NotSet, ranking_rules: NotSet, stop_words: NotSet, non_separator_tokens: NotSet, separator_tokens: NotSet, dictionary: NotSet, synonyms: NotSet, distinct_attribute: NotSet, proximity_precision: NotSet, typo_tolerance: NotSet, faceting: NotSet, pagination: NotSet, embedders: Set({"default": Set(EmbeddingSettings { source: Set(Rest), model: NotSet, revision: NotSet, pooling: NotSet, api_key: Set("My super secret"), dimensions: Set(4), binary_quantized: NotSet, document_template: NotSet, document_template_max_bytes: NotSet, url: Set("http://localhost:7777"), request: Set(String("{{text}}")), response: Set(String("{{embedding}}")), headers: NotSet, search_embedder: NotSet, indexing_embedder: NotSet, distribution: NotSet })}), search_cutoff_ms: NotSet, localized_attributes: NotSet, facet_search: NotSet, prefix_search: NotSet, _kind: PhantomData }, is_deletion: false, allow_index_creation: true }} +0 {uid: 0, batch_uid: 0, status: succeeded, details: { settings: Settings { displayed_attributes: WildcardSetting(NotSet), searchable_attributes: WildcardSetting(NotSet), filterable_attributes: NotSet, sortable_attributes: NotSet, ranking_rules: NotSet, stop_words: NotSet, non_separator_tokens: NotSet, separator_tokens: NotSet, dictionary: NotSet, synonyms: NotSet, distinct_attribute: NotSet, proximity_precision: NotSet, typo_tolerance: NotSet, faceting: NotSet, pagination: NotSet, embedders: Set({"default": Set(EmbeddingSettings { source: Set(Rest), model: NotSet, revision: NotSet, pooling: NotSet, api_key: Set("My super secret"), dimensions: Set(4), binary_quantized: NotSet, document_template: NotSet, document_template_max_bytes: NotSet, url: Set("http://localhost:7777"), request: Set(String("{{text}}")), response: Set(String("{{embedding}}")), headers: NotSet, search_embedder: NotSet, indexing_embedder: NotSet, distribution: NotSet })}), search_cutoff_ms: NotSet, localized_attributes: NotSet, facet_search: NotSet, prefix_search: NotSet, chat: NotSet, _kind: PhantomData } }, kind: SettingsUpdate { index_uid: "doggos", new_settings: Settings { displayed_attributes: WildcardSetting(NotSet), searchable_attributes: WildcardSetting(NotSet), filterable_attributes: NotSet, sortable_attributes: NotSet, ranking_rules: NotSet, stop_words: NotSet, non_separator_tokens: NotSet, separator_tokens: NotSet, dictionary: NotSet, synonyms: NotSet, distinct_attribute: NotSet, proximity_precision: NotSet, typo_tolerance: NotSet, faceting: NotSet, pagination: NotSet, embedders: Set({"default": Set(EmbeddingSettings { source: Set(Rest), model: NotSet, revision: NotSet, pooling: NotSet, api_key: Set("My super secret"), dimensions: Set(4), binary_quantized: NotSet, document_template: NotSet, document_template_max_bytes: NotSet, url: Set("http://localhost:7777"), request: Set(String("{{text}}")), response: Set(String("{{embedding}}")), headers: NotSet, search_embedder: NotSet, indexing_embedder: NotSet, distribution: NotSet })}), search_cutoff_ms: NotSet, localized_attributes: NotSet, facet_search: NotSet, prefix_search: NotSet, chat: NotSet, _kind: PhantomData }, is_deletion: false, allow_index_creation: true }} ---------------------------------------------------------------------- ### Status: enqueued [] diff --git a/crates/index-scheduler/src/scheduler/snapshots/test_embedders.rs/import_vectors/Intel to kefir succeeds.snap b/crates/index-scheduler/src/scheduler/snapshots/test_embedders.rs/import_vectors/Intel to kefir succeeds.snap index b90d5944a..c8955e2b6 100644 --- a/crates/index-scheduler/src/scheduler/snapshots/test_embedders.rs/import_vectors/Intel to kefir succeeds.snap +++ b/crates/index-scheduler/src/scheduler/snapshots/test_embedders.rs/import_vectors/Intel to kefir succeeds.snap @@ -6,7 +6,7 @@ source: crates/index-scheduler/src/scheduler/test_embedders.rs [] ---------------------------------------------------------------------- ### All Tasks: -0 {uid: 0, batch_uid: 0, status: succeeded, details: { settings: Settings { displayed_attributes: WildcardSetting(NotSet), searchable_attributes: WildcardSetting(NotSet), filterable_attributes: NotSet, sortable_attributes: NotSet, ranking_rules: NotSet, stop_words: NotSet, non_separator_tokens: NotSet, separator_tokens: NotSet, dictionary: NotSet, synonyms: NotSet, distinct_attribute: NotSet, proximity_precision: NotSet, typo_tolerance: NotSet, faceting: NotSet, pagination: NotSet, embedders: Set({"A_fakerest": Set(EmbeddingSettings { source: Set(Rest), model: NotSet, revision: NotSet, pooling: NotSet, api_key: Set("My super secret"), dimensions: Set(384), binary_quantized: NotSet, document_template: NotSet, document_template_max_bytes: NotSet, url: Set("http://localhost:7777"), request: Set(String("{{text}}")), response: Set(String("{{embedding}}")), headers: NotSet, search_embedder: NotSet, indexing_embedder: NotSet, distribution: NotSet }), "B_small_hf": Set(EmbeddingSettings { source: Set(HuggingFace), model: Set("sentence-transformers/all-MiniLM-L6-v2"), revision: Set("e4ce9877abf3edfe10b0d82785e83bdcb973e22e"), pooling: NotSet, api_key: NotSet, dimensions: NotSet, binary_quantized: NotSet, document_template: Set("{{doc.doggo}} the {{doc.breed}} best doggo"), document_template_max_bytes: NotSet, url: NotSet, request: NotSet, response: NotSet, headers: NotSet, search_embedder: NotSet, indexing_embedder: NotSet, distribution: NotSet })}), search_cutoff_ms: NotSet, localized_attributes: NotSet, facet_search: NotSet, prefix_search: NotSet, _kind: PhantomData } }, kind: SettingsUpdate { index_uid: "doggos", new_settings: Settings { displayed_attributes: WildcardSetting(NotSet), searchable_attributes: WildcardSetting(NotSet), filterable_attributes: NotSet, sortable_attributes: NotSet, ranking_rules: NotSet, stop_words: NotSet, non_separator_tokens: NotSet, separator_tokens: NotSet, dictionary: NotSet, synonyms: NotSet, distinct_attribute: NotSet, proximity_precision: NotSet, typo_tolerance: NotSet, faceting: NotSet, pagination: NotSet, embedders: Set({"A_fakerest": Set(EmbeddingSettings { source: Set(Rest), model: NotSet, revision: NotSet, pooling: NotSet, api_key: Set("My super secret"), dimensions: Set(384), binary_quantized: NotSet, document_template: NotSet, document_template_max_bytes: NotSet, url: Set("http://localhost:7777"), request: Set(String("{{text}}")), response: Set(String("{{embedding}}")), headers: NotSet, search_embedder: NotSet, indexing_embedder: NotSet, distribution: NotSet }), "B_small_hf": Set(EmbeddingSettings { source: Set(HuggingFace), model: Set("sentence-transformers/all-MiniLM-L6-v2"), revision: Set("e4ce9877abf3edfe10b0d82785e83bdcb973e22e"), pooling: NotSet, api_key: NotSet, dimensions: NotSet, binary_quantized: NotSet, document_template: Set("{{doc.doggo}} the {{doc.breed}} best doggo"), document_template_max_bytes: NotSet, url: NotSet, request: NotSet, response: NotSet, headers: NotSet, search_embedder: NotSet, indexing_embedder: NotSet, distribution: NotSet })}), search_cutoff_ms: NotSet, localized_attributes: NotSet, facet_search: NotSet, prefix_search: NotSet, _kind: PhantomData }, is_deletion: false, allow_index_creation: true }} +0 {uid: 0, batch_uid: 0, status: succeeded, details: { settings: Settings { displayed_attributes: WildcardSetting(NotSet), searchable_attributes: WildcardSetting(NotSet), filterable_attributes: NotSet, sortable_attributes: NotSet, ranking_rules: NotSet, stop_words: NotSet, non_separator_tokens: NotSet, separator_tokens: NotSet, dictionary: NotSet, synonyms: NotSet, distinct_attribute: NotSet, proximity_precision: NotSet, typo_tolerance: NotSet, faceting: NotSet, pagination: NotSet, embedders: Set({"A_fakerest": Set(EmbeddingSettings { source: Set(Rest), model: NotSet, revision: NotSet, pooling: NotSet, api_key: Set("My super secret"), dimensions: Set(384), binary_quantized: NotSet, document_template: NotSet, document_template_max_bytes: NotSet, url: Set("http://localhost:7777"), request: Set(String("{{text}}")), response: Set(String("{{embedding}}")), headers: NotSet, search_embedder: NotSet, indexing_embedder: NotSet, distribution: NotSet }), "B_small_hf": Set(EmbeddingSettings { source: Set(HuggingFace), model: Set("sentence-transformers/all-MiniLM-L6-v2"), revision: Set("e4ce9877abf3edfe10b0d82785e83bdcb973e22e"), pooling: NotSet, api_key: NotSet, dimensions: NotSet, binary_quantized: NotSet, document_template: Set("{{doc.doggo}} the {{doc.breed}} best doggo"), document_template_max_bytes: NotSet, url: NotSet, request: NotSet, response: NotSet, headers: NotSet, search_embedder: NotSet, indexing_embedder: NotSet, distribution: NotSet })}), search_cutoff_ms: NotSet, localized_attributes: NotSet, facet_search: NotSet, prefix_search: NotSet, chat: NotSet, _kind: PhantomData } }, kind: SettingsUpdate { index_uid: "doggos", new_settings: Settings { displayed_attributes: WildcardSetting(NotSet), searchable_attributes: WildcardSetting(NotSet), filterable_attributes: NotSet, sortable_attributes: NotSet, ranking_rules: NotSet, stop_words: NotSet, non_separator_tokens: NotSet, separator_tokens: NotSet, dictionary: NotSet, synonyms: NotSet, distinct_attribute: NotSet, proximity_precision: NotSet, typo_tolerance: NotSet, faceting: NotSet, pagination: NotSet, embedders: Set({"A_fakerest": Set(EmbeddingSettings { source: Set(Rest), model: NotSet, revision: NotSet, pooling: NotSet, api_key: Set("My super secret"), dimensions: Set(384), binary_quantized: NotSet, document_template: NotSet, document_template_max_bytes: NotSet, url: Set("http://localhost:7777"), request: Set(String("{{text}}")), response: Set(String("{{embedding}}")), headers: NotSet, search_embedder: NotSet, indexing_embedder: NotSet, distribution: NotSet }), "B_small_hf": Set(EmbeddingSettings { source: Set(HuggingFace), model: Set("sentence-transformers/all-MiniLM-L6-v2"), revision: Set("e4ce9877abf3edfe10b0d82785e83bdcb973e22e"), pooling: NotSet, api_key: NotSet, dimensions: NotSet, binary_quantized: NotSet, document_template: Set("{{doc.doggo}} the {{doc.breed}} best doggo"), document_template_max_bytes: NotSet, url: NotSet, request: NotSet, response: NotSet, headers: NotSet, search_embedder: NotSet, indexing_embedder: NotSet, distribution: NotSet })}), search_cutoff_ms: NotSet, localized_attributes: NotSet, facet_search: NotSet, prefix_search: NotSet, chat: NotSet, _kind: PhantomData }, is_deletion: false, allow_index_creation: true }} 1 {uid: 1, batch_uid: 1, status: succeeded, details: { received_documents: 1, indexed_documents: Some(1) }, kind: DocumentAdditionOrUpdate { index_uid: "doggos", primary_key: Some("id"), method: UpdateDocuments, content_file: 00000000-0000-0000-0000-000000000000, documents_count: 1, allow_index_creation: true }} 2 {uid: 2, batch_uid: 2, status: succeeded, details: { received_documents: 1, indexed_documents: Some(1) }, kind: DocumentAdditionOrUpdate { index_uid: "doggos", primary_key: None, method: UpdateDocuments, content_file: 00000000-0000-0000-0000-000000000001, documents_count: 1, allow_index_creation: true }} ---------------------------------------------------------------------- diff --git a/crates/index-scheduler/src/scheduler/snapshots/test_embedders.rs/import_vectors/Intel to kefir.snap b/crates/index-scheduler/src/scheduler/snapshots/test_embedders.rs/import_vectors/Intel to kefir.snap index 58bf78290..23e43860f 100644 --- a/crates/index-scheduler/src/scheduler/snapshots/test_embedders.rs/import_vectors/Intel to kefir.snap +++ b/crates/index-scheduler/src/scheduler/snapshots/test_embedders.rs/import_vectors/Intel to kefir.snap @@ -6,7 +6,7 @@ source: crates/index-scheduler/src/scheduler/test_embedders.rs [] ---------------------------------------------------------------------- ### All Tasks: -0 {uid: 0, batch_uid: 0, status: succeeded, details: { settings: Settings { displayed_attributes: WildcardSetting(NotSet), searchable_attributes: WildcardSetting(NotSet), filterable_attributes: NotSet, sortable_attributes: NotSet, ranking_rules: NotSet, stop_words: NotSet, non_separator_tokens: NotSet, separator_tokens: NotSet, dictionary: NotSet, synonyms: NotSet, distinct_attribute: NotSet, proximity_precision: NotSet, typo_tolerance: NotSet, faceting: NotSet, pagination: NotSet, embedders: Set({"A_fakerest": Set(EmbeddingSettings { source: Set(Rest), model: NotSet, revision: NotSet, pooling: NotSet, api_key: Set("My super secret"), dimensions: Set(384), binary_quantized: NotSet, document_template: NotSet, document_template_max_bytes: NotSet, url: Set("http://localhost:7777"), request: Set(String("{{text}}")), response: Set(String("{{embedding}}")), headers: NotSet, search_embedder: NotSet, indexing_embedder: NotSet, distribution: NotSet }), "B_small_hf": Set(EmbeddingSettings { source: Set(HuggingFace), model: Set("sentence-transformers/all-MiniLM-L6-v2"), revision: Set("e4ce9877abf3edfe10b0d82785e83bdcb973e22e"), pooling: NotSet, api_key: NotSet, dimensions: NotSet, binary_quantized: NotSet, document_template: Set("{{doc.doggo}} the {{doc.breed}} best doggo"), document_template_max_bytes: NotSet, url: NotSet, request: NotSet, response: NotSet, headers: NotSet, search_embedder: NotSet, indexing_embedder: NotSet, distribution: NotSet })}), search_cutoff_ms: NotSet, localized_attributes: NotSet, facet_search: NotSet, prefix_search: NotSet, _kind: PhantomData } }, kind: SettingsUpdate { index_uid: "doggos", new_settings: Settings { displayed_attributes: WildcardSetting(NotSet), searchable_attributes: WildcardSetting(NotSet), filterable_attributes: NotSet, sortable_attributes: NotSet, ranking_rules: NotSet, stop_words: NotSet, non_separator_tokens: NotSet, separator_tokens: NotSet, dictionary: NotSet, synonyms: NotSet, distinct_attribute: NotSet, proximity_precision: NotSet, typo_tolerance: NotSet, faceting: NotSet, pagination: NotSet, embedders: Set({"A_fakerest": Set(EmbeddingSettings { source: Set(Rest), model: NotSet, revision: NotSet, pooling: NotSet, api_key: Set("My super secret"), dimensions: Set(384), binary_quantized: NotSet, document_template: NotSet, document_template_max_bytes: NotSet, url: Set("http://localhost:7777"), request: Set(String("{{text}}")), response: Set(String("{{embedding}}")), headers: NotSet, search_embedder: NotSet, indexing_embedder: NotSet, distribution: NotSet }), "B_small_hf": Set(EmbeddingSettings { source: Set(HuggingFace), model: Set("sentence-transformers/all-MiniLM-L6-v2"), revision: Set("e4ce9877abf3edfe10b0d82785e83bdcb973e22e"), pooling: NotSet, api_key: NotSet, dimensions: NotSet, binary_quantized: NotSet, document_template: Set("{{doc.doggo}} the {{doc.breed}} best doggo"), document_template_max_bytes: NotSet, url: NotSet, request: NotSet, response: NotSet, headers: NotSet, search_embedder: NotSet, indexing_embedder: NotSet, distribution: NotSet })}), search_cutoff_ms: NotSet, localized_attributes: NotSet, facet_search: NotSet, prefix_search: NotSet, _kind: PhantomData }, is_deletion: false, allow_index_creation: true }} +0 {uid: 0, batch_uid: 0, status: succeeded, details: { settings: Settings { displayed_attributes: WildcardSetting(NotSet), searchable_attributes: WildcardSetting(NotSet), filterable_attributes: NotSet, sortable_attributes: NotSet, ranking_rules: NotSet, stop_words: NotSet, non_separator_tokens: NotSet, separator_tokens: NotSet, dictionary: NotSet, synonyms: NotSet, distinct_attribute: NotSet, proximity_precision: NotSet, typo_tolerance: NotSet, faceting: NotSet, pagination: NotSet, embedders: Set({"A_fakerest": Set(EmbeddingSettings { source: Set(Rest), model: NotSet, revision: NotSet, pooling: NotSet, api_key: Set("My super secret"), dimensions: Set(384), binary_quantized: NotSet, document_template: NotSet, document_template_max_bytes: NotSet, url: Set("http://localhost:7777"), request: Set(String("{{text}}")), response: Set(String("{{embedding}}")), headers: NotSet, search_embedder: NotSet, indexing_embedder: NotSet, distribution: NotSet }), "B_small_hf": Set(EmbeddingSettings { source: Set(HuggingFace), model: Set("sentence-transformers/all-MiniLM-L6-v2"), revision: Set("e4ce9877abf3edfe10b0d82785e83bdcb973e22e"), pooling: NotSet, api_key: NotSet, dimensions: NotSet, binary_quantized: NotSet, document_template: Set("{{doc.doggo}} the {{doc.breed}} best doggo"), document_template_max_bytes: NotSet, url: NotSet, request: NotSet, response: NotSet, headers: NotSet, search_embedder: NotSet, indexing_embedder: NotSet, distribution: NotSet })}), search_cutoff_ms: NotSet, localized_attributes: NotSet, facet_search: NotSet, prefix_search: NotSet, chat: NotSet, _kind: PhantomData } }, kind: SettingsUpdate { index_uid: "doggos", new_settings: Settings { displayed_attributes: WildcardSetting(NotSet), searchable_attributes: WildcardSetting(NotSet), filterable_attributes: NotSet, sortable_attributes: NotSet, ranking_rules: NotSet, stop_words: NotSet, non_separator_tokens: NotSet, separator_tokens: NotSet, dictionary: NotSet, synonyms: NotSet, distinct_attribute: NotSet, proximity_precision: NotSet, typo_tolerance: NotSet, faceting: NotSet, pagination: NotSet, embedders: Set({"A_fakerest": Set(EmbeddingSettings { source: Set(Rest), model: NotSet, revision: NotSet, pooling: NotSet, api_key: Set("My super secret"), dimensions: Set(384), binary_quantized: NotSet, document_template: NotSet, document_template_max_bytes: NotSet, url: Set("http://localhost:7777"), request: Set(String("{{text}}")), response: Set(String("{{embedding}}")), headers: NotSet, search_embedder: NotSet, indexing_embedder: NotSet, distribution: NotSet }), "B_small_hf": Set(EmbeddingSettings { source: Set(HuggingFace), model: Set("sentence-transformers/all-MiniLM-L6-v2"), revision: Set("e4ce9877abf3edfe10b0d82785e83bdcb973e22e"), pooling: NotSet, api_key: NotSet, dimensions: NotSet, binary_quantized: NotSet, document_template: Set("{{doc.doggo}} the {{doc.breed}} best doggo"), document_template_max_bytes: NotSet, url: NotSet, request: NotSet, response: NotSet, headers: NotSet, search_embedder: NotSet, indexing_embedder: NotSet, distribution: NotSet })}), search_cutoff_ms: NotSet, localized_attributes: NotSet, facet_search: NotSet, prefix_search: NotSet, chat: NotSet, _kind: PhantomData }, is_deletion: false, allow_index_creation: true }} 1 {uid: 1, batch_uid: 1, status: succeeded, details: { received_documents: 1, indexed_documents: Some(1) }, kind: DocumentAdditionOrUpdate { index_uid: "doggos", primary_key: Some("id"), method: UpdateDocuments, content_file: 00000000-0000-0000-0000-000000000000, documents_count: 1, allow_index_creation: true }} 2 {uid: 2, status: enqueued, details: { received_documents: 1, indexed_documents: None }, kind: DocumentAdditionOrUpdate { index_uid: "doggos", primary_key: None, method: UpdateDocuments, content_file: 00000000-0000-0000-0000-000000000001, documents_count: 1, allow_index_creation: true }} ---------------------------------------------------------------------- diff --git a/crates/index-scheduler/src/scheduler/snapshots/test_embedders.rs/import_vectors/adding Intel succeeds.snap b/crates/index-scheduler/src/scheduler/snapshots/test_embedders.rs/import_vectors/adding Intel succeeds.snap index 90ac17702..732527fa8 100644 --- a/crates/index-scheduler/src/scheduler/snapshots/test_embedders.rs/import_vectors/adding Intel succeeds.snap +++ b/crates/index-scheduler/src/scheduler/snapshots/test_embedders.rs/import_vectors/adding Intel succeeds.snap @@ -6,7 +6,7 @@ source: crates/index-scheduler/src/scheduler/test_embedders.rs [] ---------------------------------------------------------------------- ### All Tasks: -0 {uid: 0, batch_uid: 0, status: succeeded, details: { settings: Settings { displayed_attributes: WildcardSetting(NotSet), searchable_attributes: WildcardSetting(NotSet), filterable_attributes: NotSet, sortable_attributes: NotSet, ranking_rules: NotSet, stop_words: NotSet, non_separator_tokens: NotSet, separator_tokens: NotSet, dictionary: NotSet, synonyms: NotSet, distinct_attribute: NotSet, proximity_precision: NotSet, typo_tolerance: NotSet, faceting: NotSet, pagination: NotSet, embedders: Set({"A_fakerest": Set(EmbeddingSettings { source: Set(Rest), model: NotSet, revision: NotSet, pooling: NotSet, api_key: Set("My super secret"), dimensions: Set(384), binary_quantized: NotSet, document_template: NotSet, document_template_max_bytes: NotSet, url: Set("http://localhost:7777"), request: Set(String("{{text}}")), response: Set(String("{{embedding}}")), headers: NotSet, search_embedder: NotSet, indexing_embedder: NotSet, distribution: NotSet }), "B_small_hf": Set(EmbeddingSettings { source: Set(HuggingFace), model: Set("sentence-transformers/all-MiniLM-L6-v2"), revision: Set("e4ce9877abf3edfe10b0d82785e83bdcb973e22e"), pooling: NotSet, api_key: NotSet, dimensions: NotSet, binary_quantized: NotSet, document_template: Set("{{doc.doggo}} the {{doc.breed}} best doggo"), document_template_max_bytes: NotSet, url: NotSet, request: NotSet, response: NotSet, headers: NotSet, search_embedder: NotSet, indexing_embedder: NotSet, distribution: NotSet })}), search_cutoff_ms: NotSet, localized_attributes: NotSet, facet_search: NotSet, prefix_search: NotSet, _kind: PhantomData } }, kind: SettingsUpdate { index_uid: "doggos", new_settings: Settings { displayed_attributes: WildcardSetting(NotSet), searchable_attributes: WildcardSetting(NotSet), filterable_attributes: NotSet, sortable_attributes: NotSet, ranking_rules: NotSet, stop_words: NotSet, non_separator_tokens: NotSet, separator_tokens: NotSet, dictionary: NotSet, synonyms: NotSet, distinct_attribute: NotSet, proximity_precision: NotSet, typo_tolerance: NotSet, faceting: NotSet, pagination: NotSet, embedders: Set({"A_fakerest": Set(EmbeddingSettings { source: Set(Rest), model: NotSet, revision: NotSet, pooling: NotSet, api_key: Set("My super secret"), dimensions: Set(384), binary_quantized: NotSet, document_template: NotSet, document_template_max_bytes: NotSet, url: Set("http://localhost:7777"), request: Set(String("{{text}}")), response: Set(String("{{embedding}}")), headers: NotSet, search_embedder: NotSet, indexing_embedder: NotSet, distribution: NotSet }), "B_small_hf": Set(EmbeddingSettings { source: Set(HuggingFace), model: Set("sentence-transformers/all-MiniLM-L6-v2"), revision: Set("e4ce9877abf3edfe10b0d82785e83bdcb973e22e"), pooling: NotSet, api_key: NotSet, dimensions: NotSet, binary_quantized: NotSet, document_template: Set("{{doc.doggo}} the {{doc.breed}} best doggo"), document_template_max_bytes: NotSet, url: NotSet, request: NotSet, response: NotSet, headers: NotSet, search_embedder: NotSet, indexing_embedder: NotSet, distribution: NotSet })}), search_cutoff_ms: NotSet, localized_attributes: NotSet, facet_search: NotSet, prefix_search: NotSet, _kind: PhantomData }, is_deletion: false, allow_index_creation: true }} +0 {uid: 0, batch_uid: 0, status: succeeded, details: { settings: Settings { displayed_attributes: WildcardSetting(NotSet), searchable_attributes: WildcardSetting(NotSet), filterable_attributes: NotSet, sortable_attributes: NotSet, ranking_rules: NotSet, stop_words: NotSet, non_separator_tokens: NotSet, separator_tokens: NotSet, dictionary: NotSet, synonyms: NotSet, distinct_attribute: NotSet, proximity_precision: NotSet, typo_tolerance: NotSet, faceting: NotSet, pagination: NotSet, embedders: Set({"A_fakerest": Set(EmbeddingSettings { source: Set(Rest), model: NotSet, revision: NotSet, pooling: NotSet, api_key: Set("My super secret"), dimensions: Set(384), binary_quantized: NotSet, document_template: NotSet, document_template_max_bytes: NotSet, url: Set("http://localhost:7777"), request: Set(String("{{text}}")), response: Set(String("{{embedding}}")), headers: NotSet, search_embedder: NotSet, indexing_embedder: NotSet, distribution: NotSet }), "B_small_hf": Set(EmbeddingSettings { source: Set(HuggingFace), model: Set("sentence-transformers/all-MiniLM-L6-v2"), revision: Set("e4ce9877abf3edfe10b0d82785e83bdcb973e22e"), pooling: NotSet, api_key: NotSet, dimensions: NotSet, binary_quantized: NotSet, document_template: Set("{{doc.doggo}} the {{doc.breed}} best doggo"), document_template_max_bytes: NotSet, url: NotSet, request: NotSet, response: NotSet, headers: NotSet, search_embedder: NotSet, indexing_embedder: NotSet, distribution: NotSet })}), search_cutoff_ms: NotSet, localized_attributes: NotSet, facet_search: NotSet, prefix_search: NotSet, chat: NotSet, _kind: PhantomData } }, kind: SettingsUpdate { index_uid: "doggos", new_settings: Settings { displayed_attributes: WildcardSetting(NotSet), searchable_attributes: WildcardSetting(NotSet), filterable_attributes: NotSet, sortable_attributes: NotSet, ranking_rules: NotSet, stop_words: NotSet, non_separator_tokens: NotSet, separator_tokens: NotSet, dictionary: NotSet, synonyms: NotSet, distinct_attribute: NotSet, proximity_precision: NotSet, typo_tolerance: NotSet, faceting: NotSet, pagination: NotSet, embedders: Set({"A_fakerest": Set(EmbeddingSettings { source: Set(Rest), model: NotSet, revision: NotSet, pooling: NotSet, api_key: Set("My super secret"), dimensions: Set(384), binary_quantized: NotSet, document_template: NotSet, document_template_max_bytes: NotSet, url: Set("http://localhost:7777"), request: Set(String("{{text}}")), response: Set(String("{{embedding}}")), headers: NotSet, search_embedder: NotSet, indexing_embedder: NotSet, distribution: NotSet }), "B_small_hf": Set(EmbeddingSettings { source: Set(HuggingFace), model: Set("sentence-transformers/all-MiniLM-L6-v2"), revision: Set("e4ce9877abf3edfe10b0d82785e83bdcb973e22e"), pooling: NotSet, api_key: NotSet, dimensions: NotSet, binary_quantized: NotSet, document_template: Set("{{doc.doggo}} the {{doc.breed}} best doggo"), document_template_max_bytes: NotSet, url: NotSet, request: NotSet, response: NotSet, headers: NotSet, search_embedder: NotSet, indexing_embedder: NotSet, distribution: NotSet })}), search_cutoff_ms: NotSet, localized_attributes: NotSet, facet_search: NotSet, prefix_search: NotSet, chat: NotSet, _kind: PhantomData }, is_deletion: false, allow_index_creation: true }} 1 {uid: 1, batch_uid: 1, status: succeeded, details: { received_documents: 1, indexed_documents: Some(1) }, kind: DocumentAdditionOrUpdate { index_uid: "doggos", primary_key: Some("id"), method: UpdateDocuments, content_file: 00000000-0000-0000-0000-000000000000, documents_count: 1, allow_index_creation: true }} ---------------------------------------------------------------------- ### Status: diff --git a/crates/index-scheduler/src/scheduler/snapshots/test_embedders.rs/import_vectors/after adding Intel.snap b/crates/index-scheduler/src/scheduler/snapshots/test_embedders.rs/import_vectors/after adding Intel.snap index 10f87d389..5e01ffcdf 100644 --- a/crates/index-scheduler/src/scheduler/snapshots/test_embedders.rs/import_vectors/after adding Intel.snap +++ b/crates/index-scheduler/src/scheduler/snapshots/test_embedders.rs/import_vectors/after adding Intel.snap @@ -6,7 +6,7 @@ source: crates/index-scheduler/src/scheduler/test_embedders.rs [] ---------------------------------------------------------------------- ### All Tasks: -0 {uid: 0, batch_uid: 0, status: succeeded, details: { settings: Settings { displayed_attributes: WildcardSetting(NotSet), searchable_attributes: WildcardSetting(NotSet), filterable_attributes: NotSet, sortable_attributes: NotSet, ranking_rules: NotSet, stop_words: NotSet, non_separator_tokens: NotSet, separator_tokens: NotSet, dictionary: NotSet, synonyms: NotSet, distinct_attribute: NotSet, proximity_precision: NotSet, typo_tolerance: NotSet, faceting: NotSet, pagination: NotSet, embedders: Set({"A_fakerest": Set(EmbeddingSettings { source: Set(Rest), model: NotSet, revision: NotSet, pooling: NotSet, api_key: Set("My super secret"), dimensions: Set(384), binary_quantized: NotSet, document_template: NotSet, document_template_max_bytes: NotSet, url: Set("http://localhost:7777"), request: Set(String("{{text}}")), response: Set(String("{{embedding}}")), headers: NotSet, search_embedder: NotSet, indexing_embedder: NotSet, distribution: NotSet }), "B_small_hf": Set(EmbeddingSettings { source: Set(HuggingFace), model: Set("sentence-transformers/all-MiniLM-L6-v2"), revision: Set("e4ce9877abf3edfe10b0d82785e83bdcb973e22e"), pooling: NotSet, api_key: NotSet, dimensions: NotSet, binary_quantized: NotSet, document_template: Set("{{doc.doggo}} the {{doc.breed}} best doggo"), document_template_max_bytes: NotSet, url: NotSet, request: NotSet, response: NotSet, headers: NotSet, search_embedder: NotSet, indexing_embedder: NotSet, distribution: NotSet })}), search_cutoff_ms: NotSet, localized_attributes: NotSet, facet_search: NotSet, prefix_search: NotSet, _kind: PhantomData } }, kind: SettingsUpdate { index_uid: "doggos", new_settings: Settings { displayed_attributes: WildcardSetting(NotSet), searchable_attributes: WildcardSetting(NotSet), filterable_attributes: NotSet, sortable_attributes: NotSet, ranking_rules: NotSet, stop_words: NotSet, non_separator_tokens: NotSet, separator_tokens: NotSet, dictionary: NotSet, synonyms: NotSet, distinct_attribute: NotSet, proximity_precision: NotSet, typo_tolerance: NotSet, faceting: NotSet, pagination: NotSet, embedders: Set({"A_fakerest": Set(EmbeddingSettings { source: Set(Rest), model: NotSet, revision: NotSet, pooling: NotSet, api_key: Set("My super secret"), dimensions: Set(384), binary_quantized: NotSet, document_template: NotSet, document_template_max_bytes: NotSet, url: Set("http://localhost:7777"), request: Set(String("{{text}}")), response: Set(String("{{embedding}}")), headers: NotSet, search_embedder: NotSet, indexing_embedder: NotSet, distribution: NotSet }), "B_small_hf": Set(EmbeddingSettings { source: Set(HuggingFace), model: Set("sentence-transformers/all-MiniLM-L6-v2"), revision: Set("e4ce9877abf3edfe10b0d82785e83bdcb973e22e"), pooling: NotSet, api_key: NotSet, dimensions: NotSet, binary_quantized: NotSet, document_template: Set("{{doc.doggo}} the {{doc.breed}} best doggo"), document_template_max_bytes: NotSet, url: NotSet, request: NotSet, response: NotSet, headers: NotSet, search_embedder: NotSet, indexing_embedder: NotSet, distribution: NotSet })}), search_cutoff_ms: NotSet, localized_attributes: NotSet, facet_search: NotSet, prefix_search: NotSet, _kind: PhantomData }, is_deletion: false, allow_index_creation: true }} +0 {uid: 0, batch_uid: 0, status: succeeded, details: { settings: Settings { displayed_attributes: WildcardSetting(NotSet), searchable_attributes: WildcardSetting(NotSet), filterable_attributes: NotSet, sortable_attributes: NotSet, ranking_rules: NotSet, stop_words: NotSet, non_separator_tokens: NotSet, separator_tokens: NotSet, dictionary: NotSet, synonyms: NotSet, distinct_attribute: NotSet, proximity_precision: NotSet, typo_tolerance: NotSet, faceting: NotSet, pagination: NotSet, embedders: Set({"A_fakerest": Set(EmbeddingSettings { source: Set(Rest), model: NotSet, revision: NotSet, pooling: NotSet, api_key: Set("My super secret"), dimensions: Set(384), binary_quantized: NotSet, document_template: NotSet, document_template_max_bytes: NotSet, url: Set("http://localhost:7777"), request: Set(String("{{text}}")), response: Set(String("{{embedding}}")), headers: NotSet, search_embedder: NotSet, indexing_embedder: NotSet, distribution: NotSet }), "B_small_hf": Set(EmbeddingSettings { source: Set(HuggingFace), model: Set("sentence-transformers/all-MiniLM-L6-v2"), revision: Set("e4ce9877abf3edfe10b0d82785e83bdcb973e22e"), pooling: NotSet, api_key: NotSet, dimensions: NotSet, binary_quantized: NotSet, document_template: Set("{{doc.doggo}} the {{doc.breed}} best doggo"), document_template_max_bytes: NotSet, url: NotSet, request: NotSet, response: NotSet, headers: NotSet, search_embedder: NotSet, indexing_embedder: NotSet, distribution: NotSet })}), search_cutoff_ms: NotSet, localized_attributes: NotSet, facet_search: NotSet, prefix_search: NotSet, chat: NotSet, _kind: PhantomData } }, kind: SettingsUpdate { index_uid: "doggos", new_settings: Settings { displayed_attributes: WildcardSetting(NotSet), searchable_attributes: WildcardSetting(NotSet), filterable_attributes: NotSet, sortable_attributes: NotSet, ranking_rules: NotSet, stop_words: NotSet, non_separator_tokens: NotSet, separator_tokens: NotSet, dictionary: NotSet, synonyms: NotSet, distinct_attribute: NotSet, proximity_precision: NotSet, typo_tolerance: NotSet, faceting: NotSet, pagination: NotSet, embedders: Set({"A_fakerest": Set(EmbeddingSettings { source: Set(Rest), model: NotSet, revision: NotSet, pooling: NotSet, api_key: Set("My super secret"), dimensions: Set(384), binary_quantized: NotSet, document_template: NotSet, document_template_max_bytes: NotSet, url: Set("http://localhost:7777"), request: Set(String("{{text}}")), response: Set(String("{{embedding}}")), headers: NotSet, search_embedder: NotSet, indexing_embedder: NotSet, distribution: NotSet }), "B_small_hf": Set(EmbeddingSettings { source: Set(HuggingFace), model: Set("sentence-transformers/all-MiniLM-L6-v2"), revision: Set("e4ce9877abf3edfe10b0d82785e83bdcb973e22e"), pooling: NotSet, api_key: NotSet, dimensions: NotSet, binary_quantized: NotSet, document_template: Set("{{doc.doggo}} the {{doc.breed}} best doggo"), document_template_max_bytes: NotSet, url: NotSet, request: NotSet, response: NotSet, headers: NotSet, search_embedder: NotSet, indexing_embedder: NotSet, distribution: NotSet })}), search_cutoff_ms: NotSet, localized_attributes: NotSet, facet_search: NotSet, prefix_search: NotSet, chat: NotSet, _kind: PhantomData }, is_deletion: false, allow_index_creation: true }} 1 {uid: 1, status: enqueued, details: { received_documents: 1, indexed_documents: None }, kind: DocumentAdditionOrUpdate { index_uid: "doggos", primary_key: Some("id"), method: UpdateDocuments, content_file: 00000000-0000-0000-0000-000000000000, documents_count: 1, allow_index_creation: true }} ---------------------------------------------------------------------- ### Status: diff --git a/crates/index-scheduler/src/scheduler/snapshots/test_embedders.rs/import_vectors/after_registering_settings_task_vectors.snap b/crates/index-scheduler/src/scheduler/snapshots/test_embedders.rs/import_vectors/after_registering_settings_task_vectors.snap index 35bd9dee9..1172d1118 100644 --- a/crates/index-scheduler/src/scheduler/snapshots/test_embedders.rs/import_vectors/after_registering_settings_task_vectors.snap +++ b/crates/index-scheduler/src/scheduler/snapshots/test_embedders.rs/import_vectors/after_registering_settings_task_vectors.snap @@ -6,7 +6,7 @@ source: crates/index-scheduler/src/scheduler/test_embedders.rs [] ---------------------------------------------------------------------- ### All Tasks: -0 {uid: 0, status: enqueued, details: { settings: Settings { displayed_attributes: WildcardSetting(NotSet), searchable_attributes: WildcardSetting(NotSet), filterable_attributes: NotSet, sortable_attributes: NotSet, ranking_rules: NotSet, stop_words: NotSet, non_separator_tokens: NotSet, separator_tokens: NotSet, dictionary: NotSet, synonyms: NotSet, distinct_attribute: NotSet, proximity_precision: NotSet, typo_tolerance: NotSet, faceting: NotSet, pagination: NotSet, embedders: Set({"A_fakerest": Set(EmbeddingSettings { source: Set(Rest), model: NotSet, revision: NotSet, pooling: NotSet, api_key: Set("My super secret"), dimensions: Set(384), binary_quantized: NotSet, document_template: NotSet, document_template_max_bytes: NotSet, url: Set("http://localhost:7777"), request: Set(String("{{text}}")), response: Set(String("{{embedding}}")), headers: NotSet, search_embedder: NotSet, indexing_embedder: NotSet, distribution: NotSet }), "B_small_hf": Set(EmbeddingSettings { source: Set(HuggingFace), model: Set("sentence-transformers/all-MiniLM-L6-v2"), revision: Set("e4ce9877abf3edfe10b0d82785e83bdcb973e22e"), pooling: NotSet, api_key: NotSet, dimensions: NotSet, binary_quantized: NotSet, document_template: Set("{{doc.doggo}} the {{doc.breed}} best doggo"), document_template_max_bytes: NotSet, url: NotSet, request: NotSet, response: NotSet, headers: NotSet, search_embedder: NotSet, indexing_embedder: NotSet, distribution: NotSet })}), search_cutoff_ms: NotSet, localized_attributes: NotSet, facet_search: NotSet, prefix_search: NotSet, _kind: PhantomData } }, kind: SettingsUpdate { index_uid: "doggos", new_settings: Settings { displayed_attributes: WildcardSetting(NotSet), searchable_attributes: WildcardSetting(NotSet), filterable_attributes: NotSet, sortable_attributes: NotSet, ranking_rules: NotSet, stop_words: NotSet, non_separator_tokens: NotSet, separator_tokens: NotSet, dictionary: NotSet, synonyms: NotSet, distinct_attribute: NotSet, proximity_precision: NotSet, typo_tolerance: NotSet, faceting: NotSet, pagination: NotSet, embedders: Set({"A_fakerest": Set(EmbeddingSettings { source: Set(Rest), model: NotSet, revision: NotSet, pooling: NotSet, api_key: Set("My super secret"), dimensions: Set(384), binary_quantized: NotSet, document_template: NotSet, document_template_max_bytes: NotSet, url: Set("http://localhost:7777"), request: Set(String("{{text}}")), response: Set(String("{{embedding}}")), headers: NotSet, search_embedder: NotSet, indexing_embedder: NotSet, distribution: NotSet }), "B_small_hf": Set(EmbeddingSettings { source: Set(HuggingFace), model: Set("sentence-transformers/all-MiniLM-L6-v2"), revision: Set("e4ce9877abf3edfe10b0d82785e83bdcb973e22e"), pooling: NotSet, api_key: NotSet, dimensions: NotSet, binary_quantized: NotSet, document_template: Set("{{doc.doggo}} the {{doc.breed}} best doggo"), document_template_max_bytes: NotSet, url: NotSet, request: NotSet, response: NotSet, headers: NotSet, search_embedder: NotSet, indexing_embedder: NotSet, distribution: NotSet })}), search_cutoff_ms: NotSet, localized_attributes: NotSet, facet_search: NotSet, prefix_search: NotSet, _kind: PhantomData }, is_deletion: false, allow_index_creation: true }} +0 {uid: 0, status: enqueued, details: { settings: Settings { displayed_attributes: WildcardSetting(NotSet), searchable_attributes: WildcardSetting(NotSet), filterable_attributes: NotSet, sortable_attributes: NotSet, ranking_rules: NotSet, stop_words: NotSet, non_separator_tokens: NotSet, separator_tokens: NotSet, dictionary: NotSet, synonyms: NotSet, distinct_attribute: NotSet, proximity_precision: NotSet, typo_tolerance: NotSet, faceting: NotSet, pagination: NotSet, embedders: Set({"A_fakerest": Set(EmbeddingSettings { source: Set(Rest), model: NotSet, revision: NotSet, pooling: NotSet, api_key: Set("My super secret"), dimensions: Set(384), binary_quantized: NotSet, document_template: NotSet, document_template_max_bytes: NotSet, url: Set("http://localhost:7777"), request: Set(String("{{text}}")), response: Set(String("{{embedding}}")), headers: NotSet, search_embedder: NotSet, indexing_embedder: NotSet, distribution: NotSet }), "B_small_hf": Set(EmbeddingSettings { source: Set(HuggingFace), model: Set("sentence-transformers/all-MiniLM-L6-v2"), revision: Set("e4ce9877abf3edfe10b0d82785e83bdcb973e22e"), pooling: NotSet, api_key: NotSet, dimensions: NotSet, binary_quantized: NotSet, document_template: Set("{{doc.doggo}} the {{doc.breed}} best doggo"), document_template_max_bytes: NotSet, url: NotSet, request: NotSet, response: NotSet, headers: NotSet, search_embedder: NotSet, indexing_embedder: NotSet, distribution: NotSet })}), search_cutoff_ms: NotSet, localized_attributes: NotSet, facet_search: NotSet, prefix_search: NotSet, chat: NotSet, _kind: PhantomData } }, kind: SettingsUpdate { index_uid: "doggos", new_settings: Settings { displayed_attributes: WildcardSetting(NotSet), searchable_attributes: WildcardSetting(NotSet), filterable_attributes: NotSet, sortable_attributes: NotSet, ranking_rules: NotSet, stop_words: NotSet, non_separator_tokens: NotSet, separator_tokens: NotSet, dictionary: NotSet, synonyms: NotSet, distinct_attribute: NotSet, proximity_precision: NotSet, typo_tolerance: NotSet, faceting: NotSet, pagination: NotSet, embedders: Set({"A_fakerest": Set(EmbeddingSettings { source: Set(Rest), model: NotSet, revision: NotSet, pooling: NotSet, api_key: Set("My super secret"), dimensions: Set(384), binary_quantized: NotSet, document_template: NotSet, document_template_max_bytes: NotSet, url: Set("http://localhost:7777"), request: Set(String("{{text}}")), response: Set(String("{{embedding}}")), headers: NotSet, search_embedder: NotSet, indexing_embedder: NotSet, distribution: NotSet }), "B_small_hf": Set(EmbeddingSettings { source: Set(HuggingFace), model: Set("sentence-transformers/all-MiniLM-L6-v2"), revision: Set("e4ce9877abf3edfe10b0d82785e83bdcb973e22e"), pooling: NotSet, api_key: NotSet, dimensions: NotSet, binary_quantized: NotSet, document_template: Set("{{doc.doggo}} the {{doc.breed}} best doggo"), document_template_max_bytes: NotSet, url: NotSet, request: NotSet, response: NotSet, headers: NotSet, search_embedder: NotSet, indexing_embedder: NotSet, distribution: NotSet })}), search_cutoff_ms: NotSet, localized_attributes: NotSet, facet_search: NotSet, prefix_search: NotSet, chat: NotSet, _kind: PhantomData }, is_deletion: false, allow_index_creation: true }} ---------------------------------------------------------------------- ### Status: enqueued [0,] diff --git a/crates/index-scheduler/src/scheduler/snapshots/test_embedders.rs/import_vectors/settings_update_processed_vectors.snap b/crates/index-scheduler/src/scheduler/snapshots/test_embedders.rs/import_vectors/settings_update_processed_vectors.snap index ec8f387f0..3653eeb9a 100644 --- a/crates/index-scheduler/src/scheduler/snapshots/test_embedders.rs/import_vectors/settings_update_processed_vectors.snap +++ b/crates/index-scheduler/src/scheduler/snapshots/test_embedders.rs/import_vectors/settings_update_processed_vectors.snap @@ -6,7 +6,7 @@ source: crates/index-scheduler/src/scheduler/test_embedders.rs [] ---------------------------------------------------------------------- ### All Tasks: -0 {uid: 0, batch_uid: 0, status: succeeded, details: { settings: Settings { displayed_attributes: WildcardSetting(NotSet), searchable_attributes: WildcardSetting(NotSet), filterable_attributes: NotSet, sortable_attributes: NotSet, ranking_rules: NotSet, stop_words: NotSet, non_separator_tokens: NotSet, separator_tokens: NotSet, dictionary: NotSet, synonyms: NotSet, distinct_attribute: NotSet, proximity_precision: NotSet, typo_tolerance: NotSet, faceting: NotSet, pagination: NotSet, embedders: Set({"A_fakerest": Set(EmbeddingSettings { source: Set(Rest), model: NotSet, revision: NotSet, pooling: NotSet, api_key: Set("My super secret"), dimensions: Set(384), binary_quantized: NotSet, document_template: NotSet, document_template_max_bytes: NotSet, url: Set("http://localhost:7777"), request: Set(String("{{text}}")), response: Set(String("{{embedding}}")), headers: NotSet, search_embedder: NotSet, indexing_embedder: NotSet, distribution: NotSet }), "B_small_hf": Set(EmbeddingSettings { source: Set(HuggingFace), model: Set("sentence-transformers/all-MiniLM-L6-v2"), revision: Set("e4ce9877abf3edfe10b0d82785e83bdcb973e22e"), pooling: NotSet, api_key: NotSet, dimensions: NotSet, binary_quantized: NotSet, document_template: Set("{{doc.doggo}} the {{doc.breed}} best doggo"), document_template_max_bytes: NotSet, url: NotSet, request: NotSet, response: NotSet, headers: NotSet, search_embedder: NotSet, indexing_embedder: NotSet, distribution: NotSet })}), search_cutoff_ms: NotSet, localized_attributes: NotSet, facet_search: NotSet, prefix_search: NotSet, _kind: PhantomData } }, kind: SettingsUpdate { index_uid: "doggos", new_settings: Settings { displayed_attributes: WildcardSetting(NotSet), searchable_attributes: WildcardSetting(NotSet), filterable_attributes: NotSet, sortable_attributes: NotSet, ranking_rules: NotSet, stop_words: NotSet, non_separator_tokens: NotSet, separator_tokens: NotSet, dictionary: NotSet, synonyms: NotSet, distinct_attribute: NotSet, proximity_precision: NotSet, typo_tolerance: NotSet, faceting: NotSet, pagination: NotSet, embedders: Set({"A_fakerest": Set(EmbeddingSettings { source: Set(Rest), model: NotSet, revision: NotSet, pooling: NotSet, api_key: Set("My super secret"), dimensions: Set(384), binary_quantized: NotSet, document_template: NotSet, document_template_max_bytes: NotSet, url: Set("http://localhost:7777"), request: Set(String("{{text}}")), response: Set(String("{{embedding}}")), headers: NotSet, search_embedder: NotSet, indexing_embedder: NotSet, distribution: NotSet }), "B_small_hf": Set(EmbeddingSettings { source: Set(HuggingFace), model: Set("sentence-transformers/all-MiniLM-L6-v2"), revision: Set("e4ce9877abf3edfe10b0d82785e83bdcb973e22e"), pooling: NotSet, api_key: NotSet, dimensions: NotSet, binary_quantized: NotSet, document_template: Set("{{doc.doggo}} the {{doc.breed}} best doggo"), document_template_max_bytes: NotSet, url: NotSet, request: NotSet, response: NotSet, headers: NotSet, search_embedder: NotSet, indexing_embedder: NotSet, distribution: NotSet })}), search_cutoff_ms: NotSet, localized_attributes: NotSet, facet_search: NotSet, prefix_search: NotSet, _kind: PhantomData }, is_deletion: false, allow_index_creation: true }} +0 {uid: 0, batch_uid: 0, status: succeeded, details: { settings: Settings { displayed_attributes: WildcardSetting(NotSet), searchable_attributes: WildcardSetting(NotSet), filterable_attributes: NotSet, sortable_attributes: NotSet, ranking_rules: NotSet, stop_words: NotSet, non_separator_tokens: NotSet, separator_tokens: NotSet, dictionary: NotSet, synonyms: NotSet, distinct_attribute: NotSet, proximity_precision: NotSet, typo_tolerance: NotSet, faceting: NotSet, pagination: NotSet, embedders: Set({"A_fakerest": Set(EmbeddingSettings { source: Set(Rest), model: NotSet, revision: NotSet, pooling: NotSet, api_key: Set("My super secret"), dimensions: Set(384), binary_quantized: NotSet, document_template: NotSet, document_template_max_bytes: NotSet, url: Set("http://localhost:7777"), request: Set(String("{{text}}")), response: Set(String("{{embedding}}")), headers: NotSet, search_embedder: NotSet, indexing_embedder: NotSet, distribution: NotSet }), "B_small_hf": Set(EmbeddingSettings { source: Set(HuggingFace), model: Set("sentence-transformers/all-MiniLM-L6-v2"), revision: Set("e4ce9877abf3edfe10b0d82785e83bdcb973e22e"), pooling: NotSet, api_key: NotSet, dimensions: NotSet, binary_quantized: NotSet, document_template: Set("{{doc.doggo}} the {{doc.breed}} best doggo"), document_template_max_bytes: NotSet, url: NotSet, request: NotSet, response: NotSet, headers: NotSet, search_embedder: NotSet, indexing_embedder: NotSet, distribution: NotSet })}), search_cutoff_ms: NotSet, localized_attributes: NotSet, facet_search: NotSet, prefix_search: NotSet, chat: NotSet, _kind: PhantomData } }, kind: SettingsUpdate { index_uid: "doggos", new_settings: Settings { displayed_attributes: WildcardSetting(NotSet), searchable_attributes: WildcardSetting(NotSet), filterable_attributes: NotSet, sortable_attributes: NotSet, ranking_rules: NotSet, stop_words: NotSet, non_separator_tokens: NotSet, separator_tokens: NotSet, dictionary: NotSet, synonyms: NotSet, distinct_attribute: NotSet, proximity_precision: NotSet, typo_tolerance: NotSet, faceting: NotSet, pagination: NotSet, embedders: Set({"A_fakerest": Set(EmbeddingSettings { source: Set(Rest), model: NotSet, revision: NotSet, pooling: NotSet, api_key: Set("My super secret"), dimensions: Set(384), binary_quantized: NotSet, document_template: NotSet, document_template_max_bytes: NotSet, url: Set("http://localhost:7777"), request: Set(String("{{text}}")), response: Set(String("{{embedding}}")), headers: NotSet, search_embedder: NotSet, indexing_embedder: NotSet, distribution: NotSet }), "B_small_hf": Set(EmbeddingSettings { source: Set(HuggingFace), model: Set("sentence-transformers/all-MiniLM-L6-v2"), revision: Set("e4ce9877abf3edfe10b0d82785e83bdcb973e22e"), pooling: NotSet, api_key: NotSet, dimensions: NotSet, binary_quantized: NotSet, document_template: Set("{{doc.doggo}} the {{doc.breed}} best doggo"), document_template_max_bytes: NotSet, url: NotSet, request: NotSet, response: NotSet, headers: NotSet, search_embedder: NotSet, indexing_embedder: NotSet, distribution: NotSet })}), search_cutoff_ms: NotSet, localized_attributes: NotSet, facet_search: NotSet, prefix_search: NotSet, chat: NotSet, _kind: PhantomData }, is_deletion: false, allow_index_creation: true }} ---------------------------------------------------------------------- ### Status: enqueued [] diff --git a/crates/index-scheduler/src/scheduler/snapshots/test_failure.rs/fail_in_process_batch_for_document_deletion/after_adding_the_documents.snap b/crates/index-scheduler/src/scheduler/snapshots/test_failure.rs/fail_in_process_batch_for_document_deletion/after_adding_the_documents.snap index c5696578a..96d93de51 100644 --- a/crates/index-scheduler/src/scheduler/snapshots/test_failure.rs/fail_in_process_batch_for_document_deletion/after_adding_the_documents.snap +++ b/crates/index-scheduler/src/scheduler/snapshots/test_failure.rs/fail_in_process_batch_for_document_deletion/after_adding_the_documents.snap @@ -6,7 +6,7 @@ source: crates/index-scheduler/src/scheduler/test_failure.rs [] ---------------------------------------------------------------------- ### All Tasks: -0 {uid: 0, batch_uid: 0, status: succeeded, details: { settings: Settings { displayed_attributes: WildcardSetting(NotSet), searchable_attributes: WildcardSetting(NotSet), filterable_attributes: Set([Field("catto")]), sortable_attributes: NotSet, ranking_rules: NotSet, stop_words: NotSet, non_separator_tokens: NotSet, separator_tokens: NotSet, dictionary: NotSet, synonyms: NotSet, distinct_attribute: NotSet, proximity_precision: NotSet, typo_tolerance: NotSet, faceting: NotSet, pagination: NotSet, embedders: NotSet, search_cutoff_ms: NotSet, localized_attributes: NotSet, facet_search: NotSet, prefix_search: NotSet, _kind: PhantomData } }, kind: SettingsUpdate { index_uid: "doggos", new_settings: Settings { displayed_attributes: WildcardSetting(NotSet), searchable_attributes: WildcardSetting(NotSet), filterable_attributes: Set([Field("catto")]), sortable_attributes: NotSet, ranking_rules: NotSet, stop_words: NotSet, non_separator_tokens: NotSet, separator_tokens: NotSet, dictionary: NotSet, synonyms: NotSet, distinct_attribute: NotSet, proximity_precision: NotSet, typo_tolerance: NotSet, faceting: NotSet, pagination: NotSet, embedders: NotSet, search_cutoff_ms: NotSet, localized_attributes: NotSet, facet_search: NotSet, prefix_search: NotSet, _kind: PhantomData }, is_deletion: false, allow_index_creation: true }} +0 {uid: 0, batch_uid: 0, status: succeeded, details: { settings: Settings { displayed_attributes: WildcardSetting(NotSet), searchable_attributes: WildcardSetting(NotSet), filterable_attributes: Set([Field("catto")]), sortable_attributes: NotSet, ranking_rules: NotSet, stop_words: NotSet, non_separator_tokens: NotSet, separator_tokens: NotSet, dictionary: NotSet, synonyms: NotSet, distinct_attribute: NotSet, proximity_precision: NotSet, typo_tolerance: NotSet, faceting: NotSet, pagination: NotSet, embedders: NotSet, search_cutoff_ms: NotSet, localized_attributes: NotSet, facet_search: NotSet, prefix_search: NotSet, chat: NotSet, _kind: PhantomData } }, kind: SettingsUpdate { index_uid: "doggos", new_settings: Settings { displayed_attributes: WildcardSetting(NotSet), searchable_attributes: WildcardSetting(NotSet), filterable_attributes: Set([Field("catto")]), sortable_attributes: NotSet, ranking_rules: NotSet, stop_words: NotSet, non_separator_tokens: NotSet, separator_tokens: NotSet, dictionary: NotSet, synonyms: NotSet, distinct_attribute: NotSet, proximity_precision: NotSet, typo_tolerance: NotSet, faceting: NotSet, pagination: NotSet, embedders: NotSet, search_cutoff_ms: NotSet, localized_attributes: NotSet, facet_search: NotSet, prefix_search: NotSet, chat: NotSet, _kind: PhantomData }, is_deletion: false, allow_index_creation: true }} 1 {uid: 1, batch_uid: 1, status: succeeded, details: { received_documents: 3, indexed_documents: Some(3) }, kind: DocumentAdditionOrUpdate { index_uid: "doggos", primary_key: Some("id"), method: ReplaceDocuments, content_file: 00000000-0000-0000-0000-000000000000, documents_count: 3, allow_index_creation: true }} ---------------------------------------------------------------------- ### Status: diff --git a/crates/index-scheduler/src/scheduler/snapshots/test_failure.rs/fail_in_process_batch_for_document_deletion/after_adding_the_settings.snap b/crates/index-scheduler/src/scheduler/snapshots/test_failure.rs/fail_in_process_batch_for_document_deletion/after_adding_the_settings.snap index edabbfa3c..76a77e5c0 100644 --- a/crates/index-scheduler/src/scheduler/snapshots/test_failure.rs/fail_in_process_batch_for_document_deletion/after_adding_the_settings.snap +++ b/crates/index-scheduler/src/scheduler/snapshots/test_failure.rs/fail_in_process_batch_for_document_deletion/after_adding_the_settings.snap @@ -6,7 +6,7 @@ source: crates/index-scheduler/src/scheduler/test_failure.rs [] ---------------------------------------------------------------------- ### All Tasks: -0 {uid: 0, batch_uid: 0, status: succeeded, details: { settings: Settings { displayed_attributes: WildcardSetting(NotSet), searchable_attributes: WildcardSetting(NotSet), filterable_attributes: Set([Field("catto")]), sortable_attributes: NotSet, ranking_rules: NotSet, stop_words: NotSet, non_separator_tokens: NotSet, separator_tokens: NotSet, dictionary: NotSet, synonyms: NotSet, distinct_attribute: NotSet, proximity_precision: NotSet, typo_tolerance: NotSet, faceting: NotSet, pagination: NotSet, embedders: NotSet, search_cutoff_ms: NotSet, localized_attributes: NotSet, facet_search: NotSet, prefix_search: NotSet, _kind: PhantomData } }, kind: SettingsUpdate { index_uid: "doggos", new_settings: Settings { displayed_attributes: WildcardSetting(NotSet), searchable_attributes: WildcardSetting(NotSet), filterable_attributes: Set([Field("catto")]), sortable_attributes: NotSet, ranking_rules: NotSet, stop_words: NotSet, non_separator_tokens: NotSet, separator_tokens: NotSet, dictionary: NotSet, synonyms: NotSet, distinct_attribute: NotSet, proximity_precision: NotSet, typo_tolerance: NotSet, faceting: NotSet, pagination: NotSet, embedders: NotSet, search_cutoff_ms: NotSet, localized_attributes: NotSet, facet_search: NotSet, prefix_search: NotSet, _kind: PhantomData }, is_deletion: false, allow_index_creation: true }} +0 {uid: 0, batch_uid: 0, status: succeeded, details: { settings: Settings { displayed_attributes: WildcardSetting(NotSet), searchable_attributes: WildcardSetting(NotSet), filterable_attributes: Set([Field("catto")]), sortable_attributes: NotSet, ranking_rules: NotSet, stop_words: NotSet, non_separator_tokens: NotSet, separator_tokens: NotSet, dictionary: NotSet, synonyms: NotSet, distinct_attribute: NotSet, proximity_precision: NotSet, typo_tolerance: NotSet, faceting: NotSet, pagination: NotSet, embedders: NotSet, search_cutoff_ms: NotSet, localized_attributes: NotSet, facet_search: NotSet, prefix_search: NotSet, chat: NotSet, _kind: PhantomData } }, kind: SettingsUpdate { index_uid: "doggos", new_settings: Settings { displayed_attributes: WildcardSetting(NotSet), searchable_attributes: WildcardSetting(NotSet), filterable_attributes: Set([Field("catto")]), sortable_attributes: NotSet, ranking_rules: NotSet, stop_words: NotSet, non_separator_tokens: NotSet, separator_tokens: NotSet, dictionary: NotSet, synonyms: NotSet, distinct_attribute: NotSet, proximity_precision: NotSet, typo_tolerance: NotSet, faceting: NotSet, pagination: NotSet, embedders: NotSet, search_cutoff_ms: NotSet, localized_attributes: NotSet, facet_search: NotSet, prefix_search: NotSet, chat: NotSet, _kind: PhantomData }, is_deletion: false, allow_index_creation: true }} 1 {uid: 1, status: enqueued, details: { received_documents: 3, indexed_documents: None }, kind: DocumentAdditionOrUpdate { index_uid: "doggos", primary_key: Some("id"), method: ReplaceDocuments, content_file: 00000000-0000-0000-0000-000000000000, documents_count: 3, allow_index_creation: true }} ---------------------------------------------------------------------- ### Status: diff --git a/crates/index-scheduler/src/scheduler/snapshots/test_failure.rs/fail_in_process_batch_for_document_deletion/after_removing_the_documents.snap b/crates/index-scheduler/src/scheduler/snapshots/test_failure.rs/fail_in_process_batch_for_document_deletion/after_removing_the_documents.snap index aa86c61be..422bed51f 100644 --- a/crates/index-scheduler/src/scheduler/snapshots/test_failure.rs/fail_in_process_batch_for_document_deletion/after_removing_the_documents.snap +++ b/crates/index-scheduler/src/scheduler/snapshots/test_failure.rs/fail_in_process_batch_for_document_deletion/after_removing_the_documents.snap @@ -6,7 +6,7 @@ source: crates/index-scheduler/src/scheduler/test_failure.rs [] ---------------------------------------------------------------------- ### All Tasks: -0 {uid: 0, batch_uid: 0, status: succeeded, details: { settings: Settings { displayed_attributes: WildcardSetting(NotSet), searchable_attributes: WildcardSetting(NotSet), filterable_attributes: Set([Field("catto")]), sortable_attributes: NotSet, ranking_rules: NotSet, stop_words: NotSet, non_separator_tokens: NotSet, separator_tokens: NotSet, dictionary: NotSet, synonyms: NotSet, distinct_attribute: NotSet, proximity_precision: NotSet, typo_tolerance: NotSet, faceting: NotSet, pagination: NotSet, embedders: NotSet, search_cutoff_ms: NotSet, localized_attributes: NotSet, facet_search: NotSet, prefix_search: NotSet, _kind: PhantomData } }, kind: SettingsUpdate { index_uid: "doggos", new_settings: Settings { displayed_attributes: WildcardSetting(NotSet), searchable_attributes: WildcardSetting(NotSet), filterable_attributes: Set([Field("catto")]), sortable_attributes: NotSet, ranking_rules: NotSet, stop_words: NotSet, non_separator_tokens: NotSet, separator_tokens: NotSet, dictionary: NotSet, synonyms: NotSet, distinct_attribute: NotSet, proximity_precision: NotSet, typo_tolerance: NotSet, faceting: NotSet, pagination: NotSet, embedders: NotSet, search_cutoff_ms: NotSet, localized_attributes: NotSet, facet_search: NotSet, prefix_search: NotSet, _kind: PhantomData }, is_deletion: false, allow_index_creation: true }} +0 {uid: 0, batch_uid: 0, status: succeeded, details: { settings: Settings { displayed_attributes: WildcardSetting(NotSet), searchable_attributes: WildcardSetting(NotSet), filterable_attributes: Set([Field("catto")]), sortable_attributes: NotSet, ranking_rules: NotSet, stop_words: NotSet, non_separator_tokens: NotSet, separator_tokens: NotSet, dictionary: NotSet, synonyms: NotSet, distinct_attribute: NotSet, proximity_precision: NotSet, typo_tolerance: NotSet, faceting: NotSet, pagination: NotSet, embedders: NotSet, search_cutoff_ms: NotSet, localized_attributes: NotSet, facet_search: NotSet, prefix_search: NotSet, chat: NotSet, _kind: PhantomData } }, kind: SettingsUpdate { index_uid: "doggos", new_settings: Settings { displayed_attributes: WildcardSetting(NotSet), searchable_attributes: WildcardSetting(NotSet), filterable_attributes: Set([Field("catto")]), sortable_attributes: NotSet, ranking_rules: NotSet, stop_words: NotSet, non_separator_tokens: NotSet, separator_tokens: NotSet, dictionary: NotSet, synonyms: NotSet, distinct_attribute: NotSet, proximity_precision: NotSet, typo_tolerance: NotSet, faceting: NotSet, pagination: NotSet, embedders: NotSet, search_cutoff_ms: NotSet, localized_attributes: NotSet, facet_search: NotSet, prefix_search: NotSet, chat: NotSet, _kind: PhantomData }, is_deletion: false, allow_index_creation: true }} 1 {uid: 1, batch_uid: 1, status: succeeded, details: { received_documents: 3, indexed_documents: Some(3) }, kind: DocumentAdditionOrUpdate { index_uid: "doggos", primary_key: Some("id"), method: ReplaceDocuments, content_file: 00000000-0000-0000-0000-000000000000, documents_count: 3, allow_index_creation: true }} 2 {uid: 2, batch_uid: 2, status: succeeded, details: { received_document_ids: 1, deleted_documents: Some(1) }, kind: DocumentDeletion { index_uid: "doggos", documents_ids: ["1"] }} 3 {uid: 3, batch_uid: 2, status: failed, error: ResponseError { code: 200, message: "Index `doggos`: Invalid type for filter subexpression: expected: String, Array, found: true.", error_code: "invalid_document_filter", error_type: "invalid_request", error_link: "https://docs.meilisearch.com/errors#invalid_document_filter" }, details: { original_filter: true, deleted_documents: Some(0) }, kind: DocumentDeletionByFilter { index_uid: "doggos", filter_expr: Bool(true) }} diff --git a/crates/index-scheduler/src/scheduler/snapshots/test_failure.rs/fail_in_process_batch_for_document_deletion/registered_the_document_deletions.snap b/crates/index-scheduler/src/scheduler/snapshots/test_failure.rs/fail_in_process_batch_for_document_deletion/registered_the_document_deletions.snap index 410c9c68e..d8996f82c 100644 --- a/crates/index-scheduler/src/scheduler/snapshots/test_failure.rs/fail_in_process_batch_for_document_deletion/registered_the_document_deletions.snap +++ b/crates/index-scheduler/src/scheduler/snapshots/test_failure.rs/fail_in_process_batch_for_document_deletion/registered_the_document_deletions.snap @@ -6,7 +6,7 @@ source: crates/index-scheduler/src/scheduler/test_failure.rs [] ---------------------------------------------------------------------- ### All Tasks: -0 {uid: 0, batch_uid: 0, status: succeeded, details: { settings: Settings { displayed_attributes: WildcardSetting(NotSet), searchable_attributes: WildcardSetting(NotSet), filterable_attributes: Set([Field("catto")]), sortable_attributes: NotSet, ranking_rules: NotSet, stop_words: NotSet, non_separator_tokens: NotSet, separator_tokens: NotSet, dictionary: NotSet, synonyms: NotSet, distinct_attribute: NotSet, proximity_precision: NotSet, typo_tolerance: NotSet, faceting: NotSet, pagination: NotSet, embedders: NotSet, search_cutoff_ms: NotSet, localized_attributes: NotSet, facet_search: NotSet, prefix_search: NotSet, _kind: PhantomData } }, kind: SettingsUpdate { index_uid: "doggos", new_settings: Settings { displayed_attributes: WildcardSetting(NotSet), searchable_attributes: WildcardSetting(NotSet), filterable_attributes: Set([Field("catto")]), sortable_attributes: NotSet, ranking_rules: NotSet, stop_words: NotSet, non_separator_tokens: NotSet, separator_tokens: NotSet, dictionary: NotSet, synonyms: NotSet, distinct_attribute: NotSet, proximity_precision: NotSet, typo_tolerance: NotSet, faceting: NotSet, pagination: NotSet, embedders: NotSet, search_cutoff_ms: NotSet, localized_attributes: NotSet, facet_search: NotSet, prefix_search: NotSet, _kind: PhantomData }, is_deletion: false, allow_index_creation: true }} +0 {uid: 0, batch_uid: 0, status: succeeded, details: { settings: Settings { displayed_attributes: WildcardSetting(NotSet), searchable_attributes: WildcardSetting(NotSet), filterable_attributes: Set([Field("catto")]), sortable_attributes: NotSet, ranking_rules: NotSet, stop_words: NotSet, non_separator_tokens: NotSet, separator_tokens: NotSet, dictionary: NotSet, synonyms: NotSet, distinct_attribute: NotSet, proximity_precision: NotSet, typo_tolerance: NotSet, faceting: NotSet, pagination: NotSet, embedders: NotSet, search_cutoff_ms: NotSet, localized_attributes: NotSet, facet_search: NotSet, prefix_search: NotSet, chat: NotSet, _kind: PhantomData } }, kind: SettingsUpdate { index_uid: "doggos", new_settings: Settings { displayed_attributes: WildcardSetting(NotSet), searchable_attributes: WildcardSetting(NotSet), filterable_attributes: Set([Field("catto")]), sortable_attributes: NotSet, ranking_rules: NotSet, stop_words: NotSet, non_separator_tokens: NotSet, separator_tokens: NotSet, dictionary: NotSet, synonyms: NotSet, distinct_attribute: NotSet, proximity_precision: NotSet, typo_tolerance: NotSet, faceting: NotSet, pagination: NotSet, embedders: NotSet, search_cutoff_ms: NotSet, localized_attributes: NotSet, facet_search: NotSet, prefix_search: NotSet, chat: NotSet, _kind: PhantomData }, is_deletion: false, allow_index_creation: true }} 1 {uid: 1, batch_uid: 1, status: succeeded, details: { received_documents: 3, indexed_documents: Some(3) }, kind: DocumentAdditionOrUpdate { index_uid: "doggos", primary_key: Some("id"), method: ReplaceDocuments, content_file: 00000000-0000-0000-0000-000000000000, documents_count: 3, allow_index_creation: true }} 2 {uid: 2, status: enqueued, details: { received_document_ids: 1, deleted_documents: None }, kind: DocumentDeletion { index_uid: "doggos", documents_ids: ["1"] }} 3 {uid: 3, status: enqueued, details: { original_filter: true, deleted_documents: None }, kind: DocumentDeletionByFilter { index_uid: "doggos", filter_expr: Bool(true) }} diff --git a/crates/index-scheduler/src/scheduler/snapshots/test_failure.rs/fail_in_process_batch_for_document_deletion/registered_the_setting_and_document_addition.snap b/crates/index-scheduler/src/scheduler/snapshots/test_failure.rs/fail_in_process_batch_for_document_deletion/registered_the_setting_and_document_addition.snap index 0ba3ef598..e7b06eb31 100644 --- a/crates/index-scheduler/src/scheduler/snapshots/test_failure.rs/fail_in_process_batch_for_document_deletion/registered_the_setting_and_document_addition.snap +++ b/crates/index-scheduler/src/scheduler/snapshots/test_failure.rs/fail_in_process_batch_for_document_deletion/registered_the_setting_and_document_addition.snap @@ -6,7 +6,7 @@ source: crates/index-scheduler/src/scheduler/test_failure.rs [] ---------------------------------------------------------------------- ### All Tasks: -0 {uid: 0, status: enqueued, details: { settings: Settings { displayed_attributes: WildcardSetting(NotSet), searchable_attributes: WildcardSetting(NotSet), filterable_attributes: Set([Field("catto")]), sortable_attributes: NotSet, ranking_rules: NotSet, stop_words: NotSet, non_separator_tokens: NotSet, separator_tokens: NotSet, dictionary: NotSet, synonyms: NotSet, distinct_attribute: NotSet, proximity_precision: NotSet, typo_tolerance: NotSet, faceting: NotSet, pagination: NotSet, embedders: NotSet, search_cutoff_ms: NotSet, localized_attributes: NotSet, facet_search: NotSet, prefix_search: NotSet, _kind: PhantomData } }, kind: SettingsUpdate { index_uid: "doggos", new_settings: Settings { displayed_attributes: WildcardSetting(NotSet), searchable_attributes: WildcardSetting(NotSet), filterable_attributes: Set([Field("catto")]), sortable_attributes: NotSet, ranking_rules: NotSet, stop_words: NotSet, non_separator_tokens: NotSet, separator_tokens: NotSet, dictionary: NotSet, synonyms: NotSet, distinct_attribute: NotSet, proximity_precision: NotSet, typo_tolerance: NotSet, faceting: NotSet, pagination: NotSet, embedders: NotSet, search_cutoff_ms: NotSet, localized_attributes: NotSet, facet_search: NotSet, prefix_search: NotSet, _kind: PhantomData }, is_deletion: false, allow_index_creation: true }} +0 {uid: 0, status: enqueued, details: { settings: Settings { displayed_attributes: WildcardSetting(NotSet), searchable_attributes: WildcardSetting(NotSet), filterable_attributes: Set([Field("catto")]), sortable_attributes: NotSet, ranking_rules: NotSet, stop_words: NotSet, non_separator_tokens: NotSet, separator_tokens: NotSet, dictionary: NotSet, synonyms: NotSet, distinct_attribute: NotSet, proximity_precision: NotSet, typo_tolerance: NotSet, faceting: NotSet, pagination: NotSet, embedders: NotSet, search_cutoff_ms: NotSet, localized_attributes: NotSet, facet_search: NotSet, prefix_search: NotSet, chat: NotSet, _kind: PhantomData } }, kind: SettingsUpdate { index_uid: "doggos", new_settings: Settings { displayed_attributes: WildcardSetting(NotSet), searchable_attributes: WildcardSetting(NotSet), filterable_attributes: Set([Field("catto")]), sortable_attributes: NotSet, ranking_rules: NotSet, stop_words: NotSet, non_separator_tokens: NotSet, separator_tokens: NotSet, dictionary: NotSet, synonyms: NotSet, distinct_attribute: NotSet, proximity_precision: NotSet, typo_tolerance: NotSet, faceting: NotSet, pagination: NotSet, embedders: NotSet, search_cutoff_ms: NotSet, localized_attributes: NotSet, facet_search: NotSet, prefix_search: NotSet, chat: NotSet, _kind: PhantomData }, is_deletion: false, allow_index_creation: true }} 1 {uid: 1, status: enqueued, details: { received_documents: 3, indexed_documents: None }, kind: DocumentAdditionOrUpdate { index_uid: "doggos", primary_key: Some("id"), method: ReplaceDocuments, content_file: 00000000-0000-0000-0000-000000000000, documents_count: 3, allow_index_creation: true }} ---------------------------------------------------------------------- ### Status: diff --git a/crates/meilisearch/tests/auth/api_keys.rs b/crates/meilisearch/tests/auth/api_keys.rs index edfcd1c29..708feb2bb 100644 --- a/crates/meilisearch/tests/auth/api_keys.rs +++ b/crates/meilisearch/tests/auth/api_keys.rs @@ -421,7 +421,7 @@ async fn error_add_api_key_invalid_parameters_actions() { meili_snap::snapshot!(code, @"400 Bad Request"); meili_snap::snapshot!(meili_snap::json_string!(response, { ".createdAt" => "[ignored]", ".updatedAt" => "[ignored]" }), @r###" { - "message": "Unknown value `doc.add` at `.actions[0]`: expected one of `*`, `search`, `documents.*`, `documents.add`, `documents.get`, `documents.delete`, `indexes.*`, `indexes.create`, `indexes.get`, `indexes.update`, `indexes.delete`, `indexes.swap`, `tasks.*`, `tasks.cancel`, `tasks.delete`, `tasks.get`, `settings.*`, `settings.get`, `settings.update`, `stats.*`, `stats.get`, `metrics.*`, `metrics.get`, `dumps.*`, `dumps.create`, `snapshots.*`, `snapshots.create`, `version`, `keys.create`, `keys.get`, `keys.update`, `keys.delete`, `experimental.get`, `experimental.update`, `network.get`, `network.update`", + "message": "Unknown value `doc.add` at `.actions[0]`: expected one of `*`, `search`, `documents.*`, `documents.add`, `documents.get`, `documents.delete`, `indexes.*`, `indexes.create`, `indexes.get`, `indexes.update`, `indexes.delete`, `indexes.swap`, `tasks.*`, `tasks.cancel`, `tasks.delete`, `tasks.get`, `settings.*`, `settings.get`, `settings.update`, `stats.*`, `stats.get`, `metrics.*`, `metrics.get`, `dumps.*`, `dumps.create`, `snapshots.*`, `snapshots.create`, `version`, `keys.create`, `keys.get`, `keys.update`, `keys.delete`, `experimental.get`, `experimental.update`, `network.get`, `network.update`, `chatCompletion`, `chats.get`, `chatsSettings.*`, `chatsSettings.get`, `chatsSettings.update`, `chatsSettings.delete`", "code": "invalid_api_key_actions", "type": "invalid_request", "link": "https://docs.meilisearch.com/errors#invalid_api_key_actions" @@ -826,8 +826,8 @@ async fn list_api_keys() { "key": "[ignored]", "uid": "[ignored]", "actions": [ - "search", - "chat.get" + "chatCompletion", + "search" ], "indexes": [ "*" @@ -869,7 +869,7 @@ async fn list_api_keys() { ], "offset": 0, "limit": 20, - "total": 3 + "total": 4 } "###); meili_snap::snapshot!(code, @"200 OK"); diff --git a/crates/meilisearch/tests/dumps/mod.rs b/crates/meilisearch/tests/dumps/mod.rs index 3ba3c20eb..3d3bc01db 100644 --- a/crates/meilisearch/tests/dumps/mod.rs +++ b/crates/meilisearch/tests/dumps/mod.rs @@ -2187,7 +2187,8 @@ async fn import_dump_v6_containing_experimental_features() { "containsFilter": false, "network": false, "getTaskDocumentsRoute": false, - "compositeEmbedders": false + "compositeEmbedders": false, + "chatCompletions": false } "###); @@ -2312,7 +2313,8 @@ async fn import_dump_v6_containing_batches_and_enqueued_tasks() { "containsFilter": false, "network": false, "getTaskDocumentsRoute": false, - "compositeEmbedders": false + "compositeEmbedders": false, + "chatCompletions": false } "###); @@ -2417,7 +2419,8 @@ async fn generate_and_import_dump_containing_vectors() { "containsFilter": false, "network": false, "getTaskDocumentsRoute": false, - "compositeEmbedders": false + "compositeEmbedders": false, + "chatCompletions": false } "###); diff --git a/crates/meilisearch/tests/features/mod.rs b/crates/meilisearch/tests/features/mod.rs index 34cd40e38..d0d457d3e 100644 --- a/crates/meilisearch/tests/features/mod.rs +++ b/crates/meilisearch/tests/features/mod.rs @@ -24,7 +24,8 @@ async fn experimental_features() { "containsFilter": false, "network": false, "getTaskDocumentsRoute": false, - "compositeEmbedders": false + "compositeEmbedders": false, + "chatCompletions": false } "###); @@ -39,7 +40,8 @@ async fn experimental_features() { "containsFilter": false, "network": false, "getTaskDocumentsRoute": false, - "compositeEmbedders": false + "compositeEmbedders": false, + "chatCompletions": false } "###); @@ -54,7 +56,8 @@ async fn experimental_features() { "containsFilter": false, "network": false, "getTaskDocumentsRoute": false, - "compositeEmbedders": false + "compositeEmbedders": false, + "chatCompletions": false } "###); @@ -70,7 +73,8 @@ async fn experimental_features() { "containsFilter": false, "network": false, "getTaskDocumentsRoute": false, - "compositeEmbedders": false + "compositeEmbedders": false, + "chatCompletions": false } "###); @@ -86,7 +90,8 @@ async fn experimental_features() { "containsFilter": false, "network": false, "getTaskDocumentsRoute": false, - "compositeEmbedders": false + "compositeEmbedders": false, + "chatCompletions": false } "###); } @@ -109,7 +114,8 @@ async fn experimental_feature_metrics() { "containsFilter": false, "network": false, "getTaskDocumentsRoute": false, - "compositeEmbedders": false + "compositeEmbedders": false, + "chatCompletions": false } "###); @@ -156,7 +162,7 @@ async fn errors() { meili_snap::snapshot!(code, @"400 Bad Request"); meili_snap::snapshot!(meili_snap::json_string!(response), @r###" { - "message": "Unknown field `NotAFeature`: expected one of `metrics`, `logsRoute`, `editDocumentsByFunction`, `containsFilter`, `network`, `getTaskDocumentsRoute`, `compositeEmbedders`", + "message": "Unknown field `NotAFeature`: expected one of `metrics`, `logsRoute`, `editDocumentsByFunction`, `containsFilter`, `network`, `getTaskDocumentsRoute`, `compositeEmbedders`, `chatCompletions`", "code": "bad_request", "type": "invalid_request", "link": "https://docs.meilisearch.com/errors#bad_request" From cf2bc03bed30674cff82c10a7159801bda14ab32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Renault?= Date: Wed, 4 Jun 2025 14:50:20 +0200 Subject: [PATCH 064/103] Fix the API key issue by reordering the default keys --- crates/meilisearch-auth/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/meilisearch-auth/src/lib.rs b/crates/meilisearch-auth/src/lib.rs index a19ad7b8c..27d163192 100644 --- a/crates/meilisearch-auth/src/lib.rs +++ b/crates/meilisearch-auth/src/lib.rs @@ -350,9 +350,9 @@ pub struct IndexSearchRules { } fn generate_default_keys(store: &HeedAuthStore) -> Result<()> { + store.put_api_key(Key::default_chat())?; store.put_api_key(Key::default_admin())?; store.put_api_key(Key::default_search())?; - store.put_api_key(Key::default_chat())?; Ok(()) } From 258e6a115b9225b4e7d587786589bb9d9ff38097 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Renault?= Date: Wed, 4 Jun 2025 15:29:55 +0200 Subject: [PATCH 065/103] Fix some other tests --- crates/meilisearch/tests/auth/api_keys.rs | 32 +++++++++++------------ crates/meilisearch/tests/auth/errors.rs | 2 +- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/crates/meilisearch/tests/auth/api_keys.rs b/crates/meilisearch/tests/auth/api_keys.rs index 708feb2bb..69a1ccb53 100644 --- a/crates/meilisearch/tests/auth/api_keys.rs +++ b/crates/meilisearch/tests/auth/api_keys.rs @@ -820,22 +820,6 @@ async fn list_api_keys() { "createdAt": "[ignored]", "updatedAt": "[ignored]" }, - { - "name": "Default Chat API Key", - "description": "Use it to chat and search from the frontend", - "key": "[ignored]", - "uid": "[ignored]", - "actions": [ - "chatCompletion", - "search" - ], - "indexes": [ - "*" - ], - "expiresAt": null, - "createdAt": "[ignored]", - "updatedAt": "[ignored]" - }, { "name": "Default Search API Key", "description": "Use it to search from the frontend", @@ -865,6 +849,22 @@ async fn list_api_keys() { "expiresAt": null, "createdAt": "[ignored]", "updatedAt": "[ignored]" + }, + { + "name": "Default Chat API Key", + "description": "Use it to chat and search from the frontend", + "key": "[ignored]", + "uid": "[ignored]", + "actions": [ + "chatCompletion", + "search" + ], + "indexes": [ + "*" + ], + "expiresAt": null, + "createdAt": "[ignored]", + "updatedAt": "[ignored]" } ], "offset": 0, diff --git a/crates/meilisearch/tests/auth/errors.rs b/crates/meilisearch/tests/auth/errors.rs index 0e8968ef0..6d4c17e19 100644 --- a/crates/meilisearch/tests/auth/errors.rs +++ b/crates/meilisearch/tests/auth/errors.rs @@ -93,7 +93,7 @@ async fn create_api_key_bad_actions() { snapshot!(code, @"400 Bad Request"); snapshot!(json_string!(response), @r###" { - "message": "Unknown value `doggo` at `.actions[0]`: expected one of `*`, `search`, `documents.*`, `documents.add`, `documents.get`, `documents.delete`, `indexes.*`, `indexes.create`, `indexes.get`, `indexes.update`, `indexes.delete`, `indexes.swap`, `tasks.*`, `tasks.cancel`, `tasks.delete`, `tasks.get`, `settings.*`, `settings.get`, `settings.update`, `stats.*`, `stats.get`, `metrics.*`, `metrics.get`, `dumps.*`, `dumps.create`, `snapshots.*`, `snapshots.create`, `version`, `keys.create`, `keys.get`, `keys.update`, `keys.delete`, `experimental.get`, `experimental.update`, `network.get`, `network.update`", + "message": "Unknown value `doggo` at `.actions[0]`: expected one of `*`, `search`, `documents.*`, `documents.add`, `documents.get`, `documents.delete`, `indexes.*`, `indexes.create`, `indexes.get`, `indexes.update`, `indexes.delete`, `indexes.swap`, `tasks.*`, `tasks.cancel`, `tasks.delete`, `tasks.get`, `settings.*`, `settings.get`, `settings.update`, `stats.*`, `stats.get`, `metrics.*`, `metrics.get`, `dumps.*`, `dumps.create`, `snapshots.*`, `snapshots.create`, `version`, `keys.create`, `keys.get`, `keys.update`, `keys.delete`, `experimental.get`, `experimental.update`, `network.get`, `network.update`, `chatCompletion`, `chats.get`, `chatsSettings.*`, `chatsSettings.get`, `chatsSettings.update`, `chatsSettings.delete`", "code": "invalid_api_key_actions", "type": "invalid_request", "link": "https://docs.meilisearch.com/errors#invalid_api_key_actions" From 4dfb89168bbeb0326e38e06412cdc80dbdcea065 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Renault?= Date: Wed, 4 Jun 2025 15:41:33 +0200 Subject: [PATCH 066/103] Add a test for the chat route --- crates/meilisearch/tests/settings/get_settings.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/crates/meilisearch/tests/settings/get_settings.rs b/crates/meilisearch/tests/settings/get_settings.rs index 5c0f89ed3..e4c58cbcd 100644 --- a/crates/meilisearch/tests/settings/get_settings.rs +++ b/crates/meilisearch/tests/settings/get_settings.rs @@ -181,6 +181,16 @@ test_setting_routes!( update_verb: patch, default_value: {"enabled": true, "minWordSizeForTypos": {"oneTypo": 5, "twoTypos": 9}, "disableOnWords": [], "disableOnAttributes": [], "disableOnNumbers": false} }, + { + setting: chat, + update_verb: put, + default_value: { + "description": "", + "documentTemplate": "{% for field in fields %}{% if field.is_searchable and field.value != nil %}{{ field.name }}: {{ field.value }}\n{% endif %}{% endfor %}", + "documentTemplateMaxBytes": 400, + "searchParameters": {} + } + }, ); #[actix_rt::test] From 70670c3be4b83d77db58b5f82583217b5ebbc9e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Renault?= Date: Fri, 6 Jun 2025 12:08:37 +0200 Subject: [PATCH 067/103] Introduce the support of Azure, Gemini, vLLM --- Cargo.lock | 1 + crates/meilisearch-types/src/error.rs | 4 + crates/meilisearch-types/src/features.rs | 127 ++++++++++++++++++ crates/meilisearch/Cargo.toml | 1 + .../src/routes/chats/chat_completions.rs | 22 +-- crates/meilisearch/src/routes/chats/config.rs | 87 ++++++++++++ crates/meilisearch/src/routes/chats/mod.rs | 1 + .../meilisearch/src/routes/chats/settings.rs | 36 +++++ 8 files changed, 261 insertions(+), 18 deletions(-) create mode 100644 crates/meilisearch/src/routes/chats/config.rs diff --git a/Cargo.lock b/Cargo.lock index 4e897e580..8a3942d5b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3788,6 +3788,7 @@ dependencies = [ "rustls", "rustls-pemfile", "rustls-pki-types", + "secrecy", "segment", "serde", "serde_json", diff --git a/crates/meilisearch-types/src/error.rs b/crates/meilisearch-types/src/error.rs index 11bad977d..27f7580e2 100644 --- a/crates/meilisearch-types/src/error.rs +++ b/crates/meilisearch-types/src/error.rs @@ -391,6 +391,10 @@ EditDocumentsByFunctionError , InvalidRequest , BAD_REQU InvalidSettingsIndexChat , InvalidRequest , BAD_REQUEST ; // Experimental features - Chat Completions ChatWorkspaceNotFound , InvalidRequest , NOT_FOUND ; +InvalidChatCompletionOrgId , InvalidRequest , BAD_REQUEST ; +InvalidChatCompletionProjectId , InvalidRequest , BAD_REQUEST ; +InvalidChatCompletionApiVersion , InvalidRequest , BAD_REQUEST ; +InvalidChatCompletionDeploymentId , InvalidRequest , BAD_REQUEST ; InvalidChatCompletionSource , InvalidRequest , BAD_REQUEST ; InvalidChatCompletionBaseApi , InvalidRequest , BAD_REQUEST ; InvalidChatCompletionApiKey , InvalidRequest , BAD_REQUEST ; diff --git a/crates/meilisearch-types/src/features.rs b/crates/meilisearch-types/src/features.rs index 95706fb46..9bcd58347 100644 --- a/crates/meilisearch-types/src/features.rs +++ b/crates/meilisearch-types/src/features.rs @@ -51,6 +51,14 @@ pub struct Network { pub struct ChatCompletionSettings { pub source: ChatCompletionSource, #[serde(default)] + pub org_id: Option, + #[serde(default)] + pub project_id: Option, + #[serde(default)] + pub api_version: Option, + #[serde(default)] + pub deployment_id: Option, + #[serde(default)] pub base_api: Option, #[serde(default)] pub api_key: Option, @@ -88,6 +96,43 @@ impl ChatCompletionSettings { pub enum ChatCompletionSource { #[default] OpenAi, + AzureOpenAi, + Mistral, + Gemini, + VLlm, +} + +impl ChatCompletionSource { + pub fn system_role(&self, model: &str) -> &'static str { + match self { + ChatCompletionSource::OpenAi if Self::old_openai_model(model) => "system", + ChatCompletionSource::OpenAi => "developer", + ChatCompletionSource::AzureOpenAi if Self::old_openai_model(model) => "system", + ChatCompletionSource::AzureOpenAi => "developer", + ChatCompletionSource::Mistral => "system", + ChatCompletionSource::Gemini => "system", + ChatCompletionSource::VLlm => "system", + } + } + + /// Returns true if the model is an old OpenAI model. + /// + /// Old OpenAI models use the system role while new ones use the developer role. + fn old_openai_model(model: &str) -> bool { + ["gpt-3.5", "gpt-4", "gpt-4.1", "gpt-4.5", "gpt-4o", "chatgpt-4o"].iter().any(|old| { + model.starts_with(old) && model.chars().nth(old.len()).is_none_or(|last| last == '-') + }) + } + + pub fn base_url(&self) -> Option<&'static str> { + use ChatCompletionSource::*; + match self { + OpenAi => Some("https://api.openai.com/v1/"), + Mistral => Some("https://api.mistral.ai/v1/"), + Gemini => Some("https://generativelanguage.googleapis.com/v1beta/openai/"), + AzureOpenAi | VLlm => None, + } + } } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] @@ -111,3 +156,85 @@ impl Default for ChatCompletionPrompts { } } } + +#[cfg(test)] +mod tests { + use super::*; + + const ALL_OPENAI_MODELS_OLDINESS: &[(&str, bool)] = &[ + ("gpt-4-0613", true), + ("gpt-4", true), + ("gpt-3.5-turbo", true), + ("gpt-4o-audio-preview-2025-06-03", true), + ("gpt-4.1-nano", true), + ("gpt-4o-realtime-preview-2025-06-03", true), + ("gpt-3.5-turbo-instruct", true), + ("gpt-3.5-turbo-instruct-0914", true), + ("gpt-4-1106-preview", true), + ("gpt-3.5-turbo-1106", true), + ("gpt-4-0125-preview", true), + ("gpt-4-turbo-preview", true), + ("gpt-3.5-turbo-0125", true), + ("gpt-4-turbo", true), + ("gpt-4-turbo-2024-04-09", true), + ("gpt-4o", true), + ("gpt-4o-2024-05-13", true), + ("gpt-4o-mini-2024-07-18", true), + ("gpt-4o-mini", true), + ("gpt-4o-2024-08-06", true), + ("chatgpt-4o-latest", true), + ("gpt-4o-realtime-preview-2024-10-01", true), + ("gpt-4o-audio-preview-2024-10-01", true), + ("gpt-4o-audio-preview", true), + ("gpt-4o-realtime-preview", true), + ("gpt-4o-realtime-preview-2024-12-17", true), + ("gpt-4o-audio-preview-2024-12-17", true), + ("gpt-4o-mini-realtime-preview-2024-12-17", true), + ("gpt-4o-mini-audio-preview-2024-12-17", true), + ("gpt-4o-mini-realtime-preview", true), + ("gpt-4o-mini-audio-preview", true), + ("gpt-4o-2024-11-20", true), + ("gpt-4.5-preview", true), + ("gpt-4.5-preview-2025-02-27", true), + ("gpt-4o-search-preview-2025-03-11", true), + ("gpt-4o-search-preview", true), + ("gpt-4o-mini-search-preview-2025-03-11", true), + ("gpt-4o-mini-search-preview", true), + ("gpt-4o-transcribe", true), + ("gpt-4o-mini-transcribe", true), + ("gpt-4o-mini-tts", true), + ("gpt-4.1-2025-04-14", true), + ("gpt-4.1", true), + ("gpt-4.1-mini-2025-04-14", true), + ("gpt-4.1-mini", true), + ("gpt-4.1-nano-2025-04-14", true), + ("gpt-3.5-turbo-16k", true), + // + // new models + ("o1-preview-2024-09-12", false), + ("o1-preview", false), + ("o1-mini-2024-09-12", false), + ("o1-mini", false), + ("o1-2024-12-17", false), + ("o1", false), + ("o3-mini", false), + ("o3-mini-2025-01-31", false), + ("o1-pro-2025-03-19", false), + ("o1-pro", false), + ("o3-2025-04-16", false), + ("o4-mini-2025-04-16", false), + ("o3", false), + ("o4-mini", false), + ]; + + #[test] + fn old_openai_models() { + for (name, is_old) in ALL_OPENAI_MODELS_OLDINESS.iter().copied() { + assert_eq!( + ChatCompletionSource::old_openai_model(name), + is_old, + "Model {name} is not considered old" + ); + } + } +} diff --git a/crates/meilisearch/Cargo.toml b/crates/meilisearch/Cargo.toml index deea9f803..a40b63a24 100644 --- a/crates/meilisearch/Cargo.toml +++ b/crates/meilisearch/Cargo.toml @@ -114,6 +114,7 @@ utoipa = { version = "5.3.1", features = [ ] } utoipa-scalar = { version = "0.3.0", optional = true, features = ["actix-web"] } async-openai = { git = "https://github.com/meilisearch/async-openai", branch = "better-error-handling" } +secrecy = "0.10.3" actix-web-lab = { version = "0.24.1", default-features = false } [dev-dependencies] diff --git a/crates/meilisearch/src/routes/chats/chat_completions.rs b/crates/meilisearch/src/routes/chats/chat_completions.rs index 6f6b50d1c..f588541fa 100644 --- a/crates/meilisearch/src/routes/chats/chat_completions.rs +++ b/crates/meilisearch/src/routes/chats/chat_completions.rs @@ -7,7 +7,6 @@ use std::time::Duration; use actix_web::web::{self, Data}; use actix_web::{Either, HttpRequest, HttpResponse, Responder}; use actix_web_lab::sse::{Event, Sse}; -use async_openai::config::{Config, OpenAIConfig}; use async_openai::types::{ ChatCompletionMessageToolCall, ChatCompletionMessageToolCallChunk, ChatCompletionRequestAssistantMessageArgs, ChatCompletionRequestMessage, @@ -35,6 +34,7 @@ use serde_json::json; use tokio::runtime::Handle; use tokio::sync::mpsc::error::SendError; +use super::config::Config; use super::errors::StreamErrorEvent; use super::utils::format_documents; use super::{ @@ -312,15 +312,8 @@ async fn non_streamed_chat( } }; - let mut config = OpenAIConfig::default(); - if let Some(api_key) = chat_settings.api_key.as_ref() { - config = config.with_api_key(api_key); - } - if let Some(base_api) = chat_settings.base_api.as_ref() { - config = config.with_api_base(base_api); - } + let config = Config::new(&chat_settings); let client = Client::with_config(config); - let auth_token = extract_token_from_request(&req)?.unwrap(); // TODO do function support later let _function_support = @@ -413,14 +406,7 @@ async fn streamed_chat( }; drop(rtxn); - let mut config = OpenAIConfig::default(); - if let Some(api_key) = chat_settings.api_key.as_ref() { - config = config.with_api_key(api_key); - } - if let Some(base_api) = chat_settings.base_api.as_ref() { - config = config.with_api_base(base_api); - } - + let config = Config::new(&chat_settings); let auth_token = extract_token_from_request(&req)?.unwrap().to_string(); let function_support = setup_search_tool(&index_scheduler, filters, &mut chat_completion, &chat_settings.prompts)?; @@ -465,7 +451,7 @@ async fn streamed_chat( /// Updates the chat completion with the new messages, streams the LLM tokens, /// and report progress and errors. #[allow(clippy::too_many_arguments)] -async fn run_conversation( +async fn run_conversation( index_scheduler: &GuardedData< ActionPolicy<{ actions::CHAT_COMPLETIONS }>, Data, diff --git a/crates/meilisearch/src/routes/chats/config.rs b/crates/meilisearch/src/routes/chats/config.rs new file mode 100644 index 000000000..9babbd8c9 --- /dev/null +++ b/crates/meilisearch/src/routes/chats/config.rs @@ -0,0 +1,87 @@ +use async_openai::config::{AzureConfig, OpenAIConfig}; +use meilisearch_types::features::ChatCompletionSettings as DbChatSettings; +use reqwest::header::HeaderMap; +use secrecy::SecretString; + +#[derive(Debug, Clone)] +pub enum Config { + OpenAiCompatible(OpenAIConfig), + AzureOpenAiCompatible(AzureConfig), +} + +impl Config { + pub fn new(chat_settings: &DbChatSettings) -> Self { + use meilisearch_types::features::ChatCompletionSource::*; + match chat_settings.source { + OpenAi | Mistral | Gemini | VLlm => { + let mut config = OpenAIConfig::default(); + if let Some(org_id) = chat_settings.org_id.as_ref() { + config = config.with_org_id(org_id); + } + if let Some(project_id) = chat_settings.project_id.as_ref() { + config = config.with_project_id(project_id); + } + if let Some(api_key) = chat_settings.api_key.as_ref() { + config = config.with_api_key(api_key); + } + if let Some(base_api) = chat_settings.base_api.as_ref() { + config = config.with_api_base(base_api); + } + Self::OpenAiCompatible(config) + } + AzureOpenAi => { + let mut config = AzureConfig::default(); + if let Some(version) = chat_settings.api_version.as_ref() { + config = config.with_api_version(version); + } + if let Some(deployment_id) = chat_settings.deployment_id.as_ref() { + config = config.with_deployment_id(deployment_id); + } + if let Some(api_key) = chat_settings.api_key.as_ref() { + config = config.with_api_key(api_key); + } + if let Some(base_api) = chat_settings.base_api.as_ref() { + config = config.with_api_base(base_api); + } + Self::AzureOpenAiCompatible(config) + } + } + } +} + +impl async_openai::config::Config for Config { + fn headers(&self) -> HeaderMap { + match self { + Config::OpenAiCompatible(config) => config.headers(), + Config::AzureOpenAiCompatible(config) => config.headers(), + } + } + + fn url(&self, path: &str) -> String { + match self { + Config::OpenAiCompatible(config) => config.url(path), + Config::AzureOpenAiCompatible(config) => config.url(path), + } + } + + fn query(&self) -> Vec<(&str, &str)> { + match self { + Config::OpenAiCompatible(config) => config.query(), + Config::AzureOpenAiCompatible(config) => config.query(), + } + } + + fn api_base(&self) -> &str { + match self { + Config::OpenAiCompatible(config) => config.api_base(), + Config::AzureOpenAiCompatible(config) => config.api_base(), + } + } + + fn api_key(&self) -> &SecretString { + match self { + Config::OpenAiCompatible(config) => config.api_key(), + Config::AzureOpenAiCompatible(config) => config.api_key(), + } + } +} diff --git a/crates/meilisearch/src/routes/chats/mod.rs b/crates/meilisearch/src/routes/chats/mod.rs index 35afd69c0..ddaf4d80d 100644 --- a/crates/meilisearch/src/routes/chats/mod.rs +++ b/crates/meilisearch/src/routes/chats/mod.rs @@ -18,6 +18,7 @@ use crate::extractors::authentication::GuardedData; use crate::routes::PAGINATION_DEFAULT_LIMIT; pub mod chat_completions; +mod config; mod errors; pub mod settings; mod utils; diff --git a/crates/meilisearch/src/routes/chats/settings.rs b/crates/meilisearch/src/routes/chats/settings.rs index 0bb25f30d..c7b89d5bb 100644 --- a/crates/meilisearch/src/routes/chats/settings.rs +++ b/crates/meilisearch/src/routes/chats/settings.rs @@ -109,6 +109,26 @@ async fn patch_settings( Setting::Reset => DbChatCompletionSource::default(), Setting::NotSet => old_settings.source, }, + org_id: match new.org_id { + Setting::Set(new_org_id) => Some(new_org_id), + Setting::Reset => None, + Setting::NotSet => old_settings.org_id, + }, + project_id: match new.project_id { + Setting::Set(new_project_id) => Some(new_project_id), + Setting::Reset => None, + Setting::NotSet => old_settings.project_id, + }, + api_version: match new.api_version { + Setting::Set(new_api_version) => Some(new_api_version), + Setting::Reset => None, + Setting::NotSet => old_settings.api_version, + }, + deployment_id: match new.deployment_id { + Setting::Set(new_deployment_id) => Some(new_deployment_id), + Setting::Reset => None, + Setting::NotSet => old_settings.deployment_id, + }, base_api: match new.base_api { Setting::Set(new_base_api) => Some(new_base_api), Setting::Reset => None, @@ -171,6 +191,22 @@ pub struct GlobalChatSettings { #[schema(value_type = Option)] pub source: Setting, #[serde(default)] + #[deserr(default, error = DeserrJsonError)] + #[schema(value_type = Option, example = json!("dcba4321..."))] + pub org_id: Setting, + #[serde(default)] + #[deserr(default, error = DeserrJsonError)] + #[schema(value_type = Option, example = json!("4321dcba..."))] + pub project_id: Setting, + #[serde(default)] + #[deserr(default, error = DeserrJsonError)] + #[schema(value_type = Option, example = json!("2024-02-01"))] + pub api_version: Setting, + #[serde(default)] + #[deserr(default, error = DeserrJsonError)] + #[schema(value_type = Option, example = json!("1234abcd..."))] + pub deployment_id: Setting, + #[serde(default)] #[deserr(default, error = DeserrJsonError)] #[schema(value_type = Option, example = json!("https://api.mistral.ai/v1"))] pub base_api: Setting, From 717a026fddb94dcd0c48bf853fc525bb95030bcb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Renault?= Date: Fri, 6 Jun 2025 12:32:40 +0200 Subject: [PATCH 068/103] Make sure to use the system prompt --- crates/meilisearch-types/src/features.rs | 24 ++++++---- .../src/routes/chats/chat_completions.rs | 47 ++++++++++++++----- 2 files changed, 51 insertions(+), 20 deletions(-) diff --git a/crates/meilisearch-types/src/features.rs b/crates/meilisearch-types/src/features.rs index 9bcd58347..210a0f0f9 100644 --- a/crates/meilisearch-types/src/features.rs +++ b/crates/meilisearch-types/src/features.rs @@ -102,16 +102,24 @@ pub enum ChatCompletionSource { VLlm, } +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum SystemRole { + System, + Developer, +} + impl ChatCompletionSource { - pub fn system_role(&self, model: &str) -> &'static str { + pub fn system_role(&self, model: &str) -> SystemRole { + use ChatCompletionSource::*; + use SystemRole::*; match self { - ChatCompletionSource::OpenAi if Self::old_openai_model(model) => "system", - ChatCompletionSource::OpenAi => "developer", - ChatCompletionSource::AzureOpenAi if Self::old_openai_model(model) => "system", - ChatCompletionSource::AzureOpenAi => "developer", - ChatCompletionSource::Mistral => "system", - ChatCompletionSource::Gemini => "system", - ChatCompletionSource::VLlm => "system", + OpenAi if Self::old_openai_model(model) => System, + OpenAi => Developer, + AzureOpenAi if Self::old_openai_model(model) => System, + AzureOpenAi => Developer, + Mistral => System, + Gemini => System, + VLlm => System, } } diff --git a/crates/meilisearch/src/routes/chats/chat_completions.rs b/crates/meilisearch/src/routes/chats/chat_completions.rs index f588541fa..f4e42cae3 100644 --- a/crates/meilisearch/src/routes/chats/chat_completions.rs +++ b/crates/meilisearch/src/routes/chats/chat_completions.rs @@ -9,7 +9,8 @@ use actix_web::{Either, HttpRequest, HttpResponse, Responder}; use actix_web_lab::sse::{Event, Sse}; use async_openai::types::{ ChatCompletionMessageToolCall, ChatCompletionMessageToolCallChunk, - ChatCompletionRequestAssistantMessageArgs, ChatCompletionRequestMessage, + ChatCompletionRequestAssistantMessageArgs, ChatCompletionRequestDeveloperMessage, + ChatCompletionRequestDeveloperMessageContent, ChatCompletionRequestMessage, ChatCompletionRequestSystemMessage, ChatCompletionRequestSystemMessageContent, ChatCompletionRequestToolMessage, ChatCompletionRequestToolMessageContent, ChatCompletionStreamResponseDelta, ChatCompletionToolArgs, ChatCompletionToolType, @@ -24,6 +25,7 @@ use meilisearch_auth::AuthController; use meilisearch_types::error::{Code, ResponseError}; use meilisearch_types::features::{ ChatCompletionPrompts as DbChatCompletionPrompts, ChatCompletionSettings as DbChatSettings, + SystemRole, }; use meilisearch_types::keys::actions; use meilisearch_types::milli::index::ChatConfig; @@ -117,6 +119,7 @@ fn setup_search_tool( filters: &meilisearch_auth::AuthFilter, chat_completion: &mut CreateChatCompletionRequest, prompts: &DbChatCompletionPrompts, + system_role: SystemRole, ) -> Result { let tools = chat_completion.tools.get_or_insert_default(); if tools.iter().any(|t| t.function.name == MEILI_SEARCH_IN_INDEX_FUNCTION_NAME) { @@ -195,13 +198,21 @@ fn setup_search_tool( tools.push(tool); - chat_completion.messages.insert( - 0, - ChatCompletionRequestMessage::System(ChatCompletionRequestSystemMessage { - content: ChatCompletionRequestSystemMessageContent::Text(prompts.system.clone()), - name: None, - }), - ); + let system_message = match system_role { + SystemRole::System => { + ChatCompletionRequestMessage::System(ChatCompletionRequestSystemMessage { + content: ChatCompletionRequestSystemMessageContent::Text(prompts.system.clone()), + name: None, + }) + } + SystemRole::Developer => { + ChatCompletionRequestMessage::Developer(ChatCompletionRequestDeveloperMessage { + content: ChatCompletionRequestDeveloperMessageContent::Text(prompts.system.clone()), + name: None, + }) + } + }; + chat_completion.messages.insert(0, system_message); Ok(FunctionSupport { report_progress, report_sources, append_to_conversation }) } @@ -315,9 +326,15 @@ async fn non_streamed_chat( let config = Config::new(&chat_settings); let client = Client::with_config(config); let auth_token = extract_token_from_request(&req)?.unwrap(); + let system_role = chat_settings.source.system_role(&chat_completion.model); // TODO do function support later - let _function_support = - setup_search_tool(&index_scheduler, filters, &mut chat_completion, &chat_settings.prompts)?; + let _function_support = setup_search_tool( + &index_scheduler, + filters, + &mut chat_completion, + &chat_settings.prompts, + system_role, + )?; let mut response; loop { @@ -408,8 +425,14 @@ async fn streamed_chat( let config = Config::new(&chat_settings); let auth_token = extract_token_from_request(&req)?.unwrap().to_string(); - let function_support = - setup_search_tool(&index_scheduler, filters, &mut chat_completion, &chat_settings.prompts)?; + let system_role = chat_settings.source.system_role(&chat_completion.model); + let function_support = setup_search_tool( + &index_scheduler, + filters, + &mut chat_completion, + &chat_settings.prompts, + system_role, + )?; tracing::debug!("Conversation function support: {function_support:?}"); From 1fda05c2fd625965f8097baece5c2713120e86de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Renault?= Date: Mon, 9 Jun 2025 15:26:13 +0200 Subject: [PATCH 069/103] Delete chat.rs --- crates/meilisearch/src/routes/chat.rs | 291 -------------------------- 1 file changed, 291 deletions(-) delete mode 100644 crates/meilisearch/src/routes/chat.rs diff --git a/crates/meilisearch/src/routes/chat.rs b/crates/meilisearch/src/routes/chat.rs deleted file mode 100644 index ad46d91c8..000000000 --- a/crates/meilisearch/src/routes/chat.rs +++ /dev/null @@ -1,291 +0,0 @@ -use std::mem; - -use actix_web::web::{self, Data}; -use actix_web::{Either, HttpResponse, Responder}; -use actix_web_lab::sse::{self, Event}; -use async_openai::config::OpenAIConfig; -use async_openai::types::{ - ChatCompletionRequestAssistantMessageArgs, ChatCompletionRequestMessage, - ChatCompletionRequestToolMessage, ChatCompletionRequestToolMessageContent, - ChatCompletionToolArgs, ChatCompletionToolType, CreateChatCompletionRequest, FinishReason, - FunctionObjectArgs, -}; -use async_openai::Client; -use futures::StreamExt; -use index_scheduler::IndexScheduler; -use meilisearch_types::error::ResponseError; -use meilisearch_types::keys::actions; -use meilisearch_types::milli::index::IndexEmbeddingConfig; -use meilisearch_types::milli::prompt::PromptData; -use meilisearch_types::milli::vector::EmbeddingConfig; -use meilisearch_types::{Document, Index}; -use serde::{Deserialize, Serialize}; -use serde_json::json; - -use crate::extractors::authentication::policies::ActionPolicy; -use crate::extractors::authentication::GuardedData; -use crate::metrics::MEILISEARCH_DEGRADED_SEARCH_REQUESTS; -use crate::routes::indexes::search::search_kind; -use crate::search::{ - add_search_rules, perform_search, HybridQuery, RetrieveVectors, SearchQuery, SemanticRatio, -}; -use crate::search_queue::SearchQueue; - -/// The default description of the searchInIndex tool provided to OpenAI. -const DEFAULT_SEARCH_IN_INDEX_TOOL_DESCRIPTION: &str = - "Search the database for relevant JSON documents using an optional query."; -/// The default description of the searchInIndex `q` parameter tool provided to OpenAI. -const DEFAULT_SEARCH_IN_INDEX_Q_PARAMETER_TOOL_DESCRIPTION: &str = - "The search query string used to find relevant documents in the index. \ -This should contain keywords or phrases that best represent what the user is looking for. \ -More specific queries will yield more precise results."; -/// The default description of the searchInIndex `index` parameter tool provided to OpenAI. -const DEFAULT_SEARCH_IN_INDEX_INDEX_PARAMETER_TOOL_DESCRIPTION: &str = -"The name of the index to search within. An index is a collection of documents organized for search. \ -Selecting the right index ensures the most relevant results for the user query"; - -const EMBEDDER_NAME: &str = "openai"; - -pub fn configure(cfg: &mut web::ServiceConfig) { - cfg.service(web::resource("").route(web::post().to(chat))); -} - -/// Get a chat completion -async fn chat( - index_scheduler: GuardedData, Data>, - search_queue: web::Data, - web::Json(mut chat_completion): web::Json, -) -> impl Responder { - // To enable later on, when the feature will be experimental - // index_scheduler.features().check_chat("Using the /chat route")?; - - if chat_completion.stream.unwrap_or(false) { - Either::Right(streamed_chat(index_scheduler, search_queue, chat_completion).await) - } else { - Either::Left(non_streamed_chat(index_scheduler, search_queue, chat_completion).await) - } -} - -async fn non_streamed_chat( - index_scheduler: GuardedData, Data>, - search_queue: web::Data, - mut chat_completion: CreateChatCompletionRequest, -) -> Result { - let api_key = std::env::var("MEILI_OPENAI_API_KEY") - .expect("cannot find OpenAI API Key (MEILI_OPENAI_API_KEY)"); - let config = OpenAIConfig::default().with_api_key(&api_key); // we can also change the API base - let client = Client::with_config(config); - - assert_eq!( - chat_completion.n.unwrap_or(1), - 1, - "Meilisearch /chat only support one completion at a time (n = 1, n = null)" - ); - - let rtxn = index_scheduler.read_txn().unwrap(); - let search_in_index_description = index_scheduler - .chat_prompts(&rtxn, "searchInIndex-description") - .unwrap() - .unwrap_or(DEFAULT_SEARCH_IN_INDEX_TOOL_DESCRIPTION) - .to_string(); - let search_in_index_q_param_description = index_scheduler - .chat_prompts(&rtxn, "searchInIndex-q-param-description") - .unwrap() - .unwrap_or(DEFAULT_SEARCH_IN_INDEX_Q_PARAMETER_TOOL_DESCRIPTION) - .to_string(); - let search_in_index_index_description = index_scheduler - .chat_prompts(&rtxn, "searchInIndex-index-param-description") - .unwrap() - .unwrap_or(DEFAULT_SEARCH_IN_INDEX_INDEX_PARAMETER_TOOL_DESCRIPTION) - .to_string(); - drop(rtxn); - - let mut response; - loop { - let tools = chat_completion.tools.get_or_insert_default(); - tools.push( - ChatCompletionToolArgs::default() - .r#type(ChatCompletionToolType::Function) - .function( - FunctionObjectArgs::default() - .name("searchInIndex") - .description(&search_in_index_description) - .parameters(json!({ - "type": "object", - "properties": { - "index_uid": { - "type": "string", - "enum": ["main"], - "description": search_in_index_index_description, - }, - "q": { - "type": ["string", "null"], - "description": search_in_index_q_param_description, - } - }, - "required": ["index_uid", "q"], - "additionalProperties": false, - })) - .strict(true) - .build() - .unwrap(), - ) - .build() - .unwrap(), - ); - response = client.chat().create(chat_completion.clone()).await.unwrap(); - - let choice = &mut response.choices[0]; - match choice.finish_reason { - Some(FinishReason::ToolCalls) => { - let tool_calls = mem::take(&mut choice.message.tool_calls).unwrap_or_default(); - - let (meili_calls, other_calls): (Vec<_>, Vec<_>) = - tool_calls.into_iter().partition(|call| call.function.name == "searchInIndex"); - - chat_completion.messages.push( - ChatCompletionRequestAssistantMessageArgs::default() - .tool_calls(meili_calls.clone()) - .build() - .unwrap() - .into(), - ); - - for call in meili_calls { - let SearchInIndexParameters { index_uid, q } = - serde_json::from_str(&call.function.arguments).unwrap(); - - let mut query = SearchQuery { - q, - hybrid: Some(HybridQuery { - semantic_ratio: SemanticRatio::default(), - embedder: EMBEDDER_NAME.to_string(), - }), - limit: 20, - ..Default::default() - }; - - // Tenant token search_rules. - if let Some(search_rules) = - index_scheduler.filters().get_index_search_rules(&index_uid) - { - add_search_rules(&mut query.filter, search_rules); - } - - // TBD - // let mut aggregate = SearchAggregator::::from_query(&query); - - let index = index_scheduler.index(&index_uid)?; - let search_kind = search_kind( - &query, - index_scheduler.get_ref(), - index_uid.to_string(), - &index, - )?; - - let permit = search_queue.try_get_search_permit().await?; - let features = index_scheduler.features(); - let index_cloned = index.clone(); - let search_result = tokio::task::spawn_blocking(move || { - perform_search( - index_uid.to_string(), - &index_cloned, - query, - search_kind, - RetrieveVectors::new(false), - features, - ) - }) - .await; - permit.drop().await; - - let search_result = search_result?; - if let Ok(ref search_result) = search_result { - // aggregate.succeed(search_result); - if search_result.degraded { - MEILISEARCH_DEGRADED_SEARCH_REQUESTS.inc(); - } - } - // analytics.publish(aggregate, &req); - - let search_result = search_result?; - let formatted = format_documents( - &index, - search_result.hits.into_iter().map(|doc| doc.document), - ); - let text = formatted.join("\n"); - chat_completion.messages.push(ChatCompletionRequestMessage::Tool( - ChatCompletionRequestToolMessage { - tool_call_id: call.id, - content: ChatCompletionRequestToolMessageContent::Text(text), - }, - )); - } - - // Let the client call other tools by themselves - if !other_calls.is_empty() { - response.choices[0].message.tool_calls = Some(other_calls); - break; - } - } - _ => break, - } - } - - Ok(HttpResponse::Ok().json(response)) -} - -async fn streamed_chat( - index_scheduler: GuardedData, Data>, - search_queue: web::Data, - mut chat_completion: CreateChatCompletionRequest, -) -> impl Responder { - assert!(chat_completion.stream.unwrap_or(false)); - - let api_key = std::env::var("MEILI_OPENAI_API_KEY") - .expect("cannot find OpenAI API Key (MEILI_OPENAI_API_KEY)"); - let config = OpenAIConfig::default().with_api_key(&api_key); // we can also change the API base - let client = Client::with_config(config); - let response = client.chat().create_stream(chat_completion).await.unwrap(); - actix_web_lab::sse::Sse::from_stream(response.map(|response| { - response - .map(|mut r| Event::Data(sse::Data::new_json(r.choices.pop().unwrap().delta).unwrap())) - })) -} - -#[derive(Deserialize)] -struct SearchInIndexParameters { - /// The index uid to search in. - index_uid: String, - /// The query parameter to use. - q: Option, -} - -fn format_documents(index: &Index, documents: impl Iterator) -> Vec { - let rtxn = index.read_txn().unwrap(); - let IndexEmbeddingConfig { name: _, config, user_provided: _ } = index - .embedding_configs(&rtxn) - .unwrap() - .into_iter() - .find(|conf| conf.name == EMBEDDER_NAME) - .unwrap(); - - let EmbeddingConfig { - embedder_options: _, - prompt: PromptData { template, max_bytes }, - quantized: _, - } = config; - - #[derive(Serialize)] - struct Doc { - doc: T, - } - - let template = liquid::ParserBuilder::with_stdlib().build().unwrap().parse(&template).unwrap(); - documents - .map(|doc| { - let object = liquid::to_object(&Doc { doc }).unwrap(); - template.render(&object).unwrap() - }) - .collect() -} From 48e8356a16ccd074be781febd55208ab5ff76d81 Mon Sep 17 00:00:00 2001 From: Kerollmops Date: Tue, 10 Jun 2025 09:18:36 +0200 Subject: [PATCH 070/103] Mark the non-streaming chat completions route unimplemented --- crates/meilisearch-types/src/error.rs | 1 + .../meilisearch/src/routes/chats/chat_completions.rs | 10 ++++++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/crates/meilisearch-types/src/error.rs b/crates/meilisearch-types/src/error.rs index 27f7580e2..17c28acc3 100644 --- a/crates/meilisearch-types/src/error.rs +++ b/crates/meilisearch-types/src/error.rs @@ -390,6 +390,7 @@ InvalidDocumentEditionFunctionFilter , InvalidRequest , BAD_REQU EditDocumentsByFunctionError , InvalidRequest , BAD_REQUEST ; InvalidSettingsIndexChat , InvalidRequest , BAD_REQUEST ; // Experimental features - Chat Completions +UnimplementedNonStreamingChatCompletions , InvalidRequest , NOT_IMPLEMENTED ; ChatWorkspaceNotFound , InvalidRequest , NOT_FOUND ; InvalidChatCompletionOrgId , InvalidRequest , BAD_REQUEST ; InvalidChatCompletionProjectId , InvalidRequest , BAD_REQUEST ; diff --git a/crates/meilisearch/src/routes/chats/chat_completions.rs b/crates/meilisearch/src/routes/chats/chat_completions.rs index f4e42cae3..e89129f18 100644 --- a/crates/meilisearch/src/routes/chats/chat_completions.rs +++ b/crates/meilisearch/src/routes/chats/chat_completions.rs @@ -301,17 +301,23 @@ async fn process_search_request( Ok((index, documents, text)) } +#[allow(unreachable_code, unused_variables)] // will be correctly implemented in the future async fn non_streamed_chat( index_scheduler: GuardedData, Data>, auth_ctrl: web::Data, search_queue: web::Data, workspace_uid: &str, req: HttpRequest, - mut chat_completion: CreateChatCompletionRequest, + chat_completion: CreateChatCompletionRequest, ) -> Result { index_scheduler.features().check_chat_completions("using the /chats chat completions route")?; - let filters = index_scheduler.filters(); + return Err(ResponseError::from_msg( + format!("Non-streamed chat completions is not implemented"), + Code::UnimplementedNonStreamingChatCompletions, + )); + + let filters = index_scheduler.filters(); let rtxn = index_scheduler.read_txn()?; let chat_settings = match index_scheduler.chat_settings(&rtxn, workspace_uid).unwrap() { Some(settings) => settings, From a7f5d3bb7a711df25d29630c6623a6f12a4bfba6 Mon Sep 17 00:00:00 2001 From: Kerollmops Date: Tue, 10 Jun 2025 09:21:45 +0200 Subject: [PATCH 071/103] Redact the API Key when patching chat workspace settings --- crates/meilisearch/src/routes/chats/settings.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/crates/meilisearch/src/routes/chats/settings.rs b/crates/meilisearch/src/routes/chats/settings.rs index c7b89d5bb..07ed4dea6 100644 --- a/crates/meilisearch/src/routes/chats/settings.rs +++ b/crates/meilisearch/src/routes/chats/settings.rs @@ -103,7 +103,7 @@ async fn patch_settings( Setting::NotSet => old_settings.prompts, }; - let settings = ChatCompletionSettings { + let mut settings = ChatCompletionSettings { source: match new.source { Setting::Set(new_source) => new_source.into(), Setting::Reset => DbChatCompletionSource::default(), @@ -154,6 +154,8 @@ async fn patch_settings( index_scheduler.put_chat_settings(&mut wtxn, &workspace_uid, &settings)?; wtxn.commit()?; + settings.hide_secrets(); + Ok(HttpResponse::Ok().json(settings)) } From 29d82ade5646f7fec5f19b7618ea2beaec39d273 Mon Sep 17 00:00:00 2001 From: Kerollmops Date: Tue, 10 Jun 2025 09:24:07 +0200 Subject: [PATCH 072/103] Rename base_api into base_rul --- crates/meilisearch-types/src/features.rs | 2 +- crates/meilisearch/src/routes/chats/config.rs | 8 ++++---- crates/meilisearch/src/routes/chats/settings.rs | 8 ++++---- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/crates/meilisearch-types/src/features.rs b/crates/meilisearch-types/src/features.rs index 210a0f0f9..5cc066afd 100644 --- a/crates/meilisearch-types/src/features.rs +++ b/crates/meilisearch-types/src/features.rs @@ -59,7 +59,7 @@ pub struct ChatCompletionSettings { #[serde(default)] pub deployment_id: Option, #[serde(default)] - pub base_api: Option, + pub base_url: Option, #[serde(default)] pub api_key: Option, #[serde(default)] diff --git a/crates/meilisearch/src/routes/chats/config.rs b/crates/meilisearch/src/routes/chats/config.rs index 9babbd8c9..182eff735 100644 --- a/crates/meilisearch/src/routes/chats/config.rs +++ b/crates/meilisearch/src/routes/chats/config.rs @@ -24,8 +24,8 @@ impl Config { if let Some(api_key) = chat_settings.api_key.as_ref() { config = config.with_api_key(api_key); } - if let Some(base_api) = chat_settings.base_api.as_ref() { - config = config.with_api_base(base_api); + if let Some(base_url) = chat_settings.base_url.as_ref() { + config = config.with_api_base(base_url); } Self::OpenAiCompatible(config) } @@ -40,8 +40,8 @@ impl Config { if let Some(api_key) = chat_settings.api_key.as_ref() { config = config.with_api_key(api_key); } - if let Some(base_api) = chat_settings.base_api.as_ref() { - config = config.with_api_base(base_api); + if let Some(base_url) = chat_settings.base_url.as_ref() { + config = config.with_api_base(base_url); } Self::AzureOpenAiCompatible(config) } diff --git a/crates/meilisearch/src/routes/chats/settings.rs b/crates/meilisearch/src/routes/chats/settings.rs index 07ed4dea6..dae2826fe 100644 --- a/crates/meilisearch/src/routes/chats/settings.rs +++ b/crates/meilisearch/src/routes/chats/settings.rs @@ -129,10 +129,10 @@ async fn patch_settings( Setting::Reset => None, Setting::NotSet => old_settings.deployment_id, }, - base_api: match new.base_api { - Setting::Set(new_base_api) => Some(new_base_api), + base_url: match new.base_url { + Setting::Set(new_base_url) => Some(new_base_url), Setting::Reset => None, - Setting::NotSet => old_settings.base_api, + Setting::NotSet => old_settings.base_url, }, api_key: match new.api_key { Setting::Set(new_api_key) => Some(new_api_key), @@ -211,7 +211,7 @@ pub struct GlobalChatSettings { #[serde(default)] #[deserr(default, error = DeserrJsonError)] #[schema(value_type = Option, example = json!("https://api.mistral.ai/v1"))] - pub base_api: Setting, + pub base_url: Setting, #[serde(default)] #[deserr(default, error = DeserrJsonError)] #[schema(value_type = Option, example = json!("abcd1234..."))] From bc56087a1725ec1eee3fd4fe089961d2edb7838f Mon Sep 17 00:00:00 2001 From: Kerollmops Date: Tue, 10 Jun 2025 10:08:01 +0200 Subject: [PATCH 073/103] Fix the chatCompletions key --- crates/meilisearch-types/src/keys.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/crates/meilisearch-types/src/keys.rs b/crates/meilisearch-types/src/keys.rs index 5634d585f..fe6005796 100644 --- a/crates/meilisearch-types/src/keys.rs +++ b/crates/meilisearch-types/src/keys.rs @@ -323,9 +323,8 @@ pub enum Action { #[serde(rename = "network.update")] #[deserr(rename = "network.update")] NetworkUpdate, - // TODO should we rename it chatCompletions.get ? - #[serde(rename = "chatCompletion")] - #[deserr(rename = "chatCompletion")] + #[serde(rename = "chatCompletions")] + #[deserr(rename = "chatCompletions")] ChatCompletions, #[serde(rename = "chats.get")] #[deserr(rename = "chats.get")] From e654eddf56f2890c5caea5d5be3465ffd9e203ab Mon Sep 17 00:00:00 2001 From: Kerollmops Date: Tue, 10 Jun 2025 10:21:34 +0200 Subject: [PATCH 074/103] Improve the chat workspace REST endpoints --- crates/index-scheduler/src/lib.rs | 8 +++- crates/meilisearch-auth/src/store.rs | 9 ++-- crates/meilisearch-types/src/keys.rs | 15 ++++--- crates/meilisearch/src/routes/chats/mod.rs | 45 ++++++++++++++++++- .../meilisearch/src/routes/chats/settings.rs | 14 +++--- 5 files changed, 72 insertions(+), 19 deletions(-) diff --git a/crates/index-scheduler/src/lib.rs b/crates/index-scheduler/src/lib.rs index 3860ef5cb..3ad342bea 100644 --- a/crates/index-scheduler/src/lib.rs +++ b/crates/index-scheduler/src/lib.rs @@ -55,7 +55,7 @@ use meilisearch_types::features::{ ChatCompletionSettings, InstanceTogglableFeatures, Network, RuntimeTogglableFeatures, }; use meilisearch_types::heed::byteorder::BE; -use meilisearch_types::heed::types::{SerdeJson, Str, I128}; +use meilisearch_types::heed::types::{DecodeIgnore, SerdeJson, Str, I128}; use meilisearch_types::heed::{self, Database, Env, RoTxn, RwTxn, WithoutTls}; use meilisearch_types::milli::index::IndexEmbeddingConfig; use meilisearch_types::milli::update::IndexerConfig; @@ -904,6 +904,12 @@ impl IndexScheduler { self.chat_settings.get(rtxn, uid).map_err(Into::into) } + /// Return true if chat workspace exists. + pub fn chat_workspace_exists(&self, name: &str) -> Result { + let rtxn = self.env.read_txn()?; + Ok(self.chat_settings.remap_data_type::().get(&rtxn, name)?.is_some()) + } + pub fn put_chat_settings( &self, wtxn: &mut RwTxn, diff --git a/crates/meilisearch-auth/src/store.rs b/crates/meilisearch-auth/src/store.rs index e20f259ee..bae27afe4 100644 --- a/crates/meilisearch-auth/src/store.rs +++ b/crates/meilisearch-auth/src/store.rs @@ -125,12 +125,11 @@ impl HeedAuthStore { Action::MetricsAll => { actions.insert(Action::MetricsGet); } + Action::ChatsAll => { + actions.extend([Action::ChatsGet, Action::ChatsDelete]); + } Action::ChatsSettingsAll => { - actions.extend([ - Action::ChatsSettingsGet, - Action::ChatsSettingsUpdate, - Action::ChatsSettingsDelete, - ]); + actions.extend([Action::ChatsSettingsGet, Action::ChatsSettingsUpdate]); } other => { actions.insert(*other); diff --git a/crates/meilisearch-types/src/keys.rs b/crates/meilisearch-types/src/keys.rs index fe6005796..df2810727 100644 --- a/crates/meilisearch-types/src/keys.rs +++ b/crates/meilisearch-types/src/keys.rs @@ -326,9 +326,15 @@ pub enum Action { #[serde(rename = "chatCompletions")] #[deserr(rename = "chatCompletions")] ChatCompletions, + #[serde(rename = "chats.*")] + #[deserr(rename = "chats.*")] + ChatsAll, #[serde(rename = "chats.get")] #[deserr(rename = "chats.get")] ChatsGet, + #[serde(rename = "chats.delete")] + #[deserr(rename = "chats.delete")] + ChatsDelete, #[serde(rename = "chatsSettings.*")] #[deserr(rename = "chatsSettings.*")] ChatsSettingsAll, @@ -338,9 +344,6 @@ pub enum Action { #[serde(rename = "chatsSettings.update")] #[deserr(rename = "chatsSettings.update")] ChatsSettingsUpdate, - #[serde(rename = "chatsSettings.delete")] - #[deserr(rename = "chatsSettings.delete")] - ChatsSettingsDelete, } impl Action { @@ -367,11 +370,12 @@ impl Action { SETTINGS_GET => Some(Self::SettingsGet), SETTINGS_UPDATE => Some(Self::SettingsUpdate), CHAT_COMPLETIONS => Some(Self::ChatCompletions), + CHATS_ALL => Some(Self::ChatsAll), CHATS_GET => Some(Self::ChatsGet), + CHATS_DELETE => Some(Self::ChatsDelete), CHATS_SETTINGS_ALL => Some(Self::ChatsSettingsAll), CHATS_SETTINGS_GET => Some(Self::ChatsSettingsGet), CHATS_SETTINGS_UPDATE => Some(Self::ChatsSettingsUpdate), - CHATS_SETTINGS_DELETE => Some(Self::ChatsSettingsDelete), STATS_ALL => Some(Self::StatsAll), STATS_GET => Some(Self::StatsGet), METRICS_ALL => Some(Self::MetricsAll), @@ -438,9 +442,10 @@ pub mod actions { pub const NETWORK_UPDATE: u8 = NetworkUpdate.repr(); pub const CHAT_COMPLETIONS: u8 = ChatCompletions.repr(); + pub const CHATS_ALL: u8 = ChatsAll.repr(); pub const CHATS_GET: u8 = ChatsGet.repr(); + pub const CHATS_DELETE: u8 = ChatsDelete.repr(); pub const CHATS_SETTINGS_ALL: u8 = ChatsSettingsAll.repr(); pub const CHATS_SETTINGS_GET: u8 = ChatsSettingsGet.repr(); pub const CHATS_SETTINGS_UPDATE: u8 = ChatsSettingsUpdate.repr(); - pub const CHATS_SETTINGS_DELETE: u8 = ChatsSettingsDelete.repr(); } diff --git a/crates/meilisearch/src/routes/chats/mod.rs b/crates/meilisearch/src/routes/chats/mod.rs index ddaf4d80d..8c8c9bc32 100644 --- a/crates/meilisearch/src/routes/chats/mod.rs +++ b/crates/meilisearch/src/routes/chats/mod.rs @@ -6,9 +6,11 @@ use index_scheduler::IndexScheduler; use meilisearch_types::deserr::query_params::Param; use meilisearch_types::deserr::DeserrQueryParamError; use meilisearch_types::error::deserr_codes::{InvalidIndexLimit, InvalidIndexOffset}; -use meilisearch_types::error::ResponseError; +use meilisearch_types::error::{Code, ResponseError}; +use meilisearch_types::index_uid::IndexUid; use meilisearch_types::keys::actions; use serde::{Deserialize, Serialize}; +use serde_json::json; use tracing::debug; use utoipa::{IntoParams, ToSchema}; @@ -40,11 +42,52 @@ pub struct ChatsParam { pub fn configure(cfg: &mut web::ServiceConfig) { cfg.service(web::resource("").route(web::get().to(list_workspaces))).service( web::scope("/{workspace_uid}") + .service( + web::resource("") + .route(web::get().to(get_chat)) + .route(web::delete().to(delete_chat)), + ) .service(web::scope("/chat/completions").configure(chat_completions::configure)) .service(web::scope("/settings").configure(settings::configure)), ); } +pub async fn get_chat( + index_scheduler: GuardedData, Data>, + workspace_uid: web::Path, +) -> Result { + index_scheduler.features().check_chat_completions("displaying a chat")?; + + let workspace_uid = IndexUid::try_from(workspace_uid.into_inner())?; + if index_scheduler.chat_workspace_exists(&workspace_uid)? { + Ok(HttpResponse::Ok().json(json!({ "uid": workspace_uid }))) + } else { + Err(ResponseError::from_msg( + format!("chat {workspace_uid} not found"), + Code::ChatWorkspaceNotFound, + )) + } +} + +pub async fn delete_chat( + index_scheduler: GuardedData, Data>, + workspace_uid: web::Path, +) -> Result { + index_scheduler.features().check_chat_completions("deleting a chat")?; + + let mut wtxn = index_scheduler.write_txn()?; + let workspace_uid = workspace_uid.into_inner(); + if index_scheduler.delete_chat_settings(&mut wtxn, &workspace_uid)? { + wtxn.commit()?; + Ok(HttpResponse::NoContent().finish()) + } else { + Err(ResponseError::from_msg( + format!("chat {workspace_uid} not found"), + Code::ChatWorkspaceNotFound, + )) + } +} + #[derive(Deserr, Debug, Clone, Copy, IntoParams)] #[deserr(error = DeserrQueryParamError, rename_all = camelCase, deny_unknown_fields)] #[into_params(rename_all = "camelCase", parameter_in = Query)] diff --git a/crates/meilisearch/src/routes/chats/settings.rs b/crates/meilisearch/src/routes/chats/settings.rs index dae2826fe..329732e75 100644 --- a/crates/meilisearch/src/routes/chats/settings.rs +++ b/crates/meilisearch/src/routes/chats/settings.rs @@ -26,7 +26,7 @@ pub fn configure(cfg: &mut web::ServiceConfig) { web::resource("") .route(web::get().to(SeqHandler(get_settings))) .route(web::patch().to(SeqHandler(patch_settings))) - .route(web::delete().to(SeqHandler(delete_settings))), + .route(web::delete().to(SeqHandler(reset_settings))), ); } @@ -159,9 +159,9 @@ async fn patch_settings( Ok(HttpResponse::Ok().json(settings)) } -async fn delete_settings( +async fn reset_settings( index_scheduler: GuardedData< - ActionPolicy<{ actions::CHATS_SETTINGS_DELETE }>, + ActionPolicy<{ actions::CHATS_SETTINGS_UPDATE }>, Data, >, chats_param: web::Path, @@ -169,12 +169,12 @@ async fn delete_settings( index_scheduler.features().check_chat_completions("using the /chats/settings route")?; let ChatsParam { workspace_uid } = chats_param.into_inner(); - - // TODO do a spawn_blocking here let mut wtxn = index_scheduler.write_txn()?; - if index_scheduler.delete_chat_settings(&mut wtxn, &workspace_uid)? { + if index_scheduler.chat_settings(&wtxn, &workspace_uid)?.is_some() { + let settings = Default::default(); + index_scheduler.put_chat_settings(&mut wtxn, &workspace_uid, &settings)?; wtxn.commit()?; - Ok(HttpResponse::NoContent().finish()) + Ok(HttpResponse::Ok().json(settings)) } else { Err(ResponseError::from_msg( format!("Chat `{workspace_uid}` not found"), From 85939ae8ad4a7632864131040b57360cd30ba51a Mon Sep 17 00:00:00 2001 From: Kerollmops Date: Tue, 10 Jun 2025 10:25:22 +0200 Subject: [PATCH 075/103] Add support for missing sources --- crates/meilisearch/src/routes/chats/settings.rs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/crates/meilisearch/src/routes/chats/settings.rs b/crates/meilisearch/src/routes/chats/settings.rs index 329732e75..c3cdde74a 100644 --- a/crates/meilisearch/src/routes/chats/settings.rs +++ b/crates/meilisearch/src/routes/chats/settings.rs @@ -62,7 +62,7 @@ async fn patch_settings( Data, >, chats_param: web::Path, - web::Json(new): web::Json, + web::Json(new): web::Json, ) -> Result { index_scheduler.features().check_chat_completions("using the /chats/settings route")?; let ChatsParam { workspace_uid } = chats_param.into_inner(); @@ -187,7 +187,7 @@ async fn reset_settings( #[deserr(error = DeserrJsonError, rename_all = camelCase, deny_unknown_fields)] #[serde(deny_unknown_fields, rename_all = "camelCase")] #[schema(rename_all = "camelCase")] -pub struct GlobalChatSettings { +pub struct ChatWorkspaceSettings { #[serde(default)] #[deserr(default)] #[schema(value_type = Option)] @@ -228,12 +228,21 @@ pub struct GlobalChatSettings { pub enum ChatCompletionSource { #[default] OpenAi, + Mistral, + Gemini, + AzureOpenAi, + VLlm, } impl From for DbChatCompletionSource { fn from(source: ChatCompletionSource) -> Self { + use ChatCompletionSource::*; match source { - ChatCompletionSource::OpenAi => DbChatCompletionSource::OpenAi, + OpenAi => DbChatCompletionSource::OpenAi, + Mistral => DbChatCompletionSource::Mistral, + Gemini => DbChatCompletionSource::Gemini, + AzureOpenAi => DbChatCompletionSource::AzureOpenAi, + VLlm => DbChatCompletionSource::VLlm, } } } From 6433e498829e91cd760f085b871e7e32cbb1ba12 Mon Sep 17 00:00:00 2001 From: Kerollmops Date: Tue, 10 Jun 2025 10:27:22 +0200 Subject: [PATCH 076/103] Remove useless code --- crates/milli/src/update/settings.rs | 9 --------- 1 file changed, 9 deletions(-) diff --git a/crates/milli/src/update/settings.rs b/crates/milli/src/update/settings.rs index 70a190b19..ae46cb5d9 100644 --- a/crates/milli/src/update/settings.rs +++ b/crates/milli/src/update/settings.rs @@ -124,15 +124,6 @@ impl Setting { *self = new; true } - - #[track_caller] - pub fn unwrap(self) -> T { - match self { - Setting::Set(value) => value, - Setting::Reset => panic!("Setting::Reset unwrapped"), - Setting::NotSet => panic!("Setting::NotSet unwrapped"), - } - } } impl Serialize for Setting { From 416fcf47f17259f096f3f8f561e8769b83fd971f Mon Sep 17 00:00:00 2001 From: Kerollmops Date: Tue, 10 Jun 2025 10:28:06 +0200 Subject: [PATCH 077/103] Use the same units --- crates/meilisearch-types/src/features.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/meilisearch-types/src/features.rs b/crates/meilisearch-types/src/features.rs index 5cc066afd..322ca74d1 100644 --- a/crates/meilisearch-types/src/features.rs +++ b/crates/meilisearch-types/src/features.rs @@ -128,7 +128,8 @@ impl ChatCompletionSource { /// Old OpenAI models use the system role while new ones use the developer role. fn old_openai_model(model: &str) -> bool { ["gpt-3.5", "gpt-4", "gpt-4.1", "gpt-4.5", "gpt-4o", "chatgpt-4o"].iter().any(|old| { - model.starts_with(old) && model.chars().nth(old.len()).is_none_or(|last| last == '-') + model.starts_with(old) + && model.chars().nth(old.chars().count()).is_none_or(|last| last == '-') }) } From 95d4775d4a780dc0565fa0811abd1dfd22b447cc Mon Sep 17 00:00:00 2001 From: Kerollmops Date: Tue, 10 Jun 2025 10:32:58 +0200 Subject: [PATCH 078/103] Remove the preQuery chat setting --- crates/meilisearch-types/src/features.rs | 3 --- .../src/routes/chats/chat_completions.rs | 15 +++------------ crates/meilisearch/src/routes/chats/settings.rs | 15 +++------------ 3 files changed, 6 insertions(+), 27 deletions(-) diff --git a/crates/meilisearch-types/src/features.rs b/crates/meilisearch-types/src/features.rs index 322ca74d1..bada14fe2 100644 --- a/crates/meilisearch-types/src/features.rs +++ b/crates/meilisearch-types/src/features.rs @@ -7,7 +7,6 @@ pub const DEFAULT_CHAT_SEARCH_DESCRIPTION_PROMPT: &str = "Search the database for relevant JSON documents using an optional query."; pub const DEFAULT_CHAT_SEARCH_Q_PARAM_PROMPT: &str = "The search query string used to find relevant documents in the index. This should contain keywords or phrases that best represent what the user is looking for. More specific queries will yield more precise results."; pub const DEFAULT_CHAT_SEARCH_INDEX_UID_PARAM_PROMPT: &str = "The name of the index to search within. An index is a collection of documents organized for search. Selecting the right index ensures the most relevant results for the user query. You have access to two indexes: movies, steam. The movies index contains movies with overviews. The steam index contains steam games from the Steam platform with their prices"; -pub const DEFAULT_CHAT_PRE_QUERY_PROMPT: &str = ""; #[derive(Serialize, Deserialize, Debug, Clone, Copy, Default, PartialEq, Eq)] #[serde(rename_all = "camelCase", default)] @@ -151,7 +150,6 @@ pub struct ChatCompletionPrompts { pub search_description: String, pub search_q_param: String, pub search_index_uid_param: String, - pub pre_query: String, } impl Default for ChatCompletionPrompts { @@ -161,7 +159,6 @@ impl Default for ChatCompletionPrompts { search_description: DEFAULT_CHAT_SEARCH_DESCRIPTION_PROMPT.to_string(), search_q_param: DEFAULT_CHAT_SEARCH_Q_PARAM_PROMPT.to_string(), search_index_uid_param: DEFAULT_CHAT_SEARCH_INDEX_UID_PARAM_PROMPT.to_string(), - pre_query: DEFAULT_CHAT_PRE_QUERY_PROMPT.to_string(), } } } diff --git a/crates/meilisearch/src/routes/chats/chat_completions.rs b/crates/meilisearch/src/routes/chats/chat_completions.rs index e89129f18..d467a8fe9 100644 --- a/crates/meilisearch/src/routes/chats/chat_completions.rs +++ b/crates/meilisearch/src/routes/chats/chat_completions.rs @@ -23,10 +23,7 @@ use futures::StreamExt; use index_scheduler::IndexScheduler; use meilisearch_auth::AuthController; use meilisearch_types::error::{Code, ResponseError}; -use meilisearch_types::features::{ - ChatCompletionPrompts as DbChatCompletionPrompts, ChatCompletionSettings as DbChatSettings, - SystemRole, -}; +use meilisearch_types::features::{ChatCompletionPrompts as DbChatCompletionPrompts, SystemRole}; use meilisearch_types::keys::actions; use meilisearch_types::milli::index::ChatConfig; use meilisearch_types::milli::{all_obkv_to_json, obkv_to_json, TimeBudget}; @@ -379,12 +376,11 @@ async fn non_streamed_chat( }; // TODO report documents sources later - let text = match result { + let answer = match result { Ok((_, _documents, text)) => text, Err(err) => err, }; - let answer = format!("{}\n\n{text}", chat_settings.prompts.pre_query); chat_completion.messages.push(ChatCompletionRequestMessage::Tool( ChatCompletionRequestToolMessage { tool_call_id: call.id.clone(), @@ -456,7 +452,6 @@ async fn streamed_chat( &search_queue, &auth_token, &client, - &chat_settings, &mut chat_completion, &tx, &mut global_tool_calls, @@ -489,7 +484,6 @@ async fn run_conversation( search_queue: &web::Data, auth_token: &str, client: &Client, - chat_settings: &DbChatSettings, chat_completion: &mut CreateChatCompletionRequest, tx: &SseEventSender, global_tool_calls: &mut HashMap, @@ -579,7 +573,6 @@ async fn run_conversation( auth_ctrl, search_queue, auth_token, - chat_settings, tx, meili_calls, chat_completion, @@ -617,7 +610,6 @@ async fn handle_meili_tools( auth_ctrl: &web::Data, search_queue: &web::Data, auth_token: &str, - chat_settings: &DbChatSettings, tx: &SseEventSender, meili_calls: Vec, chat_completion: &mut CreateChatCompletionRequest, @@ -659,7 +651,7 @@ async fn handle_meili_tools( Err(err) => Err(err.to_string()), }; - let text = match result { + let answer = match result { Ok((_index, documents, text)) => { if report_sources { tx.report_sources(resp.clone(), &call.id, &documents).await?; @@ -670,7 +662,6 @@ async fn handle_meili_tools( Err(err) => err, }; - let answer = format!("{}\n\n{text}", chat_settings.prompts.pre_query); let tool = ChatCompletionRequestMessage::Tool(ChatCompletionRequestToolMessage { tool_call_id: call.id.clone(), content: ChatCompletionRequestToolMessageContent::Text(answer), diff --git a/crates/meilisearch/src/routes/chats/settings.rs b/crates/meilisearch/src/routes/chats/settings.rs index c3cdde74a..4bf323dbd 100644 --- a/crates/meilisearch/src/routes/chats/settings.rs +++ b/crates/meilisearch/src/routes/chats/settings.rs @@ -7,9 +7,9 @@ use meilisearch_types::error::deserr_codes::*; use meilisearch_types::error::{Code, ResponseError}; use meilisearch_types::features::{ ChatCompletionPrompts as DbChatCompletionPrompts, ChatCompletionSettings, - ChatCompletionSource as DbChatCompletionSource, DEFAULT_CHAT_PRE_QUERY_PROMPT, - DEFAULT_CHAT_SEARCH_DESCRIPTION_PROMPT, DEFAULT_CHAT_SEARCH_INDEX_UID_PARAM_PROMPT, - DEFAULT_CHAT_SEARCH_Q_PARAM_PROMPT, DEFAULT_CHAT_SYSTEM_PROMPT, + ChatCompletionSource as DbChatCompletionSource, DEFAULT_CHAT_SEARCH_DESCRIPTION_PROMPT, + DEFAULT_CHAT_SEARCH_INDEX_UID_PARAM_PROMPT, DEFAULT_CHAT_SEARCH_Q_PARAM_PROMPT, + DEFAULT_CHAT_SYSTEM_PROMPT, }; use meilisearch_types::keys::actions; use meilisearch_types::milli::update::Setting; @@ -93,11 +93,6 @@ async fn patch_settings( Setting::Reset => DEFAULT_CHAT_SEARCH_INDEX_UID_PARAM_PROMPT.to_string(), Setting::NotSet => old_settings.prompts.search_index_uid_param, }, - pre_query: match new_prompts.pre_query { - Setting::Set(new_description) => new_description, - Setting::Reset => DEFAULT_CHAT_PRE_QUERY_PROMPT.to_string(), - Setting::NotSet => old_settings.prompts.pre_query, - }, }, Setting::Reset => DbChatCompletionPrompts::default(), Setting::NotSet => old_settings.prompts, @@ -268,8 +263,4 @@ pub struct ChatPrompts { #[deserr(default, error = DeserrJsonError)] #[schema(value_type = Option, example = json!("This is index you want to search in..."))] pub search_index_uid_param: Setting, - #[serde(default)] - #[deserr(default, error = DeserrJsonError)] - #[schema(value_type = Option)] - pub pre_query: Setting, } From 605dea4f858ccc4a707f20a638a173c865628f2e Mon Sep 17 00:00:00 2001 From: Kerollmops Date: Tue, 10 Jun 2025 10:34:30 +0200 Subject: [PATCH 079/103] Do not leak the chat "workspace" term --- crates/meilisearch-types/src/error.rs | 2 +- crates/meilisearch/src/routes/chats/chat_completions.rs | 4 ++-- crates/meilisearch/src/routes/chats/mod.rs | 4 ++-- crates/meilisearch/src/routes/chats/settings.rs | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/crates/meilisearch-types/src/error.rs b/crates/meilisearch-types/src/error.rs index 17c28acc3..bdf9bb162 100644 --- a/crates/meilisearch-types/src/error.rs +++ b/crates/meilisearch-types/src/error.rs @@ -391,7 +391,7 @@ EditDocumentsByFunctionError , InvalidRequest , BAD_REQU InvalidSettingsIndexChat , InvalidRequest , BAD_REQUEST ; // Experimental features - Chat Completions UnimplementedNonStreamingChatCompletions , InvalidRequest , NOT_IMPLEMENTED ; -ChatWorkspaceNotFound , InvalidRequest , NOT_FOUND ; +ChatNotFound , InvalidRequest , NOT_FOUND ; InvalidChatCompletionOrgId , InvalidRequest , BAD_REQUEST ; InvalidChatCompletionProjectId , InvalidRequest , BAD_REQUEST ; InvalidChatCompletionApiVersion , InvalidRequest , BAD_REQUEST ; diff --git a/crates/meilisearch/src/routes/chats/chat_completions.rs b/crates/meilisearch/src/routes/chats/chat_completions.rs index d467a8fe9..bf336f224 100644 --- a/crates/meilisearch/src/routes/chats/chat_completions.rs +++ b/crates/meilisearch/src/routes/chats/chat_completions.rs @@ -321,7 +321,7 @@ async fn non_streamed_chat( None => { return Err(ResponseError::from_msg( format!("Chat `{workspace_uid}` not found"), - Code::ChatWorkspaceNotFound, + Code::ChatNotFound, )) } }; @@ -419,7 +419,7 @@ async fn streamed_chat( None => { return Err(ResponseError::from_msg( format!("Chat `{workspace_uid}` not found"), - Code::ChatWorkspaceNotFound, + Code::ChatNotFound, )) } }; diff --git a/crates/meilisearch/src/routes/chats/mod.rs b/crates/meilisearch/src/routes/chats/mod.rs index 8c8c9bc32..6379aa83b 100644 --- a/crates/meilisearch/src/routes/chats/mod.rs +++ b/crates/meilisearch/src/routes/chats/mod.rs @@ -64,7 +64,7 @@ pub async fn get_chat( } else { Err(ResponseError::from_msg( format!("chat {workspace_uid} not found"), - Code::ChatWorkspaceNotFound, + Code::ChatNotFound, )) } } @@ -83,7 +83,7 @@ pub async fn delete_chat( } else { Err(ResponseError::from_msg( format!("chat {workspace_uid} not found"), - Code::ChatWorkspaceNotFound, + Code::ChatNotFound, )) } } diff --git a/crates/meilisearch/src/routes/chats/settings.rs b/crates/meilisearch/src/routes/chats/settings.rs index 4bf323dbd..039655841 100644 --- a/crates/meilisearch/src/routes/chats/settings.rs +++ b/crates/meilisearch/src/routes/chats/settings.rs @@ -48,7 +48,7 @@ async fn get_settings( None => { return Err(ResponseError::from_msg( format!("Chat `{workspace_uid}` not found"), - Code::ChatWorkspaceNotFound, + Code::ChatNotFound, )) } }; @@ -173,7 +173,7 @@ async fn reset_settings( } else { Err(ResponseError::from_msg( format!("Chat `{workspace_uid}` not found"), - Code::ChatWorkspaceNotFound, + Code::ChatNotFound, )) } } From 985b892b7a8a4571714c2e7598ed0e925468d381 Mon Sep 17 00:00:00 2001 From: Kerollmops Date: Tue, 10 Jun 2025 10:57:43 +0200 Subject: [PATCH 080/103] Add a basic chat setting validation --- crates/meilisearch-types/src/features.rs | 17 +++++++++++++++++ crates/meilisearch/src/routes/chats/settings.rs | 1 + 2 files changed, 18 insertions(+) diff --git a/crates/meilisearch-types/src/features.rs b/crates/meilisearch-types/src/features.rs index bada14fe2..e30ce3b55 100644 --- a/crates/meilisearch-types/src/features.rs +++ b/crates/meilisearch-types/src/features.rs @@ -2,6 +2,8 @@ use std::collections::BTreeMap; use serde::{Deserialize, Serialize}; +use crate::error::{Code, ResponseError}; + pub const DEFAULT_CHAT_SYSTEM_PROMPT: &str = "You are a highly capable research assistant with access to powerful search tools. IMPORTANT INSTRUCTIONS:1. When answering questions, you MUST make multiple tool calls (at least 2-3) to gather comprehensive information.2. Use different search queries for each tool call - vary keywords, rephrase questions, and explore different semantic angles to ensure broad coverage.3. Always explicitly announce BEFORE making each tool call by saying: \"I'll search for [specific information] now.\"4. Combine information from ALL tool calls to provide complete, nuanced answers rather than relying on a single source.5. For complex topics, break down your research into multiple targeted queries rather than using a single generic search."; pub const DEFAULT_CHAT_SEARCH_DESCRIPTION_PROMPT: &str = "Search the database for relevant JSON documents using an optional query."; @@ -88,6 +90,21 @@ impl ChatCompletionSettings { } } } + + pub fn validate(&self) -> Result<(), ResponseError> { + use ChatCompletionSource::*; + match self { + Self { source: AzureOpenAi, base_url, deployment_id, api_version, .. } if base_url.is_none() || deployment_id.is_none() || api_version.is_none() => Err(ResponseError::from_msg( + format!("azureOpenAi requires setting a valid `baseUrl`, `deploymentId`, and `apiVersion`"), + Code::BadRequest, + )), + Self { source: VLlm, base_url, .. } if base_url.is_none() => Err(ResponseError::from_msg( + format!("vLlm requires setting a valid `baseUrl`"), + Code::BadRequest, + )), + _otherwise => Ok(()), + } + } } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Default)] diff --git a/crates/meilisearch/src/routes/chats/settings.rs b/crates/meilisearch/src/routes/chats/settings.rs index 039655841..ab696f68a 100644 --- a/crates/meilisearch/src/routes/chats/settings.rs +++ b/crates/meilisearch/src/routes/chats/settings.rs @@ -146,6 +146,7 @@ async fn patch_settings( // &req, // ); + settings.validate()?; index_scheduler.put_chat_settings(&mut wtxn, &workspace_uid, &settings)?; wtxn.commit()?; From 4a0ec15ad2f72ffdc9302180875d290a07e399bb Mon Sep 17 00:00:00 2001 From: Kerollmops Date: Tue, 10 Jun 2025 11:00:14 +0200 Subject: [PATCH 081/103] Make cargo fmt happy --- crates/meilisearch/src/routes/chats/mod.rs | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/crates/meilisearch/src/routes/chats/mod.rs b/crates/meilisearch/src/routes/chats/mod.rs index 6379aa83b..7a868fd45 100644 --- a/crates/meilisearch/src/routes/chats/mod.rs +++ b/crates/meilisearch/src/routes/chats/mod.rs @@ -62,10 +62,7 @@ pub async fn get_chat( if index_scheduler.chat_workspace_exists(&workspace_uid)? { Ok(HttpResponse::Ok().json(json!({ "uid": workspace_uid }))) } else { - Err(ResponseError::from_msg( - format!("chat {workspace_uid} not found"), - Code::ChatNotFound, - )) + Err(ResponseError::from_msg(format!("chat {workspace_uid} not found"), Code::ChatNotFound)) } } @@ -81,10 +78,7 @@ pub async fn delete_chat( wtxn.commit()?; Ok(HttpResponse::NoContent().finish()) } else { - Err(ResponseError::from_msg( - format!("chat {workspace_uid} not found"), - Code::ChatNotFound, - )) + Err(ResponseError::from_msg(format!("chat {workspace_uid} not found"), Code::ChatNotFound)) } } From 9cab754942087f46d943a95c2e77502dfda306e8 Mon Sep 17 00:00:00 2001 From: Kerollmops Date: Tue, 10 Jun 2025 11:11:34 +0200 Subject: [PATCH 082/103] Update insta snapshots --- crates/meilisearch/tests/auth/api_keys.rs | 4 ++-- crates/meilisearch/tests/auth/errors.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/meilisearch/tests/auth/api_keys.rs b/crates/meilisearch/tests/auth/api_keys.rs index 69a1ccb53..84ed24217 100644 --- a/crates/meilisearch/tests/auth/api_keys.rs +++ b/crates/meilisearch/tests/auth/api_keys.rs @@ -421,7 +421,7 @@ async fn error_add_api_key_invalid_parameters_actions() { meili_snap::snapshot!(code, @"400 Bad Request"); meili_snap::snapshot!(meili_snap::json_string!(response, { ".createdAt" => "[ignored]", ".updatedAt" => "[ignored]" }), @r###" { - "message": "Unknown value `doc.add` at `.actions[0]`: expected one of `*`, `search`, `documents.*`, `documents.add`, `documents.get`, `documents.delete`, `indexes.*`, `indexes.create`, `indexes.get`, `indexes.update`, `indexes.delete`, `indexes.swap`, `tasks.*`, `tasks.cancel`, `tasks.delete`, `tasks.get`, `settings.*`, `settings.get`, `settings.update`, `stats.*`, `stats.get`, `metrics.*`, `metrics.get`, `dumps.*`, `dumps.create`, `snapshots.*`, `snapshots.create`, `version`, `keys.create`, `keys.get`, `keys.update`, `keys.delete`, `experimental.get`, `experimental.update`, `network.get`, `network.update`, `chatCompletion`, `chats.get`, `chatsSettings.*`, `chatsSettings.get`, `chatsSettings.update`, `chatsSettings.delete`", + "message": "Unknown value `doc.add` at `.actions[0]`: expected one of `*`, `search`, `documents.*`, `documents.add`, `documents.get`, `documents.delete`, `indexes.*`, `indexes.create`, `indexes.get`, `indexes.update`, `indexes.delete`, `indexes.swap`, `tasks.*`, `tasks.cancel`, `tasks.delete`, `tasks.get`, `settings.*`, `settings.get`, `settings.update`, `stats.*`, `stats.get`, `metrics.*`, `metrics.get`, `dumps.*`, `dumps.create`, `snapshots.*`, `snapshots.create`, `version`, `keys.create`, `keys.get`, `keys.update`, `keys.delete`, `experimental.get`, `experimental.update`, `network.get`, `network.update`, `chatCompletions`, `chats.*`, `chats.get`, `chats.delete`, `chatsSettings.*`, `chatsSettings.get`, `chatsSettings.update`", "code": "invalid_api_key_actions", "type": "invalid_request", "link": "https://docs.meilisearch.com/errors#invalid_api_key_actions" @@ -856,7 +856,7 @@ async fn list_api_keys() { "key": "[ignored]", "uid": "[ignored]", "actions": [ - "chatCompletion", + "chatCompletions", "search" ], "indexes": [ diff --git a/crates/meilisearch/tests/auth/errors.rs b/crates/meilisearch/tests/auth/errors.rs index 6d4c17e19..ebe2e53fa 100644 --- a/crates/meilisearch/tests/auth/errors.rs +++ b/crates/meilisearch/tests/auth/errors.rs @@ -93,7 +93,7 @@ async fn create_api_key_bad_actions() { snapshot!(code, @"400 Bad Request"); snapshot!(json_string!(response), @r###" { - "message": "Unknown value `doggo` at `.actions[0]`: expected one of `*`, `search`, `documents.*`, `documents.add`, `documents.get`, `documents.delete`, `indexes.*`, `indexes.create`, `indexes.get`, `indexes.update`, `indexes.delete`, `indexes.swap`, `tasks.*`, `tasks.cancel`, `tasks.delete`, `tasks.get`, `settings.*`, `settings.get`, `settings.update`, `stats.*`, `stats.get`, `metrics.*`, `metrics.get`, `dumps.*`, `dumps.create`, `snapshots.*`, `snapshots.create`, `version`, `keys.create`, `keys.get`, `keys.update`, `keys.delete`, `experimental.get`, `experimental.update`, `network.get`, `network.update`, `chatCompletion`, `chats.get`, `chatsSettings.*`, `chatsSettings.get`, `chatsSettings.update`, `chatsSettings.delete`", + "message": "Unknown value `doggo` at `.actions[0]`: expected one of `*`, `search`, `documents.*`, `documents.add`, `documents.get`, `documents.delete`, `indexes.*`, `indexes.create`, `indexes.get`, `indexes.update`, `indexes.delete`, `indexes.swap`, `tasks.*`, `tasks.cancel`, `tasks.delete`, `tasks.get`, `settings.*`, `settings.get`, `settings.update`, `stats.*`, `stats.get`, `metrics.*`, `metrics.get`, `dumps.*`, `dumps.create`, `snapshots.*`, `snapshots.create`, `version`, `keys.create`, `keys.get`, `keys.update`, `keys.delete`, `experimental.get`, `experimental.update`, `network.get`, `network.update`, `chatCompletions`, `chats.*`, `chats.get`, `chats.delete`, `chatsSettings.*`, `chatsSettings.get`, `chatsSettings.update`", "code": "invalid_api_key_actions", "type": "invalid_request", "link": "https://docs.meilisearch.com/errors#invalid_api_key_actions" From 1a1317ab0f0fd2a39d242eaec89af4443238df7f Mon Sep 17 00:00:00 2001 From: Kerollmops Date: Tue, 10 Jun 2025 11:12:27 +0200 Subject: [PATCH 083/103] Make clippy happy --- crates/meilisearch-types/src/features.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/meilisearch-types/src/features.rs b/crates/meilisearch-types/src/features.rs index e30ce3b55..1cb0fa244 100644 --- a/crates/meilisearch-types/src/features.rs +++ b/crates/meilisearch-types/src/features.rs @@ -95,11 +95,11 @@ impl ChatCompletionSettings { use ChatCompletionSource::*; match self { Self { source: AzureOpenAi, base_url, deployment_id, api_version, .. } if base_url.is_none() || deployment_id.is_none() || api_version.is_none() => Err(ResponseError::from_msg( - format!("azureOpenAi requires setting a valid `baseUrl`, `deploymentId`, and `apiVersion`"), + "azureOpenAi requires setting a valid `baseUrl`, `deploymentId`, and `apiVersion`".to_string(), Code::BadRequest, )), Self { source: VLlm, base_url, .. } if base_url.is_none() => Err(ResponseError::from_msg( - format!("vLlm requires setting a valid `baseUrl`"), + "vLlm requires setting a valid `baseUrl`".to_string(), Code::BadRequest, )), _otherwise => Ok(()), From c640856cc18807ca4567acfa2aa0657871bffeea Mon Sep 17 00:00:00 2001 From: Kerollmops Date: Tue, 10 Jun 2025 11:13:32 +0200 Subject: [PATCH 084/103] Improve code comments --- crates/meilisearch/src/routes/chats/utils.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/meilisearch/src/routes/chats/utils.rs b/crates/meilisearch/src/routes/chats/utils.rs index b29747bc9..34bfebcc7 100644 --- a/crates/meilisearch/src/routes/chats/utils.rs +++ b/crates/meilisearch/src/routes/chats/utils.rs @@ -41,7 +41,7 @@ impl SseEventSender { function_name: String, function_arguments: String, ) -> Result<(), SendError> { - #[allow(deprecated)] + #[allow(deprecated)] // function_call let message = ChatCompletionRequestMessage::Assistant(ChatCompletionRequestAssistantMessage { content: None, @@ -78,7 +78,7 @@ impl SseEventSender { resp.choices[0] = ChatChoiceStream { index: 0, - #[allow(deprecated)] + #[allow(deprecated)] // function_call delta: ChatCompletionStreamResponseDelta { content: None, function_call: None, @@ -125,7 +125,7 @@ impl SseEventSender { resp.choices[0] = ChatChoiceStream { index: 0, - #[allow(deprecated)] + #[allow(deprecated)] // function_call delta: ChatCompletionStreamResponseDelta { content: None, function_call: None, @@ -170,7 +170,7 @@ impl SseEventSender { resp.choices[0] = ChatChoiceStream { index: 0, - #[allow(deprecated)] + #[allow(deprecated)] // function_call delta: ChatCompletionStreamResponseDelta { content: None, function_call: None, From ae115cee7853c60d00b498b3a06feaf28508d7aa Mon Sep 17 00:00:00 2001 From: Kerollmops Date: Tue, 10 Jun 2025 13:51:04 +0200 Subject: [PATCH 085/103] Make clippy happy --- crates/meilisearch/src/routes/chats/chat_completions.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/meilisearch/src/routes/chats/chat_completions.rs b/crates/meilisearch/src/routes/chats/chat_completions.rs index bf336f224..f65c958ee 100644 --- a/crates/meilisearch/src/routes/chats/chat_completions.rs +++ b/crates/meilisearch/src/routes/chats/chat_completions.rs @@ -310,7 +310,7 @@ async fn non_streamed_chat( index_scheduler.features().check_chat_completions("using the /chats chat completions route")?; return Err(ResponseError::from_msg( - format!("Non-streamed chat completions is not implemented"), + "Non-streamed chat completions is not implemented".to_string(), Code::UnimplementedNonStreamingChatCompletions, )); From b32e30ad27f25159c6ecfc7ec259764090d54675 Mon Sep 17 00:00:00 2001 From: Kerollmops Date: Tue, 10 Jun 2025 14:02:43 +0200 Subject: [PATCH 086/103] Make the chat setting db name a const --- crates/index-scheduler/src/lib.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/index-scheduler/src/lib.rs b/crates/index-scheduler/src/lib.rs index 3ad342bea..3a2bea40b 100644 --- a/crates/index-scheduler/src/lib.rs +++ b/crates/index-scheduler/src/lib.rs @@ -77,6 +77,7 @@ use crate::utils::clamp_to_page_size; pub(crate) type BEI128 = I128; const TASK_SCHEDULER_SIZE_THRESHOLD_PERCENT_INT: u64 = 40; +const CHAT_SETTINGS_DB_NAME: &str = "chat-settings"; #[derive(Debug)] pub struct IndexSchedulerOptions { @@ -279,7 +280,7 @@ impl IndexScheduler { let features = features::FeatureData::new(&env, &mut wtxn, options.instance_features)?; let queue = Queue::new(&env, &mut wtxn, &options)?; let index_mapper = IndexMapper::new(&env, &mut wtxn, &options, budget)?; - let chat_settings = env.create_database(&mut wtxn, Some("chat-settings"))?; + let chat_settings = env.create_database(&mut wtxn, Some(CHAT_SETTINGS_DB_NAME))?; wtxn.commit()?; // allow unreachable_code to get rids of the warning in the case of a test build. From bbe802c6565d55c02c816512e67dd27807ce885e Mon Sep 17 00:00:00 2001 From: Kerollmops Date: Tue, 10 Jun 2025 14:03:05 +0200 Subject: [PATCH 087/103] Remove the write txn method from the index scheduler --- crates/index-scheduler/src/lib.rs | 32 +++++++++---------- .../src/routes/chats/chat_completions.rs | 7 ++-- crates/meilisearch/src/routes/chats/mod.rs | 4 +-- .../meilisearch/src/routes/chats/settings.rs | 15 +++------ 4 files changed, 23 insertions(+), 35 deletions(-) diff --git a/crates/index-scheduler/src/lib.rs b/crates/index-scheduler/src/lib.rs index 3a2bea40b..639facd44 100644 --- a/crates/index-scheduler/src/lib.rs +++ b/crates/index-scheduler/src/lib.rs @@ -56,7 +56,7 @@ use meilisearch_types::features::{ }; use meilisearch_types::heed::byteorder::BE; use meilisearch_types::heed::types::{DecodeIgnore, SerdeJson, Str, I128}; -use meilisearch_types::heed::{self, Database, Env, RoTxn, RwTxn, WithoutTls}; +use meilisearch_types::heed::{self, Database, Env, RoTxn, WithoutTls}; use meilisearch_types::milli::index::IndexEmbeddingConfig; use meilisearch_types::milli::update::IndexerConfig; use meilisearch_types::milli::vector::{Embedder, EmbedderOptions, EmbeddingConfigs}; @@ -311,11 +311,7 @@ impl IndexScheduler { Ok(this) } - pub fn write_txn(&self) -> Result { - self.env.write_txn().map_err(|e| e.into()) - } - - pub fn read_txn(&self) -> Result> { + fn read_txn(&self) -> Result> { self.env.read_txn().map_err(|e| e.into()) } @@ -901,8 +897,9 @@ impl IndexScheduler { res.map(EmbeddingConfigs::new) } - pub fn chat_settings(&self, rtxn: &RoTxn, uid: &str) -> Result> { - self.chat_settings.get(rtxn, uid).map_err(Into::into) + pub fn chat_settings(&self, uid: &str) -> Result> { + let rtxn = self.env.read_txn()?; + self.chat_settings.get(&rtxn, uid).map_err(Into::into) } /// Return true if chat workspace exists. @@ -911,17 +908,18 @@ impl IndexScheduler { Ok(self.chat_settings.remap_data_type::().get(&rtxn, name)?.is_some()) } - pub fn put_chat_settings( - &self, - wtxn: &mut RwTxn, - uid: &str, - settings: &ChatCompletionSettings, - ) -> Result<()> { - self.chat_settings.put(wtxn, uid, settings).map_err(Into::into) + pub fn put_chat_settings(&self, uid: &str, settings: &ChatCompletionSettings) -> Result<()> { + let mut wtxn = self.env.write_txn()?; + self.chat_settings.put(&mut wtxn, uid, settings)?; + wtxn.commit()?; + Ok(()) } - pub fn delete_chat_settings(&self, wtxn: &mut RwTxn, uid: &str) -> Result { - self.chat_settings.delete(wtxn, uid).map_err(Into::into) + pub fn delete_chat_settings(&self, uid: &str) -> Result { + let mut wtxn = self.env.write_txn()?; + let deleted = self.chat_settings.delete(&mut wtxn, uid)?; + wtxn.commit()?; + Ok(deleted) } } diff --git a/crates/meilisearch/src/routes/chats/chat_completions.rs b/crates/meilisearch/src/routes/chats/chat_completions.rs index f65c958ee..786398cf7 100644 --- a/crates/meilisearch/src/routes/chats/chat_completions.rs +++ b/crates/meilisearch/src/routes/chats/chat_completions.rs @@ -315,8 +315,7 @@ async fn non_streamed_chat( )); let filters = index_scheduler.filters(); - let rtxn = index_scheduler.read_txn()?; - let chat_settings = match index_scheduler.chat_settings(&rtxn, workspace_uid).unwrap() { + let chat_settings = match index_scheduler.chat_settings(workspace_uid).unwrap() { Some(settings) => settings, None => { return Err(ResponseError::from_msg( @@ -413,8 +412,7 @@ async fn streamed_chat( index_scheduler.features().check_chat_completions("using the /chats chat completions route")?; let filters = index_scheduler.filters(); - let rtxn = index_scheduler.read_txn()?; - let chat_settings = match index_scheduler.chat_settings(&rtxn, workspace_uid)? { + let chat_settings = match index_scheduler.chat_settings(workspace_uid)? { Some(settings) => settings, None => { return Err(ResponseError::from_msg( @@ -423,7 +421,6 @@ async fn streamed_chat( )) } }; - drop(rtxn); let config = Config::new(&chat_settings); let auth_token = extract_token_from_request(&req)?.unwrap().to_string(); diff --git a/crates/meilisearch/src/routes/chats/mod.rs b/crates/meilisearch/src/routes/chats/mod.rs index 7a868fd45..c381a20c1 100644 --- a/crates/meilisearch/src/routes/chats/mod.rs +++ b/crates/meilisearch/src/routes/chats/mod.rs @@ -72,10 +72,8 @@ pub async fn delete_chat( ) -> Result { index_scheduler.features().check_chat_completions("deleting a chat")?; - let mut wtxn = index_scheduler.write_txn()?; let workspace_uid = workspace_uid.into_inner(); - if index_scheduler.delete_chat_settings(&mut wtxn, &workspace_uid)? { - wtxn.commit()?; + if index_scheduler.delete_chat_settings(&workspace_uid)? { Ok(HttpResponse::NoContent().finish()) } else { Err(ResponseError::from_msg(format!("chat {workspace_uid} not found"), Code::ChatNotFound)) diff --git a/crates/meilisearch/src/routes/chats/settings.rs b/crates/meilisearch/src/routes/chats/settings.rs index ab696f68a..cd1ce5767 100644 --- a/crates/meilisearch/src/routes/chats/settings.rs +++ b/crates/meilisearch/src/routes/chats/settings.rs @@ -42,8 +42,7 @@ async fn get_settings( let ChatsParam { workspace_uid } = chats_param.into_inner(); // TODO do a spawn_blocking here ??? - let rtxn = index_scheduler.read_txn()?; - let mut settings = match index_scheduler.chat_settings(&rtxn, &workspace_uid)? { + let mut settings = match index_scheduler.chat_settings(&workspace_uid)? { Some(settings) => settings, None => { return Err(ResponseError::from_msg( @@ -68,8 +67,7 @@ async fn patch_settings( let ChatsParam { workspace_uid } = chats_param.into_inner(); // TODO do a spawn_blocking here - let mut wtxn = index_scheduler.write_txn()?; - let old_settings = index_scheduler.chat_settings(&wtxn, &workspace_uid)?.unwrap_or_default(); + let old_settings = index_scheduler.chat_settings(&workspace_uid)?.unwrap_or_default(); let prompts = match new.prompts { Setting::Set(new_prompts) => DbChatCompletionPrompts { @@ -147,8 +145,7 @@ async fn patch_settings( // ); settings.validate()?; - index_scheduler.put_chat_settings(&mut wtxn, &workspace_uid, &settings)?; - wtxn.commit()?; + index_scheduler.put_chat_settings(&workspace_uid, &settings)?; settings.hide_secrets(); @@ -165,11 +162,9 @@ async fn reset_settings( index_scheduler.features().check_chat_completions("using the /chats/settings route")?; let ChatsParam { workspace_uid } = chats_param.into_inner(); - let mut wtxn = index_scheduler.write_txn()?; - if index_scheduler.chat_settings(&wtxn, &workspace_uid)?.is_some() { + if index_scheduler.chat_settings(&workspace_uid)?.is_some() { let settings = Default::default(); - index_scheduler.put_chat_settings(&mut wtxn, &workspace_uid, &settings)?; - wtxn.commit()?; + index_scheduler.put_chat_settings(&workspace_uid, &settings)?; Ok(HttpResponse::Ok().json(settings)) } else { Err(ResponseError::from_msg( From 4352a924d7a9cc35a4581caf1436dcb39729a2f0 Mon Sep 17 00:00:00 2001 From: Kerollmops Date: Tue, 10 Jun 2025 14:05:02 +0200 Subject: [PATCH 088/103] Remove useless filters parameter --- crates/index-scheduler/src/lib.rs | 1 - crates/meilisearch/src/routes/chats/mod.rs | 8 ++------ 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/crates/index-scheduler/src/lib.rs b/crates/index-scheduler/src/lib.rs index 639facd44..505ce23f8 100644 --- a/crates/index-scheduler/src/lib.rs +++ b/crates/index-scheduler/src/lib.rs @@ -553,7 +553,6 @@ impl IndexScheduler { /// And a `Vec` of the workspace_uids pub fn paginated_chat_workspace_uids( &self, - _filters: &meilisearch_auth::AuthFilter, from: usize, limit: usize, ) -> Result<(usize, Vec)> { diff --git a/crates/meilisearch/src/routes/chats/mod.rs b/crates/meilisearch/src/routes/chats/mod.rs index c381a20c1..4985deb39 100644 --- a/crates/meilisearch/src/routes/chats/mod.rs +++ b/crates/meilisearch/src/routes/chats/mod.rs @@ -114,12 +114,8 @@ pub async fn list_workspaces( index_scheduler.features().check_chat_completions("listing the chats")?; debug!(parameters = ?paginate, "List chat workspaces"); - let filters = index_scheduler.filters(); - let (total, workspaces) = index_scheduler.paginated_chat_workspace_uids( - filters, - *paginate.offset, - *paginate.limit, - )?; + let (total, workspaces) = + index_scheduler.paginated_chat_workspace_uids(*paginate.offset, *paginate.limit)?; let workspaces = workspaces.into_iter().map(|uid| ChatWorkspaceView { uid }).collect::>(); let ret = paginate.as_pagination().format_with(total, workspaces); From 7c1b15fd06914c72411dea799dcfcb9705fb3e0a Mon Sep 17 00:00:00 2001 From: Kerollmops Date: Tue, 10 Jun 2025 14:05:35 +0200 Subject: [PATCH 089/103] Remove useless liquid dependency for Meilisearch --- Cargo.lock | 1 - crates/meilisearch/Cargo.toml | 1 - 2 files changed, 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8a3942d5b..93921bb99 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3762,7 +3762,6 @@ dependencies = [ "itertools 0.14.0", "jsonwebtoken", "lazy_static", - "liquid", "manifest-dir-macros", "maplit", "meili-snap", diff --git a/crates/meilisearch/Cargo.toml b/crates/meilisearch/Cargo.toml index a40b63a24..c75b55bc2 100644 --- a/crates/meilisearch/Cargo.toml +++ b/crates/meilisearch/Cargo.toml @@ -49,7 +49,6 @@ is-terminal = "0.4.13" itertools = "0.14.0" jsonwebtoken = "9.3.0" lazy_static = "1.5.0" -liquid = "0.26.9" meilisearch-auth = { path = "../meilisearch-auth" } meilisearch-types = { path = "../meilisearch-types" } mimalloc = { version = "0.1.43", default-features = false } From 32207f9f1987b8af71b7b519a27a216764eca96f Mon Sep 17 00:00:00 2001 From: Kerollmops Date: Tue, 10 Jun 2025 14:07:53 +0200 Subject: [PATCH 090/103] Rename the error code about ranking score threshold --- crates/milli/src/index.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/crates/milli/src/index.rs b/crates/milli/src/index.rs index 6ff972ae0..9936e9b0c 100644 --- a/crates/milli/src/index.rs +++ b/crates/milli/src/index.rs @@ -1971,7 +1971,7 @@ pub struct SearchParameters { } #[derive(Debug, Clone, Copy, Default, Deserialize, Serialize, PartialEq, Deserr, ToSchema)] -#[deserr(try_from(f64) = TryFrom::try_from -> InvalidSearchRankingScoreThreshold)] +#[deserr(try_from(f64) = TryFrom::try_from -> InvalidSettingsRankingScoreThreshold)] pub struct RankingScoreThreshold(f64); impl RankingScoreThreshold { @@ -1981,11 +1981,11 @@ impl RankingScoreThreshold { } impl TryFrom for RankingScoreThreshold { - type Error = InvalidSearchRankingScoreThreshold; + type Error = InvalidSettingsRankingScoreThreshold; fn try_from(value: f64) -> StdResult { if !(0.0..=1.0).contains(&value) { - Err(InvalidSearchRankingScoreThreshold) + Err(InvalidSettingsRankingScoreThreshold) } else { Ok(RankingScoreThreshold(value)) } @@ -1993,11 +1993,11 @@ impl TryFrom for RankingScoreThreshold { } #[derive(Debug)] -pub struct InvalidSearchRankingScoreThreshold; +pub struct InvalidSettingsRankingScoreThreshold; -impl Error for InvalidSearchRankingScoreThreshold {} +impl Error for InvalidSettingsRankingScoreThreshold {} -impl fmt::Display for InvalidSearchRankingScoreThreshold { +impl fmt::Display for InvalidSettingsRankingScoreThreshold { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( f, From c60d11fb424516efe84d9b5654cedb4437f5dfba Mon Sep 17 00:00:00 2001 From: Kerollmops Date: Tue, 10 Jun 2025 14:56:13 +0200 Subject: [PATCH 091/103] Clean up the prompts --- crates/meilisearch-types/src/features.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/meilisearch-types/src/features.rs b/crates/meilisearch-types/src/features.rs index 1cb0fa244..651077484 100644 --- a/crates/meilisearch-types/src/features.rs +++ b/crates/meilisearch-types/src/features.rs @@ -8,7 +8,7 @@ pub const DEFAULT_CHAT_SYSTEM_PROMPT: &str = "You are a highly capable research pub const DEFAULT_CHAT_SEARCH_DESCRIPTION_PROMPT: &str = "Search the database for relevant JSON documents using an optional query."; pub const DEFAULT_CHAT_SEARCH_Q_PARAM_PROMPT: &str = "The search query string used to find relevant documents in the index. This should contain keywords or phrases that best represent what the user is looking for. More specific queries will yield more precise results."; -pub const DEFAULT_CHAT_SEARCH_INDEX_UID_PARAM_PROMPT: &str = "The name of the index to search within. An index is a collection of documents organized for search. Selecting the right index ensures the most relevant results for the user query. You have access to two indexes: movies, steam. The movies index contains movies with overviews. The steam index contains steam games from the Steam platform with their prices"; +pub const DEFAULT_CHAT_SEARCH_INDEX_UID_PARAM_PROMPT: &str = "The name of the index to search within. An index is a collection of documents organized for search. Selecting the right index ensures the most relevant results for the user query."; #[derive(Serialize, Deserialize, Debug, Clone, Copy, Default, PartialEq, Eq)] #[serde(rename_all = "camelCase", default)] From 28e6adc435ccda4dc5a0b2608736b95f290aca5b Mon Sep 17 00:00:00 2001 From: Kerollmops Date: Tue, 10 Jun 2025 16:16:11 +0200 Subject: [PATCH 092/103] Remove the SearchQuery Default impl and change the From impl --- crates/meilisearch/src/search/mod.rs | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/crates/meilisearch/src/search/mod.rs b/crates/meilisearch/src/search/mod.rs index 27369591b..5e543c53f 100644 --- a/crates/meilisearch/src/search/mod.rs +++ b/crates/meilisearch/src/search/mod.rs @@ -57,7 +57,7 @@ pub const DEFAULT_HIGHLIGHT_PRE_TAG: fn() -> String = || "".to_string(); pub const DEFAULT_HIGHLIGHT_POST_TAG: fn() -> String = || "".to_string(); pub const DEFAULT_SEMANTIC_RATIO: fn() -> SemanticRatio = || SemanticRatio(0.5); -#[derive(Clone, PartialEq, Deserr, ToSchema)] +#[derive(Clone, Default, PartialEq, Deserr, ToSchema)] #[deserr(error = DeserrJsonError, rename_all = camelCase, deny_unknown_fields)] pub struct SearchQuery { #[deserr(default, error = DeserrJsonError)] @@ -145,19 +145,9 @@ impl From for SearchQuery { matching_strategy: matching_strategy.map(MatchingStrategy::from).unwrap_or_default(), attributes_to_search_on, ranking_score_threshold: ranking_score_threshold.map(RankingScoreThreshold::from), - ..Default::default() - } - } -} - -impl Default for SearchQuery { - fn default() -> Self { - SearchQuery { q: None, vector: None, - hybrid: None, offset: DEFAULT_SEARCH_OFFSET(), - limit: DEFAULT_SEARCH_LIMIT(), page: None, hits_per_page: None, attributes_to_retrieve: None, @@ -169,15 +159,10 @@ impl Default for SearchQuery { show_ranking_score: false, show_ranking_score_details: false, filter: None, - sort: None, - distinct: None, facets: None, highlight_pre_tag: DEFAULT_HIGHLIGHT_PRE_TAG(), highlight_post_tag: DEFAULT_HIGHLIGHT_POST_TAG(), crop_marker: DEFAULT_CROP_MARKER(), - matching_strategy: Default::default(), - attributes_to_search_on: None, - ranking_score_threshold: None, locales: None, } } From 34d572e3e580ee472c5a7ce8bdadd8e78980128e Mon Sep 17 00:00:00 2001 From: Kerollmops Date: Tue, 10 Jun 2025 16:17:41 +0200 Subject: [PATCH 093/103] Reove useless commented code --- crates/milli/src/update/chat.rs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/crates/milli/src/update/chat.rs b/crates/milli/src/update/chat.rs index c52ede029..2f364894d 100644 --- a/crates/milli/src/update/chat.rs +++ b/crates/milli/src/update/chat.rs @@ -97,14 +97,6 @@ pub struct ChatSearchParams { #[schema(value_type = Option)] pub limit: Setting, - // #[serde(default, skip_serializing_if = "Setting::is_not_set")] - // #[deserr(default)] - // pub attributes_to_retrieve: Option>, - - // #[serde(default, skip_serializing_if = "Setting::is_not_set")] - // #[deserr(default)] - // pub filter: Option, - // #[serde(default, skip_serializing_if = "Setting::is_not_set")] #[deserr(default)] #[schema(value_type = Option>)] From 5ceb3c6a10dbd3e296f823a872cf01ebf0b5398d Mon Sep 17 00:00:00 2001 From: Kerollmops Date: Tue, 10 Jun 2025 16:27:18 +0200 Subject: [PATCH 094/103] Report an error when the document template max bytes is zero --- crates/meilisearch-types/src/error.rs | 1 + crates/milli/src/error.rs | 2 ++ crates/milli/src/update/settings.rs | 11 +++++++---- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/crates/meilisearch-types/src/error.rs b/crates/meilisearch-types/src/error.rs index bdf9bb162..077600a8e 100644 --- a/crates/meilisearch-types/src/error.rs +++ b/crates/meilisearch-types/src/error.rs @@ -447,6 +447,7 @@ impl ErrorCode for milli::Error { | UserError::InvalidSettingsDimensions { .. } | UserError::InvalidUrl { .. } | UserError::InvalidSettingsDocumentTemplateMaxBytes { .. } + | UserError::InvalidChatSettingsDocumentTemplateMaxBytes | UserError::InvalidPrompt(_) | UserError::InvalidDisableBinaryQuantization { .. } | UserError::InvalidSourceForNested { .. } diff --git a/crates/milli/src/error.rs b/crates/milli/src/error.rs index e129a31a0..2136ec97e 100644 --- a/crates/milli/src/error.rs +++ b/crates/milli/src/error.rs @@ -386,6 +386,8 @@ and can not be more than 511 bytes.", .document_id.to_string() DocumentEditionRuntimeError(Box), #[error("Document edition runtime error encountered while compiling the function: {0}")] DocumentEditionCompilationError(rhai::ParseError), + #[error("`.chat.documentTemplateMaxBytes`: `documentTemplateMaxBytes` cannot be zero")] + InvalidChatSettingsDocumentTemplateMaxBytes, #[error("{0}")] DocumentEmbeddingError(String), } diff --git a/crates/milli/src/update/settings.rs b/crates/milli/src/update/settings.rs index ae46cb5d9..05607d7a5 100644 --- a/crates/milli/src/update/settings.rs +++ b/crates/milli/src/update/settings.rs @@ -19,7 +19,7 @@ use crate::attribute_patterns::PatternMatch; use crate::constants::RESERVED_GEO_FIELD_NAME; use crate::criterion::Criterion; use crate::disabled_typos_terms::DisabledTyposTerms; -use crate::error::UserError; +use crate::error::UserError::{self, InvalidChatSettingsDocumentTemplateMaxBytes}; use crate::fields_ids_map::metadata::{FieldIdMapWithMetadata, MetadataBuilder}; use crate::filterable_attributes_rules::match_faceted_field; use crate::index::{ @@ -1249,7 +1249,7 @@ impl<'a, 't, 'i> Settings<'a, 't, 'i> { Ok(()) } - fn update_chat_config(&mut self) -> heed::Result { + fn update_chat_config(&mut self) -> Result { match &mut self.chat { Setting::Set(ChatSettings { description: new_description, @@ -1273,7 +1273,10 @@ impl<'a, 't, 'i> Settings<'a, 't, 'i> { Setting::NotSet => prompt.template.clone(), }, max_bytes: match new_document_template_max_bytes { - Setting::Set(m) => NonZeroUsize::new(*m), + Setting::Set(m) => Some( + NonZeroUsize::new(*m) + .ok_or(InvalidChatSettingsDocumentTemplateMaxBytes)?, + ), Setting::Reset => Some(default_max_bytes()), Setting::NotSet => prompt.max_bytes, }, @@ -1347,7 +1350,7 @@ impl<'a, 't, 'i> Settings<'a, 't, 'i> { Ok(true) } - Setting::Reset => self.index.delete_chat_config(self.wtxn), + Setting::Reset => self.index.delete_chat_config(self.wtxn).map_err(Into::into), Setting::NotSet => Ok(false), } } From ab0eba2f72018e48fae3de31d29cf653ac2be851 Mon Sep 17 00:00:00 2001 From: Kerollmops Date: Tue, 10 Jun 2025 16:31:58 +0200 Subject: [PATCH 095/103] Remove useless double check --- crates/meilisearch/src/routes/indexes/settings.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/crates/meilisearch/src/routes/indexes/settings.rs b/crates/meilisearch/src/routes/indexes/settings.rs index d91c3cd35..a4b7a5219 100644 --- a/crates/meilisearch/src/routes/indexes/settings.rs +++ b/crates/meilisearch/src/routes/indexes/settings.rs @@ -569,10 +569,6 @@ pub async fn update_all( debug!(parameters = ?new_settings, "Update all settings"); let new_settings = validate_settings(new_settings, &index_scheduler)?; - if !new_settings.chat.is_not_set() { - index_scheduler.features().check_chat_completions("setting `chat` in the index route")?; - } - analytics.publish( SettingsAnalytics { ranking_rules: RankingRulesAnalytics::new(new_settings.ranking_rules.as_ref().set()), From e9d547556d3f50c68b835ebc560f034403ae47c8 Mon Sep 17 00:00:00 2001 From: Kerollmops Date: Tue, 10 Jun 2025 16:40:50 +0200 Subject: [PATCH 096/103] Better error reporting when multi choices is used --- crates/meilisearch-types/src/error.rs | 1 + .../src/routes/chats/chat_completions.rs | 20 +++++++++++++------ 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/crates/meilisearch-types/src/error.rs b/crates/meilisearch-types/src/error.rs index 077600a8e..3f7f00cd0 100644 --- a/crates/meilisearch-types/src/error.rs +++ b/crates/meilisearch-types/src/error.rs @@ -391,6 +391,7 @@ EditDocumentsByFunctionError , InvalidRequest , BAD_REQU InvalidSettingsIndexChat , InvalidRequest , BAD_REQUEST ; // Experimental features - Chat Completions UnimplementedNonStreamingChatCompletions , InvalidRequest , NOT_IMPLEMENTED ; +UnimplementedMultiChoiceChatCompletions , InvalidRequest , NOT_IMPLEMENTED ; ChatNotFound , InvalidRequest , NOT_FOUND ; InvalidChatCompletionOrgId , InvalidRequest , BAD_REQUEST ; InvalidChatCompletionProjectId , InvalidRequest , BAD_REQUEST ; diff --git a/crates/meilisearch/src/routes/chats/chat_completions.rs b/crates/meilisearch/src/routes/chats/chat_completions.rs index 786398cf7..e06f9a082 100644 --- a/crates/meilisearch/src/routes/chats/chat_completions.rs +++ b/crates/meilisearch/src/routes/chats/chat_completions.rs @@ -64,12 +64,6 @@ async fn chat( ) -> impl Responder { let ChatsParam { workspace_uid } = chats_param.into_inner(); - assert_eq!( - chat_completion.n.unwrap_or(1), - 1, - "Meilisearch /chat only support one completion at a time (n = 1, n = null)" - ); - if chat_completion.stream.unwrap_or(false) { Either::Right( streamed_chat( @@ -309,6 +303,13 @@ async fn non_streamed_chat( ) -> Result { index_scheduler.features().check_chat_completions("using the /chats chat completions route")?; + if let Some(n) = chat_completion.n.filter(|&n| n != 1) { + return Err(ResponseError::from_msg( + format!("You tried to specify n = {n} but only single choices are supported (n = 1)."), + Code::UnimplementedMultiChoiceChatCompletions, + )); + } + return Err(ResponseError::from_msg( "Non-streamed chat completions is not implemented".to_string(), Code::UnimplementedNonStreamingChatCompletions, @@ -412,6 +413,13 @@ async fn streamed_chat( index_scheduler.features().check_chat_completions("using the /chats chat completions route")?; let filters = index_scheduler.filters(); + if let Some(n) = chat_completion.n.filter(|&n| n != 1) { + return Err(ResponseError::from_msg( + format!("You tried to specify n = {n} but only single choices are supported (n = 1)."), + Code::UnimplementedMultiChoiceChatCompletions, + )); + } + let chat_settings = match index_scheduler.chat_settings(workspace_uid)? { Some(settings) => settings, None => { From b037e416d3a246b8a356a922101c1dc7c2094006 Mon Sep 17 00:00:00 2001 From: Kerollmops Date: Tue, 10 Jun 2025 16:43:20 +0200 Subject: [PATCH 097/103] Make an unreachable case, unreachable --- crates/meilisearch/src/routes/chats/utils.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/crates/meilisearch/src/routes/chats/utils.rs b/crates/meilisearch/src/routes/chats/utils.rs index 34bfebcc7..ee3a1bf27 100644 --- a/crates/meilisearch/src/routes/chats/utils.rs +++ b/crates/meilisearch/src/routes/chats/utils.rs @@ -232,9 +232,8 @@ pub fn format_documents<'doc>( for (docid, external_docid) in internal_docids.into_iter().zip(external_ids) { let document = match DocumentFromDb::new(docid, rtxn, index, &fid_map)? { Some(doc) => doc, - None => continue, + None => unreachable!("Document with internal ID {docid} not found"), }; - let text = prompt.render_document(&external_docid, document, &gfid_map, doc_alloc).unwrap(); renders.push(text); } From a0a4ac66ec6172defd594c41a9aa758c14e2cfcc Mon Sep 17 00:00:00 2001 From: Kerollmops Date: Tue, 10 Jun 2025 16:48:28 +0200 Subject: [PATCH 098/103] Better document the done streamed event --- crates/meilisearch/src/routes/chats/utils.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/crates/meilisearch/src/routes/chats/utils.rs b/crates/meilisearch/src/routes/chats/utils.rs index ee3a1bf27..752eb10e6 100644 --- a/crates/meilisearch/src/routes/chats/utils.rs +++ b/crates/meilisearch/src/routes/chats/utils.rs @@ -197,7 +197,10 @@ impl SseEventSender { } pub async fn stop(self) -> Result<(), SendError> { - self.0.send(Event::Data(sse::Data::new("[DONE]"))).await + // It is the way OpenAI sends a correct end of stream + // + const DONE_DATA: &str = "[DONE]"; + self.0.send(Event::Data(sse::Data::new(DONE_DATA))).await } async fn send_json(&self, data: &S) -> Result<(), SendError> { From 7ea2e4ec7b142d14eaf2f4b9727c8678b60b340f Mon Sep 17 00:00:00 2001 From: Kerollmops Date: Tue, 10 Jun 2025 16:51:39 +0200 Subject: [PATCH 099/103] Better document why we duplicate structs --- crates/milli/src/index.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/milli/src/index.rs b/crates/milli/src/index.rs index 9936e9b0c..e9e63a853 100644 --- a/crates/milli/src/index.rs +++ b/crates/milli/src/index.rs @@ -2021,6 +2021,8 @@ pub struct PrefixSettings { pub compute_prefixes: PrefixSearch, } +/// This is unfortunately a duplication of the struct in . +/// The reason why it is duplicated is because milli cannot depend on meilisearch. It would be cyclic imports. #[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Deserr, ToSchema, Serialize, Deserialize)] #[deserr(rename_all = camelCase)] #[serde(rename_all = "camelCase")] From 952fabf8a0452dc2be01540a6b8286cc048e1334 Mon Sep 17 00:00:00 2001 From: Kerollmops Date: Tue, 10 Jun 2025 17:01:00 +0200 Subject: [PATCH 100/103] Better document function names --- crates/meilisearch/src/routes/chats/mod.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/crates/meilisearch/src/routes/chats/mod.rs b/crates/meilisearch/src/routes/chats/mod.rs index 4985deb39..a8a93e6a4 100644 --- a/crates/meilisearch/src/routes/chats/mod.rs +++ b/crates/meilisearch/src/routes/chats/mod.rs @@ -26,12 +26,21 @@ pub mod settings; mod utils; /// The function name to report search progress. +/// This function is used to report on what meilisearch is +/// doing which must be used on the frontend to report progress. const MEILI_SEARCH_PROGRESS_NAME: &str = "_meiliSearchProgress"; /// The function name to append a conversation message in the user conversation. +/// This function is used to append a conversation message in the user conversation. +/// This must be used on the frontend to keep context of what happened on the +/// Meilisearch-side and keep good context for follow up questions. const MEILI_APPEND_CONVERSATION_MESSAGE_NAME: &str = "_meiliAppendConversationMessage"; /// The function name to report sources to the frontend. +/// This function is used to report sources to the frontend. +/// The call id is associated to the one used by the search progress function. const MEILI_SEARCH_SOURCES_NAME: &str = "_meiliSearchSources"; /// The *internal* function name to provide to the LLM to search in indexes. +/// This function must not leak to the user as the LLM will call it and the +/// main goal of Meilisearch is to provide an answer to these calls. const MEILI_SEARCH_IN_INDEX_FUNCTION_NAME: &str = "_meiliSearchInIndex"; #[derive(Deserialize)] From 506ee40dc552baa337a95f2f7cd28463331a46f4 Mon Sep 17 00:00:00 2001 From: Kerollmops Date: Tue, 10 Jun 2025 17:52:35 +0200 Subject: [PATCH 101/103] Improve errors and other stuff --- crates/meilisearch-types/src/error.rs | 1 + .../src/routes/chats/chat_completions.rs | 48 +++++++++---------- .../meilisearch/src/routes/chats/settings.rs | 2 - 3 files changed, 25 insertions(+), 26 deletions(-) diff --git a/crates/meilisearch-types/src/error.rs b/crates/meilisearch-types/src/error.rs index 3f7f00cd0..10fd645ae 100644 --- a/crates/meilisearch-types/src/error.rs +++ b/crates/meilisearch-types/src/error.rs @@ -390,6 +390,7 @@ InvalidDocumentEditionFunctionFilter , InvalidRequest , BAD_REQU EditDocumentsByFunctionError , InvalidRequest , BAD_REQUEST ; InvalidSettingsIndexChat , InvalidRequest , BAD_REQUEST ; // Experimental features - Chat Completions +UnimplementedExternalFunctionCalling , InvalidRequest , NOT_IMPLEMENTED ; UnimplementedNonStreamingChatCompletions , InvalidRequest , NOT_IMPLEMENTED ; UnimplementedMultiChoiceChatCompletions , InvalidRequest , NOT_IMPLEMENTED ; ChatNotFound , InvalidRequest , NOT_FOUND ; diff --git a/crates/meilisearch/src/routes/chats/chat_completions.rs b/crates/meilisearch/src/routes/chats/chat_completions.rs index e06f9a082..143d2cddf 100644 --- a/crates/meilisearch/src/routes/chats/chat_completions.rs +++ b/crates/meilisearch/src/routes/chats/chat_completions.rs @@ -113,8 +113,24 @@ fn setup_search_tool( system_role: SystemRole, ) -> Result { let tools = chat_completion.tools.get_or_insert_default(); - if tools.iter().any(|t| t.function.name == MEILI_SEARCH_IN_INDEX_FUNCTION_NAME) { - panic!("{MEILI_SEARCH_IN_INDEX_FUNCTION_NAME} function already set"); + for tool in &tools[..] { + match tool.function.name.as_str() { + MEILI_SEARCH_IN_INDEX_FUNCTION_NAME => { + return Err(ResponseError::from_msg( + format!("{MEILI_SEARCH_IN_INDEX_FUNCTION_NAME} function is already defined."), + Code::BadRequest, + )); + } + MEILI_SEARCH_PROGRESS_NAME + | MEILI_SEARCH_SOURCES_NAME + | MEILI_APPEND_CONVERSATION_MESSAGE_NAME => (), + external_function_name => { + return Err(ResponseError::from_msg( + format!("{external_function_name}: External functions are not supported yet."), + Code::UnimplementedExternalFunctionCalling, + )); + } + } } // Remove internal tools used for front-end notifications as they should be hidden from the LLM. @@ -220,9 +236,6 @@ async fn process_search_request( index_uid: String, q: Option, ) -> Result<(Index, Vec, String), ResponseError> { - // TBD - // let mut aggregate = SearchAggregator::::from_query(&query); - let index = index_scheduler.index(&index_uid)?; let rtxn = index.static_read_txn()?; let ChatConfig { description: _, prompt: _, search_parameters } = index.chat_config(&rtxn)?; @@ -281,7 +294,6 @@ async fn process_search_request( documents.push(document); } } - // analytics.publish(aggregate, &req); let (rtxn, search_result) = output?; let render_alloc = Bump::new(); @@ -495,7 +507,7 @@ async fn run_conversation( function_support: FunctionSupport, ) -> Result, ()>, SendError> { let mut finish_reason = None; - // safety: The unwrap can only happen if the stream is not correctly configured. + // safety: unwrap: can only happens if `stream` was set to `false` let mut response = client.chat().create_stream(chat_completion.clone()).await.unwrap(); while let Some(result) = response.next().await { match result { @@ -535,15 +547,11 @@ async fn run_conversation( Call::External } }); - - if global_tool_calls.get(index).is_some_and(Call::is_external) { - todo!("Support forwarding external tool calls"); - } } } None => { if !global_tool_calls.is_empty() { - let (meili_calls, other_calls): (Vec<_>, Vec<_>) = + let (meili_calls, _other_calls): (Vec<_>, Vec<_>) = mem::take(global_tool_calls) .into_values() .flat_map(|call| match call { @@ -568,11 +576,6 @@ async fn run_conversation( .into(), ); - assert!( - other_calls.is_empty(), - "We do not support external tool forwarding for now" - ); - handle_meili_tools( index_scheduler, auth_ctrl, @@ -698,16 +701,13 @@ impl Call { matches!(self, Call::Internal { .. }) } - fn is_external(&self) -> bool { - matches!(self, Call::External { .. }) - } - + /// # Panics + /// + /// - if called on external calls fn append(&mut self, more: &str) { match self { Call::Internal { arguments, .. } => arguments.push_str(more), - Call::External { .. } => { - panic!("Cannot append argument chunks to an external function") - } + Call::External => panic!("Cannot append argument chunks to an external function"), } } } diff --git a/crates/meilisearch/src/routes/chats/settings.rs b/crates/meilisearch/src/routes/chats/settings.rs index cd1ce5767..28611ee98 100644 --- a/crates/meilisearch/src/routes/chats/settings.rs +++ b/crates/meilisearch/src/routes/chats/settings.rs @@ -41,7 +41,6 @@ async fn get_settings( let ChatsParam { workspace_uid } = chats_param.into_inner(); - // TODO do a spawn_blocking here ??? let mut settings = match index_scheduler.chat_settings(&workspace_uid)? { Some(settings) => settings, None => { @@ -66,7 +65,6 @@ async fn patch_settings( index_scheduler.features().check_chat_completions("using the /chats/settings route")?; let ChatsParam { workspace_uid } = chats_param.into_inner(); - // TODO do a spawn_blocking here let old_settings = index_scheduler.chat_settings(&workspace_uid)?.unwrap_or_default(); let prompts = match new.prompts { From 77cc3678b52cfeed6d9f387ac2a31e564c50af02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Renault?= Date: Wed, 11 Jun 2025 09:27:14 +0200 Subject: [PATCH 102/103] Make sure template errors are reported to the LLM and front-end without panicking --- crates/meilisearch-types/src/error.rs | 1 + .../src/routes/chats/chat_completions.rs | 12 ++++-- crates/meilisearch/src/routes/chats/errors.rs | 41 +++++++++++++------ crates/meilisearch/src/routes/chats/utils.rs | 12 +++++- 4 files changed, 49 insertions(+), 17 deletions(-) diff --git a/crates/meilisearch-types/src/error.rs b/crates/meilisearch-types/src/error.rs index 10fd645ae..d2500b7e1 100644 --- a/crates/meilisearch-types/src/error.rs +++ b/crates/meilisearch-types/src/error.rs @@ -394,6 +394,7 @@ UnimplementedExternalFunctionCalling , InvalidRequest , NOT_IMPL UnimplementedNonStreamingChatCompletions , InvalidRequest , NOT_IMPLEMENTED ; UnimplementedMultiChoiceChatCompletions , InvalidRequest , NOT_IMPLEMENTED ; ChatNotFound , InvalidRequest , NOT_FOUND ; +InvalidChatSettingDocumentTemplate , InvalidRequest , BAD_REQUEST ; InvalidChatCompletionOrgId , InvalidRequest , BAD_REQUEST ; InvalidChatCompletionProjectId , InvalidRequest , BAD_REQUEST ; InvalidChatCompletionApiVersion , InvalidRequest , BAD_REQUEST ; diff --git a/crates/meilisearch/src/routes/chats/chat_completions.rs b/crates/meilisearch/src/routes/chats/chat_completions.rs index 143d2cddf..60f28d638 100644 --- a/crates/meilisearch/src/routes/chats/chat_completions.rs +++ b/crates/meilisearch/src/routes/chats/chat_completions.rs @@ -646,7 +646,7 @@ async fn handle_meili_tools( } let result = match serde_json::from_str(&call.function.arguments) { - Ok(SearchInIndexParameters { index_uid, q }) => process_search_request( + Ok(SearchInIndexParameters { index_uid, q }) => match process_search_request( index_scheduler, auth_ctrl.clone(), search_queue, @@ -655,7 +655,14 @@ async fn handle_meili_tools( q, ) .await - .map_err(|e| e.to_string()), + { + Ok(output) => Ok(output), + Err(err) => { + let error_text = err.to_string(); + tx.send_error(&StreamErrorEvent::from_response_error(err)).await?; + Err(error_text) + } + }, Err(err) => Err(err.to_string()), }; @@ -664,7 +671,6 @@ async fn handle_meili_tools( if report_sources { tx.report_sources(resp.clone(), &call.id, &documents).await?; } - text } Err(err) => err, diff --git a/crates/meilisearch/src/routes/chats/errors.rs b/crates/meilisearch/src/routes/chats/errors.rs index f1aa9722b..efa60ba50 100644 --- a/crates/meilisearch/src/routes/chats/errors.rs +++ b/crates/meilisearch/src/routes/chats/errors.rs @@ -1,5 +1,6 @@ use async_openai::error::{ApiError, OpenAIError}; use async_openai::reqwest_eventsource::Error as EventSourceError; +use meilisearch_types::error::ResponseError; use serde::{Deserialize, Serialize}; use uuid::Uuid; @@ -53,12 +54,13 @@ pub struct StreamError { } impl StreamErrorEvent { + const ERROR_TYPE: &str = "error"; + pub async fn from_openai_error(error: OpenAIError) -> Result { - let error_type = "error".to_string(); match error { OpenAIError::Reqwest(e) => Ok(StreamErrorEvent { event_id: Uuid::new_v4().to_string(), - r#type: error_type, + r#type: Self::ERROR_TYPE.to_string(), error: StreamError { r#type: "internal_reqwest_error".to_string(), code: Some("internal".to_string()), @@ -69,7 +71,7 @@ impl StreamErrorEvent { }), OpenAIError::ApiError(ApiError { message, r#type, param, code }) => { Ok(StreamErrorEvent { - r#type: error_type, + r#type: Self::ERROR_TYPE.to_string(), event_id: Uuid::new_v4().to_string(), error: StreamError { r#type: r#type.unwrap_or_else(|| "unknown".to_string()), @@ -82,7 +84,7 @@ impl StreamErrorEvent { } OpenAIError::JSONDeserialize(error) => Ok(StreamErrorEvent { event_id: Uuid::new_v4().to_string(), - r#type: error_type, + r#type: Self::ERROR_TYPE.to_string(), error: StreamError { r#type: "json_deserialize_error".to_string(), code: Some("internal".to_string()), @@ -100,7 +102,7 @@ impl StreamErrorEvent { Ok(StreamErrorEvent { event_id: Uuid::new_v4().to_string(), - r#type: error_type, + r#type: Self::ERROR_TYPE.to_string(), error: StreamError { r#type, code, message, param, event_id: None }, }) } @@ -111,13 +113,13 @@ impl StreamErrorEvent { Ok(StreamErrorEvent { event_id: Uuid::new_v4().to_string(), - r#type: error_type, + r#type: Self::ERROR_TYPE.to_string(), error: StreamError { r#type, code, message, param, event_id: None }, }) } EventSourceError::Utf8(error) => Ok(StreamErrorEvent { event_id: Uuid::new_v4().to_string(), - r#type: error_type, + r#type: Self::ERROR_TYPE.to_string(), error: StreamError { r#type: "invalid_utf8_error".to_string(), code: None, @@ -128,7 +130,7 @@ impl StreamErrorEvent { }), EventSourceError::Parser(error) => Ok(StreamErrorEvent { event_id: Uuid::new_v4().to_string(), - r#type: error_type, + r#type: Self::ERROR_TYPE.to_string(), error: StreamError { r#type: "parser_error".to_string(), code: None, @@ -139,7 +141,7 @@ impl StreamErrorEvent { }), EventSourceError::Transport(error) => Ok(StreamErrorEvent { event_id: Uuid::new_v4().to_string(), - r#type: error_type, + r#type: Self::ERROR_TYPE.to_string(), error: StreamError { r#type: "transport_error".to_string(), code: None, @@ -150,7 +152,7 @@ impl StreamErrorEvent { }), EventSourceError::InvalidLastEventId(message) => Ok(StreamErrorEvent { event_id: Uuid::new_v4().to_string(), - r#type: error_type, + r#type: Self::ERROR_TYPE.to_string(), error: StreamError { r#type: "invalid_last_event_id".to_string(), code: None, @@ -161,7 +163,7 @@ impl StreamErrorEvent { }), EventSourceError::StreamEnded => Ok(StreamErrorEvent { event_id: Uuid::new_v4().to_string(), - r#type: error_type, + r#type: Self::ERROR_TYPE.to_string(), error: StreamError { r#type: "stream_ended".to_string(), code: None, @@ -173,7 +175,7 @@ impl StreamErrorEvent { }, OpenAIError::InvalidArgument(message) => Ok(StreamErrorEvent { event_id: Uuid::new_v4().to_string(), - r#type: error_type, + r#type: Self::ERROR_TYPE.to_string(), error: StreamError { r#type: "invalid_argument".to_string(), code: None, @@ -184,4 +186,19 @@ impl StreamErrorEvent { }), } } + + pub fn from_response_error(error: ResponseError) -> Self { + let ResponseError { code, message, .. } = error; + StreamErrorEvent { + event_id: Uuid::new_v4().to_string(), + r#type: Self::ERROR_TYPE.to_string(), + error: StreamError { + r#type: "response_error".to_string(), + code: Some(code.as_str().to_string()), + message, + param: None, + event_id: None, + }, + } + } } diff --git a/crates/meilisearch/src/routes/chats/utils.rs b/crates/meilisearch/src/routes/chats/utils.rs index 752eb10e6..61961bd4b 100644 --- a/crates/meilisearch/src/routes/chats/utils.rs +++ b/crates/meilisearch/src/routes/chats/utils.rs @@ -9,7 +9,7 @@ use async_openai::types::{ FunctionCall, FunctionCallStream, Role, }; use bumpalo::Bump; -use meilisearch_types::error::ResponseError; +use meilisearch_types::error::{Code, ResponseError}; use meilisearch_types::heed::RoTxn; use meilisearch_types::milli::index::ChatConfig; use meilisearch_types::milli::prompt::{Prompt, PromptData}; @@ -237,7 +237,15 @@ pub fn format_documents<'doc>( Some(doc) => doc, None => unreachable!("Document with internal ID {docid} not found"), }; - let text = prompt.render_document(&external_docid, document, &gfid_map, doc_alloc).unwrap(); + let text = match prompt.render_document(&external_docid, document, &gfid_map, doc_alloc) { + Ok(text) => text, + Err(err) => { + return Err(ResponseError::from_msg( + err.to_string(), + Code::InvalidChatSettingDocumentTemplate, + )) + } + }; renders.push(text); } From 7533a11143749e2f4d40117c58cd5b93b8f3df11 Mon Sep 17 00:00:00 2001 From: Louis Dureuil Date: Wed, 11 Jun 2025 10:49:21 +0200 Subject: [PATCH 103/103] Make sure to send the tool response before the error message --- .../meilisearch/src/routes/chats/chat_completions.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/crates/meilisearch/src/routes/chats/chat_completions.rs b/crates/meilisearch/src/routes/chats/chat_completions.rs index 60f28d638..7431609e6 100644 --- a/crates/meilisearch/src/routes/chats/chat_completions.rs +++ b/crates/meilisearch/src/routes/chats/chat_completions.rs @@ -645,6 +645,8 @@ async fn handle_meili_tools( .await?; } + let mut error = None; + let result = match serde_json::from_str(&call.function.arguments) { Ok(SearchInIndexParameters { index_uid, q }) => match process_search_request( index_scheduler, @@ -658,8 +660,8 @@ async fn handle_meili_tools( { Ok(output) => Ok(output), Err(err) => { - let error_text = err.to_string(); - tx.send_error(&StreamErrorEvent::from_response_error(err)).await?; + let error_text = format!("the search tool call failed with {err}"); + error = Some(err); Err(error_text) } }, @@ -686,6 +688,10 @@ async fn handle_meili_tools( } chat_completion.messages.push(tool); + + if let Some(error) = error { + tx.send_error(&StreamErrorEvent::from_response_error(error)).await?; + } } Ok(())