PluginProbe
LearnPress – WordPress LMS Plugin for Create and Sell Online Courses / 4.3.9
LearnPress – WordPress LMS Plugin for Create and Sell Online Courses v4.3.9
4.4.7 4.4.6 4.4.5 4.4.4 4.4.3 4.4.2 4.4.1 4.4.0 4.3.9.1 4.3.9 4.3.8 4.3.7 4.1.6.9 4.1.6.9.1 4.1.6.9.2 4.1.6.9.3 4.1.6.9.4 4.1.7 4.1.7.1 4.1.7.2 4.1.7.3 4.1.7.3.1 4.1.7.3.2 4.2.0 4.2.1 All 138 releases
learnpress / inc / Services / OpenAiService.php

OpenAiService.php in LearnPress – WordPress LMS Plugin for Create and Sell Online Courses 4.3.9, at inc/Services/OpenAiService.php

358 lines 10.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace LearnPress\Services;
4
5 use Exception;
6 use LearnPress\Helpers\Singleton;
7 use LP_Helper;
8 use LP_Settings;
9
10 /**
11 * Class OpenAiService
12 *
13 * Handles interactions with the OpenAI API for LearnPress.
14 *
15 * @package LearnPress\Services
16 * @since 4.3.0
17 * @version 1.0.0
18 */
19 class OpenAiService {
20 use Singleton;
21
22 public string $baseUrl = 'https://api.openai.com/v1/';
23 public string $urlChartCompletion;
24 public string $urlResponses;
25 public string $urlCompletionLegacy;
26 public string $urlImage;
27
28 public string $secret_key;
29 public string $text_model_type;
30 public string $image_model_type;
31 public float $frequency_penalty;
32 public float $presence_penalty;
33 public float $creativity_level;
34 public int $max_token;
35
36 public function init() {
37 $this->urlChartCompletion = $this->baseUrl . 'chat/completions';
38 $this->urlCompletionLegacy = $this->baseUrl . 'completions';
39 $this->urlResponses = $this->baseUrl . 'responses';
40 $this->urlImage = $this->baseUrl . 'images/generations';
41 $this->get_settings();
42 }
43
44 /**
45 * Check OpenAI integration is enabled
46 */
47 public function is_enable(): bool {
48 return LP_Settings::get_option( 'enable_open_ai', 'no' ) === 'yes';
49 }
50
51 /**
52 * Get secret key
53 *
54 * @return string
55 * @since 4.3.6
56 * @version 1.0.0
57 */
58 public function get_secret_key(): string {
59 return (string) LP_Settings::get_option( 'open_ai_secret_key', '' );
60 }
61
62 public function get_settings() {
63 $this->secret_key = LP_Settings::get_option( 'open_ai_secret_key', '' );
64 $this->text_model_type = LP_Settings::get_option( 'open_ai_text_model_type', 'gpt-4.1' );
65 $this->image_model_type = LP_Settings::get_option( 'open_ai_image_model_type', 'dall-e-3' );
66 $this->frequency_penalty = LP_Settings::get_option( 'open_ai_frequency_penalty_level', 0.0 );
67 $this->presence_penalty = LP_Settings::get_option( 'open_ai_presence_penalty_level', 0.0 );
68 $this->creativity_level = LP_Settings::get_option( 'open_ai_creativity_level', 1.0 );
69 $this->max_token = LP_Settings::get_option( 'open_ai_max_token', 0 );
70 }
71
72 /**
73 * Call OpenAI API
74 *
75 * @throws Exception
76 */
77 public function send_request( array $args ): array {
78 // Handle args before send request
79 $url = '';
80
81 if ( in_array(
82 $this->text_model_type,
83 [ 'chatgpt-4o-latest', 'gpt-4o', 'gpt-4o-mini', 'gpt-3.5-turbo' ]
84 ) ) {
85 $url = $this->urlChartCompletion;
86 $args = $this->handle_params_for_send_chat_completion( $args );
87 } elseif ( $this->text_model_type == 'gpt-3.5-turbo-instruct' ) {
88 $url = $this->urlCompletionLegacy;
89 $args = $this->handle_params_for_send_completion_legacy( $args );
90 } else {
91 $url = $this->urlResponses;
92 $args = $this->handle_params_for_send_responses( $args );
93 }
94
95 $response = wp_remote_post(
96 $url,
97 [
98 'headers' => [
99 'Authorization' => 'Bearer ' . $this->secret_key,
100 'Content-Type' => 'application/json',
101 ],
102 'body' => json_encode( $args ),
103 'timeout' => 3600,
104 ]
105 );
106
107 if ( is_wp_error( $response ) ) {
108 throw new Exception( $response->get_error_message(), 400 );
109 }
110
111 $body = wp_remote_retrieve_body( $response );
112 $data = LP_Helper::json_decode( $body, true );
113 if ( isset( $data['error'] ) ) {
114 throw new Exception( $data['error']['message'] );
115 }
116
117 return $this->detected_data( $data );
118 }
119
120 /**
121 * Send a chat completion request and return the raw message object(s).
122 *
123 * Unlike send_request() which pipes through detected_data() (JSON-parsing
124 * content into lp_structure_data), this method returns the raw
125 * choices[].message objects so callers can handle tool_calls, content,
126 * and role directly. Used by the AI Assistant agent loop.
127 *
128 * @param array $params Must include 'messages' array. May include 'tools', 'tool_choice'.
129 *
130 * @return array The first choice's message object (keys: role, content, tool_calls, etc.)
131 * @throws Exception On API error or empty response.
132 * @since 4.3.0
133 */
134 public function send_chat_request( array $params ): array {
135 $args = $this->handle_params_for_send_chat_completion( $params );
136
137 $response = wp_remote_post(
138 $this->urlChartCompletion,
139 [
140 'headers' => [
141 'Authorization' => 'Bearer ' . $this->secret_key,
142 'Content-Type' => 'application/json',
143 ],
144 'body' => json_encode( $args ),
145 'timeout' => 3600,
146 ]
147 );
148
149 if ( is_wp_error( $response ) ) {
150 throw new Exception( $response->get_error_message(), 400 );
151 }
152
153 $body = wp_remote_retrieve_body( $response );
154 $data = LP_Helper::json_decode( $body, true );
155
156 if ( isset( $data['error'] ) ) {
157 throw new Exception( $data['error']['message'] );
158 }
159
160 if ( empty( $data['choices'][0]['message'] ) ) {
161 throw new Exception( __( 'Empty response from OpenAI API.', 'learnpress' ) );
162 }
163
164 $message = $data['choices'][0]['message'];
165 if ( isset( $data['usage'] ) && is_array( $data['usage'] ) ) {
166 $message['usage'] = $data['usage'];
167 }
168
169 return $message;
170 }
171
172 /**
173 * @throws Exception
174 */
175 public function send_request_create_image( $args ) {
176 $args_default = [
177 'model' => $this->image_model_type,
178 'prompt' => '',
179 ];
180
181 $args = array_merge( $args_default, $args );
182
183 $model_type = LP_Settings::get_option( 'open_ai_image_model_type', 'gpt-image-1' );
184 if ( $model_type === 'gpt-image-1' ) {
185 unset( $args['n'] );
186 }
187
188 if ( $model_type == 'dall-e-3' ) {
189 // only n=1 is supported.
190 $args['n'] = 1;
191 }
192
193 $response = wp_remote_post(
194 $this->urlImage,
195 [
196 'headers' => [
197 'Authorization' => 'Bearer ' . $this->secret_key,
198 'Content-Type' => 'application/json',
199 ],
200 'body' => json_encode( $args ),
201 'timeout' => 3600,
202 ]
203 );
204
205 if ( is_wp_error( $response ) ) {
206 throw new Exception( $response->get_error_message(), 400 );
207 }
208
209 $body = wp_remote_retrieve_body( $response );
210 $data = LP_Helper::json_decode( $body, true );
211 if ( isset( $data['error'] ) ) {
212 throw new Exception( $data['error']['message'] );
213 }
214
215 return $data;
216 }
217
218 /**
219 * Handle params for send chat completion
220 *
221 * @docs https://platform.openai.com/docs/api-reference/chat/create
222 *
223 * @throws Exception
224 */
225 public function handle_params_for_send_chat_completion( $params ): array {
226 $has_tools = ! empty( $params['tools'] );
227
228 // When caller supplies a pre-built messages array (e.g. multi-turn assistant),
229 // use it as-is instead of rebuilding from a single prompt string.
230 if ( ! empty( $params['messages'] ) && is_array( $params['messages'] ) ) {
231 $messages = $params['messages'];
232 } else {
233 $messages = [
234 [
235 'role' => 'system',
236 'content' => 'You are an AI assistant specialized in education and course design.',
237 ],
238 [
239 'role' => 'user',
240 'content' => $params['prompt'] ?? '',
241 ],
242 ];
243 }
244
245 $result = [
246 'model' => $this->text_model_type,
247 'frequency_penalty' => $this->frequency_penalty,
248 'presence_penalty' => $this->presence_penalty,
249 'temperature' => $this->creativity_level,
250 'max_completion_tokens' => $this->max_token,
251 'n' => $params['outputs'] ?? 1,
252 'messages' => $messages,
253 ];
254
255 // Only set response_format when tools are NOT present.
256 // Forced json_object mode conflicts with tool_calls responses.
257 if ( ! $has_tools ) {
258 $result['response_format'] = [ 'type' => 'json_object' ];
259 }
260
261 if ( $has_tools ) {
262 $result['tools'] = $params['tools'];
263
264 if ( ! empty( $params['tool_choice'] ) ) {
265 $result['tool_choice'] = $params['tool_choice'];
266 }
267 }
268
269 if ( $this->max_token === 0 ) {
270 unset( $result['max_completion_tokens'] );
271 }
272
273 return $result;
274 }
275
276 /**
277 * Handle params for send chat completion
278 *
279 * @docs https://platform.openai.com/docs/api-reference/completions/create
280 *
281 * @throws Exception
282 */
283 public function handle_params_for_send_completion_legacy( $params ): array {
284 $params = [
285 'model' => $this->text_model_type,
286 'temperature' => $this->creativity_level,
287 'max_tokens' => $this->max_token,
288 'n' => $params['outputs'] ?? 1,
289 'prompt' => $params['prompt'] ?? '',
290 ];
291
292 if ( $this->max_token === 0 ) {
293 unset( $params['max_tokens'] );
294 }
295
296 return $params;
297 }
298
299 /**
300 * Handle params for send chat completion
301 *
302 * @docs https://platform.openai.com/docs/api-reference/responses/create
303 *
304 * @throws Exception
305 */
306 public function handle_params_for_send_responses( $params ): array {
307 $params = [
308 'model' => $this->text_model_type,
309 //'temperature' => $this->creativity_level,
310 'max_output_tokens' => $this->max_token,
311 'input' => $params['prompt'] ?? '',
312 ];
313
314 if ( $this->max_token === 0 ) {
315 unset( $params['max_output_tokens'] );
316 }
317
318 return $params;
319 }
320
321 /**
322 * Detect data from response
323 *
324 * @throws Exception
325 */
326 public function detected_data( array $data ): array {
327 $data['lp_structure_data'] = [];
328
329 if ( isset( $data['choices'] ) ) {
330 foreach ( $data['choices'] as $choice ) {
331 $text = $choice['message']['content'] ?? $choice['text'] ?? '';
332 $text = str_replace( [ "\\n", "\\r", "\n", "\r", "\t" ], '', $text );
333
334 $data['lp_structure_data'][] = LP_Helper::json_decode( $text, true );
335 }
336 } elseif ( isset( $data['output'] ) ) {
337 foreach ( $data['output'] as $output ) {
338 $content_data = $output['content'] ?? [];
339 if ( empty( $content_data ) ) {
340 continue;
341 }
342
343 foreach ( $output['content'] as $content ) {
344 try {
345 $text = $content['text'] ?? '';
346 $text = str_replace( [ "\\n", "\\r", "\n", "\r", "\t" ], '', $text );
347 $data['lp_structure_data'][] = LP_Helper::json_decode( $text, true );
348 } catch ( Exception $e ) {
349 $data['lp_structure_data'][] = $content['text'] ?? '';
350 }
351 }
352 }
353 }
354
355 return $data;
356 }
357 }
358