书接上文,如何在PHP中对接openAI接口
- PHP调用OpenAI API的方法
- js文件
PHP调用OpenAI API的方法
此处使用的框架是 symfony ,可自行根据自己框架开发,大同小异,框架无所谓,主要是功能!
先上代码:
<?php
namespace LdWxappPlugin\Api\Resource\Chatapi;
use ApiBundle\Api\ApiRequest;
use ApiBundle\Api\Resource\AbstractResource;
use ApiBundle\Api\Annotation\ApiConf;
use AppBundle\Common\ArrayToolkit;
use Symfony\Component\HttpFoundation\StreamedResponse;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
class ChatapiConversation extends AbstractResource
{protected $host = 'http://*.*.*.*'; // 服务器地址protected $uri = '/v1/chat/completions'; // 调用的接口地址protected $authKey = 'Basic c2VjcmV********'; // api鉴权的keyprotected $message = '';protected $sources = [];protected $conversationId = ''; protected $currentMessage='';/*** @var array|mixed*/private $document;/*** @param ApiRequest $request* @param $conversationId 会话id*/// 前端输入完信息,发送时调用的请求public function add(ApiRequest $request,$conversationId){$this->verifySend();$this->conversationId = $conversationId;//刷新缓冲区ob_implicit_flush(true);ob_end_flush();$response = new StreamedResponse();// 设置响应头,指定Content-Type为text/event-stream$response->headers->set('Content-Type', 'text/event-stream');$response->headers->set('Cache-Control', 'no-cache');$response->headers->set('Charset', 'UTF-8');$response->headers->set('X-Accel-Buffering', 'no');$params = $request->request->all();$aiParams = $this->filterAiParams($params);//插入用户消息$this->getChatApiConversationService()->addUserMessage($this->conversationId,$aiParams);// 设置响应内容生成器$response->setCallback(function () use ($request,$aiParams) {$this->forwardAiRequest($aiParams);});// 发送响应$response->send();}public function update(ApiRequest $request,$conversationId,$flag){$params = $request->request->all();$message = $params['messages']['0']['content'] ?? '';$result = $this->getChatApiConversationService()->addAiMessage($conversationId,['message'=>$message],'');if ($result){return ['status'=>'success','message'=>'补充消息成功','code'=> 1,"data"=>['messageId'=>$result['id']] ];} else {return ['status'=>'fail','message'=>'补充消息成功','code'=> 0];}}/*** @param $chunk* @return string*/public function formatAiResponse($chunk){if($chunk == "Internal Server Error"){throw new BadRequestHttpException("AI好像开小差了~请联系客服");}//替换掉data:$chunkJsonStr = trim(str_replace('data:','',$chunk));$isComplete = json_decode($this->currentMessage,true);if($isComplete == null){$this->currentMessage .= $chunkJsonStr;} else {$this->currentMessage = $chunkJsonStr;}if($chunkJsonStr !='[DONE]'){//这里会出现单条消息超限多条消息拼接的情况,$chunkArr = json_decode($this->currentMessage,true);if (!$chunkArr){return null;}$this->currentMessage = '';$originMessage = $chunkArr['choices'][0]['delta']['content'] ?? '';//拼接当前条消息数据$this->message .= $originMessage;//赋值引用文档if($chunkArr['choices'][0]['sources']){$this->document = $chunkArr['choices'][0]['sources'];}//重新拼装前端结构$customChunkArr = ["status"=>"going","content"=>$originMessage];return "data: ".json_encode($customChunkArr,JSON_UNESCAPED_UNICODE)."\n\n";} else {//传输结束,这里处理数据入库$this->message$sources = $this->getTaskByPageLabel($this->document);$insertData = ['message' => $this->message,"sourceFrom" => json_encode($sources,JSON_UNESCAPED_UNICODE)];//这里判断是否当前课程学员//先查出当前会话对应goods$conversation = $this->getChatApiService()->findByConversationId($this->conversationId);$isMember = false;$id = $conversation[0]['goodsId'] ?? 0;if($id !== 0){//查询当goods_$goodsApiRequest = new ApiRequest("/api/goods/{$id}", 'GET', []);try{$goods = $this->container->get('api_resource_kernel')->handleApiRequest($goodsApiRequest);$isMember = $goods['isMember'];}catch(\Exception $e){$catchFlag = 1;}}//根据taskId查询task$originData = json_encode(['message'=>$this->message,'sources'=>$this->document],JSON_UNESCAPED_UNICODE);$result = $this->getChatApiConversationService()->addAiMessage($this->conversationId,$insertData,$originData);return "data: ".json_encode(["status"=>"done","finishData"=>['id'=>$result['id'],"sourceFrom"=>$sources,"isMember"=>$isMember]],JSON_UNESCAPED_UNICODE)."\n\n";}}//前置验证protected function verifySend(){$userId = $this->getCurrentUser()->getId();if (!$userId){//登录验证throw new BadRequestHttpException("请先登录");}$factory = $this->biz->offsetGet('ratelimiter.factory');$rateLimiter = $factory('chat_send_message', 5, 60);$remained = $rateLimiter->check($userId);if (!$remained) {throw new BadRequestHttpException("发送过于频繁请于1分钟后重试");}}public function filterAiParams($params){if(!$params['messages'] || $params['conversationId']){throw new BadRequestHttpException("缺失必要参数");}$params['stream'] = true;$params['use_context'] = true;$params['include_sources'] = true;return $params;}public function forwardAiRequest($params){$curl = curl_init();curl_setopt_array($curl, array(CURLOPT_URL => $this->host.$this->uri,CURLOPT_RETURNTRANSFER => true,CURLOPT_ENCODING => '',CURLOPT_MAXREDIRS => 10,CURLOPT_TIMEOUT => 0,CURLOPT_FOLLOWLOCATION => true,CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,CURLOPT_CUSTOMREQUEST => 'POST',CURLOPT_POSTFIELDS => json_encode($params),CURLOPT_HTTPHEADER => array('Accept: application/json','Authorization: '.$this->authKey,'Content-Type: application/json'),// 启用流式传输模式));curl_setopt($curl, CURLOPT_BUFFERSIZE, 16384);curl_setopt($curl, CURLOPT_WRITEFUNCTION, function ($ch, $chunk) {// 处理接收到的流式数据$echoStream = $this->formatAiResponse($chunk);if ($echoStream != null){echo $echoStream;}flush();return strlen($chunk);});$response = curl_exec($curl);curl_close($curl);// echo $response;}//根据map,page_label获取taskprotected function getTaskByPageLabel($sources){$tasks = [];if($sources){foreach ($sources as $source){$pageLabel = $source['document']['doc_metadata']['page_label'] ?? 0;$score = $source['score'] ?? 0;if ($pageLabel && $score>0.5){$task = self::getPageTaskMap()[$pageLabel];$taskInfo = $this->getTaskService()->getTask($task['taskId']);$task['courseId'] = $taskInfo['courseId'];$tasks[] = $task;}}}return array_unique($tasks);}//page与task映射map,先写死实现功能static protected function getPageTaskMap(){//page_label => taskInforeturn [1 => ['taskId'=>51147,'taskTitle'=>"课时1:课程概述、特点及说明",],2 => ['taskId'=>51148,'taskTitle'=>"课时2:资产配置概述、均值方差模型配置法的 GPT 应用",],3 => ['taskId'=>51148,'taskTitle'=>"课时2:资产配置概述、均值方差模型配置法的 GPT 应用",],4 => ['taskId'=>51148,'taskTitle'=>"课时2:资产配置概述、均值方差模型配置法的 GPT 应用",],5 => ['taskId'=>51149,'taskTitle'=>"课时3:Black-Litterman 策略、风险平价策略介绍及GPT 应用",],6 => ['taskId'=>51149,'taskTitle'=>"课时3:Black-Litterman 策略、风险平价策略介绍及GPT 应用",],7 => ['taskId'=>51149,'taskTitle'=>"课时3:Black-Litterman 策略、风险平价策略介绍及GPT 应用",],8 => ['taskId'=>51150,'taskTitle'=>"课时4:Black-Litterman 策略、风险平价策略介绍及GPT 应用",],9 => ['taskId'=>51150,'taskTitle'=>"课时4:Black-Litterman 策略、风险平价策略介绍及GPT 应用",],10 => ['taskId'=>51151,'taskTitle'=>"课时5:基金分类、标签体系、收益风险指标分析",],11 => ['taskId'=>51151,'taskTitle'=>"课时5:基金分类、标签体系、收益风险指标分析",],12 => ['taskId'=>51152,'taskTitle'=>"课时6:基金投资体验分析和投资风格分析",],13 => ['taskId'=>51152,'taskTitle'=>"课时6:基金投资体验分析和投资风格分析",],14 => ['taskId'=>51152,'taskTitle'=>"课时6:基金投资体验分析和投资风格分析",],15 => ['taskId'=>51153,'taskTitle'=>"课时7:基金插件工具介绍及营销文案编写",],16 => ['taskId'=>51154,'taskTitle'=>"课时8:历史事件复盘",],17 => ['taskId'=>51155,'taskTitle'=>"课时9:近期市场、经济复盘",],18 => ['taskId'=>51156,'taskTitle'=>"课时10:未来经济展望",],20 => ['taskId'=>51157,'taskTitle'=>"课时11:PPI 时间序列预测",],21 => ['taskId'=>51158,'taskTitle'=>"课时12:周期划分到策略适配",],22 => ['taskId'=>51174,'taskTitle'=>"课时13:金融数据分析简介",],23 => ['taskId'=>51175,'taskTitle'=>"课时14:调研问卷分析案例",],24 => ['taskId'=>51175,'taskTitle'=>"课时14:调研问卷分析案例",],25 => ['taskId'=>51176,'taskTitle'=>"课时15:金融客户机器学习分群案例",],26 => ['taskId'=>51176,'taskTitle'=>"课时15:金融客户机器学习分群案例",],27 => ['taskId'=>51177,'taskTitle'=>"课时16:财经公众号内容提取案例",],28 => ['taskId'=>51178,'taskTitle'=>"课时17:筛选符合减持新规的股票",],29 => ['taskId'=>51159,'taskTitle'=>"课时18:数据提取:基金历史业绩(附 Noteable 插件介绍)",],30 => ['taskId'=>51159,'taskTitle'=>"课时18:数据提取:基金历史业绩(附 Noteable 插件介绍)",],31 => ['taskId'=>51160,'taskTitle'=>"课时19:数据提取:基金基础信息",],32 => ['taskId'=>51161,'taskTitle'=>"课时20:数据提取:基金基础信息",],33 => ['taskId'=>51162,'taskTitle'=>"课时21:数据分析:持仓数据图表化",],34 => ['taskId'=>51163,'taskTitle'=>"课时22:数据分析:持仓数据分析",],35 => ['taskId'=>51163,'taskTitle'=>"课时22:数据分析:持仓数据分析",],36 => ['taskId'=>51164,'taskTitle'=>"课时23:数据分析:文字描述生成",],37 => ['taskId'=>51165,'taskTitle'=>"课时24:数据分析:大类资产配比计算及增配建议",],38 => ['taskId'=>51166,'taskTitle'=>"课时25:数据分析:基金持仓行业分布",],39 => ['taskId'=>51167,'taskTitle'=>"课时26:内容生成:宏观经济与市场分析(及 KeyMate插件介绍)",],40 => ['taskId'=>51167,'taskTitle'=>"课时26:内容生成:宏观经济与市场分析(及 KeyMate插件介绍)",],41 => ['taskId'=>51168,'taskTitle'=>"课时27:内容生成:行业分析",],42 => ['taskId'=>51169,'taskTitle'=>"课时28:内容生成:基金分析",],43 => ['taskId'=>51170,'taskTitle'=>"课时29:风格适配:结构化 Prompt",],44 => ['taskId'=>51171,'taskTitle'=>"课时30:风风格适配:Custom Instructions",],45 => ['taskId'=>51172,'taskTitle'=>"课时31:风风格适配:Custom Instructions",],46 => ['taskId'=>51173,'taskTitle'=>"课时 32: 资产配置报告展示",],47 => ['taskId'=>51173,'taskTitle'=>"课时 32: 资产配置报告展示",],];}protected function getTaskService(){return $this->service('Task:TaskService');}private function getProductService(){return $this->service('Product:ProductService');}public function getGoodsService(){return $this