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
← All changes | inc/Services/OpenAiService.php +92 -56 4.3.74.4.8 View file →
@@ -13,9 +13,9 @@
13 13 * Handles interactions with the OpenAI API for LearnPress.
14 14 *
15 15 * @package LearnPress\Services
16 16 * @since 4.3.0
17 - * @version 1.0.0
17 + * @version 1.0.1
18 18 */
19 19 class OpenAiService {
20 20 use Singleton;
21 21
@@ -21,9 +21,8 @@
21 21
22 22 public string $baseUrl = 'https://api.openai.com/v1/';
23 23 public string $urlChartCompletion;
24 24 public string $urlResponses;
25 - public string $urlCompletionLegacy;
26 25 public string $urlImage;
27 26
28 27 public string $secret_key;
29 28 public string $text_model_type;
@@ -33,12 +32,11 @@
33 32 public float $creativity_level;
34 33 public int $max_token;
35 34
36 35 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';
36 + $this->urlChartCompletion = $this->baseUrl . 'chat/completions';
37 + $this->urlResponses = $this->baseUrl . 'responses';
38 + $this->urlImage = $this->baseUrl . 'images/generations';
41 39 $this->get_settings();
42 40 }
43 41
44 42 /**
@@ -61,9 +59,9 @@
61 59
62 60 public function get_settings() {
63 61 $this->secret_key = LP_Settings::get_option( 'open_ai_secret_key', '' );
64 62 $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' );
63 + $this->image_model_type = LP_Settings::get_option( 'open_ai_image_model_type', 'gpt-image-1' );
66 64 $this->frequency_penalty = LP_Settings::get_option( 'open_ai_frequency_penalty_level', 0.0 );
67 65 $this->presence_penalty = LP_Settings::get_option( 'open_ai_presence_penalty_level', 0.0 );
68 66 $this->creativity_level = LP_Settings::get_option( 'open_ai_creativity_level', 1.0 );
69 67 $this->max_token = LP_Settings::get_option( 'open_ai_max_token', 0 );
@@ -74,27 +72,55 @@
74 72 *
75 73 * @throws Exception
76 74 */
77 75 public function send_request( array $args ): array {
78 - // Handle args before send request
79 - $url = '';
76 + $url = $this->urlResponses;
77 + $args = $this->handle_params_for_send_responses( $args );
80 78
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 );
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 93 }
94 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 +
95 121 $response = wp_remote_post(
96 - $url,
122 + $this->urlChartCompletion,
97 123 [
98 124 'headers' => [
99 125 'Authorization' => 'Bearer ' . $this->secret_key,
100 126 'Content-Type' => 'application/json',
@@ -109,13 +135,23 @@
109 135 }
110 136
111 137 $body = wp_remote_retrieve_body( $response );
112 138 $data = LP_Helper::json_decode( $body, true );
139 +
113 140 if ( isset( $data['error'] ) ) {
114 141 throw new Exception( $data['error']['message'] );
115 142 }
116 143
117 - return $this->detected_data( $data );
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;
118 154 }
119 155
120 156 /**
121 157 * @throws Exception
@@ -132,12 +168,12 @@
132 168 if ( $model_type === 'gpt-image-1' ) {
133 169 unset( $args['n'] );
134 170 }
135 171
136 - if ( $model_type == 'dall-e-3' ) {
172 + /*if ( $model_type == 'dall-e-3' ) {
137 173 // only n=1 is supported.
138 174 $args['n'] = 1;
139 - }
175 + }*/
140 176
141 177 $response = wp_remote_post(
142 178 $this->urlImage,
143 179 [
@@ -170,17 +206,16 @@
170 206 *
171 207 * @throws Exception
172 208 */
173 209 public function handle_params_for_send_chat_completion( $params ): array {
174 - $params = [
175 - 'model' => $this->text_model_type,
176 - 'frequency_penalty' => $this->frequency_penalty,
177 - 'presence_penalty' => $this->presence_penalty,
178 - 'temperature' => $this->creativity_level,
179 - 'max_completion_tokens' => $this->max_token,
180 - 'response_format' => [ 'type' => 'json_object' ],
181 - 'n' => $args['outputs'] ?? 1,
182 - 'messages' => [
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 = [
183 218 [
184 219 'role' => 'system',
185 220 'content' => 'You are an AI assistant specialized in education and course design.',
186 221 ],
@@ -187,39 +222,40 @@
187 222 [
188 223 'role' => 'user',
189 224 'content' => $params['prompt'] ?? '',
190 225 ],
191 - ],
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,
192 237 ];
193 238
194 - if ( $this->max_token === 0 ) {
195 - unset( $params['max_completion_tokens'] );
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' ];
196 243 }
197 244
198 - return $params;
199 - }
245 + if ( $has_tools ) {
246 + $result['tools'] = $params['tools'];
200 247
201 - /**
202 - * Handle params for send chat completion
203 - *
204 - * @docs https://platform.openai.com/docs/api-reference/completions/create
205 - *
206 - * @throws Exception
207 - */
208 - public function handle_params_for_send_completion_legacy( $params ): array {
209 - $params = [
210 - 'model' => $this->text_model_type,
211 - 'temperature' => $this->creativity_level,
212 - 'max_tokens' => $this->max_token,
213 - 'n' => $args['outputs'] ?? 1,
214 - 'prompt' => $params['prompt'] ?? '',
215 - ];
248 + if ( ! empty( $params['tool_choice'] ) ) {
249 + $result['tool_choice'] = $params['tool_choice'];
250 + }
251 + }
216 252
217 253 if ( $this->max_token === 0 ) {
218 - unset( $params['max_tokens'] );
254 + unset( $result['max_completion_tokens'] );
219 255 }
220 256
221 - return $params;
257 + return $result;
222 258 }
223 259
224 260 /**
225 261 * Handle params for send chat completion