newsystem(''); //定义系统角色,新对话 $openai->completion(''); //发起对话 */ namespace ciy; class openai { public $id; //扩展数据1 public $obj; //扩展数据2 private $aicfg = array(); public $messages = array(); private $tt = -999; //0 2 private $tp = -999; //0 2 private $fp = -999; //-2 2 private $pp = -999; //-2 2 private $debug = false; public function __construct($ai) { if (is_array($ai)) $this->aicfg = $ai; } public function debug($debug = true) { $this->debug = $debug; } public function setparam($spstr) { $sp = getstrparam($spstr, ','); if (isset($sp['tt'])) $this->tt = (float)$sp['tt']; if (isset($sp['tp'])) $this->tp = (float)$sp['tp']; if (isset($sp['fp'])) $this->fp = (float)$sp['fp']; if (isset($sp['pp'])) $this->pp = (float)$sp['pp']; } public function newsystem($msg = null) { $this->messages = array(); if (!empty($msg)) $this->messages[] = array('role' => 'system', 'content' => $msg); } public function completion($prompt, $isjson = false, $funcdatarows = null, $toolcb = null) { $this->messages[] = array('role' => 'user', 'content' => $prompt); $tools = null; if (is_array($funcdatarows)) { $tools = array(); foreach ($funcdatarows as $funcdatarow) { $fparam = array(); $paramjson = $funcdatarow['paramjson']; if ($paramjson[0] == '{') $fparam = json_decode($paramjson, true); else { $fparam = array(); $fparam['type'] = 'object'; $fparam['properties'] = array(); $fparam['required'] = array(); $paramjsons = getstrparam($paramjson, "\n"); foreach ($paramjsons as $key => $val) { $require = false; if ($key[0] == '*') { $key = substr($key, 1); $require = true; } $fparam['properties'][$key] = array(); $fparam['properties'][$key]['type'] = 'string'; $fparam['properties'][$key]['description'] = $val; if ($require) $fparam['required'][] = $key; } } $tool = array(); $tool['type'] = 'function'; $tool['function'] = array(); $tool['function']['name'] = 'F' . $funcdatarow['id']; $tool['function']['description'] = $funcdatarow['descs']; $tool['function']['parameters'] = $fparam; $tools[] = $tool; } } $resultcontent = ''; while (true) { $data = array(); $data['model'] = $this->aicfg['model']; $data['messages'] = $this->messages; if ($tools) $data['tools'] = $tools; if ($this->aicfg['maxtoken'] > 0) $data['max_tokens'] = toint($this->aicfg['maxtoken']); if ($this->fp > -999) $data['frequency_penalty'] = $this->fp; if ($this->pp > -999) $data['presence_penalty'] = $this->pp; if ($this->tt > -999) $data['temperature'] = $this->tt; if ($this->tp > -999) $data['top_p'] = $this->tp; if ($isjson) $data['response_format'] = array('type' => 'json_object'); if ($this->debug) { savelogfile('openai', $this->aicfg['baseurl']); savelogfile('openai', json_encode($data, JSON_UNESCAPED_UNICODE)); } $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $this->aicfg['baseurl'] . '/chat/completions'); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data)); curl_setopt($ch, CURLOPT_HTTPHEADER, [ 'Content-Type: application/json', 'Authorization: Bearer ' . $this->aicfg['aikey'] ]); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0); $response = curl_exec($ch); if ($this->debug) { savelogfile('openai', '--------------------------------'); savelogfile('openai', $response); savelogfile('openai', ''); } curl_close($ch); if ($response === false) { curl_close($ch); return 'ERR: ' . curl_error($ch) . ' (' . curl_errno($ch) . ')'; } $json = json_decode($response, true); if (isset($json['error_msg'])) return 'ERR: ' . $json['error_msg']; if (isset($json['error'])) return 'ERR: ' . $json['error']['message']; if (!isset($json['choices'])) return 'ERR: no choices.' . $response; $finish = $json['choices'][0]['finish_reason']; $message = $json['choices'][0]['message']; $this->messages[] = $message; if ($finish == 'tool_calls') { foreach ($message['tool_calls'] as $tool_call) { $result = $toolcb($tool_call['function']); if (!is_string($result)) $result = json_encode($result, JSON_UNESCAPED_UNICODE); else if (substr($result, 0, 3) == 'ERR') return $result; $this->messages[] = array( "tool_call_id" => $tool_call['id'], "role" => "tool", "name" => $tool_call['function']['name'], "content" => $result ); } continue; } if ($finish == 'length') { if ($isjson) { $message['content'] = self::fixjson($message['content']); //以下暂时AI能力有限 // $context = mb_substr($content, -200, 200); // $last_char = mb_substr($context, -1); // $expected = ''; // switch ($last_char) { // case '{': $expected = '对象未闭合'; break; // case '[': $expected = '数组未闭合'; break; // case ':': $expected = '值未完成'; break; // case ',': $expected = '需要下一个元素'; break; // } // $tmp = <<messages[] = array('role' => 'user', 'content' => $tmp); } else { $resultcontent .= $message['content']; $this->messages[] = array('role' => 'user', 'content' => '请继续完成之前的对话'); continue; } } if (!$isjson) { $resultcontent .= $message['content']; $message['finish_reason'] = $finish; $message['content'] = $resultcontent; return $message; } $content = $message['content']; $ind = strpos($content, '```json'); if ($ind !== false) { $ind2 = strpos($content, '```', $ind + 7); if ($ind2 !== false) $content = substr($content, $ind + 7, $ind2 - $ind - 7); } $resultcontent .= $content; $json = json_decode($resultcontent, true); if ($json === null) return 'ERR: json decode error.' . $resultcontent; return $json; } } public function chat($messages, $cb) { $this->messages = $messages; $data = [ 'model' => $this->aicfg['model'], 'messages' => $this->messages, 'stream' => true, ]; if ($this->aicfg['maxtoken'] > 0) $data['max_tokens'] = toint($this->aicfg['maxtoken']); if ($this->fp > -999) $data['frequency_penalty'] = $this->fp; if ($this->pp > -999) $data['presence_penalty'] = $this->pp; if ($this->tt > -999) $data['temperature'] = $this->tt; if ($this->tp > -999) $data['top_p'] = $this->tp; if ($this->debug) { savelogfile('openai', $this->aicfg['baseurl']); savelogfile('openai', json_encode($data, JSON_UNESCAPED_UNICODE)); } $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $this->aicfg['baseurl'] . '/chat/completions'); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data)); curl_setopt($ch, CURLOPT_HTTPHEADER, [ 'Content-Type: application/json', 'Authorization: Bearer ' . $this->aicfg['aikey'] ]); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0); curl_setopt($ch, CURLOPT_WRITEFUNCTION, function ($ch, $data) use ($cb) { if ($this->debug) { savelogfile('openai', substr($data, 6, -2)); } $cb($data); return strlen($data); }); curl_exec($ch); if (curl_errno($ch)) return '错误: ' . curl_error($ch); curl_close($ch); return true; } function fixjson($jsonstr) { $fixed = $jsonstr; $inString = false; $escaped = false; for ($i = 0; $i < strlen($fixed); $i++) { $char = $fixed[$i]; if (!$escaped && $char === '"') { $inString = !$inString; } $escaped = ($char === '\\' && !$escaped); } if ($inString) { $fixed .= '"'; } $stack = []; $inString = false; $escaped = false; for ($i = 0; $i < strlen($fixed); $i++) { $char = $fixed[$i]; if (!$inString) { if ($char === '{') { array_push($stack, '}'); } elseif ($char === '[') { array_push($stack, ']'); } elseif ($char === '}' || $char === ']') { array_pop($stack); } } if (!$escaped && $char === '"') { $inString = !$inString; } $escaped = ($char === '\\' && !$escaped); } $fixed .= implode('', array_reverse($stack)); $fixed = preg_replace('/,\s*([\]}])/m', '$1', $fixed); $fixed = preg_replace_callback( '/([{,]\s*)([a-zA-Z_][a-zA-Z0-9_]*)(\s*:)/', function ($matches) { return $matches[1] . '"' . $matches[2] . '"' . $matches[3]; }, $fixed ); return $fixed; } }