对接OPENAI公共方法

This commit is contained in:
chengxl
2025-07-01 16:31:44 +08:00
parent 2864736e49
commit 4df77f0490

View File

@@ -0,0 +1,267 @@
<?php
namespace app\common;
use think\Cache;
use think\Db;
class OpenAi
{
protected $sApiKey = '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';
protected $proxy = '';
protected $sUrl = 'https://api.openai.com/v1/chat/completions';
protected $curl;
protected $sResponesData;
protected $sError;
protected $timeout = 60;
/**
* CURL 发送请求到 OpenAI
* @param $messages 内容
* @param $model 模型类型
*/
public function curlOpenAI($aParam = []){
//询问AI信息
$aMessage = empty($aParam['messages']) ? [] : $aParam['messages'];
if(empty($aMessage)){
return json_encode(['status' => 2,'msg' => 'AI Q&A content not obtained']);
}
//模型版本
$model = empty($aParam['model']) ? 'gpt-4.1' : $aParam['model'];
//接口地址
$sUrl = $this->sUrl;
//组装数据
$data = [
'model' => $model,
'messages' => $aMessage,
'temperature' => 0.2,// 降低随机性0-10为最确定
];
$this->curl = curl_init();
// 通用配置
curl_setopt($this->curl, CURLOPT_URL, $sUrl);
// 设置头信息
curl_setopt($this->curl, CURLOPT_HTTPHEADER, [
'Content-Type: application/json',
'Authorization: Bearer ' . $this->sApiKey
]);
curl_setopt($this->curl, CURLOPT_PROXY,$this->proxy);
curl_setopt($this->curl, CURLOPT_SSL_VERIFYPEER,true);
curl_setopt($this->curl, CURLOPT_SSL_VERIFYHOST,2);
curl_setopt($this->curl, CURLOPT_POST, true); //设置为POST方式
curl_setopt($this->curl, CURLOPT_POSTFIELDS,json_encode($data));
curl_setopt($this->curl, CURLOPT_RETURNTRANSFER, TRUE) ; // 获取数据返回
// curl_setopt($this->curl, CURLOPT_TIMEOUT, $this->timeout);
$result = curl_exec($this->curl);
//请求失败
if (curl_errno($this->curl)){
$this->sError = curl_errno($this->curl);
curl_close($this->curl);
return json_encode(['status' => 3,'OPENAI Error:'.$this->sError]);
}
$aResult = json_decode($result,true);
//处理返回信息
$aData = empty($aResult['choices']) ? [] : $aResult['choices'];
if(empty($aData)){
return json_encode(['status' => 5,'msg' => 'OPENAI returns empty content']);
}
$aData = empty($aData[0]) ? [] : $aData[0];
if(empty($aData)){
return json_encode(array('status' => 6,'msg' => 'OPENAI did not return data'));
}
$aData = empty($aData['message']) ? [] : $aData['message'];
$aData = empty($aData['content']) ? [] : $aData['content'];
if(empty($aData)){
return json_encode(array('status' => 7,'msg' => 'OPENAI did not return data'));
}
//数据转换
$aData = $this->extractAndParse($aData);
$aContent = empty($aData['data']) ? [] : $aData['data'];
$sMsg = empty($aData['msg']) ? 'OPENAI did not return data' : $aData['msg'];
if(empty($aContent)){
return json_encode(array('status' => 8,'msg' => $sMsg));
}
curl_close($this->curl);
return json_encode(['status' => 1,'msg' => 'success','data' => $aContent]);
}
/**
* 构建公微模版-处理提示词【默认】
*/
public function buildDefaultPrompt($aSearch = [])
{
if(empty($aSearch)){
return [];
}
$sSysMessagePrompt = '
你是一位专业的医学学术翻译与分析专家,擅长将复杂的医学研究论文转化为适合微信公众号推送的专业科普内容。请根据提供的医学论文信息,按照以下严格格式生成结构化输出[中文]【必须严格遵循以下 JSON 结构】:
{
"covered": "【列出文章涵盖的学科及研究方法总字数不超过100字学科和方法之间用逗号分隔例如肿瘤学,分子生物学,基因组测序,生物信息学分析】",
"digest": "【学术规范翻译并提炼摘要强调逻辑性、科学术语准确性和表达严谨性采用段落形式总字数不超过500字】",
"research_background": "【提炼研究背景采用连贯的段落形式总字数超过200字】",
"discussion_results": "【针对文章简单总结讨论和结果采用连贯的段落形式总字数超过450字】",
"research_method": "【总结文章的研究方法采用连贯的段落形式总字数超过300字】",
"prospect": "【针对稿件内容进行展望撰写,采用连贯的段落形式】",
"highlights": "【总结归纳亮点至少3点每点用分号分隔】",
"title_chinese": "【将标题翻译成中文:内容需自然流畅、口语化、连贯性、学术性】",
"content": "【将内容翻译成中文,需自然流畅、口语化、连贯性、学术性,保留原文的章节结构和图表编号】"
}';
$sUserPrompt = '标题:{#title_chinese#} 摘要: {#abstract#} 内容: {#content#}';
$sUserPrompt = str_replace(array_keys($aSearch), array_values($aSearch), $sUserPrompt);
return [
['role' => 'system', 'content' => $sSysMessagePrompt], ['role' => 'user', 'content' => $sUserPrompt]
];
}
/**
* 构建公微模版-处理提示词【Review】
*/
public function buildReviewPrompt($aSearch = [])
{
if(empty($aSearch)){
return [];
}
$sSysMessagePrompt = '
你是一位专业的医学学术翻译与传播专家,擅长将复杂的医学研究论文转化为适合微信公众号推送的专业科普内容。请根据提供的医学论文信息,按照以下严格格式生成结构化[中文]输出【必须严格遵循以下 JSON 结构】:
{
"covered": "【列出文章涵盖的学科及研究方法总字数不超过100字学科和方法之间用逗号分隔例如肿瘤学,分子生物学,基因组测序,生物信息学分析】",
"overview": "【按照学术规范翻译并提炼文章概述整体内容应大于1200字其中应包含文章背景不少于400字其他内容提炼更强调逻辑性、科学术语准确性和表达的严谨性注意内容不要有严重重复采用连贯的段落形式】",
"summary": "【针对文章结论生成一个简单总结内容不要和文章概述重复字数150以内】",
"title_chinese": "【将标题翻译成中文,需自然流畅、口语化、连贯性、学术性】",
"content": "【将文章内容翻译成中文,需自然流畅、口语化、连贯性、学术性】"
}';
$sUserPrompt = '标题:{#title_chinese#} 摘要: {#abstract#} 内容: {#content#}';
$sUserPrompt = str_replace(array_keys($aSearch), array_values($aSearch), $sUserPrompt);
return [
['role' => 'system', 'content' => $sSysMessagePrompt], ['role' => 'user', 'content' => $sUserPrompt]
];
}
/**
* 构建AI审稿-处理提示词
*/
public function buildReviewArticlePrompt($aSearch = [])
{
if(empty($aSearch)){
return [];
}
$sSysMessagePrompt = '
你是一位资深的学术评审专家,负责严谨、客观地评估学术文章。请根据提供的文章摘要、关键词及文章内容,对以下问题进行专业评审
```json
{
"journal_scope": "文章是否符合目标期刊的范围?",
"attribute": "文章是否具有科学前沿性和创新性?",
"contradiction": "文章内容是否前后矛盾或存在逻辑不一致的问题?",
"unreasonable": "文章是否有明显的不合理之处?",
"ethics": "文章是否存在伦理号缺失或明显伦理问题?",
"academic": "是否存在学术不端问题【抄袭、数据作假、图片伪造等】?",
"conclusion": "结论的科学性和可靠性如何?",
"fund_number":"文章有无基金号?请详细说明",
"hotspot":"文章热点",
"submit_direction":"送审方向",
"references_past_three":"近三年参考文献的比例?",
"references_past_five":"近五年参考文献的比例",
"references_ratio_JCR1":"参考文献JCR 1区的比例",
"references_ratio_JCR2":"参考文献JCR 2区的比例",
"registration_assessment":"临床注册号和知情同意书评估",
"cite_rate":"结合以上数据结果分析,文章发表后被引用的概率",
"overall_evaluation":"发表建议"
}请针对每个问题提供客观、专业的评估,并给出简要的理由。请返回中文解释!返回格式必须严格遵循以下 JSON 结构:{
"journal_scope": {
"assessment": "是/否/部分符合",
"explanation": "请详细说明理由并指出具体相符或不相符的内容"
},
"attribute": {
"assessment": "高/中/低",
"explanation": "请具体指出创新点并评估其在该领域的贡献"
},
"contradiction": {
"assessment": "是/否",
"explanation": "请引用具体段落说明矛盾之处"
},
"unreasonable": {
"assessment": "是/否",
"explanation": "包括实验设计、数据分析、结论推导等方面的问题"
},
"ethics": {
"assessment": "是/否/不适用",
"explanation": "详细解释"
},
"academic": {
"assessment": "是/否/无法确定",
"explanation": "请进行相似度分析并指出具体可疑部分"
},
"conclusion": {
"assessment": "强/中/弱",
"explanation": "是否基于充分证据得出"
},
"fund_number": "",
"hotspot": "",
"submit_direction": "",
"references_past_three": "",
"references_past_five": "",
"references_ratio_JCR1": "",
"references_ratio_JCR2": "",
"registration_assessment":"",
"cite_rate":"",
"overall_evaluation": "",
}';
$sUserPrompt = '关键词: {keywords}摘要: {abstrart} 内容: {content} 目标期刊:{journal_name}';
$sUserPrompt = str_replace(array_keys($aSearch), array_values($aSearch), $sUserPrompt);
return [
['role' => 'system', 'content' => $sSysMessagePrompt], ['role' => 'user', 'content' => $sUserPrompt]
];
}
/**
* 构建AI审稿-处理提示词
*/
public function buildTranslatePrompt($aSearch = [])
{
if(empty($aSearch)){
return [];
}
$sSysMessagePrompt = '你是一位专业的医学翻译专家,请将用户提供的内容准确、流畅地翻译成中文。翻译需自然流畅、口语化、连贯性、学术性,保留原文的专业术语和逻辑结构';
$sUserPrompt = '"将以下内容翻译为中文,仅返回翻译结果,不要解释:\n {#content#}"';
$sUserPrompt = str_replace(array_keys($aSearch), array_values($aSearch), $sUserPrompt);
return [
['role' => 'user', 'content' => $sUserPrompt]
];
}
/**
* 从文本中提取被```json```和```包裹的JSON内容并解析
* @param string $text 包含JSON代码块的文本
* @param bool $assoc 是否返回关联数组默认true
* @return array|object 解析后的JSON数据失败时返回null
*/
private function extractAndParse($text, $assoc = true)
{
// 使用正则表达式提取JSON代码块
preg_match('/```json\s*(\{.*?\})\s*```/s', $text, $matches);
$jsonContent = empty($matches[1]) ? '' : $matches[1];
if (empty($jsonContent)) {
// 尝试宽松匹配允许没有json标记
preg_match('/```\s*(\{.*?\})\s*```/s', $text, $matches);
$jsonContent = empty($matches[1]) ? $text : $matches[1];
}
// 解析JSON
$aData = json_decode($jsonContent, $assoc);
// 检查解析是否成功
if (json_last_error() !== JSON_ERROR_NONE) {
return ['status' => 2,'msg' => "API返回无效JSON: " . json_last_error_msg()];
}
return ['status' => 1,'msg' => 'success','data' => $aData];
}
}