大厂常考的面试题--流式输出

284 阅读4分钟

前言

流式输出是一种增量式的数据传输方式,它允许大模型在生成内容的同时,将已生成的部分立即发送给客户端,而不必等待整个响应完成。这种方式的核心特点是:

  • 实时性:模型生成一小段内容就立即传输,用户几乎可以实时看到生成过程
  • 增量传输:通过 SSE(Server-Sent Events)或 WebSocket 协议实现服务器到客户端的持续数据流
  • 低感知延迟:用户通常在 100ms 内就能看到首批内容,大幅降低等待感

流式输出的原理剖析

什么是流式输出?

简单来说,流式输出就是数据不是一次性全部返回,而是分批次逐步传输给客户端。在 AI 生成式内容的场景中,模型会一个 token 一个 token 地生成内容,每生成一个 token 就立即发送给客户端,客户端再实时显示给用户。

为什么需要流式输出?

从后端角度来看,LLM API 通常是以流式的方式提供内容,这是因为模型生成内容是逐步进行的,每个 token 的生成都有一定的开销,并且可能需要付费。流式输出可以让用户更快地看到响应,同时也能减少不必要的等待时间。从前端角度来看,流式输出能够提供更好的用户体验,让用户感受到内容是实时生成的,增强了交互的流畅性。

前端如何实现流式输出?

基于 setInterval 和事件机制

在前端,我们可以使用 setInterval 函数结合事件机制来模拟流式输出。 setInterval 可以定时触发函数,模拟数据的逐步生成。而事件机制则可以在数据到达时触发相应的处理函数,更新页面内容。以下是一个简单的示例代码:

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>流式输出示例</title>
</head>
<body>
    <div id="output"></div>
    <script>
        const outputElement = document.getElementById('output');
        const data = '这是一个流式输出的示例内容,我们将逐步显示它。';
        let index = 0;

        const intervalId = setInterval(() => {
            if (index < data.length) {
                outputElement.textContent += data[index];
                index++;
            } else {
                clearInterval(intervalId);
            }
        }, 100);
    </script>
</body>
</html>

在这个示例中,我们使用 setInterval 每隔 100 毫秒向页面添加一个字符,模拟流式输出的效果。

后端如何实现流式输出?

基于 Socket 的长连接

Socket 是一种长连接技术,它允许客户端和服务器之间保持持久的连接,双方可以随时发送和接收数据。在后端实现流式输出时,可以使用 Socket 来实现数据的实时传输。以下是一个简单的 Node.js 示例:

// 启动http server
// 引入express 框架
// import express from 'express'
// node 最初的commonjs 的模块化方案
const express = require('express')
// console.log(express)
const app =express();// 后端应用 App
// 路由
app.get('/',(req,res)=>{
    // 返回index.html
    // res.send("hello");// str 
    // response 响应对象 可以返回给前端的内容 有请求req 用户 ,用户通过浏览器(proxy) url localhost:1314 + get +path / 
    // http 足够简单  高并发 用户赶快走  断开联系 
    // __dirname  项目的根路径 
    console.log(__dirname)
    res.sendFile(__dirname+'/index.html')
})
// 添加一个支持server push 的路由
app.get('/sse',(req,res)=>{
    // 支持server push  不断地服务器端推送  少量的 
    // 设置响应头 
    res.set({
        // stream 文本流,事件 
        'Content-Type':'text/event-stream',// 服务器推送的内容类型
        'Cache-Control':'no-cache',// 不缓存  禁止前端使用缓存  
        'Connection':'keep-alive'// 保持连接
    })
    res.flushHeaders()// 刷新响应头
    setInterval(()=>{
        const message = `Current Time is ${new Date().toLocaleTimeString()}`
        // server push 
        res.write(`data:${message}\n\n`)
    },1000)
})

// node 内置模块
const http=require('http').Server(app);// server 基本能力 B/S 架构
// 监听? 伺服状态 伺候用户 
http.listen(1314,()=>{
    console.log('http server is running at 1314')
})

如何理解http.listen()这个函数,顾名思义listen是监听的意思,启动一个HTTP服务器,并让它持续监听指定端口(1314)的传入请求。当服务器成功启动后,会执行回调函数,控制台会打印:http server is running at 1314

image.png

总结

流式输出作为提升用户体验的重要技术,在 AI 产品和其他需要实时数据展示的场景中具有广泛的应用前景。无论是前端还是后端,都有多种实现流式输出的方式。在全栈开发中,我们可以结合前端和后端的技术,实现一个完整的流式输出系统。随着技术的不断发展,流式输出将会变得更加成熟和普及,成为前端开发中不可或缺的一部分。希望通过本文的介绍,大家对流式输出有了更深入的理解,能够在实际项目中灵活运用这一技术。