记一次chatgpt接入

article/2024/12/22 20:38:26

最近公司业务需要接入gpt问答,踩了不少坑,特此记录一下

流式

在网上找了很多别人gpt接入的案例,但是一直没有得到想要的效果,一直以为是我接错了,后来想通了一件事,虽然都是流式接入,但是还是有本质区别的,网上找到的很多案例是一次性类似于图片传输的流,拿到的流是最终结果了,而我们业务想实现的是分块的流,即从GPT拿到一个字就,返回给前端一个字,而不是拿到gpt的最终结果再将结果返回,缩短用户等待时间。

实现

  • 接收到的流有可能并不是完整数据,即可能是一条、N条、N.5条,需要做处理
function ask()
{$messages = [['role' => 'user','content' => '你好']];$json = json_encode(['model' => 'gpt-3.5-turbo','messages' => $messages,'temperature' => 0.6,'stream' => true,]);$headers = array("Content-Type: application/json","Authorization: Bearer " . $this->api_key,);// 原先用GuzzleHttp,但是没有达到想要的效果,不知道问题出在哪里,一怒之下,咱用原生吧$ch = curl_init();curl_setopt($ch, CURLOPT_URL, $this->api_url);curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);curl_setopt($ch, CURLOPT_HEADER, false);curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);curl_setopt($ch, CURLOPT_POSTFIELDS, $json);curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);curl_setopt($ch, CURLOPT_WRITEFUNCTION, [self::class, 'callback']);$response = curl_exec($ch);if (curl_errno($ch)) {file_put_contents('./log/curl.error.log', curl_error($ch) . PHP_EOL . PHP_EOL, FILE_APPEND);}curl_close($ch);
}/*** 流式回调* @param $ch* @param $data* @return int*/public function callback($ch, $data){$this->counter += 1;$result = json_decode($data, TRUE);if (is_array($result)) {$this->end('openai 请求错误:' . json_encode($result));return strlen($data);}/*每次 callback 函数收到的数据并不一定只有一条 data: {"key":"value"} 格式的数据,有可能只有半条,也有可能有多条,还有可能有 N 条半*/// 把上次缓冲区内数据拼接上本次的data$buffer = $this->data_buffer . $data;// 拼接完之后,要把缓冲字符串清空$this->data_buffer = '';// 把所有的 'data: {' 替换为 '{' ,'data: [' 换成 '['$buffer = str_replace('data: {', '{', $buffer);$buffer = str_replace('data: [', '[', $buffer);// 把所有的 '}\n\n{' 替换维 '}[br]{' , '}\n\n[' 替换为 '}[br]['$buffer = str_replace("}\n\n{", '}[br]{', $buffer);$buffer = str_replace("}\n\n[", '}[br][', $buffer);// 用 '[br]' 分割成多行数组$chunks = explode('[br]', $buffer);$chunkCount = count($chunks);foreach ($chunks as $key => $chunk) {$line = trim($chunk);// 数据传输完成if ($line == '[DONE]') {$this->data_buffer = "";$this->counter = 0;$this->end();break;}$chunkData = json_decode($line, true);if (!is_array($chunkData) || !isset($chunkData['choices']) || !isset($chunkData['choices'][0])) {// 已经到本次截取字段末尾了,将末尾数据储存起来,供下一次使用if ($key == ($chunkCount - 1)) {$this->data_buffer = $chunk;break;}//如果是中间行无法json解析,则写入错误日志中continue;}// 输出数据if (isset($chunkData['choices'][0]['delta']) && isset($chunkData['choices'][0]['delta']['content'])) {$this->write($chunkData['choices'][0]['delta']['content']);}}return strlen($data);}private function write($content = NULL, $flush = TRUE){if ($content != NULL) {echo 'data: ' . json_encode(['time' => date('Y-m-d H:i:s'), 'content' => $content], JSON_UNESCAPED_UNICODE) . PHP_EOL . PHP_EOL;}if ($flush) {flush();}}private function end($content = NULL){if (!empty($content)) {$this->write($content, FALSE);}echo 'data: Connection closed' . PHP_EOL . PHP_EOL;flush();
}// 返回给前端
public function ai()
{// 前端返回// 这行代码用于关闭输出缓冲。关闭后,脚本的输出将立即发送到浏览器,而不是等待缓冲区填满或脚本执行完毕。ini_set('output_buffering', 'off');// 这行代码禁用了 zlib 压缩。通常情况下,启用 zlib 压缩可以减小发送到浏览器的数据量,但对于服务器发送事件来说,实时性更重要,因此需要禁用压缩。ini_set('zlib.output_compression', false);// 这行代码使用循环来清空所有当前激活的输出缓冲区。ob_end_flush() 函数会刷新并关闭最内层的输出缓冲区,@ 符号用于抑制可能出现的错误或警告。while (@ob_end_flush()) {}// 跨域问题header('Access-Control-Allow-Credentials: true');header('Access-Control-Allow-Origin: *');header('Access-Control-Allow-Methods: GET, POST, OPTIONS');header('Access-Control-Allow-Headers: Content-Type');// 这行代码设置 HTTP 响应的 Content-Type 为 text/event-stream,这是服务器发送事件(SSE)的 MIME 类型。header('Content-Type: text/event-stream;charset=UTF-8');// 这行代码设置 HTTP 响应的 Cache-Control 为 no-cache,告诉浏览器不要缓存此响应。header('Cache-Control: no-cache');// 这行代码设置 HTTP 响应的 Connection 为 keep-alive,保持长连接,以便服务器可以持续发送事件到客户端。header('Connection: keep-alive');// 这行代码设置 HTTP 响应的自定义头部 X-Accel-Buffering 为 no,用于禁用某些代理或 Web 服务器(如 Nginx)的缓冲。// 这有助于确保服务器发送事件在传输过程中不会受到缓冲影响。header('X-Accel-Buffering: no');$this->ask();
}

另一个大佬GuzzleHttp写法

public function createChatCompletionStream($messages = [])
{if (empty($messages)) {exit();}try {$response = $this->guzzle->request("POST", '/v1/chat/completions', ['json' => ['model' => 'gpt-3.5-turbo','messages' => $messages,'stream' => true,],'stream' => true,]);$body = $response->getBody();$buffer = '';while (!$body->eof()) {$buffer .= $body->read(128);// 这里使用 while 是因为读取 n 个字节有可能同时读出 n 条 EventSource 消息while (($pos = strpos($buffer, "\n\n")) !== false) {$msg = substr($buffer, 0, $pos); // 一条 event 消息$buffer = substr($buffer, $pos + 2); // 去除已被解析的部分if (substr($msg, 0, 6) === 'data: ') { // 只解析了 data ,实际的 EventSource 还有 event 、id 、retry$obj = json_decode(substr($msg, 6));if (isset($obj->choices[0]->delta->content)) {echo $obj->choices[0]->delta->content;ob_flush();flush();}}}}exit();} catch (GuzzleException $e) {Log::error($e->getMessage());return response('请求失败,请稍后重试', 500);}
}
  • 实现原理为SSE,要实现业务效果,后端需要多次返回流,前端需要用Eventsource 接收流,根据接收的流做出处理
  • Eventsource 只支持utf8编码文本
  • 浏览器对保持连接的限制(有人说chrome只能访问6个),超过了会一直阻塞在客户端
  • 部分浏览器不太支持 查看

前端

eventsource

createEventSource() {this.resultText = ''const url = ``const eventSource = new EventSource(url)eventSource.addEventListener('open', (event) => {console.log('连接已建立', JSON.stringify(event))})eventSource.addEventListener('message', (event) => {console.log('接收数据:', event.data)if (event.data.indexOf('closed') !== -1) {eventSource.close()} else {var result = JSON.parse(event.data)if (result.time && result.content) {this.resultText += result.content}}})eventSource.addEventListener('error', (event) => {console.error('发生错误:', JSON.stringify(event))eventSource.close()})},

在这里插入图片描述
eventsource 仅支持get请求,post请求不能用原生的eventsource

   async fetchAiResponse(message) {try {const response = await fetch('http://127.0.0.1/api/test', {method: 'POST',headers: { 'Content-Type': 'application/json' },body: JSON.stringify({messages: [{ role: 'user', content: message }]})})console.log(response, 'response')if (!response.ok) {throw new Error(response.statusText)}const reader = response.body.getReader()const decoder = new TextDecoder('utf-8')const readChunk = async () => {return reader.read().then(({ value, done }) => {if (!done) {let partialResponse = decoder.decode(value, { stream: true })if (partialResponse.indexOf('closed') !== -1) return readChunk()partialResponse = partialResponse.replaceAll('data: {', '{')let chunks = partialResponse.split(/\n{2}/g)chunks = chunks.filter((item) => {return item.trim()})for (let i = 0; i < chunks.length; i++) {const chunk = chunks[i]// 第一个数据可能会为nullif (chunk) {let payloads// 可能会存在一条数据中多个对象if (chunk.indexOf('}{') !== -1) {const _arr = chunk.split('}{')payloads = _arr.map((item, i) => {let _strif (i === 0) {_str = item + '}'} else if (i === _arr.length - 1) {_str = '{' + item} else {_str = `{${item}}`}return JSON.parse(_str)})} else {payloads = [JSON.parse(chunk)]}if (payloads) {for (let k = 0; k < payloads.length; k++) {const _item = payloads[k]if (_item.content) {this.resultText += _item.contentbreak}}}}}return readChunk()} else {console.log('结束了')}})}await readChunk()} catch (error) {console.error('Error fetching AI response:', error)console.log('assistant', 'Error: Failed to fetch AI response.')}},

eventsource 无法设置header头,可以改用event-source-polyfill

参考链接
阮一峰
EventSource
纯 PHP 实现流式调用 OpenAI gpt


http://chatgpt.dhexx.cn/article/D78USJJI.shtml

相关文章

如何将chatGpt接入企业微信

1.准备工作 这次更新之后&#xff0c;国内服务器已没法直接访问openai的接口&#xff0c;需要自己买个国外的服务器。 一台海外服务器&#xff08;服务器上安装Java8&#xff0c;操作系统选Ubuntu&#xff0c;如果用windows&#xff0c;要自己研究&#xff09;注册好的企业微…

OpenAI最新官方ChatGPT聊天插件接口《接入插件快速开始》全网最详细中英文实用指南和教程,助你零基础快速轻松掌握全新技术(二)(附源码)

Getting started 快速开始 前言Introduction 导言Plugin manifest 插件清单OpenAPI definition OpenAPI定义Running a plugin 运行插件Setup a local proxy of your public API 设置公共API的本地代理 Writing descriptions 书写描述Best practices Debugging 排除故障其它资料…

关于安卓以及微软用户chatgpt上一篇文章如今第五点无法正常进入更新解决方法以及附加本地部署

目录 一、问题出现&#xff1a; 1、问题&#xff1a; 原因&#xff1a; 二、解决办法&#xff08;本地部署chatgpt&#xff09; 1、解决&#xff08;国内网络使用真的chatgpt并非镜像&#xff09;一次部署终生使用 第一步&#xff1a; ​编辑第二步&#xff1a; 三、实现结…

ChatGpt学习辅助挑战网络工程师001

ChatGpt学习辅助挑战网络工程师001 书接上回,询问ChatGpt后,来点亮第一个技能树 成为一个网络工程师ChatGpt提出的的第一步,需要熟悉网络架构. Network Architecture: You should have a good understanding of network architectures and how different components of a net…

VisualChatGPT: 微软发布可发送和接收图片的 ChatGPT

公众号关注 「奇妙的 Linux 世界」 设为「星标」&#xff0c;每天带你玩转 Linux &#xff01; ​ Visual ChatGPT 连接了 ChatGPT 和一系列的 Visual Foundation 模型&#xff0c;以便在聊天过程中发送和接收图像。 下图为演示效果&#xff1a; 对该应用实现感兴趣的可以查看其…

调用chatgpt官方api实现聊天和绘图

首先要学会科学上网 1官方api文档 https://platform.openai.com/docs/api-reference/chat/create 2 获取key https://platform.openai.com/ 登录账号 之后点击右上角的头像&#xff0c;再点击View API keys 3 http调用聊天接口 调用地址https://api.openai.com/v1/chat/com…

解决chatgpt网络错误,频繁掉线的问题,那就使用KeepChatGPT

文章目录 解决chatgpt出现An error occurred. If this issue persists please contact us through our help center at help.openai.com问题起因对比原作者github地址安装步骤浏览器要求安装油猴安装KeepChatGPT插件使用方法功能栏说明功能说明如下关于 取消审计 功能关于 调整…

ChatGPT 速通手册——让 ChatGPT 来写正则表达式

regex 生成 正则表达式可谓是一门让广大程序员们又爱又恨的技术。它易学难精&#xff0c;而且可维护性又差&#xff0c;别说交接给其他同事&#xff0c;同一个人写的正则表达式&#xff0c;三个月后回头再看&#xff0c;也可能完全不知所云。 因此&#xff0c;让 ChatGPT 来写…

轻松解决ChatGPT网络报错,畅享沟通

ChatGPT的确很不错&#xff0c;无论是在什么岗位&#xff0c;使用它都可以让工作的你提升效率&#xff0c;可是我们经常会遇到一个神奇的网络报错&#xff08;当我们一会不使用就来个这样的效果提示&#xff09;&#xff0c;是不是头大&#xff1f; 好了&#xff0c;开始进入正…

完美解决ChatGPT网络错误,不再频繁地刷新网页(分享好用的插件KeepChatGPT)

最近发现一个好用的浏览器插件KeepChatGPT&#xff01;完美解决ChatGPT网络错误&#xff0c;不再频繁地刷新网页&#xff0c;敲好用&#xff01;&#xff01;&#xff01; 废话不多说上链接&#xff01; 安装渠道如下 1 Github&#xff1a;https://github.com/xcanwin/KeepCh…

ChatGPT报错“network Error“?

文章目录 问题一、为什么ChatGPT会报错"network Error"?二、ChatGPT Plus -GPT4如何开通&#xff1f;结尾 问题一、为什么ChatGPT会报错"network Error"? ChatGPT报错“Network Error”&#xff0c;通常意味着它无法连接到服务器或API服务不可用。以下是…

chatgpt api极简入门(参考官网教程)

写在前面 心血来潮&#xff0c;复试完结束很摆&#xff0c;研究点东西玩玩&#xff0c;之前之知道nonebot搭建qq机器人的方法和步骤&#xff0c;这次记录下自己使用openai&#xff0c;gpt3.5的api的代码&#xff0c;参考自openai的官网。 环境 要求 python 版本 >3.8 &…

解决ChatGPT网络总是掉线问题

解决ChatGPT网络总是掉线问题 问题描述 1.我们在使用ChatGPT时&#xff0c;总是会遇到如下图网络掉线问题&#xff0c;是什么原因呢&#xff1f;简而言之&#xff0c;服务器检测到1-2分钟内你没有与之发生数据交互&#xff0c;认为你已经掉线了&#xff0c;就主动断开了链接&…

如何解决ChatGPT网络错误的问题,让AI对话更丝滑~

前言 在当今人工智能技术的飞速发展中&#xff0c;ChatGPT 作为一款大型语言模型备受瞩目。近期&#xff0c;其在各大社交媒体平台上的表现更是引来了一片关注之声。无论是与用户进行有趣的对话&#xff0c;还是帮助人们解决实际问题&#xff0c;ChatGPT 展现出了其强大的自然…

ChatGPT下的网站建设会收到哪些影响?

近日&#xff0c;微软发布了人工智能语言模型 ChatGPT&#xff0c;该模型可以理解人类的语言并生成响应式文本。与其他自然语言处理模型不同&#xff0c; ChatGPT具有出色的语言理解能力&#xff0c;并能够生成自然、流畅的文本。 ChatGPT不仅能够回答用户问题&#xff0c;还能…

如何一键部署自己的ChatGPT个人网站?

目录 一、前言 二、账号和项目准备 三、网站的部署 四、附上本次搭建网站的地址 一、前言 对于部署ChatGPT个人网站&#xff0c;其实网上有很多开源的项目可以拿来一键部署&#xff0c;我也在B站一些视频up主的教学下成功部署了自己ChatGPT网站。自己实践了部署了网站让我…

ChatGPT45个插件列表

ChatGPT插件现已对所有人开放。 插件介绍&#xff1a; Slack: 查询Slack信息Zapier: 与5000应用&#xff0c;如Google Sheets和Docs进行交互。Expedia: 在一个地方激活你的旅行计划Klarna购物: 在成千上万的在线商店中搜索并比较价格。Vogue时尚杂志: 搜索时尚杂志的文章TO-D…

ChatGPT 与 ChatSonic的比较

ChatGPT 与 ChatSonic的比较 李升伟 前 言 众所周知&#xff0c;ChatGPT最大的不足是新知识获取能力,它目前为止只用到了2021年前的数据作为训练&#xff0c;无法回答2021年之后的相关信息问题。在ChatGPT发布的当前&#xff0c;一家智能写作的创业公司writesonic发布了ChatGP…

Google Bard vs. ChatGPT 哪家强?结果一目了然

整理 | 梦依丹 出品 | CSDN&#xff08;ID&#xff1a;CSDNnews&#xff09; 2023 年的科技狂欢是 AI 给的&#xff0c;就在昨晚&#xff0c;AI 的新闻头条是一个接着一个&#xff1a;Google 开放 Bard&#xff1b;NVIDIA 推出了云工具&#xff0c;用于生成式人工智能&…

谷歌加紧测试ChatGPT竞品,靠对话可搜最新信息

来源&#xff1a;量子位 现代服务产业技术创新战略联盟 本文约1700字&#xff0c;建议阅读5分钟ChatGPT步步紧逼&#xff0c;谷歌终于要亮兵器了。 据CNBC最新爆料&#xff0c;谷歌正测试一款类似ChatGPT的聊天机器人&#xff0c;名为Apprentice Bard。 该产品基于谷歌对话模型…