目录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 name1. ……/step /step /instructions !--输出格式 -- outputFormat /outputFormat !--例子 -- examples/examples !--用户问题 -- userQuestion/userQuestion /task2. SSE流式通信通过 OpenAI SDK 调用大模型的流式接口const clientnew 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 StreamT implements AsyncIterableT { constructor(private response: Response) {}//fetch请求模型接口获取 response 对象 async *[Symbol.asyncIterator](): AsyncGeneratorT { 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); } } } } }