【项目实训5】自建前端配置与测试

—— written by Unalome (2025.04.29-30)

由于最终的输出界面需要对用户友好,当前阶段的目标为将终端流式输出的转为自建前端的输出,以便后续接入Cherry Studio。

一、FastAPI+SSE流式输出到前端

1. FastAPI基础配置

CORS跨域处理

  • 解决前后端端口不一致时的跨域问题(其中allow-origins字段在正式上线时改成具体的前端域名以消除安全隐患)
# 初始化
app = FastAPI()

# 添加CORS中间件(允许所有来源测试)
app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],  # 允许所有来源
    allow_credentials=True,  # 允许携带凭证
    allow_methods=["*"],  # 允许所有HTTP方法
    allow_headers=["*"],  # 允许所有请求头
)

异步服务启动

  • uvicorn是基于asyncio的ASGI服务器,支持异步请求处理。相比同步框架flask可提升高并发场景下的吞吐量
if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app, host="0.0.0.0", port=8003)

2. SSE流式传输实现

Server-Sent Events(SSE)是一种基于 HTTP 协议的轻量级实时通信技术,允许服务器主动向客户端单向推送数据流。其核心优势在于低延迟、低开销和协议简洁性:基于纯文本的 text/event-stream 格式,无需复杂的握手或帧协议;支持自动重连与消息分界,天然适配高延迟网络环境。在 Web 应用中,SSE 结合异步后端框架(如 FastAPI)可高效实现高并发的实时数据传输,成为现代流式交互的重要技术支撑。

核心组件 StreamingResponse

  • 响应头启用 text/event-stream 代表使用SSE
  • Cache-Control: no-cache 防止浏览器或中间代理缓存响应,减少延迟
  • X-Accel-Buffering: no 解决反向代理的缓冲问题
@app.post("/rag-stream")
async def rag_stream(request: Request):
    data = await request.json()
    query = data.get("query", "")
    return StreamingResponse(
        generate_stream(query),
        media_type="text/event-stream",
        headers={
            "Cache-Control": "no-cache",
            "X-Accel-Buffering": "no"
        }
    )

流式生成器 generate_stream

  • 使用 AsyncGenerator 类型,通过 yield 逐块返回数据。每次 yield 发送一个 SSE 事件,格式为 (data: 内容\n\n)。
  • 流式调用大模型API:async for chunk in stream 异步迭代每个返回的 chunk,实时转发给前端。
async def generate_stream(query: str) -> AsyncGenerator[str, None]:
    # ....检索逻辑....
    try:
        stream = await client.chat.completions.create(
            model="DeepSeek-R1",
            messages=[{"role": "user", "content": prompt}],
            stream=True,
            temperature=0.1
        )
        
        async for chunk in stream:
            if content := chunk.choices[0].delta.content:
                yield f"data: {content}\n\n"
                
    except Exception as e:
        yield f"data: 错误:{str(e)}\n\n"
    finally:
        yield "data: [DONE]\n\n"

性能优化

  • 全链路异步及资源释放:全程使用 async/await 避免同步阻塞操作拖慢整体性能,try/finally 确保连接及时释放
  • 直接调用大模型API绕过 Haystack 的 Pipeline 封装,减少中间环节的延迟,直接控制流式响应的生成逻辑,灵活处理每个 chunk
client = AsyncOpenAI(...)
stream = await client.chat.completions.create(...) 

二、输出格式规范化

简易前端使用html+css搭建

SSE 数据解析与清洗

  • 数据分块处理:后端通过 text/event-stream 格式返回数据,每个事件以双换行符(\n\n)分隔。前端通过 split('\n\n') 切分数据块,确保完整解析每个事件。
chunk.split('\n\n').forEach(event => { ... });
  • 过滤有效数据:仅处理以 data: 开头的事件内容,过滤协议无关信息(注释、空行)
if (!event.startsWith('data: ')) return;
let content = event.slice(5).trim();  // 提取有效内容

自定义标签的 HTML 转换

  • 后端返回 <think>深度思考</think> 的标记内容,前端通过正则表达式将其转换为常见的的深度思考展示格式
content = content
  .replace(/<think>/g, '<div class="think-marker">【思考中】</div><div class="think-content">')
  .replace(/<\/think>/g, '</div><div class="think-marker">【思考结束】</div>');

动态内容渲染与实时滚动

  • 渐进式内容追加:使用 innerHTML += content 动态追加新内容,避免页面闪烁,同时保留历史记录。
currentResponseDiv.innerHTML += content;
  • 自动滚动定位 每次追加内容后调用 scrollIntoView({ behavior: 'smooth' }),实现平滑滚动到底部,提升用户体验
currentResponseDiv.scrollIntoView({ behavior: 'smooth' });

三、测试与评估

经测试在输出表现良好,延迟较低,界面简介美观,可读性强。
最终效果图如下:

【后端】

image

【前端】
image

四、下一步开发目标

(1)后端检索优化:针对大量数据提升检索准确率
(2)前端工程:从自建前端转移到Cherry Studio并美化界面
posted @ 2025-04-30 10:37  Unalome  阅读(122)  评论(0)    收藏  举报