Day 4 内容,聚焦 接入真实 AI 流式接口(SSE),将模拟数据替换为与 Ollama / OpenAI 兼容的流式后端,实现真正的“边生成边渲染”。
前端 + AI 进阶学习路线|Week 1-2:流式体验优化
Day 4:接入真实 AI 流式接口(SSE)
学习时间:2025年12月28日(星期日)
关键词:SSE、流式 API、fetchEventSource、Ollama、OpenAI、token 流
🎯 今日学习目标
- 理解服务器发送事件(SSE)在 AI 流式响应中的作用
- 使用
fetchEventSource消费兼容 OpenAI/Ollama 的流式接口 - 将真实 token 数据逐块拼接并渲染到聊天界面
- 处理连接状态(加载中、错误、完成)
💡 为什么需要真实流式接口?
前 3 天我们用 模拟字符串 演示了流式效果,但真实 AI 应用需与后端模型通信。主流本地/云模型均支持 流式响应:
| 模型平台 | 流式协议 | 响应格式 |
|---|---|---|
| Ollama | SSE(Server-Sent Events) | {"model":"llama3","response":"Hello"} |
| OpenAI | SSE | data: {"choices": [{"delta": {"content": "world"}}]} |
| vLLM / LM Studio | SSE | 兼容 OpenAI 格式 |
✅ 目标:编写一个 通用流式客户端,可无缝切换不同后端
📚 核心技术:SSE 与 fetchEventSource
- SSE(Server-Sent Events):基于 HTTP 的单向流(服务器 → 客户端)
- 浏览器原生支持
EventSource,但功能有限(无法传 headers、难重试) - 推荐库:
@microsoft/fetch-event-source
→ 支持自定义 headers、重连、取消、兼容 OpenAI/Ollama
npm install @microsoft/fetch-event-source
🔧 动手实践:构建通用流式 AI 客户端
步骤 1:安装依赖
npm install @microsoft/fetch-event-source
步骤 2:创建 aiClient.js(通用流式请求函数)
// src/lib/aiClient.js
import { fetchEventSource } from '@microsoft/fetch-event-source';/*** 通用流式 AI 请求函数* @param {string} endpoint - API 地址(如 http://localhost:11434/api/generate)* @param {string} prompt - 用户输入* @param {function} onToken - 收到 token 时的回调 (token: string) => void* @param {function} onComplete - 流结束回调* @param {function} onError - 错误回调*/
export const streamAIResponse = async ({endpoint,prompt,systemPrompt = '',model = 'llama3',onToken,onComplete,onError,
}) => {// 构建请求体(兼容 Ollama / OpenAI 风格)const payload = {model,prompt,system: system_prompt,stream: true,};// 用于累积已接收内容(可选,用于调试)let fullResponse = '';try {await fetchEventSource(endpoint, {method: 'POST',headers: {'Content-Type': 'application/json',},body: JSON.stringify(payload),openWhenHidden: true, // 页面后台时也保持连接onmessage(event) {if (event.data === '[DONE]') {onComplete?.();return;}try {const data = JSON.parse(event.data);// --- 兼容不同后端 ---let token = '';// Ollama 格式if (data.response !== undefined) {token = data.response;}// OpenAI 格式else if (data.choices?.[0]?.delta?.content) {token = data.choices[0].delta.content;}if (token) {fullResponse += token;onToken?.(token);}} catch (e) {console.warn('Failed to parse SSE message:', event.data);}},onerror(error) {console.error('SSE Error:', error);onError?.(error);throw error; // 触发重连或终止},});} catch (error) {console.error('Stream failed:', error);onError?.(error);}
};
💡 提示:Ollama 默认 SSE 接口为
POST /api/generate,OpenAI 为POST /v1/chat/completions
步骤 3:更新 StreamingChat.jsx,接入真实流
// src/components/StreamingChat.jsx(关键更新部分)
import { streamAIResponse } from '../lib/aiClient';// ... 其他 state ...const handleSend = async (userMessage) => {// 1. 添加用户消息const newUserMsg = { id: Date.now(), role: 'user', content: userMessage };setMessages(prev => [...prev, newUserMsg]);// 2. 清空流式状态setStreamingMessage('');setIsStreaming(true);setError(null);// 3. 调用 AI 流式接口try {await streamAIResponse({// 本地 Ollama 示例(请确保已运行 ollama serve)endpoint: 'http://localhost:11434/api/generate',prompt: userMessage,model: 'llama3',// 或 OpenAI(需代理解决 CORS)// endpoint: 'https://api.openai.com/v1/chat/completions',// headers: { Authorization: `Bearer ${apiKey}` },onToken: (token) => {setStreamingMessage(prev => prev + token);},onComplete: () => {// 将完整消息加入历史setMessages(prev => [...prev,{ id: Date.now(), role: 'assistant', content: streamingMessageRef.current }]);setIsStreaming(false);},onError: (error) => {setError('AI 响应失败,请重试');setIsStreaming(false);}});} catch (err) {setError('请求出错');setIsStreaming(false);}
};// 使用 useRef 保持最新 streamingMessage(避免闭包问题)
const streamingMessageRef = useRef('');
useEffect(() => {streamingMessageRef.current = streamingMessage;
}, [streamingMessage]);
⚠️ CORS 注意:
- Ollama 默认不支持跨域 → 需启动时加
--host 0.0.0.0,或用代理(见下方)- OpenAI 需通过后端代理调用(前端直连会暴露 API Key)
✅ 本地测试 Ollama 的快速方案:
# 终端运行(允许跨域)
ollama serve
# 然后在另一个终端
npx http-server -p 8080 --cors # 临时静态服务器(非必需)# 或使用代理(推荐):在 React 中配置
// package.json 添加(开发阶段)
"proxy": "http://localhost:11434"
// 然后 endpoint 改为 '/api/generate'
✅ 效果验证
- 本地运行
ollama run llama3 - 启动 React 应用,输入问题(如“JavaScript 闭包是什么?”)
- 观察 AI 逐词输出,支持暂停/继续/回退
- 断网时显示错误,重试可恢复
🤔 思考与延伸
- 如何支持 OpenAI 而不暴露 Key?
→ 需要简单后端代理(如 Express +axios转发) - 流式中断后如何续传?
→ 记录已生成内容,下次请求带上完整上下文 - 能否自动检测后端类型(Ollama vs OpenAI)?
→ 发送试探请求,根据响应结构判断
📅 明日预告
Day 5:多模态输入初探 —— 图片上传与预览
- 实现文件拖拽上传 + 实时预览
- 支持截图粘贴(Clipboard API)
- 为“图文混合 AI 对话”打下基础
✍️ 小结
今天,我们跨越了“模拟”与“真实”的界限,将本地运行的 AI 模型(如 Ollama)接入前端,实现了 端到端的流式对话。这不仅是一个技术里程碑,更是迈向 AI 原生应用 的关键一步。
💬 你在连接 Ollama 时是否遇到 CORS 问题?试试在
package.json中加"proxy": "http://localhost:11434"。欢迎分享你的模型输出效果!