mirror of
https://github.com/meilisearch/meilisearch.git
synced 2025-06-06 04:05:37 +00:00
Better chat completions settings management
This commit is contained in:
parent
5bb0714c5f
commit
358abf662e
@ -51,7 +51,9 @@ pub use features::RoFeatures;
|
|||||||
use flate2::bufread::GzEncoder;
|
use flate2::bufread::GzEncoder;
|
||||||
use flate2::Compression;
|
use flate2::Compression;
|
||||||
use meilisearch_types::batches::Batch;
|
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::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, RwTxn, WithoutTls};
|
use meilisearch_types::heed::{self, Database, Env, RoTxn, RwTxn, WithoutTls};
|
||||||
@ -154,7 +156,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.
|
||||||
pub(crate) chat_settings: Database<Str, SerdeJson<serde_json::Value>>,
|
pub(crate) chat_settings: Database<Str, SerdeJson<ChatCompletionSettings>>,
|
||||||
|
|
||||||
/// Everything related to the processing of the tasks
|
/// Everything related to the processing of the tasks
|
||||||
pub scheduler: scheduler::Scheduler,
|
pub scheduler: scheduler::Scheduler,
|
||||||
@ -898,7 +900,7 @@ impl IndexScheduler {
|
|||||||
res.map(EmbeddingConfigs::new)
|
res.map(EmbeddingConfigs::new)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn chat_settings(&self, rtxn: &RoTxn, uid: &str) -> Result<Option<serde_json::Value>> {
|
pub fn chat_settings(&self, rtxn: &RoTxn, uid: &str) -> Result<Option<ChatCompletionSettings>> {
|
||||||
self.chat_settings.get(rtxn, uid).map_err(Into::into)
|
self.chat_settings.get(rtxn, uid).map_err(Into::into)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -906,7 +908,7 @@ impl IndexScheduler {
|
|||||||
&self,
|
&self,
|
||||||
wtxn: &mut RwTxn,
|
wtxn: &mut RwTxn,
|
||||||
uid: &str,
|
uid: &str,
|
||||||
settings: &serde_json::Value,
|
settings: &ChatCompletionSettings,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
self.chat_settings.put(wtxn, uid, settings).map_err(Into::into)
|
self.chat_settings.put(wtxn, uid, settings).map_err(Into::into)
|
||||||
}
|
}
|
||||||
|
@ -217,6 +217,7 @@ ImmutableIndexUpdatedAt , InvalidRequest , BAD_REQUEST;
|
|||||||
IndexAlreadyExists , InvalidRequest , CONFLICT ;
|
IndexAlreadyExists , InvalidRequest , CONFLICT ;
|
||||||
IndexCreationFailed , Internal , INTERNAL_SERVER_ERROR;
|
IndexCreationFailed , Internal , INTERNAL_SERVER_ERROR;
|
||||||
IndexNotFound , InvalidRequest , NOT_FOUND;
|
IndexNotFound , InvalidRequest , NOT_FOUND;
|
||||||
|
ChatWorkspaceNotFound , InvalidRequest , NOT_FOUND;
|
||||||
IndexPrimaryKeyAlreadyExists , InvalidRequest , BAD_REQUEST ;
|
IndexPrimaryKeyAlreadyExists , InvalidRequest , BAD_REQUEST ;
|
||||||
IndexPrimaryKeyMultipleCandidatesFound, InvalidRequest , BAD_REQUEST;
|
IndexPrimaryKeyMultipleCandidatesFound, InvalidRequest , BAD_REQUEST;
|
||||||
IndexPrimaryKeyNoCandidateFound , InvalidRequest , BAD_REQUEST ;
|
IndexPrimaryKeyNoCandidateFound , InvalidRequest , BAD_REQUEST ;
|
||||||
|
@ -2,6 +2,13 @@ use std::collections::BTreeMap;
|
|||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
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)]
|
#[derive(Serialize, Deserialize, Debug, Clone, Copy, Default, PartialEq, Eq)]
|
||||||
#[serde(rename_all = "camelCase", default)]
|
#[serde(rename_all = "camelCase", default)]
|
||||||
pub struct RuntimeTogglableFeatures {
|
pub struct RuntimeTogglableFeatures {
|
||||||
@ -37,3 +44,69 @@ pub struct Network {
|
|||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub remotes: BTreeMap<String, Remote>,
|
pub remotes: BTreeMap<String, Remote>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Default)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct ChatCompletionSettings {
|
||||||
|
pub source: ChatCompletionSource,
|
||||||
|
#[serde(default)]
|
||||||
|
pub base_api: Option<String>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub api_key: Option<String>,
|
||||||
|
#[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(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -26,13 +26,15 @@ use bumpalo::Bump;
|
|||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
use index_scheduler::IndexScheduler;
|
use index_scheduler::IndexScheduler;
|
||||||
use meilisearch_auth::AuthController;
|
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::heed::RoTxn;
|
||||||
use meilisearch_types::keys::actions;
|
use meilisearch_types::keys::actions;
|
||||||
use meilisearch_types::milli::index::ChatConfig;
|
use meilisearch_types::milli::index::ChatConfig;
|
||||||
use meilisearch_types::milli::prompt::{Prompt, PromptData};
|
use meilisearch_types::milli::prompt::{Prompt, PromptData};
|
||||||
use meilisearch_types::milli::update::new::document::DocumentFromDb;
|
use meilisearch_types::milli::update::new::document::DocumentFromDb;
|
||||||
use meilisearch_types::milli::update::Setting;
|
|
||||||
use meilisearch_types::milli::{
|
use meilisearch_types::milli::{
|
||||||
all_obkv_to_json, obkv_to_json, DocumentId, FieldIdMapWithMetadata, GlobalFieldsIdsMap,
|
all_obkv_to_json, obkv_to_json, DocumentId, FieldIdMapWithMetadata, GlobalFieldsIdsMap,
|
||||||
MetadataBuilder, TimeBudget,
|
MetadataBuilder, TimeBudget,
|
||||||
@ -44,7 +46,6 @@ 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::{ChatPrompts, GlobalChatSettings};
|
|
||||||
use super::ChatsParam;
|
use super::ChatsParam;
|
||||||
use crate::error::MeilisearchHttpError;
|
use crate::error::MeilisearchHttpError;
|
||||||
use crate::extractors::authentication::policies::ActionPolicy;
|
use crate::extractors::authentication::policies::ActionPolicy;
|
||||||
@ -132,7 +133,7 @@ fn setup_search_tool(
|
|||||||
index_scheduler: &Data<IndexScheduler>,
|
index_scheduler: &Data<IndexScheduler>,
|
||||||
filters: &meilisearch_auth::AuthFilter,
|
filters: &meilisearch_auth::AuthFilter,
|
||||||
chat_completion: &mut CreateChatCompletionRequest,
|
chat_completion: &mut CreateChatCompletionRequest,
|
||||||
prompts: &ChatPrompts,
|
prompts: &DbChatCompletionPrompts,
|
||||||
) -> Result<FunctionSupport, ResponseError> {
|
) -> Result<FunctionSupport, ResponseError> {
|
||||||
let tools = chat_completion.tools.get_or_insert_default();
|
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().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 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| {
|
index_scheduler.try_for_each_index::<_, ()>(|name, index| {
|
||||||
// Make sure to skip unauthorized indexes
|
// Make sure to skip unauthorized indexes
|
||||||
if !filters.is_index_authorized(&name) {
|
if !filters.is_index_authorized(&name) {
|
||||||
@ -195,13 +196,13 @@ fn setup_search_tool(
|
|||||||
"index_uid": {
|
"index_uid": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": index_uids,
|
"enum": index_uids,
|
||||||
"description": prompts.search_index_uid_param.clone().unwrap(),
|
"description": prompts.search_index_uid_param,
|
||||||
},
|
},
|
||||||
"q": {
|
"q": {
|
||||||
// Unfortunately, Mistral does not support an array of types, here.
|
// Unfortunately, Mistral does not support an array of types, here.
|
||||||
// "type": ["string", "null"],
|
// "type": ["string", "null"],
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": prompts.search_q_param.clone().unwrap(),
|
"description": prompts.search_q_param,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": ["index_uid", "q"],
|
"required": ["index_uid", "q"],
|
||||||
@ -219,9 +220,7 @@ fn setup_search_tool(
|
|||||||
chat_completion.messages.insert(
|
chat_completion.messages.insert(
|
||||||
0,
|
0,
|
||||||
ChatCompletionRequestMessage::System(ChatCompletionRequestSystemMessage {
|
ChatCompletionRequestMessage::System(ChatCompletionRequestSystemMessage {
|
||||||
content: ChatCompletionRequestSystemMessageContent::Text(
|
content: ChatCompletionRequestSystemMessageContent::Text(prompts.system.clone()),
|
||||||
prompts.system.as_ref().unwrap().clone(),
|
|
||||||
),
|
|
||||||
name: None,
|
name: None,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
@ -322,23 +321,27 @@ async fn non_streamed_chat(
|
|||||||
|
|
||||||
let rtxn = index_scheduler.read_txn()?;
|
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(&rtxn, workspace_uid).unwrap() {
|
||||||
Some(value) => serde_json::from_value(value).unwrap(),
|
Some(settings) => settings,
|
||||||
None => GlobalChatSettings::default(),
|
None => {
|
||||||
|
return Err(ResponseError::from_msg(
|
||||||
|
format!("Chat `{workspace_uid}` not found"),
|
||||||
|
Code::ChatWorkspaceNotFound,
|
||||||
|
))
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut config = OpenAIConfig::default();
|
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);
|
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);
|
config = config.with_api_base(base_api);
|
||||||
}
|
}
|
||||||
let client = Client::with_config(config);
|
let client = Client::with_config(config);
|
||||||
|
|
||||||
let auth_token = extract_token_from_request(&req)?.unwrap();
|
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 } =
|
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;
|
let mut response;
|
||||||
loop {
|
loop {
|
||||||
@ -381,13 +384,11 @@ async fn non_streamed_chat(
|
|||||||
Err(err) => err,
|
Err(err) => err,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let answer = format!("{}\n\n{text}", chat_settings.prompts.pre_query);
|
||||||
chat_completion.messages.push(ChatCompletionRequestMessage::Tool(
|
chat_completion.messages.push(ChatCompletionRequestMessage::Tool(
|
||||||
ChatCompletionRequestToolMessage {
|
ChatCompletionRequestToolMessage {
|
||||||
tool_call_id: call.id.clone(),
|
tool_call_id: call.id.clone(),
|
||||||
content: ChatCompletionRequestToolMessageContent::Text(format!(
|
content: ChatCompletionRequestToolMessageContent::Text(answer),
|
||||||
"{}\n\n{text}",
|
|
||||||
chat_settings.prompts.clone().unwrap().pre_query.unwrap()
|
|
||||||
)),
|
|
||||||
},
|
},
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
@ -416,24 +417,28 @@ async fn streamed_chat(
|
|||||||
let filters = index_scheduler.filters();
|
let filters = index_scheduler.filters();
|
||||||
|
|
||||||
let rtxn = index_scheduler.read_txn()?;
|
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(&rtxn, workspace_uid)? {
|
||||||
Some(value) => serde_json::from_value(value.clone()).unwrap(),
|
Some(settings) => settings,
|
||||||
None => GlobalChatSettings::default(),
|
None => {
|
||||||
|
return Err(ResponseError::from_msg(
|
||||||
|
format!("Chat `{workspace_uid}` not found"),
|
||||||
|
Code::ChatWorkspaceNotFound,
|
||||||
|
))
|
||||||
|
}
|
||||||
};
|
};
|
||||||
drop(rtxn);
|
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 Some(api_key) = chat_settings.api_key.as_ref() {
|
||||||
config = config.with_api_key(api_key);
|
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);
|
config = config.with_api_base(base_api);
|
||||||
}
|
}
|
||||||
|
|
||||||
let auth_token = extract_token_from_request(&req)?.unwrap().to_string();
|
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 =
|
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, rx) = tokio::sync::mpsc::channel(10);
|
||||||
let tx = SseEventSender(tx);
|
let tx = SseEventSender(tx);
|
||||||
@ -478,7 +483,7 @@ async fn run_conversation<C: Config>(
|
|||||||
search_queue: &web::Data<SearchQueue>,
|
search_queue: &web::Data<SearchQueue>,
|
||||||
auth_token: &str,
|
auth_token: &str,
|
||||||
client: &Client<C>,
|
client: &Client<C>,
|
||||||
chat_settings: &GlobalChatSettings,
|
chat_settings: &DbChatSettings,
|
||||||
chat_completion: &mut CreateChatCompletionRequest,
|
chat_completion: &mut CreateChatCompletionRequest,
|
||||||
tx: &SseEventSender,
|
tx: &SseEventSender,
|
||||||
global_tool_calls: &mut HashMap<u32, Call>,
|
global_tool_calls: &mut HashMap<u32, Call>,
|
||||||
@ -605,7 +610,7 @@ async fn handle_meili_tools(
|
|||||||
auth_ctrl: &web::Data<AuthController>,
|
auth_ctrl: &web::Data<AuthController>,
|
||||||
search_queue: &web::Data<SearchQueue>,
|
search_queue: &web::Data<SearchQueue>,
|
||||||
auth_token: &str,
|
auth_token: &str,
|
||||||
chat_settings: &GlobalChatSettings,
|
chat_settings: &DbChatSettings,
|
||||||
tx: &SseEventSender,
|
tx: &SseEventSender,
|
||||||
meili_calls: Vec<ChatCompletionMessageToolCall>,
|
meili_calls: Vec<ChatCompletionMessageToolCall>,
|
||||||
chat_completion: &mut CreateChatCompletionRequest,
|
chat_completion: &mut CreateChatCompletionRequest,
|
||||||
@ -658,12 +663,10 @@ async fn handle_meili_tools(
|
|||||||
Err(err) => err,
|
Err(err) => err,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let answer = format!("{}\n\n{text}", chat_settings.prompts.pre_query);
|
||||||
let tool = ChatCompletionRequestMessage::Tool(ChatCompletionRequestToolMessage {
|
let tool = ChatCompletionRequestMessage::Tool(ChatCompletionRequestToolMessage {
|
||||||
tool_call_id: call.id.clone(),
|
tool_call_id: call.id.clone(),
|
||||||
content: ChatCompletionRequestToolMessageContent::Text(format!(
|
content: ChatCompletionRequestToolMessageContent::Text(answer),
|
||||||
"{}\n\n{text}",
|
|
||||||
chat_settings.prompts.as_ref().unwrap().pre_query.as_ref().unwrap()
|
|
||||||
)),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if append_to_conversation {
|
if append_to_conversation {
|
||||||
|
@ -1,7 +1,13 @@
|
|||||||
use actix_web::web::{self, Data};
|
use actix_web::web::{self, Data};
|
||||||
use actix_web::HttpResponse;
|
use actix_web::HttpResponse;
|
||||||
use index_scheduler::IndexScheduler;
|
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::keys::actions;
|
||||||
use meilisearch_types::milli::update::Setting;
|
use meilisearch_types::milli::update::Setting;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
@ -15,7 +21,7 @@ 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(SeqHandler(get_settings)))
|
||||||
.route(web::patch().to(SeqHandler(patch_settings)))
|
.route(web::patch().to(SeqHandler(patch_settings)))
|
||||||
.route(web::delete().to(SeqHandler(delete_settings))),
|
.route(web::delete().to(SeqHandler(delete_settings))),
|
||||||
);
|
);
|
||||||
@ -33,8 +39,13 @@ async fn get_settings(
|
|||||||
// TODO do a spawn_blocking here ???
|
// TODO do a spawn_blocking here ???
|
||||||
let rtxn = index_scheduler.read_txn()?;
|
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(&rtxn, &workspace_uid)? {
|
||||||
Some(value) => serde_json::from_value(value).unwrap(),
|
Some(settings) => settings,
|
||||||
None => GlobalChatSettings::default(),
|
None => {
|
||||||
|
return Err(ResponseError::from_msg(
|
||||||
|
format!("Chat `{workspace_uid}` not found"),
|
||||||
|
Code::ChatWorkspaceNotFound,
|
||||||
|
))
|
||||||
|
}
|
||||||
};
|
};
|
||||||
settings.hide_secrets();
|
settings.hide_secrets();
|
||||||
Ok(HttpResponse::Ok().json(settings))
|
Ok(HttpResponse::Ok().json(settings))
|
||||||
@ -52,35 +63,73 @@ async fn patch_settings(
|
|||||||
|
|
||||||
// TODO do a spawn_blocking here
|
// TODO do a spawn_blocking here
|
||||||
let mut wtxn = index_scheduler.write_txn()?;
|
let mut wtxn = index_scheduler.write_txn()?;
|
||||||
let old = match index_scheduler.chat_settings(&mut wtxn, &workspace_uid)? {
|
let old_settings =
|
||||||
Some(value) => serde_json::from_value(value).unwrap(),
|
index_scheduler.chat_settings(&mut wtxn, &workspace_uid)?.unwrap_or_default();
|
||||||
None => GlobalChatSettings::default(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let settings = GlobalChatSettings {
|
let prompts = match new.prompts {
|
||||||
source: new.source.or(old.source),
|
Setting::Set(new_prompts) => DbChatCompletionPrompts {
|
||||||
base_api: new.base_api.clone().or(old.base_api),
|
system: match new_prompts.system {
|
||||||
api_key: new.api_key.clone().or(old.api_key),
|
Setting::Set(new_system) => new_system,
|
||||||
prompts: match (new.prompts, old.prompts) {
|
Setting::Reset => DEFAULT_CHAT_SYSTEM_PROMPT.to_string(),
|
||||||
(Setting::NotSet, set) | (set, Setting::NotSet) => set,
|
Setting::NotSet => old_settings.prompts.system,
|
||||||
(Setting::Set(_) | Setting::Reset, Setting::Reset) => Setting::Reset,
|
},
|
||||||
(Setting::Reset, Setting::Set(set)) => Setting::Set(set),
|
search_description: match new_prompts.search_description {
|
||||||
// If both are set we must merge the prompts settings
|
Setting::Set(new_description) => new_description,
|
||||||
(Setting::Set(new), Setting::Set(old)) => Setting::Set(ChatPrompts {
|
Setting::Reset => DEFAULT_CHAT_SEARCH_DESCRIPTION_PROMPT.to_string(),
|
||||||
system: new.system.or(old.system),
|
Setting::NotSet => old_settings.prompts.search_description,
|
||||||
search_description: new.search_description.or(old.search_description),
|
},
|
||||||
search_q_param: new.search_q_param.or(old.search_q_param),
|
search_q_param: match new_prompts.search_q_param {
|
||||||
search_index_uid_param: new.search_index_uid_param.or(old.search_index_uid_param),
|
Setting::Set(new_description) => new_description,
|
||||||
pre_query: new.pre_query.or(old.pre_query),
|
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();
|
let settings = ChatCompletionSettings {
|
||||||
index_scheduler.put_chat_settings(&mut wtxn, &workspace_uid, &value)?;
|
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()?;
|
wtxn.commit()?;
|
||||||
|
|
||||||
Ok(HttpResponse::Ok().finish())
|
Ok(HttpResponse::Ok().json(settings))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn delete_settings(
|
async fn delete_settings(
|
||||||
@ -96,58 +145,42 @@ async fn delete_settings(
|
|||||||
let mut wtxn = index_scheduler.write_txn()?;
|
let mut wtxn = index_scheduler.write_txn()?;
|
||||||
if index_scheduler.delete_chat_settings(&mut wtxn, &workspace_uid)? {
|
if index_scheduler.delete_chat_settings(&mut wtxn, &workspace_uid)? {
|
||||||
wtxn.commit()?;
|
wtxn.commit()?;
|
||||||
Ok(HttpResponse::Ok().finish())
|
Ok(HttpResponse::NoContent().finish())
|
||||||
} else {
|
} 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")]
|
#[serde(deny_unknown_fields, rename_all = "camelCase")]
|
||||||
pub enum ChatSource {
|
pub enum ChatCompletionSource {
|
||||||
|
#[default]
|
||||||
OpenAi,
|
OpenAi,
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO Implement Deserr on that.
|
impl From<ChatCompletionSource> for DbChatCompletionSource {
|
||||||
// TODO Declare DbGlobalChatSettings (alias it).
|
fn from(source: ChatCompletionSource) -> Self {
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
match source {
|
||||||
#[serde(deny_unknown_fields, rename_all = "camelCase")]
|
ChatCompletionSource::OpenAi => DbChatCompletionSource::OpenAi,
|
||||||
pub struct GlobalChatSettings {
|
}
|
||||||
#[serde(default, skip_serializing_if = "Setting::is_not_set")]
|
}
|
||||||
pub source: Setting<ChatSource>,
|
|
||||||
#[serde(default, skip_serializing_if = "Setting::is_not_set")]
|
|
||||||
pub base_api: Setting<String>,
|
|
||||||
#[serde(default, skip_serializing_if = "Setting::is_not_set")]
|
|
||||||
pub api_key: Setting<String>,
|
|
||||||
#[serde(default, skip_serializing_if = "Setting::is_not_set")]
|
|
||||||
pub prompts: Setting<ChatPrompts>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GlobalChatSettings {
|
// TODO Implement Deserr on that.
|
||||||
pub fn hide_secrets(&mut self) {
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
match &mut self.api_key {
|
#[serde(deny_unknown_fields, rename_all = "camelCase")]
|
||||||
Setting::Set(key) => Self::hide_secret(key),
|
pub struct GlobalChatSettings {
|
||||||
Setting::Reset => (),
|
#[serde(default)]
|
||||||
Setting::NotSet => (),
|
pub source: Setting<ChatCompletionSource>,
|
||||||
}
|
#[serde(default)]
|
||||||
}
|
pub base_api: Setting<String>,
|
||||||
|
#[serde(default)]
|
||||||
fn hide_secret(secret: &mut String) {
|
pub api_key: Setting<String>,
|
||||||
match secret.len() {
|
#[serde(default)]
|
||||||
x if x < 10 => {
|
pub prompts: Setting<ChatPrompts>,
|
||||||
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.
|
// TODO Implement Deserr on that.
|
||||||
|
@ -115,7 +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("/chats").configure(chats::settings::configure));
|
.service(web::scope("/chats").configure(chats::configure));
|
||||||
|
|
||||||
#[cfg(feature = "swagger")]
|
#[cfg(feature = "swagger")]
|
||||||
{
|
{
|
||||||
|
Loading…
x
Reference in New Issue
Block a user