diff --git a/.code-samples.meilisearch.yaml b/.code-samples.meilisearch.yaml index 35697bcf..94872534 100644 --- a/.code-samples.meilisearch.yaml +++ b/.code-samples.meilisearch.yaml @@ -722,7 +722,14 @@ search_parameter_reference_retrieve_vectors_1: |- $client->index('INDEX_NAME')->search('kitchen utensils', [ 'retrieveVectors' => true, 'hybrid' => [ - 'embedder': 'default' + 'embedder': 'EMBEDDER_NAME' + ] + ]); +search_parameter_guide_hybrid_1: |- + $client->index('INDEX_NAME')->search('kitchen utensils', [ + 'hybrid' => [ + 'semanticRatio' => 0.9, + 'embedder' => 'EMBEDDER_NAME' ] ]); search_parameter_reference_ranking_score_threshold_1: |- @@ -742,7 +749,7 @@ distinct_attribute_guide_distinct_parameter_1: |- search_parameter_guide_matching_strategy_3: |- $client->index('movies')->search('white shirt', ['matchingStrategy' => 'frequency']); get_similar_post_1: |- - $similarQuery = new SimilarDocumentsQuery('TARGET_DOCUMENT_ID'); + $similarQuery = new SimilarDocumentsQuery('TARGET_DOCUMENT_ID', 'default'); $client->index('INDEX_NAME')->searchSimilarDocuments($similarQuery); multi_search_federated_1: |- $client->multiSearch([ @@ -756,7 +763,7 @@ multi_search_federated_1: |- (new MultiSearchFederation()) ); search_parameter_reference_locales_1: |- - $client->index('INDEX_NAME')->search('進撃の巨人', [ + $client->index('INDEX_NAME')->search('QUERY TEXT IN JAPANESE', [ 'locales' => ['jpn'] ]); get_localized_attribute_settings_1: |- diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index b3d04cde..afa864e5 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -69,7 +69,7 @@ jobs: name: integration-tests (PHP ${{ matrix.php-version }}) (${{ matrix.http-client }}) services: meilisearch: - image: getmeili/meilisearch:latest + image: getmeili/meilisearch:v1.11.0 ports: - '7700:7700' env: diff --git a/docker-compose.yml b/docker-compose.yml index 97838e8a..2f74ff20 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -14,7 +14,7 @@ services: - ./:/home/package meilisearch: - image: getmeili/meilisearch:latest + image: getmeili/meilisearch:v1.10.0 ports: - "7700" environment: diff --git a/phpunit.xml.dist.bak b/phpunit.xml.dist.bak new file mode 100644 index 00000000..b9d5e31d --- /dev/null +++ b/phpunit.xml.dist.bak @@ -0,0 +1,25 @@ + + + + + ./tests + + + + + src/ + + + + + + + diff --git a/src/Contracts/MultiSearchFederation.php b/src/Contracts/MultiSearchFederation.php index 3f9db1ec..d998aa37 100644 --- a/src/Contracts/MultiSearchFederation.php +++ b/src/Contracts/MultiSearchFederation.php @@ -16,6 +16,16 @@ class MultiSearchFederation */ private ?int $offset = null; + /** + * @var array>|null + */ + private ?array $facetsByIndex = null; + + /** + * @var array{maxValuesPerFacet: positive-int}|null + */ + private ?array $mergeFacets = null; + /** * @param non-negative-int $limit * @@ -40,10 +50,36 @@ public function setOffset(int $offset): self return $this; } + /** + * @param array> $facetsByIndex + * + * @return $this + */ + public function setFacetsByIndex(array $facetsByIndex): self + { + $this->facetsByIndex = $facetsByIndex; + + return $this; + } + + /** + * @param array{maxValuesPerFacet: positive-int} $mergeFacets + * + * @return $this + */ + public function setMergeFacets(array $mergeFacets): self + { + $this->mergeFacets = $mergeFacets; + + return $this; + } + /** * @return array{ * limit?: non-negative-int, - * offset?: non-negative-int + * offset?: non-negative-int, + * facetsByIndex?: array>, + * mergeFacets?: array{maxValuesPerFacet: positive-int}, * } */ public function toArray(): array @@ -51,6 +87,8 @@ public function toArray(): array return array_filter([ 'limit' => $this->limit, 'offset' => $this->offset, + 'facetsByIndex' => $this->facetsByIndex, + 'mergeFacets' => $this->mergeFacets, ], static function ($item) { return null !== $item; }); } } diff --git a/src/Contracts/SimilarDocumentsQuery.php b/src/Contracts/SimilarDocumentsQuery.php index ab6dc95b..04c65ae3 100644 --- a/src/Contracts/SimilarDocumentsQuery.php +++ b/src/Contracts/SimilarDocumentsQuery.php @@ -11,6 +11,11 @@ class SimilarDocumentsQuery */ private $id; + /** + * @var non-empty-string + */ + private string $embedder; + /** * @var non-negative-int|null */ @@ -21,11 +26,6 @@ class SimilarDocumentsQuery */ private ?int $limit = null; - /** - * @var non-empty-string|null - */ - private ?string $embedder = null; - /** * @var list|null */ @@ -48,11 +48,13 @@ class SimilarDocumentsQuery private $rankingScoreThreshold; /** - * @param int|string $id + * @param int|string $id + * @param non-empty-string $embedder */ - public function __construct($id) + public function __construct($id, string $embedder) { $this->id = $id; + $this->embedder = $embedder; } /** @@ -91,18 +93,6 @@ public function setFilter(array $filter): self return $this; } - /** - * @param non-empty-string $embedder - * - * @return $this - */ - public function setEmbedder(string $embedder): self - { - $this->embedder = $embedder; - - return $this; - } - /** * @param list $attributesToRetrieve an array of attribute names to retrieve * @@ -166,10 +156,10 @@ public function setRankingScoreThreshold($rankingScoreThreshold): self /** * @return array{ * id: int|string, + * embedder: non-empty-string, * offset?: non-negative-int, * limit?: positive-int, * filter?: array|string>, - * embedder?: non-empty-string, * attributesToRetrieve?: list, * showRankingScore?: bool, * showRankingScoreDetails?: bool, @@ -181,10 +171,10 @@ public function toArray(): array { return array_filter([ 'id' => $this->id, + 'embedder' => $this->embedder, 'offset' => $this->offset, 'limit' => $this->limit, 'filter' => $this->filter, - 'embedder' => $this->embedder, 'attributesToRetrieve' => $this->attributesToRetrieve, 'showRankingScore' => $this->showRankingScore, 'showRankingScoreDetails' => $this->showRankingScoreDetails, diff --git a/tests/Contracts/MultiSearchFederationTest.php b/tests/Contracts/MultiSearchFederationTest.php index 191b3692..ec002b21 100644 --- a/tests/Contracts/MultiSearchFederationTest.php +++ b/tests/Contracts/MultiSearchFederationTest.php @@ -29,4 +29,18 @@ public function testSetOffset(): void self::assertSame(['offset' => 5], $data->toArray()); } + + public function testSetFacetsByIndex(): void + { + $data = (new MultiSearchFederation())->setFacetsByIndex(['books' => ['author', 'genre']]); + + self::assertSame(['facetsByIndex' => ['books' => ['author', 'genre']]], $data->toArray()); + } + + public function testSetMergeFacets(): void + { + $data = (new MultiSearchFederation())->setMergeFacets(['maxValuesPerFacet' => 10]); + + self::assertSame(['mergeFacets' => ['maxValuesPerFacet' => 10]], $data->toArray()); + } } diff --git a/tests/Contracts/SimilarDocumentsQueryTest.php b/tests/Contracts/SimilarDocumentsQueryTest.php index 79785d92..88cbe6e1 100644 --- a/tests/Contracts/SimilarDocumentsQueryTest.php +++ b/tests/Contracts/SimilarDocumentsQueryTest.php @@ -10,54 +10,48 @@ final class SimilarDocumentsQueryTest extends TestCase { /** - * @param int|string $id + * @param int|string $id + * @param non-empty-string $embedder * - * @testWith [123] - * ["test"] + * @testWith [123, "default"] + * ["test", "manual"] */ - public function testConstruct($id): void + public function testConstruct($id, string $embedder): void { - $data = new SimilarDocumentsQuery($id); + $data = new SimilarDocumentsQuery($id, $embedder); - self::assertSame(['id' => $id], $data->toArray()); + self::assertSame(['id' => $id, 'embedder' => $embedder], $data->toArray()); } public function testSetOffset(): void { - $data = (new SimilarDocumentsQuery('test'))->setOffset(66); + $data = (new SimilarDocumentsQuery('test', 'default'))->setOffset(66); - self::assertSame(['id' => 'test', 'offset' => 66], $data->toArray()); + self::assertSame(['id' => 'test', 'embedder' => 'default', 'offset' => 66], $data->toArray()); } public function testSetLimit(): void { - $data = (new SimilarDocumentsQuery('test'))->setLimit(50); + $data = (new SimilarDocumentsQuery('test', 'default'))->setLimit(50); - self::assertSame(['id' => 'test', 'limit' => 50], $data->toArray()); + self::assertSame(['id' => 'test', 'embedder' => 'default', 'limit' => 50], $data->toArray()); } public function testSetFilter(): void { - $data = (new SimilarDocumentsQuery('test'))->setFilter([ + $data = (new SimilarDocumentsQuery('test', 'default'))->setFilter([ ['genres = horror', 'genres = mystery'], "director = 'Jordan Peele'", ]); - self::assertSame(['id' => 'test', 'filter' => [['genres = horror', 'genres = mystery'], "director = 'Jordan Peele'"]], $data->toArray()); - } - - public function testSetEmbedder(): void - { - $data = (new SimilarDocumentsQuery('test'))->setEmbedder('default'); - - self::assertSame(['id' => 'test', 'embedder' => 'default'], $data->toArray()); + self::assertSame(['id' => 'test', 'embedder' => 'default', 'filter' => [['genres = horror', 'genres = mystery'], "director = 'Jordan Peele'"]], $data->toArray()); } public function testSetAttributesToRetrieve(): void { - $data = (new SimilarDocumentsQuery('test'))->setAttributesToRetrieve(['name', 'price']); + $data = (new SimilarDocumentsQuery('test', 'default'))->setAttributesToRetrieve(['name', 'price']); - self::assertSame(['id' => 'test', 'attributesToRetrieve' => ['name', 'price']], $data->toArray()); + self::assertSame(['id' => 'test', 'embedder' => 'default', 'attributesToRetrieve' => ['name', 'price']], $data->toArray()); } /** @@ -66,9 +60,9 @@ public function testSetAttributesToRetrieve(): void */ public function testSetShowRankingScore(bool $showRankingScore): void { - $data = (new SimilarDocumentsQuery('test'))->setShowRankingScore($showRankingScore); + $data = (new SimilarDocumentsQuery('test', 'default'))->setShowRankingScore($showRankingScore); - self::assertSame(['id' => 'test', 'showRankingScore' => $showRankingScore], $data->toArray()); + self::assertSame(['id' => 'test', 'embedder' => 'default', 'showRankingScore' => $showRankingScore], $data->toArray()); } /** @@ -77,9 +71,9 @@ public function testSetShowRankingScore(bool $showRankingScore): void */ public function testSetShowRankingScoreDetails(bool $showRankingScoreDetails): void { - $data = (new SimilarDocumentsQuery('test'))->setShowRankingScoreDetails($showRankingScoreDetails); + $data = (new SimilarDocumentsQuery('test', 'default'))->setShowRankingScoreDetails($showRankingScoreDetails); - self::assertSame(['id' => 'test', 'showRankingScoreDetails' => $showRankingScoreDetails], $data->toArray()); + self::assertSame(['id' => 'test', 'embedder' => 'default', 'showRankingScoreDetails' => $showRankingScoreDetails], $data->toArray()); } /** @@ -88,9 +82,9 @@ public function testSetShowRankingScoreDetails(bool $showRankingScoreDetails): v */ public function testSetRetrieveVectors(bool $retrieveVectors): void { - $data = (new SimilarDocumentsQuery('test'))->setRetrieveVectors($retrieveVectors); + $data = (new SimilarDocumentsQuery('test', 'default'))->setRetrieveVectors($retrieveVectors); - self::assertSame(['id' => 'test', 'retrieveVectors' => $retrieveVectors], $data->toArray()); + self::assertSame(['id' => 'test', 'embedder' => 'default', 'retrieveVectors' => $retrieveVectors], $data->toArray()); } /** @@ -101,8 +95,8 @@ public function testSetRetrieveVectors(bool $retrieveVectors): void */ public function testSetRankingScoreThreshold($rankingScoreThreshold): void { - $data = (new SimilarDocumentsQuery('test'))->setRankingScoreThreshold($rankingScoreThreshold); + $data = (new SimilarDocumentsQuery('test', 'default'))->setRankingScoreThreshold($rankingScoreThreshold); - self::assertSame(['id' => 'test', 'rankingScoreThreshold' => $rankingScoreThreshold], $data->toArray()); + self::assertSame(['id' => 'test', 'embedder' => 'default', 'rankingScoreThreshold' => $rankingScoreThreshold], $data->toArray()); } } diff --git a/tests/Endpoints/MultiSearchTest.php b/tests/Endpoints/MultiSearchTest.php index 0352cae0..9d82ac9c 100644 --- a/tests/Endpoints/MultiSearchTest.php +++ b/tests/Endpoints/MultiSearchTest.php @@ -89,7 +89,7 @@ public function testFederation(): void // By setting the weight to 0.9 this query should appear second ->setFederationOptions((new FederationOptions())->setWeight(0.9)), ], - (new MultiSearchFederation())->setLimit(2) + (new MultiSearchFederation())->setLimit(2)->setFacetsByIndex([$this->booksIndex->getUid() => ['genre'], $this->songsIndex->getUid() => ['duration-float']])->setMergeFacets(['maxValuesPerFacet' => 10]) ); self::assertArrayHasKey('hits', $response); @@ -97,6 +97,7 @@ public function testFederation(): void self::assertArrayHasKey('limit', $response); self::assertArrayHasKey('offset', $response); self::assertArrayHasKey('estimatedTotalHits', $response); + self::assertArrayHasKey('facetDistribution', $response); self::assertCount(2, $response['hits']); self::assertSame(2, $response['limit']); self::assertSame(0, $response['offset']); diff --git a/tests/Endpoints/SearchTest.php b/tests/Endpoints/SearchTest.php index dbd6cac3..eea4046c 100644 --- a/tests/Endpoints/SearchTest.php +++ b/tests/Endpoints/SearchTest.php @@ -701,12 +701,12 @@ public function testVectorSearch(): void $this->assertIsValidPromise($promise); $index->waitForTask($promise['taskUid']); - $response = $index->search('', ['vector' => [-0.5, 0.3, 0.85], 'hybrid' => ['semanticRatio' => 1.0]]); + $response = $index->search('', ['vector' => [-0.5, 0.3, 0.85], 'hybrid' => ['semanticRatio' => 1.0, 'embedder' => 'manual']]); self::assertSame(5, $response->getSemanticHitCount()); self::assertArrayNotHasKey('_vectors', $response->getHit(0)); - $response = $index->search('', ['vector' => [-0.5, 0.3, 0.85], 'hybrid' => ['semanticRatio' => 1.0], 'retrieveVectors' => true]); + $response = $index->search('', ['vector' => [-0.5, 0.3, 0.85], 'hybrid' => ['semanticRatio' => 1.0, 'embedder' => 'manual'], 'retrieveVectors' => true]); self::assertSame(5, $response->getSemanticHitCount()); self::assertArrayHasKey('_vectors', $response->getHit(0)); diff --git a/tests/Endpoints/SimilarDocumentsTest.php b/tests/Endpoints/SimilarDocumentsTest.php index 20ce0048..c5d6e40c 100644 --- a/tests/Endpoints/SimilarDocumentsTest.php +++ b/tests/Endpoints/SimilarDocumentsTest.php @@ -30,14 +30,14 @@ public function testBasicSearchWithSimilarDocuments(): void self::assertSame(1, $response->getHitsCount()); $documentId = $response->getHit(0)['id']; - $response = $this->index->searchSimilarDocuments(new SimilarDocumentsQuery($documentId)); + $response = $this->index->searchSimilarDocuments(new SimilarDocumentsQuery($documentId, 'manual')); self::assertGreaterThanOrEqual(4, $response->getHitsCount()); self::assertArrayNotHasKey('_vectors', $response->getHit(0)); self::assertArrayHasKey('id', $response->getHit(0)); self::assertSame($documentId, $response->getId()); - $similarQuery = new SimilarDocumentsQuery($documentId); + $similarQuery = new SimilarDocumentsQuery($documentId, 'manual'); $response = $this->index->searchSimilarDocuments($similarQuery->setRetrieveVectors(true)); self::assertGreaterThanOrEqual(4, $response->getHitsCount()); self::assertArrayHasKey('_vectors', $response->getHit(0)); diff --git a/tests/Settings/EmbeddersTest.php b/tests/Settings/EmbeddersTest.php new file mode 100644 index 00000000..35497559 --- /dev/null +++ b/tests/Settings/EmbeddersTest.php @@ -0,0 +1,57 @@ +host, getenv('MEILISEARCH_API_KEY')); + $http->patch('/experimental-features', ['vectorStore' => true]); + $this->index = $this->createEmptyIndex($this->safeIndexName()); + } + + public function testGetDefaultEmbedders(): void + { + $response = $this->index->getEmbedders(); + + self::assertSame(self::DEFAULT_EMBEDDER, $response); + } + + public function testUpdateEmbedders(): void + { + $newEmbedders = ['manual' => ['source' => 'userProvided', 'dimensions' => 3, 'binaryQuantized' => true]]; + + $promise = $this->index->updateEmbedders($newEmbedders); + + $this->assertIsValidPromise($promise); + $this->index->waitForTask($promise['taskUid']); + + $embedders = $this->index->getEmbedders(); + + self::assertSame($newEmbedders, $embedders); + } + + public function testResetEmbedders(): void + { + $promise = $this->index->resetEmbedders(); + + $this->assertIsValidPromise($promise); + + $this->index->waitForTask($promise['taskUid']); + $embedders = $this->index->getEmbedders(); + + self::assertSame(self::DEFAULT_EMBEDDER, $embedders); + } +}