Skip to content

Commit

Permalink
Add hybrid search for v1.6.0 (#608)
Browse files Browse the repository at this point in the history
* Add embedders management methods

* Fix vector search calls after v1.6
  • Loading branch information
brunoocasali authored Jan 15, 2024
1 parent c5ae8b9 commit 264620e
Show file tree
Hide file tree
Showing 6 changed files with 163 additions and 23 deletions.
15 changes: 15 additions & 0 deletions src/Contracts/Index/Embedders.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

declare(strict_types=1);

namespace Meilisearch\Contracts\Index;

use Meilisearch\Contracts\Data;

class Embedders extends Data implements \JsonSerializable
{
public function jsonSerialize(): object
{
return (object) $this->getIterator()->getArrayCopy();
}
}
3 changes: 3 additions & 0 deletions src/Contracts/Index/Settings.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ public function __construct(array $data = [])
$data['synonyms'] = new Synonyms($data['synonyms'] ?? []);
$data['typoTolerance'] = new TypoTolerance($data['typoTolerance'] ?? []);
$data['faceting'] = new Faceting($data['faceting'] ?? []);
if (\array_key_exists('embedders', $data)) {
$data['embedders'] = new Embedders($data['embedders'] ?? []);
}

parent::__construct($data);
}
Expand Down
17 changes: 17 additions & 0 deletions src/Endpoints/Delegates/HandlesSettings.php
Original file line number Diff line number Diff line change
Expand Up @@ -361,4 +361,21 @@ public function resetProximityPrecision(): array
{
return $this->http->delete(self::PATH.'/'.$this->uid.'/settings/proximity-precision');
}

// Settings - Experimental: Embedders (hybrid search)

public function getEmbedders(): array
{
return $this->http->get(self::PATH.'/'.$this->uid.'/settings/embedders');
}

public function updateEmbedders(array $embedders): array
{
return $this->http->patch(self::PATH.'/'.$this->uid.'/settings/embedders', $embedders);
}

public function resetEmbedders(): array
{
return $this->http->delete(self::PATH.'/'.$this->uid.'/settings/embedders');
}
}
16 changes: 6 additions & 10 deletions tests/Endpoints/SearchTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -692,19 +692,15 @@ public function testVectorSearch(): void
{
$http = new Client($this->host, getenv('MEILISEARCH_API_KEY'));
$http->patch('/experimental-features', ['vectorStore' => true]);
$index = $this->createEmptyIndex($this->safeIndexName());

$response = $this->index->addDocuments([
['id' => 32, 'title' => 'The Witcher', '_vectors' => [1, 0.3]],
['id' => 32, 'title' => 'Interestellar', '_vectors' => [0.5, 0.53]],
]);
$this->index->waitForTask($response['taskUid']);
$promise = $index->updateEmbedders(['default' => ['source' => 'userProvided', 'dimensions' => 1]]);
$this->assertIsValidPromise($promise);
$index->waitForTask($promise['taskUid']);

$response = $this->index->search('', ['vector' => [1, 0.5921]]);
$hit = $response->getHits()[0];
$response = $index->search('', ['vector' => [1], 'hybrid' => ['semanticRatio' => 1.0]]);

$this->assertEquals($hit['title'], 'Interestellar');
$this->assertEquals($hit['_vectors'], [0.5, 0.53]);
$this->assertArrayHasKey('_semanticScore', $hit);
$this->assertEmpty($response->getHits());
}

public function testShowRankingScoreDetails(): void
Expand Down
104 changes: 104 additions & 0 deletions tests/Settings/EmbeddersTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
<?php

declare(strict_types=1);

namespace Tests\Settings;

use Meilisearch\Http\Client;
use Tests\TestCase;

final class EmbeddersTest extends TestCase
{
protected function setUp(): void
{
parent::setUp();

$http = new Client($this->host, getenv('MEILISEARCH_API_KEY'));
$http->patch('/experimental-features', ['vectorStore' => true]);
}

public function testGetEmbedders(): void
{
$index = $this->createEmptyIndex($this->safeIndexName('books-1'));
$embedders = $index->getEmbedders();

$this->assertEquals([], $embedders);
}

public function testUpdateEmbeddersWithOpenAi(): void
{
$embedderConfig = [
'source' => 'openAi',
'apiKey' => '<your-OpenAI-API-key>',
'model' => 'text-embedding-ada-002',
'documentTemplate' => "A movie titled '{{doc.title}}' whose description starts with {{doc.overview|truncatewords: 20}}",
];
$index = $this->createEmptyIndex($this->safeIndexName());

$promise = $index->updateEmbedders(['myEmbedder' => $embedderConfig]);

$this->assertIsValidPromise($promise);
$index->waitForTask($promise['taskUid']);

$embedders = $index->getEmbedders();

$this->assertEquals($embedderConfig, $embedders['myEmbedder']);
}

public function testUpdateEmbeddersWithUserProvided(): void
{
$embedderConfig = [
'source' => 'userProvided',
'dimensions' => 1,
];
$index = $this->createEmptyIndex($this->safeIndexName());

$promise = $index->updateEmbedders(['myEmbedder' => $embedderConfig]);

$this->assertIsValidPromise($promise);
$index->waitForTask($promise['taskUid']);

$embedders = $index->getEmbedders();

$this->assertEquals($embedderConfig, $embedders['myEmbedder']);
}

public function testUpdateEmbeddersWithHuggingFace(): void
{
$embedderConfig = [
'source' => 'huggingFace',
'model' => 'sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2',
'documentTemplate' => "A movie titled '{{doc.title}}' whose description starts with {{doc.overview|truncatewords: 20}}",
];
$index = $this->createEmptyIndex($this->safeIndexName());

$promise = $index->updateEmbedders(['myEmbedder' => $embedderConfig]);

$this->assertIsValidPromise($promise);
$index->waitForTask($promise['taskUid']);

$embedders = $index->getEmbedders();

$this->assertEquals($embedderConfig, $embedders['myEmbedder']);
}

public function testResetEmbedders(): void
{
$embedderConfig = [
'source' => 'userProvided',
'dimensions' => 1,
];
$index = $this->createEmptyIndex($this->safeIndexName());

$promise = $index->updateEmbedders(['myEmbedder' => $embedderConfig]);
$this->assertIsValidPromise($promise);
$index->waitForTask($promise['taskUid']);

$promise = $index->resetEmbedders();
$this->assertIsValidPromise($promise);
$index->waitForTask($promise['taskUid']);

$embedders = $index->getEmbedders();
$this->assertEquals([], $embedders);
}
}
31 changes: 18 additions & 13 deletions tests/Settings/SettingsTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -187,17 +187,22 @@ public function testResetSettings(): void
}

// Here the test to prevent https://github.com/meilisearch/meilisearch-php/issues/204.
public function testGetThenUpdateSettings(): void
{
$index = $this->createEmptyIndex($this->safeIndexName());

$resetPromise = $index->resetSettings();
$this->assertIsValidPromise($resetPromise);
$index->waitForTask($resetPromise['taskUid']);

$settings = $index->getSettings();
$promise = $index->updateSettings($settings);
$this->assertIsValidPromise($promise);
$index->waitForTask($promise['taskUid']);
}
// Rollback this comment after meilisearch v1.6.0 final release.
// Related to: https://github.com/meilisearch/meilisearch/issues/4323
//
// public function testGetThenUpdateSettings(): void
// {
// $http = new \Meilisearch\Http\Client($this->host, getenv('MEILISEARCH_API_KEY'));
// $http->patch('/experimental-features', ['vectorStore' => false]);
// $index = $this->createEmptyIndex($this->safeIndexName());

// $resetPromise = $index->resetSettings();
// $this->assertIsValidPromise($resetPromise);
// $index->waitForTask($resetPromise['taskUid']);

// $settings = $index->getSettings();
// $promise = $index->updateSettings($settings);
// $this->assertIsValidPromise($promise);
// $index->waitForTask($promise['taskUid']);
// }
}

0 comments on commit 264620e

Please sign in to comment.