102 public function generateContent($instructions, $model =
'auto', $function =
'textgeneration', $format =
'', $moreheaders = array(), $moreendpoint =
'')
104 global $dolibarr_main_data_root;
109 if (empty($this->apiKey) && in_array($this->apiService, array(
'chatgpt',
'groq',
'mistral'))) {
110 return array(
'error' =>
true,
'message' =>
'API key is not defined for the AI enabled service ('.$this->apiService.
')');
115 if (empty($this->apiEndpoint) && $this->apiService ==
'custom' && !
getDolGlobalString(
'AI_API_CUSTOM_URL')) {
116 return array(
'error' =>
true,
'message' =>
'API URL is not defined for the AI enabled service ('.$this->apiService.
')');
120 if (empty($this->apiEndpoint)) {
122 if ($function ==
'imagegeneration') {
123 $this->apiEndpoint =
getDolGlobalString(
'AI_API_'.strtoupper($this->apiService).
'_URL', $arrayofai[$this->apiService][
'url']);
124 $this->apiEndpoint .= (preg_match(
'/\/$/', $this->apiEndpoint) ?
'' :
'/').
'images/generations';
125 } elseif ($function ==
'audiogeneration') {
126 $this->apiEndpoint =
getDolGlobalString(
'AI_API_'.strtoupper($this->apiService).
'_URL', $arrayofai[$this->apiService][
'url']);
127 $this->apiEndpoint .= (preg_match(
'/\/$/', $this->apiEndpoint) ?
'' :
'/').
'audio/speech';
128 } elseif ($function ==
'transcription') {
129 $this->apiEndpoint =
getDolGlobalString(
'AI_API_'.strtoupper($this->apiService).
'_URL', $arrayofai[$this->apiService][
'url']);
130 $this->apiEndpoint .= (preg_match(
'/\/$/', $this->apiEndpoint) ?
'' :
'/').
'transcriptions';
131 } elseif ($function ==
'file') {
132 $this->apiEndpoint =
getDolGlobalString(
'AI_API_'.strtoupper($this->apiService).
'_URL', $arrayofai[$this->apiService][
'url']);
133 $this->apiEndpoint .= (preg_match(
'/\/$/', $this->apiEndpoint) ?
'' :
'/').
'files';
134 } elseif ($function ==
'assistant') {
135 $this->apiEndpoint =
getDolGlobalString(
'AI_API_'.strtoupper($this->apiService).
'_URL', $arrayofai[$this->apiService][
'url']);
136 $this->apiEndpoint .= (preg_match(
'/\/$/', $this->apiEndpoint) ?
'' :
'/').
'assistans';
137 } elseif ($function ==
'thread') {
138 $this->apiEndpoint =
getDolGlobalString(
'AI_API_'.strtoupper($this->apiService).
'_URL', $arrayofai[$this->apiService][
'url']);
139 $this->apiEndpoint .= (preg_match(
'/\/$/', $this->apiEndpoint) ?
'' :
'/').
'threads';
141 $this->apiEndpoint =
getDolGlobalString(
'AI_API_'.strtoupper($this->apiService).
'_URL', $arrayofai[$this->apiService][
'url']);
142 if ($this->apiService ==
'google') {
146 $this->apiEndpoint = rtrim($this->apiEndpoint,
'/');
148 $this->apiEndpoint .= (preg_match(
'/\/$/', $this->apiEndpoint) ?
'' :
'/').
'chat/completions';
153 $this->apiEndpoint .=
'/'.$moreendpoint;
159 if (empty($model) || $model ==
'auto') {
161 if (in_array($function, array(
'file',
'assistant',
'thread'))) {
163 } elseif ($function ==
'imagegeneration') {
164 $model =
getDolGlobalString(
'AI_API_'.strtoupper($this->apiService).
'_MODEL_IMAGE', $arrayofai[$this->apiService][$function][
'default']);
165 } elseif ($function ==
'audiogeneration') {
166 $model =
getDolGlobalString(
'AI_API_'.strtoupper($this->apiService).
'_MODEL_AUDIO', $arrayofai[$this->apiService][$function][
'default']);
167 } elseif ($function ==
'transcription') {
168 $model =
getDolGlobalString(
'AI_API_'.strtoupper($this->apiService).
'_MODEL_TRANSCRIPT', $arrayofai[$this->apiService][$function][
'default']);
169 } elseif ($function ==
'translation') {
170 $model =
getDolGlobalString(
'AI_API_'.strtoupper($this->apiService).
'_MODEL_TRANSLATE', $arrayofai[$this->apiService][$function][
'default']);
171 } elseif ($function ==
'docparsing') {
172 $model =
getDolGlobalString(
'AI_API_'.strtoupper($this->apiService).
'_MODEL_DOCPARSING', $arrayofai[$this->apiService][$function][
'default']);
175 $model =
getDolGlobalString(
'AI_API_'.strtoupper($this->apiService).
'_MODEL_TEXT', $arrayofai[$this->apiService][
'textgeneration'][
'default']);
180 if ($this->apiService ==
'google' && !in_array($function, array(
'file',
'assistant',
'thread'))
181 && strpos($this->apiEndpoint,
':generateContent') ===
false) {
182 $this->apiEndpoint .=
'/models/'.rawurlencode($model).
':generateContent';
185 dol_syslog(
"Call API for apiKey=".substr($this->apiKey, 0, 5).
'***********, apiEndpoint='.$this->apiEndpoint.
", model=".$model.
", format=".$format);
187 if (@is_writable($dolibarr_main_data_root)) {
188 $outputfile = $dolibarr_main_data_root.
"/dolibarr_ai.log";
189 $fp = fopen($outputfile,
"w");
192 fwrite($fp,
"Call API for apiKey=".substr($this->apiKey, 0, 5).
'***********, apiEndpoint='.$this->apiEndpoint.
", model=".$model.
", format=".$format.
"\n");
202 if (empty($this->apiEndpoint)) {
203 throw new Exception(
'The AI service '.$this->apiService.
' is not yet supported for the type of request '.$function);
207 $configurations = json_decode($configurationsJson,
true);
212 if (isset($configurations[$function])) {
213 if (isset($configurations[$function][
'prePrompt'])) {
214 $prePrompt = $configurations[$function][
'prePrompt'];
217 if (isset($configurations[$function][
'postPrompt'])) {
218 $postPrompt = $configurations[$function][
'postPrompt'];
224 if (empty($prePrompt) && $function ==
'textgenerationemail') {
225 $prePrompt = self::AI_DEFAULT_PROMPT_FOR_EMAIL;
226 if ($format ===
'html') {
227 $prePrompt .=
' Return all HTML content inside a section tag';
229 $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.';
232 if (empty($prePrompt) && $function ==
'textgenerationwebpage') {
233 $prePrompt = self::AI_DEFAULT_PROMPT_FOR_WEBPAGE;
235 if (empty($prePrompt) && $function ==
'textgenerationextrafield') {
236 $prePrompt = self::AI_DEFAULT_PROMPT_FOR_EXTRAFIELD_FILLER;
238 if (empty($prePrompt) && $function ==
'texttranslation') {
239 $prePrompt = self::AI_DEFAULT_PROMPT_FOR_TEXT_TRANSLATION;
241 if (empty($prePrompt) && $function ==
'textsummarize') {
242 $prePrompt = self::AI_DEFAULT_PROMPT_FOR_TEXT_SUMMARIZE;
244 if (empty($prePrompt) && $function ==
'textrephraser') {
245 $prePrompt = self::AI_DEFAULT_PROMPT_FOR_TEXT_REPHRASER;
247 if (empty($prePrompt) && $function ==
'textspellchecker') {
248 $prePrompt = self::AI_DEFAULT_PROMPT_FOR_TEXT_SPELLCHECKER;
250 if (empty($prePrompt) && $function ==
'docparsing') {
251 $prePrompt = self::AI_DEFAULT_PROMPT_FOR_DOC_PARSING;
254 if (is_array($instructions)) {
255 $arrayforpayload = $instructions;
256 $fullInstructions =
'';
258 $fullInstructions = $instructions.($postPrompt ? (preg_match(
'/[\.\!\?]$/', $instructions) ?
'' :
'.').
' '.$postPrompt :
'');
285 $addDateTimeContext =
false;
286 if ($addDateTimeContext) {
287 $prePrompt = ($prePrompt ? $prePrompt.(preg_match(
'/[\.\!\?]$/', $prePrompt) ?
'' :
'.').
' ' :
'').
'Today we are '.
dol_print_date(
dol_now(),
'dayhourtext');
290 if ($this->apiService ==
'google') {
292 $arrayforpayload = array(
294 array(
'role' =>
'user',
'parts' => array(array(
'text' => $fullInstructions)))
298 $arrayforpayload[
'system_instruction'] = array(
299 'parts' => array(array(
'text' => $prePrompt))
304 $arrayforpayload = array(
305 'messages' => array(array(
'role' =>
'user',
'content' => $fullInstructions)),
309 $arrayforpayload[
'messages'][] = array(
'role' =>
'system',
'content' => $prePrompt);
320 if ($function ==
'thread') {
321 $payload = $instructions;
323 $payload = json_encode($arrayforpayload);
326 if ($this->apiService ==
'google') {
329 'x-goog-api-key: ' . $this->apiKey,
333 'Authorization: Bearer ' . $this->apiKey,
336 if ($function !=
'file') {
337 $headers[] =
'Content-Type: application/json';
339 if (!empty($moreheaders)) {
340 foreach ($moreheaders as $morekey => $moreval) {
341 $headers[] = $morekey.
': '.$moreval;
346 if (@is_writable($dolibarr_main_data_root)) {
347 $outputfile = $dolibarr_main_data_root.
"/dolibarr_ai.log";
348 $fp = fopen($outputfile,
"a");
351 if ($function ==
'docparsing') {
352 fwrite($fp,
"Call endpoint ".$this->apiEndpoint.
" with POST and the following file to upload:\n");
353 fwrite($fp, $instructions.
"\n");
355 fwrite($fp,
"Call endpoint ".$this->apiEndpoint.
" with POST and the following message:\n");
356 fwrite($fp, $fullInstructions.
"\n");
357 fwrite($fp,
"And prepompt:\n");
358 fwrite($fp, $prePrompt.
"\n");
360 fwrite($fp,
"HTTP Header\n");
361 fwrite($fp, var_export($headers,
true).
"\n");
362 fwrite($fp,
"Payload\n");
363 fwrite($fp, var_export($payload,
true).
"\n");
373 global $dolibarr_ai_allow_local_endpoints;
374 $localurl = $dolibarr_ai_allow_local_endpoints ?? 0;
376 $response =
getURLContent($this->apiEndpoint,
'POST', $payload, 1, $headers, array(
'http',
'https'), $localurl);
378 if (empty($response[
'http_code'])) {
379 throw new Exception(
'API request failed. No http received');
381 if (!empty($response[
'http_code']) && $response[
'http_code'] != 200) {
382 if (in_array($response[
'http_code'], array(400, 401, 403, 429)) && !empty($response[
'content'])) {
383 $tmp = json_decode($response[
'content'],
true);
384 if (!empty($tmp[
'message'])) {
387 'message' => $tmp[
'message'],
388 'code' => (empty($response[
'http_code']) ? 0 : $response[
'http_code']),
389 'curl_error_no' => (empty($response[
'curl_error_no']) ? 0 : $response[
'curl_error_no']),
391 'service' => $this->apiService,
392 'function' => $function
396 throw new Exception(
'API request on AI endpoint '.$this->apiEndpoint.
' failed with status code '.$response[
'http_code']);
400 if (@is_writable($dolibarr_main_data_root)) {
401 $outputfile = $dolibarr_main_data_root.
"/dolibarr_ai.log";
402 $fp = fopen($outputfile,
"a");
405 fwrite($fp,
"Answer\n");
406 fwrite($fp, var_export((empty($response[
'content']) ?
'No content result' : $response[
'content']),
true).
"\n");
416 $decodedResponse = json_decode($response[
'content'],
true);
419 if (!empty($decodedResponse[
'error'])) {
420 if (is_scalar($decodedResponse[
'error'])) {
421 $generatedContent = $decodedResponse[
'error'];
423 $generatedContent = var_export($decodedResponse[
'error'],
true);
425 } elseif ($this->apiService ==
'google') {
429 $generatedContent =
'';
430 if (!empty($decodedResponse[
'candidates'][0][
'content'][
'parts'])) {
431 foreach ($decodedResponse[
'candidates'][0][
'content'][
'parts'] as $part) {
432 if (isset($part[
'text'])) {
433 $generatedContent .= $part[
'text'];
438 $generatedContent = $decodedResponse[
'choices'][0][
'message'][
'content'];
443 if ($format ==
'html') {
445 dol_syslog(
"Result was detected as not HTML so we convert it into HTML.");
446 $generatedContent =
dol_nl2br($generatedContent);
448 dol_syslog(
"Result was detected as already HTML. Do nothing.");
456 return $generatedContent;
458 $errormessage = $e->getMessage();
459 $errormessagelog = $e->getMessage();
460 if (!empty($response[
'content'])) {
461 $decodedResponse = json_decode($response[
'content'],
true);
462 $errormessagelog .=
' - '.$response[
'content'];
464 if (!empty($decodedResponse[
'error'][
'message'])) {
466 $errormessage .=
' - '.$decodedResponse[
'error'][
'message'];
468 $errormessage .=
' - '.$response[
'content'];
473 if (@is_writable($dolibarr_main_data_root)) {
474 $outputfile = $dolibarr_main_data_root.
"/dolibarr_ai.log";
475 $fp = fopen($outputfile,
"a");
478 fwrite($fp,
"Error: ".$errormessagelog.
"\n");
488 'message' => $errormessage,
489 'code' => (empty($response[
'http_code']) ? 0 : $response[
'http_code']),
490 'curl_error_no' => (empty($response[
'curl_error_no']) ? 0 : $response[
'curl_error_no']),
492 'service' => $this->apiService,
493 'function' => $function