rag-chat-assistant

AI 智能助手 — 详细接口文档

基于后端实际代码 + 前端已实现的 SSE 消费逻辑生成。

Base URL: http://127.0.0.1:8000

认证方式: 请求头携带 Authorization: Bearer <token>


目录

模块 接口 方法 路径 认证 状态
认证 登录 POST /auth/login 已实现
认证 注册 POST /auth/register 已实现
认证 获取当前用户 GET /auth/me 已实现
认证 退出登录 POST /auth/logout 已实现
对话 获取对话列表 GET /chats 已实现
对话 创建对话 POST /chats 已实现
对话 获取对话消息 GET /chats/{chat_id} 已实现
对话 修改对话标题 PUT /chats/{chat_id} 已实现
对话 自动生成标题 GET /chats/{chat_id}/craete_title 已实现
对话 删除对话 DELETE /chats/{chat_id} 已实现
对话 清空对话消息 DELETE /chats/{chat_id}/messages 已实现
消息 发送消息(SSE流式) POST /message/send 已实现
文件 上传文件 POST /files/upload 已实现
知识库 创建知识库 POST /knowledge-bases 待实现
知识库 获取知识库列表 GET /knowledge-bases 待实现
知识库 删除知识库 DELETE /knowledge-bases/{kb_id} 待实现
知识库 上传知识库文档 POST /knowledge-bases/{kb_id}/documents 待实现
知识库 获取文档列表 GET /knowledge-bases/{kb_id}/documents 待实现
知识库 删除文档 DELETE /knowledge-bases/{kb_id}/documents/{doc_id} 待实现

前端已适配: sendMessage() 会自动检测响应类型。如果后端返回 text/event-stream 则走流式渲染,如果返回 application/json 则走普通 JSON 解析。两种模式前端都支持,无需切换


统一响应格式

普通 JSON 响应

{
  "code": 200,
  "message": "描述信息",
  "data": { ... }
}

SSE 流式响应

Content-Type: text/event-stream

data: {"type": "start", "messageId": 6}

data: {"type": "chunk", "content": "递归"}

data: {"type": "chunk", "content": "是一种"}

data: {"type": "done", "content": "递归是一种编程技巧...", "messageId": 6}

错误响应

HTTP 状态码 含义 示例
401 未认证 / Token 无效 {"detail": "token无效或已过期"}
404 资源不存在 {"detail": "对话不存在"}
409 资源冲突 {"detail": "用户名或邮箱已被注册!"}
422 请求体验证失败 FastAPI 自动生成的验证错误

一、认证模块

1. POST /auth/login — 登录

请求格式: application/x-www-form-urlencoded

参数 类型 必填 说明
username string 用户名或邮箱
password string 密码

请求示例:

POST /auth/login
Content-Type: application/x-www-form-urlencoded

username=李灏&password=123456

成功响应 (200):

{
  "code": 200,
  "message": "登录成功",
  "access_token": "eyJhbGciOiJIUzI1NiIs...",
  "data": {
    "token": "eyJhbGciOiJIUzI1NiIs...",
    "user": {
      "id": 1,
      "username": "李灏",
      "email": "lihao@example.com",
      "avatar": null
    }
  }
}

access_token 供 Swagger 的 Authorize 功能使用,前端取 data.token

错误:

状态码 detail
401 账号不存在!
401 密码错误!

2. POST /auth/register — 注册

请求格式: application/x-www-form-urlencoded

参数 类型 必填 约束 说明
username string 2-20 字符 用户名
password string 6-20 字符 密码
email string 6-20 字符 邮箱

成功响应 (200): 与登录响应格式相同,message 为 “注册成功”。

错误:

状态码 detail
409 用户名或邮箱已被注册!

3. GET /auth/me — 获取当前用户

请求头: Authorization: Bearer <token>

成功响应 (200):

{
  "code": 200,
  "message": "成功",
  "data": {
    "id": 1,
    "username": "李灏",
    "email": "lihao@example.com",
    "avatar": null
  }
}

4. POST /auth/logout — 退出登录

请求头: Authorization: Bearer <token>

成功响应 (200):

{
  "code": 200,
  "message": "已退出登录"
}

二、对话管理模块

5. GET /chats — 获取对话列表

请求头: Authorization: Bearer <token>

成功响应 (200):

{
  "code": 200,
  "message": "成功",
  "data": [
    {
      "id": 1001,
      "title": "关于 Python 的问答",
      "messageCount": 6,
      "createdAt": "2026-08-10T10:00:00",
      "updatedAt": "2026-08-12T14:30:00"
    }
  ]
}

6. POST /chats — 创建对话

请求头: Authorization: Bearer <token>

请求格式: application/json

参数 类型 必填 默认值 说明
title string “新对话” 对话标题

请求示例:

{ "title": "新对话" }

成功响应 (200):

{
  "code": 200,
  "message": "创建成功",
  "data": {
    "id": 1002,
    "title": "新对话",
    "messageCount": 0,
    "createdAt": "2026-08-14T10:00:00",
    "updatedAt": "2026-08-14T10:00:00"
  }
}

7. GET /chats/{chat_id} — 获取对话消息

请求头: Authorization: Bearer <token>

路径参数: chat_id (int) — 对话 ID

成功响应 (200):

{
  "code": 200,
  "message": "成功",
  "data": [
    {
      "id": 1,
      "chat_id": 1001,
      "role": "user",
      "content": "你好,请介绍一下 Python",
      "images": null,
      "files": null,
      "created_at": "2026-08-10T10:00:00"
    },
    {
      "id": 2,
      "chat_id": 1001,
      "role": "ai",
      "content": "Python 是一种广泛使用的高级编程语言...",
      "images": null,
      "files": null,
      "created_at": "2026-08-10T10:00:05"
    }
  ]
}

错误: 404 — 对话不存在


8. PUT /chats/{chat_id} — 修改对话标题

请求头: Authorization: Bearer <token>

请求格式: application/json

{ "title": "Python 学习笔记" }

成功响应 (200):

{
  "code": 200,
  "message": "修改成功",
  "data": { "id": 1001, "title": "Python 学习笔记" }
}

9. DELETE /chats/{chat_id} — 删除对话

删除对话及其所有消息。

成功响应 (200):

{ "code": 200, "message": "删除成功", "data": null }

10. DELETE /chats/{chat_id}/messages — 清空对话消息

删除对话的所有消息,保留对话本身。

成功响应 (200):

{ "code": 200, "message": "已清空消息", "data": null }

三、消息模块

11. POST /message/send — 发送消息(普通模式)

当前后端已实现的模式,返回完整 JSON。

请求头: Authorization: Bearer <token>

请求格式: application/json

参数 类型 必填 说明
chat_id int 对话 ID
content string 文本内容(与图片/文件至少有一个)
images array 图片数组 [{url, name}]
files array 文件数组 [{fileId, name, size}]

请求示例:

{
  "chat_id": 1001,
  "content": "请帮我解释什么是递归",
  "images": null,
  "files": null
}

成功响应 (200) — Content-Type: application/json:

{
  "code": 200,
  "message": "成功",
  "data": {
    "userMessageId": 5,
    "aiMessage": {
      "id": 6,
      "chat_id": 1001,
      "role": "ai",
      "content": "递归是一种编程技巧...",
      "images": null,
      "files": null,
      "created_at": "2026-08-14T10:05:00"
    }
  }
}

12. POST /message/send — 发送消息(SSE 流式模式)

前端已支持,后端待实现。 请求参数与普通模式完全相同,区别仅在于响应格式。

前端通过检测响应头 Content-Type 自动判断模式:

响应格式: Content-Type: text/event-stream

SSE 事件格式:

每条事件以 data: 开头,以空行结尾。data 后面是 JSON 字符串。

事件类型

type 说明 字段
start AI 开始回复 messageId (int): AI 消息 ID
chunk 增量内容 content (string): 本次的文本片段
done 回复完成 content (string): 完整回复内容(可选,用于校正)
error 回复出错 content (string): 错误信息

SSE 响应示例

data: {"type": "start", "messageId": 6}

data: {"type": "chunk", "content": "递归"}

data: {"type": "chunk", "content": "是一种"}

data: {"type": "chunk", "content": "编程技巧,"}

data: {"type": "chunk", "content": "函数在执行过程中调用自身..."}

data: {"type": "done", "messageId": 6}

后端实现参考(不需要改,仅供参考)

from fastapi.responses import StreamingResponse
import json

@router.post("/message/send")
async def send_messages(message: UserMessages,
                        current_user: User = Depends(get_current_user),
                        session=Depends(get_session)):
    # 1. 保存用户消息(与当前代码相同)
    user_message = Message(chat_id=message.chat_id, role="user", content=message.content, ...)
    session.add(user_message)
    await session.flush()

    # 2. 定义生成器函数,逐块产出 SSE 事件
    async def event_stream():
        # 发送 start 事件
        yield f"data: {json.dumps({'type': 'start'})}\n\n"

        # 调用 AI 流式接口
        full_content = ""
        async for chunk in agent.astream({
            "messages": [{"role": "user", "content": message.content}]
        }):
            delta = chunk["messages"][-1].content
            if delta:
                full_content += delta
                yield f"data: {json.dumps({'type': 'chunk', 'content': delta})}\n\n"

        # 保存 AI 消息
        ai_message = Message(chat_id=message.chat_id, role="ai", content=full_content)
        session.add(ai_message)
        await session.flush()
        await session.commit()

        # 发送 done 事件
        yield f"data: {json.dumps({'type': 'done', 'messageId': ai_message.id})}\n\n"

    # 3. 返回 StreamingResponse
    return StreamingResponse(event_stream(), media_type="text/event-stream")

关键改动


四、文件模块(待实现)

13. POST /files/upload — 上传文件

尚未实现,以下为建议设计。

请求头: Authorization: Bearer <token>

请求格式: multipart/form-data

参数 类型 必填 说明
file file 上传的文件

建议的成功响应 (200):

{
  "code": 200,
  "message": "上传成功",
  "data": {
    "fileId": "uuid-string",
    "url": "/uploads/xxx.png",
    "name": "screenshot.png",
    "size": 102400
  }
}

五、前端已实现的功能

粘贴图片/文件

在输入框中直接 Ctrl+V 粘贴图片或文件,自动添加到文件预览区。

拖拽上传

将文件拖拽到聊天消息区域,释放后自动添加到文件预览区。拖拽时区域会高亮显示。

SSE 流式渲染

前端通过 fetch + ReadableStream 读取 SSE 流:

向后兼容:如果后端返回普通 JSON,前端自动走 JSON 解析逻辑,不影响使用。


六、数据库表结构

users — 用户表

字段 类型 说明
id INTEGER PK 自增主键
username VARCHAR(50) UNIQUE 用户名
email VARCHAR(100) UNIQUE 邮箱
password_hash VARCHAR   密码哈希(bcrypt)
avatar VARCHAR(500)   头像 URL
created_at DATETIME   创建时间

chats — 对话表

字段 类型 说明
id INTEGER PK 自增主键
user_id INTEGER FK → users.id 所属用户
title VARCHAR(100)   对话标题
created_at DATETIME   创建时间
updated_at DATETIME   最后更新时间(自动更新)

messages — 消息表

字段 类型 说明
id INTEGER PK 自增主键
chat_id INTEGER FK → chats.id 所属对话
role VARCHAR(10)   "user""ai"
content TEXT   消息文本内容
images JSON   图片信息
files JSON   文件信息
created_at DATETIME   创建时间

七、可扩展建议

1. 对话上下文记忆

当前问题:每次发消息只传当前一条消息给 AI,AI 不记得之前的对话。

建议:在 POST /message/send 接口中,查询该对话的历史消息,一起传给 AI:

# 查询最近 20 条消息作为上下文
history = (await session.exec(
    select(Message).where(Message.chat_id == message.chat_id)
    .order_by(Message.created_at.desc()).limit(20)
)).all()[::-1]  # 反转成时间正序

# 构建带上下文的消息列表
messages = [{"role": m.role, "content": m.content} for m in history]
messages.append({"role": "user", "content": message.content})

response = await agent.ainvoke({"messages": messages})

2. 文件上传接口

当前问题files.py 为空,前端上传的文件只存在浏览器本地,无法持久化。

建议实现

import uuid, os

@router.post("/files/upload")
async def upload_file(file: UploadFile = File(...),
                      current_user: User = Depends(get_current_user)):
    # 生成唯一文件名
    ext = os.path.splitext(file.filename)[1]
    filename = f"{uuid.uuid4()}{ext}"
    filepath = f"uploads/{filename}"

    # 保存文件
    os.makedirs("uploads", exist_ok=True)
    with open(filepath, "wb") as f:
        f.write(await file.read())

    return {
        "code": 200,
        "message": "上传成功",
        "data": {
            "fileId": filename,
            "url": f"/uploads/{filename}",
            "name": file.filename,
            "size": os.path.getsize(filepath)
        }
    }

3. Token 黑名单持久化

当前问题blacklisted_tokens 是内存 set,服务器重启后清空。

建议:用数据库表存储黑名单,或用 Redis:

# 方案一:数据库表
class BlacklistedToken(SQLModel, table=True):
    id: Optional[int] = Field(default=None, primary_key=True)
    token: str
    expired_at: datetime

# 方案二:Redis(推荐)
import redis
r = redis.Redis()
r.setex(f"blacklist:{token}", 86400, "1")  # 24小时后自动清除

4. 流式中断(用户停止生成)

场景:AI 回复太长,用户想中途停止。

前端实现:在流式读取循环中,提供一个”停止”按钮,调用 reader.cancel() 中断流。

后端实现:FastAPI 的 StreamingResponse 会自动检测客户端断开,生成器中捕获 asyncio.CancelledError 即可。

5. 消息分页加载

当前问题GET /chats/{chat_id} 返回所有消息,消息多时性能差。

建议:加分页参数:

GET /chats/{chat_id}?page=1&page_size=20

6. 对话标题自动生成

当前问题:前端用用户消息前 30 字符作为标题。

建议:后端在 AI 回复后,调用 AI 生成一句话摘要作为标题:

title_response = await llm.ainvoke(f"请用10个字以内总结这个对话的主题:{message.content}")
chat.title = title_response.content
session.add(chat)

7. 图片分析能力

当前问题:AI Agent 只接收文本,不支持图片。

建议:使用支持视觉的模型(如 GPT-4V / Qwen-VL),将图片作为多模态输入:

from langchain_core.messages import HumanMessage

messages = [HumanMessage(content=[
    {"type": "text", "text": message.content},
    {"type": "image_url", "image_url": {"url": image.url}}
])]

八、知识库模块(RAG)— 待实现

前端已完整实现知识库管理 UI,后端需要实现以下 6 个接口。

RAG 流程:用户上传文档 → 后端解析+分块+向量化存储 → 用户聊天时选择知识库 → 后端检索相关片段 → 注入 AI 上下文 → AI 基于知识库回答

需要新增的数据表

knowledge_bases — 知识库表

字段 类型 说明
id INTEGER PK 自增主键
user_id INTEGER FK → users.id 所属用户
name VARCHAR(100)   知识库名称
description VARCHAR(500)   描述(可选)
created_at DATETIME   创建时间
updated_at DATETIME   最后更新时间

knowledge_documents — 知识库文档表

字段 类型 说明
id INTEGER PK 自增主键
kb_id INTEGER FK → knowledge_bases.id 所属知识库
filename VARCHAR(255)   原始文件名
file_path VARCHAR(500)   服务器存储路径
file_size INTEGER   文件大小(字节)
status VARCHAR(20)   processing / ready / failed
chunk_count INTEGER   分块数量
created_at DATETIME   创建时间

另外需要向量存储(如 ChromaDB / FAISS),用于存储文档分块的 embedding。


14. POST /knowledge-bases — 创建知识库

请求头: Authorization: Bearer <token>

请求格式: application/json

参数 类型 必填 说明
name string 知识库名称(≤100字符)
description string 描述(≤500字符)

请求示例:

{
  "name": "Python学习资料",
  "description": "Python基础到进阶教程"
}

成功响应 (200):

{
  "code": 200,
  "message": "创建成功",
  "data": {
    "id": 1,
    "name": "Python学习资料",
    "description": "Python基础到进阶教程",
    "documentCount": 0,
    "createdAt": "2026-08-15T10:00:00",
    "updatedAt": "2026-08-15T10:00:00"
  }
}

15. GET /knowledge-bases — 获取知识库列表

请求头: Authorization: Bearer <token>

成功响应 (200):

{
  "code": 200,
  "message": "成功",
  "data": [
    {
      "id": 1,
      "name": "Python学习资料",
      "description": "Python基础到进阶教程",
      "documentCount": 3,
      "createdAt": "2026-08-15T10:00:00",
      "updatedAt": "2026-08-15T10:30:00"
    }
  ]
}

16. DELETE /knowledge-bases/{kb_id} — 删除知识库

删除知识库及其所有文档和向量数据。

路径参数: kb_id (int)

成功响应 (200):

{
  "code": 200,
  "message": "删除成功",
  "data": null
}

错误: 404 — 知识库不存在


17. POST /knowledge-bases/{kb_id}/documents — 上传知识库文档

上传文档到指定知识库,后端异步解析、分块、向量化。

请求头: Authorization: Bearer <token>

请求格式: multipart/form-data

参数 类型 必填 说明
file file 文档文件(支持 PDF/Word/TXT/MD/CSV)

成功响应 (200):

{
  "code": 200,
  "message": "上传成功,正在处理",
  "data": {
    "id": 1,
    "filename": "tutorial.pdf",
    "fileSize": 102400,
    "status": "processing",
    "createdAt": "2026-08-15T10:00:00"
  }
}

注意:文档解析是耗时操作,建议异步处理。上传后立即返回 status: "processing",后台任务完成后更新为 "ready"。前端可通过轮询 GET /knowledge-bases/{kb_id}/documents 获取最新状态。


18. GET /knowledge-bases/{kb_id}/documents — 获取文档列表

请求头: Authorization: Bearer <token>

路径参数: kb_id (int)

成功响应 (200):

{
  "code": 200,
  "message": "成功",
  "data": [
    {
      "id": 1,
      "filename": "tutorial.pdf",
      "fileSize": 102400,
      "status": "ready",
      "chunkCount": 25,
      "createdAt": "2026-08-15T10:00:00"
    },
    {
      "id": 2,
      "filename": "notes.md",
      "fileSize": 5120,
      "status": "processing",
      "chunkCount": 0,
      "createdAt": "2026-08-15T10:05:00"
    }
  ]
}

19. DELETE /knowledge-bases/{kb_id}/documents/{doc_id} — 删除文档

删除文档及其所有向量数据。

路径参数: kb_id (int), doc_id (int)

成功响应 (200):

{
  "code": 200,
  "message": "删除成功",
  "data": null
}

20. POST /message/send — 发送消息(新增 kb_id 字段)

在现有 /message/send 接口的请求体中新增 kb_id 字段(可选)。

请求体新增字段:

参数 类型 必填 说明
kb_id int | null 知识库 ID。传入时 AI 会先检索知识库相关内容再回答

请求示例:

{
  "chat_id": 1001,
  "content": "解释一下Python的装饰器",
  "images": null,
  "files": null,
  "kb_id": 1
}

后端处理逻辑:当 kb_id 不为空时,在调用 agent.astream() 之前,先用用户的问题去向量数据库检索相关文档片段,将检索结果拼接到消息内容或系统提示中,再交给 AI 回答。

后端实现建议

# messages.py 中的 send_messages 函数
if message.kb_id:
    # 1. 检索相关文档片段
    from app.ai.rag import retrieve_relevant_docs
    relevant_chunks = await retrieve_relevant_docs(message.kb_id, message.content)
    
    # 2. 将检索结果拼接到用户消息中
    context = "\n\n".join(relevant_chunks)
    enhanced_content = f"以下是知识库中的相关内容,请基于这些内容回答:\n\n{context}\n\n用户问题:{message.content}"
    # 用 enhanced_content 替代 message.content 传给 AI

RAG 后端实现建议(学习参考)

1. 安装依赖

pip install chromadb langchain-chroma langchain-community
# 文档加载器
pip install pypdf python-docx markdown

2. 向量存储初始化(app/ai/vector_store.py)

from langchain_chroma import Chroma
from langchain_openai import OpenAIEmbeddings
import os

embeddings = OpenAIEmbeddings(
    model="text-embedding-v3",
    api_key=os.getenv("DASHSCOPE_API_KEY"),
    base_url=os.getenv("DASHSCOPE_BASE_URL"),
)

# 每个知识库一个 collection
def get_vectorstore(kb_id: int) -> Chroma:
    return Chroma(
        collection_name=f"kb_{kb_id}",
        embedding_function=embeddings,
        persist_directory="data/chroma_db",
    )

3. 文档处理(app/ai/document_loader.py)

from langchain_community.document_loaders import PyPDFLoader, TextLoader, Docx2txtLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter

def load_and_split(file_path: str):
    # 根据扩展名选择加载器
    ext = file_path.rsplit('.', 1)[-1].lower()
    if ext == 'pdf':
        loader = PyPDFLoader(file_path)
    elif ext == 'docx':
        loader = Docx2txtLoader(file_path)
    else:
        loader = TextLoader(file_path)
    
    docs = loader.load()
    
    # 分块
    splitter = RecursiveCharacterTextSplitter(
        chunk_size=500,
        chunk_overlap=50,
    )
    return splitter.split_documents(docs)

4. 检索函数(app/ai/rag.py)

from app.ai.vector_store import get_vectorstore

async def retrieve_relevant_docs(kb_id: int, query: str, k: int = 3):
    """检索与用户问题最相关的文档片段"""
    vectorstore = get_vectorstore(kb_id)
    results = vectorstore.similarity_search(query, k=k)
    return [doc.page_content for doc in results]

5. 在 main.py 中挂载知识库路由

from app.routers.knowledge import router as knowledge_router
app.include_router(knowledge_router)

九、认证流程

章节编号已调整:原”八”认证流程 → “九”,新增”八”知识库模块。

1. 用户登录/注册 → 后端生成 JWT Token
2. 前端保存 Token 到 localStorage
3. 后续请求携带 Authorization: Bearer <token>
4. 后端 get_current_user 依赖验证 Token:
   a. 检查 Token 是否在黑名单中
   b. 解码 JWT,提取用户 ID
   c. 查数据库确认用户存在
5. 验证通过 → 接口正常执行
   验证失败 → 返回 401,前端跳转登录页

十、Swagger 文档使用说明

  1. 访问 http://127.0.0.1:8000/docs
  2. 调用 POST /auth/login,从响应中复制 access_token
  3. 点击右上角 Authorize 按钮,粘贴 Token
  4. 之后调用的所有需要认证的接口会自动携带 Token