From 7604609efa68c0b9aaa9191b5f227db3585df4b9 Mon Sep 17 00:00:00 2001 From: Strift Date: Wed, 6 Aug 2025 19:00:17 +0800 Subject: [PATCH 01/26] Add method to update workspace settings --- src/Client.php | 4 ++ src/Endpoints/ChatWorkspaces.php | 31 +++++++++++++ .../Delegates/HandlesChatWorkspaces.php | 12 +++++ tests/Endpoints/ChatTest.php | 46 +++++++++++++++++++ 4 files changed, 93 insertions(+) create mode 100644 src/Endpoints/ChatWorkspaces.php create mode 100644 src/Endpoints/Delegates/HandlesChatWorkspaces.php create mode 100644 tests/Endpoints/ChatTest.php diff --git a/src/Client.php b/src/Client.php index dffa598a..9baf3f3c 100644 --- a/src/Client.php +++ b/src/Client.php @@ -5,7 +5,9 @@ namespace Meilisearch; use Meilisearch\Endpoints\Batches; +use Meilisearch\Endpoints\ChatWorkspaces; use Meilisearch\Endpoints\Delegates\HandlesBatches; +use Meilisearch\Endpoints\Delegates\HandlesChatWorkspaces; use Meilisearch\Endpoints\Delegates\HandlesDumps; use Meilisearch\Endpoints\Delegates\HandlesIndex; use Meilisearch\Endpoints\Delegates\HandlesKeys; @@ -31,6 +33,7 @@ class Client { + use HandlesChatWorkspaces; use HandlesDumps; use HandlesIndex; use HandlesTasks; @@ -53,6 +56,7 @@ public function __construct( ?StreamFactoryInterface $streamFactory = null ) { $this->http = new MeilisearchClientAdapter($url, $apiKey, $httpClient, $requestFactory, $clientAgents, $streamFactory); + $this->chats = new ChatWorkspaces($this->http); $this->index = new Indexes($this->http); $this->health = new Health($this->http); $this->version = new Version($this->http); diff --git a/src/Endpoints/ChatWorkspaces.php b/src/Endpoints/ChatWorkspaces.php new file mode 100644 index 00000000..f2a8c3df --- /dev/null +++ b/src/Endpoints/ChatWorkspaces.php @@ -0,0 +1,31 @@ +workspaceName = $workspaceName; + parent::__construct($http); + } + + public function workspace(string $workspaceName): self + { + return new self($this->http, $workspaceName); + } + + public function updateSettings(array $settings): array + { + return $this->http->patch(self::PATH.'/'.$this->workspaceName.'/settings', $settings); + } +} diff --git a/src/Endpoints/Delegates/HandlesChatWorkspaces.php b/src/Endpoints/Delegates/HandlesChatWorkspaces.php new file mode 100644 index 00000000..8504bea5 --- /dev/null +++ b/src/Endpoints/Delegates/HandlesChatWorkspaces.php @@ -0,0 +1,12 @@ + "openAi", + "orgId" => "some-org-id", + "projectId" => "some-project-id", + "apiVersion" => "some-api-version", + "deploymentId" => "some-deployment-id", + "baseUrl" => "https://baseurl.com", + "apiKey" => "sk-abc...", + "prompts" => [ + "system" => "You are a helpful assistant that answers questions based on the provided context.", + ], + ]; + + protected function setUp(): void + { + parent::setUp(); + + $http = new Client($this->host, getenv('MEILISEARCH_API_KEY')); + $http->patch('/experimental-features', ['chatCompletions' => true]); + } + + public function testUpdateWorkspacesSettings(): void + { + $response = $this->client->chats->workspace('myWorkspace')->updateSettings($this->workspaceSettings); + self::assertSame($this->workspaceSettings['source'], $response['source']); + self::assertSame($this->workspaceSettings['orgId'], $response['orgId']); + self::assertSame($this->workspaceSettings['projectId'], $response['projectId']); + self::assertSame($this->workspaceSettings['apiVersion'], $response['apiVersion']); + self::assertSame($this->workspaceSettings['deploymentId'], $response['deploymentId']); + self::assertSame($this->workspaceSettings['baseUrl'], $response['baseUrl']); + self::assertSame($this->workspaceSettings['prompts']['system'], $response['prompts']['system']); + // Meilisearch will mask the API key in the response + self::assertSame('XXX...', $response['apiKey']); + } +} From 4897fbfc09928fd96db2a6a8b9c7638b53dce528 Mon Sep 17 00:00:00 2001 From: Strift Date: Wed, 13 Aug 2025 17:13:17 +0800 Subject: [PATCH 02/26] add CRUD methods --- src/Endpoints/ChatWorkspaces.php | 15 +++++++++++ tests/Endpoints/ChatTest.php | 44 ++++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+) diff --git a/src/Endpoints/ChatWorkspaces.php b/src/Endpoints/ChatWorkspaces.php index f2a8c3df..ad50e56b 100644 --- a/src/Endpoints/ChatWorkspaces.php +++ b/src/Endpoints/ChatWorkspaces.php @@ -19,13 +19,28 @@ public function __construct(Http $http, ?string $workspaceName = null) parent::__construct($http); } + public function listWorkspaces(): array + { + return $this->http->get(self::PATH); + } + public function workspace(string $workspaceName): self { return new self($this->http, $workspaceName); } + public function getSettings(): array + { + return $this->http->get(self::PATH.'/'.$this->workspaceName.'/settings'); + } + public function updateSettings(array $settings): array { return $this->http->patch(self::PATH.'/'.$this->workspaceName.'/settings', $settings); } + + public function resetSettings(): array + { + return $this->http->delete(self::PATH.'/'.$this->workspaceName.'/settings'); + } } diff --git a/tests/Endpoints/ChatTest.php b/tests/Endpoints/ChatTest.php index 135da303..589f1471 100644 --- a/tests/Endpoints/ChatTest.php +++ b/tests/Endpoints/ChatTest.php @@ -43,4 +43,48 @@ public function testUpdateWorkspacesSettings(): void // Meilisearch will mask the API key in the response self::assertSame('XXX...', $response['apiKey']); } + + public function testGetWorkspaceSettings(): void + { + $this->client->chats->workspace('myWorkspace')->updateSettings($this->workspaceSettings); + + $response = $this->client->chats->workspace('myWorkspace')->getSettings(); + self::assertSame($this->workspaceSettings['source'], $response['source']); + self::assertSame($this->workspaceSettings['orgId'], $response['orgId']); + self::assertSame($this->workspaceSettings['projectId'], $response['projectId']); + self::assertSame($this->workspaceSettings['apiVersion'], $response['apiVersion']); + self::assertSame($this->workspaceSettings['deploymentId'], $response['deploymentId']); + self::assertSame($this->workspaceSettings['baseUrl'], $response['baseUrl']); + self::assertSame($this->workspaceSettings['prompts']['system'], $response['prompts']['system']); + // Meilisearch will mask the API key in the response + self::assertSame('XXX...', $response['apiKey']); + } + + public function testListWorkspaces(): void + { + $this->client->chats->workspace('myWorkspace')->updateSettings($this->workspaceSettings); + $response = $this->client->chats->listWorkspaces(); + self::assertSame([ + ['uid' => 'myWorkspace'], + ], $response['results']); + } + + public function testDeleteWorkspaceSettings(): void + { + $this->client->chats->workspace('myWorkspace')->updateSettings($this->workspaceSettings); + $this->client->chats->workspace('myWorkspace')->resetSettings(); + $settingsResponse = $this->client->chats->workspace('myWorkspace')->getSettings(); + self::assertSame('openAi', $settingsResponse['source']); + self::assertNull($settingsResponse['orgId']); + self::assertNull($settingsResponse['projectId']); + self::assertNull($settingsResponse['apiVersion']); + self::assertNull($settingsResponse['deploymentId']); + self::assertNull($settingsResponse['baseUrl']); + self::assertNull($settingsResponse['apiKey']); + + $listResponse = $this->client->chats->listWorkspaces(); + self::assertSame([ + ['uid' => 'myWorkspace'], + ], $listResponse['results']); + } } From 51f440016aef8e8773075a2694519e4ab48d0e72 Mon Sep 17 00:00:00 2001 From: Strift Date: Wed, 13 Aug 2025 17:33:55 +0800 Subject: [PATCH 03/26] Refactor --- src/Contracts/ChatWorkspaceSettings.php | 88 +++++++++++++++++++ src/Contracts/ChatWorkspacesResults.php | 59 +++++++++++++ src/Endpoints/ChatWorkspaces.php | 26 ++---- .../HandlesChatWorkspaceSettings.php | 68 ++++++++++++++ .../Delegates/HandlesChatWorkspaces.php | 17 ++++ tests/Endpoints/ChatTest.php | 50 +++++------ 6 files changed, 266 insertions(+), 42 deletions(-) create mode 100644 src/Contracts/ChatWorkspaceSettings.php create mode 100644 src/Contracts/ChatWorkspacesResults.php create mode 100644 src/Endpoints/Delegates/HandlesChatWorkspaceSettings.php diff --git a/src/Contracts/ChatWorkspaceSettings.php b/src/Contracts/ChatWorkspaceSettings.php new file mode 100644 index 00000000..5aaf4953 --- /dev/null +++ b/src/Contracts/ChatWorkspaceSettings.php @@ -0,0 +1,88 @@ +source = $params['source'] ?? null; + $this->orgId = $params['orgId'] ?? null; + $this->projectId = $params['projectId'] ?? null; + $this->apiVersion = $params['apiVersion'] ?? null; + $this->deploymentId = $params['deploymentId'] ?? null; + $this->baseUrl = $params['baseUrl'] ?? null; + $this->apiKey = $params['apiKey'] ?? null; + $this->prompts = $params['prompts'] ?? []; + } + + public function getSource(): ?string + { + return $this->source; + } + + public function getOrgId(): ?string + { + return $this->orgId; + } + + public function getProjectId(): ?string + { + return $this->projectId; + } + + public function getApiVersion(): ?string + { + return $this->apiVersion; + } + + public function getDeploymentId(): ?string + { + return $this->deploymentId; + } + + public function getBaseUrl(): ?string + { + return $this->baseUrl; + } + + public function getApiKey(): ?string + { + return $this->apiKey; + } + + /** + * @return array{system?: string, searchDescription?: string, searchQParam?: string, searchIndexUidParam?: string} + */ + public function getPrompts(): array + { + return $this->prompts; + } + + public function toArray(): array + { + return [ + 'source' => $this->source, + 'orgId' => $this->orgId, + 'projectId' => $this->projectId, + 'apiVersion' => $this->apiVersion, + 'deploymentId' => $this->deploymentId, + 'baseUrl' => $this->baseUrl, + 'apiKey' => $this->apiKey, + 'prompts' => $this->prompts, + ]; + } +} diff --git a/src/Contracts/ChatWorkspacesResults.php b/src/Contracts/ChatWorkspacesResults.php new file mode 100644 index 00000000..cfcb6d2d --- /dev/null +++ b/src/Contracts/ChatWorkspacesResults.php @@ -0,0 +1,59 @@ +offset = $params['offset']; + $this->limit = $params['limit']; + $this->total = $params['total'] ?? 0; + } + + /** + * @return array + */ + public function getResults(): array + { + return $this->data; + } + + public function getOffset(): int + { + return $this->offset; + } + + public function getLimit(): int + { + return $this->limit; + } + + public function getTotal(): int + { + return $this->total; + } + + public function toArray(): array + { + return [ + 'results' => $this->data, + 'offset' => $this->offset, + 'limit' => $this->limit, + 'total' => $this->total, + ]; + } + + public function count(): int + { + return \count($this->data); + } +} diff --git a/src/Endpoints/ChatWorkspaces.php b/src/Endpoints/ChatWorkspaces.php index ad50e56b..3847fdec 100644 --- a/src/Endpoints/ChatWorkspaces.php +++ b/src/Endpoints/ChatWorkspaces.php @@ -4,11 +4,16 @@ namespace Meilisearch\Endpoints; +use Meilisearch\Contracts\ChatWorkspacesResults; +use Meilisearch\Contracts\ChatWorkspaceSettings; use Meilisearch\Contracts\Endpoint; use Meilisearch\Contracts\Http; +use Meilisearch\Endpoints\Delegates\HandlesChatWorkspaceSettings; class ChatWorkspaces extends Endpoint { + use HandlesChatWorkspaceSettings; + protected const PATH = '/chats'; private ?string $workspaceName; @@ -19,28 +24,15 @@ public function __construct(Http $http, ?string $workspaceName = null) parent::__construct($http); } - public function listWorkspaces(): array + public function listWorkspaces(): ChatWorkspacesResults { - return $this->http->get(self::PATH); + $response = $this->http->get(self::PATH); + + return new ChatWorkspacesResults($response); } public function workspace(string $workspaceName): self { return new self($this->http, $workspaceName); } - - public function getSettings(): array - { - return $this->http->get(self::PATH.'/'.$this->workspaceName.'/settings'); - } - - public function updateSettings(array $settings): array - { - return $this->http->patch(self::PATH.'/'.$this->workspaceName.'/settings', $settings); - } - - public function resetSettings(): array - { - return $this->http->delete(self::PATH.'/'.$this->workspaceName.'/settings'); - } } diff --git a/src/Endpoints/Delegates/HandlesChatWorkspaceSettings.php b/src/Endpoints/Delegates/HandlesChatWorkspaceSettings.php new file mode 100644 index 00000000..ad934c8f --- /dev/null +++ b/src/Endpoints/Delegates/HandlesChatWorkspaceSettings.php @@ -0,0 +1,68 @@ +workspaceName) { + throw new \InvalidArgumentException('Workspace name is required to get settings'); + } + + $response = $this->http->get('/chats/'.$this->workspaceName.'/settings'); + + return new ChatWorkspaceSettings($response); + } + + /** + * Update the settings for this chat workspace. + * + * @param array{ + * source?: 'openAi'|'azureOpenAi'|'mistral'|'gemini'|'vLlm', + * orgId?: string, + * projectId?: string, + * apiVersion?: string, + * deploymentId?: string, + * baseUrl?: string, + * apiKey?: string, + * prompts?: array{ + * system?: string, + * searchDescription?: string, + * searchQParam?: string, + * searchIndexUidParam?: string + * } + * } $settings + */ + public function updateSettings(array $settings): ChatWorkspaceSettings + { + if (!$this->workspaceName) { + throw new \InvalidArgumentException('Workspace name is required to update settings'); + } + + $response = $this->http->patch('/chats/'.$this->workspaceName.'/settings', $settings); + + return new ChatWorkspaceSettings($response); + } + + /** + * Reset the settings for this chat workspace to default values. + */ + public function resetSettings(): ChatWorkspaceSettings + { + if (!$this->workspaceName) { + throw new \InvalidArgumentException('Workspace name is required to reset settings'); + } + + $response = $this->http->delete('/chats/'.$this->workspaceName.'/settings'); + + return new ChatWorkspaceSettings($response); + } +} diff --git a/src/Endpoints/Delegates/HandlesChatWorkspaces.php b/src/Endpoints/Delegates/HandlesChatWorkspaces.php index 8504bea5..343a6a51 100644 --- a/src/Endpoints/Delegates/HandlesChatWorkspaces.php +++ b/src/Endpoints/Delegates/HandlesChatWorkspaces.php @@ -4,9 +4,26 @@ namespace Meilisearch\Endpoints\Delegates; +use Meilisearch\Contracts\ChatWorkspacesResults; use Meilisearch\Endpoints\ChatWorkspaces; trait HandlesChatWorkspaces { public ChatWorkspaces $chats; + + /** + * List all chat workspaces. + */ + public function getChatWorkspaces(): ChatWorkspacesResults + { + return $this->chats->listWorkspaces(); + } + + /** + * Get a specific chat workspace instance. + */ + public function chatWorkspace(string $workspaceName): ChatWorkspaces + { + return $this->chats->workspace($workspaceName); + } } diff --git a/tests/Endpoints/ChatTest.php b/tests/Endpoints/ChatTest.php index 589f1471..d5e9be37 100644 --- a/tests/Endpoints/ChatTest.php +++ b/tests/Endpoints/ChatTest.php @@ -33,15 +33,15 @@ protected function setUp(): void public function testUpdateWorkspacesSettings(): void { $response = $this->client->chats->workspace('myWorkspace')->updateSettings($this->workspaceSettings); - self::assertSame($this->workspaceSettings['source'], $response['source']); - self::assertSame($this->workspaceSettings['orgId'], $response['orgId']); - self::assertSame($this->workspaceSettings['projectId'], $response['projectId']); - self::assertSame($this->workspaceSettings['apiVersion'], $response['apiVersion']); - self::assertSame($this->workspaceSettings['deploymentId'], $response['deploymentId']); - self::assertSame($this->workspaceSettings['baseUrl'], $response['baseUrl']); - self::assertSame($this->workspaceSettings['prompts']['system'], $response['prompts']['system']); + self::assertSame($this->workspaceSettings['source'], $response->getSource()); + self::assertSame($this->workspaceSettings['orgId'], $response->getOrgId()); + self::assertSame($this->workspaceSettings['projectId'], $response->getProjectId()); + self::assertSame($this->workspaceSettings['apiVersion'], $response->getApiVersion()); + self::assertSame($this->workspaceSettings['deploymentId'], $response->getDeploymentId()); + self::assertSame($this->workspaceSettings['baseUrl'], $response->getBaseUrl()); + self::assertSame($this->workspaceSettings['prompts']['system'], $response->getPrompts()['system']); // Meilisearch will mask the API key in the response - self::assertSame('XXX...', $response['apiKey']); + self::assertSame('XXX...', $response->getApiKey()); } public function testGetWorkspaceSettings(): void @@ -49,15 +49,15 @@ public function testGetWorkspaceSettings(): void $this->client->chats->workspace('myWorkspace')->updateSettings($this->workspaceSettings); $response = $this->client->chats->workspace('myWorkspace')->getSettings(); - self::assertSame($this->workspaceSettings['source'], $response['source']); - self::assertSame($this->workspaceSettings['orgId'], $response['orgId']); - self::assertSame($this->workspaceSettings['projectId'], $response['projectId']); - self::assertSame($this->workspaceSettings['apiVersion'], $response['apiVersion']); - self::assertSame($this->workspaceSettings['deploymentId'], $response['deploymentId']); - self::assertSame($this->workspaceSettings['baseUrl'], $response['baseUrl']); - self::assertSame($this->workspaceSettings['prompts']['system'], $response['prompts']['system']); + self::assertSame($this->workspaceSettings['source'], $response->getSource()); + self::assertSame($this->workspaceSettings['orgId'], $response->getOrgId()); + self::assertSame($this->workspaceSettings['projectId'], $response->getProjectId()); + self::assertSame($this->workspaceSettings['apiVersion'], $response->getApiVersion()); + self::assertSame($this->workspaceSettings['deploymentId'], $response->getDeploymentId()); + self::assertSame($this->workspaceSettings['baseUrl'], $response->getBaseUrl()); + self::assertSame($this->workspaceSettings['prompts']['system'], $response->getPrompts()['system']); // Meilisearch will mask the API key in the response - self::assertSame('XXX...', $response['apiKey']); + self::assertSame('XXX...', $response->getApiKey()); } public function testListWorkspaces(): void @@ -66,7 +66,7 @@ public function testListWorkspaces(): void $response = $this->client->chats->listWorkspaces(); self::assertSame([ ['uid' => 'myWorkspace'], - ], $response['results']); + ], $response->getResults()); } public function testDeleteWorkspaceSettings(): void @@ -74,17 +74,17 @@ public function testDeleteWorkspaceSettings(): void $this->client->chats->workspace('myWorkspace')->updateSettings($this->workspaceSettings); $this->client->chats->workspace('myWorkspace')->resetSettings(); $settingsResponse = $this->client->chats->workspace('myWorkspace')->getSettings(); - self::assertSame('openAi', $settingsResponse['source']); - self::assertNull($settingsResponse['orgId']); - self::assertNull($settingsResponse['projectId']); - self::assertNull($settingsResponse['apiVersion']); - self::assertNull($settingsResponse['deploymentId']); - self::assertNull($settingsResponse['baseUrl']); - self::assertNull($settingsResponse['apiKey']); + self::assertSame('openAi', $settingsResponse->getSource()); + self::assertNull($settingsResponse->getOrgId()); + self::assertNull($settingsResponse->getProjectId()); + self::assertNull($settingsResponse->getApiVersion()); + self::assertNull($settingsResponse->getDeploymentId()); + self::assertNull($settingsResponse->getBaseUrl()); + self::assertNull($settingsResponse->getApiKey()); $listResponse = $this->client->chats->listWorkspaces(); self::assertSame([ ['uid' => 'myWorkspace'], - ], $listResponse['results']); + ], $listResponse->getResults()); } } From cd2b8dfbded6ba8cb39f6e12390779c8d7dc472d Mon Sep 17 00:00:00 2001 From: Strift Date: Wed, 13 Aug 2025 18:01:17 +0800 Subject: [PATCH 04/26] Add chat completion streaming --- src/Contracts/Http.php | 6 ++ .../HandlesChatWorkspaceSettings.php | 26 ++++++++- src/Http/Client.php | 55 +++++++++++++++++++ .../{ChatTest.php => ChatWorkspaceTest.php} | 45 ++++++++++++++- 4 files changed, 128 insertions(+), 4 deletions(-) rename tests/Endpoints/{ChatTest.php => ChatWorkspaceTest.php} (73%) diff --git a/src/Contracts/Http.php b/src/Contracts/Http.php index d20d0e64..cd9d1b7b 100644 --- a/src/Contracts/Http.php +++ b/src/Contracts/Http.php @@ -46,4 +46,10 @@ public function patch(string $path, $body = null, array $query = []); * @throws JsonDecodingException */ public function delete(string $path, array $query = []); + + /** + * @throws ApiException + * @throws JsonEncodingException + */ + public function postStream(string $path, $body = null, array $query = []): \Psr\Http\Message\StreamInterface; } diff --git a/src/Endpoints/Delegates/HandlesChatWorkspaceSettings.php b/src/Endpoints/Delegates/HandlesChatWorkspaceSettings.php index ad934c8f..80b6f05d 100644 --- a/src/Endpoints/Delegates/HandlesChatWorkspaceSettings.php +++ b/src/Endpoints/Delegates/HandlesChatWorkspaceSettings.php @@ -13,7 +13,7 @@ trait HandlesChatWorkspaceSettings */ public function getSettings(): ChatWorkspaceSettings { - if (!$this->workspaceName) { + if (null === $this->workspaceName) { throw new \InvalidArgumentException('Workspace name is required to get settings'); } @@ -43,7 +43,7 @@ public function getSettings(): ChatWorkspaceSettings */ public function updateSettings(array $settings): ChatWorkspaceSettings { - if (!$this->workspaceName) { + if (null === $this->workspaceName) { throw new \InvalidArgumentException('Workspace name is required to update settings'); } @@ -57,7 +57,7 @@ public function updateSettings(array $settings): ChatWorkspaceSettings */ public function resetSettings(): ChatWorkspaceSettings { - if (!$this->workspaceName) { + if (null === $this->workspaceName) { throw new \InvalidArgumentException('Workspace name is required to reset settings'); } @@ -65,4 +65,24 @@ public function resetSettings(): ChatWorkspaceSettings return new ChatWorkspaceSettings($response); } + + /** + * Create a streaming chat completion. + * + * @param array{ + * model: string, + * messages: array, + * stream: bool, + * temperature?: float, + * max_tokens?: int + * } $options + */ + public function streamCompletion(array $options): \Psr\Http\Message\StreamInterface + { + if (null === $this->workspaceName) { + throw new \InvalidArgumentException('Workspace name is required for chat completion'); + } + + return $this->http->postStream('/chats/'.$this->workspaceName.'/chat/completions', $options); + } } diff --git a/src/Http/Client.php b/src/Http/Client.php index 9eef6472..87a0019d 100644 --- a/src/Http/Client.php +++ b/src/Http/Client.php @@ -139,6 +139,22 @@ public function delete(string $path, array $query = []) return $this->execute($request); } + /** + * @throws ApiException + * @throws ClientExceptionInterface + * @throws CommunicationException + * @throws JsonEncodingException + */ + public function postStream(string $path, $body = null, array $query = []): \Psr\Http\Message\StreamInterface + { + $request = $this->requestFactory->createRequest( + 'POST', + $this->baseUrl.$path.$this->buildQueryString($query) + )->withBody($this->streamFactory->createStream($this->json->serialize($body))); + + return $this->executeStream($request, ['Content-type' => 'application/json']); + } + /** * @param array $headers * @@ -159,6 +175,45 @@ private function execute(RequestInterface $request, array $headers = []) } } + /** + * @param array $headers + * + * @throws ApiException + * @throws ClientExceptionInterface + * @throws CommunicationException + */ + private function executeStream(RequestInterface $request, array $headers = []): \Psr\Http\Message\StreamInterface + { + foreach (array_merge($this->headers, $headers) as $header => $value) { + $request = $request->withAddedHeader($header, $value); + } + + try { + $response = $this->http->sendRequest($request); + + if ($response->getStatusCode() >= 300) { + $bodyContent = (string) $response->getBody(); + + // Try to parse as JSON for structured errors, fall back to raw content + if ($this->isJSONResponse($response->getHeader('content-type'))) { + try { + $body = $this->json->unserialize($bodyContent) ?? $response->getReasonPhrase(); + } catch (\JsonException $e) { + $body = $bodyContent ?: $response->getReasonPhrase(); + } + } else { + $body = $bodyContent ?: $response->getReasonPhrase(); + } + + throw new ApiException($response, $body); + } + + return $response->getBody(); + } catch (NetworkExceptionInterface $e) { + throw new CommunicationException($e->getMessage(), $e->getCode(), $e); + } + } + private function buildQueryString(array $queryParams = []): string { return \count($queryParams) > 0 ? '?'.http_build_query($queryParams) : ''; diff --git a/tests/Endpoints/ChatTest.php b/tests/Endpoints/ChatWorkspaceTest.php similarity index 73% rename from tests/Endpoints/ChatTest.php rename to tests/Endpoints/ChatWorkspaceTest.php index d5e9be37..66b1b9ea 100644 --- a/tests/Endpoints/ChatTest.php +++ b/tests/Endpoints/ChatWorkspaceTest.php @@ -4,10 +4,11 @@ namespace Tests\Endpoints; +use Meilisearch\Client as MeilisearchClient; use Meilisearch\Http\Client; use Tests\TestCase; -final class ChatTest extends TestCase +final class ChatWorkspaceTest extends TestCase { private array $workspaceSettings = [ "source" => "openAi", @@ -87,4 +88,46 @@ public function testDeleteWorkspaceSettings(): void ['uid' => 'myWorkspace'], ], $listResponse->getResults()); } + + public function testCompletionStreaming(): void + { + $this->client->chats->workspace('myWorkspace')->updateSettings($this->workspaceSettings); + + $stream = $this->client->chats->workspace('myWorkspace')->streamCompletion([ + 'model' => 'gpt-4o-mini', + 'messages' => [ + [ + 'role' => 'user', + 'content' => 'Hello, how are you?', + ], + ], + 'stream' => true, + ]); + + $receivedData = ''; + $chunkCount = 0; + $maxChunks = 1000; // Safety limit + + try { + while (!$stream->eof() && $chunkCount < $maxChunks) { + $chunk = $stream->read(8192); + if ('' === $chunk) { + // Small backoff to avoid tight loop on empty reads + usleep(10_000); + continue; + } + $receivedData .= $chunk; + $chunkCount++; + } + + if ($chunkCount >= $maxChunks) { + self::fail('Test exceeded maximum chunk limit of '.$maxChunks); + } + + self::assertGreaterThan(0, strlen($receivedData)); + } finally { + // Ensure we release network resources + $stream->close(); + } + } } From e52759cf133940fa3b4e1ecaddfd7a4492d1dce9 Mon Sep 17 00:00:00 2001 From: Strift Date: Wed, 13 Aug 2025 18:23:09 +0800 Subject: [PATCH 05/26] Lint --- src/Endpoints/ChatWorkspaces.php | 1 - src/Http/Client.php | 4 ++-- tests/Endpoints/ChatWorkspaceTest.php | 29 +++++++++++++-------------- 3 files changed, 16 insertions(+), 18 deletions(-) diff --git a/src/Endpoints/ChatWorkspaces.php b/src/Endpoints/ChatWorkspaces.php index 3847fdec..3bc9318f 100644 --- a/src/Endpoints/ChatWorkspaces.php +++ b/src/Endpoints/ChatWorkspaces.php @@ -5,7 +5,6 @@ namespace Meilisearch\Endpoints; use Meilisearch\Contracts\ChatWorkspacesResults; -use Meilisearch\Contracts\ChatWorkspaceSettings; use Meilisearch\Contracts\Endpoint; use Meilisearch\Contracts\Http; use Meilisearch\Endpoints\Delegates\HandlesChatWorkspaceSettings; diff --git a/src/Http/Client.php b/src/Http/Client.php index 87a0019d..61bec266 100644 --- a/src/Http/Client.php +++ b/src/Http/Client.php @@ -199,10 +199,10 @@ private function executeStream(RequestInterface $request, array $headers = []): try { $body = $this->json->unserialize($bodyContent) ?? $response->getReasonPhrase(); } catch (\JsonException $e) { - $body = $bodyContent ?: $response->getReasonPhrase(); + $body = $bodyContent !== '' ? $bodyContent : $response->getReasonPhrase(); } } else { - $body = $bodyContent ?: $response->getReasonPhrase(); + $body = $bodyContent !== '' ? $bodyContent : $response->getReasonPhrase(); } throw new ApiException($response, $body); diff --git a/tests/Endpoints/ChatWorkspaceTest.php b/tests/Endpoints/ChatWorkspaceTest.php index 66b1b9ea..5a3d1b30 100644 --- a/tests/Endpoints/ChatWorkspaceTest.php +++ b/tests/Endpoints/ChatWorkspaceTest.php @@ -4,23 +4,22 @@ namespace Tests\Endpoints; -use Meilisearch\Client as MeilisearchClient; use Meilisearch\Http\Client; use Tests\TestCase; final class ChatWorkspaceTest extends TestCase { private array $workspaceSettings = [ - "source" => "openAi", - "orgId" => "some-org-id", - "projectId" => "some-project-id", - "apiVersion" => "some-api-version", - "deploymentId" => "some-deployment-id", - "baseUrl" => "https://baseurl.com", - "apiKey" => "sk-abc...", - "prompts" => [ - "system" => "You are a helpful assistant that answers questions based on the provided context.", - ], + 'source' => 'openAi', + 'orgId' => 'some-org-id', + 'projectId' => 'some-project-id', + 'apiVersion' => 'some-api-version', + 'deploymentId' => 'some-deployment-id', + 'baseUrl' => 'https://baseurl.com', + 'apiKey' => 'sk-abc...', + 'prompts' => [ + 'system' => 'You are a helpful assistant that answers questions based on the provided context.', + ], ]; protected function setUp(): void @@ -66,7 +65,7 @@ public function testListWorkspaces(): void $this->client->chats->workspace('myWorkspace')->updateSettings($this->workspaceSettings); $response = $this->client->chats->listWorkspaces(); self::assertSame([ - ['uid' => 'myWorkspace'], + ['uid' => 'myWorkspace'], ], $response->getResults()); } @@ -85,7 +84,7 @@ public function testDeleteWorkspaceSettings(): void $listResponse = $this->client->chats->listWorkspaces(); self::assertSame([ - ['uid' => 'myWorkspace'], + ['uid' => 'myWorkspace'], ], $listResponse->getResults()); } @@ -117,14 +116,14 @@ public function testCompletionStreaming(): void continue; } $receivedData .= $chunk; - $chunkCount++; + ++$chunkCount; } if ($chunkCount >= $maxChunks) { self::fail('Test exceeded maximum chunk limit of '.$maxChunks); } - self::assertGreaterThan(0, strlen($receivedData)); + self::assertGreaterThan(0, \strlen($receivedData)); } finally { // Ensure we release network resources $stream->close(); From 1395b8dba6587b1e28560202a176ed035907cfe3 Mon Sep 17 00:00:00 2001 From: Strift Date: Wed, 13 Aug 2025 18:35:05 +0800 Subject: [PATCH 06/26] Run phpstan --- src/Http/Client.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Http/Client.php b/src/Http/Client.php index 61bec266..74647adc 100644 --- a/src/Http/Client.php +++ b/src/Http/Client.php @@ -199,10 +199,10 @@ private function executeStream(RequestInterface $request, array $headers = []): try { $body = $this->json->unserialize($bodyContent) ?? $response->getReasonPhrase(); } catch (\JsonException $e) { - $body = $bodyContent !== '' ? $bodyContent : $response->getReasonPhrase(); + $body = '' !== $bodyContent ? $bodyContent : $response->getReasonPhrase(); } } else { - $body = $bodyContent !== '' ? $bodyContent : $response->getReasonPhrase(); + $body = '' !== $bodyContent ? $bodyContent : $response->getReasonPhrase(); } throw new ApiException($response, $body); From f87bde49dc37288f94a4e52ffdca90d70f0955c1 Mon Sep 17 00:00:00 2001 From: Strift Date: Wed, 13 Aug 2025 18:44:40 +0800 Subject: [PATCH 07/26] Fix doc types --- .../Delegates/HandlesChatWorkspaceSettings.php | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/src/Endpoints/Delegates/HandlesChatWorkspaceSettings.php b/src/Endpoints/Delegates/HandlesChatWorkspaceSettings.php index 80b6f05d..9a7d3a64 100644 --- a/src/Endpoints/Delegates/HandlesChatWorkspaceSettings.php +++ b/src/Endpoints/Delegates/HandlesChatWorkspaceSettings.php @@ -33,12 +33,7 @@ public function getSettings(): ChatWorkspaceSettings * deploymentId?: string, * baseUrl?: string, * apiKey?: string, - * prompts?: array{ - * system?: string, - * searchDescription?: string, - * searchQParam?: string, - * searchIndexUidParam?: string - * } + * prompts?: array * } $settings */ public function updateSettings(array $settings): ChatWorkspaceSettings @@ -67,15 +62,13 @@ public function resetSettings(): ChatWorkspaceSettings } /** - * Create a streaming chat completion. + * Create a streaming chat completion using OpenAI-compatible API. * * @param array{ * model: string, * messages: array, - * stream: bool, - * temperature?: float, - * max_tokens?: int - * } $options + * stream: bool + * } $options The request body for the chat completion */ public function streamCompletion(array $options): \Psr\Http\Message\StreamInterface { From b063d4bfc004c410a691ea24213068c6890cc982 Mon Sep 17 00:00:00 2001 From: Strift Date: Fri, 15 Aug 2025 12:12:41 +0800 Subject: [PATCH 08/26] Add get/update methods for chat settings --- src/Endpoints/Delegates/HandlesSettings.php | 97 ++++++++++++++++++++- tests/Settings/ChatTest.php | 64 ++++++++++++++ 2 files changed, 160 insertions(+), 1 deletion(-) create mode 100644 tests/Settings/ChatTest.php diff --git a/src/Endpoints/Delegates/HandlesSettings.php b/src/Endpoints/Delegates/HandlesSettings.php index da392df7..aeecda93 100644 --- a/src/Endpoints/Delegates/HandlesSettings.php +++ b/src/Endpoints/Delegates/HandlesSettings.php @@ -418,18 +418,27 @@ public function resetSearchCutoffMs(): array return $this->http->delete(self::PATH.'/'.$this->uid.'/settings/search-cutoff-ms'); } - // Settings - Experimental: Embedders (hybrid search) + // Settings - Embedders + /** + * @since Meilisearch v1.13.0 + */ public function getEmbedders(): ?array { return $this->http->get(self::PATH.'/'.$this->uid.'/settings/embedders'); } + /** + * @since Meilisearch v1.13.0 + */ public function updateEmbedders(array $embedders): array { return $this->http->patch(self::PATH.'/'.$this->uid.'/settings/embedders', $embedders); } + /** + * @since Meilisearch v1.13.0 + */ public function resetEmbedders(): array { return $this->http->delete(self::PATH.'/'.$this->uid.'/settings/embedders'); @@ -490,4 +499,90 @@ public function resetPrefixSearch(): array { return $this->http->delete(self::PATH.'/'.$this->uid.'/settings/prefix-search'); } + + // Settings - Chat + + /** + * @since Meilisearch v1.15.1 + * @return array{ + * description: string, + * documentTemplate: string, + * documentTemplateMaxBytes: int, + * searchParameters: array{ + * indexUid?: non-empty-string, + * q?: string, + * filter?: list>, + * locales?: list, + * attributesToRetrieve?: list, + * attributesToCrop?: list, + * cropLength?: positive-int, + * attributesToHighlight?: list, + * cropMarker?: string, + * highlightPreTag?: string, + * highlightPostTag?: string, + * facets?: list, + * showMatchesPosition?: bool, + * sort?: list, + * matchingStrategy?: 'last'|'all'|'frequency', + * offset?: non-negative-int, + * limit?: non-negative-int, + * hitsPerPage?: non-negative-int, + * page?: non-negative-int, + * vector?: non-empty-list>, + * hybrid?: array, + * attributesToSearchOn?: non-empty-list, + * showRankingScore?: bool, + * showRankingScoreDetails?: bool, + * rankingScoreThreshold?: float, + * distinct?: non-empty-string, + * federationOptions?: array + * } + * } + */ + public function getChat(): array + { + return $this->http->get(self::PATH.'/'.$this->uid.'/settings/chat'); + } + + /** + * @since Meilisearch v1.15.1 + * @param array{ + * description: string, + * documentTemplate: string, + * documentTemplateMaxBytes: int, + * searchParameters: array{ + * indexUid?: non-empty-string, + * q?: string, + * filter?: list>, + * locales?: list, + * attributesToRetrieve?: list, + * attributesToCrop?: list, + * cropLength?: positive-int, + * attributesToHighlight?: list, + * cropMarker?: string, + * highlightPreTag?: string, + * highlightPostTag?: string, + * facets?: list, + * showMatchesPosition?: bool, + * sort?: list, + * matchingStrategy?: 'last'|'all'|'frequency', + * offset?: non-negative-int, + * limit?: non-negative-int, + * hitsPerPage?: non-negative-int, + * page?: non-negative-int, + * vector?: non-empty-list>, + * hybrid?: array, + * attributesToSearchOn?: non-empty-list, + * showRankingScore?: bool, + * showRankingScoreDetails?: bool, + * rankingScoreThreshold?: float, + * distinct?: non-empty-string, + * federationOptions?: array + * } + * } $chatSettings + */ + public function updateChat(array $chatSettings): array + { + return $this->http->patch(self::PATH.'/'.$this->uid.'/settings/chat', $chatSettings); + } } diff --git a/tests/Settings/ChatTest.php b/tests/Settings/ChatTest.php new file mode 100644 index 00000000..8c163572 --- /dev/null +++ b/tests/Settings/ChatTest.php @@ -0,0 +1,64 @@ + '', + 'documentTemplate' => '{% for field in fields %}{% if field.is_searchable and field.value != nil %}{{ field.name }}: {{ field.value }} +{% endif %}{% endfor %}', + 'documentTemplateMaxBytes' => 400, + 'searchParameters' => [] + ]; + + protected function setUp(): void + { + parent::setUp(); + + $http = new Client($this->host, getenv('MEILISEARCH_API_KEY')); + $http->patch('/experimental-features', ['chatCompletions' => true]); + + $this->index = $this->createEmptyIndex($this->safeIndexName()); + } + + public function testGetChatDefaultSettings(): void + { + $settings = $this->index->getChat(); + self::assertSame(self::DEFAULT_CHAT_SETTINGS['description'], $settings['description']); + self::assertSame(self::DEFAULT_CHAT_SETTINGS['documentTemplate'], $settings['documentTemplate']); + self::assertSame(self::DEFAULT_CHAT_SETTINGS['documentTemplateMaxBytes'], $settings['documentTemplateMaxBytes']); + self::assertSame(self::DEFAULT_CHAT_SETTINGS['searchParameters'], $settings['searchParameters']); + } + + public function testUpdateChatSettings(): void + { + $newSettings = [ + 'description' => 'New description', + 'documentTemplate' => 'New document template', + 'documentTemplateMaxBytes' => 500, + 'searchParameters' => [ + 'limit' => 10, + ], + ]; + + $promise = $this->index->updateChat($newSettings); + + $this->assertIsValidPromise($promise); + $this->index->waitForTask($promise['taskUid']); + + $settings = $this->index->getChat(); + self::assertSame($newSettings['description'], $settings['description']); + self::assertSame($newSettings['documentTemplate'], $settings['documentTemplate']); + self::assertSame($newSettings['documentTemplateMaxBytes'], $settings['documentTemplateMaxBytes']); + self::assertSame($newSettings['searchParameters'], $settings['searchParameters']); + } +} From b09585f0a819cfe47b60309fd63adc75a28773de Mon Sep 17 00:00:00 2001 From: Strift Date: Fri, 15 Aug 2025 13:06:28 +0800 Subject: [PATCH 09/26] Fix incorrect exception catch block --- src/Http/Client.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Http/Client.php b/src/Http/Client.php index 74647adc..abf0acbc 100644 --- a/src/Http/Client.php +++ b/src/Http/Client.php @@ -198,7 +198,7 @@ private function executeStream(RequestInterface $request, array $headers = []): if ($this->isJSONResponse($response->getHeader('content-type'))) { try { $body = $this->json->unserialize($bodyContent) ?? $response->getReasonPhrase(); - } catch (\JsonException $e) { + } catch (JsonDecodingException $e) { $body = '' !== $bodyContent ? $bodyContent : $response->getReasonPhrase(); } } else { From 0cc39727766a7459dc7905fe8d4938cefbcb458f Mon Sep 17 00:00:00 2001 From: Strift Date: Fri, 15 Aug 2025 13:12:26 +0800 Subject: [PATCH 10/26] lint --- src/Endpoints/Delegates/HandlesSettings.php | 2 + tests/Settings/ChatTest.php | 50 ++++++++++----------- 2 files changed, 27 insertions(+), 25 deletions(-) diff --git a/src/Endpoints/Delegates/HandlesSettings.php b/src/Endpoints/Delegates/HandlesSettings.php index aeecda93..203e84af 100644 --- a/src/Endpoints/Delegates/HandlesSettings.php +++ b/src/Endpoints/Delegates/HandlesSettings.php @@ -504,6 +504,7 @@ public function resetPrefixSearch(): array /** * @since Meilisearch v1.15.1 + * * @return array{ * description: string, * documentTemplate: string, @@ -546,6 +547,7 @@ public function getChat(): array /** * @since Meilisearch v1.15.1 + * * @param array{ * description: string, * documentTemplate: string, diff --git a/tests/Settings/ChatTest.php b/tests/Settings/ChatTest.php index 8c163572..5bd23395 100644 --- a/tests/Settings/ChatTest.php +++ b/tests/Settings/ChatTest.php @@ -13,11 +13,11 @@ final class ChatTest extends TestCase private Indexes $index; private const DEFAULT_CHAT_SETTINGS = [ - 'description' => '', - 'documentTemplate' => '{% for field in fields %}{% if field.is_searchable and field.value != nil %}{{ field.name }}: {{ field.value }} + 'description' => '', + 'documentTemplate' => '{% for field in fields %}{% if field.is_searchable and field.value != nil %}{{ field.name }}: {{ field.value }} {% endif %}{% endfor %}', - 'documentTemplateMaxBytes' => 400, - 'searchParameters' => [] + 'documentTemplateMaxBytes' => 400, + 'searchParameters' => [], ]; protected function setUp(): void @@ -32,33 +32,33 @@ protected function setUp(): void public function testGetChatDefaultSettings(): void { - $settings = $this->index->getChat(); - self::assertSame(self::DEFAULT_CHAT_SETTINGS['description'], $settings['description']); - self::assertSame(self::DEFAULT_CHAT_SETTINGS['documentTemplate'], $settings['documentTemplate']); - self::assertSame(self::DEFAULT_CHAT_SETTINGS['documentTemplateMaxBytes'], $settings['documentTemplateMaxBytes']); - self::assertSame(self::DEFAULT_CHAT_SETTINGS['searchParameters'], $settings['searchParameters']); + $settings = $this->index->getChat(); + self::assertSame(self::DEFAULT_CHAT_SETTINGS['description'], $settings['description']); + self::assertSame(self::DEFAULT_CHAT_SETTINGS['documentTemplate'], $settings['documentTemplate']); + self::assertSame(self::DEFAULT_CHAT_SETTINGS['documentTemplateMaxBytes'], $settings['documentTemplateMaxBytes']); + self::assertSame(self::DEFAULT_CHAT_SETTINGS['searchParameters'], $settings['searchParameters']); } public function testUpdateChatSettings(): void { - $newSettings = [ - 'description' => 'New description', - 'documentTemplate' => 'New document template', - 'documentTemplateMaxBytes' => 500, - 'searchParameters' => [ - 'limit' => 10, - ], - ]; + $newSettings = [ + 'description' => 'New description', + 'documentTemplate' => 'New document template', + 'documentTemplateMaxBytes' => 500, + 'searchParameters' => [ + 'limit' => 10, + ], + ]; - $promise = $this->index->updateChat($newSettings); + $promise = $this->index->updateChat($newSettings); - $this->assertIsValidPromise($promise); - $this->index->waitForTask($promise['taskUid']); + $this->assertIsValidPromise($promise); + $this->index->waitForTask($promise['taskUid']); - $settings = $this->index->getChat(); - self::assertSame($newSettings['description'], $settings['description']); - self::assertSame($newSettings['documentTemplate'], $settings['documentTemplate']); - self::assertSame($newSettings['documentTemplateMaxBytes'], $settings['documentTemplateMaxBytes']); - self::assertSame($newSettings['searchParameters'], $settings['searchParameters']); + $settings = $this->index->getChat(); + self::assertSame($newSettings['description'], $settings['description']); + self::assertSame($newSettings['documentTemplate'], $settings['documentTemplate']); + self::assertSame($newSettings['documentTemplateMaxBytes'], $settings['documentTemplateMaxBytes']); + self::assertSame($newSettings['searchParameters'], $settings['searchParameters']); } } From 2b98215752a73a7c760360c7fbda147480172770 Mon Sep 17 00:00:00 2001 From: Laurent Cazanove Date: Fri, 22 Aug 2025 13:44:55 +0800 Subject: [PATCH 11/26] Update src/Http/Client.php MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Tomas Norkūnas --- src/Http/Client.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Http/Client.php b/src/Http/Client.php index abf0acbc..54df8128 100644 --- a/src/Http/Client.php +++ b/src/Http/Client.php @@ -145,7 +145,7 @@ public function delete(string $path, array $query = []) * @throws CommunicationException * @throws JsonEncodingException */ - public function postStream(string $path, $body = null, array $query = []): \Psr\Http\Message\StreamInterface + public function postStream(string $path, $body = null, array $query = []): StreamInterface { $request = $this->requestFactory->createRequest( 'POST', From bde19cca30fe8b4845bc2eac86c3117f0ef9f516 Mon Sep 17 00:00:00 2001 From: Laurent Cazanove Date: Fri, 22 Aug 2025 13:45:06 +0800 Subject: [PATCH 12/26] Update src/Http/Client.php MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Tomas Norkūnas --- src/Http/Client.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Http/Client.php b/src/Http/Client.php index 54df8128..d80ab6be 100644 --- a/src/Http/Client.php +++ b/src/Http/Client.php @@ -182,7 +182,7 @@ private function execute(RequestInterface $request, array $headers = []) * @throws ClientExceptionInterface * @throws CommunicationException */ - private function executeStream(RequestInterface $request, array $headers = []): \Psr\Http\Message\StreamInterface + private function executeStream(RequestInterface $request, array $headers = []): StreamInterface { foreach (array_merge($this->headers, $headers) as $header => $value) { $request = $request->withAddedHeader($header, $value); From cb6c0bb7bf09532f7d5f02d486fb20ca9fa09cf8 Mon Sep 17 00:00:00 2001 From: Strift Date: Fri, 22 Aug 2025 14:32:49 +0800 Subject: [PATCH 13/26] Improve chat workspaces type hints --- .../ChatWorkspacePromptsSettings.php | 69 +++++++++++++++++++ src/Contracts/ChatWorkspaceSettings.php | 53 +++++++++++--- src/Contracts/ChatWorkspacesResults.php | 4 +- src/Http/Client.php | 1 + tests/Endpoints/ChatWorkspaceTest.php | 26 +++++-- 5 files changed, 137 insertions(+), 16 deletions(-) create mode 100644 src/Contracts/ChatWorkspacePromptsSettings.php diff --git a/src/Contracts/ChatWorkspacePromptsSettings.php b/src/Contracts/ChatWorkspacePromptsSettings.php new file mode 100644 index 00000000..bb6fad0d --- /dev/null +++ b/src/Contracts/ChatWorkspacePromptsSettings.php @@ -0,0 +1,69 @@ +system = $params['system']; + $this->searchDescription = $params['searchDescription']; + $this->searchQParam = $params['searchQParam']; + $this->searchIndexUidParam = $params['searchIndexUidParam']; + } + + public function getSystem(): string + { + return $this->system; + } + + public function getSearchDescription(): string + { + return $this->searchDescription; + } + + public function getSearchQParam(): string + { + return $this->searchQParam; + } + + public function getSearchIndexUidParam(): string + { + return $this->searchIndexUidParam; + } + + /** + * @return array{ + * system: string, + * searchDescription: string, + * searchQParam: string, + * searchIndexUidParam: string + * } + */ + public function toArray(): array + { + return [ + 'system' => $this->system, + 'searchDescription' => $this->searchDescription, + 'searchQParam' => $this->searchQParam, + 'searchIndexUidParam' => $this->searchIndexUidParam, + ]; + } +} diff --git a/src/Contracts/ChatWorkspaceSettings.php b/src/Contracts/ChatWorkspaceSettings.php index 5aaf4953..b6b31d13 100644 --- a/src/Contracts/ChatWorkspaceSettings.php +++ b/src/Contracts/ChatWorkspaceSettings.php @@ -4,32 +4,51 @@ namespace Meilisearch\Contracts; +use Meilisearch\Contracts\ChatWorkspacePromptsSettings; + class ChatWorkspaceSettings extends Data { - private ?string $source; + private string $source; private ?string $orgId; private ?string $projectId; private ?string $apiVersion; private ?string $deploymentId; private ?string $baseUrl; private ?string $apiKey; - private array $prompts; + private ?ChatWorkspacePromptsSettings $prompts; + /** + * @param array{ + * source: string, + * orgId?: string, + * projectId?: string, + * apiVersion?: string, + * deploymentId?: string, + * baseUrl?: string, + * apiKey?: string, + * prompts?: array{ + * system: string, + * searchDescription: string, + * searchQParam: string, + * searchIndexUidParam: string + * } + * } $params + */ public function __construct(array $params) { parent::__construct($params); - $this->source = $params['source'] ?? null; + $this->source = $params['source']; $this->orgId = $params['orgId'] ?? null; $this->projectId = $params['projectId'] ?? null; $this->apiVersion = $params['apiVersion'] ?? null; $this->deploymentId = $params['deploymentId'] ?? null; $this->baseUrl = $params['baseUrl'] ?? null; $this->apiKey = $params['apiKey'] ?? null; - $this->prompts = $params['prompts'] ?? []; + $this->prompts = $params['prompts'] === null ? null : new ChatWorkspacePromptsSettings($params['prompts']); } - public function getSource(): ?string + public function getSource(): string { return $this->source; } @@ -64,14 +83,28 @@ public function getApiKey(): ?string return $this->apiKey; } - /** - * @return array{system?: string, searchDescription?: string, searchQParam?: string, searchIndexUidParam?: string} - */ - public function getPrompts(): array + public function getPrompts(): ?ChatWorkspacePromptsSettings { return $this->prompts; } + /** + * @return array{ + * source: string, + * orgId?: string, + * projectId?: string, + * apiVersion?: string, + * deploymentId?: string, + * baseUrl?: string, + * apiKey?: string, + * prompts?: array{ + * system: string, + * searchDescription: string, + * searchQParam: string, + * searchIndexUidParam: string + * } + * } + */ public function toArray(): array { return [ @@ -82,7 +115,7 @@ public function toArray(): array 'deploymentId' => $this->deploymentId, 'baseUrl' => $this->baseUrl, 'apiKey' => $this->apiKey, - 'prompts' => $this->prompts, + 'prompts' => $this->prompts === null ? null : $this->prompts->toArray(), ]; } } diff --git a/src/Contracts/ChatWorkspacesResults.php b/src/Contracts/ChatWorkspacesResults.php index cfcb6d2d..b31aa10b 100644 --- a/src/Contracts/ChatWorkspacesResults.php +++ b/src/Contracts/ChatWorkspacesResults.php @@ -12,11 +12,11 @@ class ChatWorkspacesResults extends Data public function __construct(array $params) { - parent::__construct($params['results'] ?? []); + parent::__construct($params['results']); $this->offset = $params['offset']; $this->limit = $params['limit']; - $this->total = $params['total'] ?? 0; + $this->total = $params['total']; } /** diff --git a/src/Http/Client.php b/src/Http/Client.php index d80ab6be..3805e63f 100644 --- a/src/Http/Client.php +++ b/src/Http/Client.php @@ -21,6 +21,7 @@ use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\StreamFactoryInterface; +use Psr\Http\Message\StreamInterface; class Client implements Http { diff --git a/tests/Endpoints/ChatWorkspaceTest.php b/tests/Endpoints/ChatWorkspaceTest.php index 5a3d1b30..30a9c1de 100644 --- a/tests/Endpoints/ChatWorkspaceTest.php +++ b/tests/Endpoints/ChatWorkspaceTest.php @@ -4,13 +4,14 @@ namespace Tests\Endpoints; +use Meilisearch\Exceptions\ApiException; use Meilisearch\Http\Client; use Tests\TestCase; final class ChatWorkspaceTest extends TestCase { private array $workspaceSettings = [ - 'source' => 'openAi', + 'source' => 'mistral', 'orgId' => 'some-org-id', 'projectId' => 'some-project-id', 'apiVersion' => 'some-api-version', @@ -44,6 +45,13 @@ public function testUpdateWorkspacesSettings(): void self::assertSame('XXX...', $response->getApiKey()); } + public function testExceptionWhenWorkspaceDoesNotExist(): void + { + self::expectException(ApiException::class); + self::expectExceptionCode(404); + $this->client->chats->workspace('non-existent-workspace')->getSettings(); + } + public function testGetWorkspaceSettings(): void { $this->client->chats->workspace('myWorkspace')->updateSettings($this->workspaceSettings); @@ -55,9 +63,13 @@ public function testGetWorkspaceSettings(): void self::assertSame($this->workspaceSettings['apiVersion'], $response->getApiVersion()); self::assertSame($this->workspaceSettings['deploymentId'], $response->getDeploymentId()); self::assertSame($this->workspaceSettings['baseUrl'], $response->getBaseUrl()); - self::assertSame($this->workspaceSettings['prompts']['system'], $response->getPrompts()['system']); // Meilisearch will mask the API key in the response self::assertSame('XXX...', $response->getApiKey()); + + self::assertSame($this->workspaceSettings['prompts']['system'], $response->getPrompts()->getSystem()); + self::assertNotEmpty($response->getPrompts()->getSearchDescription()); + self::assertNotEmpty($response->getPrompts()->getSearchQParam()); + self::assertNotEmpty($response->getPrompts()->getSearchIndexUidParam()); } public function testListWorkspaces(): void @@ -69,19 +81,25 @@ public function testListWorkspaces(): void ], $response->getResults()); } - public function testDeleteWorkspaceSettings(): void + public function testResetWorkspaceSettings(): void { $this->client->chats->workspace('myWorkspace')->updateSettings($this->workspaceSettings); $this->client->chats->workspace('myWorkspace')->resetSettings(); $settingsResponse = $this->client->chats->workspace('myWorkspace')->getSettings(); - self::assertSame('openAi', $settingsResponse->getSource()); + self::assertSame('openAi', $settingsResponse->getSource()); // Source is reset to openAi for some reason self::assertNull($settingsResponse->getOrgId()); self::assertNull($settingsResponse->getProjectId()); self::assertNull($settingsResponse->getApiVersion()); self::assertNull($settingsResponse->getDeploymentId()); self::assertNull($settingsResponse->getBaseUrl()); self::assertNull($settingsResponse->getApiKey()); + // Prompts are reset to their original values + self::assertNotEmpty($settingsResponse->getPrompts()->getSystem()); + self::assertNotEmpty($settingsResponse->getPrompts()->getSearchDescription()); + self::assertNotEmpty($settingsResponse->getPrompts()->getSearchQParam()); + self::assertNotEmpty($settingsResponse->getPrompts()->getSearchIndexUidParam()); + // Workspace still appears when listing workspaces $listResponse = $this->client->chats->listWorkspaces(); self::assertSame([ ['uid' => 'myWorkspace'], From b6ae3bb10df735278c5ef407a54fcce2883cf1c8 Mon Sep 17 00:00:00 2001 From: Strift Date: Fri, 22 Aug 2025 15:27:18 +0800 Subject: [PATCH 14/26] Make workspaceName non-empty-string --- src/Endpoints/ChatWorkspaces.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Endpoints/ChatWorkspaces.php b/src/Endpoints/ChatWorkspaces.php index 3bc9318f..6568c9d9 100644 --- a/src/Endpoints/ChatWorkspaces.php +++ b/src/Endpoints/ChatWorkspaces.php @@ -15,6 +15,9 @@ class ChatWorkspaces extends Endpoint protected const PATH = '/chats'; + /** + * @var non-empty-string|null + */ private ?string $workspaceName; public function __construct(Http $http, ?string $workspaceName = null) From c91b873807b96277b3cabc96a4522217cf015dec Mon Sep 17 00:00:00 2001 From: Strift Date: Fri, 22 Aug 2025 15:29:26 +0800 Subject: [PATCH 15/26] Throw exception if token is not available --- tests/Settings/ChatTest.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/tests/Settings/ChatTest.php b/tests/Settings/ChatTest.php index 5bd23395..9b478f3b 100644 --- a/tests/Settings/ChatTest.php +++ b/tests/Settings/ChatTest.php @@ -24,9 +24,13 @@ protected function setUp(): void { parent::setUp(); - $http = new Client($this->host, getenv('MEILISEARCH_API_KEY')); - $http->patch('/experimental-features', ['chatCompletions' => true]); + $apiKey = getenv('MEILISEARCH_API_KEY'); + if (!$apiKey) { + throw new \Exception('Missing `MEILISEARCH_API_KEY` environment variable'); + } + $http = new Client($this->host, $apiKey); + $http->patch('/experimental-features', ['chatCompletions' => true]); $this->index = $this->createEmptyIndex($this->safeIndexName()); } From dd6aef9ef75a2c2d834dfbae0971c5af1c6bc31d Mon Sep 17 00:00:00 2001 From: Strift Date: Fri, 22 Aug 2025 15:48:54 +0800 Subject: [PATCH 16/26] Update types and tests --- src/Contracts/ChatWorkspaceSettings.php | 14 ++++++-------- tests/Endpoints/ChatWorkspaceTest.php | 11 ++++++++--- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/src/Contracts/ChatWorkspaceSettings.php b/src/Contracts/ChatWorkspaceSettings.php index b6b31d13..9eaa4455 100644 --- a/src/Contracts/ChatWorkspaceSettings.php +++ b/src/Contracts/ChatWorkspaceSettings.php @@ -4,8 +4,6 @@ namespace Meilisearch\Contracts; -use Meilisearch\Contracts\ChatWorkspacePromptsSettings; - class ChatWorkspaceSettings extends Data { private string $source; @@ -15,7 +13,7 @@ class ChatWorkspaceSettings extends Data private ?string $deploymentId; private ?string $baseUrl; private ?string $apiKey; - private ?ChatWorkspacePromptsSettings $prompts; + private ChatWorkspacePromptsSettings $prompts; /** * @param array{ @@ -26,7 +24,7 @@ class ChatWorkspaceSettings extends Data * deploymentId?: string, * baseUrl?: string, * apiKey?: string, - * prompts?: array{ + * prompts: array{ * system: string, * searchDescription: string, * searchQParam: string, @@ -45,7 +43,7 @@ public function __construct(array $params) $this->deploymentId = $params['deploymentId'] ?? null; $this->baseUrl = $params['baseUrl'] ?? null; $this->apiKey = $params['apiKey'] ?? null; - $this->prompts = $params['prompts'] === null ? null : new ChatWorkspacePromptsSettings($params['prompts']); + $this->prompts = new ChatWorkspacePromptsSettings($params['prompts']); } public function getSource(): string @@ -83,7 +81,7 @@ public function getApiKey(): ?string return $this->apiKey; } - public function getPrompts(): ?ChatWorkspacePromptsSettings + public function getPrompts(): ChatWorkspacePromptsSettings { return $this->prompts; } @@ -97,7 +95,7 @@ public function getPrompts(): ?ChatWorkspacePromptsSettings * deploymentId?: string, * baseUrl?: string, * apiKey?: string, - * prompts?: array{ + * prompts: array{ * system: string, * searchDescription: string, * searchQParam: string, @@ -115,7 +113,7 @@ public function toArray(): array 'deploymentId' => $this->deploymentId, 'baseUrl' => $this->baseUrl, 'apiKey' => $this->apiKey, - 'prompts' => $this->prompts === null ? null : $this->prompts->toArray(), + 'prompts' => $this->prompts->toArray(), ]; } } diff --git a/tests/Endpoints/ChatWorkspaceTest.php b/tests/Endpoints/ChatWorkspaceTest.php index 30a9c1de..4950a5a8 100644 --- a/tests/Endpoints/ChatWorkspaceTest.php +++ b/tests/Endpoints/ChatWorkspaceTest.php @@ -20,6 +20,9 @@ final class ChatWorkspaceTest extends TestCase 'apiKey' => 'sk-abc...', 'prompts' => [ 'system' => 'You are a helpful assistant that answers questions based on the provided context.', + 'searchDescription' => 'You are a helpful assistant that answers questions based on the provided context.', + 'searchQParam' => 'q', + 'searchIndexUidParam' => 'indexUid', ], ]; @@ -29,6 +32,8 @@ protected function setUp(): void $http = new Client($this->host, getenv('MEILISEARCH_API_KEY')); $http->patch('/experimental-features', ['chatCompletions' => true]); + + // List workspaces and } public function testUpdateWorkspacesSettings(): void @@ -67,9 +72,9 @@ public function testGetWorkspaceSettings(): void self::assertSame('XXX...', $response->getApiKey()); self::assertSame($this->workspaceSettings['prompts']['system'], $response->getPrompts()->getSystem()); - self::assertNotEmpty($response->getPrompts()->getSearchDescription()); - self::assertNotEmpty($response->getPrompts()->getSearchQParam()); - self::assertNotEmpty($response->getPrompts()->getSearchIndexUidParam()); + self::assertSame($this->workspaceSettings['prompts']['searchDescription'], $response->getPrompts()->getSearchDescription()); + self::assertSame($this->workspaceSettings['prompts']['searchQParam'], $response->getPrompts()->getSearchQParam()); + self::assertSame($this->workspaceSettings['prompts']['searchIndexUidParam'], $response->getPrompts()->getSearchIndexUidParam()); } public function testListWorkspaces(): void From d78323d4f0ac99e81799dd8ec621e8e790ec3ab8 Mon Sep 17 00:00:00 2001 From: Laurent Cazanove Date: Tue, 2 Sep 2025 14:58:33 +0800 Subject: [PATCH 17/26] Update src/Contracts/ChatWorkspacesResults.php MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Tomas Norkūnas --- src/Contracts/ChatWorkspacesResults.php | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/Contracts/ChatWorkspacesResults.php b/src/Contracts/ChatWorkspacesResults.php index b31aa10b..8162497a 100644 --- a/src/Contracts/ChatWorkspacesResults.php +++ b/src/Contracts/ChatWorkspacesResults.php @@ -6,8 +6,19 @@ class ChatWorkspacesResults extends Data { + /** + * @var non-negative-int + */ private int $offset; + + /** + * @var non-negative-int + */ private int $limit; + + /** + * @var non-negative-int + */ private int $total; public function __construct(array $params) From 4dce531dc960a1db6991c55ff06677e934b3852d Mon Sep 17 00:00:00 2001 From: Laurent Cazanove Date: Tue, 2 Sep 2025 14:58:45 +0800 Subject: [PATCH 18/26] Update src/Contracts/ChatWorkspacesResults.php MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Tomas Norkūnas --- src/Contracts/ChatWorkspacesResults.php | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/Contracts/ChatWorkspacesResults.php b/src/Contracts/ChatWorkspacesResults.php index 8162497a..75e03c60 100644 --- a/src/Contracts/ChatWorkspacesResults.php +++ b/src/Contracts/ChatWorkspacesResults.php @@ -38,16 +38,25 @@ public function getResults(): array return $this->data; } + /** + * @return non-negative-int + */ public function getOffset(): int { return $this->offset; } + /** + * @return non-negative-int + */ public function getLimit(): int { return $this->limit; } + /** + * @return non-negative-int + */ public function getTotal(): int { return $this->total; From 72e933e33ea897ce0115510ec146b0632e4e6304 Mon Sep 17 00:00:00 2001 From: Strift Date: Wed, 3 Sep 2025 15:27:49 +0800 Subject: [PATCH 19/26] Require prompts settings search params to be non-empty --- .../ChatWorkspacePromptsSettings.php | 34 ++++++++++++------- 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/src/Contracts/ChatWorkspacePromptsSettings.php b/src/Contracts/ChatWorkspacePromptsSettings.php index bb6fad0d..a2c49683 100644 --- a/src/Contracts/ChatWorkspacePromptsSettings.php +++ b/src/Contracts/ChatWorkspacePromptsSettings.php @@ -4,20 +4,29 @@ namespace Meilisearch\Contracts; +/** + * @phpstan-type ChatWorkspacePromptsArray array{ + * system: string, + * searchDescription: string, + * searchQParam: non-empty-string, + * searchIndexUidParam: non-empty-string + * } + */ class ChatWorkspacePromptsSettings extends Data { public string $system; public string $searchDescription; + /** + * @var non-empty-string + */ public string $searchQParam; + /** + * @var non-empty-string + */ public string $searchIndexUidParam; /** - * @param array{ - * system: string, - * searchDescription: string, - * searchQParam: string, - * searchIndexUidParam: string - * } $params + * @param ChatWorkspacePromptsArray $params */ public function __construct(array $params) { @@ -39,23 +48,24 @@ public function getSearchDescription(): string return $this->searchDescription; } + /** + * @return non-empty-string + */ public function getSearchQParam(): string { return $this->searchQParam; } + /** + * @return non-empty-string + */ public function getSearchIndexUidParam(): string { return $this->searchIndexUidParam; } /** - * @return array{ - * system: string, - * searchDescription: string, - * searchQParam: string, - * searchIndexUidParam: string - * } + * @return ChatWorkspacePromptsArray */ public function toArray(): array { From 586c3c6351c79b31675f745fc50de53425896cea Mon Sep 17 00:00:00 2001 From: Strift Date: Wed, 3 Sep 2025 15:58:22 +0800 Subject: [PATCH 20/26] Add type hints to chat workspace settings --- .../ChatWorkspacePromptsSettings.php | 7 ++ src/Contracts/ChatWorkspaceSettings.php | 77 +++++++++++++++---- 2 files changed, 68 insertions(+), 16 deletions(-) diff --git a/src/Contracts/ChatWorkspacePromptsSettings.php b/src/Contracts/ChatWorkspacePromptsSettings.php index a2c49683..bd30e92b 100644 --- a/src/Contracts/ChatWorkspacePromptsSettings.php +++ b/src/Contracts/ChatWorkspacePromptsSettings.php @@ -12,9 +12,16 @@ * searchIndexUidParam: non-empty-string * } */ + class ChatWorkspacePromptsSettings extends Data { + /** + * @var string + */ public string $system; + /** + * @var string + */ public string $searchDescription; /** * @var non-empty-string diff --git a/src/Contracts/ChatWorkspaceSettings.php b/src/Contracts/ChatWorkspaceSettings.php index 9eaa4455..80abe77d 100644 --- a/src/Contracts/ChatWorkspaceSettings.php +++ b/src/Contracts/ChatWorkspaceSettings.php @@ -4,31 +4,55 @@ namespace Meilisearch\Contracts; +/** + * @phpstan-type ChatWorkspaceSource 'openAi'|'azureOpenAi'|'mistral'|'gemini'|'vLlm' + */ class ChatWorkspaceSettings extends Data { + /** + * @var ChatWorkspaceSource + */ private string $source; + /** + * @var non-empty-string|null + */ private ?string $orgId; + /** + * @var non-empty-string|null + */ private ?string $projectId; + /** + * @var non-empty-string|null + */ private ?string $apiVersion; + /** + * @var non-empty-string|null + */ private ?string $deploymentId; + /** + * @var non-empty-string|null + */ private ?string $baseUrl; + /** + * @var string|null + */ private ?string $apiKey; private ChatWorkspacePromptsSettings $prompts; /** * @param array{ - * source: string, - * orgId?: string, - * projectId?: string, - * apiVersion?: string, - * deploymentId?: string, - * baseUrl?: string, + * source: ChatWorkspaceSource, + * orgId?: non-empty-string, + * projectId?: non-empty-string, + * apiVersion?: non-empty-string, + * deploymentId?: non-empty-string, + * baseUrl?: non-empty-string, * apiKey?: string, * prompts: array{ * system: string, * searchDescription: string, - * searchQParam: string, - * searchIndexUidParam: string + * searchQParam: non-empty-string, + * searchIndexUidParam: non-empty-string * } * } $params */ @@ -46,36 +70,57 @@ public function __construct(array $params) $this->prompts = new ChatWorkspacePromptsSettings($params['prompts']); } + /** + * @return ChatWorkspaceSource + */ public function getSource(): string { return $this->source; } + /** + * @return non-empty-string|null + */ public function getOrgId(): ?string { return $this->orgId; } + /** + * @return non-empty-string|null + */ public function getProjectId(): ?string { return $this->projectId; } + /** + * @return non-empty-string|null + */ public function getApiVersion(): ?string { return $this->apiVersion; } + /** + * @return non-empty-string|null + */ public function getDeploymentId(): ?string { return $this->deploymentId; } + /** + * @return non-empty-string|null + */ public function getBaseUrl(): ?string { return $this->baseUrl; } + /** + * @return non-empty-string|null + */ public function getApiKey(): ?string { return $this->apiKey; @@ -88,18 +133,18 @@ public function getPrompts(): ChatWorkspacePromptsSettings /** * @return array{ - * source: string, - * orgId?: string, - * projectId?: string, - * apiVersion?: string, - * deploymentId?: string, - * baseUrl?: string, + * source: ChatWorkspaceSource, + * orgId?: non-empty-string, + * projectId?: non-empty-string, + * apiVersion?: non-empty-string, + * deploymentId?: non-empty-string, + * baseUrl?: non-empty-string, * apiKey?: string, * prompts: array{ * system: string, * searchDescription: string, - * searchQParam: string, - * searchIndexUidParam: string + * searchQParam: non-empty-string, + * searchIndexUidParam: non-empty-string * } * } */ From fb270353540837d42dbd0f9b179ade6953dfb7ae Mon Sep 17 00:00:00 2001 From: Strift Date: Wed, 3 Sep 2025 16:03:40 +0800 Subject: [PATCH 21/26] Added typehints to chat workspaces results --- src/Contracts/ChatWorkspacesResults.php | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/Contracts/ChatWorkspacesResults.php b/src/Contracts/ChatWorkspacesResults.php index 75e03c60..9132e2c3 100644 --- a/src/Contracts/ChatWorkspacesResults.php +++ b/src/Contracts/ChatWorkspacesResults.php @@ -10,12 +10,12 @@ class ChatWorkspacesResults extends Data * @var non-negative-int */ private int $offset; - + /** * @var non-negative-int */ private int $limit; - + /** * @var non-negative-int */ @@ -62,6 +62,14 @@ public function getTotal(): int return $this->total; } + /** + * @return array{ + * results: array, + * offset: non-negative-int, + * limit: non-negative-int, + * total: non-negative-int + * } + */ public function toArray(): array { return [ @@ -72,6 +80,9 @@ public function toArray(): array ]; } + /** + * @return non-negative-int + */ public function count(): int { return \count($this->data); From 56c6996c0b8854c269fc88c811debbdf57774251 Mon Sep 17 00:00:00 2001 From: Strift Date: Wed, 3 Sep 2025 16:25:48 +0800 Subject: [PATCH 22/26] Add import and use only class name --- src/Contracts/Http.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Contracts/Http.php b/src/Contracts/Http.php index cd9d1b7b..9b76a506 100644 --- a/src/Contracts/Http.php +++ b/src/Contracts/Http.php @@ -7,6 +7,7 @@ use Meilisearch\Exceptions\ApiException; use Meilisearch\Exceptions\JsonDecodingException; use Meilisearch\Exceptions\JsonEncodingException; +use Psr\Http\Message\StreamInterface; interface Http { @@ -18,7 +19,6 @@ public function get(string $path, array $query = []); /** * @param non-empty-string|null $contentType - * * @throws ApiException * @throws JsonEncodingException * @throws JsonDecodingException @@ -51,5 +51,5 @@ public function delete(string $path, array $query = []); * @throws ApiException * @throws JsonEncodingException */ - public function postStream(string $path, $body = null, array $query = []): \Psr\Http\Message\StreamInterface; + public function postStream(string $path, $body = null, array $query = []): StreamInterface; } From 84b8ee02a8d36fe3f2691d9ebaaf32d2e3c0255b Mon Sep 17 00:00:00 2001 From: Strift Date: Wed, 3 Sep 2025 16:26:21 +0800 Subject: [PATCH 23/26] Lint --- src/Contracts/ChatWorkspacePromptsSettings.php | 7 ------- src/Contracts/ChatWorkspaceSettings.php | 3 --- src/Contracts/Http.php | 1 + 3 files changed, 1 insertion(+), 10 deletions(-) diff --git a/src/Contracts/ChatWorkspacePromptsSettings.php b/src/Contracts/ChatWorkspacePromptsSettings.php index bd30e92b..a2c49683 100644 --- a/src/Contracts/ChatWorkspacePromptsSettings.php +++ b/src/Contracts/ChatWorkspacePromptsSettings.php @@ -12,16 +12,9 @@ * searchIndexUidParam: non-empty-string * } */ - class ChatWorkspacePromptsSettings extends Data { - /** - * @var string - */ public string $system; - /** - * @var string - */ public string $searchDescription; /** * @var non-empty-string diff --git a/src/Contracts/ChatWorkspaceSettings.php b/src/Contracts/ChatWorkspaceSettings.php index 80abe77d..5589ad2a 100644 --- a/src/Contracts/ChatWorkspaceSettings.php +++ b/src/Contracts/ChatWorkspaceSettings.php @@ -33,9 +33,6 @@ class ChatWorkspaceSettings extends Data * @var non-empty-string|null */ private ?string $baseUrl; - /** - * @var string|null - */ private ?string $apiKey; private ChatWorkspacePromptsSettings $prompts; diff --git a/src/Contracts/Http.php b/src/Contracts/Http.php index 9b76a506..2710fc29 100644 --- a/src/Contracts/Http.php +++ b/src/Contracts/Http.php @@ -19,6 +19,7 @@ public function get(string $path, array $query = []); /** * @param non-empty-string|null $contentType + * * @throws ApiException * @throws JsonEncodingException * @throws JsonDecodingException From f67b66b9860eea052b9f20bbbba3e578558731e8 Mon Sep 17 00:00:00 2001 From: Strift Date: Wed, 3 Sep 2025 18:53:53 +0800 Subject: [PATCH 24/26] Import streaminterface and use class name --- src/Endpoints/Delegates/HandlesChatWorkspaceSettings.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Endpoints/Delegates/HandlesChatWorkspaceSettings.php b/src/Endpoints/Delegates/HandlesChatWorkspaceSettings.php index 9a7d3a64..960d0f4d 100644 --- a/src/Endpoints/Delegates/HandlesChatWorkspaceSettings.php +++ b/src/Endpoints/Delegates/HandlesChatWorkspaceSettings.php @@ -5,6 +5,7 @@ namespace Meilisearch\Endpoints\Delegates; use Meilisearch\Contracts\ChatWorkspaceSettings; +use Psr\Http\Message\StreamInterface; trait HandlesChatWorkspaceSettings { @@ -70,7 +71,7 @@ public function resetSettings(): ChatWorkspaceSettings * stream: bool * } $options The request body for the chat completion */ - public function streamCompletion(array $options): \Psr\Http\Message\StreamInterface + public function streamCompletion(array $options): StreamInterface { if (null === $this->workspaceName) { throw new \InvalidArgumentException('Workspace name is required for chat completion'); From 4990e741e028464d6c2d000194cedd27032fa26e Mon Sep 17 00:00:00 2001 From: Strift Date: Wed, 3 Sep 2025 19:03:23 +0800 Subject: [PATCH 25/26] Make class attributes private --- src/Contracts/ChatWorkspacePromptsSettings.php | 8 ++++---- src/Endpoints/Delegates/HandlesChatWorkspaces.php | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Contracts/ChatWorkspacePromptsSettings.php b/src/Contracts/ChatWorkspacePromptsSettings.php index a2c49683..cd016a14 100644 --- a/src/Contracts/ChatWorkspacePromptsSettings.php +++ b/src/Contracts/ChatWorkspacePromptsSettings.php @@ -14,16 +14,16 @@ */ class ChatWorkspacePromptsSettings extends Data { - public string $system; - public string $searchDescription; + private string $system; + private string $searchDescription; /** * @var non-empty-string */ - public string $searchQParam; + private string $searchQParam; /** * @var non-empty-string */ - public string $searchIndexUidParam; + private string $searchIndexUidParam; /** * @param ChatWorkspacePromptsArray $params diff --git a/src/Endpoints/Delegates/HandlesChatWorkspaces.php b/src/Endpoints/Delegates/HandlesChatWorkspaces.php index 343a6a51..3746f378 100644 --- a/src/Endpoints/Delegates/HandlesChatWorkspaces.php +++ b/src/Endpoints/Delegates/HandlesChatWorkspaces.php @@ -9,7 +9,7 @@ trait HandlesChatWorkspaces { - public ChatWorkspaces $chats; + private ChatWorkspaces $chats; /** * List all chat workspaces. From 2b0b414925462518051c18c74adde84895a34b49 Mon Sep 17 00:00:00 2001 From: Strift Date: Wed, 3 Sep 2025 19:12:18 +0800 Subject: [PATCH 26/26] Make chats property private --- tests/Endpoints/ChatWorkspaceTest.php | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/tests/Endpoints/ChatWorkspaceTest.php b/tests/Endpoints/ChatWorkspaceTest.php index 4950a5a8..1a8db4fa 100644 --- a/tests/Endpoints/ChatWorkspaceTest.php +++ b/tests/Endpoints/ChatWorkspaceTest.php @@ -38,7 +38,7 @@ protected function setUp(): void public function testUpdateWorkspacesSettings(): void { - $response = $this->client->chats->workspace('myWorkspace')->updateSettings($this->workspaceSettings); + $response = $this->client->chatWorkspace('myWorkspace')->updateSettings($this->workspaceSettings); self::assertSame($this->workspaceSettings['source'], $response->getSource()); self::assertSame($this->workspaceSettings['orgId'], $response->getOrgId()); self::assertSame($this->workspaceSettings['projectId'], $response->getProjectId()); @@ -54,14 +54,14 @@ public function testExceptionWhenWorkspaceDoesNotExist(): void { self::expectException(ApiException::class); self::expectExceptionCode(404); - $this->client->chats->workspace('non-existent-workspace')->getSettings(); + $this->client->chatWorkspace('non-existent-workspace')->getSettings(); } public function testGetWorkspaceSettings(): void { - $this->client->chats->workspace('myWorkspace')->updateSettings($this->workspaceSettings); + $this->client->chatWorkspace('myWorkspace')->updateSettings($this->workspaceSettings); - $response = $this->client->chats->workspace('myWorkspace')->getSettings(); + $response = $this->client->chatWorkspace('myWorkspace')->getSettings(); self::assertSame($this->workspaceSettings['source'], $response->getSource()); self::assertSame($this->workspaceSettings['orgId'], $response->getOrgId()); self::assertSame($this->workspaceSettings['projectId'], $response->getProjectId()); @@ -79,8 +79,8 @@ public function testGetWorkspaceSettings(): void public function testListWorkspaces(): void { - $this->client->chats->workspace('myWorkspace')->updateSettings($this->workspaceSettings); - $response = $this->client->chats->listWorkspaces(); + $this->client->chatWorkspace('myWorkspace')->updateSettings($this->workspaceSettings); + $response = $this->client->getChatWorkspaces(); self::assertSame([ ['uid' => 'myWorkspace'], ], $response->getResults()); @@ -88,9 +88,9 @@ public function testListWorkspaces(): void public function testResetWorkspaceSettings(): void { - $this->client->chats->workspace('myWorkspace')->updateSettings($this->workspaceSettings); - $this->client->chats->workspace('myWorkspace')->resetSettings(); - $settingsResponse = $this->client->chats->workspace('myWorkspace')->getSettings(); + $this->client->chatWorkspace('myWorkspace')->updateSettings($this->workspaceSettings); + $this->client->chatWorkspace('myWorkspace')->resetSettings(); + $settingsResponse = $this->client->chatWorkspace('myWorkspace')->getSettings(); self::assertSame('openAi', $settingsResponse->getSource()); // Source is reset to openAi for some reason self::assertNull($settingsResponse->getOrgId()); self::assertNull($settingsResponse->getProjectId()); @@ -105,7 +105,7 @@ public function testResetWorkspaceSettings(): void self::assertNotEmpty($settingsResponse->getPrompts()->getSearchIndexUidParam()); // Workspace still appears when listing workspaces - $listResponse = $this->client->chats->listWorkspaces(); + $listResponse = $this->client->getChatWorkspaces(); self::assertSame([ ['uid' => 'myWorkspace'], ], $listResponse->getResults()); @@ -113,9 +113,9 @@ public function testResetWorkspaceSettings(): void public function testCompletionStreaming(): void { - $this->client->chats->workspace('myWorkspace')->updateSettings($this->workspaceSettings); + $this->client->chatWorkspace('myWorkspace')->updateSettings($this->workspaceSettings); - $stream = $this->client->chats->workspace('myWorkspace')->streamCompletion([ + $stream = $this->client->chatWorkspace('myWorkspace')->streamCompletion([ 'model' => 'gpt-4o-mini', 'messages' => [ [