Files
resourcespace/plugins/openai_gpt/include/openai_gpt_functions.php
2025-07-18 16:20:14 +07:00

319 lines
13 KiB
PHP

<?php
/**
* Send the new field value or image to the OpenAI API in order to update the linked field
*
* @param int|array $resources Resource ID or array of resource IDS
* @param array $target_field Target metadata field array from get_resource_type_field()
* @param array $values Array of strings from the field currently being processed
* @param string $file Path to image file. If provided will use this file instead of metadata values
*
* @return bool|array Array indicating success/failure
* True if update successful, false if invalid field or no data returned
*
*/
function openai_gpt_update_field($resources,array $target_field,array $values, string $file="")
{
global $valid_ai_field_types, $FIXED_LIST_FIELD_TYPES,$language, $defaultlanguage, $openai_gpt_message_input_JSON,
$openai_gpt_message_output_json, $openai_gpt_message_text, $openai_gpt_processed, $openai_gpt_api_key,$openai_gpt_model,
$openai_gpt_temperature,$openai_gpt_example_json_user,$openai_gpt_example_json_assistant,$openai_gpt_example_text_user,
$openai_gpt_example_text_assistant,$openai_gpt_max_tokens, $openai_gpt_max_data_length, $openai_gpt_system_message,
$openai_gpt_fallback_model, $openai_gpt_message_output_text, $open_gpt_model_override, $lang, $language, $languages, $openai_gpt_language;
// Don't update if not a valid field type
if(!in_array($target_field["type"],$valid_ai_field_types))
{
return false;
}
if(!is_array($resources))
{
$resources = [$resources];
}
set_processing_message(((count($resources)>1)?$lang["openai_gpt_processing_multiple_resources"]:str_replace("[resource]",$resources[0],$lang["openai_gpt_processing_resource"])) . ": " . str_replace("[field]",$target_field["name"],$lang["openai_gpt_processing_field"]));
// Define a language instruction based on the language of the current user.
$output_language=$openai_gpt_language;
if ($output_language=="") {$output_language=$language;} // Empty string = use the language of the current user.
$language_instruction=" The response should be in language: " . $languages[$output_language];
$resources = array_filter($resources,"is_int_loose");
$valid_response = false;
if(trim($file) != "")
{
$file_data = file_get_contents($file);
$file_data_base64 = base64_encode($file_data);
$return_json = in_array($target_field["type"],$FIXED_LIST_FIELD_TYPES);
$outtype = $return_json ? $openai_gpt_message_output_json : $openai_gpt_message_output_text;
$system_message = str_replace(["%%IN_TYPE%%","%%OUT_TYPE%%"],["image",$outtype],$openai_gpt_system_message) . $language_instruction;
$messages = [];
$messages[] = ["role"=>"system","content"=>$system_message];
$messages[] = [
"role" => "user",
"content" => [
["type" => "text", "text" => $target_field["openai_gpt_prompt"]],
["type" => "image_url",
"image_url" => [
"url" => "data:image/jpeg;base64, " . $file_data_base64,
"detail" => "low"
]
]
]
];
debug("openai_gpt - sending request prompt for image");
}
else
{
// Get data to use
// Remove any i18n variants and use default system language
$prompt_values = [];
$saved_language = $language;
$language = $defaultlanguage;
foreach($values as $value)
{
if(substr($value,0,1) == "~")
{
$prompt_values[] = mb_strcut(i18n_get_translated($value),0,$openai_gpt_max_data_length);
}
elseif(trim($value) != "")
{
$prompt_values[] = mb_strcut($value,0,$openai_gpt_max_data_length);
}
}
$language = $saved_language;
// Generate prompt (only if there are any strings)
if(count($prompt_values)==0)
{
// No nodes present, fake a valid response to clear target field
$newvalue = '';
$valid_response = true;
$messages = [];
}
else
{
$send_as_json = count($prompt_values) > 1;
$return_json = in_array($target_field["type"],$FIXED_LIST_FIELD_TYPES);
$intype = $send_as_json ? $openai_gpt_message_input_JSON : $openai_gpt_message_text;
$outtype = $return_json ? $openai_gpt_message_output_json : $openai_gpt_message_output_text;
$system_message = str_replace(["%%IN_TYPE%%","%%OUT_TYPE%%"],[$intype,$outtype],$openai_gpt_system_message) . $language_instruction;
$messages = [];
$messages[] = ["role"=>"system","content"=>$system_message];
// Give a sample
if(in_array($target_field["type"],$FIXED_LIST_FIELD_TYPES))
{
$messages[] = ["role"=>"user","content"=> $openai_gpt_example_json_user];
$messages[] = ["role"=>"assistant","content"=>$openai_gpt_example_json_assistant];
}
else
{
$messages[] = ["role"=>"user","content"=> $openai_gpt_example_text_user];
$messages[] = ["role"=>"assistant","content"=>$openai_gpt_example_text_assistant];
}
$messages[] = ["role"=>"user","content"=> $target_field["openai_gpt_prompt"] . ": " . ($send_as_json ? json_encode($prompt_values) : $prompt_values[0])];
}
}
debug("openai_gpt - sending request prompt " . json_encode($messages));
// Can't use old model since move to chat API
$use_model =trim($openai_gpt_model) == "text-davinci-003" ? $openai_gpt_fallback_model : $openai_gpt_model;
if (isset($open_gpt_model_override)) {$use_model=$open_gpt_model_override;}
$openai_response = openai_gpt_generate_completions($openai_gpt_api_key,$use_model,$messages,$openai_gpt_temperature,$openai_gpt_max_tokens);
if(trim($openai_response) != "")
{
debug("response from openai_gpt_generate_completions() : " . $openai_response);
if(in_array($target_field["type"],$FIXED_LIST_FIELD_TYPES))
{
// Clean up response
if(substr($openai_response,0,7) == "```json")
{
debug("openai_gpt - extracting JSON text");
$openai_response = substr(trim($openai_response," `\""),4);
}
else
{
$openai_response = trim($openai_response," \"");
}
$apivalues = json_decode(trim($openai_response),true);
if(json_last_error() !== JSON_ERROR_NONE || !is_array($apivalues))
{
debug("openai_gpt error - invalid JSON text response received from API: " . json_last_error_msg() . " " . trim($openai_response));
if (strpos($openai_response, ",") !== false)
{
// Try and split on comma
$apivalues = explode(",",$openai_response);
}
else
{
$apivalues = [$openai_response];
}
}
// The returned array elements may be associative or contain sub arrays - convert to list of strings
$newstrings = [];
foreach($apivalues as $attribute=>&$value)
{
if(is_array($value))
{
$value = json_encode($value);
}
$newstrings[] = is_int_loose($attribute) ? $value : $attribute . " : " . $value;
}
// update_field() will separate on NODE_NAME_STRING_SEPARATOR
$newvalue = implode(NODE_NAME_STRING_SEPARATOR,$newstrings);
}
else
{
$newvalue = trim($openai_response," \"");
}
$valid_response = true;
}
else
{
debug("openai_gpt error - empty response received from API: '" . trim($openai_response) . "'");
}
$results = [];
foreach ($resources as $resource) {
$valuepresent = false;
if (!$GLOBALS["openai_gpt_overwrite_data"]) {
$current_data = get_data_by_field($resource, $target_field["ref"]);
if (trim((string) $current_data) !== '') {
$valuepresent = true;
}
}
if (isset($openai_gpt_processed[$resource . "_" . $target_field["ref"]]) || $valuepresent) {
// This resource/field has already been processed, or already has data present
continue;
}
if ($valid_response) {
debug("openai_gpt_update_field() - resource # " . $resource . ", target field #" . $target_field["ref"]);
// Set a flag to prevent any possibility of infinite recursion within update_field()
$openai_gpt_processed[$resource . "_" . $target_field["ref"]] = true;
$result = update_field($resource,$target_field["ref"],$newvalue);
$results[$resource] = $result;
} else {
$results[$resource] = false;
}
}
return $results;
}
/**
* Call the OpenAI API
*
* Refer to https://beta.openai.com/docs/api-reference for detailed explanation
*
* @param string $apiKey API key
* @param string $model Model name e.g. "text-davinci-003"
* @param array $messages Array of prompt messages to generate response from API.
* See https://platform.openai.com/docs/guides/chat/introduction for more information
* @param float $temperature Value between 0 and 1 - higher values means model will take more risks. Default 0.
* @param int $max_tokens The maximum number of completions to generate, default 2048
*
* @return string The first API response text output
*
*/
function openai_gpt_generate_completions($apiKey, $model, $messages, $temperature = 0, $max_tokens = 2048)
{
debug("openai_gpt_generate_completions() \$model = '" . $model . "', \$prompt = '" . json_encode($messages) . "' \$temperature = '" . $temperature . "', \$max_tokens = " . $max_tokens);
// Set the endpoint URL
global $openai_gpt_endpoint, $openai_response_cache, $userref;
$messagestring = json_encode($messages);
if(isset($openai_response_cache[md5($openai_gpt_endpoint . $messagestring)]))
{
return $openai_response_cache[md5($openai_gpt_endpoint . $messagestring)];
}
// $temperature must be between 0 and 1
$temperature = floatval($temperature);
if($temperature>1 || $temperature<0)
{
debug("openai_gpt invalid temperature value set : '" . $temperature . "'");
$temperature = 0;
}
if(trim($apiKey) == "")
{
debug("openai_gpt error - missing API key");
}
// Set the headers for the request
$headers = [
"Content-Type: application/json",
"Authorization: Bearer $apiKey",
];
// Set the data to send with the request
$data = [
"model" => $model,
"messages" => $messages,
"temperature" => $temperature,
"max_tokens" => (int)$max_tokens,
];
// Initialize cURL
$ch = curl_init($openai_gpt_endpoint);
// Set the options for the request
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => $headers,
CURLOPT_POSTFIELDS => json_encode($data),
CURLOPT_SSL_VERIFYPEER => true,
CURLOPT_SSL_VERIFYHOST => 2,
]);
// Send the request and get the response
$response = curl_exec($ch);
// Decode the response as JSON
debug("openai_gpt_generate_completions original response : " . print_r($response,true));
$response_data = json_decode($response, true);
if(json_last_error() !== JSON_ERROR_NONE)
{
debug("openai_gpt error - invalid JSON response received from API: " . json_last_error_msg() . " " . trim($response));
$openai_response_cache[md5($openai_gpt_endpoint . $messagestring)] = false;
return false;
}
$error = $response_data["error"] ?? ($response_data["error"][0] ?? []);
if(!empty($error))
{
debug("openai_gpt_generate_completions API error - type:" . $error["type"] . ", message: " . $error["message"]);
$openai_response_cache[md5($openai_gpt_endpoint . $messagestring)] = false;
return false;
}
// Log the usage
if (isset($response_data["usage"]["total_tokens"]) && is_numeric($response_data["usage"]["total_tokens"])) {daily_stat("OpenAI Token Usage",$userref,$response_data["usage"]["total_tokens"]);}
// Return the text from the completions
if (isset($response_data["choices"][0]["message"]["content"]))
{
$return = $response_data["choices"][0]["message"]["content"];
$openai_response_cache[md5($openai_gpt_endpoint . $messagestring)] = $return;
return $return;
}
return false;
}
function openai_gpt_get_dependent_fields($field)
{
return ps_query("SELECT " . columns_in("resource_type_field") . " FROM resource_type_field WHERE openai_gpt_input_field = ?",["i", $field]);
}