Skip to content

Commit 7c7b994

Browse files
authored
Merge pull request #98 from tharropoulos/nl-search
feat(nl-search): add natural language search models support
2 parents 89bace5 + a9123c6 commit 7c7b994

File tree

6 files changed

+357
-0
lines changed

6 files changed

+357
-0
lines changed

src/Client.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,11 @@ class Client
8585
*/
8686
public Conversations $conversations;
8787

88+
/**
89+
* @var NLSearchModels
90+
*/
91+
public NLSearchModels $nlSearchModels;
92+
8893
/**
8994
* @var ApiCall
9095
*/
@@ -115,6 +120,7 @@ public function __construct(array $config)
115120
$this->analytics = new Analytics($this->apiCall);
116121
$this->stemming = new Stemming($this->apiCall);
117122
$this->conversations = new Conversations($this->apiCall);
123+
$this->nlSearchModels = new NLSearchModels($this->apiCall);
118124
}
119125

120126
/**
@@ -220,4 +226,12 @@ public function getConversations(): Conversations
220226
{
221227
return $this->conversations;
222228
}
229+
230+
/**
231+
* @return NLSearchModels
232+
*/
233+
public function getNLSearchModels(): NLSearchModels
234+
{
235+
return $this->nlSearchModels;
236+
}
223237
}

src/NLSearchModel.php

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
<?php
2+
3+
namespace Typesense;
4+
5+
use Http\Client\Exception as HttpClientException;
6+
use Typesense\Exceptions\TypesenseClientError;
7+
8+
/**
9+
* Class NLSearchModel
10+
*
11+
* @package \Typesense
12+
*/
13+
class NLSearchModel
14+
{
15+
/**
16+
* @var string
17+
*/
18+
private string $id;
19+
20+
/**
21+
* @var ApiCall
22+
*/
23+
private ApiCall $apiCall;
24+
25+
/**
26+
* NLSearchModel constructor.
27+
*
28+
* @param string $id
29+
* @param ApiCall $apiCall
30+
*/
31+
public function __construct(string $id, ApiCall $apiCall)
32+
{
33+
$this->id = $id;
34+
$this->apiCall = $apiCall;
35+
}
36+
37+
/**
38+
* @param array $params
39+
*
40+
* @return array
41+
* @throws TypesenseClientError|HttpClientException
42+
*/
43+
public function update(array $params): array
44+
{
45+
return $this->apiCall->put($this->endPointPath(), $params);
46+
}
47+
48+
/**
49+
* @return array
50+
* @throws TypesenseClientError|HttpClientException
51+
*/
52+
public function retrieve(): array
53+
{
54+
return $this->apiCall->get($this->endPointPath(), []);
55+
}
56+
57+
/**
58+
* @return array
59+
* @throws TypesenseClientError|HttpClientException
60+
*/
61+
public function delete(): array
62+
{
63+
return $this->apiCall->delete($this->endPointPath());
64+
}
65+
66+
/**
67+
* @return string
68+
*/
69+
public function endPointPath(): string
70+
{
71+
return sprintf('%s/%s', NLSearchModels::RESOURCE_PATH, encodeURIComponent($this->id));
72+
}
73+
}

src/NLSearchModels.php

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
<?php
2+
3+
namespace Typesense;
4+
5+
use Http\Client\Exception as HttpClientException;
6+
use Typesense\Exceptions\TypesenseClientError;
7+
8+
/**
9+
* Class NLSearchModels
10+
*
11+
* @package \Typesense
12+
*/
13+
class NLSearchModels implements \ArrayAccess
14+
{
15+
public const RESOURCE_PATH = '/nl_search_models';
16+
17+
/**
18+
* @var ApiCall
19+
*/
20+
private ApiCall $apiCall;
21+
22+
/**
23+
* @var array
24+
*/
25+
private array $nlSearchModels = [];
26+
27+
/**
28+
* NLSearchModels constructor.
29+
*
30+
* @param ApiCall $apiCall
31+
*/
32+
public function __construct(ApiCall $apiCall)
33+
{
34+
$this->apiCall = $apiCall;
35+
}
36+
37+
/**
38+
* @param $id
39+
*
40+
* @return mixed
41+
*/
42+
public function __get($id)
43+
{
44+
if (isset($this->{$id})) {
45+
return $this->{$id};
46+
}
47+
if (!isset($this->nlSearchModels[$id])) {
48+
$this->nlSearchModels[$id] = new NLSearchModel($id, $this->apiCall);
49+
}
50+
51+
return $this->nlSearchModels[$id];
52+
}
53+
54+
/**
55+
* @param array $params
56+
*
57+
* @return array
58+
* @throws TypesenseClientError|HttpClientException
59+
*/
60+
public function create(array $params): array
61+
{
62+
return $this->apiCall->post(static::RESOURCE_PATH, $params);
63+
}
64+
65+
/**
66+
* @return array
67+
* @throws TypesenseClientError|HttpClientException
68+
*/
69+
public function retrieve(): array
70+
{
71+
return $this->apiCall->get(static::RESOURCE_PATH, []);
72+
}
73+
74+
/**
75+
* @inheritDoc
76+
*/
77+
public function offsetExists($offset): bool
78+
{
79+
return isset($this->nlSearchModels[$offset]);
80+
}
81+
82+
/**
83+
* @inheritDoc
84+
*/
85+
public function offsetGet($offset): NLSearchModel
86+
{
87+
if (!isset($this->nlSearchModels[$offset])) {
88+
$this->nlSearchModels[$offset] = new NLSearchModel($offset, $this->apiCall);
89+
}
90+
91+
return $this->nlSearchModels[$offset];
92+
}
93+
94+
/**
95+
* @inheritDoc
96+
*/
97+
public function offsetSet($offset, $value): void
98+
{
99+
$this->nlSearchModels[$offset] = $value;
100+
}
101+
102+
/**
103+
* @inheritDoc
104+
*/
105+
public function offsetUnset($offset): void
106+
{
107+
unset($this->nlSearchModels[$offset]);
108+
}
109+
}

tests/Feature/NLSearchModelsTest.php

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
<?php
2+
3+
namespace Feature;
4+
5+
use Tests\NLSearchModelsTestCase;
6+
7+
class NLSearchModelsTest extends NLSearchModelsTestCase
8+
{
9+
public const RESOURCE_PATH = '/nl_search_models';
10+
11+
public function testCanCreateAModel(): void
12+
{
13+
$data = [
14+
"id" => "test-collection-model",
15+
"model_name" => "openai/gpt-3.5-turbo",
16+
"api_key" => $_ENV['OPENAI_API_KEY'] ?? getenv('OPENAI_API_KEY'),
17+
"system_prompt" => "You are a helpful search assistant.",
18+
"max_bytes" => 16384,
19+
"temperature" => 0.7
20+
];
21+
22+
$response = $this->client()->nlSearchModels->create($data);
23+
$this->assertArrayHasKey('id', $response);
24+
$this->assertEquals('test-collection-model', $response['id']);
25+
$this->assertEquals('openai/gpt-3.5-turbo', $response['model_name']);
26+
27+
$this->client()->nlSearchModels['test-collection-model']->delete();
28+
}
29+
30+
public function testCanRetrieveAllModels(): void
31+
{
32+
$testData = [
33+
"id" => "retrieve-test-model",
34+
"model_name" => "openai/gpt-3.5-turbo",
35+
"api_key" => $_ENV['OPENAI_API_KEY'] ?? getenv('OPENAI_API_KEY'),
36+
"system_prompt" => "Test model for retrieval.",
37+
"max_bytes" => 8192
38+
];
39+
40+
$this->client()->nlSearchModels->create($testData);
41+
42+
$response = $this->client()->nlSearchModels->retrieve();
43+
$this->assertIsArray($response);
44+
45+
$foundModel = false;
46+
foreach ($response as $model) {
47+
if ($model['id'] === 'retrieve-test-model') {
48+
$foundModel = true;
49+
$this->assertEquals('openai/gpt-3.5-turbo', $model['model_name']);
50+
break;
51+
}
52+
}
53+
$this->assertTrue($foundModel, 'Created test model should be found in the list');
54+
55+
$this->client()->nlSearchModels['retrieve-test-model']->delete();
56+
}
57+
58+
public function testCreateWithMissingRequiredFields(): void
59+
{
60+
$incompleteData = [
61+
"model_name" => "openai/gpt-3.5-turbo"
62+
];
63+
64+
$this->expectException(\Typesense\Exceptions\RequestMalformed::class);
65+
$this->client()->nlSearchModels->create($incompleteData);
66+
}
67+
68+
public function testCreateWithInvalidModelName(): void
69+
{
70+
$invalidData = [
71+
"id" => "invalid-model-test",
72+
"model_name" => "invalid/model-name",
73+
"api_key" => $_ENV['OPENAI_API_KEY'] ?? getenv('OPENAI_API_KEY'),
74+
"system_prompt" => "This should fail.",
75+
"max_bytes" => 16384
76+
];
77+
78+
$this->expectException(\Typesense\Exceptions\RequestMalformed::class);
79+
$this->client()->nlSearchModels->create($invalidData);
80+
}
81+
82+
public function testUpdate(): void
83+
{
84+
$data = [
85+
"id" => "test-collection-model",
86+
"model_name" => "openai/gpt-3.5-turbo",
87+
"api_key" => $_ENV['OPENAI_API_KEY'] ?? getenv('OPENAI_API_KEY'),
88+
"system_prompt" => "You are a helpful search assistant.",
89+
"max_bytes" => 16384,
90+
"temperature" => 0.7
91+
];
92+
93+
$response = $this->client()->nlSearchModels->create($data);
94+
$this->assertArrayHasKey('id', $response);
95+
$this->assertEquals('test-collection-model', $response['id']);
96+
$this->assertEquals('openai/gpt-3.5-turbo', $response['model_name']);
97+
98+
$response = $this->client()->nlSearchModels['test-collection-model']->update([
99+
"temperature" => 0.5
100+
]);
101+
$this->assertArrayHasKey('id', $response);
102+
$this->assertEquals('test-collection-model', $response['id']);
103+
$this->assertEquals(0.5, $response['temperature']);
104+
105+
$this->client()->nlSearchModels['test-collection-model']->delete();
106+
}
107+
108+
public function testDelete(): void
109+
{
110+
$data = [
111+
"id" => "test-collection-model",
112+
"model_name" => "openai/gpt-3.5-turbo",
113+
"api_key" => $_ENV['OPENAI_API_KEY'] ?? getenv('OPENAI_API_KEY'),
114+
"system_prompt" => "You are a helpful search assistant.",
115+
"max_bytes" => 16384,
116+
"temperature" => 0.7
117+
];
118+
119+
$response = $this->client()->nlSearchModels->create($data);
120+
$this->assertArrayHasKey('id', $response);
121+
$this->assertEquals('test-collection-model', $response['id']);
122+
$this->assertEquals('openai/gpt-3.5-turbo', $response['model_name']);
123+
124+
$response = $this->client()->nlSearchModels['test-collection-model']->delete();
125+
$this->assertArrayHasKey('id', $response);
126+
$this->assertEquals('test-collection-model', $response['id']);
127+
128+
}
129+
}

tests/NLSearchModelsTestCase.php

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
3+
namespace Tests;
4+
5+
use Tests\TestCase;
6+
use Typesense\NLSearchModels;
7+
8+
abstract class NLSearchModelsTestCase extends TestCase
9+
{
10+
private NLSearchModels $mockNLSearchModels;
11+
12+
protected function setUp(): void
13+
{
14+
$apiKey = $_ENV['OPENAI_API_KEY'] ?? getenv('OPENAI_API_KEY') ?? null;
15+
if (empty($apiKey)) {
16+
$this->markTestSkipped('OPENAI_API_KEY environment variable is not set. Skipping NL Search Models tests.');
17+
return;
18+
}
19+
20+
parent::setUp();
21+
$this->mockNLSearchModels = new NLSearchModels(parent::mockApiCall());
22+
}
23+
24+
protected function mockNLSearchModels(): NLSearchModels
25+
{
26+
return $this->mockNLSearchModels;
27+
}
28+
}

tests/TestCase.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,10 @@ protected function setUpDocuments(string $schema): void
8989

9090
protected function tearDownTypesense(): void
9191
{
92+
if ($this->typesenseClient === null) {
93+
return;
94+
}
95+
9296
$collections = $this->typesenseClient->collections->retrieve();
9397
foreach ($collections as $collection) {
9498
$this->typesenseClient->collections[$collection['name']]->delete();

0 commit comments

Comments
 (0)