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, 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; 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; pub fn configure(cfg: &mut web::ServiceConfig) { cfg.service( web::resource("") .route(web::get().to(SeqHandler(get_settings))) .route(web::patch().to(SeqHandler(patch_settings))) .route(web::delete().to(SeqHandler(reset_settings))), ); } async fn get_settings( index_scheduler: GuardedData< ActionPolicy<{ actions::CHATS_SETTINGS_GET }>, Data, >, chats_param: web::Path, ) -> Result { index_scheduler.features().check_chat_completions("using the /chats/settings route")?; let ChatsParam { workspace_uid } = chats_param.into_inner(); let mut settings = match index_scheduler.chat_settings(&workspace_uid)? { Some(settings) => settings, None => { return Err(ResponseError::from_msg( format!("Chat `{workspace_uid}` not found"), Code::ChatNotFound, )) } }; settings.hide_secrets(); Ok(HttpResponse::Ok().json(settings)) } async fn patch_settings( index_scheduler: GuardedData< ActionPolicy<{ actions::CHATS_SETTINGS_UPDATE }>, Data, >, 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(); let old_settings = index_scheduler.chat_settings(&workspace_uid)?.unwrap_or_default(); 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, }, }, Setting::Reset => DbChatCompletionPrompts::default(), Setting::NotSet => old_settings.prompts, }; let mut settings = ChatCompletionSettings { source: match new.source { Setting::Set(new_source) => new_source.into(), 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_url: match new.base_url { Setting::Set(new_base_url) => Some(new_base_url), Setting::Reset => None, Setting::NotSet => old_settings.base_url, }, 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, // ); settings.validate()?; index_scheduler.put_chat_settings(&workspace_uid, &settings)?; settings.hide_secrets(); Ok(HttpResponse::Ok().json(settings)) } async fn reset_settings( index_scheduler: GuardedData< ActionPolicy<{ actions::CHATS_SETTINGS_UPDATE }>, Data, >, chats_param: web::Path, ) -> Result { index_scheduler.features().check_chat_completions("using the /chats/settings route")?; let ChatsParam { workspace_uid } = chats_param.into_inner(); if index_scheduler.chat_settings(&workspace_uid)?.is_some() { let settings = Default::default(); index_scheduler.put_chat_settings(&workspace_uid, &settings)?; Ok(HttpResponse::Ok().json(settings)) } else { Err(ResponseError::from_msg( format!("Chat `{workspace_uid}` not found"), Code::ChatNotFound, )) } } #[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 ChatWorkspaceSettings { #[serde(default)] #[deserr(default)] #[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_url: 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, Mistral, Gemini, AzureOpenAi, VLlm, } impl From for DbChatCompletionSource { fn from(source: ChatCompletionSource) -> Self { use ChatCompletionSource::*; match source { OpenAi => DbChatCompletionSource::OpenAi, Mistral => DbChatCompletionSource::Mistral, Gemini => DbChatCompletionSource::Gemini, AzureOpenAi => DbChatCompletionSource::AzureOpenAi, VLlm => DbChatCompletionSource::VLlm, } } } #[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)] #[deserr(default, error = DeserrJsonError)] #[schema(value_type = Option, example = json!("You are a helpful assistant..."))] pub system: Setting, #[serde(default)] #[deserr(default, error = DeserrJsonError)] #[schema(value_type = Option, example = json!("This is the search function..."))] pub search_description: Setting, #[serde(default)] #[deserr(default, error = DeserrJsonError)] #[schema(value_type = Option, example = json!("This is query parameter..."))] pub search_q_param: Setting, #[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, }