mirror of
				https://github.com/meilisearch/meilisearch.git
				synced 2025-10-31 16:06:31 +00:00 
			
		
		
		
	Introduce listing/getting/deleting/updating chat workspace settings
This commit is contained in:
		
				
					committed by
					
						 Clément Renault
						Clément Renault
					
				
			
			
				
	
			
			
			
						parent
						
							50fafbbc8b
						
					
				
				
					commit
					0f7f5fa104
				
			| @@ -54,7 +54,7 @@ use meilisearch_types::batches::Batch; | |||||||
| use meilisearch_types::features::{InstanceTogglableFeatures, Network, RuntimeTogglableFeatures}; | use meilisearch_types::features::{InstanceTogglableFeatures, Network, RuntimeTogglableFeatures}; | ||||||
| use meilisearch_types::heed::byteorder::BE; | use meilisearch_types::heed::byteorder::BE; | ||||||
| use meilisearch_types::heed::types::{SerdeJson, Str, I128}; | 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::index::IndexEmbeddingConfig; | ||||||
| use meilisearch_types::milli::update::IndexerConfig; | use meilisearch_types::milli::update::IndexerConfig; | ||||||
| use meilisearch_types::milli::vector::{Embedder, EmbedderOptions, EmbeddingConfigs}; | use meilisearch_types::milli::vector::{Embedder, EmbedderOptions, EmbeddingConfigs}; | ||||||
| @@ -154,7 +154,7 @@ pub struct IndexScheduler { | |||||||
|     features: features::FeatureData, |     features: features::FeatureData, | ||||||
|  |  | ||||||
|     /// Stores the custom chat prompts and other settings of the indexes. |     /// Stores the custom chat prompts and other settings of the indexes. | ||||||
|     chat_settings: Database<Str, SerdeJson<serde_json::Value>>, |     pub(crate) chat_settings: Database<Str, SerdeJson<serde_json::Value>>, | ||||||
|  |  | ||||||
|     /// Everything related to the processing of the tasks |     /// Everything related to the processing of the tasks | ||||||
|     pub scheduler: scheduler::Scheduler, |     pub scheduler: scheduler::Scheduler, | ||||||
| @@ -308,6 +308,14 @@ impl IndexScheduler { | |||||||
|         Ok(this) |         Ok(this) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     pub fn write_txn(&self) -> Result<RwTxn> { | ||||||
|  |         self.env.write_txn().map_err(|e| e.into()) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     pub fn read_txn(&self) -> Result<RoTxn<WithoutTls>> { | ||||||
|  |         self.env.read_txn().map_err(|e| e.into()) | ||||||
|  |     } | ||||||
|  |  | ||||||
|     /// Return `Ok(())` if the index scheduler is able to access one of its database. |     /// Return `Ok(())` if the index scheduler is able to access one of its database. | ||||||
|     pub fn health(&self) -> Result<()> { |     pub fn health(&self) -> Result<()> { | ||||||
|         let rtxn = self.env.read_txn()?; |         let rtxn = self.env.read_txn()?; | ||||||
| @@ -384,10 +392,6 @@ impl IndexScheduler { | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     pub fn read_txn(&self) -> Result<RoTxn<WithoutTls>> { |  | ||||||
|         self.env.read_txn().map_err(|e| e.into()) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     /// Start the run loop for the given index scheduler. |     /// Start the run loop for the given index scheduler. | ||||||
|     /// |     /// | ||||||
|     /// This function will execute in a different thread and must be called |     /// 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. |     /// Returns the total number of indexes available for the specified filter. | ||||||
|     /// And a `Vec` of the index_uid + its stats |     /// And a `Vec` of the index_uid + its stats | ||||||
|     pub fn get_paginated_indexes_stats( |     pub fn paginated_indexes_stats( | ||||||
|         &self, |         &self, | ||||||
|         filters: &meilisearch_auth::AuthFilter, |         filters: &meilisearch_auth::AuthFilter, | ||||||
|         from: usize, |         from: usize, | ||||||
| @@ -546,6 +550,25 @@ impl IndexScheduler { | |||||||
|         ret.map(|ret| (total, ret)) |         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<String>)> { | ||||||
|  |         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::<Result<Vec<_>, Error>>() | ||||||
|  |             .map(|ret| (total as usize, ret)) | ||||||
|  |     } | ||||||
|  |  | ||||||
|     /// The returned structure contains: |     /// The returned structure contains: | ||||||
|     /// 1. The name of the property being observed can be `statuses`, `types`, or `indexes`. |     /// 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. |     /// 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) |         res.map(EmbeddingConfigs::new) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     pub fn chat_settings(&self) -> Result<Option<serde_json::Value>> { |     pub fn chat_settings(&self, rtxn: &RoTxn, uid: &str) -> Result<Option<serde_json::Value>> { | ||||||
|         let rtxn = self.env.read_txn().map_err(Error::HeedTransaction)?; |         self.chat_settings.get(rtxn, uid).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<()> { |     pub fn put_chat_settings( | ||||||
|         let mut wtxn = self.env.write_txn().map_err(Error::HeedTransaction)?; |         &self, | ||||||
|         self.chat_settings.put(&mut wtxn, "main", settings)?; |         wtxn: &mut RwTxn, | ||||||
|         wtxn.commit().map_err(Error::HeedTransaction)?; |         uid: &str, | ||||||
|         Ok(()) |         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<bool> { | ||||||
|  |         self.chat_settings.delete(wtxn, uid).map_err(Into::into) | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -894,7 +894,7 @@ fn create_and_list_index() { | |||||||
|  |  | ||||||
|     let err = index_scheduler.index("kefir").map(|_| ()).unwrap_err(); |     let err = index_scheduler.index("kefir").map(|_| ()).unwrap_err(); | ||||||
|     snapshot!(err, @"Index `kefir` not found."); |     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, [])"); |     snapshot!(format!("{empty:?}"), @"(0, [])"); | ||||||
|  |  | ||||||
|     // After advancing just once the index should've been created, the wtxn has been released and commited |     // 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]); |     handle.advance_till([InsideProcessBatch]); | ||||||
|  |  | ||||||
|     index_scheduler.index("kefir").unwrap(); |     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###" |     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, |       1, | ||||||
|   | |||||||
| @@ -323,18 +323,25 @@ pub enum Action { | |||||||
|     #[serde(rename = "network.update")] |     #[serde(rename = "network.update")] | ||||||
|     #[deserr(rename = "network.update")] |     #[deserr(rename = "network.update")] | ||||||
|     NetworkUpdate, |     NetworkUpdate, | ||||||
|  |     // TODO should we rename it chatCompletions.get ? | ||||||
|     #[serde(rename = "chat.get")] |     #[serde(rename = "chat.get")] | ||||||
|     #[deserr(rename = "chat.get")] |     #[deserr(rename = "chat.get")] | ||||||
|     Chat, |     Chat, | ||||||
|     #[serde(rename = "chatSettings.*")] |     #[serde(rename = "chats.get")] | ||||||
|     #[deserr(rename = "chatSettings.*")] |     #[deserr(rename = "chats.get")] | ||||||
|     ChatSettingsAll, |     Chats, | ||||||
|     #[serde(rename = "chatSettings.get")] |     #[serde(rename = "chatsSettings.*")] | ||||||
|     #[deserr(rename = "chatSettings.get")] |     #[deserr(rename = "chatsSettings.*")] | ||||||
|     ChatSettingsGet, |     ChatsSettingsAll, | ||||||
|     #[serde(rename = "chatSettings.update")] |     #[serde(rename = "chatsSettings.get")] | ||||||
|     #[deserr(rename = "chatSettings.update")] |     #[deserr(rename = "chatsSettings.get")] | ||||||
|     ChatSettingsUpdate, |     ChatsSettingsGet, | ||||||
|  |     #[serde(rename = "chatsSettings.update")] | ||||||
|  |     #[deserr(rename = "chatsSettings.update")] | ||||||
|  |     ChatsSettingsUpdate, | ||||||
|  |     #[serde(rename = "chatsSettings.delete")] | ||||||
|  |     #[deserr(rename = "chatsSettings.delete")] | ||||||
|  |     ChatsSettingsDelete, | ||||||
| } | } | ||||||
|  |  | ||||||
| impl Action { | impl Action { | ||||||
| @@ -360,9 +367,12 @@ impl Action { | |||||||
|             SETTINGS_ALL => Some(Self::SettingsAll), |             SETTINGS_ALL => Some(Self::SettingsAll), | ||||||
|             SETTINGS_GET => Some(Self::SettingsGet), |             SETTINGS_GET => Some(Self::SettingsGet), | ||||||
|             SETTINGS_UPDATE => Some(Self::SettingsUpdate), |             SETTINGS_UPDATE => Some(Self::SettingsUpdate), | ||||||
|             CHAT_SETTINGS_ALL => Some(Self::ChatSettingsAll), |             CHAT => Some(Self::Chat), | ||||||
|             CHAT_SETTINGS_GET => Some(Self::ChatSettingsGet), |             CHATS_GET => Some(Self::Chats), | ||||||
|             CHAT_SETTINGS_UPDATE => Some(Self::ChatSettingsUpdate), |             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_ALL => Some(Self::StatsAll), | ||||||
|             STATS_GET => Some(Self::StatsGet), |             STATS_GET => Some(Self::StatsGet), | ||||||
|             METRICS_ALL => Some(Self::MetricsAll), |             METRICS_ALL => Some(Self::MetricsAll), | ||||||
| @@ -379,7 +389,6 @@ impl Action { | |||||||
|             EXPERIMENTAL_FEATURES_UPDATE => Some(Self::ExperimentalFeaturesUpdate), |             EXPERIMENTAL_FEATURES_UPDATE => Some(Self::ExperimentalFeaturesUpdate), | ||||||
|             NETWORK_GET => Some(Self::NetworkGet), |             NETWORK_GET => Some(Self::NetworkGet), | ||||||
|             NETWORK_UPDATE => Some(Self::NetworkUpdate), |             NETWORK_UPDATE => Some(Self::NetworkUpdate), | ||||||
|             CHAT => Some(Self::Chat), |  | ||||||
|             _otherwise => None, |             _otherwise => None, | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| @@ -430,7 +439,9 @@ pub mod actions { | |||||||
|     pub const NETWORK_UPDATE: u8 = NetworkUpdate.repr(); |     pub const NETWORK_UPDATE: u8 = NetworkUpdate.repr(); | ||||||
|  |  | ||||||
|     pub const CHAT: u8 = Chat.repr(); |     pub const CHAT: u8 = Chat.repr(); | ||||||
|     pub const CHAT_SETTINGS_ALL: u8 = ChatSettingsAll.repr(); |     pub const CHATS_GET: u8 = Chats.repr(); | ||||||
|     pub const CHAT_SETTINGS_GET: u8 = ChatSettingsGet.repr(); |     pub const CHATS_SETTINGS_ALL: u8 = ChatsSettingsAll.repr(); | ||||||
|     pub const CHAT_SETTINGS_UPDATE: u8 = ChatSettingsUpdate.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(); | ||||||
| } | } | ||||||
|   | |||||||
| @@ -44,7 +44,8 @@ use tokio::runtime::Handle; | |||||||
| use tokio::sync::mpsc::error::SendError; | use tokio::sync::mpsc::error::SendError; | ||||||
| use tokio::sync::mpsc::Sender; | 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::error::MeilisearchHttpError; | ||||||
| use crate::extractors::authentication::policies::ActionPolicy; | use crate::extractors::authentication::policies::ActionPolicy; | ||||||
| use crate::extractors::authentication::{extract_token_from_request, GuardedData, Policy as _}; | 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"; | const MEILI_SEARCH_IN_INDEX_FUNCTION_NAME: &str = "_meiliSearchInIndex"; | ||||||
| 
 | 
 | ||||||
| pub fn configure(cfg: &mut web::ServiceConfig) { | 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
 | /// Get a chat completion
 | ||||||
| async fn chat( | async fn chat( | ||||||
|     index_scheduler: GuardedData<ActionPolicy<{ actions::CHAT }>, Data<IndexScheduler>>, |     index_scheduler: GuardedData<ActionPolicy<{ actions::CHAT }>, Data<IndexScheduler>>, | ||||||
|     auth_ctrl: web::Data<AuthController>, |     auth_ctrl: web::Data<AuthController>, | ||||||
|  |     chats_param: web::Path<ChatsParam>, | ||||||
|     req: HttpRequest, |     req: HttpRequest, | ||||||
|     search_queue: web::Data<SearchQueue>, |     search_queue: web::Data<SearchQueue>, | ||||||
|     web::Json(chat_completion): web::Json<CreateChatCompletionRequest>, |     web::Json(chat_completion): web::Json<CreateChatCompletionRequest>, | ||||||
| @@ -74,6 +76,8 @@ async fn chat( | |||||||
|     // To enable later on, when the feature will be experimental
 |     // To enable later on, when the feature will be experimental
 | ||||||
|     // index_scheduler.features().check_chat("Using the /chat route")?;
 |     // index_scheduler.features().check_chat("Using the /chat route")?;
 | ||||||
| 
 | 
 | ||||||
|  |     let ChatsParam { workspace_uid } = chats_param.into_inner(); | ||||||
|  | 
 | ||||||
|     assert_eq!( |     assert_eq!( | ||||||
|         chat_completion.n.unwrap_or(1), |         chat_completion.n.unwrap_or(1), | ||||||
|         1, |         1, | ||||||
| @@ -82,11 +86,27 @@ async fn chat( | |||||||
| 
 | 
 | ||||||
|     if chat_completion.stream.unwrap_or(false) { |     if chat_completion.stream.unwrap_or(false) { | ||||||
|         Either::Right( |         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 { |     } else { | ||||||
|         Either::Left( |         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<ActionPolicy<{ actions::CHAT }>, Data<IndexScheduler>>, |     index_scheduler: GuardedData<ActionPolicy<{ actions::CHAT }>, Data<IndexScheduler>>, | ||||||
|     auth_ctrl: web::Data<AuthController>, |     auth_ctrl: web::Data<AuthController>, | ||||||
|     search_queue: web::Data<SearchQueue>, |     search_queue: web::Data<SearchQueue>, | ||||||
|  |     workspace_uid: &str, | ||||||
|     req: HttpRequest, |     req: HttpRequest, | ||||||
|     mut chat_completion: CreateChatCompletionRequest, |     mut chat_completion: CreateChatCompletionRequest, | ||||||
| ) -> Result<HttpResponse, ResponseError> { | ) -> Result<HttpResponse, ResponseError> { | ||||||
|     let filters = index_scheduler.filters(); |     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(), |         Some(value) => serde_json::from_value(value).unwrap(), | ||||||
|         None => GlobalChatSettings::default(), |         None => GlobalChatSettings::default(), | ||||||
|     }; |     }; | ||||||
| @@ -387,15 +409,18 @@ async fn streamed_chat( | |||||||
|     index_scheduler: GuardedData<ActionPolicy<{ actions::CHAT }>, Data<IndexScheduler>>, |     index_scheduler: GuardedData<ActionPolicy<{ actions::CHAT }>, Data<IndexScheduler>>, | ||||||
|     auth_ctrl: web::Data<AuthController>, |     auth_ctrl: web::Data<AuthController>, | ||||||
|     search_queue: web::Data<SearchQueue>, |     search_queue: web::Data<SearchQueue>, | ||||||
|  |     workspace_uid: &str, | ||||||
|     req: HttpRequest, |     req: HttpRequest, | ||||||
|     mut chat_completion: CreateChatCompletionRequest, |     mut chat_completion: CreateChatCompletionRequest, | ||||||
| ) -> Result<impl Responder, ResponseError> { | ) -> Result<impl Responder, ResponseError> { | ||||||
|     let filters = index_scheduler.filters(); |     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(), |         Some(value) => serde_json::from_value(value.clone()).unwrap(), | ||||||
|         None => GlobalChatSettings::default(), |         None => GlobalChatSettings::default(), | ||||||
|     }; |     }; | ||||||
|  |     drop(rtxn); | ||||||
| 
 | 
 | ||||||
|     let mut config = OpenAIConfig::default(); |     let mut config = OpenAIConfig::default(); | ||||||
|     if let Setting::Set(api_key) = chat_settings.api_key.as_ref() { |     if let Setting::Set(api_key) = chat_settings.api_key.as_ref() { | ||||||
| @@ -662,6 +687,7 @@ impl SseEventSender { | |||||||
|         function_name: String, |         function_name: String, | ||||||
|         function_arguments: String, |         function_arguments: String, | ||||||
|     ) -> Result<(), SendError<Event>> { |     ) -> Result<(), SendError<Event>> { | ||||||
|  |         #[allow(deprecated)] | ||||||
|         let message = |         let message = | ||||||
|             ChatCompletionRequestMessage::Assistant(ChatCompletionRequestAssistantMessage { |             ChatCompletionRequestMessage::Assistant(ChatCompletionRequestAssistantMessage { | ||||||
|                 content: None, |                 content: None, | ||||||
| @@ -698,6 +724,7 @@ impl SseEventSender { | |||||||
| 
 | 
 | ||||||
|         resp.choices[0] = ChatChoiceStream { |         resp.choices[0] = ChatChoiceStream { | ||||||
|             index: 0, |             index: 0, | ||||||
|  |             #[allow(deprecated)] | ||||||
|             delta: ChatCompletionStreamResponseDelta { |             delta: ChatCompletionStreamResponseDelta { | ||||||
|                 content: None, |                 content: None, | ||||||
|                 function_call: None, |                 function_call: None, | ||||||
| @@ -744,6 +771,7 @@ impl SseEventSender { | |||||||
| 
 | 
 | ||||||
|         resp.choices[0] = ChatChoiceStream { |         resp.choices[0] = ChatChoiceStream { | ||||||
|             index: 0, |             index: 0, | ||||||
|  |             #[allow(deprecated)] | ||||||
|             delta: ChatCompletionStreamResponseDelta { |             delta: ChatCompletionStreamResponseDelta { | ||||||
|                 content: None, |                 content: None, | ||||||
|                 function_call: None, |                 function_call: None, | ||||||
| @@ -788,6 +816,7 @@ impl SseEventSender { | |||||||
| 
 | 
 | ||||||
|         resp.choices[0] = ChatChoiceStream { |         resp.choices[0] = ChatChoiceStream { | ||||||
|             index: 0, |             index: 0, | ||||||
|  |             #[allow(deprecated)] | ||||||
|             delta: ChatCompletionStreamResponseDelta { |             delta: ChatCompletionStreamResponseDelta { | ||||||
|                 content: None, |                 content: None, | ||||||
|                 function_call: None, |                 function_call: None, | ||||||
							
								
								
									
										87
									
								
								crates/meilisearch/src/routes/chats/mod.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										87
									
								
								crates/meilisearch/src/routes/chats/mod.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -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<usize>, default, example = 100)] | ||||||
|  |     #[deserr(default, error = DeserrQueryParamError<InvalidIndexOffset>)] | ||||||
|  |     pub offset: Param<usize>, | ||||||
|  |     /// The number of chat workspaces to retrieve | ||||||
|  |     #[param(value_type = Option<usize>, default = 20, example = 1)] | ||||||
|  |     #[deserr(default = Param(PAGINATION_DEFAULT_LIMIT), error = DeserrQueryParamError<InvalidIndexLimit>)] | ||||||
|  |     pub limit: Param<usize>, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | 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<ActionPolicy<{ actions::CHATS_GET }>, Data<IndexScheduler>>, | ||||||
|  |     paginate: AwebQueryParameter<ListChats, DeserrQueryParamError>, | ||||||
|  | ) -> Result<HttpResponse, ResponseError> { | ||||||
|  |     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::<Vec<_>>(); | ||||||
|  |     let ret = paginate.as_pagination().format_with(total, workspaces); | ||||||
|  |  | ||||||
|  |     debug!(returns = ?ret, "List chat workspaces"); | ||||||
|  |     Ok(HttpResponse::Ok().json(ret)) | ||||||
|  | } | ||||||
| @@ -10,21 +10,29 @@ use crate::extractors::authentication::policies::ActionPolicy; | |||||||
| use crate::extractors::authentication::GuardedData; | use crate::extractors::authentication::GuardedData; | ||||||
| use crate::extractors::sequential_extractor::SeqHandler; | use crate::extractors::sequential_extractor::SeqHandler; | ||||||
| 
 | 
 | ||||||
|  | use super::ChatsParam; | ||||||
|  | 
 | ||||||
| pub fn configure(cfg: &mut web::ServiceConfig) { | pub fn configure(cfg: &mut web::ServiceConfig) { | ||||||
|     cfg.service( |     cfg.service( | ||||||
|         web::resource("") |         web::resource("") | ||||||
|             .route(web::get().to(get_settings)) |             .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( | async fn get_settings( | ||||||
|     index_scheduler: GuardedData< |     index_scheduler: GuardedData< | ||||||
|         ActionPolicy<{ actions::CHAT_SETTINGS_GET }>, |         ActionPolicy<{ actions::CHATS_SETTINGS_GET }>, | ||||||
|         Data<IndexScheduler>, |         Data<IndexScheduler>, | ||||||
|     >, |     >, | ||||||
|  |     chats_param: web::Path<ChatsParam>, | ||||||
| ) -> Result<HttpResponse, ResponseError> { | ) -> Result<HttpResponse, ResponseError> { | ||||||
|     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(), |         Some(value) => serde_json::from_value(value).unwrap(), | ||||||
|         None => GlobalChatSettings::default(), |         None => GlobalChatSettings::default(), | ||||||
|     }; |     }; | ||||||
| @@ -34,12 +42,17 @@ async fn get_settings( | |||||||
| 
 | 
 | ||||||
| async fn patch_settings( | async fn patch_settings( | ||||||
|     index_scheduler: GuardedData< |     index_scheduler: GuardedData< | ||||||
|         ActionPolicy<{ actions::CHAT_SETTINGS_UPDATE }>, |         ActionPolicy<{ actions::CHATS_SETTINGS_UPDATE }>, | ||||||
|         Data<IndexScheduler>, |         Data<IndexScheduler>, | ||||||
|     >, |     >, | ||||||
|  |     chats_param: web::Path<ChatsParam>, | ||||||
|     web::Json(new): web::Json<GlobalChatSettings>, |     web::Json(new): web::Json<GlobalChatSettings>, | ||||||
| ) -> Result<HttpResponse, ResponseError> { | ) -> Result<HttpResponse, ResponseError> { | ||||||
|     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(), |         Some(value) => serde_json::from_value(value).unwrap(), | ||||||
|         None => GlobalChatSettings::default(), |         None => GlobalChatSettings::default(), | ||||||
|     }; |     }; | ||||||
| @@ -64,16 +77,39 @@ async fn patch_settings( | |||||||
|     }; |     }; | ||||||
| 
 | 
 | ||||||
|     let value = serde_json::to_value(settings).unwrap(); |     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()) |     Ok(HttpResponse::Ok().finish()) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | async fn delete_settings( | ||||||
|  |     index_scheduler: GuardedData< | ||||||
|  |         ActionPolicy<{ actions::CHATS_SETTINGS_DELETE }>, | ||||||
|  |         Data<IndexScheduler>, | ||||||
|  |     >, | ||||||
|  |     chats_param: web::Path<ChatsParam>, | ||||||
|  | ) -> Result<HttpResponse, ResponseError> { | ||||||
|  |     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)] | #[derive(Debug, Clone, Serialize, Deserialize)] | ||||||
| #[serde(deny_unknown_fields, rename_all = "camelCase")] | #[serde(deny_unknown_fields, rename_all = "camelCase")] | ||||||
| pub enum ChatSource { | pub enum ChatSource { | ||||||
|     OpenAi, |     OpenAi, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // TODO Implement Deserr on that.
 | ||||||
|  | // TODO Declare DbGlobalChatSettings (alias it).
 | ||||||
| #[derive(Debug, Clone, Serialize, Deserialize)] | #[derive(Debug, Clone, Serialize, Deserialize)] | ||||||
| #[serde(deny_unknown_fields, rename_all = "camelCase")] | #[serde(deny_unknown_fields, rename_all = "camelCase")] | ||||||
| pub struct GlobalChatSettings { | pub struct GlobalChatSettings { | ||||||
| @@ -114,6 +150,8 @@ impl GlobalChatSettings { | |||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // TODO Implement Deserr on that.
 | ||||||
|  | // TODO Declare DbChatPrompts (alias it).
 | ||||||
| #[derive(Debug, Clone, Serialize, Deserialize)] | #[derive(Debug, Clone, Serialize, Deserialize)] | ||||||
| #[serde(deny_unknown_fields, rename_all = "camelCase")] | #[serde(deny_unknown_fields, rename_all = "camelCase")] | ||||||
| pub struct ChatPrompts { | pub struct ChatPrompts { | ||||||
| @@ -172,7 +172,7 @@ pub async fn list_indexes( | |||||||
|     debug!(parameters = ?paginate, "List indexes"); |     debug!(parameters = ?paginate, "List indexes"); | ||||||
|     let filters = index_scheduler.filters(); |     let filters = index_scheduler.filters(); | ||||||
|     let (total, indexes) = |     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 |     let indexes = indexes | ||||||
|         .into_iter() |         .into_iter() | ||||||
|         .map(|(name, stats)| IndexView { |         .map(|(name, stats)| IndexView { | ||||||
|   | |||||||
| @@ -52,7 +52,7 @@ const PAGINATION_DEFAULT_LIMIT_FN: fn() -> usize = || 20; | |||||||
|  |  | ||||||
| mod api_key; | mod api_key; | ||||||
| pub mod batches; | pub mod batches; | ||||||
| pub mod chat; | pub mod chats; | ||||||
| mod dump; | mod dump; | ||||||
| pub mod features; | pub mod features; | ||||||
| pub mod indexes; | pub mod indexes; | ||||||
| @@ -62,7 +62,6 @@ mod multi_search; | |||||||
| mod multi_search_analytics; | mod multi_search_analytics; | ||||||
| pub mod network; | pub mod network; | ||||||
| mod open_api_utils; | mod open_api_utils; | ||||||
| pub mod settings; |  | ||||||
| mod snapshot; | mod snapshot; | ||||||
| mod swap_indexes; | mod swap_indexes; | ||||||
| pub mod tasks; | pub mod tasks; | ||||||
| @@ -116,8 +115,7 @@ pub fn configure(cfg: &mut web::ServiceConfig) { | |||||||
|         .service(web::scope("/metrics").configure(metrics::configure)) |         .service(web::scope("/metrics").configure(metrics::configure)) | ||||||
|         .service(web::scope("/experimental-features").configure(features::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)) |         .service(web::scope("/chats").configure(chats::settings::configure)); | ||||||
|         .service(web::scope("/settings/chat").configure(settings::chat::configure)); |  | ||||||
|  |  | ||||||
|     #[cfg(feature = "swagger")] |     #[cfg(feature = "swagger")] | ||||||
|     { |     { | ||||||
|   | |||||||
| @@ -1 +0,0 @@ | |||||||
| pub mod chat; |  | ||||||
		Reference in New Issue
	
	Block a user