-
Notifications
You must be signed in to change notification settings - Fork 98
Support embedders
setting and other vector/hybrid search related configuration
#554
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Support embedders
setting and other vector/hybrid search related configuration
#554
Conversation
embedders
setting
1076241
to
8ffb555
Compare
Hello @CommanderStorm Can you rebase your branch? We made changes recently to improve the library Sorry for the inconvenience, I try to review your PR as soon as possible after the rebase |
51820a9
to
8ade09d
Compare
Done ^^ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hey @CommanderStorm,
Code-wise, the PR is very nice and well-documented; I love it!
But I’m hitting way too many timeouts to really help you; sorry 😖
I don’t think we’ll be able to merge this PR with tests that take about 10 minutes. Could you mock Meilisearch as we did here:
Lines 902 to 924 in cb32534
async fn test_get_tasks_with_params() -> Result<(), Error> { | |
let mut s = mockito::Server::new_async().await; | |
let mock_server_url = s.url(); | |
let client = Client::new(mock_server_url, Some("masterKey")).unwrap(); | |
let path = | |
"/tasks?indexUids=movies,test&statuses=equeued&types=documentDeletion&uids=1&limit=0&from=1"; | |
let mock_res = s.mock("GET", path).with_status(200).create_async().await; | |
let mut query = TasksSearchQuery::new(&client); | |
query | |
.with_index_uids(["movies", "test"]) | |
.with_statuses(["equeued"]) | |
.with_types(["documentDeletion"]) | |
.with_from(1) | |
.with_limit(0) | |
.with_uids([&1]); | |
let _ = client.get_tasks_with(&query).await; | |
mock_res.assert_async().await; | |
Ok(()) |
That way, we simply ensure that meilisearch-rust sends a valid payload and hope meilisearch works as expected.
userProvided seens more brittle, but we may want change to this instead
Or we could do that and actually send the payload to meilisearch.
Or do both; let me know what you prefer, but I would very much like the tests to not take that much time to run 😖
Where in the Meilisearch codebase could I find an e2e-test how to use feature?
I don’t think there is. I believe you’re right, and we only wrote tests for user-provided vectors
introduces the experimental-vector-search-feature flag
I don’t know if this is required, @curquiza. Could we simply expose the feature as-is instead of hiding it behind a feature flag (that will make it harder to test and use).
Since it doesn’t add any dependency, I don’t see much point in putting it behind a feature flag
Yes, that's ok not to do feature flag 😊 |
0285e62
to
9033232
Compare
Time after being migrated to
I think keeping the testcases from requiring a active internet connection is better as otherwise the test might be unnessesarily flaky in CI.
I can add a testcase where I mock the routes. I have tweaked a bunch with the vectors available and can't get the It seems to always return everything when I set I have tried similar 2D vectors as the upstream test and went as far as to use 1D vectors with considerable (1/10/1k/1m) spread (=>something that as far as I understand embeddings should not match) => I am missing something. 😅 Note I can obviously steal the testcase from |
9033232
to
0e044b1
Compare
@CommanderStorm really sorry for this. |
Thanks for the pinging (github does not give me notifications for this) ^^ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@CommanderStorm thank you again! Can you add the rest
and ollama
models we added in v1.8.0?
Sorry for the late notice again!
And thank you for your involvement 🙏
Hey @CommanderStorm, since we introduced a new |
Any update to this PR? It's very useful for AI search |
From a features/tests/documentation POV nothing is blocking merging. In terms of docs, my last sync with their docs/features was two months ago. If there is drift, feel free to provide a PR to this PR or copy my changes into a new PR. You can use this via overriding what meilisearch means - meilisearch-sdk = "1"
+ meilisearch-sdk = { git = "https://github.com/commanderstorm/meilisearch-rust.git", branch = "vector-search-embedder" } |
embedders
settingembedders
setting and other vector/hybrid search related configuration
Hi, I ran into this error when I try to create an index with settings provided in this PR. Meilisearch: 1.14 Settings::new()
.with_embedders(HashMap::from([(
"default",
Embedder::OpenAI(OpenAIEmbedderSettings {
api_key: "...",
model: Some("text-embedding-3-small".to_string()),
dimensions: Some(1536),
document_template_max_bytes: Some(6000),
..Default::default()
}),
)])) Error:
|
WalkthroughThe changes introduce comprehensive support for AI-powered and hybrid semantic search in the SDK. This includes new settings and API methods for configuring multiple embedders with provider-specific options, extended search query capabilities for hybrid and vector-based search, and enhanced test coverage for these features. All updates are additive and maintain existing functionality. Changes
Sequence Diagram(s)sequenceDiagram
participant Client
participant SDK
participant Meilisearch
Client->>SDK: Configure Settings with Embedders
SDK->>Meilisearch: PATCH /indexes/:uid/settings (with embedders)
Meilisearch-->>SDK: TaskInfo
Client->>SDK: Build SearchQuery (with hybrid/vector/retrieve_vectors)
SDK->>Meilisearch: POST /indexes/:uid/search (with hybrid and/or vector params)
Meilisearch-->>SDK: Search results (optionally with vectors)
SDK-->>Client: Search results
Assessment against linked issues
Poem
Note ⚡️ AI Code Reviews for VS Code, Cursor, WindsurfCodeRabbit now has a plugin for VS Code, Cursor and Windsurf. This brings AI code reviews directly in the code editor. Each commit is reviewed immediately, finding bugs before the PR is raised. Seamless context handoff to your AI code agent ensures that you can easily incorporate review feedback. Note ⚡️ Faster reviews with cachingCodeRabbit now supports caching for code and dependencies, helping speed up reviews. This means quicker feedback, reduced wait times, and a smoother review experience overall. Cached data is encrypted and stored securely. This feature will be automatically enabled for all accounts on May 16th. To opt out, configure 📜 Recent review detailsConfiguration used: CodeRabbit UI 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 4
🔭 Outside diff range comments (2)
src/settings.rs (2)
3083-3090
:⚠️ Potential issueCopy-paste error: test validates the wrong endpoint
test_reset_separator_tokens
resets the separator tokens but then calls
get_dictionary
, so it never checks whether the reset actually worked.
The test will pass even if separator-token APIs are broken.- let res = index.get_dictionary().await.unwrap(); - assert_eq!(separator, res); + let res = index.get_separator_tokens().await.unwrap(); + assert_eq!(separator, res);Please adjust the assertion to target the correct getter (same problem exists a few lines below for non-separator tokens).
3113-3120
:⚠️ Potential issueSame mismatch in non-separator-token reset test
The test resets non-separator tokens but queries the dictionary.
- let res = index.get_dictionary().await.unwrap(); + let res = index.get_non_separator_tokens().await.unwrap();Without this change, the test gives a false sense of security.
♻️ Duplicate comments (1)
src/search.rs (1)
639-655
: Leverage the newHybridSearch::new
& clean upwith_vector
- pub fn with_hybrid<'b>( - &'b mut self, - embedder: &'a str, - semantic_ratio: f32, - ) -> &'b mut SearchQuery<'a, Http> { - self.hybrid = Some(HybridSearch { - embedder, - semantic_ratio, - }); + pub fn with_hybrid<'b>( + &'b mut self, + embedder: &'a str, + semantic_ratio: f32, + ) -> &'b mut SearchQuery<'a, Http> { + self.hybrid = Some( + HybridSearch::new(embedder, semantic_ratio) + .expect("semantic_ratio must be within [0.0, 1.0]"), + ); self } - /// Defines what vectors an userprovided embedder has gotten for semantic searching - pub fn with_vector<'b>(&'b mut self, vector: &'a [f32]) -> &'b mut SearchQuery<'a, Http> { - self.vector = Some(vector); - self - } + + // new `with_vector` added in previous commentThis compiles the validation logic into the API surface and removes the lifetime trap.
🧹 Nitpick comments (2)
src/settings.rs (2)
193-205
:OllamaEmbedderSettings
derivesDefault
but has a required field
model: String
is documented as mandatory, yet derivingDefault
silently
sets it to an empty string. That encourages users to write..Default::default()
and ship an invalid configuration that will only fail at runtime.Consider one of:
-#[derive(Serialize, Deserialize, Default, Debug, Clone, Eq, PartialEq)] +#[derive(Serialize, Deserialize, Debug, Clone, Eq, PartialEq)]or implement a custom
Default
that provides a sensible model name.
This keeps the type API honest.
281-288
: Nit: misleading environment-variable referenceThe docs say that, if
url
is not set, Meilisearch will readMEILI_OLLAMA_URL
.
For the generic REST embedder this is confusing and probably inaccurate (left-over from copy-pasting the Ollama section).Update the comment or remove the sentence to avoid misleading users.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Cache: Disabled due to data retention organization setting
Knowledge Base: Disabled due to data retention organization setting
📒 Files selected for processing (2)
src/search.rs
(17 hunks)src/settings.rs
(13 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (1)
- GitHub Check: integration-tests
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
♻️ Duplicate comments (1)
src/settings.rs (1)
46-49
:⚠️ Potential issueFix OpenAI enum variant serialization to match upstream API
Users have reported errors when creating indices with embedders because the case in "openAI" doesn't match what Meilisearch expects ("openAi" with lowercase 'i'). While renaming the variant to
OpenAi
helps, using an explicitrename
attribute would make the solution more obvious and maintainable./// Compute embeddings inside meilisearch with models from [HuggingFace](https://huggingface.co/). /// You may be able to significantly improve performance by [compiling a CUDA-compatible Meilisearch binary](https://www.meilisearch.com/docs/guides/ai/computing_hugging_face_embeddings_gpu). /// This is a resource-intensive operation and might affect indexing performance negatively. HuggingFace(HuggingFaceEmbedderSettings), /// Use OpenAI's API to generate embeddings /// Depending on hardware, this is a - OpenAi(OpenAIEmbedderSettings), + /// + #[serde(rename = "openAi")] + OpenAI(OpenAIEmbedderSettings),
🧹 Nitpick comments (5)
src/settings.rs (5)
48-49
: Incomplete documentation commentThe documentation for OpenAI embedder has an incomplete sentence: "Depending on hardware, this is a". Please complete or remove this sentence to maintain high documentation quality.
/// Use OpenAI's API to generate embeddings -/// Depending on hardware, this is a +/// Depending on hardware, this can be more efficient than computing embeddings locally.
131-133
: Naming inconsistency between enum variant and structThe enum variant is named
OpenAi
but the struct is namedOpenAIEmbedderSettings
. This inconsistency makes the code harder to understand and maintain. Consider standardizing on one naming convention.-#[derive(Serialize, Deserialize, Default, Debug, Clone, Eq, PartialEq)] -#[serde(rename_all = "camelCase")] -pub struct OpenAIEmbedderSettings { +#[derive(Serialize, Deserialize, Default, Debug, Clone, Eq, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct OpenAiEmbedderSettings {Also update all references to this struct throughout the file.
285-286
: Documentation error - incorrect environment variableThe documentation for the GenericRestEmbedderSettings mentions
MEILI_OLLAMA_URL
which appears to be a copy-paste error from the OllamaEmbedderSettings section./// Must be parseable as a URL. - /// If not specified, [Meilisearch](https://www.meilisearch.com/) (**not the sdk you are currently using**) will try to fetch the `MEILI_OLLAMA_URL` environment variable + /// If not specified, [Meilisearch](https://www.meilisearch.com/) (**not the sdk you are currently using**) will try to fetch the appropriate environment variable /// Example: `"http://localhost:12345/api/v1/embed"`
330-334
: Incorrect documentation alignmentThe documentation comment appears to be misaligned with the field it describes. The comment about maximum template size should be on the
document_template_max_bytes
field rather than interrupting therequest
field documentation./// ``` - /// The maximum size of a rendered document template. - // - // Longer texts are truncated to fit the configured limit. - /// Default: `400` #[serde(skip_serializing_if = "Option::is_none")] pub document_template_max_bytes: Option<usize>, + /// The maximum size of a rendered document template. + /// + /// Longer texts are truncated to fit the configured limit. + /// Default: `400`
2995-2996
: Suggest additional test coverage for other embedder typesWhile the basic test is good, consider adding additional tests for the other embedder types to ensure they serialize and deserialize correctly. This would help catch issues like the OpenAI/OpenAi capitalization problem earlier.
#[meilisearch_test] async fn test_set_huggingface_embedder_settings(client: Client, index: Index) { let hf_embedder = Embedder::HuggingFace(HuggingFaceEmbedderSettings { model: Some("BAAI/bge-base-en-v1.5".to_string()), document_template: Some("A document titled {{doc.title}}".to_string()), ..Default::default() }); let embeddings = HashMap::from([("hf".into(), hf_embedder)]); let settings = Settings::new().with_embedders(embeddings.clone()); let task_info = index.set_settings(&settings).await.unwrap(); client.wait_for_task(task_info, None, None).await.unwrap(); let res = index.get_embedders().await.unwrap(); assert_eq!(embeddings, res); }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Cache: Disabled due to data retention organization setting
Knowledge Base: Disabled due to data retention organization setting
📒 Files selected for processing (1)
src/settings.rs
(13 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (1)
- GitHub Check: integration-tests
🔇 Additional comments (6)
src/settings.rs (6)
1214-1227
: Proper default handling in get_embedders methodThe implementation of
get_embedders
correctly handles the case where the server returnsNone
by providing an empty HashMap. However, making this more explicit in the documentation would help users understand this behavior.Consider adding a documentation comment explaining that this method will never return
None
, but instead return an empty HashMap if no embedders are configured.
2995-3006
: Well-structured test for embedder settingsThe test for setting embedder settings is well-structured and provides good coverage of the basic functionality. It creates a simple user-provided embedder, sets it using
with_embedders
, and then verifies thatget_embedders
returns the expected configuration.
655-670
: Well-implemented builder method for embeddersThe
with_embedders
method follows the same pattern as other builder methods in the SDK, ensuring consistency for users. It correctly handles the mapping of string keys and preserves the rest of the settings.
1190-1213
: Good documentation with complete example for get_embeddersThe documentation and example for the
get_embedders
method provides a clear, complete example that demonstrates how to use the method, including setting up an embedder first to make the example more realistic.
2575-2606
: Good documentation with example for reset_embeddersThe
reset_embedders
method has clear documentation and a practical example that follows the same pattern as other reset methods in the SDK, maintaining consistency.
39-58
: Well-structured Embedder enum with comprehensive documentationThe
Embedder
enum is well-structured with appropriate serde tags for serialization/deserialization. The documentation for each variant clearly explains its purpose and any performance considerations.
@awyl could you retry please? Should be fixed now |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (1)
src/settings.rs (1)
331-337
: Documentation comment format issueThe documentation for
document_template_max_bytes
mixes both///
and//
comment styles, which can cause inconsistencies in generated documentation.Use consistent
///
style for all documentation comments:/// The maximum size of a rendered document template. -// -// Longer texts are truncated to fit the configured limit. +/// +/// Longer texts are truncated to fit the configured limit. /// Default: `400`
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Lite
Cache: Disabled due to data retention organization setting
Knowledge Base: Disabled due to data retention organization setting
📒 Files selected for processing (1)
src/settings.rs
(13 hunks)
🔇 Additional comments (12)
src/settings.rs (12)
39-60
: Enum variant type matches Meilisearch API requirementsThe
Embedder
enum correctly implements the tagged enum pattern withtag = "source"
for proper serialization. Based on past review comments, the variantOpenAi
with lowercase 'i' (rather thanOpenAI
) matches what the Meilisearch API expects, preventing deserialization errors.
76-115
: Well-structured settings for HuggingFace embeddersThe implementation provides comprehensive documentation and proper serialization. All fields are optional with appropriate
skip_serializing_if
attributes, following the codebase's established patterns.
133-176
: Well-structured settings for OpenAI embeddersThe implementation correctly requires an API key for OpenAI while making other fields optional. Documentation is thorough with examples and default values.
194-243
: Appropriate settings for Ollama embeddersThe
model
field is properly required whileurl
andapi_key
are optional, which aligns with Ollama's configuration requirements.
456-458
: Embedders field added to Settings structThe
embedders
field is appropriately optional and uses aHashMap<String, Embedder>
to support multiple named embedders.
656-671
: Builder method for embedders follows established patternsThe
with_embedders
method follows the same pattern as other builder methods in the struct, allowing for a fluent interface while handling string-like keys.
1191-1228
: Get embedders method with proper default handlingThe
get_embedders
method follows the established pattern in the SDK. It correctly handles the case when no embedders are configured by returning an empty map rather thanNone
.
2576-2607
: Reset embedders method implementationThe implementation of
reset_embedders
follows the same pattern as other reset methods in the codebase, using the DELETE HTTP method and expecting a 202 response.
2775-2780
: Test for retrieving embeddersThe test confirms that
get_embedders
correctly returns an empty HashMap when no embedders are configured.
2808-2816
: Test for resetting embeddersThe test properly verifies the functionality of
reset_embedders
by checking that the API returns an empty HashMap after reset.
2994-3007
: Comprehensive test for setting embedder settingsThe test demonstrates setting a
UserProvided
embedder via theSettings
struct and verifies that the changes are correctly applied.
1-3163
:✅ Verification successful
Implementation note: Missing dedicated set_embedders method
While you can set embedders through the general
set_settings
method, there's no dedicatedset_embedders
method like there is for other settings. According to the PR objectives, this was intentional since "set_embedders was not functional upstream at the time of development."When the upstream API supports this functionality, you might want to add a dedicated method for consistency with the rest of the API.
You should verify that the upstream Meilisearch API now properly supports setting embedders directly before adding a dedicated method:
🏁 Script executed:
#!/bin/bash # Check the current Meilisearch docs for embedder PATCH support curl -s "https://www.meilisearch.com/docs/api/settings/embedders" | grep -o "PATCH" || echo "PATCH not found in embedders documentation"Length of output: 176
Upstream API Does Not Support Direct
embedders
PATCHVerified the Meilisearch documentation at https://www.meilisearch.com/docs/api/settings/embedders – there is no
PATCH
method listed for embedders. Until the upstream API adds a dedicated endpoint to update embedders, using the generalset_settings
is correct and no dedicatedset_embedders
method is needed.
Thank you, it works perfectly. |
Pull Request
Related issue
Fixes #541
Fixes #612
Fixes #621
Fixes #646
What does this PR do?
Adds the required settings
with_embedders
does use the same "API" (not usingimpl AsRef
for items passed) aswith_synonyms
, as this is the closest existingset_embedders
has not been implemented upstream (at least when I try toPATCH
the object, it does not work){get,reset}_embedders
settings have been implemented.Said implementation goes with the work done in Implement vector search experimental feature v2 (v1.6) meilisearch-python#924
adds the
hybrid
field to search via the vector search to add an end-to-end test of this feature with thehuggingface
configuration.userProvided
seens more brittle, but we may want change to this insteadusing
userProvided
instead would mean (at the cost of hardcoding stuff)=> lower cpu effort
=> no higher timeout necceeseary
=> aligning with
meilisearch/meilisearch
to only have a test foruserProvided
)TODO:
PR checklist
Please check if your PR fulfills the following requirements:
Thank you so much for contributing to Meilisearch!
Summary by CodeRabbit
New Features
Documentation
Tests