├── banner.png ├── composer.json ├── README.md └── src └── OpenAIAssistant.php /banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/erdum/php-open-ai-assistant-sdk/HEAD/banner.png -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "erdum/php-open-ai-assistant-sdk", 3 | "description": "A PHP class for seamless interaction with the OpenAI Assistant API, enabling developers build powerful AI assistants capable of performing a variety of tasks.", 4 | "type": "library", 5 | "keywords": [ 6 | "openai", 7 | "ai", 8 | "assistant", 9 | "api", 10 | "conversation", 11 | "messaging", 12 | "function calling", 13 | "sdk" 14 | ], 15 | "license": "MIT", 16 | "autoload": { 17 | "psr-4": { 18 | "Erdum\\": "src/" 19 | } 20 | }, 21 | "authors": [ 22 | { 23 | "name": "Erdum", 24 | "email": "erdumadnan@gmail.com" 25 | } 26 | ], 27 | "require": { 28 | "php": ">=5.3.3", 29 | "ext-curl": "*" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # OpenAI Assistant API PHP SDK 3 | 4 | A PHP class for seamless interaction with the OpenAI Assistant API, enabling developers to build powerful AI assistants capable of performing a variety of tasks.
5 | 6 | 7 | 8 | ## Table of Contents 9 | 10 | - [Installation](#installation) 11 | - [Usage](#usage) 12 | - [Reference](#reference) 13 | - [Feedback](#feedback) 14 | - [License](#license) 15 | 16 | ## Installation 17 | 18 | Install with composer 19 | 20 | ```bash 21 | composer require erdum/php-open-ai-assistant-sdk 22 | ``` 23 | 24 | Or install without composer on older PHP versions just clone the project in your working directory and require the OpenAIAssistant.php 25 | ```bash 26 | git clone https://github.com/erdum/php-open-ai-assistant-sdk.git 27 | ``` 28 | 29 | ```php 30 | create_thread($query); 61 | $_SESSION[$session_id] = $thread_id; 62 | } else { 63 | $openai->add_message($thread_id, 'Can you help me?'); 64 | } 65 | $openai->run_thread($thread_id); 66 | 67 | while ($openai->has_tool_calls) { 68 | $outputs = $openai->execute_tools( 69 | $thread_id, 70 | $openai->tool_call_id 71 | ); 72 | $openai->submit_tool_outputs( 73 | $thread_id, 74 | $openai->tool_call_id, 75 | $outputs 76 | ); 77 | } 78 | 79 | // Get the last recent message 80 | $message = $openai->list_thread_messages($thread_id); 81 | $message = $message[0]; 82 | $output = ''; 83 | 84 | if ($message['role'] == 'assistant') { 85 | foreach ($message['content'] as $msg) { 86 | $output .= "{$msg['text']['value']}\n"; 87 | } 88 | exit($output); 89 | } 90 | ``` 91 | ### How PHP functions will be called 92 | 93 | Whatever function names you provide in the tools array will be called in your PHP environment accordingly by the Assistant API, for example in the below code function "get_account_balance" should be present in your PHP environment in order to be executed. If you want to run methods of an object or static methods of a class then you can provide an additional argument to the "execute_tools" method 94 | ```php 95 | execute_tools( 99 | $thread_id, 100 | $openai->tool_call_id 101 | ); 102 | 103 | // This will call methods on an instance of a class 104 | $myObj = new MyAPI(); 105 | $outputs = $openai->execute_tools( 106 | $thread_id, 107 | $openai->tool_call_id, 108 | $myObj 109 | ); 110 | 111 | // This will call static methods of a class 112 | $outputs = $openai->execute_tools( 113 | $thread_id, 114 | $openai->tool_call_id, 115 | 'MyAPI' 116 | ); 117 | 118 | $openai->submit_tool_outputs( 119 | $thread_id, 120 | $openai->tool_call_id, 121 | $outputs 122 | ); 123 | ``` 124 | ### Create a new Assistant 125 | ```php 126 | create_assistant( 136 | 'Customer Support Assistant', 137 | 'You are a customer support assistant of an Telecom company. which is a wholesale DID numbers marketplace. You have to greet the customers and ask them how you can help them then understand their query and do the required operation. The functions and tools may require order-id in the arguments but do not ask the customers to provide their order-id because order-id will be included automatically to function calls.', 138 | array( 139 | array( 140 | 'type' => 'function', 141 | 'function' => array( 142 | 'name' => 'get_account_balance', 143 | 'description' => 'This function retrieves the account balance of the customer with the provided order-id.', 144 | 'parameters' => array( 145 | 'type' => 'object', 146 | 'properties' => array( 147 | 'order_id' => array( 148 | 'type' => 'string', 149 | 'description' => 'The order-id of the customer.' 150 | ) 151 | ), 152 | 'required' => array('order_id') 153 | ), 154 | ) 155 | ) 156 | ) 157 | ); 158 | ``` 159 | 160 | ## Reference 161 | 162 | #### Constructor 163 | 164 | | Parameter | Type | Description | 165 | | :-------------- | :------- | :------------------------------------------------ | 166 | | `$api_key` | `string` | **Required**. Your OpenAI API key. | 167 | | `$assistant_id` | `string` | Assistant ID (default = `null`). | 168 | | `$base_url` | `string` | OpenAI API base URL (default = 'https://api.openai.com/v1'). | 169 | | `$version_header` | `string` | OpenAI API version header (default = 'OpenAI-Beta: assistants=v1'). | 170 | | **Returns** | `void` | No return value. | 171 | 172 | #### `create_assistant` 173 | 174 | | Parameter | Type | Description | 175 | | :-------------- | :------- | :------------------------------------------------ | 176 | | `$name` | `string` | Name of the assistant. | 177 | | `$instructions` | `string` | Instructions for the assistant. | 178 | | `$tools` | `array` | Array of tools. | 179 | | **Returns** | `string` | ID of the created assistant. | 180 | 181 | #### `modify_assistant` 182 | 183 | | Parameter | Type | Description | 184 | | :-------------- | :------- | :------------------------------------------------ | 185 | | `$name` | `string` | Name of the assistant. | 186 | | `$instructions` | `string` | Instructions for the assistant. | 187 | | `$tools` | `array` | Array of tools. | 188 | | **Returns** | `string` | ID of the modified assistant. | 189 | 190 | #### `list_assistants` 191 | 192 | No parameters. 193 | 194 | | **Returns** | `array` | List of available assistants. | 195 | | :-------------- | :------- | :------------------------------------------------ | 196 | 197 | #### `create_thread` 198 | 199 | | Parameter | Type | Description | 200 | | :-------------- | :------- | :------------------------------------------------ | 201 | | `$content` | `string` | Content of the thread. | 202 | | `$role` | `string` | Role (default = 'user'). | 203 | | **Returns** | `string` | ID of the created thread. | 204 | 205 | #### `get_thread` 206 | 207 | | Parameter | Type | Description | 208 | | :-------------- | :------- | :------------------------------------------------ | 209 | | `$thread_id` | `string` | Thread ID. | 210 | | **Returns** | `array` | Details of the thread. | 211 | 212 | #### `add_message` 213 | 214 | | Parameter | Type | Description | 215 | | :-------------- | :------- | :------------------------------------------------ | 216 | | `$thread_id` | `string` | Thread ID. | 217 | | `$content` | `string` | Content of the message. | 218 | | `$role` | `string` | Role (default = 'user'). | 219 | | **Returns** | `string` | ID of the added message. | 220 | 221 | #### `get_message` 222 | 223 | | Parameter | Type | Description | 224 | | :-------------- | :------- | :------------------------------------------------ | 225 | | `$thread_id` | `string` | Thread ID. | 226 | | `$message_id` | `string` | Message ID. | 227 | | **Returns** | `array` | Details of the message. | 228 | 229 | #### `list_thread_messages` 230 | 231 | | Parameter | Type | Description | 232 | | :-------------- | :------- | :------------------------------------------------ | 233 | | `$thread_id` | `string` | Thread ID. | 234 | | **Returns** | `array` | List of messages in the thread. | 235 | 236 | #### `run_thread` 237 | 238 | | Parameter | Type | Description | 239 | | :-------------- | :------- | :------------------------------------------------ | 240 | | `$thread_id` | `string` | Thread ID. | 241 | | **Returns** | `string` | ID of the thread run. | 242 | 243 | #### `execute_tools` 244 | 245 | | Parameter | Type | Description | 246 | | :------------------ | :------- | :------------------------------------------------ | 247 | | `$thread_id` | `string` | Thread ID. | 248 | | `$execution_id` | `string` | Execution ID. | 249 | | `$optional_object` | `object` | Optional object. | 250 | | **Returns** | `array` | Outputs of executed tools. | 251 | 252 | #### `submit_tool_outputs` 253 | 254 | | Parameter | Type | Description | 255 | | :------------------ | :------- | :------------------------------------------------ | 256 | | `$thread_id` | `string` | Thread ID. | 257 | | `$execution_id` | `string` | Execution ID. | 258 | | `$outputs` | `array` | Tool outputs array. | 259 | | **Returns** | `string` | ID of the submitted tool outputs. | 260 | 261 | 262 | 263 | ## Feedback 264 | 265 | If you have any feedback, please reach out to us at erdumadnan@gmail.com 266 | 267 | ## License 268 | [![MIT License](https://img.shields.io/badge/License-MIT-green.svg)](https://choosealicense.com/licenses/mit/) 269 | -------------------------------------------------------------------------------- /src/OpenAIAssistant.php: -------------------------------------------------------------------------------- 1 | api_key = $api_key; 23 | $this->assistant_id = $assistant_id; 24 | $this->base_url = $base_url; 25 | $this->version_header = $version_header; 26 | } 27 | 28 | public function create_assistant($name, $instructions, $tools) 29 | { 30 | $response = $this->send_post_request('/assistants', array( 31 | 'name' => $name, 32 | 'instructions' => $instructions, 33 | 'model' => 'gpt-4-1106-preview', 34 | 'tools' => $tools 35 | )); 36 | 37 | if (empty($response['id'])) { 38 | throw new \Exception('Unable to create a assistant'); 39 | } 40 | $this->assistant_id = $response['id']; 41 | return $response['id']; 42 | } 43 | 44 | public function modify_assistant($name, $instructions, $tools) 45 | { 46 | if (!$this->assistant_id) { 47 | throw new \Exception( 48 | 'You need to provide a assistant_id or create an assistant.' 49 | ); 50 | } 51 | 52 | $response = $this->send_post_request("/assistants/{$this->assistant_id}", array( 53 | 'name' => $name, 54 | 'instructions' => $instructions, 55 | 'model' => 'gpt-4-1106-preview', 56 | 'tools' => $tools 57 | )); 58 | 59 | if (empty($response['id'])) { 60 | throw new \Exception('Unable to create a assistant'); 61 | } 62 | $this->assistant_id = $response['id']; 63 | return $response['id']; 64 | } 65 | 66 | public function list_assistants() 67 | { 68 | $response = $this->send_get_request('/assistants'); 69 | 70 | if (empty($response['data'])) { 71 | return array(); 72 | } 73 | return $response['data']; 74 | } 75 | 76 | public function create_thread($content, $role = 'user') 77 | { 78 | $response = $this->send_post_request('/threads', array( 79 | 'messages' => array( 80 | array( 81 | 'role' => $role, 82 | 'content' => $content 83 | ) 84 | ) 85 | )); 86 | 87 | if (empty($response['id'])) { 88 | throw new \Exception('Unable to create a thread'); 89 | } 90 | return $response['id']; 91 | } 92 | 93 | public function get_thread($thread_id) 94 | { 95 | $response = $this->send_get_request("/threads/{$thread_id}"); 96 | 97 | if (empty($response['id'])) { 98 | throw new \Exception('Unable to retrive a thread'); 99 | } 100 | return $response; 101 | } 102 | 103 | public function add_message($thread_id, $content, $role = 'user') 104 | { 105 | // Check if any latest run requires_action 106 | // Before adding a new message to the thread 107 | $runs = $this->list_runs($thread_id); 108 | 109 | if (count($runs) > 0) { 110 | $last_run = $runs[0]; 111 | 112 | if ($last_run['status'] == 'requires_action') { 113 | $this->has_tool_calls = true; 114 | $this->tool_call_id = $last_run['id']; 115 | return false; 116 | } else { 117 | $this->has_tool_calls = false; 118 | $this->tool_call_id = null; 119 | } 120 | } 121 | 122 | $response = $this->send_post_request( 123 | "/threads/{$thread_id}/messages", 124 | array( 125 | 'role' => $role, 126 | 'content' => $content 127 | ) 128 | ); 129 | 130 | if (empty($response['id'])) { 131 | throw new \Exception('Unable to create a message'); 132 | } 133 | return $response['id']; 134 | } 135 | 136 | public function get_message($thread_id, $message_id) 137 | { 138 | $response = $this->send_get_request("/threads/{$thread_id}/messages/{$message_id}"); 139 | 140 | if (empty($response['id'])) { 141 | throw new \Exception('Unable to retrive a message'); 142 | } 143 | return $response; 144 | } 145 | 146 | public function list_thread_messages($thread_id) 147 | { 148 | $response = $this->send_get_request("/threads/{$thread_id}/messages"); 149 | 150 | if (empty($response['data'])) { 151 | return array(); 152 | } 153 | return $response['data']; 154 | } 155 | 156 | public function run_thread($thread_id) 157 | { 158 | // Check if any latest run requires_action 159 | // Before creating and running a new thread 160 | $runs = $this->list_runs($thread_id); 161 | 162 | if (count($runs) > 0) { 163 | $last_run = $runs[0]; 164 | 165 | if ($last_run['status'] == 'requires_action') { 166 | $this->has_tool_calls = true; 167 | $this->tool_call_id = $last_run['id']; 168 | return false; 169 | } else { 170 | $this->has_tool_calls = false; 171 | $this->tool_call_id = null; 172 | } 173 | } 174 | 175 | $run_id = $this->create_run($thread_id, $this->assistant_id); 176 | 177 | do { 178 | sleep(5); 179 | $run = $this->get_run($thread_id, $run_id); 180 | } while (!( 181 | $run['status'] == 'completed' 182 | || $run['status'] == 'requires_action' 183 | )); 184 | 185 | if ($run['status'] == 'requires_action') { 186 | $this->has_tool_calls = true; 187 | $this->tool_call_id = $run['id']; 188 | return $run['id']; 189 | } else if ($run['status'] == 'completed') { 190 | return $run['id']; 191 | } 192 | return false; 193 | } 194 | 195 | public function execute_tools( 196 | $thread_id, 197 | $execution_id, 198 | $optional_object = null 199 | ) 200 | { 201 | $run = $this->get_run($thread_id, $execution_id); 202 | $calls = $run['required_action']['submit_tool_outputs']['tool_calls']; 203 | $outputs = array(); 204 | $log_entry = ''; 205 | 206 | foreach ($calls as $call) { 207 | $method_name = $call['function']['name']; 208 | $method_args = json_decode($call['function']['arguments'], true); 209 | $callable = $optional_object ? 210 | array($optional_object, $method_name) : $method_name; 211 | 212 | if (is_callable($callable)) { 213 | $data = call_user_func_array( 214 | $callable, 215 | $method_args 216 | ); 217 | array_push($outputs, array( 218 | 'tool_call_id' => $call['id'], 219 | 'output' => json_encode($data) 220 | )); 221 | $log_entry .= "$method_name -> " . print_r($method_args, true); 222 | } else { 223 | throw new \Exception("Failed to execute tool: The $method_name you provided is not callable"); 224 | } 225 | } 226 | $this->write_log($log_entry); 227 | $this->has_tool_calls = false; 228 | return $outputs; 229 | } 230 | 231 | public function submit_tool_outputs($thread_id, $execution_id, $outputs) 232 | { 233 | $response = $this->send_post_request( 234 | "/threads/{$thread_id}/runs/{$execution_id}/submit_tool_outputs", 235 | array('tool_outputs' => $outputs) 236 | ); 237 | $this->write_log("outputs -> " . print_r($outputs, true)); 238 | 239 | if (empty($response['id'])) { 240 | throw new \Exception('Unable to submit tool outputs'); 241 | } 242 | 243 | do { 244 | sleep(5); 245 | $run = $this->get_run($thread_id, $response['id']); 246 | } while (!( 247 | $run['status'] == 'completed' 248 | || $run['status'] == 'requires_action' 249 | )); 250 | 251 | if ($run['status'] == 'requires_action') { 252 | $this->has_tool_calls = true; 253 | $this->tool_call_id = $run['id']; 254 | return $run['id']; 255 | } else if ($run['status'] == 'completed') { 256 | return $run['id']; 257 | } 258 | return false; 259 | } 260 | 261 | private function execute_request($ch) 262 | { 263 | $response = curl_exec($ch); 264 | $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE); 265 | 266 | if ($errno = curl_errno($ch)) { 267 | throw new \Exception( 268 | 'CURL failed to call OpenAI API: ' . curl_error($ch), 269 | $errno 270 | ); 271 | } else if ($http_code != 200) { 272 | throw new \Exception( 273 | "OpenAI API Returned Unexpected HTTP code $http_code. " . print_r($response, true) 274 | ); 275 | } 276 | curl_close($ch); 277 | return json_decode($response, true); 278 | } 279 | 280 | private function send_get_request($route) 281 | { 282 | $ch = curl_init(); 283 | curl_setopt($ch, CURLOPT_URL, "{$this->base_url}{$route}"); 284 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); 285 | curl_setopt($ch, CURLOPT_HTTPHEADER, array( 286 | "Authorization: Bearer {$this->api_key}", 287 | 'Content-Type: application/json', 288 | 'Accept: application/json', 289 | $this->version_header 290 | )); 291 | return $this->execute_request($ch); 292 | } 293 | 294 | private function send_post_request($route, $payload = null) 295 | { 296 | $ch = curl_init(); 297 | 298 | if (!empty($payload)) curl_setopt( 299 | $ch, 300 | CURLOPT_POSTFIELDS, 301 | json_encode($payload) 302 | ); 303 | curl_setopt($ch, CURLOPT_URL, "{$this->base_url}{$route}"); 304 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); 305 | curl_setopt($ch, CURLOPT_POST, true); 306 | curl_setopt($ch, CURLOPT_HTTPHEADER, array( 307 | "Authorization: Bearer {$this->api_key}", 308 | 'Content-Type: application/json', 309 | 'Accept: application/json', 310 | $this->version_header 311 | )); 312 | return $this->execute_request($ch); 313 | } 314 | 315 | private function create_run($thread_id, $assistant_id) 316 | { 317 | $response = $this->send_post_request( 318 | "/threads/{$thread_id}/runs", 319 | array('assistant_id' => $assistant_id) 320 | ); 321 | 322 | if (empty($response['id'])) { 323 | throw new \Exception('Unable to create a run'); 324 | } 325 | return $response['id']; 326 | } 327 | 328 | private function get_run($thread_id, $run_id) 329 | { 330 | $response = $this->send_get_request("/threads/{$thread_id}/runs/{$run_id}"); 331 | 332 | if (empty($response['id'])) { 333 | throw new \Exception('Unable to create a run'); 334 | } 335 | return $response; 336 | } 337 | 338 | private function list_runs($thread_id) 339 | { 340 | $response = $this->send_get_request("/threads/{$thread_id}/runs"); 341 | 342 | if (empty($response['data'])) { 343 | return array(); 344 | } 345 | return $response['data']; 346 | } 347 | 348 | private function write_log($message) 349 | { 350 | $logFile = __DIR__ . '/tool_calls_log'; 351 | $logEntry = date('Y-m-d H:i:s') . ' - ' . $message . PHP_EOL; 352 | 353 | if ($fileHandle = fopen($logFile, 'a')) { 354 | fwrite($fileHandle, $logEntry); 355 | fclose($fileHandle); 356 | return true; 357 | } 358 | return false; 359 | } 360 | } 361 | --------------------------------------------------------------------------------