Skip to content

Conversation

Strift
Copy link
Collaborator

@Strift Strift commented Aug 15, 2025

Pull Request

Related issue

Fixes #1995

What does this PR do?

  • Update REST embedder type to include optional indexingFragments
  • Update search request type to include optional searchFragments
  • Add tests for multi-modal search (requires VITE_VOYAGE_API_KEY from VoyageAI)
  • Add code sample for Meilisearch documentation

PR checklist

Please check if your PR fulfills the following requirements:

  • Does this PR fix an existing issue, or have you listed the changes applied in the PR description (and why they are needed)?
  • Have you read the contributing guidelines?
  • Have you made sure that the title is accurate and descriptive of the changes?

Thank you so much for contributing to Meilisearch!

Summary by CodeRabbit

  • New Features

    • Added experimental flags: chatCompletions, compositeEmbedders, multimodal.
    • Search accepts richer media payloads and embedder configs now support additional indexing/search fragments.
  • Tests

    • Added end-to-end multimodal search tests (text, image, combined) with a new movies fixture; tests run conditionally on an env API key.
  • Documentation

    • Added a code sample demonstrating hybrid search with media (text + base64 image).
  • Chores

    • Test config loads environment variables via Vite.

Copy link

coderabbitai bot commented Aug 15, 2025

Walkthrough

Expands runtime experimental feature flags, adds types for media and embedder fragments, introduces multimodal end-to-end tests and fixtures, updates Vite test env loading for tests, and adds a documentation code sample for multimodal/hybrid search.

Changes

Cohort / File(s) Summary
Experimental features types
src/types/experimental-features.ts
Added chatCompletions?, compositeEmbedders?, and multimodal? to RuntimeTogglableFeatures and reordered fields; type-level public API change.
Core multimodal types
src/types/types.ts
Added MediaBinary, MediaPayload; added SearchParams.media?: MediaPayload; added EmbedderFragments; extended RestEmbedder with indexingFragments? and searchFragments?.
Experimental features tests
tests/experimental-features.test.ts
Updated test fixtures and teardown to include new flags (chatCompletions, compositeEmbedders, multimodal); assertions modified to match updated feature set.
Multimodal E2E tests & fixtures
tests/multi_modal_search.test.ts, tests/fixtures/movies.json
New multimodal embedder E2E tests (Voyage-based, conditional on VOYAGE_API_KEY), base64 helper, and movies fixture dataset.
Test env configuration
vite.config.ts
Added loadEnv import and env: loadEnv("test", process.cwd()) to test config to expose test env vars.
Documentation samples
.code-samples.meilisearch.yaml
Added search_parameter_reference_media_1 example demonstrating hybrid search with media payload (text + base64 image).

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant Test as Test Suite
  participant SDK as JS SDK
  participant MS as Meilisearch

  rect rgba(232,240,255,0.6)
    Test->>SDK: PATCH /experimental-features { multimodal: true }
    SDK->>MS: PUT /experimental-features
    MS-->>SDK: 200 OK
  end

  rect rgba(255,250,220,0.6)
    Test->>SDK: PATCH /indexes/{uid}/settings (embedder with indexingFragments & searchFragments)
    SDK->>MS: PATCH /indexes/{uid}/settings
    MS-->>SDK: 202 Accepted
  end

  rect rgba(255,238,255,0.6)
    Test->>SDK: POST /indexes/{uid}/documents (movies fixture)
    SDK->>MS: POST /indexes/{uid}/documents
    MS-->>SDK: 202 Accepted
  end

  rect rgba(221,255,221,0.6)
    Test->>SDK: POST /indexes/{uid}/search (q and/or media payload)
    SDK->>MS: POST /indexes/{uid}/search (media + fragments)
    MS-->>SDK: 200 OK (multimodal-ranked hits)
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Assessment against linked issues

Objective Addressed Explanation
Update settings methods to allow configuring indexingFragments and searchFragments (#1995) Types for fragments added, but no settings method implementation or runtime request-shaping changes in this diff.
Add new test cases (#1995)
Add code sample in .code-samples.meilisearch.yaml under search_parameter_reference_media_1 (#1995)

Assessment against linked issues: Out-of-scope changes

Code Change Explanation
Added env: loadEnv("test", process.cwd()) in vite.config.ts (vite.config.ts) Test environment loading change affects test runner configuration and exposure of env vars; not required by the fragment/settings API objective.

Possibly related PRs

Suggested reviewers

  • nicolasvienot
  • flevi29

Poem

I thump on keys and nibble code,
Fragments hop where images flowed.
Text and poster pair in tune,
Vectors hum beneath the moon.
A rabbit ships the multimodal bloom. 🐇✨


📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 77d704e and 880c889.

📒 Files selected for processing (1)
  • vite.config.ts (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • vite.config.ts
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: integration-tests (Node.js 20)
  • GitHub Check: integration-tests (Node.js 22)
✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/add-multi-modal-search

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore or @coderabbit ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

codecov bot commented Aug 15, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 98.83%. Comparing base (a15460f) to head (880c889).

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #1998   +/-   ##
=======================================
  Coverage   98.83%   98.83%           
=======================================
  Files          19       19           
  Lines        1550     1550           
  Branches      334      334           
=======================================
  Hits         1532     1532           
  Misses         18       18           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@Strift Strift added the enhancement New feature or request label Aug 21, 2025
@Strift
Copy link
Collaborator Author

Strift commented Aug 21, 2025

Waiting for #2000 to fix tests

@Strift
Copy link
Collaborator Author

Strift commented Sep 3, 2025

@coderabbitai review

Copy link

coderabbitai bot commented Sep 3, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link

@coderabbitai coderabbitai bot left a 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

🧹 Nitpick comments (6)
src/types/types.ts (2)

240-244: Clarify and alias the media payload type to reduce ambiguity and improve reuse

Current shape is correct but opaque. Consider aliasing and documenting the expected forms (URL vs base64 blob) and that data should be base64-encoded. This avoids repetition and helps downstream users.

Apply within-range change:

-    media?: Record<
-      string,
-      Record<string, string | { mime: string; data: string }>
-    >;
+    media?: MediaPayload;

Add these type aliases near other shared types:

// Media payloads can be either:
// - string: typically an absolute URL or data: URL
// - object: base64 data with explicit MIME
export type MediaBinary = { mime: string; data: string };
export type MediaPayload = Record<string, Record<string, string | MediaBinary>>;

594-606: Deduplicate fragment shapes with a shared alias and add a brief doc

Both fragment maps share the same structure. Use a single alias to keep them in sync and add a short comment to clarify intent.

Apply within-range change:

-  indexingFragments?: Record<
-    string,
-    {
-      value: RecordAny;
-    }
-  >;
-  searchFragments?: Record<
-    string,
-    {
-      value: RecordAny;
-    }
-  >;
+  indexingFragments?: EmbedderFragments;
+  searchFragments?: EmbedderFragments;

Add alongside other embedder types:

/** Name → JSON fragment injected into the REST embedder's request */
export type EmbedderFragments = Record<string, { value: RecordAny }>;
tests/fixtures/movies.json (1)

1-90: Avoid network flakiness from remote poster URLs in tests

External TMDB URLs can cause nondeterministic failures (rate limits, outages). Prefer local fixture images or embedded base64 for stability.

If keeping URLs, gate network usage behind a flag and fall back to local placeholders when offline.

tests/multi_modal_search.test.ts (3)

168-174: Keep search API usage consistent; avoid mixing q in options with positional query.

Prefer passing the query via the first argument (as done in other tests) for clarity.

-const response = await searchClient.index(INDEX_UID).search(null, {
-  q: query,
+const response = await searchClient.index(INDEX_UID).search(query, {

Reference: Search endpoint uses q in the HTTP body; SDK maps the first arg to it. (meilisearch.com)


119-133: Clean up test artifacts.

Add an afterAll to delete the index so repeated CI runs don’t accumulate test indexes.

 describe.skipIf(!VOYAGE_API_KEY)("Multi-modal search", () => {
   let searchClient: Meilisearch;
@@
   beforeAll(async () => {
     const client = await getClient("Admin");
@@
     searchClient = await getClient("Search");
   });
+
+  afterAll(async () => {
+    const client = await getClient("Admin");
+    await client.index(INDEX_UID).delete();
+  });

116-149: Flakiness guard looks good; consider recording why expectations map to these movies.

Assertions depend on the movies fixture semantics. If the fixture changes, tests may fail. Consider pinning the exact records (ids) in comments to aid maintenance.

Also applies to: 151-166, 168-189

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between a15460f and 490b1de.

⛔ Files ignored due to path filters (1)
  • tests/fixtures/master-yoda.jpeg is excluded by !**/*.jpeg
📒 Files selected for processing (6)
  • src/types/experimental-features.ts (1 hunks)
  • src/types/types.ts (2 hunks)
  • tests/experimental-features.test.ts (1 hunks)
  • tests/fixtures/movies.json (1 hunks)
  • tests/multi_modal_search.test.ts (1 hunks)
  • vite.config.ts (2 hunks)
🧰 Additional context used
🪛 Biome (2.1.2)
tests/multi_modal_search.test.ts

[error] 4-4: Expected a semicolon or an implicit semicolon after a statement, but found none

An explicit or implicit semicolon is expected here...

...Which is required to end this statement

(parse)


[error] 4-4: Expected a semicolon or an implicit semicolon after a statement, but found none

An explicit or implicit semicolon is expected here...

...Which is required to end this statement

(parse)

🔇 Additional comments (7)
vite.config.ts (1)

1-1: Import looks good

The new loadEnv import is appropriate for Vitest env hydration.

src/types/experimental-features.ts (1)

7-16: New toggles align with 1.16 features

Additions for chatCompletions, compositeEmbedders, and multimodal look consistent and non-breaking.

tests/experimental-features.test.ts (2)

9-18: Teardown coverage is complete

Disabling all toggles via a mapped type ensures future additions surface as compile errors. Good pattern.


23-32: Enablement matrix LGTM

The positive matrix mirrors the teardown and validates round-trip correctness.

tests/multi_modal_search.test.ts (3)

24-24: Embedding dimension 1024 matches voyage-multimodal-3.

Good choice; the model’s embedding dimension is 1024.

Reference: Voyage docs list voyage-multimodal-3 embedding dimension as 1024. (docs.voyageai.com)


64-100: Fragment shapes look correct for Voyage (text, image_url, image_base64).

indexingFragments/searchFragments use the right content schema and types for Voyage. Ensure media in queries matches exactly one search fragment at a time to avoid server errors.

Reference: Meilisearch multimodal fragments and media matching rules. (meilisearch.dev, meilisearch.com)


65-77: Confirm image_base64 usage matches Voyage’s expected format.

The type: "image_base64" with a data:{mime};base64,{data} string is valid for Voyage; just ensure the base64 is prefixed as you do here.

Reference: Voyage FAQ example uses type: "image_base64" with a data-URL string. (docs.voyageai.com)

@Strift Strift marked this pull request as ready for review September 3, 2025 09:52
Copy link

@coderabbitai coderabbitai bot left a 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)
tests/multi_modal_search.test.ts (1)

106-110: REST embedder uses input, not inputs — request will 400.

Voyage’s endpoint expects input. Using inputs breaks requests and the fragments won’t be embedded.

   request: {
     // This request object matches the Voyage API request object
-    inputs: ["{{fragment}}", "{{..}}"],
+    input: ["{{fragment}}", "{{..}}"],
     model: "voyage-multimodal-3",
   },
🧹 Nitpick comments (7)
src/types/types.ts (2)

214-231: Clarify MediaBinary payload (no data URL prefix).

To avoid misuse, explicitly state that data must be raw base64 without a data: prefix (you already prepend it in fragments).

 export type MediaBinary = {
   /** MIME type of the file */
   mime: string;
-  /** Base64-encoded data of the file */
+  /** Base64-encoded payload only (no `data:` prefix). */
   data: string;
 };

601-626: Make EmbedderFragments generic for better type inference.

Keeps backward compatibility while letting consumers narrow value to a model-specific shape.

-export type EmbedderFragments = Record<string, { value: RecordAny }>;
+export type EmbedderFragments<T = RecordAny> = Record<string, { value: T }>;
tests/multi_modal_search.test.ts (5)

4-4: Keep JSON import assertion for Node 18; fix Biome parsing instead.

Node 18 requires assert { type: "json" }. Address the Biome parse error via tooling (upgrade Biome to a version that supports import attributes or exclude this test file from Biome parsing), rather than changing the import.

Example biome.json tweak (pick one):

  • Ignore tests: { "files": { "ignore": ["tests/**/*.test.ts"] } }
  • Or upgrade Biome and enable import attributes support in parser if available in your version.

125-133: Make index deletion idempotent to avoid flakiness on fresh runs.

-    // Delete the index if it already exists
-    await client.index(INDEX_UID).delete().waitTask();
+    // Delete the index if it already exists (ignore 404)
+    try {
+      await client.index(INDEX_UID).delete().waitTask();
+    } catch (e) {
+      if ((e as any)?.code !== "index_not_found") throw e;
+    }

125-141: Clean up experimental flag after tests to avoid leaking state.

Add this after the suite:

afterAll(async () => {
  const client = await getClient("Admin");
  try {
    await client.updateExperimentalFeatures({ multimodal: false });
  } catch {}
  try {
    await client.index(INDEX_UID).delete().waitTask();
  } catch (e) {
    if ((e as any)?.code !== "index_not_found") throw e;
  }
});

176-195: Be consistent: pass the query via the first argument instead of mixing q in options.

-    const response = await searchClient.index(INDEX_UID).search(null, {
-      q: query,
+    const response = await searchClient.index(INDEX_UID).search(query, {
       media: {
         textAndPoster: {
           text: query,
           image: {
             mime: "image/jpeg",
             data: masterYodaBase64,
           },
         },
       },
       hybrid: {
         embedder: EMBEDDER_NAME,
         semanticRatio: 1,
       },
     });

95-104: Minor naming nit: prefer url over repeating poster.poster.

Purely cosmetic, but media.poster.url reads clearer than media.poster.poster. If you agree, rename the property and update the corresponding image_url template.

Also applies to: 163-167

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 490b1de and 8456e21.

📒 Files selected for processing (2)
  • src/types/types.ts (4 hunks)
  • tests/multi_modal_search.test.ts (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-09-03T09:36:56.618Z
Learnt from: Strift
PR: meilisearch/meilisearch-js#1998
File: tests/multi_modal_search.test.ts:4-4
Timestamp: 2025-09-03T09:36:56.618Z
Learning: Import assertions with `assert { type: "json" }` are required for JSON imports in Node.js 18 and should not be removed for Node.js 18 compatibility.

Applied to files:

  • tests/multi_modal_search.test.ts
🧬 Code graph analysis (1)
tests/multi_modal_search.test.ts (1)
src/types/types.ts (1)
  • Embedder (661-668)
🪛 Biome (2.1.2)
tests/multi_modal_search.test.ts

[error] 4-4: Expected a semicolon or an implicit semicolon after a statement, but found none

An explicit or implicit semicolon is expected here...

...Which is required to end this statement

(parse)


[error] 4-4: Expected a semicolon or an implicit semicolon after a statement, but found none

An explicit or implicit semicolon is expected here...

...Which is required to end this statement

(parse)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: integration-tests (Node.js 20)
  • GitHub Check: integration-tests (Node.js 22)
🔇 Additional comments (4)
src/types/types.ts (3)

233-235: MediaPayload shape is appropriate and flexible.


262-262: Adding media to SearchParams is correct; omitting it from GET params is prudent.


639-641: Fragments supported on RestEmbedder look good.

This aligns with 1.16 multi-modal; no issues spotted.

tests/multi_modal_search.test.ts (1)

12-21: Good ESM-safe file path resolution.

Using dirname(fileURLToPath(import.meta.url)) avoids __dirname pitfalls under ESM/Vitest.

@Strift Strift self-assigned this Sep 3, 2025
@Strift Strift requested review from flevi29 and a team September 3, 2025 10:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[v1.16.0] Add support for multi-modal search
2 participants