diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 530085b3..bc3cefd0 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -29,7 +29,7 @@ jobs: - name: Meilisearch (latest) setup with Docker run: docker run -d -p 7700:7700 getmeili/meilisearch:latest meilisearch --master-key=masterKey --no-analytics - name: Build and run unit and integration tests - run: ./gradlew build integrationTest + run: ./gradlew build integrationTest --info - name: Archive test report uses: actions/upload-artifact@v4 if: failure() diff --git a/src/main/java/com/meilisearch/sdk/Index.java b/src/main/java/com/meilisearch/sdk/Index.java index ee0431a4..828fbe68 100644 --- a/src/main/java/com/meilisearch/sdk/Index.java +++ b/src/main/java/com/meilisearch/sdk/Index.java @@ -1249,4 +1249,42 @@ public SimilarDocumentsResults searchSimilarDocuments(SimilarDocumentRequest que query, SimilarDocumentsResults.class); } + + /** + * Gets the embedders settings of the index + * + * @return a Map that contains all embedders settings + * @throws MeilisearchException if an error occurs + * @see API + * specification + */ + public Map getEmbeddersSettings() throws MeilisearchException { + return this.settingsHandler.getEmbedders(this.uid); + } + + /** + * Updates the embedders settings of the index + * + * @param embedders a Map that contains the new embedders settings + * @return TaskInfo instance + * @throws MeilisearchException if an error occurs + * @see API + * specification + */ + public TaskInfo updateEmbeddersSettings(Map embedders) + throws MeilisearchException { + return this.settingsHandler.updateEmbedders(this.uid, embedders); + } + + /** + * Resets the embedders settings of the index + * + * @return TaskInfo instance + * @throws MeilisearchException if an error occurs + * @see API + * specification + */ + public TaskInfo resetEmbeddersSettings() throws MeilisearchException { + return this.settingsHandler.resetEmbedders(this.uid); + } } diff --git a/src/main/java/com/meilisearch/sdk/IndexSearchRequest.java b/src/main/java/com/meilisearch/sdk/IndexSearchRequest.java index 3ba8eef4..8e35ab3e 100644 --- a/src/main/java/com/meilisearch/sdk/IndexSearchRequest.java +++ b/src/main/java/com/meilisearch/sdk/IndexSearchRequest.java @@ -38,13 +38,14 @@ public class IndexSearchRequest { private FederationOptions federationOptions; protected String[] locales; protected String distinct; + protected Boolean retrieveVectors; /** * Constructor for MultiSearchRequest for building search queries with the default values: * offset: 0, limit: 20, attributesToRetrieve: ["*"], attributesToCrop: null, cropLength: 200, * attributesToHighlight: null, filter: null, showMatchesPosition: false, facets: null, sort: * null, showRankingScore: false, showRankingScoreDetails: false, rankingScoreThreshold: null - * distinct: null + * distinct: null, retrieveVectors: false * * @param indexUid uid of the requested index String */ @@ -106,7 +107,8 @@ public String toString() { .putOpt("rankingScoreThreshold", this.rankingScoreThreshold) .putOpt("attributesToSearchOn", this.attributesToSearchOn) .putOpt("locales", this.locales) - .putOpt("distinct", this.distinct); + .putOpt("distinct", this.distinct) + .putOpt("retrieveVectors", this.retrieveVectors); return jsonObject.toString(); } diff --git a/src/main/java/com/meilisearch/sdk/SearchRequest.java b/src/main/java/com/meilisearch/sdk/SearchRequest.java index 77650ad5..43e043cc 100644 --- a/src/main/java/com/meilisearch/sdk/SearchRequest.java +++ b/src/main/java/com/meilisearch/sdk/SearchRequest.java @@ -1,5 +1,6 @@ package com.meilisearch.sdk; +import com.meilisearch.sdk.model.Hybrid; import com.meilisearch.sdk.model.MatchingStrategy; import lombok.AccessLevel; import lombok.AllArgsConstructor; @@ -42,12 +43,15 @@ public class SearchRequest { protected Double rankingScoreThreshold; protected String[] locales; protected String distinct; - + protected Hybrid hybrid; + protected Double[] vector; + protected Boolean retrieveVectors; /** * Constructor for SearchRequest for building search queries with the default values: offset: 0, * limit: 20, attributesToRetrieve: ["*"], attributesToCrop: null, cropLength: 200, * attributesToHighlight: null, filter: null, showMatchesPosition: false, facets: null, sort: - * null, showRankingScore: false, showRankingScoreDetails: false, rankingScoreThreshold: null + * null, showRankingScore: false, showRankingScoreDetails: false, rankingScoreThreshold: null, + * retrieveVectors: false * * @param q Query String */ @@ -104,7 +108,13 @@ public String toString() { .putOpt("showRankingScoreDetails", this.showRankingScoreDetails) .putOpt("rankingScoreThreshold", this.rankingScoreThreshold) .putOpt("locales", this.locales) - .putOpt("distinct", this.distinct); + .putOpt("distinct", this.distinct) + .putOpt("vector", this.vector) + .putOpt("retrieveVectors", this.retrieveVectors); + + if (this.hybrid != null) { + jsonObject.put("hybrid", this.hybrid.toJSONObject()); + } return jsonObject.toString(); } diff --git a/src/main/java/com/meilisearch/sdk/SettingsHandler.java b/src/main/java/com/meilisearch/sdk/SettingsHandler.java index c70f9f80..9ee73a3a 100644 --- a/src/main/java/com/meilisearch/sdk/SettingsHandler.java +++ b/src/main/java/com/meilisearch/sdk/SettingsHandler.java @@ -2,6 +2,7 @@ import com.meilisearch.sdk.exceptions.MeilisearchException; import com.meilisearch.sdk.http.URLBuilder; +import com.meilisearch.sdk.model.Embedder; import com.meilisearch.sdk.model.Faceting; import com.meilisearch.sdk.model.LocalizedAttribute; import com.meilisearch.sdk.model.Pagination; @@ -769,4 +770,47 @@ public TaskInfo resetNonSeparatorTokensSettings(String uid) { return httpClient.delete( settingsPath(uid).addSubroute("non-separator-tokens").getURL(), TaskInfo.class); } + + /** + * Gets the embedders settings of the index + * + * @param uid Index identifier + * @return a Map that contains all embedders settings + * @throws MeilisearchException if an error occurs + */ + Map getEmbedders(String uid) throws MeilisearchException { + return httpClient.get( + settingsPath(uid).addSubroute("embedders").getURL(), + Map.class, + String.class, + Embedder.class); + } + + /** + * Updates the embedders settings of the index + * + * @param uid Index identifier + * @param embedders a Map that contains the new embedders settings + * @return TaskInfo instance + * @throws MeilisearchException if an error occurs + */ + TaskInfo updateEmbedders(String uid, Map embedders) + throws MeilisearchException { + return httpClient.patch( + settingsPath(uid).addSubroute("embedders").getURL(), + embedders == null ? null : httpClient.jsonHandler.encode(embedders), + TaskInfo.class); + } + + /** + * Resets the embedders settings of the index + * + * @param uid Index identifier + * @return TaskInfo instance + * @throws MeilisearchException if an error occurs + */ + TaskInfo resetEmbedders(String uid) throws MeilisearchException { + return httpClient.delete( + settingsPath(uid).addSubroute("embedders").getURL(), TaskInfo.class); + } } diff --git a/src/main/java/com/meilisearch/sdk/SimilarDocumentRequest.java b/src/main/java/com/meilisearch/sdk/SimilarDocumentRequest.java index 99ad5f46..a8dc1191 100644 --- a/src/main/java/com/meilisearch/sdk/SimilarDocumentRequest.java +++ b/src/main/java/com/meilisearch/sdk/SimilarDocumentRequest.java @@ -31,18 +31,17 @@ public SimilarDocumentRequest() {} @Override public String toString() { - JSONObject jsonObject = - new JSONObject() - .put("id", this.id) - .put("embedder", this.embedder) - .put("attributesToRetrieve", this.attributesToRetrieve) - .put("offset", this.offset) - .put("limit", this.limit) - .put("filter", this.filter) - .put("showRankingScore", this.showRankingScore) - .put("showRankingScoreDetails", this.showRankingScoreDetails) - .put("rankingScoreThreshold", this.rankingScoreThreshold) - .put("retrieveVectors", this.retrieveVectors); + JSONObject jsonObject = new JSONObject(); + jsonObject.putOpt("id", this.id); + jsonObject.putOpt("embedder", this.embedder); + jsonObject.putOpt("attributesToRetrieve", this.attributesToRetrieve); + jsonObject.putOpt("offset", this.offset); + jsonObject.putOpt("limit", this.limit); + jsonObject.putOpt("filter", this.filter); + jsonObject.putOpt("showRankingScore", this.showRankingScore); + jsonObject.putOpt("showRankingScoreDetails", this.showRankingScoreDetails); + jsonObject.putOpt("rankingScoreThreshold", this.rankingScoreThreshold); + jsonObject.putOpt("retrieveVectors", this.retrieveVectors); return jsonObject.toString(); } diff --git a/src/main/java/com/meilisearch/sdk/model/Embedder.java b/src/main/java/com/meilisearch/sdk/model/Embedder.java new file mode 100644 index 00000000..7179428d --- /dev/null +++ b/src/main/java/com/meilisearch/sdk/model/Embedder.java @@ -0,0 +1,73 @@ +package com.meilisearch.sdk.model; + +import com.fasterxml.jackson.annotation.JsonInclude; +import java.util.Map; +import lombok.*; +import lombok.experimental.Accessors; + +@Builder +@AllArgsConstructor(access = AccessLevel.PACKAGE) +@Getter +@Setter +@Accessors(chain = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +public class Embedder { + /** Source of the embedder. Accepts: ollama, rest, openAI, huggingFace and userProvided */ + protected EmbedderSource source; + + /** + * API key for authentication with the embedder service. Optional: Only applicable for openAi, + * ollama, and rest sources. + */ + protected String apiKey; + + /** + * Model to use for generating embeddings. Optional: Only applicable for ollama, openAI, and + * huggingFace sources. + */ + protected String model; + + /** Template for document embedding. Optional. */ + protected String documentTemplate; + + /** + * Dimensions of the embedding vectors. Optional: Only applicable for openAi, huggingFace, + * ollama, and rest sources. + */ + protected Integer dimensions; + + /** Distribution configuration. Optional. */ + protected EmbedderDistribution distribution; + + /** Request configuration. Mandatory only when using rest embedder, optional otherwise. */ + protected Map request; + + /** Response configuration. Mandatory only when using rest embedder, optional otherwise. */ + protected Map response; + + /** Maximum bytes for document template. Optional. */ + protected Integer documentTemplateMaxBytes; + + /** Revision identifier. Optional: Only applicable for huggingFace. */ + protected String revision; + + /** HTTP headers. Optional: Only applicable for rest. */ + protected Map headers; + + /** Whether to use binary quantization. Optional. */ + protected Boolean binaryQuantized; + + /** URL for the embedder service. Optional. */ + protected String url; + + /** Input fields for the embedder. Optional. */ + protected String[] inputField; + + /** Type of input for the embedder. Optional. */ + protected EmbedderInputType inputType; + + /** Query for the embedder. Optional. */ + protected String query; + + public Embedder() {} +} diff --git a/src/main/java/com/meilisearch/sdk/model/EmbedderDistribution.java b/src/main/java/com/meilisearch/sdk/model/EmbedderDistribution.java new file mode 100644 index 00000000..e99480bb --- /dev/null +++ b/src/main/java/com/meilisearch/sdk/model/EmbedderDistribution.java @@ -0,0 +1,56 @@ +package com.meilisearch.sdk.model; + +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.experimental.Accessors; + +/** + * Describes the natural distribution of search results for embedders. Contains mean and sigma + * values, each between 0 and 1. + */ +@Builder +@AllArgsConstructor(access = AccessLevel.PACKAGE) +@NoArgsConstructor(access = AccessLevel.PUBLIC) +@Getter +@Setter +@Accessors(chain = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +public class EmbedderDistribution { + /** Mean value of the distribution, between 0 and 1 */ + private Double mean; + + /** Sigma (standard deviation) value of the distribution, between 0 and 1 */ + private Double sigma; + + /** + * Creates a uniform distribution with default values + * + * @return An EmbedderDistribution instance with mean=0.5 and sigma=0.5 + */ + public static EmbedderDistribution uniform() { + return new EmbedderDistribution().setMean(0.5).setSigma(0.5); + } + + /** + * Creates a custom distribution with specified mean and sigma values + * + * @param mean Mean value between 0 and 1 + * @param sigma Sigma value between 0 and 1 + * @return An EmbedderDistribution instance with the specified values + * @throws IllegalArgumentException if mean or sigma are outside the valid range + */ + public static EmbedderDistribution custom(double mean, double sigma) { + if (mean < 0 || mean > 1) { + throw new IllegalArgumentException("Mean must be between 0 and 1"); + } + if (sigma < 0 || sigma > 1) { + throw new IllegalArgumentException("Sigma must be between 0 and 1"); + } + return new EmbedderDistribution().setMean(mean).setSigma(sigma); + } +} diff --git a/src/main/java/com/meilisearch/sdk/model/Embedders.java b/src/main/java/com/meilisearch/sdk/model/Embedders.java deleted file mode 100644 index 99fa7408..00000000 --- a/src/main/java/com/meilisearch/sdk/model/Embedders.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.meilisearch.sdk.model; - -import lombok.*; -import lombok.experimental.Accessors; - -@Builder -@AllArgsConstructor(access = AccessLevel.PACKAGE) -@Getter -@Setter -@Accessors(chain = true) -public class Embedders { - protected EmbedderSource source; - protected String url; - protected String apiKey; - protected String model; - protected String documentTemplate; - protected Integer dimensions; - protected String revision; - protected String[] inputField; - protected EmbedderInputType inputType; - protected String query; - - public Embedders() {} -} diff --git a/src/main/java/com/meilisearch/sdk/model/Hybrid.java b/src/main/java/com/meilisearch/sdk/model/Hybrid.java new file mode 100644 index 00000000..62cdd1bb --- /dev/null +++ b/src/main/java/com/meilisearch/sdk/model/Hybrid.java @@ -0,0 +1,48 @@ +package com.meilisearch.sdk.model; + +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.experimental.Accessors; +import org.json.JSONObject; + +/** Hybrid search configuration */ +@Builder +@AllArgsConstructor(access = AccessLevel.PACKAGE) +@NoArgsConstructor(access = AccessLevel.PACKAGE) +@Getter +@Setter +@Accessors(chain = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +public class Hybrid { + /** Semantic ratio for hybrid search (between 0 and 1) */ + private Double semanticRatio; + + /** Embedder to use for hybrid search (mandatory if hybrid is set) */ + private String embedder; + + /** + * Method that returns the JSON representation of the Hybrid object + * + * @return JSONObject representation of the Hybrid object + */ + public JSONObject toJSONObject() { + return new JSONObject() + .putOpt("semanticRatio", this.semanticRatio) + .putOpt("embedder", this.embedder); + } + + /** + * Method that returns the JSON String of the Hybrid object + * + * @return JSON String of the Hybrid object + */ + @Override + public String toString() { + return toJSONObject().toString(); + } +} diff --git a/src/main/java/com/meilisearch/sdk/model/SearchResult.java b/src/main/java/com/meilisearch/sdk/model/SearchResult.java index a77f30a5..ef8c424a 100644 --- a/src/main/java/com/meilisearch/sdk/model/SearchResult.java +++ b/src/main/java/com/meilisearch/sdk/model/SearchResult.java @@ -22,6 +22,7 @@ public class SearchResult implements Searchable { int offset; int limit; int estimatedTotalHits; + HashMap _vectors; public SearchResult() {} } diff --git a/src/main/java/com/meilisearch/sdk/model/Settings.java b/src/main/java/com/meilisearch/sdk/model/Settings.java index 2a4233b6..419bd09f 100644 --- a/src/main/java/com/meilisearch/sdk/model/Settings.java +++ b/src/main/java/com/meilisearch/sdk/model/Settings.java @@ -31,7 +31,7 @@ public class Settings { protected Integer searchCutoffMs; protected String[] separatorTokens; protected String[] nonSeparatorTokens; - protected HashMap embedders; + protected HashMap embedders; protected LocalizedAttribute[] localizedAttributes; public Settings() {} diff --git a/src/test/java/com/meilisearch/integration/SearchTest.java b/src/test/java/com/meilisearch/integration/SearchTest.java index 86c17d92..c89a752d 100644 --- a/src/test/java/com/meilisearch/integration/SearchTest.java +++ b/src/test/java/com/meilisearch/integration/SearchTest.java @@ -1090,9 +1090,9 @@ public void testMultiSearchWithMergeFacets() { public void testSimilarDocuments() throws Exception { String indexUid = "SimilarDocuments"; Index index = client.index(indexUid); - HashMap embedders = new HashMap<>(); + HashMap embedders = new HashMap<>(); embedders.put( - "manual", new Embedders().setSource(EmbedderSource.USER_PROVIDED).setDimensions(3)); + "manual", new Embedder().setSource(EmbedderSource.USER_PROVIDED).setDimensions(3)); Settings settings = new Settings(); settings.setEmbedders(embedders); @@ -1116,6 +1116,81 @@ public void testSimilarDocuments() throws Exception { assertThat(hits.get(3).get("title"), is("Shazam!")); } + /** Test vector search */ + @Test + public void testVectorSearch() throws Exception { + String indexUid = "testVectorSearch"; + Index index = client.index(indexUid); + HashMap embedders = new HashMap<>(); + embedders.put( + "manual", new Embedder().setSource(EmbedderSource.USER_PROVIDED).setDimensions(3)); + + Settings settings = new Settings(); + settings.setEmbedders(embedders); + + index.updateSettings(settings); + + TestData testData = this.getTestData(VECTOR_MOVIES, Movie.class); + TaskInfo task = index.addDocuments(testData.getRaw()); + + index.waitForTask(task.getTaskUid()); + + SearchRequest searchRequest = + SearchRequest.builder() + .vector(new Double[] {0.1, 0.6, 0.8}) + .hybrid(Hybrid.builder().semanticRatio(0.5).embedder("manual").build()) + .build(); + + SearchResult searchResult = (SearchResult) index.search(searchRequest); + + assertThat(searchResult.getHits(), hasSize(5)); + // The most similar document should be "Escape Room" since its vector [0.1, 0.6, 0.8] + assertThat(searchResult.getHits().get(0).get("id"), is("522681")); + assertThat(searchResult.getHits().get(0).get("title"), is("Escape Room")); + } + + /** Test vector search with retrieveVectors option */ + @Test + public void testVectorSearchWithRetrieveVectors() throws Exception { + String indexUid = "testVectorSearchWithRetrieveVectors"; + Index index = client.index(indexUid); + HashMap embedders = new HashMap<>(); + embedders.put( + "manual", new Embedder().setSource(EmbedderSource.USER_PROVIDED).setDimensions(3)); + + Settings settings = new Settings(); + settings.setEmbedders(embedders); + + index.updateSettings(settings); + + TestData testData = this.getTestData(VECTOR_MOVIES, Movie.class); + TaskInfo task = index.addDocuments(testData.getRaw()); + + index.waitForTask(task.getTaskUid()); + + SearchRequest searchRequest = + SearchRequest.builder() + .vector(new Double[] {0.1, 0.6, 0.8}) + .hybrid(Hybrid.builder().semanticRatio(0.5).embedder("manual").build()) + .retrieveVectors(true) + .build(); + + SearchResult searchResult = (SearchResult) index.search(searchRequest); + + assertThat(searchResult.getHits(), hasSize(5)); + // The most similar document should be "Escape Room" since its vector [0.1, 0.6, 0.8] + assertThat(searchResult.getHits().get(0).get("id"), is("522681")); + assertThat(searchResult.getHits().get(0).get("title"), is("Escape Room")); + + // Verify that vectors are returned in the response + Map escapeRoomHit = searchResult.getHits().get(0); + assertThat(escapeRoomHit.containsKey("_vectors"), is(true)); + + @SuppressWarnings("unchecked") + Map vectors = (Map) escapeRoomHit.get("_vectors"); + assertThat(vectors.containsKey("manual"), is(true)); + } + /** Test Search with locales */ @Test public void testSearchWithLocales() throws Exception { @@ -1191,4 +1266,45 @@ public void testMultiSearchWithLocales() throws Exception { assertThat(results[0].getHits().size(), is(7)); assertThat(results[1].getHits().size(), is(2)); } + + /** Test search with retrieveVectors parameter */ + @Test + public void testSearchWithRetrieveVectors() throws Exception { + String indexUid = "testSearchWithRetrieveVectors"; + Index index = client.index(indexUid); + HashMap embedders = new HashMap<>(); + embedders.put( + "manual", new Embedder().setSource(EmbedderSource.USER_PROVIDED).setDimensions(3)); + + Settings settings = new Settings(); + settings.setEmbedders(embedders); + + index.updateSettings(settings); + + TestData testData = this.getTestData(VECTOR_MOVIES, Movie.class); + TaskInfo task = index.addDocuments(testData.getRaw()); + + index.waitForTask(task.getTaskUid()); + + // First search without retrieveVectors + SearchRequest searchRequestWithout = SearchRequest.builder().q("").build(); + SearchResult searchResultWithout = (SearchResult) index.search(searchRequestWithout); + + assertThat(searchResultWithout.getHits(), hasSize(5)); + Map hitWithout = searchResultWithout.getHits().get(0); + assertThat(hitWithout.containsKey("_vectors"), is(false)); + + // Then search with retrieveVectors + SearchRequest searchRequestWith = + SearchRequest.builder().q("").retrieveVectors(true).build(); + SearchResult searchResultWith = (SearchResult) index.search(searchRequestWith); + + assertThat(searchResultWith.getHits(), hasSize(5)); + Map hitWith = searchResultWith.getHits().get(0); + assertThat(hitWith.containsKey("_vectors"), is(true)); + + @SuppressWarnings("unchecked") + Map vectors = (Map) hitWith.get("_vectors"); + assertThat(vectors.containsKey("manual"), is(true)); + } } diff --git a/src/test/java/com/meilisearch/integration/SettingsTest.java b/src/test/java/com/meilisearch/integration/SettingsTest.java index b345bdac..10979242 100644 --- a/src/test/java/com/meilisearch/integration/SettingsTest.java +++ b/src/test/java/com/meilisearch/integration/SettingsTest.java @@ -16,6 +16,8 @@ import com.meilisearch.integration.classes.AbstractIT; import com.meilisearch.integration.classes.TestData; import com.meilisearch.sdk.Index; +import com.meilisearch.sdk.model.Embedder; +import com.meilisearch.sdk.model.EmbedderSource; import com.meilisearch.sdk.model.FacetSortValue; import com.meilisearch.sdk.model.Faceting; import com.meilisearch.sdk.model.LocalizedAttribute; @@ -1465,4 +1467,59 @@ public void testResetNonSeparatorTokensSettings() throws Exception { nonSeparatorTokensAfterReset, is(arrayWithSize(initialNonSeparatorTokens.length))); assertThat(nonSeparatorTokensAfterReset, is(equalTo(initialNonSeparatorTokens))); } + + @Test + @DisplayName("Test get embedders settings by uid") + public void testGetEmbeddersSettings() throws Exception { + Index index = createIndex("testGetEmbeddersSettings"); + Settings initialSettings = index.getSettings(); + Map initialEmbedders = index.getEmbeddersSettings(); + + assertThat(initialEmbedders, is(equalTo(initialSettings.getEmbedders()))); + } + + @Test + @DisplayName("Test update embedders settings") + public void testUpdateEmbeddersSettings() throws Exception { + Index index = createEmptyIndex("testUpdateEmbeddersSettings"); + + // Update settings + HashMap newEmbedders = new HashMap<>(); + Embedder userProvidedEmbedder = + new Embedder().setSource(EmbedderSource.USER_PROVIDED).setDimensions(768); + newEmbedders.put("custom", userProvidedEmbedder); + TaskInfo task = index.updateEmbeddersSettings(newEmbedders); + index.waitForTask(task.getTaskUid()); + + // Verify results + Map updatedEmbedders = index.getEmbeddersSettings(); + assertThat(updatedEmbedders.size(), is(equalTo(1))); + Embedder retrievedUser = updatedEmbedders.get("custom"); + assertThat(retrievedUser.getSource(), is(equalTo(EmbedderSource.USER_PROVIDED))); + assertThat(retrievedUser.getDimensions(), is(equalTo(768))); + } + + @Test + @DisplayName("Test reset embedders settings") + public void testResetEmbeddersSettings() throws Exception { + // Create and set new embedders + Index index = createEmptyIndex("testResetEmbeddersSettings"); + HashMap embedders = new HashMap<>(); + Embedder userProvidedEmbedder = + new Embedder().setSource(EmbedderSource.USER_PROVIDED).setDimensions(768); + embedders.put("custom", userProvidedEmbedder); + + // Update settings + TaskInfo updateTask = index.updateEmbeddersSettings(embedders); + index.waitForTask(updateTask.getTaskUid()); + Map updatedEmbedders = index.getEmbeddersSettings(); + assertThat(updatedEmbedders.size(), is(equalTo(1))); + assertThat(updatedEmbedders.containsKey("custom"), is(true)); + + // Reset settings + TaskInfo resetTask = index.resetEmbeddersSettings(); + index.waitForTask(resetTask.getTaskUid()); + Map resetEmbedders = index.getEmbeddersSettings(); + assertThat(resetEmbedders.size(), is(equalTo(0))); + } } diff --git a/src/test/java/com/meilisearch/integration/TasksTest.java b/src/test/java/com/meilisearch/integration/TasksTest.java index 0eec58d1..2690c8d9 100644 --- a/src/test/java/com/meilisearch/integration/TasksTest.java +++ b/src/test/java/com/meilisearch/integration/TasksTest.java @@ -138,6 +138,22 @@ public void testClientGetTaskErrorWhenAddingDocuments() throws Exception { /** Test Get Tasks with limit and from */ @Test public void testClientGetTasksLimitAndFrom() throws Exception { + // Create several indexes to make sure we have enough tasks + String indexUid1 = "GetClientTasksLimitFrom1"; + String indexUid2 = "GetClientTasksLimitFrom2"; + String indexUid3 = "GetClientTasksLimitFrom3"; + String indexUid4 = "GetClientTasksLimitFrom4"; + + // Create indexes to generate tasks + TaskInfo response1 = client.createIndex(indexUid1); + client.waitForTask(response1.getTaskUid()); + TaskInfo response2 = client.createIndex(indexUid2); + client.waitForTask(response2.getTaskUid()); + TaskInfo response3 = client.createIndex(indexUid3); + client.waitForTask(response3.getTaskUid()); + TaskInfo response4 = client.createIndex(indexUid4); + client.waitForTask(response4.getTaskUid()); + int limit = 2; int from = 2; TasksQuery query = new TasksQuery().setLimit(limit).setFrom(from); @@ -147,7 +163,7 @@ public void testClientGetTasksLimitAndFrom() throws Exception { assertThat(result.getFrom(), is(equalTo(from))); assertThat(result.getFrom(), is(notNullValue())); assertThat(result.getNext(), is(notNullValue())); - assertThat(result.getResults().length, is(notNullValue())); + assertThat(result.getResults().length, is(equalTo(limit))); } /** Test Get Tasks with uid as filter */ diff --git a/src/test/java/com/meilisearch/sdk/SearchRequestTest.java b/src/test/java/com/meilisearch/sdk/SearchRequestTest.java index 6eb9f850..ca004a30 100644 --- a/src/test/java/com/meilisearch/sdk/SearchRequestTest.java +++ b/src/test/java/com/meilisearch/sdk/SearchRequestTest.java @@ -5,7 +5,9 @@ import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.nullValue; +import com.meilisearch.sdk.model.Hybrid; import com.meilisearch.sdk.model.MatchingStrategy; +import org.json.JSONObject; import org.junit.jupiter.api.Test; class SearchRequestTest { @@ -363,4 +365,73 @@ void toStringEveryParametersWithArrayMatchingStrategyNull() { assertThat(classToTest.getDistinct(), is(equalTo("distinct"))); assertThat(classToTest.toString(), is(equalTo(expectedToString))); } + + @Test + void toStringWithHybrid() { + Hybrid hybrid = Hybrid.builder().semanticRatio(0.5).embedder("default").build(); + + SearchRequest classToTest = new SearchRequest("This is a Test").setHybrid(hybrid); + + String expected = + "{\"q\":\"This is a Test\",\"hybrid\":{\"semanticRatio\":0.5,\"embedder\":\"default\"}}"; + assertThat(classToTest.toString(), is(equalTo(expected))); + + // Verify getters + assertThat(classToTest.getHybrid().getSemanticRatio(), is(equalTo(0.5))); + assertThat(classToTest.getHybrid().getEmbedder(), is(equalTo("default"))); + } + + @Test + void toStringWithHybridUsingBuilder() { + SearchRequest classToTest = + SearchRequest.builder() + .q("This is a Test") + .hybrid(Hybrid.builder().semanticRatio(0.5).embedder("default").build()) + .build(); + + String expected = + "{\"q\":\"This is a Test\",\"hybrid\":{\"semanticRatio\":0.5,\"embedder\":\"default\"}}"; + assertThat(classToTest.toString(), is(equalTo(expected))); + + // Verify getters + assertThat(classToTest.getHybrid().getSemanticRatio(), is(equalTo(0.5))); + assertThat(classToTest.getHybrid().getEmbedder(), is(equalTo("default"))); + } + + @Test + void toStringWithHybridAndOtherParameters() { + SearchRequest classToTest = + SearchRequest.builder() + .q("This is a Test") + .offset(200) + .limit(900) + .hybrid( + Hybrid.builder() + .semanticRatio(0.7) + .embedder("custom-embedder") + .build()) + .build(); + + String expected = + "{\"q\":\"This is a Test\",\"hybrid\":{\"semanticRatio\":0.7,\"embedder\":\"custom-embedder\"},\"offset\":200,\"limit\":900}"; + assertThat(classToTest.toString(), is(equalTo(expected))); + } + + @Test + void toStringWithHybridOnlyEmbedder() { + SearchRequest classToTest = + new SearchRequest("This is a Test") + .setHybrid(Hybrid.builder().embedder("default").build()); + + String expected = "{\"q\":\"This is a Test\",\"hybrid\":{\"embedder\":\"default\"}}"; + assertThat(classToTest.toString(), is(equalTo(expected))); + } + + @Test + void toStringWithRetrieveVectors() { + SearchRequest searchRequest = new SearchRequest("test").setRetrieveVectors(true); + String result = searchRequest.toString(); + JSONObject json = new JSONObject(result); + assertThat(json.getBoolean("retrieveVectors"), is(true)); + } } diff --git a/src/test/java/com/meilisearch/sdk/SimilarDocumentRequestTest.java b/src/test/java/com/meilisearch/sdk/SimilarDocumentRequestTest.java new file mode 100644 index 00000000..52435b82 --- /dev/null +++ b/src/test/java/com/meilisearch/sdk/SimilarDocumentRequestTest.java @@ -0,0 +1,184 @@ +package com.meilisearch.sdk; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.nullValue; + +import org.json.JSONObject; +import org.junit.jupiter.api.Test; + +class SimilarDocumentRequestTest { + + @Test + void toStringSimpleRequest() { + SimilarDocumentRequest classToTest = new SimilarDocumentRequest().setId("123"); + + JSONObject jsonObject = new JSONObject(classToTest.toString()); + assertThat(jsonObject.getString("id"), is(equalTo("123"))); + } + + @Test + void toStringWithEmbedder() { + SimilarDocumentRequest classToTest = + new SimilarDocumentRequest().setId("123").setEmbedder("custom"); + + JSONObject jsonObject = new JSONObject(classToTest.toString()); + assertThat(jsonObject.getString("id"), is(equalTo("123"))); + assertThat(jsonObject.getString("embedder"), is(equalTo("custom"))); + } + + @Test + void toStringWithAttributesToRetrieve() { + SimilarDocumentRequest classToTest = + new SimilarDocumentRequest() + .setId("123") + .setAttributesToRetrieve(new String[] {"title", "description"}); + + JSONObject jsonObject = new JSONObject(classToTest.toString()); + assertThat(jsonObject.getString("id"), is(equalTo("123"))); + assertThat( + jsonObject.getJSONArray("attributesToRetrieve").getString(0), is(equalTo("title"))); + assertThat( + jsonObject.getJSONArray("attributesToRetrieve").getString(1), + is(equalTo("description"))); + } + + @Test + void toStringWithOffsetAndLimit() { + SimilarDocumentRequest classToTest = + new SimilarDocumentRequest().setId("123").setOffset(10).setLimit(20); + + JSONObject jsonObject = new JSONObject(classToTest.toString()); + assertThat(jsonObject.getString("id"), is(equalTo("123"))); + assertThat(jsonObject.getInt("offset"), is(equalTo(10))); + assertThat(jsonObject.getInt("limit"), is(equalTo(20))); + } + + @Test + void toStringWithFilter() { + SimilarDocumentRequest classToTest = + new SimilarDocumentRequest().setId("123").setFilter("genre = 'action'"); + + JSONObject jsonObject = new JSONObject(classToTest.toString()); + assertThat(jsonObject.getString("id"), is(equalTo("123"))); + assertThat(jsonObject.getString("filter"), is(equalTo("genre = 'action'"))); + } + + @Test + void toStringWithShowRankingScore() { + SimilarDocumentRequest classToTest = + new SimilarDocumentRequest().setId("123").setShowRankingScore(true); + + JSONObject jsonObject = new JSONObject(classToTest.toString()); + assertThat(jsonObject.getString("id"), is(equalTo("123"))); + assertThat(jsonObject.getBoolean("showRankingScore"), is(equalTo(true))); + } + + @Test + void toStringWithShowRankingScoreDetails() { + SimilarDocumentRequest classToTest = + new SimilarDocumentRequest().setId("123").setShowRankingScoreDetails(true); + + JSONObject jsonObject = new JSONObject(classToTest.toString()); + assertThat(jsonObject.getString("id"), is(equalTo("123"))); + assertThat(jsonObject.getBoolean("showRankingScoreDetails"), is(equalTo(true))); + } + + @Test + void toStringWithRankingScoreThreshold() { + SimilarDocumentRequest classToTest = + new SimilarDocumentRequest().setId("123").setRankingScoreThreshold(0.5); + + JSONObject jsonObject = new JSONObject(classToTest.toString()); + assertThat(jsonObject.getString("id"), is(equalTo("123"))); + assertThat(jsonObject.getDouble("rankingScoreThreshold"), is(equalTo(0.5))); + } + + @Test + void toStringWithRetrieveVectors() { + SimilarDocumentRequest classToTest = + new SimilarDocumentRequest().setId("123").setRetrieveVectors(true); + + JSONObject jsonObject = new JSONObject(classToTest.toString()); + assertThat(jsonObject.getString("id"), is(equalTo("123"))); + assertThat(jsonObject.getBoolean("retrieveVectors"), is(equalTo(true))); + } + + @Test + void toStringWithAllParameters() { + SimilarDocumentRequest classToTest = + new SimilarDocumentRequest() + .setId("123") + .setEmbedder("custom") + .setAttributesToRetrieve(new String[] {"title", "description"}) + .setOffset(10) + .setLimit(20) + .setFilter("genre = 'action'") + .setShowRankingScore(true) + .setShowRankingScoreDetails(true) + .setRankingScoreThreshold(0.5) + .setRetrieveVectors(true); + + JSONObject jsonObject = new JSONObject(classToTest.toString()); + assertThat(jsonObject.getString("id"), is(equalTo("123"))); + assertThat(jsonObject.getString("embedder"), is(equalTo("custom"))); + assertThat( + jsonObject.getJSONArray("attributesToRetrieve").getString(0), is(equalTo("title"))); + assertThat( + jsonObject.getJSONArray("attributesToRetrieve").getString(1), + is(equalTo("description"))); + assertThat(jsonObject.getInt("offset"), is(equalTo(10))); + assertThat(jsonObject.getInt("limit"), is(equalTo(20))); + assertThat(jsonObject.getString("filter"), is(equalTo("genre = 'action'"))); + assertThat(jsonObject.getBoolean("showRankingScore"), is(equalTo(true))); + assertThat(jsonObject.getBoolean("showRankingScoreDetails"), is(equalTo(true))); + assertThat(jsonObject.getDouble("rankingScoreThreshold"), is(equalTo(0.5))); + assertThat(jsonObject.getBoolean("retrieveVectors"), is(equalTo(true))); + } + + @Test + void gettersAndSetters() { + SimilarDocumentRequest classToTest = + new SimilarDocumentRequest() + .setId("123") + .setEmbedder("custom") + .setAttributesToRetrieve(new String[] {"title", "description"}) + .setOffset(10) + .setLimit(20) + .setFilter("genre = 'action'") + .setShowRankingScore(true) + .setShowRankingScoreDetails(true) + .setRankingScoreThreshold(0.5) + .setRetrieveVectors(true); + + assertThat(classToTest.getId(), is(equalTo("123"))); + assertThat(classToTest.getEmbedder(), is(equalTo("custom"))); + assertThat( + classToTest.getAttributesToRetrieve(), + is(equalTo(new String[] {"title", "description"}))); + assertThat(classToTest.getOffset(), is(equalTo(10))); + assertThat(classToTest.getLimit(), is(equalTo(20))); + assertThat(classToTest.getFilter(), is(equalTo("genre = 'action'"))); + assertThat(classToTest.getShowRankingScore(), is(equalTo(true))); + assertThat(classToTest.getShowRankingScoreDetails(), is(equalTo(true))); + assertThat(classToTest.getRankingScoreThreshold(), is(equalTo(0.5))); + assertThat(classToTest.getRetrieveVectors(), is(equalTo(true))); + } + + @Test + void defaultValues() { + SimilarDocumentRequest classToTest = new SimilarDocumentRequest(); + + assertThat(classToTest.getId(), is(nullValue())); + assertThat(classToTest.getEmbedder(), is(nullValue())); + assertThat(classToTest.getAttributesToRetrieve(), is(nullValue())); + assertThat(classToTest.getOffset(), is(nullValue())); + assertThat(classToTest.getLimit(), is(nullValue())); + assertThat(classToTest.getFilter(), is(nullValue())); + assertThat(classToTest.getShowRankingScore(), is(nullValue())); + assertThat(classToTest.getShowRankingScoreDetails(), is(nullValue())); + assertThat(classToTest.getRankingScoreThreshold(), is(nullValue())); + assertThat(classToTest.getRetrieveVectors(), is(nullValue())); + } +}