实现AI聊天-核心技术点详解

实现chatGpt的效果,包含以下几个技术点:

1. 流式获取

要实现像chatgpt一个字一个字回复的效果,涉及到流式获取数据SSE(Server - Sent Events)。

sse利用向客户端声明,接下来要发送的是流信息的机制,向浏览器连续不断地推送信息

这完全不同于与平常我们所用的一次性加载方式。在传统方式中,前端发起请求后,需要等待后端准备好完整数据才能一次性返回;而流式方式则是后端逐步返回数据片段(chunk),前端逐块接收和处理,实现"边接收边处理"的效果。

如何实现:

  1. 后端实现

后端设置响应头:(记得添加charset=utf-8,声明编码格式,防止中文乱码

vbnet 复制代码
text/event-stream;charset=utf-8

响应体格式:

每一次发送的信息,由若干个message组成,每个message之间用\n\n分隔。每个message内部由若干行组成,每一行都是如下格式。

markdown 复制代码
[field]: value\n

上面的field可以取四个值。

  • data
  • event
  • id
  • retry

返回示例:

vbnet 复制代码
event: userconnect
data: {"username": "bobby", "time": "02:33:48"}

data: {"username": "bobby", "time": "02:34:11", "text": "Hi everyone."}

data: {"username": "bobby", "time": "02:34:23"}

data: {"username": "sean", "time": "02:34:36", "text": "Bye, bobby."}

# Server-Sent Events 教程-阮一峰- 4.1 数据格式

MDN-使用服务器发送事件-事件流格式

用nodejs,express实现一个简易的sse接口:

js 复制代码
const express = require('express');
const cors = require('cors');
const app = express();
const port = 3000;

// 配置CORS选项
const corsOptions = {
  origin: true, // 或指定域名如 'http://example.com'
  methods: ['GET', 'OPTIONS'], // SSE通常使用GET
  allowedHeaders: ['Content-Type'],
  credentials: true, // 如果需要凭证
};

// 应用CORS中间件
app.use(cors(corsOptions));

app.get('/sse', (req, res) => {
  const text = '随便写一句话,看看效果';
  const speed = 100;

  res.writeHead(200, {
    'Content-Type': 'text/event-stream;charset=utf-8',
    'Cache-Control': 'no-cache',
    Connection: 'keep-alive',
    // 添加cors
    'Access-Control-Allow-Origin': '*',
    'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
    'Access-Control-Allow-Credentials': true,
  });

  let index = 0;
  const timer = setInterval(() => {
    if (index < text.length) {
      res.write(`data: ${text[index]}\n\n`);
      index++;
    } else {
      clearInterval(timer);
      res.write('event: end\ndata: stream ended\n\n');
      res.end();
    }
  }, speed);

  req.on('close', () => {
    clearInterval(timer);
    res.end();
  });
});

app.listen(port, () => {
  console.log(`SSE Typewriter server running at http://localhost:${port}`);
});

如果是nginx部署,需注意添加接口配置

关闭代理缓冲 proxy_buffering off;详见处理 EventStream 不能流式返回的问题:Nginx 配置优化

  1. 前端实现:

利用fetch api处理,示例如下:

js 复制代码
<!DOCTYPE html>
<html>
  <head>
    <title>获取流式数据 Demo</title>
    <style>
      #output {
        font-family: monospace;
        font-size: 24px;
        min-height: 100px;
        border: 1px solid #ccc;
        padding: 10px;
        white-space: pre-wrap;
      }
    </style>
  </head>
  <body>
    <button id="startBtn">获取流式数据</button>
    <div id="output"></div>

    <script>
      document
        .getElementById('startBtn')
        .addEventListener('click', async () => {
          const output = document.getElementById('output');
          output.innerHTML = ''; // 清空输出

          try {
            const response = await fetch('http://localhost:3000/sse', {
              method: 'GET',
              headers: {
                'Content-Type': 'application/json',
              },
            });

            if (!response.ok) {
              throw new Error(`HTTP error! status: ${response.status}`);
            }

            // 获取可读流
            const reader = response.body.getReader();
            const decoder = new TextDecoder();

            // 递归读取流
            const readChunk = async () => {
              const { done, value } = await reader.read();

              if (done) {
                return;
              }

              // 解码并显示字符
              const chunk = decoder.decode(value);
              const text = chunk.split('data: ')[1].replace('\n\n', '');
              console.log(text);

              output.innerHTML += text || '';

              // 继续读取下一个字符
              readChunk();
            };

            readChunk();
          } catch (error) {
            console.error('Error:', error);
            output.textContent = 'Error: ' + error.message;
            cursor.remove();
          }
        });
    </script>
  </body>
</html>

这里前端处理方式还有eventSoure对象,但有局限,只支持get请求。以及相关库实现@microsoft/fetch-event-source。流式处理前端方案对比详见:juejin.cn/post/747810... juejin.cn/post/747849...

2. 体验优化

  1. 暂停控制:通过AbortController中断流

示例:

js 复制代码
const controller = new AbortController(); //创建AbortController对象

async function streamData() {
  try {
    const response = await fetch('/stream-endpoint', {
      signal: controller.signal // 添加signal
    });
    
    const reader = response.body.getReader();
    
    while (true) {
      const { done, value } = await reader.read();
      if (done) break;
      console.log('收到数据块:', value);
    }
  } catch (err) {
    if (err.name === 'AbortError') {
      console.log('流已中断');
    } else {
      console.error('流错误:', err);
    }
  }
}

// 中断流
function cancelStream() {
  controller.abort(); // 在合适时机触发abort,取消请求
}
  1. 智能滚动:仅当用户未手动滚动时自动滚动到底部
js 复制代码
// 智能滚动:仅当用户未手动滚动时生效
if (isNearBottom()) container.scrollTop = container.scrollHeight;

 // 判断是否接近底部
  isNearBottom() {
    const { scrollTop, scrollHeight, clientHeight } = this.container;
    return scrollHeight - (scrollTop + clientHeight) < this.threshold;
  }
  
this.threshold = 10; // 距离底部的阈值(px),10,为举例
相关推荐
晴天161 小时前
前端 postMessage 使用场景
前端·javascript·网络
zhanghaha13142 小时前
HTML系列教程:15_标签 全称 + 简写 超详细讲解(新手专用)
前端·html
zhanghaha13142 小时前
HTML系列教程:14_HTML 表单与输入框 <form>、<input> 零基础详解
java·前端·javascript
IMPYLH2 小时前
HTML 的 <samp> 元素
前端·网络·html
IMPYLH2 小时前
HTML 的 <script> 元素
前端·html
weixin_493503677 小时前
Vue3 前端生成 PDF:会员证书与活动签到表的三种打印方案与踩坑记录
前端·pdf·状态模式
尾善爱看海10 小时前
前端算法与手写题集
前端·算法
徐小夕10 小时前
JitWord 4.0 万字分享:从协同工具到AI Word操作系统,聊聊3年产品创业史
前端·vue.js·后端
萧鼎12 小时前
Python 高性能Web框架神器 FastAPI:自动生成API文、基于Pydant、异步请求处理全搞定
前端·python·fastapi
IT_陈寒12 小时前
Redis误用keys命令把生产环境搞崩了,血的教训
前端·人工智能·后端