跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客我的书AI学习GitHub 精选镜像AI 生图工具UI配色美学关于
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
TypeScriptAI大前端

AI 对话页流式处理架构:Web Streams 与 Fetch API 实践

AI 对话应用中,流式响应能显著提升用户体验。通过 Web Streams 和 Fetch API 构建生产者 - 消费者模型,可实现网络 IO、数据解码与 UI 渲染的解耦。核心在于利用 AbortController 管理请求中止,TransformStream 处理粘包半包及行拆分,并结合 SSE 协议解析增量内容。配合 Ant Design X 组件库封装消息展示与发送交互,确保实时性与稳定性。

beaabea发布于 2026/3/16更新于 2026/10/773 浏览
AI 对话页流式处理架构:Web Streams 与 Fetch API 实践

引言

在构建 AI 智能体应用时,为了降低模型搭建复杂度,通常会将数据流转与 UI 实现统一封装。但若要深入理解底层机制,采用'生产者 - 消费者模式'的流式处理架构是更优解。这种架构将网络 IO、数据解码、文本解析与 UI 渲染解耦,从而实现实时流式响应和 UI 增量渲染。

本文基于 Vue 3 + TypeScript + Vite 技术栈,结合 Ant Design Vue 及 Ant Design X Vue 组件库,探讨如何利用 Web Streams API 与 Fetch API 实现这一流程。

从请求发送到 UI 渲染的整体链路如下:

![流式响应整体链路示意图]

流式响应处理

请求管理

我们需要同时控制上游的网络请求和下游的字节流读取,以实现会话的可取消与可停止。

  1. AbortController:作为上游网络中止句柄。在发起新提问前中止旧请求,或用户点击取消时立即终止当前请求。
  2. ReadableStreamDefaultReader:作为下游传输层字节流的读取器句柄。它驱动上游生产者向管道入队字节块,并在用户取消时终止读取。
// 流控制相关句柄
let abortController: AbortController | null = null;
let currentReader: ReadableStreamDefaultReader<Uint8Array> | null = null;

流处理管道

建立一条 Web Streams 解析管线:生产者 → 解码 → 按行拆分 → 消费者。整体处理流程包含以下三个核心环节。

生产者流

负责将上游 reader 获取的 chunk 字节块按背压节奏入队,供下游消费,实现'读 - 推送'循环。同时需将外部触发的取消信号传播到上游,终止读取链路。当状态指示停止或上游耗尽时,关闭控制器并复位响应状态,确保资源释放。

参考文档:ReadableStream

const producerStream = new ReadableStream<Uint8Array>({
  start(controller) {
    function pump() {
      if (!isResponding.value) {
        controller.close();
        return;
      }
      currentReader?.read().then(({ done, value }) => {
        if (done) {
          isResponding.value = false;
          controller.close();
          return;
        }
        controller.enqueue(value); // 推送字节块
        pump();
      });
    }
    pump();
  },
});
转换流

上游产生的二进制字节流需要兼容粘包/半包场景。我们先将字节流解码为字符串流,再定义 TransformStream 按行拆分并过滤空行,确保下游以'完整且非空的文本行'为单位消费。在流结束的 flush() 钩子中冲刷缓冲区,避免丢失收尾数据。

const textStream = producerStream.pipeThrough(
  new TextDecoderStream() as unknown as TransformStream<Uint8Array, string>
);

const lineSplitter = new TransformStream<string, string>({
  transform(chunk, controller) {
    buffer += chunk;
    const lines = buffer.split('\n');
    buffer = lines.pop() || '';
    for (const line of lines) {
      const trimmed = line.trim();
      if (trimmed) controller.enqueue(trimmed);
    }
  },
  flush(controller) {
    const trimmed = buffer.trim();
    if (trimmed) controller.enqueue(trimmed);
  },
});
SSE 解析流

构建 SSE 消费者读取器并驱动 UI 增量渲染。若 done = true,表示传输层流真正结束,调用 releaseLock 释放锁;若读取到 message === '[DONE]',表示应用层结束,业务上不再需要继续读取,此时主动调用 cancel 终止读取,防止连接残留。

const sseStream = textStream.pipeThrough(lineSplitter);
const downstreamReader = sseStream.getReader();

function consume() {
  downstreamReader.read().then(({ done, value }) => {
    if (done) {
      isResponding.value = false;
      downstreamReader.releaseLock();
      return;
    }
    const message = value.replace(/^data:\s*/, '');
    if (message === '[DONE]') {
      downstreamReader.cancel('stream finished');
      return;
    }
    try {
      const parsed = JSON.parse(message);
      const content = parsed?.choices?.[0]?.delta?.content;
      if (content) {
        streamReply += content;
        // 消息增量渲染
        if (chatList.value.length < index + 1) {
          const newReply: ChatItem = {
            key: index,
            role: 'assistant',
            content: streamReply,
          };
          chatList.value.push(newReply);
        } else {
          chatList.value[index]!.content = streamReply;
        }
      }
    } catch (e) {
      console.warn('SSE parse error:', e);
    }
    consume();
  });
}
consume();

整体数据流转逻辑如下:

![数据流转示意图]

API 通信层

发送请求

以 qwen-plus 模型为例,接入方式如下:

async function fetchReply(list: ChatItem[], signal?: AbortSignal) {
  const messages = list.map(({ role, content }) => ({ role, content }));
  return fetch('https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions', {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${import.meta.env.VITE_ALIYUN_API_KEY}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      model: 'qwen-plus',
      messages,
      stream: true,
    }),
    signal,
  });
}

中止请求

  1. 重置 UI 响应状态:标记为非响应中,避免界面显示'正在响应'。
  2. 终止流式读取:若存在读取器,主动取消读取并清理引用。
  3. 终止网络请求:触发中止并清理引用。
const handleCancel = () => {
  isResponding.value = false;
  // 终止读取
  if (currentReader) {
    try {
      currentReader.cancel('user canceled');
    } catch (e) {
      console.error(e);
    }
    currentReader = null;
  }
  // 终止请求
  if (abortController) {
    try {
      abortController.abort('user cancellation');
    } catch (e) {
      console.error(e);
    }
    abortController = null;
  }
  message.error('已取消发送');
};

基础 UI 交互组件封装

UI 设计主要基于 Ant Design Vue 和 Ant Design X Vue。后者专注于 Vue 生态的 AI 组件库,支持 TS 和 TSX,能简化对话式 AI 应用的开发。

消息展示

  1. 功能:展示对话历史,支持多角色渲染及 Markdown 渲染。
  2. Markdown 渲染处理:
import { h } from 'vue';
import { type BubbleProps } from 'ant-design-x-vue';
import { Typography } from 'ant-design-vue';
import markdownit from 'markdown-it';

const md = new markdownit({
  html: false,
  breaks: true,
  linkify: true,
  typographer: true,
});

const renderMarkdown: BubbleProps['messageRender'] = (content) =>
  h(Typography, null, {
    default: () => h('div', { innerHTML: md.render(content) }),
  });
  1. 角色映射配置:
import { UserOutlined } from '@ant-design/icons-vue';
import { h } from 'vue';

const rolesAsObject = {
  assistant: {
    placement: 'start',
    avatar: {
      icon: h(UserOutlined),
      style: { background: '#fde3cf' },
    },
    typing: { step: 5, interval: 20 },
    styles: { maxWidth: '600px' },
    messageRender: renderMarkdown,
  },
  user: {
    placement: 'end',
    avatar: {
      icon: h(UserOutlined),
      style: { background: '#87d068' },
    },
  },
  system: {
    placement: 'start',
    avatar: {
      icon: h(UserOutlined),
      style: { background: '#d9d9d9' },
    },
    styles: { maxWidth: '600px' },
    messageRender: renderMarkdown,
  },
} as const;
  1. 适配 BubbleList 组件的 roles 类型处理:
const bubbleListRoles = rolesAsObject as NonNullable<BubbleListProps['roles']>;
const bubbleItems = computed(() =>
  props.chatList.map((m, idx) => {
    type RoleKey = keyof typeof rolesAsObject;
    const roleKey = (m.role in rolesAsObject ? m.role : 'assistant') as RoleKey;
    return {
      key: m.key ?? idx,
      role: m.role,
      placement: rolesAsObject[roleKey].placement,
      avatar: rolesAsObject[roleKey].avatar,
      content: m.content,
    };
  })
);
  1. 传入 BubbleList 组件:
<!-- ChatBubble 组件 -->
<template>
  <BubbleList :items="bubbleItems" :roles="bubbleListRoles" />
</template>

<!-- 使用方法 -->
<ChatBubble :chat-list="chatList" :md-render="false" />

消息发送

  1. 功能:支持消息输入、发送控制及状态展示。
<template>
  <Sender
    :value="props.inputText"
    @update:value="onUpdateValue"
    :loading="props.isResponding"
    :auto-size="{ minRows: 2, maxRows: 6 }"
    :onSubmit="handleSubmit"
    :onCancel="handleCancel"
  />
</template>

<script setup lang="ts">
import { Sender } from 'ant-design-x-vue';

const props = defineProps({
  inputText: String,
  isResponding: Boolean,
});

const emit = defineEmits(['submit', 'cancel', 'update:inputText']);

const onUpdateValue = (val: string) => {
  emit('update:inputText', val);
};

const handleSubmit = () => {
  emit('submit', props.inputText);
};

const handleCancel = () => {
  emit('cancel');
};
</script>

以上是 AI 对话页中最基础也是必不可少的部分。在实际业务场景中,还可以根据需求扩展更多交互配置,例如集成剪贴板工具实现一键复制功能等。

目录

  1. 引言
  2. 流式响应处理
  3. 请求管理
  4. 流处理管道
  5. 生产者流
  6. 转换流
  7. SSE 解析流
  8. API 通信层
  9. 发送请求
  10. 中止请求
  11. 基础 UI 交互组件封装
  12. 消息展示
  13. 消息发送

更多推荐文章

查看全部
  • Spring Cloud Feign 性能优化:连接池与超时配置详解
  • Python 与 C++ 参数传递机制详解
  • GitHub 项目源码查找与下载指南
  • Qwen-Image-Lightning 本地部署与实战指南
  • PyCharm 集成 GitHub Copilot:从环境准备到实战配置
  • Soft Prompts 微调大模型技术综述:超越 LoRA 的选择
  • AI 产品经理岗位需求分析与职业发展路径
  • LFM2.5-1.2B-Thinking 实战:打造个人 AI 写作助手
  • 主流无人机厂商 RemoteID 支持情况汇总
  • Flutter 组件 genkit 在鸿蒙平台的适配与 AI 应用实践
  • Python 使用 ezdxf 读写 DXF 文件指南
  • Ubuntu 22.04 安装 ROS2 实战:鱼香 ROS 一键部署
  • SubtitleEdit Purfview Faster Whisper XXL 引擎安装失败修复指南
  • Rust 异步 Web 框架 Axum 深入原理与高级用法
  • Buzz 语音转文字离线工具安装与使用指南(含 Whisper 模型)
  • 延凡 AI 工业视觉分析平台:制造业质检与效率优化方案
  • 通义万相 2.1 视频生成模型技术特性与应用场景
  • 扩散模型(Diffusion Model)原理与图像生成实战
  • 动态规划解决单双序列问题:最长递增子序列与正则匹配
  • Distil-Whisper 快速入门:6 倍加速的语音识别方案

相关免费在线工具

  • RSA密钥对生成器

    生成新的随机RSA私钥和公钥pem证书。 在线工具,RSA密钥对生成器在线工具,online

  • Mermaid 预览与可视化编辑

    基于 Mermaid.js 实时预览流程图、时序图等图表,支持源码编辑与即时渲染。 在线工具,Mermaid 预览与可视化编辑在线工具,online

  • 随机西班牙地址生成器

    随机生成西班牙地址(支持马德里、加泰罗尼亚、安达卢西亚、瓦伦西亚筛选),支持数量快捷选择、显示全部与下载。 在线工具,随机西班牙地址生成器在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

  • Markdown转HTML

    将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online