test_chat.md 22 KB

routers/chat.py 接口测试文档

文件功能概述

该文件提供 AI 聊天相关的全部接口,包括:

  • 非流式消息发送(支持 AI 问答 / PPT 大纲 / AI 写作 / 考试工坊 4 种业务类型)
  • 流式 SSE 聊天(无 DB / 有 DB 两种模式)
  • 对话历史管理(获取、删除)
  • 猜你想问
  • 意图识别
  • 在线搜索(Dify 工作流集成)
  • 推荐问题查询
  • PPT 大纲 / 文档编辑保存

所有接口(除 get_user_recommend_question 外)都需要 Token 认证。

路由前缀:/apiv1(以 routers/__init__.py 中注册为准)


接口列表


1. POST /apiv1/send_deepseek_message — 发送消息(非流式)

功能说明: 发送消息并获取 AI 回复。支持 4 种业务类型:

  • 0: AI 问答(意图识别 + RAG 检索)
  • 1: PPT 大纲生成
  • 2: AI 写作
  • 3: 考试工坊(题目生成)

自动创建对话或关联到已有对话。

是否需要认证:

请求方式: POST

请求体(JSON): | 字段 | 类型 | 必填 | 默认值 | 说明 | |------|------|------|--------|------| | message | string | 是 | - | 用户消息内容 | | conversation_id | int | 否 | null | 对话 ID,为空则新建对话 | | business_type | int | 否 | 0 | 业务类型:0=AI问答, 1=PPT大纲, 2=AI写作, 3=考试工坊 | | exam_name | string | 否 | "" | 考试名称(business_type=3 时有效) | | ai_message_id | int | 否 | 0 | AI 消息 ID |

测试用例:

用例 1:AI 问答 — 正常发送(新建对话)

// 请求
POST /apiv1/send_deepseek_message
token: <有效Token>
Content-Type: application/json

{
  "message": "隧道施工有哪些安全注意事项?",
  "business_type": 0
}

// 预期响应 (HTTP 200)
{
  "statusCode": 200,
  "msg": "success",
  "data": {
    "conversation_id": 1,
    "response": "<AI回答文本>",
    "user_id": 100,
    "business_type": 0
  }
}

用例 2:PPT 大纲生成

// 请求
POST /apiv1/send_deepseek_message
token: <有效Token>
Content-Type: application/json

{
  "message": "桥梁施工安全培训 PPT",
  "business_type": 1
}

// 预期响应 (HTTP 200)
{
  "statusCode": 200,
  "msg": "success",
  "data": {
    "conversation_id": 2,
    "response": "<PPT大纲文本>",
    "user_id": 100,
    "business_type": 1
  }
}

用例 3:AI 写作

// 请求
POST /apiv1/send_deepseek_message
token: <有效Token>
Content-Type: application/json

{
  "message": "新写一篇关于路桥隧施工安全管理的报告",
  "business_type": 2
}

// 预期响应 (HTTP 200)
{
  "statusCode": 200,
  "msg": "success",
  "data": {
    "conversation_id": 3,
    "response": "<写作文本>",
    "user_id": 100,
    "business_type": 2
  }
}

用例 4:考试工坊(需带 exam_name)

// 请求
POST /apiv1/send_deepseek_message
token: <有效Token>
Content-Type: application/json

{
  "message": "生成10道关于隧道施工安全的单选题",
  "business_type": 3,
  "exam_name": "隧道施工安全考试"
}

// 预期响应 (HTTP 200)
{
  "statusCode": 200,
  "msg": "success",
  "data": {
    "conversation_id": 4,
    "response": "<JSON格式题目>",
    "user_id": 100,
    "business_type": 3
  }
}

用例 5:关联已有对话

// 请求
POST /apiv1/send_deepseek_message
token: <有效Token>
Content-Type: application/json

{
  "message": "继续上面的话题",
  "conversation_id": 1,
  "business_type": 0
}

// 预期响应 (HTTP 200)
{
  "statusCode": 200,
  "msg": "success",
  "data": {
    "conversation_id": 1,
    "response": "<AI回答>",
    "user_id": 100,
    "business_type": 0
  }
}

用例 6:消息为空

// 请求
POST /apiv1/send_deepseek_message
token: <有效Token>
Content-Type: application/json

{
  "message": "",
  "business_type": 0
}

// 预期响应 (HTTP 200, 业务码 400)
{
  "statusCode": 400,
  "msg": "消息不能为空"
}

用例 7:不支持的业务类型

// 请求
POST /apiv1/send_deepseek_message
token: <有效Token>
Content-Type: application/json

{
  "message": "测试消息",
  "business_type": 99
}

// 预期响应 (HTTP 200, 业务码 400)
{
  "statusCode": 400,
  "msg": "不支持的业务类型: 99"
}

用例 8:未认证

// 请求(不带 Token)
POST /apiv1/send_deepseek_message
Content-Type: application/json

{
  "message": "测试"
}

// 预期响应 (HTTP 401)
{
  "statusCode": 401,
  "msg": "未提供认证Token"
}

2. GET /apiv1/get_history_record — 获取对话历史记录列表

功能说明: 获取当前用户的对话历史列表,按创建时间倒序排列,最多返回 50 条。

是否需要认证:

请求方式: GET

请求路径: /apiv1/get_history_record

请求参数:

测试用例:

用例 1:正常获取

// 请求
GET /apiv1/get_history_record
token: <有效Token>

// 预期响应 (HTTP 200)
{
  "statusCode": 200,
  "msg": "success",
  "data": [
    {
      "id": 5,
      "content": "隧道施工有哪些安全注意事项?...",
      "business_type": 0,
      "exam_name": "",
      "created_at": 1700000000
    }
  ]
}

注意:content 字段最多截取前 50 个字符并添加 ... 后缀。

用例 2:无历史记录

// 请求
GET /apiv1/get_history_record
token: <有效Token(新用户)>

// 预期响应 (HTTP 200)
{
  "statusCode": 200,
  "msg": "success",
  "data": []
}

用例 3:未认证

// 请求
GET /apiv1/get_history_record

// 预期响应 (HTTP 401)
{
  "statusCode": 401,
  "msg": "未提供认证Token"
}

3. POST /apiv1/delete_conversation — 删除对话(软删除)

功能说明: 软删除对话记录及其所有关联的消息(设置 is_deleted = 1)。

是否需要认证:

请求方式: POST

请求体(JSON): | 字段 | 类型 | 必填 | 说明 | |------|------|------|------| | ai_conversation_id | int | 是 | 要删除的对话 ID |

测试用例:

用例 1:正常删除

// 请求
POST /apiv1/delete_conversation
token: <有效Token>
Content-Type: application/json

{
  "ai_conversation_id": 1
}

// 预期响应 (HTTP 200)
{
  "statusCode": 200,
  "msg": "删除成功"
}

用例 2:删除不属于自己的对话(无报错但实际不删除任何数据)

// 请求
POST /apiv1/delete_conversation
token: <用户A的Token>
Content-Type: application/json

{
  "ai_conversation_id": 999
}

// 预期响应 (HTTP 200)
{
  "statusCode": 200,
  "msg": "删除成功"
}

注意:代码中未检查是否实际匹配到记录,update 0 条也不报错。

用例 3:未认证

// 请求
POST /apiv1/delete_conversation

// 预期响应 (HTTP 401)
{
  "statusCode": 401,
  "msg": "未提供认证Token"
}

4. POST /apiv1/delete_history_record — 删除历史记录(软删除)

功能说明: 仅软删除对话记录本身,不删除关联消息。

是否需要认证:

请求方式: POST

请求体(JSON): | 字段 | 类型 | 必填 | 说明 | |------|------|------|------| | ai_conversation_id | int | 是 | 要删除的对话 ID |

测试用例:

用例 1:正常删除

// 请求
POST /apiv1/delete_history_record
token: <有效Token>
Content-Type: application/json

{
  "ai_conversation_id": 2
}

// 预期响应 (HTTP 200)
{
  "statusCode": 200,
  "msg": "删除成功"
}

用例 2:未认证

// 请求
POST /apiv1/delete_history_record

// 预期响应 (HTTP 401)
{
  "statusCode": 401,
  "msg": "未提供认证Token"
}

5. POST /apiv1/stream/chat — 流式聊天(SSE,不写 DB)

功能说明: 流式输出 AI 回复,不保存到数据库。内部先做意图识别,按需执行 RAG 检索,再流式输出回答。

是否需要认证: 是(由中间件处理)

请求方式: POST

请求体(JSON): | 字段 | 类型 | 必填 | 默认值 | 说明 | |------|------|------|--------|------| | message | string | 是 | - | 用户消息 | | model | string | 否 | "" | 模型名称(预留,目前未使用) |

响应格式: SSE(Server-Sent Events,text/event-stream

SSE 事件流格式:

data: {"content": "回答的第一段.."}

data: {"content": "回答的第二段..."}

data: [DONE]

测试用例:

用例 1:正常流式聊天

// 请求
POST /apiv1/stream/chat
token: <有效Token>
Content-Type: application/json

{
  "message": "什么是预应力混凝土?"
}

// 预期响应 (HTTP 200, text/event-stream)
// SSE 流:
data: {"content": "预应力混凝土是.."}

data: {"content": "在施工中..."}

data: [DONE]

用例 2:消息为空

// 请求
POST /apiv1/stream/chat
token: <有效Token>
Content-Type: application/json

{
  "message": ""
}

// 预期响应 (HTTP 200, JSON)
{
  "statusCode": 400,
  "msg": "消息不能为空"
}

6. POST /apiv1/stream/chat-with-db — 带 DB 的流式聊天(SSE)

功能说明: 主聊天接口。完整流程:

  1. 创建/获取对话
  2. 插入用户消息和 AI 占位消息
  3. 发送 initial 事件(返回 ai_conversation_idai_message_id
  4. RAG 检索
  5. 构建历史上下文(最近 4 条消息 = 2 轮对话)
  6. 流式输出回答
  7. 更新 AI 消息内容到 DB
  8. 发送 [DONE] 结束标记

是否需要认证:

请求方式: POST

请求体(JSON): | 字段 | 类型 | 必填 | 默认值 | 说明 | |------|------|------|--------|------| | message | string | 是 | - | 用户消息 | | ai_conversation_id | int | 否 | 0 | 对话 ID,0 则新建 | | business_type | int | 否 | 0 | 业务类型 | | exam_name | string | 否 | "" | 考试名称 | | ai_message_id | int | 否 | 0 | AI 消息 ID | | online_search_content | string | 否 | "" | 联网搜索结果内容(由前端传入) |

响应格式: SSE(text/event-stream

SSE 事件流格式:

data: {"type": "initial", "ai_conversation_id": 1, "ai_message_id": 10}

data: 回答的第一段文本..

data: 回答的第二段文本...

data: [DONE]

测试用例:

用例 1:新建对话并流式聊天

// 请求
POST /apiv1/stream/chat-with-db
token: <有效Token>
Content-Type: application/json

{
  "message": "隧道围岩分类有哪些?",
  "business_type": 0
}

// 预期 SSE 流响应
data: {"type": "initial", "ai_conversation_id": 5, "ai_message_id": 20}

data: 隧道围岩根据...

data: [DONE]

用例 2:在已有对话中继续聊天

// 请求
POST /apiv1/stream/chat-with-db
token: <有效Token>
Content-Type: application/json

{
  "message": "详细说明III类围岩",
  "ai_conversation_id": 5
}

// 预期 SSE 流响应
data: {"type": "initial", "ai_conversation_id": 5, "ai_message_id": 21}

data: III类围岩...

data: [DONE]

用例 3:携带联网搜索内容

// 请求
POST /apiv1/stream/chat-with-db
token: <有效Token>
Content-Type: application/json

{
  "message": "最新的桥梁施工规范",
  "online_search_content": "根据2024年最新《公路桥梁施工技术规范》..."
}

// 预期 SSE 流响应(正常流式输出)

用例 4:消息为空

// 请求
POST /apiv1/stream/chat-with-db
token: <有效Token>
Content-Type: application/json

{
  "message": ""
}

// 预期响应 (HTTP 200, JSON)
{
  "statusCode": 400,
  "msg": "消息不能为空"
}

用例 5:未认证

// 请求
POST /apiv1/stream/chat-with-db

// 预期响应 (HTTP 200, JSON)
{
  "statusCode": 401,
  "msg": "未授权"
}

7. POST /apiv1/guess_you_want — 猜你想问

功能说明: 根据 AI 回答内容生成 3 个关联推荐问题,保存到 AIMessage.guess_you_want 字段。

是否需要认证:

请求方式: POST

请求体(JSON): | 字段 | 类型 | 必填 | 说明 | |------|------|------|------| | ai_message_id | int | 是 | AI 消息 ID |

测试用例:

用例 1:正常生成

// 请求
POST /apiv1/guess_you_want
token: <有效Token>
Content-Type: application/json

{
  "ai_message_id": 10
}

// 预期响应 (HTTP 200)
{
  "statusCode": 200,
  "msg": "success",
  "data": {
    "ai_message_id": 10,
    "questions": [
      "隧道围岩分类的具体标准是什么?",
      "不同围岩类别的支护方式有什么区别?",
      "如何进行现场围岩判定?"
    ]
  }
}

用例 2:消息 ID 不存在

// 请求
POST /apiv1/guess_you_want
token: <有效Token>
Content-Type: application/json

{
  "ai_message_id": 99999
}

// 预期响应 (HTTP 200, 业务码 404)
{
  "statusCode": 404,
  "msg": "消息不存在"
}

用例 3:未认证

// 请求
POST /apiv1/guess_you_want

// 预期响应 (HTTP 401)
{
  "statusCode": 401,
  "msg": "未提供认证Token"
}

8. GET /apiv1/online_search — 在线搜索

功能说明: 先通过 Qwen 提炼关键词,再调用 Dify 工作流执行在线搜索,返回搜索摘要。

是否需要认证:

请求方式: GET

请求参数(Query): | 字段 | 类型 | 必填 | 说明 | |------|------|------|------| | question | string | 是 | 搜索问题 |

测试用例:

用例 1:正常搜索

// 请求
GET /apiv1/online_search?question=最新桥梁施工安全规范
token: <有效Token>

// 预期响应 (HTTP 200)
{
  "statusCode": 200,
  "msg": "success",
  "data": {
    "keywords": "桥梁 施工 安全 规范 最新",
    "result": "<搜索摘要文本>"
  }
}

用例 2:Dify 配置未设置

// 请求(服务端未配置 Dify)
GET /apiv1/online_search?question=测试
token: <有效Token>

// 预期响应 (HTTP 200, 业务码 500)
{
  "statusCode": 500,
  "msg": "Dify 配置未设置"
}

用例 3:未认证

// 请求
GET /apiv1/online_search?question=测试

// 预期响应 (HTTP 401)
{
  "statusCode": 401,
  "msg": "未提供认证Token"
}

9. POST /apiv1/save_online_search_result — 保存联网搜索结果

功能说明: 将联网搜索结果保存到 AIMessage.search_source 字段。

是否需要认证:

请求方式: POST

请求体(JSON): | 字段 | 类型 | 必填 | 说明 | |------|------|------|------| | ai_message_id | int | 是 | AI 消息 ID | | search_result | string | 是 | 搜索结果文本 |

测试用例:

用例 1:正常保存

// 请求
POST /apiv1/save_online_search_result
token: <有效Token>
Content-Type: application/json

{
  "ai_message_id": 10,
  "search_result": "根据最新《公路桥梁施工技术规范》(JTG/T 3650-2020)..."
}

// 预期响应 (HTTP 200)
{
  "statusCode": 200,
  "msg": "保存成功"
}

用例 2:未认证

// 请求
POST /apiv1/save_online_search_result

// 预期响应 (HTTP 401)
{
  "statusCode": 401,
  "msg": "未提供认证Token"
}

10. POST /apiv1/intent_recognition — 意图识别

功能说明: 对用户消息进行意图识别。若意图为问候/FAQ 且 save_to_db=True,则自动将用户消息和 AI 直接回复存入数据库。

是否需要认证:

请求方式: POST

请求体(JSON): | 字段 | 类型 | 必填 | 默认值 | 说明 | |------|------|------|--------|------| | message | string | 是 | - | 用户消息 | | save_to_db | bool | 否 | false | 是否保存到数据库(仅 greeting/faq 类型有效) | | ai_conversation_id | int | 否 | 0 | 对话 ID,0 则新建 |

测试用例:

用例 1:纯意图识别(不保存)

// 请求
POST /apiv1/intent_recognition
token: <有效Token>
Content-Type: application/json

{
  "message": "隧道施工有哪些危险源?"
}

// 预期响应 (HTTP 200)
{
  "statusCode": 200,
  "msg": "success",
  "data": {
    "intent_type": "query_knowledge_base",
    "response": "",
    "saved_to_db": false
  }
}

用例 2:问候意图 + 保存到 DB

// 请求
POST /apiv1/intent_recognition
token: <有效Token>
Content-Type: application/json

{
  "message": "你好",
  "save_to_db": true
}

// 预期响应 (HTTP 200)
{
  "statusCode": 200,
  "msg": "success",
  "data": {
    "intent_type": "greeting",
    "response": "你好,请问有什么可以帮助您的?",
    "ai_conversation_id": 6,
    "ai_message_id": 25,
    "saved_to_db": true
  }
}

用例 3:问候意图 + 关联已有对话

// 请求
POST /apiv1/intent_recognition
token: <有效Token>
Content-Type: application/json

{
  "message": "你好啊",
  "save_to_db": true,
  "ai_conversation_id": 6
}

// 预期响应 (HTTP 200)
{
  "statusCode": 200,
  "msg": "success",
  "data": {
    "intent_type": "greeting",
    "response": "你好,请问有什么可以帮助您的?",
    "ai_conversation_id": 6,
    "ai_message_id": 26,
    "saved_to_db": true
  }
}

用例 4:未认证

// 请求
POST /apiv1/intent_recognition

// 预期响应 (HTTP 401)
{
  "statusCode": 401,
  "msg": "未提供认证Token"
}

11. GET /apiv1/get_user_recommend_question — 获取推荐问题

功能说明:RecommendQuestion 表中获取推荐问题列表,支持关键词模糊查询。

是否需要认证: 否(代码中未检查 user 状态,但中间件仍会校验 Token,需注意实际部署路径是否在白名单中)

请求方式: GET

请求参数(Query): | 字段 | 类型 | 必填 | 默认值 | 说明 | |------|------|------|--------|------| | keyword | string | 否 | "" | 模糊查询关键词 | | limit | int | 否 | 10 | 返回数量上限 |

测试用例:

用例 1:不带参数获取推荐列表

// 请求
GET /apiv1/get_user_recommend_question
token: <有效Token>

// 预期响应 (HTTP 200)
{
  "statusCode": 200,
  "msg": "success",
  "data": [
    {
      "id": 1,
      "question": "隧道施工安全有哪些注意事项?",
      "created_at": 1700000000
    },
    {
      "id": 2,
      "question": "桥梁施工中的常见危险源有哪些?",
      "created_at": 1700000100
    }
  ]
}

用例 2:带关键词查询

// 请求
GET /apiv1/get_user_recommend_question?keyword=隧道&limit=5
token: <有效Token>

// 预期响应 (HTTP 200)
{
  "statusCode": 200,
  "msg": "success",
  "data": [
    {
      "id": 1,
      "question": "隧道施工安全有哪些注意事项?",
      "created_at": 1700000000
    }
  ]
}

用例 3:无匹配结果

// 请求
GET /apiv1/get_user_recommend_question?keyword=不存在的关键词
token: <有效Token>

// 预期响应 (HTTP 200)
{
  "statusCode": 200,
  "msg": "success",
  "data": []
}

12. POST /apiv1/save_ppt_outline — 保存 PPT 大纲内容

功能说明: 更新指定 AI 消息的 content 字段,用于保存用户编辑后的 PPT 大纲。

是否需要认证:

请求方式: POST

请求体(JSON): | 字段 | 类型 | 必填 | 说明 | |------|------|------|------| | ai_message_id | int | 是 | AI 消息 ID | | content | string | 是 | PPT 大纲内容 |

测试用例:

用例 1:正常保存

// 请求
POST /apiv1/save_ppt_outline
token: <有效Token>
Content-Type: application/json

{
  "ai_message_id": 10,
  "content": "# 桥梁施工安全培训\n## 一、概述\n## 二、安全要点\n..."
}

// 预期响应 (HTTP 200)
{
  "statusCode": 200,
  "msg": "保存成功"
}

用例 2:未认证

// 请求
POST /apiv1/save_ppt_outline

// 预期响应 (HTTP 401)
{
  "statusCode": 401,
  "msg": "未提供认证Token"
}

13. POST /apiv1/save_edit_document — 保存编辑文档内容

功能说明: 更新指定 AI 消息的 content 字段,用于保存用户编辑后的文档。功能和参数与 save_ppt_outline 完全一致。

是否需要认证:

请求方式: POST

请求体(JSON): | 字段 | 类型 | 必填 | 说明 | |------|------|------|------| | ai_message_id | int | 是 | AI 消息 ID | | content | string | 是 | 文档内容 |

测试用例:

用例 1:正常保存

// 请求
POST /apiv1/save_edit_document
token: <有效Token>
Content-Type: application/json

{
  "ai_message_id": 11,
  "content": "修改后的文档内容..."
}

// 预期响应 (HTTP 200)
{
  "statusCode": 200,
  "msg": "保存成功"
}

用例 2:未认证

// 请求
POST /apiv1/save_edit_document

// 预期响应 (HTTP 401)
{
  "statusCode": 401,
  "msg": "未提供认证Token"
}

依赖说明

依赖项 说明
database.get_db / SessionLocal SQLAlchemy 数据库会话
models.chat.AIConversation 对话模型(字段:id, user_id, content, business_type, exam_name, created_at, updated_at, is_deleted)
models.chat.AIMessage 消息模型(字段:id, ai_conversation_id, user_id, type, content, prev_user_id, guess_you_want, search_source, created_at, updated_at, is_deleted)
models.total.RecommendQuestion 推荐问题模型(字段:id, question, is_deleted, created_at)
services.qwen_service Qwen AI 服务(chat, stream_chat, intent_recognition, extract_keywords)
utils.prompt_loader.load_prompt Prompt 模板加载器(final_answer, ppt_outline, document_writing, guess_questions)
httpx HTTP 异步客户端(用于 RAG 检索和 Dify 调用)
settings.search.api_url RAG 搜索 API 地址
settings.dify Dify 工作流配置(workflow_url, auth_token, workflow_id)

辅助函数说明

函数 说明
_rag_search(message, top_k=5) 调用搜索 API 做 RAG 检索,返回知识库上下文文本。失败时静默返回空字符串。
_build_history_messages(conv_id, limit=10) 从数据库读取最近对话历史,构建 messages 列表(注意:此函数在文件中定义但未在任何接口中直接调用)。