PluginProbe
LearnPress – WordPress LMS Plugin for Create and Sell Online Courses / 4.4.8
LearnPress – WordPress LMS Plugin for Create and Sell Online Courses v4.4.8
4.4.8 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 All 139 releases
learnpress / inc / Services / OpenAiService.php

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

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