当前位置: 首页 > news >正文

AI聊天助手:如何实现打字机效果的流式渲染

目录

1.写好 Prompt 让 AI 听懂人话

2. SSE流式通信

通过 OpenAI SDK 调用大模型的流式接口

OpenAI SDK 内部实现(简化版),返回异步迭代器

前端请求接口获取响应(大模型的key要写在后端,减少在前端暴露敏感信息)

3. Markdown 增量渲染优化


1.写好 Prompt 让 AI 听懂人话

  • 纯文本prompt:可读性强,适用于简单任务、日常聊天;但在应用开发中,输出会不稳定,难以复用

  • markdown格式prompt:结构清晰,适用的模型比较广,也比较通用;但以缩进体现层级,不适合多级嵌套的数据结构

  • xml格式prompt:对于复杂的逻辑可以选择xml格式,对多级嵌套的数据结构相对友好,AI输出的结果也相对稳定

提示词一般可以拆分为角色定义、上下文描述、任务说明、约束条件、输出格式、用户问题和示例等结构

<task> <!--角色 --> <role> 你是一个专业的视频转录分析 AI 助手。核心职责:1. …… 2.……</role> <!-- 上下文内容 --> <context> <!--历史对话 --> <conversationHistory></conversationHistory> </context> <!--约束 --> <constraints></constraints> <!--分析步骤 --> <instructions> <step name="1. ……"></step> </step> </instructions> <!--输出格式 --> <outputFormat> </outputFormat> <!--例子 --> <examples></examples> <!--用户问题 --> <userQuestion></userQuestion> </task>

2. SSE流式通信

通过 OpenAI SDK 调用大模型的流式接口
const client=new OpenAI({ apiKey, baseURL });//填入调用模型的key和调用接口 const completion = await client.chat.completions.create({ model: 'glm-5',//调用模型 messages: [ { role: 'system', content: systemPrompt }, { role: 'user', content: currentQuestion || '' }, ], stream: true,//开启流式 });
OpenAI SDK 内部实现(简化版),返回异步迭代器
class Stream<T> implements AsyncIterable<T> { constructor(private response: Response) {}//fetch请求模型接口,获取 response 对象 async *[Symbol.asyncIterator](): AsyncGenerator<T> { const reader = this.response.body!.getReader(); const decoder = new TextDecoder(); try { while (true) { const { done, value } = await reader.read();//读取流数据(二进制) if (done) break; const chunk = decoder.decode(value);//解析为字符串 const lines = chunk.split('\n'); for (const line of lines) { if (line.startsWith('data: ')) { const data = line.slice(6); if (data === '[DONE]') return; const parsed = JSON.parse(data); yield parsed; // yield 生成下一个值(异步迭代器的返回值) } } } } finally { reader.releaseLock(); } } }
const encoder = new TextEncoder();//TextEncoder 将字符串转换为 Uint8Array const stream = new ReadableStream({ async start(controller) { try { // 👉 使用 for await 遍历 AsyncIterable for await (const chunk of completion) { const content = chunk.choices[0]?.delta?.content; if (content) { const data = JSON.stringify({ content }); controller.enqueue(encoder.encode(`data: ${data}\n\n`)); } } controller.close(); } catch (error) { controller.error(error); } }, }); return new NextResponse(stream);//封装为SSE流传给前端
前端请求接口获取响应(大模型的key要写在后端,减少在前端暴露敏感信息)
const response = await fetch('/api/chat', { method: 'POST', body: JSON.stringify({ messages }), }); const reader = response.body!.getReader(); // 👈 读取后端的响应 const decoder = new TextDecoder(); while (true) { const { done, value } = await reader.read(); // 👈 从网络读取 if (done) break; const chunk = decoder.decode(value); console.log(chunk); // 👈 处理数据 }

3. Markdown 增量渲染优化

Markdown 标题需要在 行尾有换行符 才能识别
每次获取流数据的时候都以 \n分割数据,然后再进行拼接;减少不完整的情况,不完整时当作普通文本,完整后自动转换为正确的 HTML 元素

const reader = response.body?.getReader(); const decoder = new TextDecoder(); let fullContent = ''; if (reader) { while (true) { const { done, value } = await reader.read(); if (done) break; const chunk = decoder.decode(value); const lines = chunk.split('\n'); for (const line of lines) { if (line.startsWith('data: ')) { const data = line.slice(6); if (data === '[DONE]') { break; } try { const parsed = JSON.parse(data); if (parsed.content) { fullContent += parsed.content; setIsLoading(false); setMessages((prev) => { const newMessages = [...prev]; if (newMessages.length > 0) { newMessages[newMessages.length - 1] = { role: 'assistant', content: fullContent, }; } return newMessages; }); } } catch (error) { console.error('Error parsing JSON:', error); } } } } }
http://www.jsqmd.com/news/653099/

相关文章:

  • 源码级赋能:基于 Spring Boot 的 AI 视频管理平台二次开发与低代码集成实战
  • 告别繁琐!手把手教你封装超实用Android原生Adapter基类
  • 高效学习挖漏洞!全网最全的挖洞平台 + 零基础到精通实战指南
  • 端到端的“两极对话”:TCP和UDP,你天天用却未必懂
  • 逆向某多Anti-Content参数:从定位到环境补全的实战解析
  • 3分钟快速汉化:Axure RP中文语言包终极指南
  • 如何用 performance.navigation 判断页面刷新并清理缓存
  • 有什么好用的AI来辅助写代码吗
  • 软件聊天机器人中的意图识别技术
  • 强化学习的实战演进:从虚拟博弈到实体操控
  • Agent Marketplace:未来的AI应用商店长什么样?
  • 3步解锁:Nucleus Co-Op带你体验单机游戏多人同屏的魔法
  • 从石墨烯芯片到简历微调:2026奇点大会硬核披露AI简历优化器底层架构(含3类Transformer轻量化部署路径)
  • STM32CubeIDE HAL库实战:MPU9250传感器数据读取全流程(附避坑指南)
  • Bootstrap制作后台管理系统布局 Bootstrap如何搭建Dashboard框架.txt
  • SITS2026正式发布:2024年唯一经Gartner交叉验证的生成式AI应用成熟度评估框架
  • 《SAP FICO系统配置从入门到精通共40篇》019、内部订单(IO)管理:订单类型与结算规则
  • 雀魂Mod Plus终极教程:三步解锁全角色皮肤的免费指南
  • 新加坡榜鹅:从蛮荒之地到AI创新热土,自动驾驶与智慧小镇共筑科技新篇
  • C++ vs PHP vs Python:三大编程语言终极对比
  • 国泰君安国际荣获2025年度离岸中资基金大奖“货币市场基金 - 港币(1年)”冠军
  • 逆向工程实战:解码大众奥迪碟盒通信协议,打造个性化车载音频中枢
  • 被n整除的n位数
  • HBA卡深度解析:从基础原理到企业级应用实战
  • 走了弯路的捷径——V5 Q-Learning的诱惑与反思
  • 2026 - 解决Typora文档内快捷键失效(与其他软件快捷键冲突)
  • 用好 Semantic Scholar API:一条更聪明的学术检索路径
  • 如何突破Cursor免费限制:3步解锁AI编程无限使用
  • 微服务配置中心 Apollo-Nacos 实战
  • 猫抓插件:三步搞定网页视频音频下载的终极解决方案