Flask流式聊天界面搭建求助:OpenAI助手流输出异常及集成问题
Flask + OpenAI Assistants API 流式聊天解决方案
一、修复流式输出不实时问题
你的代码存在三个核心问题:浏览器缓冲内容、格式不匹配、EventHandler与手动遍历冲突,以下是修复后的后端代码:
from flask import current_app, Response, request, session from flask.stream_with_context import stream_with_context import json @main.route('/chat_stream', methods=['POST']) def chat_stream(): # 获取用户输入 user_input = request.json.get('message', '') if not user_input: return Response( json.dumps({"error": "未提供消息内容"}), content_type='application/json', status=400 ) # 从全局配置获取OpenAI客户端 chat_session = current_app.config['chat_session'] client = chat_session.client # 从Session维护用户对话线程(首次访问自动创建) if 'thread_id' not in session: thread = client.beta.threads.create() session['thread_id'] = thread.id thread_id = session['thread_id'] # 添加用户消息到对话线程 client.beta.threads.messages.create( thread_id=thread_id, role='user', content=user_input ) @stream_with_context def generate(): try: # 流式运行助手,禁用EventHandler避免冲突 stream = client.beta.threads.runs.stream( thread_id=thread_id, assistant_id=chat_session.assistant_id, instructions='system_prompt' ) for chunk in stream: # 仅处理文本增量片段 if hasattr(chunk, 'data') and chunk.data.type == 'thread.message.delta': delta = chunk.data.delta if delta.content and delta.content[0].type == 'text': text_chunk = delta.content[0].text.value # 按SSE格式返回,强制浏览器实时接收 yield f"data: {json.dumps({'content': text_chunk})}\n\n" except Exception as e: yield f"data: {json.dumps({'error': str(e)})}\n\n" # 配置响应头,禁用缓存并指定SSE类型 return Response( generate(), content_type='text/event-stream', headers={ 'Cache-Control': 'no-cache', 'Connection': 'keep-alive' } )
关键修复点:
- 改用
text/event-stream作为响应类型,这是服务器发送事件(SSE)的标准格式,浏览器会实时处理每个返回块 - 每个片段按
data: {JSON内容}\n\n格式返回,确保前端可解析 - 移除
EventHandler,避免与手动遍历流的逻辑冲突 - 添加
Cache-Control: no-cache头,彻底禁用浏览器缓冲
二、集成多轮聊天界面
以下是配套的前端HTML代码,实现实时流式打字机效果与多轮对话展示:
<!DOCTYPE html> <html> <head> <title>AI助手聊天</title> <style> .chat-container { max-width: 800px; margin: 20px auto; border: 1px solid #eee; border-radius: 8px; padding: 20px; height: 600px; overflow-y: auto; } .message { margin: 10px 0; padding: 12px 16px; border-radius: 8px; max-width: 75%; word-wrap: break-word; } .user-message { background-color: #007bff; color: white; margin-left: auto; } .assistant-message { background-color: #f8f9fa; color: #333; margin-right: auto; } .input-area { max-width: 800px; margin: 0 auto; display: flex; gap: 10px; } #message-input { flex: 1; padding: 12px; border-radius: 8px; border: 1px solid #ddd; font-size: 16px; } #send-btn { padding: 12px 24px; background-color: #007bff; color: white; border: none; border-radius: 8px; cursor: pointer; font-size: 16px; } </style> </head> <body> <div class="chat-container" id="chat-container"></div> <div class="input-area"> <input type="text" id="message-input" placeholder="输入消息..."> <button id="send-btn">发送</button> </div> <script> const chatContainer = document.getElementById('chat-container'); const messageInput = document.getElementById('message-input'); const sendBtn = document.getElementById('send-btn'); // 添加消息到聊天容器 function addMessage(content, role) { const messageDiv = document.createElement('div'); messageDiv.className = `message ${role}-message`; messageDiv.textContent = content; chatContainer.appendChild(messageDiv); chatContainer.scrollTop = chatContainer.scrollHeight; } // 发送消息并接收流式响应 async function sendMessage() { const message = messageInput.value.trim(); if (!message) return; // 显示用户消息 addMessage(message, 'user'); messageInput.value = ''; // 创建SSE连接接收流式数据 const eventSource = new EventSource(`/chat_stream`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ message: message }) }); // 临时存储助手消息元素,实现实时追加 let assistantMsgEl = null; eventSource.onmessage = function(event) { const data = JSON.parse(event.data); if (data.error) { addMessage(`错误: ${data.error}`, 'assistant'); eventSource.close(); return; } if (!assistantMsgEl) { assistantMsgEl = document.createElement('div'); assistantMsgEl.className = 'message assistant-message'; chatContainer.appendChild(assistantMsgEl); } // 追加片段内容 assistantMsgEl.textContent += data.content; chatContainer.scrollTop = chatContainer.scrollHeight; }; // 处理连接错误 eventSource.onerror = function(error) { console.error('连接错误:', error); eventSource.close(); if (assistantMsgEl) assistantMsgEl.textContent += '\n[连接中断]'; }; // 流结束时关闭连接 eventSource.addEventListener('close', () => eventSource.close()); } // 绑定发送事件 sendBtn.addEventListener('click', sendMessage); messageInput.addEventListener('keypress', e => { if (e.key === 'Enter') sendMessage(); }); </script> </body> </html>
前端核心逻辑:
- 使用
EventSource建立SSE连接,实时接收后端返回的文本片段 - 为助手消息创建临时DOM元素,每次收到片段就追加内容,实现打字机效果
- 自动滚动到聊天底部,保证用户始终看到最新内容
- 处理连接错误与中断情况,提升界面友好性
三、多轮对话维护
通过Flask的session存储用户的thread_id,每个用户的对话历史会自动保存在OpenAI的线程中,无需额外维护本地会话数据,实现天然的多轮对话支持。
内容的提问来源于stack exchange,提问作者John Chambers
相关产品推荐
相关产品推荐

