mirror of
https://github.com/meilisearch/MeiliSearch
synced 2025-01-09 04:54:31 +01:00
add the similar route
This commit is contained in:
parent
668b26b641
commit
4eaa626bca
@ -42,6 +42,7 @@ mod similar_analytics;
|
||||
nest(
|
||||
(path = "/", api = documents::DocumentsApi),
|
||||
(path = "/", api = facet_search::FacetSearchApi),
|
||||
(path = "/", api = similar::SimilarApi),
|
||||
),
|
||||
paths(list_indexes, create_index, get_index, update_index, delete_index, get_index_stats),
|
||||
tags(
|
||||
|
@ -11,6 +11,7 @@ use meilisearch_types::keys::actions;
|
||||
use meilisearch_types::serde_cs::vec::CS;
|
||||
use serde_json::Value;
|
||||
use tracing::debug;
|
||||
use utoipa::{IntoParams, OpenApi};
|
||||
|
||||
use super::ActionPolicy;
|
||||
use crate::analytics::Analytics;
|
||||
@ -22,6 +23,21 @@ use crate::search::{
|
||||
SimilarQuery, SimilarResult, DEFAULT_SEARCH_LIMIT, DEFAULT_SEARCH_OFFSET,
|
||||
};
|
||||
|
||||
#[derive(OpenApi)]
|
||||
#[openapi(
|
||||
paths(similar_get, similar_post),
|
||||
tags(
|
||||
(
|
||||
name = "Similar documents",
|
||||
description = "The /similar route uses AI-powered search to return a number of documents similar to a target document.
|
||||
|
||||
Meilisearch exposes two routes for retrieving similar documents: POST and GET. In the majority of cases, POST will offer better performance and ease of use.",
|
||||
external_docs(url = "https://www.meilisearch.com/docs/reference/api/similar"),
|
||||
),
|
||||
),
|
||||
)]
|
||||
pub struct SimilarApi;
|
||||
|
||||
pub fn configure(cfg: &mut web::ServiceConfig) {
|
||||
cfg.service(
|
||||
web::resource("")
|
||||
@ -30,6 +46,62 @@ pub fn configure(cfg: &mut web::ServiceConfig) {
|
||||
);
|
||||
}
|
||||
|
||||
/// Get similar documents with GET
|
||||
///
|
||||
/// Retrieve documents similar to a specific search result.
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/{indexUid}/similar",
|
||||
tags = ["Indexes", "Similar documents"],
|
||||
security(("Bearer" = ["search", "*"])),
|
||||
params(
|
||||
("indexUid" = String, Path, example = "movies", description = "Index Unique Identifier", nullable = false),
|
||||
SimilarQueryGet
|
||||
),
|
||||
responses(
|
||||
(status = 200, description = "The documents are returned", body = SimilarResult, content_type = "application/json", example = json!(
|
||||
{
|
||||
"hits": [
|
||||
{
|
||||
"id": 2770,
|
||||
"title": "American Pie 2",
|
||||
"poster": "https://image.tmdb.org/t/p/w1280/q4LNgUnRfltxzp3gf1MAGiK5LhV.jpg",
|
||||
"overview": "The whole gang are back and as close as ever. They decide to get even closer by spending the summer together at a beach house. They decide to hold the biggest…",
|
||||
"release_date": 997405200
|
||||
},
|
||||
{
|
||||
"id": 190859,
|
||||
"title": "American Sniper",
|
||||
"poster": "https://image.tmdb.org/t/p/w1280/svPHnYE7N5NAGO49dBmRhq0vDQ3.jpg",
|
||||
"overview": "U.S. Navy SEAL Chris Kyle takes his sole mission—protect his comrades—to heart and becomes one of the most lethal snipers in American history. His pinpoint accuracy not only saves countless lives but also makes him a prime…",
|
||||
"release_date": 1418256000
|
||||
}
|
||||
],
|
||||
"offset": 0,
|
||||
"limit": 2,
|
||||
"estimatedTotalHits": 976,
|
||||
"processingTimeMs": 35,
|
||||
"query": "american "
|
||||
}
|
||||
)),
|
||||
(status = 404, description = "Index not found", body = ResponseError, content_type = "application/json", example = json!(
|
||||
{
|
||||
"message": "Index `movies` not found.",
|
||||
"code": "index_not_found",
|
||||
"type": "invalid_request",
|
||||
"link": "https://docs.meilisearch.com/errors#index_not_found"
|
||||
}
|
||||
)),
|
||||
(status = 401, description = "The authorization header is missing", body = ResponseError, content_type = "application/json", example = json!(
|
||||
{
|
||||
"message": "The Authorization header is missing. It must use the bearer authorization method.",
|
||||
"code": "missing_authorization_header",
|
||||
"type": "auth",
|
||||
"link": "https://docs.meilisearch.com/errors#missing_authorization_header"
|
||||
}
|
||||
)),
|
||||
)
|
||||
)]
|
||||
pub async fn similar_get(
|
||||
index_scheduler: GuardedData<ActionPolicy<{ actions::SEARCH }>, Data<IndexScheduler>>,
|
||||
index_uid: web::Path<String>,
|
||||
@ -58,6 +130,62 @@ pub async fn similar_get(
|
||||
Ok(HttpResponse::Ok().json(similar))
|
||||
}
|
||||
|
||||
/// Get similar documents with POST
|
||||
///
|
||||
/// Retrieve documents similar to a specific search result.
|
||||
#[utoipa::path(
|
||||
post,
|
||||
path = "/{indexUid}/similar",
|
||||
tags = ["Indexes", "Similar documents"],
|
||||
security(("Bearer" = ["search", "*"])),
|
||||
params(
|
||||
("indexUid" = String, Path, example = "movies", description = "Index Unique Identifier", nullable = false),
|
||||
),
|
||||
request_body = SimilarQuery,
|
||||
responses(
|
||||
(status = 200, description = "The documents are returned", body = SimilarResult, content_type = "application/json", example = json!(
|
||||
{
|
||||
"hits": [
|
||||
{
|
||||
"id": 2770,
|
||||
"title": "American Pie 2",
|
||||
"poster": "https://image.tmdb.org/t/p/w1280/q4LNgUnRfltxzp3gf1MAGiK5LhV.jpg",
|
||||
"overview": "The whole gang are back and as close as ever. They decide to get even closer by spending the summer together at a beach house. They decide to hold the biggest…",
|
||||
"release_date": 997405200
|
||||
},
|
||||
{
|
||||
"id": 190859,
|
||||
"title": "American Sniper",
|
||||
"poster": "https://image.tmdb.org/t/p/w1280/svPHnYE7N5NAGO49dBmRhq0vDQ3.jpg",
|
||||
"overview": "U.S. Navy SEAL Chris Kyle takes his sole mission—protect his comrades—to heart and becomes one of the most lethal snipers in American history. His pinpoint accuracy not only saves countless lives but also makes him a prime…",
|
||||
"release_date": 1418256000
|
||||
}
|
||||
],
|
||||
"offset": 0,
|
||||
"limit": 2,
|
||||
"estimatedTotalHits": 976,
|
||||
"processingTimeMs": 35,
|
||||
"query": "american "
|
||||
}
|
||||
)),
|
||||
(status = 404, description = "Index not found", body = ResponseError, content_type = "application/json", example = json!(
|
||||
{
|
||||
"message": "Index `movies` not found.",
|
||||
"code": "index_not_found",
|
||||
"type": "invalid_request",
|
||||
"link": "https://docs.meilisearch.com/errors#index_not_found"
|
||||
}
|
||||
)),
|
||||
(status = 401, description = "The authorization header is missing", body = ResponseError, content_type = "application/json", example = json!(
|
||||
{
|
||||
"message": "The Authorization header is missing. It must use the bearer authorization method.",
|
||||
"code": "missing_authorization_header",
|
||||
"type": "auth",
|
||||
"link": "https://docs.meilisearch.com/errors#missing_authorization_header"
|
||||
}
|
||||
)),
|
||||
)
|
||||
)]
|
||||
pub async fn similar_post(
|
||||
index_scheduler: GuardedData<ActionPolicy<{ actions::SEARCH }>, Data<IndexScheduler>>,
|
||||
index_uid: web::Path<String>,
|
||||
@ -125,26 +253,35 @@ async fn similar(
|
||||
.await?
|
||||
}
|
||||
|
||||
#[derive(Debug, deserr::Deserr)]
|
||||
#[derive(Debug, deserr::Deserr, IntoParams)]
|
||||
#[deserr(error = DeserrQueryParamError, rename_all = camelCase, deny_unknown_fields)]
|
||||
#[into_params(parameter_in = Query)]
|
||||
pub struct SimilarQueryGet {
|
||||
#[deserr(error = DeserrQueryParamError<InvalidSimilarId>)]
|
||||
#[param(value_type = String)]
|
||||
id: Param<String>,
|
||||
#[deserr(default = Param(DEFAULT_SEARCH_OFFSET()), error = DeserrQueryParamError<InvalidSimilarOffset>)]
|
||||
#[param(value_type = usize, default = DEFAULT_SEARCH_OFFSET)]
|
||||
offset: Param<usize>,
|
||||
#[deserr(default = Param(DEFAULT_SEARCH_LIMIT()), error = DeserrQueryParamError<InvalidSimilarLimit>)]
|
||||
#[param(value_type = usize, default = DEFAULT_SEARCH_LIMIT)]
|
||||
limit: Param<usize>,
|
||||
#[deserr(default, error = DeserrQueryParamError<InvalidSimilarAttributesToRetrieve>)]
|
||||
#[param(value_type = Vec<String>)]
|
||||
attributes_to_retrieve: Option<CS<String>>,
|
||||
#[deserr(default, error = DeserrQueryParamError<InvalidSimilarRetrieveVectors>)]
|
||||
#[param(value_type = bool, default)]
|
||||
retrieve_vectors: Param<bool>,
|
||||
#[deserr(default, error = DeserrQueryParamError<InvalidSimilarFilter>)]
|
||||
filter: Option<String>,
|
||||
#[deserr(default, error = DeserrQueryParamError<InvalidSimilarShowRankingScore>)]
|
||||
#[param(value_type = bool, default)]
|
||||
show_ranking_score: Param<bool>,
|
||||
#[deserr(default, error = DeserrQueryParamError<InvalidSimilarShowRankingScoreDetails>)]
|
||||
#[param(value_type = bool, default)]
|
||||
show_ranking_score_details: Param<bool>,
|
||||
#[deserr(default, error = DeserrQueryParamError<InvalidSimilarRankingScoreThreshold>, default)]
|
||||
#[param(value_type = Option<f32>)]
|
||||
pub ranking_score_threshold: Option<RankingScoreThresholdGet>,
|
||||
#[deserr(error = DeserrQueryParamError<InvalidEmbedder>)]
|
||||
pub embedder: String,
|
||||
|
@ -2,6 +2,7 @@ use std::collections::BTreeMap;
|
||||
|
||||
use crate::extractors::authentication::policies::*;
|
||||
use crate::extractors::authentication::GuardedData;
|
||||
use crate::search::{SimilarQuery, SimilarResult};
|
||||
use crate::search_queue::SearchQueue;
|
||||
use crate::Opt;
|
||||
use actix_web::web::Data;
|
||||
@ -69,7 +70,7 @@ pub mod tasks;
|
||||
(name = "Stats", description = "Stats gives extended information and metrics about indexes and the Meilisearch database."),
|
||||
),
|
||||
modifiers(&OpenApiAuth),
|
||||
components(schemas(PaginationView<serde_json::Value>, BrowseQuery, UpdateIndexRequest, IndexUid, IndexCreateRequest, KeyView, Action, CreateApiKey, UpdateStderrLogs, LogMode, GetLogs, IndexStats, Stats, HealthStatus, HealthResponse, VersionResponse, Code, ErrorType, AllTasks, TaskView, Status, DetailsView, ResponseError, Settings<Unchecked>, Settings<Checked>, TypoSettings, MinWordSizeTyposSetting, FacetingSettings, PaginationSettings, SummarizedTaskView, Kind))
|
||||
components(schemas(SimilarQuery, SimilarResult, PaginationView<serde_json::Value>, BrowseQuery, UpdateIndexRequest, IndexUid, IndexCreateRequest, KeyView, Action, CreateApiKey, UpdateStderrLogs, LogMode, GetLogs, IndexStats, Stats, HealthStatus, HealthResponse, VersionResponse, Code, ErrorType, AllTasks, TaskView, Status, DetailsView, ResponseError, Settings<Unchecked>, Settings<Checked>, TypoSettings, MinWordSizeTyposSetting, FacetingSettings, PaginationSettings, SummarizedTaskView, Kind))
|
||||
)]
|
||||
pub struct MeilisearchApi;
|
||||
|
||||
|
@ -537,10 +537,11 @@ impl SearchQueryWithIndex {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Deserr)]
|
||||
#[derive(Debug, Clone, PartialEq, Deserr, ToSchema)]
|
||||
#[deserr(error = DeserrJsonError, rename_all = camelCase, deny_unknown_fields)]
|
||||
pub struct SimilarQuery {
|
||||
#[deserr(error = DeserrJsonError<InvalidSimilarId>)]
|
||||
#[schema(value_type = String)]
|
||||
pub id: ExternalDocumentId,
|
||||
#[deserr(default = DEFAULT_SEARCH_OFFSET(), error = DeserrJsonError<InvalidSimilarOffset>)]
|
||||
pub offset: usize,
|
||||
@ -559,6 +560,7 @@ pub struct SimilarQuery {
|
||||
#[deserr(default, error = DeserrJsonError<InvalidSimilarShowRankingScoreDetails>, default)]
|
||||
pub show_ranking_score_details: bool,
|
||||
#[deserr(default, error = DeserrJsonError<InvalidSimilarRankingScoreThreshold>, default)]
|
||||
#[schema(value_type = f64)]
|
||||
pub ranking_score_threshold: Option<RankingScoreThresholdSimilar>,
|
||||
}
|
||||
|
||||
@ -722,7 +724,7 @@ impl fmt::Debug for SearchResult {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Debug, Clone, PartialEq)]
|
||||
#[derive(Serialize, Debug, Clone, PartialEq, ToSchema)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct SimilarResult {
|
||||
pub hits: Vec<SearchHit>,
|
||||
|
Loading…
x
Reference in New Issue
Block a user