Merge branch 'ai-next/ernie' into 18.x

This commit is contained in:
liwenrui
2023-10-10 11:30:51 +08:00
10 changed files with 386 additions and 149 deletions
+36 -9
View File
@@ -2,10 +2,14 @@
$config->ai->vendorList = array();
$config->ai->vendorList['openai']['requiredFields'] = array('key');
$config->ai->vendorList['azure']['requiredFields'] = array('key', 'resource', 'deployment');
$config->ai->vendorList['baidu']['requiredFields'] = array('key', 'secret');
$config->ai->models = array('openai-gpt35' => 'openai', 'baidu-ernie' => 'ernie');
/* OpenAI GPT configurations. */
$config->ai->openai = new stdclass();
$config->ai->openai->api = new stdclass();
$config->ai->openai->api->vendor = array('openai', 'azure');
$config->ai->openai->api->vendor = array('openai', 'azure');
$config->ai->openai->api->openai = new stdclass();
$config->ai->openai->api->openai->version = 'v1'; // OpenAI API version, required.
$config->ai->openai->api->openai->format = 'https://api.openai.com/%s/%s'; // OpenAI API format, args: API version, API name.
@@ -16,35 +20,58 @@ $config->ai->openai->api->azure->deployment = 'the-first-gpt'; /
$config->ai->openai->api->azure->apiVersion = '2023-07-01-preview'; // Azure OpenAI API version, required.
$config->ai->openai->api->azure->format = 'https://%s.openai.azure.com/openai/deployments/%s/%s?api-version=%s'; // Azure API format, args: resource name, deployment name, API name, API version.
$config->ai->openai->api->azure->authFormat = 'api-key: %s'; // Azure API auth header format.
$config->ai->openai->api->methods = array('function' => 'chat/completions', 'chat' => 'chat/completions', 'completion' => 'completions', 'edit' => 'edits');
$config->ai->openai->api->methods = array('function' => 'chat/completions', 'chat' => 'chat/completions', 'completion' => 'completions');
$config->ai->openai->params = new stdclass();
$config->ai->openai->params->chat = new stdclass();
$config->ai->openai->params->function = new stdclass();
$config->ai->openai->params->completion = new stdclass();
$config->ai->openai->params->edit = new stdclass();
$config->ai->openai->params->chat->required = array('messages');
$config->ai->openai->params->chat->optional = array('max_tokens', 'temperature', 'top_p', 'n', 'stream', 'stop', 'presence_penalty', 'frequency_penalty', 'logit_bias', 'user');
$config->ai->openai->params->function->required = array('messages', 'functions', 'function_call');
$config->ai->openai->params->function->optional = array('max_tokens', 'temperature', 'top_p', 'n', 'stream', 'stop', 'presence_penalty', 'frequency_penalty', 'logit_bias', 'user');
$config->ai->openai->params->completion->required = array('prompt', 'max_tokens');
$config->ai->openai->params->completion->optional = array('suffix', 'temperature', 'top_p', 'n', 'stream', 'logprobs', 'echo', 'stop', 'presence_penalty', 'frequency_penalty', 'best_of', 'logit_bias', 'user');
$config->ai->openai->params->edit->required = array('input', 'instruction');
$config->ai->openai->params->edit->optional = array('temperature', 'top_p', 'n');
$config->ai->openai->model = new stdclass();
$config->ai->openai->model->chat = 'gpt-3.5-turbo';
$config->ai->openai->model->function = 'gpt-3.5-turbo-0613';
$config->ai->openai->model->completion = 'text-davinci-003';
$config->ai->openai->model->edit = 'text-davinci-edit-001';
$config->ai->openai->model->function = 'gpt-3.5-turbo';
$config->ai->openai->model->completion = 'gpt-3.5-turbo-instruct';
$config->ai->openai->contentTypeMapping = array('Content-Type: application/json' => array('', 'function', 'chat', 'completion', 'edit'), 'Content-Type: multipart/form-data' => array());
$config->ai->openai->contentTypeMapping = array('Content-Type: application/json' => array('', 'function', 'chat', 'completion'), 'Content-Type: multipart/form-data' => array());
$config->ai->openai->contentType = array();
foreach($config->ai->openai->contentTypeMapping as $contentType => $apis)
{
foreach($apis as $api) $config->ai->openai->contentType[$api] = $contentType;
}
/* Baidu ERNIE configurations. */
$config->ai->ernie = new stdclass();
$config->ai->ernie->api = new stdclass();
$config->ai->ernie->api->vendor = array('baidu');
$config->ai->ernie->api->baidu = new stdclass();
$config->ai->ernie->api->baidu->format = 'https://aip.baidubce.com/rpc/2.0/ai_custom/v1/wenxinworkshop/chat/completions?access_token=%s'; // ERNIE API format, arg: access_token (obtained from bce oauth).
$config->ai->ernie->api->baidu->auth = 'https://aip.baidubce.com/oauth/2.0/token?grant_type=client_credentials&client_id=%s&client_secret=%s'; // BCE auth URL format, args: client_id, client_secret.
$config->ai->ernie->params = new stdclass();
$config->ai->ernie->params->chat = new stdclass();
$config->ai->ernie->params->function = new stdclass();
$config->ai->ernie->params->chat->required = array('messages');
$config->ai->ernie->params->chat->optional = array('temperature', 'top_p', 'penalty_score', 'stream', 'user_id');
$config->ai->ernie->params->function->required = array('messages', 'functions');
$config->ai->ernie->params->function->optional = array('temperature', 'top_p', 'penalty_score', 'stream', 'user_id');
$config->ai->ernie->model = new stdclass();
$config->ai->ernie->model->chat = 'ernie-bot-turbo';
$config->ai->ernie->model->function = 'ernie-bot-turbo';
$config->ai->ernie->contentTypeMapping = array('Content-Type: application/json' => array('', 'function', 'chat'), 'Content-Type: multipart/form-data' => array());
$config->ai->ernie->contentType = array();
foreach($config->ai->ernie->contentTypeMapping as $contentType => $apis)
{
foreach($apis as $api) $config->ai->ernie->contentType[$api] = $contentType;
}
/* Required fields of forms. */
$config->ai->createprompt = new stdclass();
$config->ai->testPrompt = new stdclass();
+22 -8
View File
@@ -81,7 +81,7 @@ class ai extends control
{
$modelConfig = fixer::input('post')->get();
$currentVendor = empty($modelConfig->vendor) ? key($lang->ai->models->openaiVendorList) : $modelConfig->vendor;
$currentVendor = empty($modelConfig->vendor) ? key($this->lang->ai->models->vendorList->{empty($modelConfig->type) ? key($this->lang->ai->models->typeList) : $modelConfig->type}) : $modelConfig->vendor;
$vendorRequiredFields = $this->config->ai->vendorList[$currentVendor]['requiredFields'];
$errors = array();
@@ -128,7 +128,7 @@ class ai extends control
{
$modelConfig = fixer::input('post')->get();
$currentVendor = empty($modelConfig->vendor) ? key($lang->ai->models->openaiVendorList) : $modelConfig->vendor;
$currentVendor = empty($modelConfig->vendor) ? key($this->lang->ai->models->vendorList->{empty($modelConfig->type) ? key($this->lang->ai->models->typeList) : $modelConfig->type}) : $modelConfig->vendor;
$vendorRequiredFields = $this->config->ai->vendorList[$currentVendor]['requiredFields'];
$errors = array();
@@ -145,12 +145,10 @@ class ai extends control
$this->ai->setConfig($modelConfig);
if($currentVendor == 'azure')
if($this->config->ai->models[$modelConfig->type] == 'ernie' || $currentVendor == 'azure')
{
$messages = array(
(object)array('role' => 'user', 'content' => 'Hello?')
);
$result = $this->ai->converse($messages);
$messages = array((object)array('role' => 'user', 'content' => 'test'));
$result = $this->ai->converse($messages, array('maxTokens' => 1));
}
else
{
@@ -520,7 +518,23 @@ class ai extends control
if(empty($location)) return $this->send(array('result' => 'fail', 'message' => sprintf($this->lang->ai->execute->failFormat, $this->lang->ai->execute->failReasons['noTargetForm'])));
if(!empty($stop)) return header("location: $location", true, 302);
$response = $this->ai->executePrompt($prompt, $object);
/* Execute prompt and catch exceptions. */
try
{
$response = $this->ai->executePrompt($prompt, $object);
}
catch(AIResponseException $e)
{
$output = array('result' => 'fail', 'message' => sprintf($this->lang->ai->execute->failFormat, $e->getMessage()));
/* Audition shall quit on such exception. */
if(isset($_SESSION['auditPrompt']) && time() - $_SESSION['auditPrompt']['time'] < 10 * 60)
{
$output['locate'] = $this->inlink('promptAudit', "promptID=$promptId&objectId=$objectId&exit=true");
}
return $this->send($output);
}
if(is_int($response)) return $this->send(array('result' => 'fail', 'message' => sprintf($this->lang->ai->execute->failFormat, $this->lang->ai->execute->executeErrors["$response"]) . (empty($this->ai->errors) ? '' : implode(', ', $this->ai->errors))));
if(empty($response)) return $this->send(array('result' => 'fail', 'message' => sprintf($this->lang->ai->execute->failFormat, $this->lang->ai->execute->failReasons['noResponse'])));
+30 -23
View File
@@ -59,25 +59,26 @@ $lang->ai->validate->dirtyForm = 'The design step of %s has changed. Do you
$lang->ai->validate->nameNotUnique = 'A prompt with the same name already exists, please change the name.';
$lang->ai->prompts = new stdclass();
$lang->ai->prompts->common = 'Prompt';
$lang->ai->prompts->emptyList = 'No prompts yet.';
$lang->ai->prompts->create = 'Create Prompt';
$lang->ai->prompts->edit = 'Edit Prompt';
$lang->ai->prompts->id = 'ID';
$lang->ai->prompts->name = 'Name';
$lang->ai->prompts->description = 'Description';
$lang->ai->prompts->createdBy = 'Creator';
$lang->ai->prompts->createdDate = 'Created Date';
$lang->ai->prompts->targetForm = 'Target Form';
$lang->ai->prompts->funcDesc = 'Function Description';
$lang->ai->prompts->deleted = 'Deleted';
$lang->ai->prompts->stage = 'Stage';
$lang->ai->prompts->basicInfo = 'Basic Info';
$lang->ai->prompts->editInfo = 'Edit Info';
$lang->ai->prompts->createdBy = 'Created By';
$lang->ai->prompts->publishedBy = 'Published By';
$lang->ai->prompts->draftedBy = 'Drafted By';
$lang->ai->prompts->lastEditor = 'Last Editor';
$lang->ai->prompts->common = 'Prompt';
$lang->ai->prompts->emptyList = 'No prompts yet.';
$lang->ai->prompts->create = 'Create Prompt';
$lang->ai->prompts->edit = 'Edit Prompt';
$lang->ai->prompts->id = 'ID';
$lang->ai->prompts->name = 'Name';
$lang->ai->prompts->description = 'Description';
$lang->ai->prompts->createdBy = 'Creator';
$lang->ai->prompts->createdDate = 'Created Date';
$lang->ai->prompts->targetForm = 'Target Form';
$lang->ai->prompts->funcDesc = 'Function Description';
$lang->ai->prompts->deleted = 'Deleted';
$lang->ai->prompts->stage = 'Stage';
$lang->ai->prompts->basicInfo = 'Basic Info';
$lang->ai->prompts->editInfo = 'Edit Info';
$lang->ai->prompts->createdBy = 'Created By';
$lang->ai->prompts->publishedBy = 'Published By';
$lang->ai->prompts->draftedBy = 'Drafted By';
$lang->ai->prompts->lastEditor = 'Last Editor';
$lang->ai->prompts->modelNeutral = 'Model Neutral';
$lang->ai->prompts->summary = 'There are %s prompts on this page.';
@@ -423,11 +424,11 @@ $lang->ai->models->statusList['off'] = 'Disable';
$lang->ai->models->typeList = array();
$lang->ai->models->typeList['openai-gpt35'] = 'OpenAI / GPT-3.5';
// $lang->ai->models->typeList['azure-gpt35'] = 'Azure / GPT-3.5';
$lang->ai->models->typeList['baidu-ernie'] = 'Baidu / ERNIE';
$lang->ai->models->openaiVendorList = array();
$lang->ai->models->openaiVendorList['openai'] = 'OpenAI';
$lang->ai->models->openaiVendorList['azure'] = 'Azure';
$lang->ai->models->vendorList = new stdclass();
$lang->ai->models->vendorList->{'openai-gpt35'} = array('openai' => 'OpenAI', 'azure' => 'Azure');
$lang->ai->models->vendorList->{'baidu-ernie'} = array('baidu' => 'Baidu Qianfan LLM Platform');
$lang->ai->models->proxyTypes = array();
$lang->ai->models->proxyTypes[''] = 'No Proxy';
@@ -793,3 +794,9 @@ $lang->ai->audit->exit = 'Exit Audit';
$lang->ai->audit->backLocationList = array();
$lang->ai->audit->backLocationList[0] = 'back to audit page.';
$lang->ai->audit->backLocationList[1] = 'back to audit page and regenerate.';
$lang->ai->engineeredPrompts = new stdclass();
$lang->ai->engineeredPrompts->askForFunctionCalling = array((object)array('role' => 'user', 'content' => 'Please convert my next message into a function call.'), (object)array('role' => 'assistant', 'content' => 'Sure, I\'ll convert your next message into a function call.'));
$lang->ai->aiResponseException = array();
$lang->ai->aiResponseException['notFunctionCalling'] = 'The response is not a function calling';
+30 -23
View File
@@ -59,25 +59,26 @@ $lang->ai->validate->dirtyForm = 'The design step of %s has changed. Do you
$lang->ai->validate->nameNotUnique = 'A prompt with the same name already exists, please change the name.';
$lang->ai->prompts = new stdclass();
$lang->ai->prompts->common = 'Prompt';
$lang->ai->prompts->emptyList = 'No prompts yet.';
$lang->ai->prompts->create = 'Create Prompt';
$lang->ai->prompts->edit = 'Edit Prompt';
$lang->ai->prompts->id = 'ID';
$lang->ai->prompts->name = 'Name';
$lang->ai->prompts->description = 'Description';
$lang->ai->prompts->createdBy = 'Creator';
$lang->ai->prompts->createdDate = 'Created Date';
$lang->ai->prompts->targetForm = 'Target Form';
$lang->ai->prompts->funcDesc = 'Function Description';
$lang->ai->prompts->deleted = 'Deleted';
$lang->ai->prompts->stage = 'Stage';
$lang->ai->prompts->basicInfo = 'Basic Info';
$lang->ai->prompts->editInfo = 'Edit Info';
$lang->ai->prompts->createdBy = 'Created By';
$lang->ai->prompts->publishedBy = 'Published By';
$lang->ai->prompts->draftedBy = 'Drafted By';
$lang->ai->prompts->lastEditor = 'Last Editor';
$lang->ai->prompts->common = 'Prompt';
$lang->ai->prompts->emptyList = 'No prompts yet.';
$lang->ai->prompts->create = 'Create Prompt';
$lang->ai->prompts->edit = 'Edit Prompt';
$lang->ai->prompts->id = 'ID';
$lang->ai->prompts->name = 'Name';
$lang->ai->prompts->description = 'Description';
$lang->ai->prompts->createdBy = 'Creator';
$lang->ai->prompts->createdDate = 'Created Date';
$lang->ai->prompts->targetForm = 'Target Form';
$lang->ai->prompts->funcDesc = 'Function Description';
$lang->ai->prompts->deleted = 'Deleted';
$lang->ai->prompts->stage = 'Stage';
$lang->ai->prompts->basicInfo = 'Basic Info';
$lang->ai->prompts->editInfo = 'Edit Info';
$lang->ai->prompts->createdBy = 'Created By';
$lang->ai->prompts->publishedBy = 'Published By';
$lang->ai->prompts->draftedBy = 'Drafted By';
$lang->ai->prompts->lastEditor = 'Last Editor';
$lang->ai->prompts->modelNeutral = 'Model Neutral';
$lang->ai->prompts->summary = 'There are %s prompts on this page.';
@@ -423,11 +424,11 @@ $lang->ai->models->statusList['off'] = 'Disable';
$lang->ai->models->typeList = array();
$lang->ai->models->typeList['openai-gpt35'] = 'OpenAI / GPT-3.5';
// $lang->ai->models->typeList['azure-gpt35'] = 'Azure / GPT-3.5';
$lang->ai->models->typeList['baidu-ernie'] = 'Baidu / ERNIE';
$lang->ai->models->openaiVendorList = array();
$lang->ai->models->openaiVendorList['openai'] = 'OpenAI';
$lang->ai->models->openaiVendorList['azure'] = 'Azure';
$lang->ai->models->vendorList = new stdclass();
$lang->ai->models->vendorList->{'openai-gpt35'} = array('openai' => 'OpenAI', 'azure' => 'Azure');
$lang->ai->models->vendorList->{'baidu-ernie'} = array('baidu' => 'Baidu Qianfan LLM Platform');
$lang->ai->models->proxyTypes = array();
$lang->ai->models->proxyTypes[''] = 'No Proxy';
@@ -793,3 +794,9 @@ $lang->ai->audit->exit = 'Exit Audit';
$lang->ai->audit->backLocationList = array();
$lang->ai->audit->backLocationList[0] = 'back to audit page.';
$lang->ai->audit->backLocationList[1] = 'back to audit page and regenerate.';
$lang->ai->engineeredPrompts = new stdclass();
$lang->ai->engineeredPrompts->askForFunctionCalling = array((object)array('role' => 'user', 'content' => 'Please convert my next message into a function call.'), (object)array('role' => 'assistant', 'content' => 'Sure, I\'ll convert your next message into a function call.'));
$lang->ai->aiResponseException = array();
$lang->ai->aiResponseException['notFunctionCalling'] = 'The response is not a function calling';
+30 -23
View File
@@ -59,25 +59,26 @@ $lang->ai->validate->dirtyForm = 'The design step of %s has changed. Do you
$lang->ai->validate->nameNotUnique = 'A prompt with the same name already exists, please change the name.';
$lang->ai->prompts = new stdclass();
$lang->ai->prompts->common = 'Prompt';
$lang->ai->prompts->emptyList = 'No prompts yet.';
$lang->ai->prompts->create = 'Create Prompt';
$lang->ai->prompts->edit = 'Edit Prompt';
$lang->ai->prompts->id = 'ID';
$lang->ai->prompts->name = 'Name';
$lang->ai->prompts->description = 'Description';
$lang->ai->prompts->createdBy = 'Creator';
$lang->ai->prompts->createdDate = 'Created Date';
$lang->ai->prompts->targetForm = 'Target Form';
$lang->ai->prompts->funcDesc = 'Function Description';
$lang->ai->prompts->deleted = 'Deleted';
$lang->ai->prompts->stage = 'Stage';
$lang->ai->prompts->basicInfo = 'Basic Info';
$lang->ai->prompts->editInfo = 'Edit Info';
$lang->ai->prompts->createdBy = 'Created By';
$lang->ai->prompts->publishedBy = 'Published By';
$lang->ai->prompts->draftedBy = 'Drafted By';
$lang->ai->prompts->lastEditor = 'Last Editor';
$lang->ai->prompts->common = 'Prompt';
$lang->ai->prompts->emptyList = 'No prompts yet.';
$lang->ai->prompts->create = 'Create Prompt';
$lang->ai->prompts->edit = 'Edit Prompt';
$lang->ai->prompts->id = 'ID';
$lang->ai->prompts->name = 'Name';
$lang->ai->prompts->description = 'Description';
$lang->ai->prompts->createdBy = 'Creator';
$lang->ai->prompts->createdDate = 'Created Date';
$lang->ai->prompts->targetForm = 'Target Form';
$lang->ai->prompts->funcDesc = 'Function Description';
$lang->ai->prompts->deleted = 'Deleted';
$lang->ai->prompts->stage = 'Stage';
$lang->ai->prompts->basicInfo = 'Basic Info';
$lang->ai->prompts->editInfo = 'Edit Info';
$lang->ai->prompts->createdBy = 'Created By';
$lang->ai->prompts->publishedBy = 'Published By';
$lang->ai->prompts->draftedBy = 'Drafted By';
$lang->ai->prompts->lastEditor = 'Last Editor';
$lang->ai->prompts->modelNeutral = 'Model Neutral';
$lang->ai->prompts->summary = 'There are %s prompts on this page.';
@@ -423,11 +424,11 @@ $lang->ai->models->statusList['off'] = 'Disable';
$lang->ai->models->typeList = array();
$lang->ai->models->typeList['openai-gpt35'] = 'OpenAI / GPT-3.5';
// $lang->ai->models->typeList['azure-gpt35'] = 'Azure / GPT-3.5';
$lang->ai->models->typeList['baidu-ernie'] = 'Baidu / ERNIE';
$lang->ai->models->openaiVendorList = array();
$lang->ai->models->openaiVendorList['openai'] = 'OpenAI';
$lang->ai->models->openaiVendorList['azure'] = 'Azure';
$lang->ai->models->vendorList = new stdclass();
$lang->ai->models->vendorList->{'openai-gpt35'} = array('openai' => 'OpenAI', 'azure' => 'Azure');
$lang->ai->models->vendorList->{'baidu-ernie'} = array('baidu' => 'Baidu Qianfan LLM Platform');
$lang->ai->models->proxyTypes = array();
$lang->ai->models->proxyTypes[''] = 'No Proxy';
@@ -793,3 +794,9 @@ $lang->ai->audit->exit = 'Exit Audit';
$lang->ai->audit->backLocationList = array();
$lang->ai->audit->backLocationList[0] = 'back to audit page.';
$lang->ai->audit->backLocationList[1] = 'back to audit page and regenerate.';
$lang->ai->engineeredPrompts = new stdclass();
$lang->ai->engineeredPrompts->askForFunctionCalling = array((object)array('role' => 'user', 'content' => 'Please convert my next message into a function call.'), (object)array('role' => 'assistant', 'content' => 'Sure, I\'ll convert your next message into a function call.'));
$lang->ai->aiResponseException = array();
$lang->ai->aiResponseException['notFunctionCalling'] = 'The response is not a function calling';
+31 -23
View File
@@ -59,25 +59,26 @@ $lang->ai->validate->dirtyForm = '%s的参数配置已变动,是否保存
$lang->ai->validate->nameNotUnique = '该名称已使用,请尝试其他名称。';
$lang->ai->prompts = new stdclass();
$lang->ai->prompts->common = '提词';
$lang->ai->prompts->emptyList = '暂时没有提词。';
$lang->ai->prompts->create = '创建提词';
$lang->ai->prompts->edit = '编辑提词';
$lang->ai->prompts->id = 'ID';
$lang->ai->prompts->name = '名称';
$lang->ai->prompts->description = '描述';
$lang->ai->prompts->createdBy = '创建者';
$lang->ai->prompts->createdDate = '创建时间';
$lang->ai->prompts->targetForm = '表单';
$lang->ai->prompts->funcDesc = '功能描述';
$lang->ai->prompts->deleted = '已删除';
$lang->ai->prompts->stage = '阶段';
$lang->ai->prompts->basicInfo = '基本信息';
$lang->ai->prompts->editInfo = '创建编辑';
$lang->ai->prompts->createdBy = '由谁创建';
$lang->ai->prompts->publishedBy = '由谁发布';
$lang->ai->prompts->draftedBy = '由谁下架';
$lang->ai->prompts->lastEditor = '最后编辑';
$lang->ai->prompts->common = '提词';
$lang->ai->prompts->emptyList = '暂时没有提词。';
$lang->ai->prompts->create = '创建提词';
$lang->ai->prompts->edit = '编辑提词';
$lang->ai->prompts->id = 'ID';
$lang->ai->prompts->name = '名称';
$lang->ai->prompts->description = '描述';
$lang->ai->prompts->createdBy = '创建者';
$lang->ai->prompts->createdDate = '创建时间';
$lang->ai->prompts->targetForm = '表单';
$lang->ai->prompts->funcDesc = '功能描述';
$lang->ai->prompts->deleted = '已删除';
$lang->ai->prompts->stage = '阶段';
$lang->ai->prompts->basicInfo = '基本信息';
$lang->ai->prompts->editInfo = '创建编辑';
$lang->ai->prompts->createdBy = '由谁创建';
$lang->ai->prompts->publishedBy = '由谁发布';
$lang->ai->prompts->draftedBy = '由谁下架';
$lang->ai->prompts->lastEditor = '最后编辑';
$lang->ai->prompts->modelNeutral = '通用';
$lang->ai->prompts->summary = '本页共 %s 个提词。';
@@ -400,6 +401,7 @@ $lang->ai->models->common = '语言模型';
$lang->ai->models->type = '语言模型';
$lang->ai->models->vendor = '供应商';
$lang->ai->models->key = 'API Key';
$lang->ai->models->secret = 'Secret Key';
$lang->ai->models->resource = 'Resource';
$lang->ai->models->deployment = 'Deployment';
$lang->ai->models->proxyType = '代理类型';
@@ -423,11 +425,11 @@ $lang->ai->models->statusList['off'] = '停用';
$lang->ai->models->typeList = array();
$lang->ai->models->typeList['openai-gpt35'] = 'OpenAI / GPT-3.5';
// $lang->ai->models->typeList['azure-gpt35'] = 'Azure / GPT-3.5';
$lang->ai->models->typeList['baidu-ernie'] = '百度 / 文心一言';
$lang->ai->models->openaiVendorList = array();
$lang->ai->models->openaiVendorList['openai'] = 'OpenAI';
$lang->ai->models->openaiVendorList['azure'] = 'Azure';
$lang->ai->models->vendorList = new stdclass();
$lang->ai->models->vendorList->{'openai-gpt35'} = array('openai' => 'OpenAI', 'azure' => 'Azure');
$lang->ai->models->vendorList->{'baidu-ernie'} = array('baidu' => '百度千帆大模型平台');
$lang->ai->models->proxyTypes = array();
$lang->ai->models->proxyTypes[''] = '不使用代理';
@@ -793,3 +795,9 @@ $lang->ai->audit->exit = '退出调试';
$lang->ai->audit->backLocationList = array();
$lang->ai->audit->backLocationList[0] = '返回调试页面';
$lang->ai->audit->backLocationList[1] = '返回调试页面并重新生成';
$lang->ai->engineeredPrompts = new stdclass();
$lang->ai->engineeredPrompts->askForFunctionCalling = array((object)array('role' => 'user', 'content' => '请把我所发的下一条消息内容转换为 function 调用。'), (object)array('role' => 'assistant', 'content' => '好的,我会把下一条消息转换为 function 调用。'));
$lang->ai->aiResponseException = array();
$lang->ai->aiResponseException['notFunctionCalling'] = 'AI 提词执行返回值结构不正确,请重试(可能可以通过优化提词来解决)';
+172 -25
View File
@@ -79,22 +79,61 @@ class aiModel extends model
*/
private function makeRequest($type, $data, $timeout = 10)
{
$modelType = $this->config->ai->models[$this->modelConfig->type];
$modelVendor = $this->modelConfig->vendor;
/* Try encoding data to json, handles both encoded json and raw data. */
$postData = json_encode($data);
if(json_last_error()) $postData = $data;
/* Set auth and content-type headers. */
$requestHeaders = array(sprintf($this->config->ai->openai->api->{$this->modelConfig->vendor}->authFormat, $this->modelConfig->key));
$requestHeaders[] = isset($this->config->ai->openai->contentType[$type]) ? $this->config->ai->openai->contentType[$type] : $this->config->ai->openai->contentType[''];
$requestHeaders = array();
if(isset($this->config->ai->$modelType->api->$modelVendor->authFormat)) $requestHeaders[] = sprintf($this->config->ai->$modelType->api->$modelVendor->authFormat, $this->modelConfig->key);
$requestHeaders[] = isset($this->config->ai->$modelType->contentType[$type]) ? $this->config->ai->$modelType->contentType[$type] : $this->config->ai->$modelType->contentType[''];
/* Assemble request url. */
if($this->modelConfig->vendor == 'azure')
if($modelType == 'openai')
{
$url = sprintf($this->config->ai->openai->api->azure->format, $this->config->ai->openai->api->azure->resource, $this->config->ai->openai->api->azure->deployment, $this->config->ai->openai->api->methods[$type], $this->config->ai->openai->api->azure->apiVersion);
if($modelVendor == 'azure')
{
$url = sprintf($this->config->ai->openai->api->azure->format, $this->config->ai->openai->api->azure->resource, $this->config->ai->openai->api->azure->deployment, $this->config->ai->openai->api->methods[$type], $this->config->ai->openai->api->azure->apiVersion);
}
else
{
$url = sprintf($this->config->ai->openai->api->openai->format, $this->config->ai->openai->api->openai->version, $this->config->ai->openai->api->methods[$type]);
}
}
else
elseif($modelType == 'ernie')
{
$url = sprintf($this->config->ai->openai->api->openai->format, $this->config->ai->openai->api->openai->version, $this->config->ai->openai->api->methods[$type]);
$clientID = $this->modelConfig->key;
$clientSecret = $this->modelConfig->secret;
$authURL = sprintf($this->config->ai->ernie->api->$modelVendor->auth, $clientID, $clientSecret);
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $authURL);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5);
$result = curl_exec($ch);
if(!$result || curl_errno($ch))
{
$response = new stdclass();
$response->result = 'fail';
$response->message = curl_error($ch);
curl_close($ch);
return $response;
}
$result = json_decode($result);
if(json_last_error())
{
$response = new stdclass();
$response->result = 'fail';
$response->message = 'JSON decode error: ' . json_last_error_msg();
curl_close($ch);
return $response;
}
$accessToken = $result->access_token;
$url = sprintf($this->config->ai->ernie->api->$modelVendor->format, $accessToken);
}
/* Set up requestor. */
@@ -197,20 +236,22 @@ class aiModel extends model
*/
private function assembleRequestData($type, $data)
{
$modelType = $this->config->ai->models[$this->modelConfig->type];
$postData = new stdclass();
$postData->model = $this->config->ai->openai->model->$type;
$postData->model = $this->config->ai->$modelType->model->$type;
$data = static::standardizeParams($data);
/* Set required params, abort if missing. */
foreach($this->config->ai->openai->params->$type->required as $param)
foreach($this->config->ai->$modelType->params->$type->required as $param)
{
if(!isset($data->$param)) return false;
$postData->$param = $data->$param;
}
/* Set optional params. */
foreach($this->config->ai->openai->params->$type->optional as $param)
foreach($this->config->ai->$modelType->params->$type->optional as $param)
{
if(isset($data->$param)) $postData->$param = $data->$param;
}
@@ -293,12 +334,24 @@ class aiModel extends model
$response = $this->decodeResponse($response);
if(empty($response)) return false;
/* Extract chat message choices. */
if(isset($response->choices) && count($response->choices) > 0)
if($this->config->ai->models[$this->modelConfig->type] == 'ernie')
{
$messages = array();
foreach($response->choices as $choice) $messages[] = $choice->message->content;
return $messages;
/* Extract chat message text. */
if(!empty($response->result))
{
$messages = array($response->result);
return $messages;
}
}
else
{
/* Extract chat message choices. */
if(isset($response->choices) && count($response->choices) > 0)
{
$messages = array();
foreach($response->choices as $choice) $messages[] = $choice->message->content;
return $messages;
}
}
return false;
}
@@ -315,15 +368,24 @@ class aiModel extends model
$response = $this->decodeResponse($response);
if(empty($response)) return false;
/* Extract function call choices. */
if(isset($response->choices) && count($response->choices) > 0)
if($this->config->ai->models[$this->modelConfig->type] == 'ernie')
{
$arguments = array();
foreach($response->choices as $choice)
/* Extract function call arguments. */
if(!empty($response->function_call)) return array($response->function_call->arguments);
throw new AIResponseException('notFunctionCalling', $response);
}
else
{
/* Extract function call choices. */
if(isset($response->choices) && count($response->choices) > 0)
{
if(!empty($choice->message->function_call)) $arguments[] = $choice->message->function_call->arguments;
$arguments = array();
foreach($response->choices as $choice)
{
if(!empty($choice->message->function_call)) $arguments[] = $choice->message->function_call->arguments;
}
return $arguments;
}
return $arguments;
}
return false;
}
@@ -459,9 +521,55 @@ class aiModel extends model
}
/**
* Get list of prompts.
* Generate conversations with LLM for JSON output, but seperate the conversation into two parts.
*
* TODO: fully implement this.
* Usage is the same as function `converseForJSON`, but this function will generate two conversations:
* one for manipulating the natural language data, and the other for generating the JSON output.
*
* @param array $messages array of chat messages
* @param object $schema schema of the output
* @param array $options optional params, see https://platform.openai.com/docs/api-reference/chat/create
* @access public
* @return mixed false if error, array of JSON object if success
* @throws AIResponseException
*/
public function converseTwiceForJSON($messages, $schema, $options = array())
{
/* First conversation. */
$data = compact('messages');
if(!empty($options))
{
foreach($options as $key => $value) $data[$key] = $value;
}
$postData = $this->assembleRequestData('chat', $data);
if(!$postData) return false;
$chatResponse = $this->makeRequest('chat', $postData);
$chatMessages = $this->parseChatResponse($chatResponse);
$chatMessage = current($chatMessages);
if(empty($chatMessage)) return false;
/* Second conversation for JSON output. */
$messages = array_merge($this->lang->ai->engineeredPrompts->askForFunctionCalling, array((object)array('role' => 'user', 'content' => $chatMessage)));
$functions = array((object)array('name' => 'function', 'description' => 'function', 'parameters' => $schema));
$data = compact('messages', 'functions');
if(!empty($options))
{
foreach($options as $key => $value) $data[$key] = $value;
}
$postData = $this->assembleRequestData('function', $data);
if(!$postData) return false;
$response = $this->makeRequest('function', $postData);
return $this->parseFunctionCallResponse($response);
}
/**
* Get list of prompts.
*
* @param string $module
* @param string $status
@@ -484,8 +592,6 @@ class aiModel extends model
/**
* Get prompt by id.
*
* TODO: fully implement this.
*
* @param int $id
* @access public
* @return object
@@ -929,6 +1035,7 @@ class aiModel extends model
* @param int|object $object object (or id) to execute prompt on.
* @access public
* @return string|int returns either JSON string or negative integer on error.
* @throws AIResponseException
*/
public function executePrompt($prompt, $object)
{
@@ -946,7 +1053,7 @@ class aiModel extends model
$schema = $this->getFunctionCallSchema($prompt->targetForm);
if(empty($schema)) return -4;
$response = $this->converseForJSON(array((object)array('role' => 'user', 'content' => $wholePrompt)), $schema);
$response = $this->{$this->config->ai->models[$this->modelConfig->type] == 'ernie' ? 'converseTwiceForJSON' : 'converseForJSON'}(array((object)array('role' => 'user', 'content' => $wholePrompt)), $schema);
if(empty($response)) return -5;
return current($response);
@@ -1624,3 +1731,43 @@ class aiModel extends model
return true;
}
}
/**
* Exception class for response from AI.
*/
class AIResponseException extends Exception
{
/**
* Type of response error, error messages are defined in lang files with type as key.
*
* See `$lang->ai->aiResponseException`.
*
* @var string
* @access public
*/
public $type;
/**
* Response with error.
*
* @var string|object
* @access public
*/
public $response;
/**
* Create a AIResponseException, load error message from lang file.
*
* @param string $type
* @param string|object $response
* @access public
* @return void
*/
public function __construct($type, $response)
{
global $app;
$this->type = $type;
$this->response = $response;
$this->message = zget($app->lang->ai->aiResponseException, $type, '');
}
}
+26 -12
View File
@@ -11,9 +11,10 @@
?>
<?php include '../../common/view/header.html.php';?>
<?php
$currentVendor = empty($modelConfig->vendor) ? key($lang->ai->models->openaiVendorList) : $modelConfig->vendor;
$currentVendor = empty($modelConfig->vendor) ? key($lang->ai->models->vendorList->{empty($modelConfig->type) ? key($lang->ai->models->typeList) : $modelConfig->type}) : $modelConfig->vendor;
$requiredFields = $config->ai->vendorList[$currentVendor]['requiredFields'];
js::set('vendorList', $config->ai->vendorList);
js::set('vendorListLang', $lang->ai->models->vendorList);
?>
<style>
.required:after {right: -12px;}
@@ -27,12 +28,16 @@ js::set('vendorList', $config->ai->vendorList);
</tr>
<tr>
<th><?php echo $lang->ai->models->vendor;?></th>
<td><?php echo html::select('vendor', $lang->ai->models->openaiVendorList, $currentVendor, "class='form-control chosen' required");?></td>
<td><?php echo html::select('vendor', $lang->ai->models->vendorList->{empty($modelConfig->type) ? key($lang->ai->models->typeList) : $modelConfig->type}, $currentVendor, "class='form-control chosen' required");?></td>
</tr>
<tr class="vendor-row <?php echo in_array('key', $requiredFields) ? '' : ' hidden'; ?>" data-vendor-field="key">
<th><?php echo $lang->ai->models->key;?></th>
<td><?php echo html::input('key', $modelConfig->key, "class='form-control' required");?></td>
</tr>
<tr class="vendor-row <?php echo in_array('secret', $requiredFields) ? '' : ' hidden'; ?>" data-vendor-field="secret">
<th><?php echo $lang->ai->models->secret;?></th>
<td><?php echo html::input('secret', $modelConfig->secret, "class='form-control' required");?></td>
</tr>
<tr class="vendor-row <?php echo in_array('resource', $requiredFields) ? '' : ' hidden'; ?>" data-vendor-field="resource">
<th><?php echo $lang->ai->models->resource;?></th>
<td><?php echo html::input('resource', empty($modelConfig->resource) ? '' : $modelConfig->resource, "class='form-control' required");?></td>
@@ -77,21 +82,30 @@ js::set('vendorList', $config->ai->vendorList);
</div>
<script>
$(function() {
$('select[name="type"]').change(function()
{
var type = $(this).val();
var vendorList = vendorListLang[type];
$('select[name="vendor"]').html('');
for(var vendor in vendorList) $('select[name="vendor"]').append('<option value="' + vendor + '">' + vendorList[vendor] + '</option>');
$('select[name="vendor"]').trigger('chosen:updated');
$('select[name="vendor"]').trigger('change');
});
$('select[name="vendor"]').change(function()
{
var vendor = $(this).val();
var requiredFields = vendorList[vendor]['requiredFields'];
$('.vendor-row').each(function()
{
var name = $(this).data('vendor-field');
$(this).toggleClass('hidden', !requiredFields.includes(name));
});
});
$('select[name="proxyType"]').change(function()
{
var proxyType = $(this).val();
$('#proxyAddrContainer').toggle(proxyType != '');
});
$('select[name="vendor"]').change(function()
{
var vendor = $(this).val();
var requiredFields = vendorList[vendor]['requiredFields'];
$('.vendor-row').each(function()
{
var name = $(this).data('vendor-field');
$(this).toggleClass('hidden', !requiredFields.includes(name));
});
});
$('#mainForm').on('submit', function()
{
$('#testConn').attr('disabled', 'disabled');
+8 -2
View File
@@ -11,7 +11,7 @@
?>
<?php include '../../common/view/header.html.php';?>
<?php
$currentVendor = empty($modelConfig->vendor) ? key($lang->ai->models->openaiVendorList) : $modelConfig->vendor;
$currentVendor = empty($modelConfig->vendor) ? key($lang->ai->models->vendorList->{empty($modelConfig->type) ? key($lang->ai->models->typeList) : $modelConfig->type}) : $modelConfig->vendor;
$requiredFields = $config->ai->vendorList[$currentVendor]['requiredFields'];
?>
<div id='mainContent' class='main-content'>
@@ -22,7 +22,7 @@ $requiredFields = $config->ai->vendorList[$currentVendor]['requiredFields'];
</tr>
<tr>
<th><?php echo $lang->ai->models->vendor;?></th>
<td><?php echo empty($modelConfig->vendor) ? $lang->ai->models->unconfigured : $lang->ai->models->openaiVendorList[$modelConfig->vendor]?></td>
<td><?php echo (empty($modelConfig->vendor) || empty($modelConfig->type)) ? $lang->ai->models->unconfigured : $lang->ai->models->vendorList->{$modelConfig->type}[$modelConfig->vendor]?></td>
</tr>
<?php if(in_array('key', $requiredFields)): ?>
<tr>
@@ -30,6 +30,12 @@ $requiredFields = $config->ai->vendorList[$currentVendor]['requiredFields'];
<td><?php echo empty($modelConfig->key) ? $lang->ai->models->unconfigured : ("<code title='{$lang->ai->models->concealTip}'>" . substr_replace($modelConfig->key, '...', 4, strlen($modelConfig->key) - 8) . '</code>'); ?></td>
</tr>
<?php endif; ?>
<?php if(in_array('secret', $requiredFields)): ?>
<tr>
<th><?php echo $lang->ai->models->secret; ?></th>
<td><?php echo empty($modelConfig->secret) ? $lang->ai->models->unconfigured : ("<code title='{$lang->ai->models->concealTip}'>" . substr_replace($modelConfig->secret, '...', 4, strlen($modelConfig->secret) - 8) . '</code>'); ?></td>
</tr>
<?php endif; ?>
<?php if(in_array('resource', $requiredFields)): ?>
<tr>
<th><?php echo $lang->ai->models->resource; ?></th>
+1 -1
View File
@@ -110,7 +110,7 @@
</tr>
<tr>
<th class="w-90px"><?php echo $lang->prompt->model; ?></th>
<td><?php echo $lang->ai->models->typeList['openai-gpt35']; ?></td>
<td><?php echo $lang->ai->prompts->modelNeutral; ?></td>
</tr>
</tbody>
</table>