企业实战:Web服务端搭建


在这里插入图片描述

1 前端交互设计

本节介绍如何基于 FastAPI 搭建后端服务,提供页面访问和查询接口。检索 Web 聊天界面(chat.html)的核心结构与数据流转逻辑,该页面直接决定了后端接口的设计规范。

在这里插入图片描述

1)页面核心组件

  • 顶栏 (Topbar):展示服务连接状态与流式开关。
  • 对话区 (Chat)
    • 用户气泡:展示提问内容。
    • 系统气泡:集成文本答案处理进度(折叠面板),实时展示后台(检索/重排/生成)的执行状态。
  • 输入区 (Composer):支持快捷发送与多行输入。

2)数据交互闭环

前端与后端的全双工交互流程如下:

  1. 会话初始化:加载页面时生成或读取 session_id,作为用户唯一标识。
  2. 提交任务:点击发送 -> POST /query(携带问题与流式标记) -> 获取 session_id 确认任务已接收。
  3. 建立长连接 (SSE):立即通过 EventSource 监听 /stream/{session_id},建立实时通信管道。
  4. 事件驱动更新
    • ready: 连接握手成功。
    • progress: 实时更新进度条(如:正在检索… -> 检索完成)。
    • delta: 流式逐字输出(打字机效果)。
    • final: 接收完整答案与引用源。

基于此逻辑,后端需提供 /query(任务提交)与 /stream(事件推送)两个核心接口。

2 服务接口设计

本节详细定义查询服务的所有对外接口,包括页面访问、任务提交、流式推送及历史管理。

1)页面访问接口

  • 路径: /chat.html (GET)
  • 功能: 返回前端聊天界面。
  • 响应: HTML 静态页面。

2)检索查询接口

  • 路径: /query (POST)

  • 功能: 接收用户提问并启动后台处理图逻辑。

  • 参数:

    {
      "query": "万用表怎么测量电压?",
      "session_id": "可选,未传则后台自动生成",
      "is_stream": true // 是否启用流式推送
    }
    
  • 响应 (is_stream: true):

    { "message": "结果正在处理中...", "session_id": "xxx-uuid" }
    
  • 响应 (is_stream: false):

    { "message": "处理完成!", "session_id": "xxx", "answer": "回答内容...", "done_list": [] }
    

3)流式获取接口 (SSE)

  • 路径: /stream/{session_id} (GET)
  • 功能: 建立 SSE 长连接,实时推送任务进度与生成文本。
  • 推送数据格式 (JSON):
    • progress 事件: {"done_list": ["节点A", "节点B"], "running_list": ["节点C"]}
    • delta 事件: {"text": "生成的增量字符"}
    • final 事件: {"answer": "完整最终答案"}
    • error 事件: {"error": "错误详情"}

4)会话历史查询

  • 路径: /history/{session_id} (GET)

  • 功能: 从 MongoDB 中获取当前会话的历史聊天记录。

  • 参数: limit (可选,默认50条)

  • 响应:

    {
      "session_id": "xxx",
      "items": [
        {
          "_id": str(r.get("_id")) if r.get("_id") is not None else "",
          "session_id": r.get("session_id", ""),
           "role": r.get("role", ""),
           "text": r.get("text", ""),
           "rewritten_query": r.get("rewritten_query", ""),
            "item_names": r.get("item_names", []),
            "ts": r.get("ts")
          }
      ]
    }
    

5)清空会话历史

  • 路径: /history/{session_id} (DELETE)
  • 功能: 删除 MongoDB 中该会话的所有记录。
  • 响应: { "message": "History cleared", "deleted_count": 10 }

6)健康检查接口

  • 路径: /health (GET)
  • 功能: 检查服务存活状态。
  • 响应: { "ok": True }

2.1 接口代码实现

1)导入查询页面

将资料chat.html添加到app/query_process/page文件夹中!

在这里插入图片描述

3)前后端交互说明

知识库查询服务的流式响应流程 ,涉及到四个核心模块的协同工作:

  1. Web 服务层 ( query_service.py ): 负责接收请求、建立 SSE 连接。
  2. SSE 工具层 ( sse_utils.py ): 负责管理消息队列、打包和推送事件。
  3. 任务状态层 ( task_utils.py ): 负责记录每个节点的执行进度,并自动触发 SSE 推送。
  4. 图节点执行层 ( query_process/ ): 实际的业务逻辑节点(如检索、Rerank、生成答案),它们通过更新状态来驱动进度条。

核心流程时序图:

在这里插入图片描述

2)定义和实现api接口服务

app/query_process/api 目录下创建 query_service.py,我们将代码拆解为以下几个部分进行实现。

首先引入 FastAPI、Pydantic 以及项目内部的工具类。

from pathlib import Path
import uuid
import uvicorn
from fastapi import FastAPI, BackgroundTasks, HTTPException, Request
from fastapi.responses import FileResponse, StreamingResponse
from pydantic import BaseModel, Field
from starlette.middleware.cors import CORSMiddleware
from app.core.logger import logger

from app.utils.task_utils import *
from app.utils.sse_utils import create_sse_queue, SSEEvent, sse_generator
from app.clients.mongo_history_utils import *
from app.query_process.agent.main_graph import query_app


# 定义fastapi对象
app = FastAPI(title="query service", description="掌柜智库查询服务!")

# 跨域配置
app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_methods=["*"],
    allow_headers=["*"],
)

# 返回chat.html页面
@app.get("/chat.html")
async def chat():
    current_dir_parent_path = Path(__file__).absolute().parent.parent
    chat_html_path = current_dir_parent_path / "page" / "chat.html"
    if not chat_html_path.exists():
        logger.error(f"页面不存在:{chat_html_path}")
        raise HTTPException(status_code=404, detail=f"没有查询到页面,地址为:{chat_html_path}!")
    logger.info("成功加载 chat.html 页面")
    return FileResponse(chat_html_path)

3)定义数据模型 (Pydantic)

定义前端请求的数据结构,确保参数类型安全,并增加兼容字段。

# 定义接口接收的数据结构
class QueryRequest(BaseModel):
    """查询请求数据结构"""
    query: str = Field(..., description="查询内容")
    session_id: str = Field(None, description="会话ID")
    is_stream: bool = Field(False, description="是否流式返回")

4)实现核心查询逻辑

这是最关键的逻辑部分,包含后台任务处理函数 run_query_graph 和 API 接口 /query

# 核心查询处理函数
def run_query_graph(session_id: str, user_query: str, is_stream: bool = True):
    logger.info(f"[{session_id}] 开始执行查询流程,流式模式:{is_stream}")
    default_state = {"original_query": user_query, "session_id": session_id, "is_stream": is_stream}
    try:
        query_app.invoke(default_state)
        update_task_status(session_id, TASK_STATUS_COMPLETED, is_stream)
        logger.info(f"[{session_id}] 查询流程执行完成")
    except Exception as e:
        logger.exception(f"[{session_id}] 查询流程异常:{str(e)}")
        update_task_status(session_id, TASK_STATUS_FAILED, is_stream)
        if is_stream:
            push_to_session(session_id, SSEEvent.ERROR, {"error": str(e)})

# 查询接口
@app.post("/query")
async def query(background_tasks: BackgroundTasks, request: QueryRequest):
    """
    1 解析参数
    2 更新任务状态
    3 调用处理流程图
    4 返回结果
    """
    user_query = request.query
    session_id = request.session_id if request.session_id else str(uuid.uuid4())
    is_stream = request.is_stream

    if is_stream:
        create_sse_queue(session_id)
        logger.info(f"[{session_id}] 已创建 SSE 消息队列")

    update_task_status(session_id, TASK_STATUS_PROCESSING, is_stream)
    logger.info(f"[{session_id}] 任务开始处理,查询内容:{user_query}")

    if is_stream:
        background_tasks.add_task(run_query_graph, session_id, user_query, is_stream)
        logger.info(f"[{session_id}] 流式任务已提交至后台执行")
        return {
            "message": "结果正在处理中...",
            "session_id": session_id
        }
    else:
        run_query_graph(session_id, user_query, is_stream)
        answer = get_task_result(session_id, "answer", "")
        logger.info(f"[{session_id}] 非流式查询处理完成")
        return {
            "message": "处理完成!",
            "session_id": session_id,
            "answer": answer,
            "done_list": []
        }
        
# SSE 流式推送接口
@app.get("/stream/{session_id}")
async def stream(session_id: str, request: Request):
    logger.info(f"[{session_id}] 客户端已建立 SSE 流式连接")
    return StreamingResponse(
        sse_generator(session_id, request),
        media_type="text/event-stream",
        headers={
            "Cache-Control": "no-cache",
            "Connection": "keep-alive",
            "X-Accel-Buffering": "no"
        }
    )

5)健康检查接口

# 健康检查
@app.get("/health")
async def health():
    """服务健康检查"""
    logger.info("健康检查接口调用成功")
    return {"ok": True}

6)启动查询服务

if __name__ == "__main__":
    uvicorn.run(app, host="127.0.0.1", port=8001)

3 历史对话记录管理

  1. 上下文连续:保留关键信息,支持追问、补充条件与多轮推理不断档。
  2. 指代消歧:如果只考虑本次的问题往往不足以让大模型理解用户的上下文含义,尤其是一些指代比如:“这个设备”,“它”等等。
  3. 交互确认:为明确问题的一些信息,Agent 可能会反问用户一些问题,经过几次确认后才能准确解答后续问题,比如商品的信号。
  4. 识别用户:长期记录对话可以记住用户偏好,最终形成用户画像。

咱们项目这里没有使用 LangChain 的 Checkpointer 方式管理会话,而是自定义了一套基于 MongoDB 的持久化管理策略。

这样做的有很多好处:更加灵活自主,可以根据条件范围查询对话,可以给对话自定义格式,管理查询写入内容。

但是相对来说需要做的开发工作也比较多。

MongoDB 是一种文档数据库,特别适用于存储海量数据,结构简单,关系简单的数据。

  • 性能和单表容量上都比 MySQL/PostgreSQL 等传统关系型数据库要好。虽比不上 Redis 这种内存数据库,但在持久化能力和检索功能又比内存数据库强上很多。
  • 缺点是不适合保持有复杂关联关系,查询复杂的场景。

所以对于这种结构简单、查询简单但数据庞大的对话信息特别合适。尤其是 MongoDB 的存储基本单元就是一份 JSON 文档,与对话也是非常契合。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

赵广陆

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值