dolibarr  21.0.0-alpha
ai.class.php
Go to the documentation of this file.
1 <?php
2 /* Copyright (C) 2024 Laurent Destailleur <eldy@users.sourceforge.net>
3  * Copyright (C) 2024 Frédéric France <frederic.france@free.fr>
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program. If not, see <https://www.gnu.org/licenses/>.
17  * or see https://www.gnu.org/
18  */
19 
26 require_once DOL_DOCUMENT_ROOT."/core/lib/admin.lib.php";
27 require_once DOL_DOCUMENT_ROOT.'/core/lib/geturl.lib.php';
28 
29 
33 class Ai
34 {
38  protected $db;
39 
43  private $apiService;
44 
48  private $apiKey;
49 
53  private $apiEndpoint;
54 
55 
62  public function __construct($db)
63  {
64  $this->db = $db;
65 
66  // Get API key according to enabled AI
67  $this->apiService = getDolGlobalString('AI_API_SERVICE', 'chatgpt');
68  $this->apiKey = getDolGlobalString('AI_API_'.strtoupper($this->apiService).'_KEY');
69  }
70 
80  public function generateContent($instructions, $model = 'auto', $function = 'textgeneration', $format = '')
81  {
82  if (empty($this->apiKey)) {
83  return array('error' => true, 'message' => 'API key is not defined for the AI enabled service '.$this->apiService);
84  }
85 
86  if (empty($this->apiEndpoint)) {
87  if ($function == 'imagegeneration') {
88  if ($this->apiService == 'chatgpt') {
89  $this->apiEndpoint = 'https://api.openai.com/v1/images/generations';
90  if ($model == 'auto') {
91  $model = getDolGlobalString('AI_API_CHATGPT_MODEL_IMAGE', 'dall-e-3');
92  }
93  }
94  } elseif ($function == 'audiotext') {
95  if ($this->apiService == 'chatgpt') {
96  $this->apiEndpoint = 'https://api.openai.com/v1/audio/speech';
97  if ($model == 'auto') {
98  $model = getDolGlobalString('AI_API_CHATGPT_MODEL_AUDIO', 'tts-1');
99  }
100  }
101  } elseif ($function == 'transcription') {
102  if ($this->apiService == 'chatgpt') {
103  $this->apiEndpoint = 'https://api.openai.com/v1/audio/transcriptions';
104  if ($model == 'auto') {
105  $model = getDolGlobalString('AI_API_CHATGPT_MODEL_TRANSCRIPT', 'whisper-1');
106  }
107  }
108  } elseif ($function == 'translation') {
109  if ($this->apiService == 'chatgpt') {
110  $this->apiEndpoint = 'https://api.openai.com/v1/audio/translations';
111  if ($model == 'auto') {
112  $model = getDolGlobalString('AI_API_CHATGPT_MODEL_TRANSLATE', 'whisper-1');
113  }
114  }
115  } else { // else textgeneration...
116  if ($this->apiService == 'groq') {
117  $this->apiEndpoint = 'https://api.groq.com/openai/v1/chat/completions';
118  if ($model == 'auto') {
119  $model = getDolGlobalString('AI_API_GROK_MODEL_TEXT', 'mixtral-8x7b-32768'); // 'llama3-8b-8192', 'gemma-7b-it'
120  }
121  } elseif ($this->apiService == 'chatgpt') {
122  $this->apiEndpoint = 'https://api.openai.com/v1/chat/completions';
123  if ($model == 'auto') {
124  $model = getDolGlobalString('AI_API_CHATGPT_MODEL_TEXT', 'gpt-3.5-turbo');
125  }
126  }
127  }
128  }
129 
130  dol_syslog("Call API for apiEndpoint=".$this->apiEndpoint." apiKey=".substr($this->apiKey, 0, 3).'***********, model='.$model);
131 
132  try {
133  if (empty($this->apiEndpoint)) {
134  throw new Exception('The AI service '.$this->apiService.' is not yet supported for the type of request '.$function);
135  }
136 
137  $configurationsJson = getDolGlobalString('AI_CONFIGURATIONS_PROMPT');
138  $configurations = json_decode($configurationsJson, true);
139 
140  $prePrompt = '';
141  $postPrompt = '';
142 
143  if (isset($configurations[$function])) {
144  if (isset($configurations[$function]['prePrompt'])) {
145  $prePrompt = $configurations[$function]['prePrompt'];
146  }
147 
148  if (isset($configurations[$function]['postPrompt'])) {
149  $postPrompt = $configurations[$function]['postPrompt'];
150  }
151  }
152  $fullInstructions = $prePrompt.' '.$instructions.' .'.$postPrompt;
153 
154 
155  $payload = json_encode([
156  'messages' => [
157  ['role' => 'user', 'content' => $fullInstructions]
158  ],
159  'model' => $model
160  ]);
161 
162  $headers = ([
163  'Authorization: Bearer ' . $this->apiKey,
164  'Content-Type: application/json'
165  ]);
166  $response = getURLContent($this->apiEndpoint, 'POST', $payload, 1, $headers);
167 
168  if (empty($response['http_code'])) {
169  throw new Exception('API request failed. No http received');
170  }
171  if (!empty($response['http_code']) && $response['http_code'] != 200) {
172  throw new Exception('API request failed with status code ' . $response['http_code']);
173  }
174  // Decode JSON response
175  $decodedResponse = json_decode($response['content'], true);
176 
177  // Extraction content
178  $generatedContent = $decodedResponse['choices'][0]['message']['content'];
179 
180  dol_syslog("generatedContent=".$generatedContent);
181 
182  // If content is not HTML, we convert it into HTML
183  if ($format == 'html') {
184  if (!dol_textishtml($generatedContent)) {
185  dol_syslog("Result was detected as not HTML so we convert it into HTML.");
186  $generatedContent = dol_nl2br($generatedContent);
187  } else {
188  dol_syslog("Result was detected as already HTML. Do nothing.");
189  }
190 
191  // TODO If content is for website module, we must
192  // - clan html header, keep body only and remove ``` ticks added by AI
193  // - add tags <section contenEditable="true"> </section>
194  }
195 
196  return $generatedContent;
197  } catch (Exception $e) {
198  $errormessage = $e->getMessage();
199  if (!empty($response['content'])) {
200  $decodedResponse = json_decode($response['content'], true);
201 
202  // With OpenAI, error is into an object error into the content
203  if (!empty($decodedResponse['error']['message'])) {
204  $errormessage .= ' - '.$decodedResponse['error']['message'];
205  }
206  }
207 
208  return array(
209  'error' => true,
210  'message' => $errormessage,
211  'code' => (empty($response['http_code']) ? 0 : $response['http_code']),
212  'curl_error_no' => (empty($response['curl_error_no']) ? $response['curl_error_no'] : ''),
213  'format' => $format,
214  'service' => $this->apiService,
215  'function'=>$function
216  );
217  }
218  }
219 }
Class for AI.
Definition: ai.class.php:34
generateContent($instructions, $model='auto', $function='textgeneration', $format='')
Generate response of instructions.
Definition: ai.class.php:80
__construct($db)
Constructor.
Definition: ai.class.php:62
dol_nl2br($stringtoencode, $nl2brmode=0, $forxml=false)
Replace CRLF in string with a HTML BR tag.
dol_textishtml($msg, $option=0)
Return if a text is a html content.
getDolGlobalString($key, $default='')
Return dolibarr global constant string value.
dol_syslog($message, $level=LOG_INFO, $ident=0, $suffixinfilename='', $restricttologhandler='', $logcontext=null)
Write log message into outputs.
getURLContent($url, $postorget='GET', $param='', $followlocation=1, $addheaders=array(), $allowedschemes=array('http', 'https'), $localurl=0, $ssl_verifypeer=-1)
Function to get a content from an URL (use proxy if proxy defined).
Definition: geturl.lib.php:42