Skip to content

Commit 97fb11e

Browse files
committed
feat: Adding support Multimodal embedders.
1 parent 1488a95 commit 97fb11e

File tree

5 files changed

+109
-39
lines changed

5 files changed

+109
-39
lines changed

.code-samples.meilisearch.yaml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -767,3 +767,17 @@ get_batch_1: |-
767767
client.get_batch(BATCH_UID)
768768
get_similar_post_1: |-
769769
client.index("INDEX_NAME").get_similar_documents({"id": "TARGET_DOCUMENT_ID", "embedder": "default"})
770+
search_parameter_reference_media_1: |-
771+
client.index('movies_fragments').search(
772+
"",
773+
{
774+
"media": {
775+
"image_url": "https://image.tmdb.org/t/p/w500/gEU2QniE6E77NI6lCU6MxlNBvIx.jpg"
776+
},
777+
"hybrid": {
778+
"embedder": "voyage",
779+
"semanticRatio": 1.0
780+
},
781+
"limit": 3
782+
}
783+
)

meilisearch/client.py

Lines changed: 33 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -531,6 +531,27 @@ def create_snapshot(self) -> TaskInfo:
531531

532532
return TaskInfo(**task)
533533

534+
def swap_indexes(self, parameters: List[Mapping[str, List[str]]]) -> TaskInfo:
535+
"""Swap two indexes.
536+
537+
Parameters
538+
----------
539+
indexes:
540+
List of indexes to swap (ex: [{"indexes": ["indexA", "indexB"]}).
541+
542+
Returns
543+
-------
544+
task_info:
545+
TaskInfo instance containing information about a task to track the progress of an asynchronous process.
546+
https://www.meilisearch.com/docs/reference/api/tasks#get-one-task
547+
548+
Raises
549+
------
550+
MeilisearchApiError
551+
An error containing details about why Meilisearch can't process your request. Meilisearch error codes are described here: https://www.meilisearch.com/docs/reference/errors/error_codes#meilisearch-errors
552+
"""
553+
return TaskInfo(**self.http.post(self.config.paths.swap, parameters))
554+
534555
def get_tasks(self, parameters: Optional[MutableMapping[str, Any]] = None) -> TaskResults:
535556
"""Get all tasks.
536557
@@ -977,48 +998,21 @@ def _valid_uuid(uuid: str) -> bool:
977998
return bool(match)
978999

9791000
def get_experimental_features(self) -> dict:
980-
"""Get current experimental features settings."""
1001+
"""
1002+
Retrieve the current settings for all experimental features.
1003+
Returns:
1004+
dict: A mapping of feature names to their enabled/disabled state.
1005+
"""
9811006
return self.http.get(self.config.paths.experimental_features)
9821007

9831008
def update_experimental_features(self, features: dict) -> dict:
984-
"""Update experimental features settings."""
985-
return self.http.patch(self.config.paths.experimental_features, body=features)
986-
987-
def enable_multimodal(self) -> dict:
988-
"""Enable multimodal experimental feature."""
989-
return self.update_experimental_features({"multimodal": True})
990-
991-
def disable_multimodal(self) -> dict:
992-
"""Disable multimodal experimental feature."""
993-
return self.update_experimental_features({"multimodal": False})
994-
995-
def swap_indexes(self, swaps: List[Dict[str, list]]) -> TaskInfo:
9961009
"""
997-
Swap or rename indexes in Meilisearch.
998-
This method accepts a list of swap instructions.
999-
Each instruction must contain:
1000-
1001-
- "indexes": a list of exactly two index UIDs
1002-
- "rename" (optional): boolean flag
1003-
* False (default): swap two existing indexes
1004-
* True: rename index_a → index_b (index_b must NOT exist)
1010+
Update one or more experimental features.
10051011
1006-
A single request can perform multiple swap or rename operations.
1007-
All operations in the request are atomic—either all succeed, or none do.
1008-
1009-
Example:
1010-
[
1011-
{"indexes": ["A", "B"]},
1012-
{"indexes": ["C_tmp", "C"], "rename": True}
1013-
]
1014-
1015-
Returns
1016-
-------
1017-
TaskInfo
1018-
Task information for the asynchronous swap/rename task.
1012+
Args:
1013+
features (dict): A dictionary mapping feature names to booleans.
1014+
For example, {"multimodal": True} to enable multimodal.
1015+
Returns:
1016+
dict: The updated experimental features settings.
10191017
"""
1020-
if not swaps or not all("indexes" in s and len(s["indexes"]) == 2 for s in swaps):
1021-
raise ValueError("Each swap must contain exactly two index UIDs under 'indexes' key.")
1022-
1023-
task = self.http.post("/swap-indexes",swaps)
1024-
return TaskInfo(**task)
1018+
return self.http.patch(self.config.paths.experimental_features, body=features)

meilisearch/config.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ class Paths:
4747
localized_attributes = "localized-attributes"
4848
edit = "edit"
4949
network = "network"
50+
experimental_features = "experimental-features"
5051

5152
def __init__(
5253
self,

meilisearch/models/embedders.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,10 @@ class RestEmbedder(CamelBase):
167167
Template defining the data Meilisearch sends to the embedder
168168
document_template_max_bytes: Optional[int]
169169
Maximum allowed size of rendered document template (defaults to 400)
170+
indexing_fragments: Optional[Dict[str, Dict[str, str]]]
171+
Defines how to fragment documents for indexing (multi-modal search)
172+
search_fragments: Optional[Dict[str, Dict[str, str]]]
173+
Defines how to fragment search queries (multi-modal search)
170174
request: Dict[str, Any]
171175
A JSON value representing the request Meilisearch makes to the remote embedder
172176
response: Dict[str, Any]
@@ -185,6 +189,8 @@ class RestEmbedder(CamelBase):
185189
dimensions: Optional[int] = None
186190
document_template: Optional[str] = None
187191
document_template_max_bytes: Optional[int] = None
192+
indexing_fragments: Optional[Dict[str, Dict[str, str]]] = None
193+
search_fragments: Optional[Dict[str, Dict[str, str]]] = None
188194
request: Dict[str, Any]
189195
response: Dict[str, Any]
190196
headers: Optional[Dict[str, str]] = None
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
"""Tests for experimental features API."""
2+
3+
4+
def test_get_experimental_features(client):
5+
"""Test getting experimental features."""
6+
response = client.get_experimental_features()
7+
assert isinstance(response, dict)
8+
assert "multimodal" in response or "vectorStoreSetting" in response
9+
10+
11+
def test_update_experimental_features(client):
12+
"""Test updating experimental features."""
13+
# Enable multimodal
14+
response = client.update_experimental_features({"multimodal": True})
15+
assert isinstance(response, dict)
16+
assert response.get("multimodal") is True
17+
18+
# Disable multimodal
19+
response = client.update_experimental_features({"multimodal": False})
20+
assert isinstance(response, dict)
21+
assert response.get("multimodal") is False
22+
23+
24+
def test_enable_multimodal(client):
25+
"""Test enabling multimodal experimental feature."""
26+
response = client.enable_multimodal()
27+
assert isinstance(response, dict)
28+
assert response.get("multimodal") is True
29+
30+
# Verify it's enabled
31+
features = client.get_experimental_features()
32+
assert features.get("multimodal") is True
33+
34+
35+
def test_disable_multimodal(client):
36+
"""Test disabling multimodal experimental feature."""
37+
# First enable it
38+
client.enable_multimodal()
39+
40+
# Then disable it
41+
response = client.disable_multimodal()
42+
assert isinstance(response, dict)
43+
assert response.get("multimodal") is False
44+
45+
# Verify it's disabled
46+
features = client.get_experimental_features()
47+
assert features.get("multimodal") is False
48+
49+
50+
def test_update_multiple_experimental_features(client):
51+
"""Test updating multiple experimental features at once."""
52+
response = client.update_experimental_features({"multimodal": True, "vectorStoreSetting": True})
53+
assert isinstance(response, dict)
54+
# At least one should be accepted (depending on Meilisearch version)
55+
assert "multimodal" in response or "vectorStoreSetting" in response

0 commit comments

Comments
 (0)