108 public function generateContent($instructions, $model =
'auto', $function =
'textgeneration', $format =
'', $moreheaders = array(), $moreendpoint =
'')
110 global $dolibarr_main_data_root;
115 if (empty($this->apiKey) && in_array($this->apiService, array(
'chatgpt',
'groq',
'mistral'))) {
116 return array(
'error' =>
true,
'message' =>
'API key is not defined for the AI enabled service ('.$this->apiService.
')');
121 if (empty($this->apiEndpoint) && $this->apiService ==
'custom' && !
getDolGlobalString(
'AI_API_CUSTOM_URL')) {
122 return array(
'error' =>
true,
'message' =>
'API URL is not defined for the AI enabled service ('.$this->apiService.
')');
126 if (empty($this->apiEndpoint)) {
128 if ($function ==
'imagegeneration') {
129 $this->apiEndpoint =
getDolGlobalString(
'AI_API_'.strtoupper($this->apiService).
'_URL', $arrayofai[$this->apiService][
'url']);
130 $this->apiEndpoint .= (preg_match(
'/\/$/', $this->apiEndpoint) ?
'' :
'/').
'images/generations';
131 } elseif ($function ==
'audiogeneration') {
132 $this->apiEndpoint =
getDolGlobalString(
'AI_API_'.strtoupper($this->apiService).
'_URL', $arrayofai[$this->apiService][
'url']);
133 $this->apiEndpoint .= (preg_match(
'/\/$/', $this->apiEndpoint) ?
'' :
'/').
'audio/speech';
134 } elseif ($function ==
'transcription') {
135 $this->apiEndpoint =
getDolGlobalString(
'AI_API_'.strtoupper($this->apiService).
'_URL', $arrayofai[$this->apiService][
'url']);
136 $this->apiEndpoint .= (preg_match(
'/\/$/', $this->apiEndpoint) ?
'' :
'/').
'transcriptions';
137 } elseif ($function ==
'file') {
138 $this->apiEndpoint =
getDolGlobalString(
'AI_API_'.strtoupper($this->apiService).
'_URL', $arrayofai[$this->apiService][
'url']);
139 $this->apiEndpoint .= (preg_match(
'/\/$/', $this->apiEndpoint) ?
'' :
'/').
'files';
140 } elseif ($function ==
'assistant') {
141 $this->apiEndpoint =
getDolGlobalString(
'AI_API_'.strtoupper($this->apiService).
'_URL', $arrayofai[$this->apiService][
'url']);
142 $this->apiEndpoint .= (preg_match(
'/\/$/', $this->apiEndpoint) ?
'' :
'/').
'assistans';
143 } elseif ($function ==
'thread') {
144 $this->apiEndpoint =
getDolGlobalString(
'AI_API_'.strtoupper($this->apiService).
'_URL', $arrayofai[$this->apiService][
'url']);
145 $this->apiEndpoint .= (preg_match(
'/\/$/', $this->apiEndpoint) ?
'' :
'/').
'threads';
147 $this->apiEndpoint =
getDolGlobalString(
'AI_API_'.strtoupper($this->apiService).
'_URL', $arrayofai[$this->apiService][
'url']);
148 if ($this->apiService ==
'google') {
152 $this->apiEndpoint = rtrim($this->apiEndpoint,
'/');
154 $this->apiEndpoint .= (preg_match(
'/\/$/', $this->apiEndpoint) ?
'' :
'/').
'chat/completions';
159 $this->apiEndpoint .=
'/'.$moreendpoint;
165 if (empty($model) || $model ==
'auto') {
167 if (in_array($function, array(
'file',
'assistant',
'thread'))) {
169 } elseif ($function ==
'imagegeneration') {
170 $model =
getDolGlobalString(
'AI_API_'.strtoupper($this->apiService).
'_MODEL_IMAGE', $arrayofai[$this->apiService][$function][
'default']);
171 } elseif ($function ==
'audiogeneration') {
172 $model =
getDolGlobalString(
'AI_API_'.strtoupper($this->apiService).
'_MODEL_AUDIO', $arrayofai[$this->apiService][$function][
'default']);
173 } elseif ($function ==
'transcription') {
174 $model =
getDolGlobalString(
'AI_API_'.strtoupper($this->apiService).
'_MODEL_TRANSCRIPT', $arrayofai[$this->apiService][$function][
'default']);
175 } elseif ($function ==
'translation') {
176 $model =
getDolGlobalString(
'AI_API_'.strtoupper($this->apiService).
'_MODEL_TRANSLATE', $arrayofai[$this->apiService][$function][
'default']);
177 } elseif ($function ==
'docparsing') {
178 $model =
getDolGlobalString(
'AI_API_'.strtoupper($this->apiService).
'_MODEL_DOCPARSING', $arrayofai[$this->apiService][$function][
'default']);
181 $model =
getDolGlobalString(
'AI_API_'.strtoupper($this->apiService).
'_MODEL_TEXT', $arrayofai[$this->apiService][
'textgeneration'][
'default']);
186 if ($this->apiService ==
'google' && !in_array($function, array(
'file',
'assistant',
'thread'))
187 && strpos($this->apiEndpoint,
':generateContent') ===
false) {
188 $this->apiEndpoint .=
'/models/'.rawurlencode($model).
':generateContent';
191 dol_syslog(
"Call API for apiKey=".substr($this->apiKey, 0, 5).
'***********, apiEndpoint='.$this->apiEndpoint.
", model=".$model.
", format=".$format);
193 if (@is_writable($dolibarr_main_data_root)) {
194 $outputfile = $dolibarr_main_data_root.
"/dolibarr_ai.log";
195 $fp = fopen($outputfile,
"w");
198 fwrite($fp,
"Call API for apiKey=".substr($this->apiKey, 0, 5).
'***********, apiEndpoint='.$this->apiEndpoint.
", model=".$model.
", format=".$format.
"\n");
208 if (empty($this->apiEndpoint)) {
209 throw new Exception(
'The AI service '.$this->apiService.
' is not yet supported for the type of request '.$function);
213 $configurations = json_decode($configurationsJson,
true);
218 if (isset($configurations[$function])) {
219 if (isset($configurations[$function][
'prePrompt'])) {
220 $prePrompt = $configurations[$function][
'prePrompt'];
223 if (isset($configurations[$function][
'postPrompt'])) {
224 $postPrompt = $configurations[$function][
'postPrompt'];
230 if (empty($prePrompt) && $function ==
'textgenerationemail') {
231 $prePrompt = self::AI_DEFAULT_PROMPT_FOR_EMAIL;
232 if ($format ===
'html') {
233 $prePrompt .=
' Return all HTML content inside a section tag';
235 $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.';
238 if (empty($prePrompt) && $function ==
'textgenerationwebpage') {
239 $prePrompt = self::AI_DEFAULT_PROMPT_FOR_WEBPAGE;
241 if (empty($prePrompt) && $function ==
'textgenerationextrafield') {
242 $prePrompt = self::AI_DEFAULT_PROMPT_FOR_EXTRAFIELD_FILLER;
244 if (empty($prePrompt) && $function ==
'texttranslation') {
245 $prePrompt = self::AI_DEFAULT_PROMPT_FOR_TEXT_TRANSLATION;
247 if (empty($prePrompt) && $function ==
'textsummarize') {
248 $prePrompt = self::AI_DEFAULT_PROMPT_FOR_TEXT_SUMMARIZE;
250 if (empty($prePrompt) && $function ==
'textrephraser') {
251 $prePrompt = self::AI_DEFAULT_PROMPT_FOR_TEXT_REPHRASER;
253 if (empty($prePrompt) && $function ==
'textspellchecker') {
254 $prePrompt = self::AI_DEFAULT_PROMPT_FOR_TEXT_SPELLCHECKER;
256 if (empty($prePrompt) && $function ==
'docparsing') {
257 $prePrompt = self::AI_DEFAULT_PROMPT_FOR_DOC_PARSING;
260 if (is_array($instructions)) {
261 $arrayforpayload = $instructions;
262 $fullInstructions =
'';
264 $fullInstructions = $instructions.($postPrompt ? (preg_match(
'/[\.\!\?]$/', $instructions) ?
'' :
'.').
' '.$postPrompt :
'');
291 $addDateTimeContext =
false;
292 if ($addDateTimeContext) {
293 $prePrompt = ($prePrompt ? $prePrompt.(preg_match(
'/[\.\!\?]$/', $prePrompt) ?
'' :
'.').
' ' :
'').
'Today we are '.
dol_print_date(
dol_now(),
'dayhourtext');
296 if ($this->apiService ==
'google') {
298 $arrayforpayload = array(
300 array(
'role' =>
'user',
'parts' => array(array(
'text' => $fullInstructions)))
304 $arrayforpayload[
'system_instruction'] = array(
305 'parts' => array(array(
'text' => $prePrompt))
310 $arrayforpayload = array(
311 'messages' => array(array(
'role' =>
'user',
'content' => $fullInstructions)),
315 $arrayforpayload[
'messages'][] = array(
'role' =>
'system',
'content' => $prePrompt);
326 if ($function ==
'thread') {
327 $payload = $instructions;
329 $payload = json_encode($arrayforpayload);
332 if ($this->apiService ==
'google') {
335 'x-goog-api-key: ' . $this->apiKey,
339 'Authorization: Bearer ' . $this->apiKey,
342 if ($function !=
'file') {
343 $headers[] =
'Content-Type: application/json';
345 if (!empty($moreheaders)) {
346 foreach ($moreheaders as $morekey => $moreval) {
347 $headers[] = $morekey.
': '.$moreval;
352 if (@is_writable($dolibarr_main_data_root)) {
353 $outputfile = $dolibarr_main_data_root.
"/dolibarr_ai.log";
354 $fp = fopen($outputfile,
"a");
357 if ($function ==
'docparsing') {
358 fwrite($fp,
"Call endpoint ".$this->apiEndpoint.
" with POST and the following file to upload:\n");
359 fwrite($fp, $instructions.
"\n");
361 fwrite($fp,
"Call endpoint ".$this->apiEndpoint.
" with POST and the following message:\n");
362 fwrite($fp, $fullInstructions.
"\n");
363 fwrite($fp,
"And prepompt:\n");
364 fwrite($fp, $prePrompt.
"\n");
366 fwrite($fp,
"HTTP Header\n");
367 fwrite($fp, var_export($headers,
true).
"\n");
368 fwrite($fp,
"Payload\n");
369 fwrite($fp, var_export($payload,
true).
"\n");
379 global $dolibarr_ai_allow_local_endpoints;
380 $localurl = $dolibarr_ai_allow_local_endpoints ?? 0;
382 $response =
getURLContent($this->apiEndpoint,
'POST', $payload, 1, $headers, array(
'http',
'https'), $localurl);
384 if (empty($response[
'http_code'])) {
385 throw new Exception(
'API request failed. No http received');
387 if (!empty($response[
'http_code']) && $response[
'http_code'] != 200) {
388 if (in_array($response[
'http_code'], array(400, 401, 403, 429)) && !empty($response[
'content'])) {
389 $tmp = json_decode($response[
'content'],
true);
390 if (!empty($tmp[
'message'])) {
393 'message' => $tmp[
'message'],
394 'code' => (empty($response[
'http_code']) ? 0 : $response[
'http_code']),
395 'curl_error_no' => (empty($response[
'curl_error_no']) ? 0 : $response[
'curl_error_no']),
397 'service' => $this->apiService,
398 'function' => $function
402 throw new Exception(
'API request on AI endpoint '.$this->apiEndpoint.
' failed with status code '.$response[
'http_code']);
406 if (@is_writable($dolibarr_main_data_root)) {
407 $outputfile = $dolibarr_main_data_root.
"/dolibarr_ai.log";
408 $fp = fopen($outputfile,
"a");
411 fwrite($fp,
"Answer\n");
412 fwrite($fp, var_export((empty($response[
'content']) ?
'No content result' : $response[
'content']),
true).
"\n");
422 $decodedResponse = json_decode($response[
'content'],
true);
425 if (!empty($decodedResponse[
'error'])) {
426 if (is_scalar($decodedResponse[
'error'])) {
427 $generatedContent = $decodedResponse[
'error'];
429 $generatedContent = var_export($decodedResponse[
'error'],
true);
431 } elseif ($this->apiService ==
'google') {
435 $generatedContent =
'';
436 if (!empty($decodedResponse[
'candidates'][0][
'content'][
'parts'])) {
437 foreach ($decodedResponse[
'candidates'][0][
'content'][
'parts'] as $part) {
438 if (isset($part[
'text'])) {
439 $generatedContent .= $part[
'text'];
444 $generatedContent = $decodedResponse[
'choices'][0][
'message'][
'content'];
448 $this->tokenUsage = $decodedResponse[
'usage'] ??
null;
451 if ($format ==
'html') {
453 dol_syslog(
"Result was detected as not HTML so we convert it into HTML.");
454 $generatedContent =
dol_nl2br($generatedContent);
456 dol_syslog(
"Result was detected as already HTML. Do nothing.");
464 return $generatedContent;
466 $errormessage = $e->getMessage();
467 $errormessagelog = $e->getMessage();
468 if (!empty($response[
'content'])) {
469 $decodedResponse = json_decode($response[
'content'],
true);
470 $errormessagelog .=
' - '.$response[
'content'];
472 if (!empty($decodedResponse[
'error'][
'message'])) {
474 $errormessage .=
' - '.$decodedResponse[
'error'][
'message'];
476 $errormessage .=
' - '.$response[
'content'];
481 if (@is_writable($dolibarr_main_data_root)) {
482 $outputfile = $dolibarr_main_data_root.
"/dolibarr_ai.log";
483 $fp = fopen($outputfile,
"a");
486 fwrite($fp,
"Error: ".$errormessagelog.
"\n");
496 'message' => $errormessage,
497 'code' => (empty($response[
'http_code']) ? 0 : $response[
'http_code']),
498 'curl_error_no' => (empty($response[
'curl_error_no']) ? 0 : $response[
'curl_error_no']),
500 'service' => $this->apiService,
501 'function' => $function