├── 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 | [](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 |
--------------------------------------------------------------------------------