记一次chatgpt接入

最近公司业务需要接入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://www.rhkb.cn/news/4148.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

ChatGPT:AI时代的创造力激活

《你好&#xff0c;ChatGPT》是一本深入探索人工智能&#xff08;AI&#xff09;领域的畅销书籍&#xff0c;它以ChatGPT为切入点&#xff0c;系统地介绍了AI和AIGC的基础概念、技术原理、应用领域和未来展望。这本书通俗易懂&#xff0c;由浅入深&#xff0c;层层递进&#xf…

chatgpt赋能python:Python怎么汉化:从入门到流利

Python怎么汉化&#xff1a;从入门到流利 Python作为一门非常受欢迎的编程语言&#xff0c;受到了各式各样用户的追捧。但对于非英语母语的用户来说&#xff0c;可能面临的一个挑战就是如何在这个基本上是英语核心的编程语言中进行汉化操作。本文将为大家介绍Python的汉化方法…

chatgpt赋能python:Python如何转换成中文

Python如何转换成中文 在全球范围内&#xff0c;Python是一种广泛使用的编程语言&#xff0c;它拥有简单易学、高效和可扩展的特点。 在中国的工程师社区中&#xff0c;Python也广泛应用于各类项目中。但是&#xff0c;在一些情况下&#xff0c;我们需要将Python代码转换为中…

chatgpt赋能python:Python装汉化包详解:完美解决你的语言难题

Python装汉化包详解&#xff1a;完美解决你的语言难题 前言 作为一种高效、易读易写、功能强大的编程语言&#xff0c;Python已经逐渐成为了开发者们的不二选择。不过&#xff0c;对于初学者而言&#xff0c;英文文档的学习难度可能会稍微大一点。于是&#xff0c;本文将详细…

国内外学者联合撰写,ChatGPT技术路线图

Datawhale干货 作者&#xff1a;符尧&#xff0c;爱丁堡大学&#xff0c;编辑&#xff1a;李rumor 最近有幸看到了一篇十分深度剖析GPT系列模型的文章&#xff0c;读到后赶紧与作者联系&#xff0c;没想到他们很快就翻译成了中文&#xff0c;在这里分享给大家。 英文原版&…

【万字拆解】ChatGPT各项能力的起源

每天给你送来NLP技术干货&#xff01; 来自&#xff1a;李rumor 卷友们好&#xff0c;最近有幸看到了一篇十分深度剖析GPT系列模型的文章&#xff0c;读到后赶紧与作者联系&#xff0c;没想到他们很快就翻译成了中文&#xff0c;在这里分享给大家。 英文原版&#xff1a;https:…

美学生用ChatGPT写论文被识破,导师:好到不符合我对学生的预期

据央视网快看微博3月20日消息&#xff0c;北密歇根大学的教授奥曼在学生作业中发现了一篇关于世界宗教的“完美论文”。 “这篇文章写得比大多数学生都要好......好到不符合我对学生的预期&#xff01;”他去问ChatGPT&#xff1a;“这是你写的吗&#xff1f;”ChatGPT回答&…

ChatGPT 使用 拓展资料:吴恩达大咖 Building Systems with the ChatGPT API 内容审查

ChatGPT 使用 拓展资料:吴恩达大咖 Building Systems with the ChatGPT API 内容审查 https://learn.deeplearning.ai/chatgpt-building-system?_gl=114hjbho_gaMTEwNzkwNDAyMC4xNjgyNjUxMzg4_ga_PZF1GBS1R1*MTY4NTk2NTg1Ni4xNS4wLjE2ODU5NjU4NTYuNjAuMC4w 如果你正在建立一个…

国内外在讨论 ChatGPT 时,都在讨论什么?差距是真的大...

来自&#xff1a;月小水长 最近 ChatGPT 火的一塌糊涂&#xff0c;程序员用它生成一个简单需求的代码&#xff0c;公务员用它生成一段感想报告的初稿&#xff0c;于是乎&#xff0c;大家纷纷担忧起来了 ChatGPT 会取代搜索引擎&#xff0c;以 ChatGPT 为代表作的 AI 会取代一部…

MOSS与ChatGPT,人工智能真的会取代人类吗?

MOSS与ChatGPT&#xff0c;人工智能真的会取代人类吗&#xff1f; 1 引言2 MOSS2.1 MOSS能力表现2.2 量子计算机2.3 MOSS实现的可能 3 ChatGPT3.1 ChatGPT的爆火3.2 ChatGPT能干什么3.3 ChatGPT带起的潮流3.4 ChatGPT具有自主意识吗&#xff1f; 4 结语 550W听起来不像是个名字…

时下热门话题:ChatGPT能否取代人类?

时下热门话题&#xff1a;ChatGPT能否取代人类&#xff1f; 2022年11月底&#xff0c;人工智能对话聊天机器人ChatGPT推出&#xff0c;迅速在社交媒体上走红&#xff0c;短短5天&#xff0c;注册用户数就超过100万。2023年1月末&#xff0c;ChatGPT的月活用户已突破1亿&#x…

ChatGPT桌面客户端支持gpt4模型,附使用说明

#软件核心功能&#xff1a; 1、支持OpenAI官方秘钥及API2D双秘钥使用&#xff1b;如果全局魔法&#xff0c;可以自己用官方秘钥&#xff1b;没魔法国内可直接使用API2D秘钥&#xff1b; 2、内置GPT4模型选项&#xff0c;如果你的官方秘钥支持可直接使用&#xff1b;你也可以注册…

太强大了!比ChatGPT更强大的桌面版本来了!

大家好&#xff0c;我是菜鸟哥&#xff01; 最近一边写Python代码&#xff0c;一边带着大家玩ChatGPT 。通过python可以挖掘GPT很多高级功能&#xff0c;通过GPT也可以更快学习Python。 目前我们社群已经有快600人了&#xff01;大家玩的不亦乐乎&#xff0c;一起玩微信机器人&…

起步篇-- 超强的Chatgpt桌面版来了!可以保存聊天记录以及内置128个聊天场景!

Hello 小伙伴们,看了我们前面几篇起步篇的基本的内容,是不是已经开始玩起了ChatGPT了!但是有小伙伴不知道怎么聊天,因为Chatgpt有100多种prompt的角色,每一个场景都需要特殊的训练语句,才能完美的发挥ChatGPT的功能! 比如一些角色扮演啊,面试官啊,旅游啊,理财顾问啊,…

基于ChatGPT官方API的电脑桌面版应用抢先体验

今天主要是利用ChatGPT官方内测API搭建的一个桌面程序&#xff0c;以方便大家使用&#xff0c;桌面程序可在CSDN中直接下载。下载地址为&#xff1a;“https://download.csdn.net/download/suiyingy/87416076”&#xff08;正在审核&#xff09;。百度网盘链接为“https://pan.…

【无标题】chatgpt桌面化,桌面应用的安装

前言&#xff1a;关于chatgpt最近来说可算是大火&#xff0c;不过在我使用过程中发现没事都要上openai的官网过于麻烦&#xff0c;而且卡顿&#xff0c;于是乎就在网上寻找一些方法&#xff0c;发现chatgpt可以桌面化。 话不多说&#xff0c;直接上图。 1.上github找这位大佬 …

桌面版 ChatGPT 来了!

由于工作、学习需要&#xff0c;现在基本上每天都要使用几个小时的 ChatGPT。一直以来的方法就是登录 OpenAI ChatGPT 的 web 版&#xff1a;http://chat.openai.com/ 每天登录网页比较麻烦&#xff0c;如果能有一个桌面应用就方便了&#xff01;今天就给大家推荐一个 ChatGPT …

Visual ChatGPT 通过连接ChatGPT和一系列视觉基础模型,在聊天过程中实现了图像的发送和接收。

Visual ChatGPT Visual ChatGPT 通过连接ChatGPT和一系列视觉基础模型&#xff0c;在聊天过程中实现了图像的发送和接收。 项目地址 https://github.com/microsoft/visual-chatgpt 论文 https://arxiv.org/abs/2303.04671 Demo 架构 快速开始 # create a new environment…

ChatGPT 辅助硬件设计

前言 总结一些在硬件设计中使用 ChatGPT 的命令,以助力提升开发效率。 文章目录 前言一、应用方面二、举例1、查阅基础知识2、元器件选型1)芯片2)MCU3)电路设计三、辅助检查网址一、应用方面 查阅电路基础知识的原理;做芯片选型;做硬件方案设计,实现业务需求;二、举例 …

ChatGPT翻译有什么特点,与传统翻译工具有什么不同

谷歌翻译和ChatGPT翻译各有特点&#xff0c;根据使用场景的不同&#xff0c;选择哪一种更划算也会有所差异。 一、翻译准确度&#xff1a; 谷歌翻译的准确度相当高&#xff0c;并且它已经在机器翻译领域占据了很大的市场份额。但是&#xff0c;谷歌翻译的问题在于&#xff0c…