From 6f611e85b4b882431301312b7f784f6ab68618f5 Mon Sep 17 00:00:00 2001 From: Hayden Date: Wed, 26 Jun 2024 17:57:00 +0700 Subject: [PATCH 1/3] feat: add support for conversations --- composer.json | 3 +- src/Client.php | 36 +++++--- src/Conversation.php | 73 +++++++++++++++ src/ConversationModel.php | 73 +++++++++++++++ src/ConversationModels.php | 109 ++++++++++++++++++++++ src/Conversations.php | 110 +++++++++++++++++++++++ tests/ConversationsTestCase.php | 23 +++++ tests/Feature/ClientTest.php | 2 + tests/Feature/ConversationModelTest.php | 43 +++++++++ tests/Feature/ConversationModelsTest.php | 35 ++++++++ tests/Feature/ConversationTest.php | 42 +++++++++ tests/Feature/ConversationsTest.php | 19 ++++ tests/TestCase.php | 11 ++- 13 files changed, 566 insertions(+), 13 deletions(-) create mode 100644 src/Conversation.php create mode 100644 src/ConversationModel.php create mode 100644 src/ConversationModels.php create mode 100644 src/Conversations.php create mode 100644 tests/ConversationsTestCase.php create mode 100644 tests/Feature/ConversationModelTest.php create mode 100644 tests/Feature/ConversationModelsTest.php create mode 100644 tests/Feature/ConversationTest.php create mode 100644 tests/Feature/ConversationsTest.php diff --git a/composer.json b/composer.json index 6d2e3a0d..c97f9e04 100644 --- a/composer.json +++ b/composer.json @@ -49,7 +49,8 @@ "require-dev": { "phpunit/phpunit": "^11.2", "squizlabs/php_codesniffer": "3.*", - "symfony/http-client": "^5.2" + "symfony/http-client": "^5.2", + "mockery/mockery": "^1.6" }, "config": { "optimize-autoloader": true, diff --git a/src/Client.php b/src/Client.php index 00d73858..df2a7a2f 100644 --- a/src/Client.php +++ b/src/Client.php @@ -74,6 +74,11 @@ class Client */ public Analytics $analytics; + /** + * @var Conversations + */ + public Conversations $conversations; + /** * @var ApiCall */ @@ -91,17 +96,18 @@ public function __construct(array $config) $this->config = new Configuration($config); $this->apiCall = new ApiCall($this->config); - $this->collections = new Collections($this->apiCall); - $this->stopwords = new Stopwords($this->apiCall); - $this->aliases = new Aliases($this->apiCall); - $this->keys = new Keys($this->apiCall); - $this->debug = new Debug($this->apiCall); - $this->metrics = new Metrics($this->apiCall); - $this->health = new Health($this->apiCall); - $this->operations = new Operations($this->apiCall); - $this->multiSearch = new MultiSearch($this->apiCall); - $this->presets = new Presets($this->apiCall); - $this->analytics = new Analytics($this->apiCall); + $this->collections = new Collections($this->apiCall); + $this->stopwords = new Stopwords($this->apiCall); + $this->aliases = new Aliases($this->apiCall); + $this->keys = new Keys($this->apiCall); + $this->debug = new Debug($this->apiCall); + $this->metrics = new Metrics($this->apiCall); + $this->health = new Health($this->apiCall); + $this->operations = new Operations($this->apiCall); + $this->multiSearch = new MultiSearch($this->apiCall); + $this->presets = new Presets($this->apiCall); + $this->analytics = new Analytics($this->apiCall); + $this->conversations = new Conversations($this->apiCall); } /** @@ -191,4 +197,12 @@ public function getAnalytics(): Analytics { return $this->analytics; } + + /** + * @return Conversations + */ + public function getConversations(): Conversations + { + return $this->conversations; + } } diff --git a/src/Conversation.php b/src/Conversation.php new file mode 100644 index 00000000..855368b0 --- /dev/null +++ b/src/Conversation.php @@ -0,0 +1,73 @@ +id = $id; + $this->apiCall = $apiCall; + } + + /** + * @param array $params + * + * @return array + * @throws TypesenseClientError|HttpClientException + */ + public function update(array $params): array + { + return $this->apiCall->put($this->endPointPath(), $params); + } + + /** + * @return array + * @throws TypesenseClientError|HttpClientException + */ + public function retrieve(): array + { + return $this->apiCall->get($this->endPointPath(), []); + } + + /** + * @return array + * @throws TypesenseClientError|HttpClientException + */ + public function delete(): array + { + return $this->apiCall->delete($this->endPointPath()); + } + + /** + * @return string + */ + public function endPointPath(): string + { + return sprintf('%s/%s', Conversations::RESOURCE_PATH, $this->id); + } +} diff --git a/src/ConversationModel.php b/src/ConversationModel.php new file mode 100644 index 00000000..305fa39c --- /dev/null +++ b/src/ConversationModel.php @@ -0,0 +1,73 @@ +id = $id; + $this->apiCall = $apiCall; + } + + /** + * @param array $params + * + * @return array + * @throws TypesenseClientError|HttpClientException + */ + public function update(array $params): array + { + return $this->apiCall->put($this->endPointPath(), $params); + } + + /** + * @return array + * @throws TypesenseClientError|HttpClientException + */ + public function retrieve(): array + { + return $this->apiCall->get($this->endPointPath(), []); + } + + /** + * @return array + * @throws TypesenseClientError|HttpClientException + */ + public function delete(): array + { + return $this->apiCall->delete($this->endPointPath()); + } + + /** + * @return string + */ + public function endPointPath(): string + { + return sprintf('%s/%s', ConversationModels::RESOURCE_PATH, $this->id); + } +} diff --git a/src/ConversationModels.php b/src/ConversationModels.php new file mode 100644 index 00000000..a75ef260 --- /dev/null +++ b/src/ConversationModels.php @@ -0,0 +1,109 @@ +apiCall = $apiCall; + } + + /** + * @param $id + * + * @return mixed + */ + public function __get($id) + { + if (isset($this->{$id})) { + return $this->{$id}; + } + if (!isset($this->models[$id])) { + $this->models[$id] = new ConversationModel($id, $this->apiCall); + } + + return $this->models[$id]; + } + + /** + * @param array $params + * + * @return array + * @throws TypesenseClientError|HttpClientException + */ + public function create(array $params): array + { + return $this->apiCall->post(static::RESOURCE_PATH, $params); + } + + /** + * @return array + * @throws TypesenseClientError|HttpClientException + */ + public function retrieve(): array + { + return $this->apiCall->get(static::RESOURCE_PATH, []); + } + + /** + * @inheritDoc + */ + public function offsetExists($offset): bool + { + return isset($this->models[$offset]); + } + + /** + * @inheritDoc + */ + public function offsetGet($offset): ConversationModel + { + if (!isset($this->models[$offset])) { + $this->models[$offset] = new ConversationModel($offset, $this->apiCall); + } + + return $this->models[$offset]; + } + + /** + * @inheritDoc + */ + public function offsetSet($offset, $value): void + { + $this->models[$offset] = $value; + } + + /** + * @inheritDoc + */ + public function offsetUnset($offset): void + { + unset($this->models[$offset]); + } +} diff --git a/src/Conversations.php b/src/Conversations.php new file mode 100644 index 00000000..dd4698f1 --- /dev/null +++ b/src/Conversations.php @@ -0,0 +1,110 @@ +apiCall = $apiCall; + $this->models = new ConversationModels($this->apiCall); + } + + /** + * @return array + * @throws TypesenseClientError|HttpClientException + */ + public function retrieve(): array + { + return $this->apiCall->get(static::RESOURCE_PATH, []); + } + + /** + * @return Models + */ + public function getModels(): ConversationModels + { + return $this->models; + } + + /** + * @param $id + * + * @return mixed + */ + public function __get($id) + { + if (isset($this->{$id})) { + return $this->{$id}; + } + + if (!isset($this->individualConversations[$id])) { + $this->individualConversations[$id] = new Conversation($id, $this->apiCall); + } + + return $this->individualConversations[$id]; + } + + /** + * @inheritDoc + */ + public function offsetExists($offset): bool + { + return isset($this->individualConversations[$offset]); + } + + /** + * @inheritDoc + */ + public function offsetGet($offset): Conversation + { + if (!isset($this->individualConversations[$offset])) { + $this->individualConversations[$offset] = new Conversation($offset, $this->apiCall); + } + + return $this->individualConversations[$offset]; + } + + /** + * @inheritDoc + */ + public function offsetSet($offset, $value): void + { + $this->individualConversations[$offset] = $value; + } + + /** + * @inheritDoc + */ + public function offsetUnset($offset): void + { + unset($this->individualConversations[$offset]); + } +} diff --git a/tests/ConversationsTestCase.php b/tests/ConversationsTestCase.php new file mode 100644 index 00000000..1270b917 --- /dev/null +++ b/tests/ConversationsTestCase.php @@ -0,0 +1,23 @@ +mockConversations = new Conversations(parent::mockApiCall()); + } + + protected function mockConversations(): Conversations + { + return $this->mockConversations; + } +} diff --git a/tests/Feature/ClientTest.php b/tests/Feature/ClientTest.php index 42d0760c..7881d0dc 100644 --- a/tests/Feature/ClientTest.php +++ b/tests/Feature/ClientTest.php @@ -14,6 +14,7 @@ use Typesense\MultiSearch; use Typesense\Presets; use Typesense\Analytics; +use Typesense\Conversations; class ClientTest extends TestCase { @@ -30,5 +31,6 @@ public function testCanCreateAClient(): void $this->assertInstanceOf(MultiSearch::class, $this->client()->multiSearch); $this->assertInstanceOf(Presets::class, $this->client()->presets); $this->assertInstanceOf(Analytics::class, $this->client()->analytics); + $this->assertInstanceOf(Conversations::class, $this->client()->conversations); } } diff --git a/tests/Feature/ConversationModelTest.php b/tests/Feature/ConversationModelTest.php new file mode 100644 index 00000000..e0a18b36 --- /dev/null +++ b/tests/Feature/ConversationModelTest.php @@ -0,0 +1,43 @@ +mockApiCall()->allows()->get($this->endPointPath(), [])->andReturns([]); + + $response = $this->mockConversations()->models[$this->id]->retrieve(); + $this->assertEquals([], $response); + } + + public function testCanUpdateAModelById(): void + { + $data = [ + "system_prompt" => "You are an assistant for question-answering..." + ]; + $this->mockApiCall()->allows()->put($this->endPointPath(), $data)->andReturns([]); + + $response = $this->mockConversations()->models[$this->id]->update($data); + $this->assertEquals([], $response); + } + + public function testCanDeleteAModelById(): void + { + $this->mockApiCall()->allows()->delete($this->endPointPath())->andReturns([]); + + $response = $this->mockConversations()->models[$this->id]->delete(); + $this->assertEquals([], $response); + } + + private function endPointPath(): string + { + return sprintf('%s/%s', '/conversations/models', $this->id); + } +} diff --git a/tests/Feature/ConversationModelsTest.php b/tests/Feature/ConversationModelsTest.php new file mode 100644 index 00000000..3f9c73d1 --- /dev/null +++ b/tests/Feature/ConversationModelsTest.php @@ -0,0 +1,35 @@ + "openai/gpt-3.5-turbo", + "api_key" => "OPENAI_API_KEY", + "system_prompt" => "You are an assistant for question-answering...", + "max_bytes" => 16384 + ]; + + $this->mockApiCall()->allows()->post(static::RESOURCE_PATH, $data)->andReturns([]); + $this->mockConversations()->models->create($data); + + $this->expectExceptionMessage('OpenAI API error: Incorrect API key provided: OPENAI_A**_KEY'); + $this->client()->conversations->models->create($data); + } + + public function testCanRetrieveAllModels(): void + { + $this->mockApiCall()->allows()->get(static::RESOURCE_PATH, [])->andReturns([]); + $this->mockConversations()->models->retrieve(); + + $response = $this->client()->conversations->models->retrieve(); + $this->assertEquals([], $response); + } +} diff --git a/tests/Feature/ConversationTest.php b/tests/Feature/ConversationTest.php new file mode 100644 index 00000000..01fc495c --- /dev/null +++ b/tests/Feature/ConversationTest.php @@ -0,0 +1,42 @@ +mockApiCall()->allows()->get($this->endPointPath(), [])->andReturns([]); + + $response = $this->mockConversations()[$this->id]->retrieve(); + $this->assertEquals([], $response); + } + + public function testCanUpdateAConversation(): void + { + $data = [ + "ttl" => 3600 + ]; + $this->mockApiCall()->allows()->put($this->endPointPath(), $data)->andReturns([]); + + $response = $this->mockConversations()[$this->id]->update($data); + $this->assertEquals([], $response); + } + + public function testCanDeleteAConversation(): void + { + $this->mockApiCall()->allows()->delete($this->endPointPath())->andReturns([]); + + $response = $this->mockConversations()[$this->id]->delete(); + $this->assertEquals([], $response); + } + + private function endPointPath(): string + { + return sprintf('%s/%s', ConversationsTest::RESOURCE_PATH, $this->id); + } +} diff --git a/tests/Feature/ConversationsTest.php b/tests/Feature/ConversationsTest.php new file mode 100644 index 00000000..81ab63fc --- /dev/null +++ b/tests/Feature/ConversationsTest.php @@ -0,0 +1,19 @@ +mockApiCall()->allows()->get(static::RESOURCE_PATH, [])->andReturns([]); + $this->mockConversations()->retrieve(); + + $response = $this->client()->conversations->retrieve(); + $this->assertEquals([], $response); + } +} diff --git a/tests/TestCase.php b/tests/TestCase.php index f2ddd007..dca1d4e8 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -4,14 +4,18 @@ use PHPUnit\Framework\TestCase as BaseTestCase; use Typesense\Client; +use Mockery; +use Typesense\ApiCall; abstract class TestCase extends BaseTestCase { private ?Client $typesenseClient = null; + private $mockApiCall; protected function setUp(): void { $this->setUpTypesenseClient(); + $this->mockApiCall = Mockery::mock(ApiCall::class); } protected function tearDown(): void @@ -24,6 +28,11 @@ protected function client(): Client return $this->typesenseClient; } + protected function mockApiCall() + { + return $this->mockApiCall; + } + protected function getSchema(string $name): array { $path = __DIR__ . "/data/{$name}.schema.json"; @@ -78,7 +87,7 @@ protected function setUpDocuments(string $schema): void $this->typesenseClient->collections[$schema]->documents->import($documents); } - private function tearDownTypesense(): void + protected function tearDownTypesense(): void { $collections = $this->typesenseClient->collections->retrieve(); foreach ($collections as $collection) { From b07e2eb3b5e3f7f419bbff806e4aae9ed1e97dc2 Mon Sep 17 00:00:00 2001 From: Hayden Date: Wed, 26 Jun 2024 18:09:49 +0700 Subject: [PATCH 2/3] test: update conversationModels typesense v27.0.rc21 --- tests/Feature/ConversationModelsTest.php | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/Feature/ConversationModelsTest.php b/tests/Feature/ConversationModelsTest.php index 3f9c73d1..70603285 100644 --- a/tests/Feature/ConversationModelsTest.php +++ b/tests/Feature/ConversationModelsTest.php @@ -18,10 +18,9 @@ public function testCanCreateAModel(): void ]; $this->mockApiCall()->allows()->post(static::RESOURCE_PATH, $data)->andReturns([]); - $this->mockConversations()->models->create($data); - $this->expectExceptionMessage('OpenAI API error: Incorrect API key provided: OPENAI_A**_KEY'); - $this->client()->conversations->models->create($data); + $response = $this->mockConversations()->models->create($data); + $this->assertEquals([], $response); } public function testCanRetrieveAllModels(): void From 4e6f14b581f826b8dd4cd5fa20a1ea460f1bafb1 Mon Sep 17 00:00:00 2001 From: Hayden Date: Wed, 26 Jun 2024 18:29:40 +0700 Subject: [PATCH 3/3] refactor: ConversationModelTest RESOURCE_PATH --- tests/Feature/ConversationModelTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Feature/ConversationModelTest.php b/tests/Feature/ConversationModelTest.php index e0a18b36..675eb0e5 100644 --- a/tests/Feature/ConversationModelTest.php +++ b/tests/Feature/ConversationModelTest.php @@ -38,6 +38,6 @@ public function testCanDeleteAModelById(): void private function endPointPath(): string { - return sprintf('%s/%s', '/conversations/models', $this->id); + return sprintf('%s/%s', ConversationModelsTest::RESOURCE_PATH, $this->id); } }