diff --git a/README.md b/README.md index 0f13e67..8b8c40a 100644 --- a/README.md +++ b/README.md @@ -432,6 +432,251 @@ echo($d->choices[0]->message->content); > ### Related: [ChatGPT Clone Project](#chatgpt-clone-project) +## Responses + +The Responses API (`POST /v1/responses`) is OpenAI's recommended replacement for Chat Completions. It accepts a flexible +`input` (string or message array) plus an optional `instructions` system prompt, supports stateful multi-turn via +`previous_response_id`, exposes built-in tools (`web_search`, `file_search`, `code_interpreter`, etc.), and returns a +typed `output[]` array instead of `choices[]`. + +### Create response + +```php +$result = $open_ai->response([ + 'model' => 'gpt-4o-mini', + 'input' => 'Tell me a three sentence bedtime story about a unicorn.', +]); +``` + +With an `instructions` system prompt and structured input: + +```php +$result = $open_ai->response([ + 'model' => 'gpt-4o-mini', + 'instructions' => 'You are a terse assistant.', + 'input' => [ + ['role' => 'user', 'content' => 'What is the capital of France?'], + ], +]); +``` + +### Multi-turn with `previous_response_id` + +Instead of resending the full message history, pass the previous response's `id`: + +```php +$first = $open_ai->response([ + 'model' => 'gpt-4o-mini', + 'input' => 'My name is Joacir.', + 'store' => true, +]); +$firstId = json_decode($first, true)['id']; + +$second = $open_ai->response([ + 'model' => 'gpt-4o-mini', + 'previous_response_id' => $firstId, + 'input' => 'What is my name?', +]); +``` + +### Stream response + +Streaming uses Server-Sent Events. Pass `stream => true` plus a write callback, just like `chat()` / `completion()`: + +```php +$opts = [ + 'model' => 'gpt-4o-mini', + 'input' => 'Tell me a short story.', + 'stream' => true, +]; + +header('Content-type: text/event-stream'); +header('Cache-Control: no-cache'); + +$open_ai->response($opts, function ($curl_info, $data) { + echo $data; + ob_flush(); + flush(); + + return strlen($data); +}); +``` + +### Retrieve response + +```php +$result = $open_ai->retrieveResponse('resp_abc123'); +``` + +### Delete response + +```php +$result = $open_ai->deleteResponse('resp_abc123'); +``` + +### Cancel response + +Cancels a response created with `background => true` that has not finished yet. + +```php +$result = $open_ai->cancelResponse('resp_abc123'); +``` + +### List response input items + +Returns the input items for a stored response. Accepts paging query params (`limit`, `order`, `after`, `before`, +`include`). + +```php +$result = $open_ai->listResponseInputItems('resp_abc123', ['limit' => 20]); +``` + +### Migrating from `chat()` to `response()` + +| Chat Completions | Responses | +|----------------------------------------|---------------------------------------------------| +| `messages` array with `system`/`user` | `input` (string or array) + `instructions` | +| `response_format` | `text.format` | +| `choices[0].message.content` | `output[].content[].text` (or SDK `output_text`) | +| Manual conversation history | `previous_response_id` for stateful chaining | +| Custom tool plumbing | Built-in tools via `tools` (web_search, etc.) | + +## Conversations + +The Conversations API replaces Assistants Threads for stateful multi-turn. A conversation stores `items` (messages, tool +calls, tool outputs, reasoning items). Reference a conversation from `response()` via `$opts['conversation']` and the +model has the full history without you resending it. + +### Create conversation + +```php +$conv = $open_ai->createConversation([ + 'metadata' => ['user_id' => '42'], + 'items' => [ + ['type' => 'message', 'role' => 'user', 'content' => 'Remember my name is Joacir.'], + ], +]); +$convId = json_decode($conv, true)['id']; +``` + +### Use a conversation with `response()` + +```php +$result = $open_ai->response([ + 'model' => 'gpt-4o-mini', + 'conversation' => $convId, + 'input' => 'What is my name?', +]); +``` + +### Retrieve, modify, delete conversation + +```php +$open_ai->retrieveConversation($convId); +$open_ai->modifyConversation($convId, ['metadata' => ['status' => 'closed']]); +$open_ai->deleteConversation($convId); +``` + +### Items (messages, tool calls, tool outputs) + +```php +$open_ai->createConversationItems($convId, [ + 'items' => [ + ['type' => 'message', 'role' => 'user', 'content' => 'New question.'], + ], +]); + +$open_ai->listConversationItems($convId, ['limit' => 20, 'order' => 'desc']); +$open_ai->retrieveConversationItem($convId, 'item_abc123'); +$open_ai->deleteConversationItem($convId, 'item_abc123'); +``` + +## Vector Stores + +Vector stores power the `file_search` tool in Responses. Upload files via `uploadFile()` first, then attach them to a +vector store. + +### Create, list, retrieve, modify, delete + +```php +$vs = $open_ai->createVectorStore([ + 'name' => 'support-docs', + 'expires_after' => ['anchor' => 'last_active_at', 'days' => 7], +]); +$vsId = json_decode($vs, true)['id']; + +$open_ai->listVectorStores(['limit' => 20]); +$open_ai->retrieveVectorStore($vsId); +$open_ai->modifyVectorStore($vsId, ['name' => 'kb-v2']); +$open_ai->deleteVectorStore($vsId); +``` + +### Search a vector store directly + +```php +$open_ai->searchVectorStore($vsId, [ + 'query' => 'refund policy', + 'max_num_results' => 5, +]); +``` + +### Files in a vector store + +```php +$open_ai->createVectorStoreFile($vsId, ['file_id' => 'file-abc']); +$open_ai->listVectorStoreFiles($vsId, ['limit' => 50]); +$open_ai->retrieveVectorStoreFile($vsId, 'file-abc'); +$open_ai->updateVectorStoreFileAttributes($vsId, 'file-abc', [ + 'attributes' => ['language' => 'pt-BR'], +]); +$open_ai->deleteVectorStoreFile($vsId, 'file-abc'); +$open_ai->retrieveVectorStoreFileContent($vsId, 'file-abc'); +``` + +### File batches + +Bulk attach files in one async job: + +```php +$batch = $open_ai->createVectorStoreFileBatch($vsId, [ + 'file_ids' => ['file-a', 'file-b', 'file-c'], +]); +$batchId = json_decode($batch, true)['id']; + +$open_ai->retrieveVectorStoreFileBatch($vsId, $batchId); +$open_ai->listVectorStoreFileBatchFiles($vsId, $batchId, ['limit' => 100]); +$open_ai->cancelVectorStoreFileBatch($vsId, $batchId); +``` + +### Use vector store with `response()` and `file_search` + +```php +$open_ai->response([ + 'model' => 'gpt-4o-mini', + 'input' => 'What is the refund policy?', + 'tools' => [ + ['type' => 'file_search', 'vector_store_ids' => [$vsId]], + ], +]); +``` + +## Prompts + +Retrieve a Prompt template created in the OpenAI dashboard: + +```php +$open_ai->retrievePrompt('pmpt_abc123'); +``` + +Then reference it from `response()`: + +```php +$open_ai->response([ + 'model' => 'gpt-4o-mini', + 'prompt' => ['id' => 'pmpt_abc123', 'variables' => ['city' => 'São Paulo']], +]); +``` + ## Completions Given a prompt, the model will return one or more predicted completions, and can also return the probabilities of @@ -931,6 +1176,24 @@ $result = $open_ai->retrieveModel("text-ada-001"); ```php echo $search; ``` +## ⚠️ Assistants Beta API — Deprecation Notice + +The Assistants beta family below (**Assistants**, **Threads**, **Messages**, **Runs**) is being shut down by OpenAI +on **August 26, 2026**. Migrate to the new stack: + +| Legacy (Assistants beta) | New (GA) | +|---------------------------------|-----------------------------------------------------| +| `createAssistant()` | Build prompts in the dashboard + pass via `prompt` | +| `createThread()` | `createConversation()` | +| `createThreadMessage()` | `createConversationItems()` | +| `listThreadMessages()` | `listConversationItems()` | +| `createRun()` | `response()` with `'conversation' => $convId` | +| `retrieveRunStep()` | Inspect `response.output[]` (steps are items) | +| `submitToolOutputs()` | App code manages tool loops between `response()` calls | +| Assistant Files / Message Files | `createVectorStoreFile()` + `file_search` tool | + +All legacy methods are annotated `@deprecated` and will be removed in a future major release. + ## Assistants (beta) Allows you to build AI assistants within your own applications. diff --git a/src/OpenAi.php b/src/OpenAi.php index 87bfcbe..0f34e10 100644 --- a/src/OpenAi.php +++ b/src/OpenAi.php @@ -213,8 +213,8 @@ public function moderation($opts) */ public function chat($opts, $stream = null) { - if ($stream != null && array_key_exists('stream', $opts)) { - if (! $opts['stream']) { + if (array_key_exists('stream', $opts) && $opts['stream']) { + if ($stream == null) { throw new Exception( 'Please provide a stream function. Check https://github.com/orhanerday/open-ai#stream-example for an example.' ); @@ -230,6 +230,416 @@ public function chat($opts, $stream = null) return $this->sendRequest($url, 'POST', $opts); } + /** + * @param $opts + * @param null $stream + * @return bool|string + * @throws Exception + */ + public function response($opts, $stream = null) + { + if (array_key_exists('stream', $opts) && $opts['stream']) { + if ($stream == null) { + throw new Exception( + 'Please provide a stream function. Check https://github.com/orhanerday/open-ai#stream-example for an example.' + ); + } + + $this->stream_method = $stream; + } + + $url = Url::responsesUrl(); + $this->baseUrl($url); + + return $this->sendRequest($url, 'POST', $opts); + } + + /** + * @param $response_id + * @return bool|string + */ + public function retrieveResponse($response_id) + { + $response_id = "/$response_id"; + $url = Url::responsesUrl().$response_id; + $this->baseUrl($url); + + return $this->sendRequest($url, 'GET'); + } + + /** + * @param $response_id + * @return bool|string + */ + public function deleteResponse($response_id) + { + $response_id = "/$response_id"; + $url = Url::responsesUrl().$response_id; + $this->baseUrl($url); + + return $this->sendRequest($url, 'DELETE'); + } + + /** + * @param $response_id + * @return bool|string + */ + public function cancelResponse($response_id) + { + $response_id = "/$response_id/cancel"; + $url = Url::responsesUrl().$response_id; + $this->baseUrl($url); + + return $this->sendRequest($url, 'POST'); + } + + /** + * @param $response_id + * @param array $query + * @return bool|string + */ + public function listResponseInputItems($response_id, $query = []) + { + $url = Url::responsesUrl()."/$response_id/input_items"; + if (! empty($query)) { + $url .= '?'.http_build_query($query); + } + $this->baseUrl($url); + + return $this->sendRequest($url, 'GET'); + } + + /** + * @param array $data + * @return bool|string + */ + public function createConversation($data = []) + { + $url = Url::conversationsUrl(); + $this->baseUrl($url); + + return $this->sendRequest($url, 'POST', $data); + } + + /** + * @param $conversation_id + * @return bool|string + */ + public function retrieveConversation($conversation_id) + { + $url = Url::conversationsUrl()."/$conversation_id"; + $this->baseUrl($url); + + return $this->sendRequest($url, 'GET'); + } + + /** + * @param $conversation_id + * @param array $data + * @return bool|string + */ + public function modifyConversation($conversation_id, $data) + { + $url = Url::conversationsUrl()."/$conversation_id"; + $this->baseUrl($url); + + return $this->sendRequest($url, 'POST', $data); + } + + /** + * @param $conversation_id + * @return bool|string + */ + public function deleteConversation($conversation_id) + { + $url = Url::conversationsUrl()."/$conversation_id"; + $this->baseUrl($url); + + return $this->sendRequest($url, 'DELETE'); + } + + /** + * @param $conversation_id + * @param array $data + * @return bool|string + */ + public function createConversationItems($conversation_id, $data) + { + $url = Url::conversationsUrl()."/$conversation_id/items"; + $this->baseUrl($url); + + return $this->sendRequest($url, 'POST', $data); + } + + /** + * @param $conversation_id + * @param array $query + * @return bool|string + */ + public function listConversationItems($conversation_id, $query = []) + { + $url = Url::conversationsUrl()."/$conversation_id/items"; + if (! empty($query)) { + $url .= '?'.http_build_query($query); + } + $this->baseUrl($url); + + return $this->sendRequest($url, 'GET'); + } + + /** + * @param $conversation_id + * @param $item_id + * @return bool|string + */ + public function retrieveConversationItem($conversation_id, $item_id) + { + $url = Url::conversationsUrl()."/$conversation_id/items/$item_id"; + $this->baseUrl($url); + + return $this->sendRequest($url, 'GET'); + } + + /** + * @param $conversation_id + * @param $item_id + * @return bool|string + */ + public function deleteConversationItem($conversation_id, $item_id) + { + $url = Url::conversationsUrl()."/$conversation_id/items/$item_id"; + $this->baseUrl($url); + + return $this->sendRequest($url, 'DELETE'); + } + + /** + * @param array $data + * @return bool|string + */ + public function createVectorStore($data = []) + { + $url = Url::vectorStoresUrl(); + $this->baseUrl($url); + + return $this->sendRequest($url, 'POST', $data); + } + + /** + * @param array $query + * @return bool|string + */ + public function listVectorStores($query = []) + { + $url = Url::vectorStoresUrl(); + if (! empty($query)) { + $url .= '?'.http_build_query($query); + } + $this->baseUrl($url); + + return $this->sendRequest($url, 'GET'); + } + + /** + * @param $vector_store_id + * @return bool|string + */ + public function retrieveVectorStore($vector_store_id) + { + $url = Url::vectorStoresUrl()."/$vector_store_id"; + $this->baseUrl($url); + + return $this->sendRequest($url, 'GET'); + } + + /** + * @param $vector_store_id + * @param array $data + * @return bool|string + */ + public function modifyVectorStore($vector_store_id, $data) + { + $url = Url::vectorStoresUrl()."/$vector_store_id"; + $this->baseUrl($url); + + return $this->sendRequest($url, 'POST', $data); + } + + /** + * @param $vector_store_id + * @return bool|string + */ + public function deleteVectorStore($vector_store_id) + { + $url = Url::vectorStoresUrl()."/$vector_store_id"; + $this->baseUrl($url); + + return $this->sendRequest($url, 'DELETE'); + } + + /** + * @param $vector_store_id + * @param array $data + * @return bool|string + */ + public function searchVectorStore($vector_store_id, $data) + { + $url = Url::vectorStoresUrl()."/$vector_store_id/search"; + $this->baseUrl($url); + + return $this->sendRequest($url, 'POST', $data); + } + + /** + * @param $vector_store_id + * @param array $data + * @return bool|string + */ + public function createVectorStoreFile($vector_store_id, $data) + { + $url = Url::vectorStoresUrl()."/$vector_store_id/files"; + $this->baseUrl($url); + + return $this->sendRequest($url, 'POST', $data); + } + + /** + * @param $vector_store_id + * @param array $query + * @return bool|string + */ + public function listVectorStoreFiles($vector_store_id, $query = []) + { + $url = Url::vectorStoresUrl()."/$vector_store_id/files"; + if (! empty($query)) { + $url .= '?'.http_build_query($query); + } + $this->baseUrl($url); + + return $this->sendRequest($url, 'GET'); + } + + /** + * @param $vector_store_id + * @param $file_id + * @return bool|string + */ + public function retrieveVectorStoreFile($vector_store_id, $file_id) + { + $url = Url::vectorStoresUrl()."/$vector_store_id/files/$file_id"; + $this->baseUrl($url); + + return $this->sendRequest($url, 'GET'); + } + + /** + * @param $vector_store_id + * @param $file_id + * @param array $data + * @return bool|string + */ + public function updateVectorStoreFileAttributes($vector_store_id, $file_id, $data) + { + $url = Url::vectorStoresUrl()."/$vector_store_id/files/$file_id"; + $this->baseUrl($url); + + return $this->sendRequest($url, 'POST', $data); + } + + /** + * @param $vector_store_id + * @param $file_id + * @return bool|string + */ + public function deleteVectorStoreFile($vector_store_id, $file_id) + { + $url = Url::vectorStoresUrl()."/$vector_store_id/files/$file_id"; + $this->baseUrl($url); + + return $this->sendRequest($url, 'DELETE'); + } + + /** + * @param $vector_store_id + * @param $file_id + * @return bool|string + */ + public function retrieveVectorStoreFileContent($vector_store_id, $file_id) + { + $url = Url::vectorStoresUrl()."/$vector_store_id/files/$file_id/content"; + $this->baseUrl($url); + + return $this->sendRequest($url, 'GET'); + } + + /** + * @param $vector_store_id + * @param array $data + * @return bool|string + */ + public function createVectorStoreFileBatch($vector_store_id, $data) + { + $url = Url::vectorStoresUrl()."/$vector_store_id/file_batches"; + $this->baseUrl($url); + + return $this->sendRequest($url, 'POST', $data); + } + + /** + * @param $vector_store_id + * @param $batch_id + * @return bool|string + */ + public function retrieveVectorStoreFileBatch($vector_store_id, $batch_id) + { + $url = Url::vectorStoresUrl()."/$vector_store_id/file_batches/$batch_id"; + $this->baseUrl($url); + + return $this->sendRequest($url, 'GET'); + } + + /** + * @param $vector_store_id + * @param $batch_id + * @return bool|string + */ + public function cancelVectorStoreFileBatch($vector_store_id, $batch_id) + { + $url = Url::vectorStoresUrl()."/$vector_store_id/file_batches/$batch_id/cancel"; + $this->baseUrl($url); + + return $this->sendRequest($url, 'POST'); + } + + /** + * @param $vector_store_id + * @param $batch_id + * @param array $query + * @return bool|string + */ + public function listVectorStoreFileBatchFiles($vector_store_id, $batch_id, $query = []) + { + $url = Url::vectorStoresUrl()."/$vector_store_id/file_batches/$batch_id/files"; + if (! empty($query)) { + $url .= '?'.http_build_query($query); + } + $this->baseUrl($url); + + return $this->sendRequest($url, 'GET'); + } + + /** + * @param $prompt_id + * @return bool|string + */ + public function retrievePrompt($prompt_id) + { + $url = Url::promptsUrl()."/$prompt_id"; + $this->baseUrl($url); + + return $this->sendRequest($url, 'GET'); + } + /** * @param $opts * @return bool|string @@ -432,6 +842,7 @@ public function embeddings($opts) /** * @param array $data * @return bool|string + * @deprecated The Assistants API is being shut down on August 26, 2026. Migrate to the Responses API (response()) and Conversations API. */ public function createAssistant($data) { @@ -446,6 +857,7 @@ public function createAssistant($data) /** * @param string $assistantId * @return bool|string + * @deprecated The Assistants API is being shut down on August 26, 2026. Migrate to the Responses API (response()) and Conversations API. */ public function retrieveAssistant($assistantId) { @@ -460,6 +872,7 @@ public function retrieveAssistant($assistantId) * @param string $assistantId * @param array $data * @return bool|string + * @deprecated The Assistants API is being shut down on August 26, 2026. Migrate to the Responses API (response()) and Conversations API. */ public function modifyAssistant($assistantId, $data) { @@ -473,6 +886,7 @@ public function modifyAssistant($assistantId, $data) /** * @param string $assistantId * @return bool|string + * @deprecated The Assistants API is being shut down on August 26, 2026. Migrate to the Responses API (response()) and Conversations API. */ public function deleteAssistant($assistantId) { @@ -486,6 +900,7 @@ public function deleteAssistant($assistantId) /** * @param array $query * @return bool|string + * @deprecated The Assistants API is being shut down on August 26, 2026. Migrate to the Responses API (response()) and Conversations API. */ public function listAssistants($query = []) { @@ -503,6 +918,7 @@ public function listAssistants($query = []) * @param string $assistantId * @param string $fileId * @return bool|string + * @deprecated The Assistants API is being shut down on August 26, 2026. Migrate to the Responses API (response()) and Conversations API. */ public function createAssistantFile($assistantId, $fileId) { @@ -517,6 +933,7 @@ public function createAssistantFile($assistantId, $fileId) * @param string $assistantId * @param string $fileId * @return bool|string + * @deprecated The Assistants API is being shut down on August 26, 2026. Migrate to the Responses API (response()) and Conversations API. */ public function retrieveAssistantFile($assistantId, $fileId) { @@ -531,6 +948,7 @@ public function retrieveAssistantFile($assistantId, $fileId) * @param string $assistantId * @param array $query * @return bool|string + * @deprecated The Assistants API is being shut down on August 26, 2026. Migrate to the Responses API (response()) and Conversations API. */ public function listAssistantFiles($assistantId, $query = []) { @@ -548,6 +966,7 @@ public function listAssistantFiles($assistantId, $query = []) * @param string $assistantId * @param string $fileId * @return bool|string + * @deprecated The Assistants API is being shut down on August 26, 2026. Migrate to the Responses API (response()) and Conversations API. */ public function deleteAssistantFile($assistantId, $fileId) { @@ -561,6 +980,7 @@ public function deleteAssistantFile($assistantId, $fileId) /** * @param array $data * @return bool|string + * @deprecated The Assistants API is being shut down on August 26, 2026. Migrate to the Conversations API (createConversation()). */ public function createThread($data = []) { @@ -574,6 +994,7 @@ public function createThread($data = []) /** * @param string $threadId * @return bool|string + * @deprecated The Assistants API is being shut down on August 26, 2026. Migrate to the Conversations API (retrieveConversation()). */ public function retrieveThread($threadId) { @@ -588,6 +1009,7 @@ public function retrieveThread($threadId) * @param string $threadId * @param array $data * @return bool|string + * @deprecated The Assistants API is being shut down on August 26, 2026. Migrate to the Conversations API (modifyConversation()). */ public function modifyThread($threadId, $data) { @@ -601,6 +1023,7 @@ public function modifyThread($threadId, $data) /** * @param string $threadId * @return bool|string + * @deprecated The Assistants API is being shut down on August 26, 2026. Migrate to the Conversations API (deleteConversation()). */ public function deleteThread($threadId) { @@ -615,6 +1038,7 @@ public function deleteThread($threadId) * @param string $threadId * @param array $data * @return bool|string + * @deprecated The Assistants API is being shut down on August 26, 2026. Migrate to the Conversations API (createConversationItems()). */ public function createThreadMessage($threadId, $data) { @@ -629,6 +1053,7 @@ public function createThreadMessage($threadId, $data) * @param string $threadId * @param string $messageId * @return bool|string + * @deprecated The Assistants API is being shut down on August 26, 2026. Migrate to the Conversations API (retrieveConversationItem()). */ public function retrieveThreadMessage($threadId, $messageId) { @@ -644,6 +1069,7 @@ public function retrieveThreadMessage($threadId, $messageId) * @param string $messageId * @param array $data * @return bool|string + * @deprecated The Assistants API is being shut down on August 26, 2026. Migrate to the Conversations API. */ public function modifyThreadMessage($threadId, $messageId, $data) { @@ -658,6 +1084,7 @@ public function modifyThreadMessage($threadId, $messageId, $data) * @param string $threadId * @param array $query * @return bool|string + * @deprecated The Assistants API is being shut down on August 26, 2026. Migrate to the Conversations API (listConversationItems()). */ public function listThreadMessages($threadId, $query = []) { @@ -676,6 +1103,7 @@ public function listThreadMessages($threadId, $query = []) * @param string $messageId * @param string $fileId * @return bool|string + * @deprecated The Assistants API is being shut down on August 26, 2026. Migrate to the Responses/Conversations API. */ public function retrieveMessageFile($threadId, $messageId, $fileId) { @@ -691,6 +1119,7 @@ public function retrieveMessageFile($threadId, $messageId, $fileId) * @param string $messageId * @param array $query * @return bool|string + * @deprecated The Assistants API is being shut down on August 26, 2026. Migrate to the Responses/Conversations API. */ public function listMessageFiles($threadId, $messageId, $query = []) { @@ -708,6 +1137,7 @@ public function listMessageFiles($threadId, $messageId, $query = []) * @param string $threadId * @param array $data * @return bool|string + * @deprecated The Assistants API is being shut down on August 26, 2026. Migrate to the Responses API (response() with conversation reference). */ public function createRun($threadId, $data, $stream = null) { @@ -732,6 +1162,7 @@ public function createRun($threadId, $data, $stream = null) * @param string $threadId * @param string $runId * @return bool|string + * @deprecated The Assistants API is being shut down on August 26, 2026. Migrate to the Responses API (retrieveResponse()). */ public function retrieveRun($threadId, $runId) { @@ -747,6 +1178,7 @@ public function retrieveRun($threadId, $runId) * @param string $runId * @param array $data * @return bool|string + * @deprecated The Assistants API is being shut down on August 26, 2026. Migrate to the Responses API. */ public function modifyRun($threadId, $runId, $data) { @@ -761,6 +1193,7 @@ public function modifyRun($threadId, $runId, $data) * @param string $threadId * @param array $query * @return bool|string + * @deprecated The Assistants API is being shut down on August 26, 2026. Migrate to the Responses API. */ public function listRuns($threadId, $query = []) { @@ -779,6 +1212,7 @@ public function listRuns($threadId, $query = []) * @param string $runId * @param array $outputs * @return bool|string + * @deprecated The Assistants API is being shut down on August 26, 2026. In Responses API, tool call loops are managed by the application code. */ public function submitToolOutputs($threadId, $runId, $outputs, $stream = null) { @@ -803,6 +1237,7 @@ public function submitToolOutputs($threadId, $runId, $outputs, $stream = null) * @param string $threadId * @param string $runId * @return bool|string + * @deprecated The Assistants API is being shut down on August 26, 2026. Migrate to the Responses API (cancelResponse()). */ public function cancelRun($threadId, $runId) { @@ -816,6 +1251,7 @@ public function cancelRun($threadId, $runId) /** * @param array $data * @return bool|string + * @deprecated The Assistants API is being shut down on August 26, 2026. Migrate to the Responses API (response()). */ public function createThreadAndRun($data) { @@ -831,6 +1267,7 @@ public function createThreadAndRun($data) * @param string $runId * @param string $stepId * @return bool|string + * @deprecated The Assistants API is being shut down on August 26, 2026. In Responses API, run steps are represented as items in response.output. */ public function retrieveRunStep($threadId, $runId, $stepId) { @@ -846,6 +1283,7 @@ public function retrieveRunStep($threadId, $runId, $stepId) * @param string $runId * @param array $query * @return bool|string + * @deprecated The Assistants API is being shut down on August 26, 2026. In Responses API, run steps are represented as items in response.output. */ public function listRunSteps($threadId, $runId, $query = []) { diff --git a/src/Url.php b/src/Url.php index f3c90ef..7fe3dce 100644 --- a/src/Url.php +++ b/src/Url.php @@ -161,6 +161,42 @@ public static function chatUrl(): string return self::OPEN_AI_URL . "/chat/completions"; } + /** + * @param + * @return string + */ + public static function responsesUrl(): string + { + return self::OPEN_AI_URL . "/responses"; + } + + /** + * @param + * @return string + */ + public static function conversationsUrl(): string + { + return self::OPEN_AI_URL . "/conversations"; + } + + /** + * @param + * @return string + */ + public static function vectorStoresUrl(): string + { + return self::OPEN_AI_URL . "/vector_stores"; + } + + /** + * @param + * @return string + */ + public static function promptsUrl(): string + { + return self::OPEN_AI_URL . "/prompts"; + } + /** * @param * @return string diff --git a/tests/OpenAiTest.php b/tests/OpenAiTest.php index 4a27b97..a0837d2 100644 --- a/tests/OpenAiTest.php +++ b/tests/OpenAiTest.php @@ -252,7 +252,18 @@ "presence_penalty" => 0, ]); - $this->assertStringContainsString('text', $result); + $this->assertStringContainsString('"object": "chat.completion"', $result); + $this->assertStringContainsString('content', $result); +})->group('working'); + +it('should throw error when stream true without callback in chat', function () use ($open_ai) { + expect(fn () => $open_ai->chat([ + 'model' => 'gpt-3.5-turbo', + 'messages' => [ + ['role' => 'user', 'content' => 'Hello'], + ], + 'stream' => true, + ]))->toThrow(Exception::class, 'Please provide a stream function. Check https://github.com/orhanerday/open-ai#stream-example for an example.'); })->group('working'); it('should handle create assistant', function () use ($open_ai) { @@ -603,3 +614,129 @@ $this->assertStringContainsString('"object": "list"', $steps); $this->assertStringContainsString('data', $steps); })->group('working'); + +it('should handle simple response', function () use ($open_ai) { + $result = $open_ai->response([ + 'model' => 'gpt-4o-mini', + 'input' => 'Say hello in one word.', + ]); + + $this->assertStringContainsString('"object": "response"', $result); + $this->assertStringContainsString('output', $result); +})->group('working'); + +it('should throw error when stream true without callback in response', function () use ($open_ai) { + expect(fn () => $open_ai->response([ + 'model' => 'gpt-4o-mini', + 'input' => 'Hello', + 'stream' => true, + ]))->toThrow(Exception::class, 'Please provide a stream function. Check https://github.com/orhanerday/open-ai#stream-example for an example.'); +})->group('working'); + +it('should handle response with instructions', function () use ($open_ai) { + $result = $open_ai->response([ + 'model' => 'gpt-4o-mini', + 'instructions' => 'You are a terse assistant. Reply with a single word.', + 'input' => 'Greet me.', + ]); + + $this->assertStringContainsString('"object": "response"', $result); + $this->assertStringContainsString('output', $result); +})->group('working'); + +it('should retrieve a response', function () use ($open_ai) { + $created = $open_ai->response([ + 'model' => 'gpt-4o-mini', + 'input' => 'Hello.', + 'store' => true, + ]); + $id = json_decode($created, true)['id']; + + $result = $open_ai->retrieveResponse($id); + + $this->assertStringContainsString('"id": "'.$id.'"', $result); + $this->assertStringContainsString('"object": "response"', $result); +})->group('working'); + +it('should list response input items', function () use ($open_ai) { + $created = $open_ai->response([ + 'model' => 'gpt-4o-mini', + 'input' => 'Hello.', + 'store' => true, + ]); + $id = json_decode($created, true)['id']; + + $result = $open_ai->listResponseInputItems($id, ['limit' => 10]); + + $this->assertStringContainsString('"object": "list"', $result); + $this->assertStringContainsString('data', $result); +})->group('working'); + +it('should delete a response', function () use ($open_ai) { + $created = $open_ai->response([ + 'model' => 'gpt-4o-mini', + 'input' => 'Hello.', + 'store' => true, + ]); + $id = json_decode($created, true)['id']; + + $result = $open_ai->deleteResponse($id); + + $this->assertStringContainsString('deleted', $result); +})->group('working'); + +it('should handle create conversation', function () use ($open_ai) { + $result = $open_ai->createConversation([ + 'metadata' => ['topic' => 'demo'], + ]); + + $this->assertStringContainsString('"object": "conversation"', $result); + $this->assertStringContainsString('id', $result); +})->group('working'); + +it('should handle retrieve and delete conversation', function () use ($open_ai) { + $created = $open_ai->createConversation(); + $id = json_decode($created, true)['id']; + + $retrieved = $open_ai->retrieveConversation($id); + $this->assertStringContainsString('"object": "conversation"', $retrieved); + + $deleted = $open_ai->deleteConversation($id); + $this->assertStringContainsString('deleted', $deleted); +})->group('working'); + +it('should handle create and list conversation items', function () use ($open_ai) { + $created = $open_ai->createConversation(); + $id = json_decode($created, true)['id']; + + $items = $open_ai->createConversationItems($id, [ + 'items' => [ + ['type' => 'message', 'role' => 'user', 'content' => 'Hello.'], + ], + ]); + $this->assertStringContainsString('"object": "list"', $items); + + $list = $open_ai->listConversationItems($id, ['limit' => 10]); + $this->assertStringContainsString('"object": "list"', $list); + $this->assertStringContainsString('data', $list); + + $open_ai->deleteConversation($id); +})->group('working'); + +it('should handle create and delete vector store', function () use ($open_ai) { + $created = $open_ai->createVectorStore([ + 'name' => 'test-vs', + ]); + $this->assertStringContainsString('"object": "vector_store"', $created); + $id = json_decode($created, true)['id']; + + $deleted = $open_ai->deleteVectorStore($id); + $this->assertStringContainsString('deleted', $deleted); +})->group('working'); + +it('should handle list vector stores', function () use ($open_ai) { + $result = $open_ai->listVectorStores(['limit' => 5]); + + $this->assertStringContainsString('"object": "list"', $result); + $this->assertStringContainsString('data', $result); +})->group('working');