<?php
namespace App\Shared\Infrastructure\ChatGpt;
class ChatGptService
{
private $cache=[];
public function __construct(private array $apiKeys, private $cacheDir) {
$this->cache = @unserialize(@file_get_contents($this->cacheDir."/chatGpt.cache"))??[];
}
private function setCache($prompt, $response){
$md5 = md5($prompt);
$this->cache[$md5] = $response;
file_put_contents($this->cacheDir."/chatGpt.cache", serialize($this->cache));
}
private function getCache($prompt){
$md5 = md5($prompt);
if (isset($this->cache[$md5])) return $this->cache[$md5];
return false;
}
public function __invoke($prompt, $isJsonReponse = false)
{
if ($this->getCache($prompt)) {
return $this->getCache($prompt);
}
$apiKeys = $this->apiKeys;
$data = trim($prompt) ;
$dataFile = tempnam($this->cacheDir, 'data_') . '.txt';
file_put_contents($dataFile, $data);
$pythonScript = <<<EOF
import sys
import json
import openai
import traceback
import time
import os
import tempfile
# Lista de claves API pasadas desde PHP
api_keys = sys.argv[4].split(',')
cache_dir = sys.argv[5]
index_file = os.path.join(cache_dir, 'gpt_api_key_index.txt')
backoff_file = os.path.join(cache_dir, 'api_key_backoff.txt')
def read_backoff_times():
if os.path.exists(backoff_file):
with open(backoff_file, 'r') as file:
return json.load(file)
return {}
def write_backoff_times(backoff_data):
with open(backoff_file, 'w') as file:
json.dump(backoff_data, file)
def get_next_api_key():
backoff_data = read_backoff_times()
if os.path.exists(index_file):
with open(index_file, 'r') as f:
counter = int(f.read().strip())
else:
counter = -1
while True:
counter = (counter + 1) % len(api_keys)
current_key_index = str(counter)
if current_key_index in backoff_data and time.time() < backoff_data[current_key_index]:
continue
with open(index_file, 'w') as f:
f.write(str(counter))
return api_keys[counter]
def api_call(data_file, output_file="response.txt", error_file="error.txt"):
with open(data_file, 'r') as file:
content = file.read()
backoff_data = read_backoff_times()
initial_backoff = 1
print(f"Data file: {data_file}\\n")
print(f"Output file {output_file}\\n")
print(f"Error file {error_file}\\n")
while True:
try:
api_key = get_next_api_key()
openai.api_key = api_key
print(f"API Key: {api_key}")
print(f"Content: {content}")
print("Trying to generate response...\\n")
sys.executable = '/usr/bin/python3'
print(f"sys.executable: {sys.executable}\\n")
response = openai.ChatCompletion.create(
model="gpt-3.5-turbo",
messages=[
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": content}
],
temperature=0.7
)
print("Response success\\n")
with open(output_file, "w") as file:
file.write(response['choices'][0]['message']['content'].strip())
print("Response written to file successfully\\n")
break
except openai.error.RateLimitError as e:
current_key_index = str(api_keys.index(api_key))
print(f"Rate limit exceeded for API key {api_key}.")
next_backoff = min(initial_backoff * 2, 3600)
backoff_data[current_key_index] = time.time() + next_backoff
write_backoff_times(backoff_data)
initial_backoff = next_backoff
except Exception as e:
print(f"Unhandled exception: {e}")
error_details = f"{str(e)}\\n{traceback.format_exc()}"
with open(error_file, "w") as file:
file.write(error_details)
break
if __name__ == "__main__":
data_file = sys.argv[1]
output_file = sys.argv[2] if len(sys.argv) > 2 else "response.txt"
error_file = sys.argv[3] if len(sys.argv) > 3 else "error.txt"
api_call(data_file, output_file=output_file, error_file=error_file)
EOF;
$response = '';
$attempt = 0;
$maxAttempts = 5;
$error = "";
while ($attempt < $maxAttempts && empty($response)) {
$outputFile = tempnam($this->cacheDir, 'gpt_response_') . '.txt';
$errorFile = tempnam($this->cacheDir, 'gpt_error_') . '.txt';
$tempName = tempnam($this->cacheDir, 'py');
file_put_contents($tempName, $pythonScript);
$apiKeysString = implode(',', $apiKeys);
$command = escapeshellcmd("python3 " . $tempName . " " . escapeshellarg($dataFile) . " " . escapeshellarg($outputFile) . " " . escapeshellarg($errorFile) . " " . escapeshellarg($apiKeysString)." " . escapeshellarg($this->cacheDir));
$output = shell_exec($command);
if (file_exists($outputFile)) {
$response = trim(file_get_contents($outputFile));
unlink($tempName);
unlink($outputFile);
unlink($errorFile);
} else {
$error .= $output. " ".file_get_contents($errorFile).PHP_EOL;
}
unlink($errorFile);
unlink($outputFile);
if ($isJsonReponse) {
if (json_decode($response, true)) {
$response = json_decode($response, true);
}
else {
$isMatch = preg_match("/(?<json>[\[{].*?[}\]])/ms", $response, $mt);
if ($isMatch && isset($mt['json'])) {
$response = json_decode($mt['json'], true);
}
else $response = null;
}
}
$attempt++;
}
if (!$response) throw new \Exception("CHATGPT: ".$error);
unlink($dataFile);
$this->setCache($prompt, $response);
return $response;
}
}