mirror of
https://github.com/meilisearch/meilisearch.git
synced 2025-09-06 12:46:31 +00:00
refactor: rename personalization API fields and move checks inside service
- Rename 'personalization' field to 'personalize' in API - Rename 'userProfile' to 'userContext' in personalization object - Remove 'personalized' boolean field (activation now based on non-null 'personalize') - Move personalization checks inside rerank_search_results function - Use 'let else' pattern for better error handling - Update error types and messages to reflect new field names - Update all search routes and analytics to use new field names
This commit is contained in:
@ -310,9 +310,8 @@ InvalidSearchShowRankingScoreDetails , InvalidRequest , BAD_REQU
|
||||
InvalidSimilarShowRankingScoreDetails , InvalidRequest , BAD_REQUEST ;
|
||||
InvalidSearchSort , InvalidRequest , BAD_REQUEST ;
|
||||
InvalidSearchDistinct , InvalidRequest , BAD_REQUEST ;
|
||||
InvalidSearchPersonalization , InvalidRequest , BAD_REQUEST ;
|
||||
InvalidSearchPersonalizationPersonalized , InvalidRequest , BAD_REQUEST ;
|
||||
InvalidSearchPersonalizationUserProfile , InvalidRequest , BAD_REQUEST ;
|
||||
InvalidSearchPersonalize , InvalidRequest , BAD_REQUEST ;
|
||||
InvalidSearchPersonalizeUserContext , InvalidRequest , BAD_REQUEST ;
|
||||
InvalidSearchMediaAndVector , InvalidRequest , BAD_REQUEST ;
|
||||
InvalidSettingsDisplayedAttributes , InvalidRequest , BAD_REQUEST ;
|
||||
InvalidSettingsDistinctAttribute , InvalidRequest , BAD_REQUEST ;
|
||||
@ -653,21 +652,15 @@ impl fmt::Display for deserr_codes::InvalidNetworkSearchApiKey {
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for deserr_codes::InvalidSearchPersonalization {
|
||||
impl fmt::Display for deserr_codes::InvalidSearchPersonalize {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "the value of `personalization` is invalid, expected a JSON object with `personalized` boolean and optional `userProfile` string.")
|
||||
write!(f, "the value of `personalize` is invalid, expected a JSON object with optional `userContext` string.")
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for deserr_codes::InvalidSearchPersonalizationPersonalized {
|
||||
impl fmt::Display for deserr_codes::InvalidSearchPersonalizeUserContext {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "the value of `personalized` is invalid, expected a boolean.")
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for deserr_codes::InvalidSearchPersonalizationUserProfile {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "the value of `userProfile` is invalid, expected a string.")
|
||||
write!(f, "the value of `userContext` is invalid, expected a string.")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
use crate::search::{Personalization, SearchResult};
|
||||
use crate::search::{Personalize, SearchResult};
|
||||
use cohere_rust::{
|
||||
api::rerank::{ReRankModel, ReRankRequest},
|
||||
Cohere,
|
||||
@ -26,15 +26,13 @@ impl PersonalizationService {
|
||||
pub async fn rerank_search_results(
|
||||
&self,
|
||||
search_result: SearchResult,
|
||||
personalization: &Personalization,
|
||||
query: &str,
|
||||
personalize: Option<&Personalize>,
|
||||
query: Option<&str>,
|
||||
) -> Result<SearchResult, ResponseError> {
|
||||
// If personalization is not enabled or no API key, return original results
|
||||
if !personalization.personalized || self.cohere.is_none() {
|
||||
return Ok(search_result);
|
||||
}
|
||||
|
||||
let cohere = self.cohere.as_ref().unwrap();
|
||||
// If personalization is not requested, no API key, or no query, return original results
|
||||
let Some(_personalize) = personalize else { return Ok(search_result) };
|
||||
let Some(cohere) = &self.cohere else { return Ok(search_result) };
|
||||
let Some(query) = query else { return Ok(search_result) };
|
||||
|
||||
// Extract documents for reranking
|
||||
let documents: Vec<String> = search_result
|
||||
@ -95,8 +93,7 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn test_personalization_service_without_api_key() {
|
||||
let service = PersonalizationService::new(None);
|
||||
let personalization =
|
||||
Personalization { personalized: true, user_profile: Some("test user".to_string()) };
|
||||
let personalize = Personalize { user_context: Some("test user".to_string()) };
|
||||
|
||||
let search_result = SearchResult {
|
||||
hits: vec![SearchHit {
|
||||
@ -116,8 +113,9 @@ mod tests {
|
||||
used_negative_operator: false,
|
||||
};
|
||||
|
||||
let result =
|
||||
service.rerank_search_results(search_result.clone(), &personalization, "test").await;
|
||||
let result = service
|
||||
.rerank_search_results(search_result.clone(), Some(&personalize), Some("test"))
|
||||
.await;
|
||||
assert!(result.is_ok());
|
||||
|
||||
// Should return original results when no API key is provided
|
||||
@ -128,10 +126,7 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn test_personalization_service_disabled() {
|
||||
let service = PersonalizationService::new(Some("fake_key".to_string()));
|
||||
let personalization = Personalization {
|
||||
personalized: false, // Personalization disabled
|
||||
user_profile: Some("test user".to_string()),
|
||||
};
|
||||
let personalize = Personalize { user_context: Some("test user".to_string()) };
|
||||
|
||||
let search_result = SearchResult {
|
||||
hits: vec![SearchHit {
|
||||
@ -151,8 +146,9 @@ mod tests {
|
||||
used_negative_operator: false,
|
||||
};
|
||||
|
||||
let result =
|
||||
service.rerank_search_results(search_result.clone(), &personalization, "test").await;
|
||||
let result = service
|
||||
.rerank_search_results(search_result.clone(), Some(&personalize), Some("test"))
|
||||
.await;
|
||||
assert!(result.is_ok());
|
||||
|
||||
// Should return original results when personalization is disabled
|
||||
|
@ -343,7 +343,7 @@ impl From<FacetSearchQuery> for SearchQuery {
|
||||
hybrid,
|
||||
ranking_score_threshold,
|
||||
locales,
|
||||
personalization: None,
|
||||
personalize: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -22,7 +22,7 @@ use crate::extractors::sequential_extractor::SeqHandler;
|
||||
use crate::metrics::MEILISEARCH_DEGRADED_SEARCH_REQUESTS;
|
||||
use crate::routes::indexes::search_analytics::{SearchAggregator, SearchGET, SearchPOST};
|
||||
use crate::search::{
|
||||
add_search_rules, perform_search, HybridQuery, MatchingStrategy, Personalization,
|
||||
add_search_rules, perform_search, HybridQuery, MatchingStrategy, Personalize,
|
||||
RankingScoreThreshold, RetrieveVectors, SearchKind, SearchQuery, SearchResult, SemanticRatio,
|
||||
DEFAULT_CROP_LENGTH, DEFAULT_CROP_MARKER, DEFAULT_HIGHLIGHT_POST_TAG,
|
||||
DEFAULT_HIGHLIGHT_PRE_TAG, DEFAULT_SEARCH_LIMIT, DEFAULT_SEARCH_OFFSET, DEFAULT_SEMANTIC_RATIO,
|
||||
@ -132,11 +132,8 @@ pub struct SearchQueryGet {
|
||||
#[deserr(default, error = DeserrQueryParamError<InvalidSearchLocales>)]
|
||||
#[param(value_type = Vec<Locale>, explode = false)]
|
||||
pub locales: Option<CS<Locale>>,
|
||||
#[deserr(default, error = DeserrQueryParamError<InvalidSearchPersonalizationPersonalized>)]
|
||||
#[param(value_type = bool)]
|
||||
pub personalization_personalized: Option<bool>,
|
||||
#[deserr(default, error = DeserrQueryParamError<InvalidSearchPersonalizationUserProfile>)]
|
||||
pub personalization_user_profile: Option<String>,
|
||||
#[deserr(default, error = DeserrQueryParamError<InvalidSearchPersonalizeUserContext>)]
|
||||
pub personalize_user_context: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, deserr::Deserr)]
|
||||
@ -208,21 +205,9 @@ impl TryFrom<SearchQueryGet> for SearchQuery {
|
||||
));
|
||||
}
|
||||
|
||||
let personalization = match (
|
||||
other.personalization_personalized,
|
||||
other.personalization_user_profile,
|
||||
) {
|
||||
(None, None) => None,
|
||||
(Some(personalized), user_profile) => {
|
||||
Some(Personalization { personalized, user_profile })
|
||||
}
|
||||
(None, Some(_)) => {
|
||||
return Err(ResponseError::from_msg(
|
||||
"`personalizationPersonalized` is mandatory when `personalizationUserProfile` is present".into(),
|
||||
meilisearch_types::error::Code::InvalidSearchPersonalization,
|
||||
));
|
||||
}
|
||||
};
|
||||
let personalize = other
|
||||
.personalize_user_context
|
||||
.map(|user_context| Personalize { user_context: Some(user_context) });
|
||||
|
||||
Ok(Self {
|
||||
q: other.q,
|
||||
@ -253,7 +238,7 @@ impl TryFrom<SearchQueryGet> for SearchQuery {
|
||||
hybrid,
|
||||
ranking_score_threshold: other.ranking_score_threshold.map(|o| o.0),
|
||||
locales: other.locales.map(|o| o.into_iter().collect()),
|
||||
personalization,
|
||||
personalize,
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -367,7 +352,7 @@ pub async fn search_with_url_query(
|
||||
let retrieve_vector = RetrieveVectors::new(query.retrieve_vectors);
|
||||
|
||||
// Extract personalization and query string before moving query
|
||||
let personalization = query.personalization.clone();
|
||||
let personalize = query.personalize.clone();
|
||||
let query_str = query.q.clone();
|
||||
|
||||
let permit = search_queue.try_get_search_permit().await?;
|
||||
@ -392,13 +377,9 @@ pub async fn search_with_url_query(
|
||||
let mut search_result = search_result?;
|
||||
|
||||
// Apply personalization if requested
|
||||
if let Some(personalization) = &personalization {
|
||||
if let Some(query_str) = &query_str {
|
||||
search_result = personalization_service
|
||||
.rerank_search_results(search_result, personalization, query_str)
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
search_result = personalization_service
|
||||
.rerank_search_results(search_result, personalize.as_ref(), query_str.as_deref())
|
||||
.await?;
|
||||
|
||||
debug!(returns = ?search_result, "Search get");
|
||||
Ok(HttpResponse::Ok().json(search_result))
|
||||
@ -488,7 +469,7 @@ pub async fn search_with_post(
|
||||
let retrieve_vectors = RetrieveVectors::new(query.retrieve_vectors);
|
||||
|
||||
// Extract personalization and query string before moving query
|
||||
let personalization = query.personalization.clone();
|
||||
let personalize = query.personalize.clone();
|
||||
let query_str = query.q.clone();
|
||||
|
||||
let permit = search_queue.try_get_search_permit().await?;
|
||||
@ -516,13 +497,9 @@ pub async fn search_with_post(
|
||||
let mut search_result = search_result?;
|
||||
|
||||
// Apply personalization if requested
|
||||
if let Some(personalization) = &personalization {
|
||||
if let Some(query_str) = &query_str {
|
||||
search_result = personalization_service
|
||||
.rerank_search_results(search_result, personalization, query_str)
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
search_result = personalization_service
|
||||
.rerank_search_results(search_result, personalize.as_ref(), query_str.as_deref())
|
||||
.await?;
|
||||
|
||||
debug!(returns = ?search_result, "Search post");
|
||||
Ok(HttpResponse::Ok().json(search_result))
|
||||
|
@ -128,7 +128,7 @@ impl<Method: AggregateMethod> SearchAggregator<Method> {
|
||||
hybrid,
|
||||
ranking_score_threshold,
|
||||
locales,
|
||||
personalization: _,
|
||||
personalize: _,
|
||||
} = query;
|
||||
|
||||
let mut ret = Self::default();
|
||||
|
@ -67,7 +67,7 @@ impl MultiSearchAggregator {
|
||||
hybrid: _,
|
||||
ranking_score_threshold: _,
|
||||
locales: _,
|
||||
personalization: _,
|
||||
personalize: _,
|
||||
} in &federated_search.queries
|
||||
{
|
||||
if let Some(federation_options) = federation_options {
|
||||
|
@ -58,12 +58,10 @@ pub const DEFAULT_HIGHLIGHT_POST_TAG: fn() -> String = || "</em>".to_string();
|
||||
pub const DEFAULT_SEMANTIC_RATIO: fn() -> SemanticRatio = || SemanticRatio(0.5);
|
||||
|
||||
#[derive(Clone, Default, PartialEq, Deserr, ToSchema, Debug)]
|
||||
#[deserr(error = DeserrJsonError<InvalidSearchPersonalization>, rename_all = camelCase, deny_unknown_fields)]
|
||||
pub struct Personalization {
|
||||
#[deserr(default, error = DeserrJsonError<InvalidSearchPersonalizationPersonalized>)]
|
||||
pub personalized: bool,
|
||||
#[deserr(default, error = DeserrJsonError<InvalidSearchPersonalizationUserProfile>)]
|
||||
pub user_profile: Option<String>,
|
||||
#[deserr(error = DeserrJsonError<InvalidSearchPersonalize>, rename_all = camelCase, deny_unknown_fields)]
|
||||
pub struct Personalize {
|
||||
#[deserr(default, error = DeserrJsonError<InvalidSearchPersonalizeUserContext>)]
|
||||
pub user_context: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Default, PartialEq, Deserr, ToSchema)]
|
||||
@ -129,8 +127,8 @@ pub struct SearchQuery {
|
||||
pub ranking_score_threshold: Option<RankingScoreThreshold>,
|
||||
#[deserr(default, error = DeserrJsonError<InvalidSearchLocales>)]
|
||||
pub locales: Option<Vec<Locale>>,
|
||||
#[deserr(default, error = DeserrJsonError<InvalidSearchPersonalization>, default)]
|
||||
pub personalization: Option<Personalization>,
|
||||
#[deserr(default, error = DeserrJsonError<InvalidSearchPersonalize>, default)]
|
||||
pub personalize: Option<Personalize>,
|
||||
}
|
||||
|
||||
impl From<SearchParameters> for SearchQuery {
|
||||
@ -178,7 +176,7 @@ impl From<SearchParameters> for SearchQuery {
|
||||
highlight_post_tag: DEFAULT_HIGHLIGHT_POST_TAG(),
|
||||
crop_marker: DEFAULT_CROP_MARKER(),
|
||||
locales: None,
|
||||
personalization: None,
|
||||
personalize: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -260,7 +258,7 @@ impl fmt::Debug for SearchQuery {
|
||||
attributes_to_search_on,
|
||||
ranking_score_threshold,
|
||||
locales,
|
||||
personalization,
|
||||
personalize,
|
||||
} = self;
|
||||
|
||||
let mut debug = f.debug_struct("SearchQuery");
|
||||
@ -349,8 +347,8 @@ impl fmt::Debug for SearchQuery {
|
||||
debug.field("locales", &locales);
|
||||
}
|
||||
|
||||
if let Some(personalization) = personalization {
|
||||
debug.field("personalization", &personalization);
|
||||
if let Some(personalize) = personalize {
|
||||
debug.field("personalize", &personalize);
|
||||
}
|
||||
|
||||
debug.finish()
|
||||
@ -558,9 +556,9 @@ pub struct SearchQueryWithIndex {
|
||||
pub ranking_score_threshold: Option<RankingScoreThreshold>,
|
||||
#[deserr(default, error = DeserrJsonError<InvalidSearchLocales>, default)]
|
||||
pub locales: Option<Vec<Locale>>,
|
||||
#[deserr(default, error = DeserrJsonError<InvalidSearchPersonalization>, default)]
|
||||
#[deserr(default, error = DeserrJsonError<InvalidSearchPersonalize>, default)]
|
||||
#[serde(skip)]
|
||||
pub personalization: Option<Personalization>,
|
||||
pub personalize: Option<Personalize>,
|
||||
|
||||
#[deserr(default)]
|
||||
pub federation_options: Option<FederationOptions>,
|
||||
@ -618,7 +616,7 @@ impl SearchQueryWithIndex {
|
||||
attributes_to_search_on,
|
||||
ranking_score_threshold,
|
||||
locales,
|
||||
personalization,
|
||||
personalize,
|
||||
} = query;
|
||||
|
||||
SearchQueryWithIndex {
|
||||
@ -650,7 +648,7 @@ impl SearchQueryWithIndex {
|
||||
attributes_to_search_on,
|
||||
ranking_score_threshold,
|
||||
locales,
|
||||
personalization,
|
||||
personalize,
|
||||
federation_options,
|
||||
}
|
||||
}
|
||||
@ -686,7 +684,7 @@ impl SearchQueryWithIndex {
|
||||
hybrid,
|
||||
ranking_score_threshold,
|
||||
locales,
|
||||
personalization,
|
||||
personalize,
|
||||
} = self;
|
||||
(
|
||||
index_uid,
|
||||
@ -718,7 +716,7 @@ impl SearchQueryWithIndex {
|
||||
hybrid,
|
||||
ranking_score_threshold,
|
||||
locales,
|
||||
personalization,
|
||||
personalize,
|
||||
// do not use ..Default::default() here,
|
||||
// rather add any missing field from `SearchQuery` to `SearchQueryWithIndex`
|
||||
},
|
||||
@ -1190,7 +1188,7 @@ pub fn perform_search(
|
||||
attributes_to_search_on: _,
|
||||
filter: _,
|
||||
distinct: _,
|
||||
personalization: _,
|
||||
personalize: _,
|
||||
} = query;
|
||||
|
||||
let format = AttributesFormat {
|
||||
|
Reference in New Issue
Block a user