103 public function generateContent($instructions, $model =
'auto', $function =
'textgeneration', $format =
'', $moreheaders = array(), $moreendpoint =
'')
105 global $dolibarr_main_data_root;
110 if (empty($this->apiKey) && in_array($this->apiService, array(
'chatgpt',
'groq',
'mistral'))) {
111 return array(
'error' =>
true,
'message' =>
'API key is not defined for the AI enabled service ('.$this->apiService.
')');
116 if (empty($this->apiEndpoint) && $this->apiService ==
'custom' && !
getDolGlobalString(
'AI_API_CUSTOM_URL')) {
117 return array(
'error' =>
true,
'message' =>
'API URL is not defined for the AI enabled service ('.$this->apiService.
')');
121 if (empty($this->apiEndpoint)) {
123 if ($function ==
'imagegeneration') {
124 $this->apiEndpoint =
getDolGlobalString(
'AI_API_'.strtoupper($this->apiService).
'_URL', $arrayofai[$this->apiService][
'url']);
125 $this->apiEndpoint .= (preg_match(
'/\/$/', $this->apiEndpoint) ?
'' :
'/').
'images/generations';
126 } elseif ($function ==
'audiogeneration') {
127 $this->apiEndpoint =
getDolGlobalString(
'AI_API_'.strtoupper($this->apiService).
'_URL', $arrayofai[$this->apiService][
'url']);
128 $this->apiEndpoint .= (preg_match(
'/\/$/', $this->apiEndpoint) ?
'' :
'/').
'audio/speech';
129 } elseif ($function ==
'transcription') {
130 $this->apiEndpoint =
getDolGlobalString(
'AI_API_'.strtoupper($this->apiService).
'_URL', $arrayofai[$this->apiService][
'url']);
131 $this->apiEndpoint .= (preg_match(
'/\/$/', $this->apiEndpoint) ?
'' :
'/').
'transcriptions';
132 } elseif ($function ==
'file') {
133 $this->apiEndpoint =
getDolGlobalString(
'AI_API_'.strtoupper($this->apiService).
'_URL', $arrayofai[$this->apiService][
'url']);
134 $this->apiEndpoint .= (preg_match(
'/\/$/', $this->apiEndpoint) ?
'' :
'/').
'files';
135 } elseif ($function ==
'assistant') {
136 $this->apiEndpoint =
getDolGlobalString(
'AI_API_'.strtoupper($this->apiService).
'_URL', $arrayofai[$this->apiService][
'url']);
137 $this->apiEndpoint .= (preg_match(
'/\/$/', $this->apiEndpoint) ?
'' :
'/').
'assistans';
138 } elseif ($function ==
'thread') {
139 $this->apiEndpoint =
getDolGlobalString(
'AI_API_'.strtoupper($this->apiService).
'_URL', $arrayofai[$this->apiService][
'url']);
140 $this->apiEndpoint .= (preg_match(
'/\/$/', $this->apiEndpoint) ?
'' :
'/').
'threads';
142 $this->apiEndpoint =
getDolGlobalString(
'AI_API_'.strtoupper($this->apiService).
'_URL', $arrayofai[$this->apiService][
'url']);
143 if ($this->apiService ==
'google') {
147 $this->apiEndpoint = rtrim($this->apiEndpoint,
'/');
149 $this->apiEndpoint .= (preg_match(
'/\/$/', $this->apiEndpoint) ?
'' :
'/').
'chat/completions';
154 $this->apiEndpoint .=
'/'.$moreendpoint;
160 if (empty($model) || $model ==
'auto') {
162 if (in_array($function, array(
'file',
'assistant',
'thread'))) {
164 } elseif ($function ==
'imagegeneration') {
165 $model =
getDolGlobalString(
'AI_API_'.strtoupper($this->apiService).
'_MODEL_IMAGE', $arrayofai[$this->apiService][$function][
'default']);
166 } elseif ($function ==
'audiogeneration') {
167 $model =
getDolGlobalString(
'AI_API_'.strtoupper($this->apiService).
'_MODEL_AUDIO', $arrayofai[$this->apiService][$function][
'default']);
168 } elseif ($function ==
'transcription') {
169 $model =
getDolGlobalString(
'AI_API_'.strtoupper($this->apiService).
'_MODEL_TRANSCRIPT', $arrayofai[$this->apiService][$function][
'default']);
170 } elseif ($function ==
'translation') {
171 $model =
getDolGlobalString(
'AI_API_'.strtoupper($this->apiService).
'_MODEL_TRANSLATE', $arrayofai[$this->apiService][$function][
'default']);
172 } elseif ($function ==
'docparsing') {
173 $model =
getDolGlobalString(
'AI_API_'.strtoupper($this->apiService).
'_MODEL_DOCPARSING', $arrayofai[$this->apiService][$function][
'default']);
176 $model =
getDolGlobalString(
'AI_API_'.strtoupper($this->apiService).
'_MODEL_TEXT', $arrayofai[$this->apiService][
'textgeneration'][
'default']);
181 if ($this->apiService ==
'google' && !in_array($function, array(
'file',
'assistant',
'thread'))
182 && strpos($this->apiEndpoint,
':generateContent') ===
false) {
183 $this->apiEndpoint .=
'/models/'.rawurlencode($model).
':generateContent';
186 dol_syslog(
"Call API for apiKey=".substr($this->apiKey, 0, 5).
'***********, apiEndpoint='.$this->apiEndpoint.
", model=".$model.
", format=".$format);
188 if (@is_writable($dolibarr_main_data_root)) {
189 $outputfile = $dolibarr_main_data_root.
"/dolibarr_ai.log";
190 $fp = fopen($outputfile,
"w");
193 fwrite($fp,
"Call API for apiKey=".substr($this->apiKey, 0, 5).
'***********, apiEndpoint='.$this->apiEndpoint.
", model=".$model.
", format=".$format.
"\n");
203 if (empty($this->apiEndpoint)) {
204 throw new Exception(
'The AI service '.$this->apiService.
' is not yet supported for the type of request '.$function);
208 $configurations = json_decode($configurationsJson,
true);
213 if (isset($configurations[$function])) {
214 if (isset($configurations[$function][
'prePrompt'])) {
215 $prePrompt = $configurations[$function][
'prePrompt'];
218 if (isset($configurations[$function][
'postPrompt'])) {
219 $postPrompt = $configurations[$function][
'postPrompt'];
225 if (empty($prePrompt) && $function ==
'textgenerationemail') {
226 $prePrompt = self::AI_DEFAULT_PROMPT_FOR_EMAIL;
227 if ($format ===
'html') {
228 $prePrompt .=
' Return all HTML content inside a section tag';
230 $prePrompt .=
' Return content in UTF8 text. Use Linux carriage return if you need to split a line. Do not include any HTML tag neither HTML entities.';
233 if (empty($prePrompt) && $function ==
'textgenerationwebpage') {
234 $prePrompt = self::AI_DEFAULT_PROMPT_FOR_WEBPAGE;
236 if (empty($prePrompt) && $function ==
'textgenerationextrafield') {
237 $prePrompt = self::AI_DEFAULT_PROMPT_FOR_EXTRAFIELD_FILLER;
239 if (empty($prePrompt) && $function ==
'texttranslation') {
240 $prePrompt = self::AI_DEFAULT_PROMPT_FOR_TEXT_TRANSLATION;
242 if (empty($prePrompt) && $function ==
'textsummarize') {
243 $prePrompt = self::AI_DEFAULT_PROMPT_FOR_TEXT_SUMMARIZE;
245 if (empty($prePrompt) && $function ==
'textrephraser') {
246 $prePrompt = self::AI_DEFAULT_PROMPT_FOR_TEXT_REPHRASER;
248 if (empty($prePrompt) && $function ==
'textspellchecker') {
249 $prePrompt = self::AI_DEFAULT_PROMPT_FOR_TEXT_SPELLCHECKER;
251 if (empty($prePrompt) && $function ==
'docparsing') {
252 $prePrompt = self::AI_DEFAULT_PROMPT_FOR_DOC_PARSING;
255 if (is_array($instructions)) {
256 $arrayforpayload = $instructions;
257 $fullInstructions =
'';
259 $fullInstructions = $instructions.($postPrompt ? (preg_match(
'/[\.\!\?]$/', $instructions) ?
'' :
'.').
' '.$postPrompt :
'');
286 $addDateTimeContext =
false;
287 if ($addDateTimeContext) {
288 $prePrompt = ($prePrompt ? $prePrompt.(preg_match(
'/[\.\!\?]$/', $prePrompt) ?
'' :
'.').
' ' :
'').
'Today we are '.
dol_print_date(
dol_now(),
'dayhourtext');
291 if ($this->apiService ==
'google') {
293 $arrayforpayload = array(
295 array(
'role' =>
'user',
'parts' => array(array(
'text' => $fullInstructions)))
299 $arrayforpayload[
'system_instruction'] = array(
300 'parts' => array(array(
'text' => $prePrompt))
305 $arrayforpayload = array(
306 'messages' => array(array(
'role' =>
'user',
'content' => $fullInstructions)),
310 $arrayforpayload[
'messages'][] = array(
'role' =>
'system',
'content' => $prePrompt);
321 if ($function ==
'thread') {
322 $payload = $instructions;
324 $payload = json_encode($arrayforpayload);
327 if ($this->apiService ==
'google') {
330 'x-goog-api-key: ' . $this->apiKey,
334 'Authorization: Bearer ' . $this->apiKey,
337 if ($function !=
'file') {
338 $headers[] =
'Content-Type: application/json';
340 if (!empty($moreheaders)) {
341 foreach ($moreheaders as $morekey => $moreval) {
342 $headers[] = $morekey.
': '.$moreval;
347 if (@is_writable($dolibarr_main_data_root)) {
348 $outputfile = $dolibarr_main_data_root.
"/dolibarr_ai.log";
349 $fp = fopen($outputfile,
"a");
352 if ($function ==
'docparsing') {
353 fwrite($fp,
"Call endpoint ".$this->apiEndpoint.
" with POST and the following file to upload:\n");
354 fwrite($fp, $instructions.
"\n");
356 fwrite($fp,
"Call endpoint ".$this->apiEndpoint.
" with POST and the following message:\n");
357 fwrite($fp, $fullInstructions.
"\n");
358 fwrite($fp,
"And prepompt:\n");
359 fwrite($fp, $prePrompt.
"\n");
361 fwrite($fp,
"HTTP Header\n");
362 fwrite($fp, var_export($headers,
true).
"\n");
363 fwrite($fp,
"Payload\n");
364 fwrite($fp, var_export($payload,
true).
"\n");
374 global $dolibarr_ai_allow_local_endpoints;
375 $localurl = $dolibarr_ai_allow_local_endpoints ?? 0;
377 $response =
getURLContent($this->apiEndpoint,
'POST', $payload, 1, $headers, array(
'http',
'https'), $localurl);
379 if (empty($response[
'http_code'])) {
380 throw new Exception(
'API request failed. No http received');
382 if (!empty($response[
'http_code']) && $response[
'http_code'] != 200) {
383 if (in_array($response[
'http_code'], array(400, 401, 403, 429)) && !empty($response[
'content'])) {
384 $tmp = json_decode($response[
'content'],
true);
385 if (!empty($tmp[
'message'])) {
388 'message' => $tmp[
'message'],
389 'code' => (empty($response[
'http_code']) ? 0 : $response[
'http_code']),
390 'curl_error_no' => (empty($response[
'curl_error_no']) ? 0 : $response[
'curl_error_no']),
392 'service' => $this->apiService,
393 'function' => $function
397 throw new Exception(
'API request on AI endpoint '.$this->apiEndpoint.
' failed with status code '.$response[
'http_code']);
401 if (@is_writable($dolibarr_main_data_root)) {
402 $outputfile = $dolibarr_main_data_root.
"/dolibarr_ai.log";
403 $fp = fopen($outputfile,
"a");
406 fwrite($fp,
"Answer\n");
407 fwrite($fp, var_export((empty($response[
'content']) ?
'No content result' : $response[
'content']),
true).
"\n");
417 $decodedResponse = json_decode($response[
'content'],
true);
420 if (!empty($decodedResponse[
'error'])) {
421 if (is_scalar($decodedResponse[
'error'])) {
422 $generatedContent = $decodedResponse[
'error'];
424 $generatedContent = var_export($decodedResponse[
'error'],
true);
426 } elseif ($this->apiService ==
'google') {
430 $generatedContent =
'';
431 if (!empty($decodedResponse[
'candidates'][0][
'content'][
'parts'])) {
432 foreach ($decodedResponse[
'candidates'][0][
'content'][
'parts'] as $part) {
433 if (isset($part[
'text'])) {
434 $generatedContent .= $part[
'text'];
439 $generatedContent = $decodedResponse[
'choices'][0][
'message'][
'content'];
444 if ($format ==
'html') {
446 dol_syslog(
"Result was detected as not HTML so we convert it into HTML.");
447 $generatedContent =
dol_nl2br($generatedContent);
449 dol_syslog(
"Result was detected as already HTML. Do nothing.");
457 return $generatedContent;
459 $errormessage = $e->getMessage();
460 $errormessagelog = $e->getMessage();
461 if (!empty($response[
'content'])) {
462 $decodedResponse = json_decode($response[
'content'],
true);
463 $errormessagelog .=
' - '.$response[
'content'];
465 if (!empty($decodedResponse[
'error'][
'message'])) {
467 $errormessage .=
' - '.$decodedResponse[
'error'][
'message'];
469 $errormessage .=
' - '.$response[
'content'];
474 if (@is_writable($dolibarr_main_data_root)) {
475 $outputfile = $dolibarr_main_data_root.
"/dolibarr_ai.log";
476 $fp = fopen($outputfile,
"a");
479 fwrite($fp,
"Error: ".$errormessagelog.
"\n");
489 'message' => $errormessage,
490 'code' => (empty($response[
'http_code']) ? 0 : $response[
'http_code']),
491 'curl_error_no' => (empty($response[
'curl_error_no']) ? 0 : $response[
'curl_error_no']),
493 'service' => $this->apiService,
494 'function' => $function