如何在Django Rest Framework中使用OpenAI stream=true并保存返回内容
解决OpenAI流式响应返回React前端及数据保存问题
后端(Django)实现
1. 用StreamingHttpResponse结合生成器处理流式响应
直接通过生成器迭代OpenAI的流式结果,同时收集完整内容用于后续保存。推荐用SSE(Server-Sent Events)格式返回数据,方便前端解析:
from django.http import StreamingHttpResponse import openai from your_app.models import GeneratedContent # 替换为你的数据模型 def stream_openai_response(request): prompt = "Write me a story about dogs." MAX_TOKENS = 1000 openai.api_key = "your-api-key" # 或全局配置 full_response = [] # 收集完整输出用于保存 def generate_stream(): nonlocal full_response # 调用OpenAI流式接口 completion = openai.Completion.create( model="text-davinci-003", prompt=prompt, temperature=0.7, max_tokens=MAX_TOKENS, frequency_penalty=1.0, presence_penalty=1.0, stream=True, ) # 迭代每个流式chunk for chunk in completion: text_chunk = chunk.choices[0].text full_response.append(text_chunk) # 按SSE格式返回,必须以\n\n结尾 yield f"data: {text_chunk}\n\n" # 流式传输完成后,保存完整内容到数据库 GeneratedContent.objects.create( prompt=prompt, content=''.join(full_response) ) # 返回流式响应,设置正确的响应头 return StreamingHttpResponse( generate_stream(), content_type='text/event-stream', headers={ 'Cache-Control': 'no-cache', 'Connection': 'keep-alive', } )
核心要点
- 用
nonlocal让生成器能修改外部变量,收集所有文本chunk - SSE格式要求每个数据块以
data:开头、\n\n结尾,前端才能正确识别 - 响应头
Cache-Control: no-cache避免浏览器缓存,Connection: keep-alive维持长连接
前端(React)实现
用EventSource接收SSE流式数据,实时更新页面内容:
import { useState, useEffect } from 'react'; function DogStoryGenerator() { const [story, setStory] = useState(''); const [isGenerating, setIsGenerating] = useState(false); useEffect(() => { let eventSource; if (isGenerating) { // 建立SSE连接 eventSource = new EventSource('/api/stream-openai-response/'); // 接收每个文本块 eventSource.onmessage = (event) => { setStory(prev => prev + event.data); }; // 处理错误或连接关闭 eventSource.onerror = (err) => { console.error('SSE连接异常:', err); eventSource.close(); setIsGenerating(false); }; eventSource.onclose = () => { setIsGenerating(false); }; } // 组件卸载时关闭连接 return () => { eventSource?.close(); }; }, [isGenerating]); const startGenerate = () => { setStory(''); setIsGenerating(true); }; return ( <div> <button onClick={startGenerate} disabled={isGenerating}> {isGenerating ? '生成中...' : '生成狗狗故事'} </button> <div className="story-content" style={{ marginTop: '16px', whiteSpace: 'pre-wrap' }}> {story} </div> </div> ); } export default DogStoryGenerator;
替代方案:用Fetch API的ReadableStream
如果不想用SSE,也可以直接用Fetch处理原始文本流:
const startGenerate = async () => { setStory(''); setIsGenerating(true); try { const res = await fetch('/api/stream-openai-response/', { method: 'GET' }); const reader = res.body.getReader(); const decoder = new TextDecoder(); while (true) { const { done, value } = await reader.read(); if (done) break; const text = decoder.decode(value, { stream: true }); setStory(prev => prev + text); } } catch (err) { console.error('请求失败:', err); } finally { setIsGenerating(false); } };
此时后端生成器不需要SSE包装,直接yield原始文本即可:
def generate_stream(): nonlocal full_response completion = openai.Completion.create(...) for chunk in completion: text_chunk = chunk.choices[0].text full_response.append(text_chunk) yield text_chunk # 保存数据...
数据保存注意事项
- 保存逻辑放在生成器的最后,确保所有流式chunk都已收集完成
- 保存操作不会影响前端的流式体验,因为此时所有数据已发送给前端
- 可根据需求修改保存目标,比如文件、Redis或其他存储系统
内容的提问来源于stack exchange,提问作者rd360
相关产品推荐
相关产品推荐

