# 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 问答 — 正常发送(新建对话) ```json // 请求 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": "", "user_id": 100, "business_type": 0 } } ``` #### 用例 2:PPT 大纲生成 ```json // 请求 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": "", "user_id": 100, "business_type": 1 } } ``` #### 用例 3:AI 写作 ```json // 请求 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) ```json // 请求 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": "", "user_id": 100, "business_type": 3 } } ``` #### 用例 5:关联已有对话 ```json // 请求 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": "", "user_id": 100, "business_type": 0 } } ``` #### 用例 6:消息为空 ```json // 请求 POST /apiv1/send_deepseek_message token: <有效Token> Content-Type: application/json { "message": "", "business_type": 0 } // 预期响应 (HTTP 200, 业务码 400) { "statusCode": 400, "msg": "消息不能为空" } ``` #### 用例 7:不支持的业务类型 ```json // 请求 POST /apiv1/send_deepseek_message token: <有效Token> Content-Type: application/json { "message": "测试消息", "business_type": 99 } // 预期响应 (HTTP 200, 业务码 400) { "statusCode": 400, "msg": "不支持的业务类型: 99" } ``` #### 用例 8:未认证 ```json // 请求(不带 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:正常获取 ```json // 请求 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:无历史记录 ```json // 请求 GET /apiv1/get_history_record token: <有效Token(新用户)> // 预期响应 (HTTP 200) { "statusCode": 200, "msg": "success", "data": [] } ``` #### 用例 3:未认证 ```json // 请求 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:正常删除 ```json // 请求 POST /apiv1/delete_conversation token: <有效Token> Content-Type: application/json { "ai_conversation_id": 1 } // 预期响应 (HTTP 200) { "statusCode": 200, "msg": "删除成功" } ``` #### 用例 2:删除不属于自己的对话(无报错但实际不删除任何数据) ```json // 请求 POST /apiv1/delete_conversation token: <用户A的Token> Content-Type: application/json { "ai_conversation_id": 999 } // 预期响应 (HTTP 200) { "statusCode": 200, "msg": "删除成功" } ``` > 注意:代码中未检查是否实际匹配到记录,update 0 条也不报错。 #### 用例 3:未认证 ```json // 请求 POST /apiv1/delete_conversation // 预期响应 (HTTP 401) { "statusCode": 401, "msg": "未提供认证Token" } ``` --- ### 4. POST `/apiv1/delete_history_record` — 删除历史记录(软删除) **功能说明:** 仅软删除对话记录本身,不删除关联消息。 **是否需要认证:** 是 **请求方式:** POST **请求体(JSON):** | 字段 | 类型 | 必填 | 说明 | |------|------|------|------| | ai_conversation_id | int | 是 | 要删除的对话 ID | **测试用例:** #### 用例 1:正常删除 ```json // 请求 POST /apiv1/delete_history_record token: <有效Token> Content-Type: application/json { "ai_conversation_id": 2 } // 预期响应 (HTTP 200) { "statusCode": 200, "msg": "删除成功" } ``` #### 用例 2:未认证 ```json // 请求 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:正常流式聊天 ```json // 请求 POST /apiv1/stream/chat token: <有效Token> Content-Type: application/json { "message": "什么是预应力混凝土?" } // 预期响应 (HTTP 200, text/event-stream) // SSE 流: data: {"content": "预应力混凝土是.."} data: {"content": "在施工中..."} data: [DONE] ``` #### 用例 2:消息为空 ```json // 请求 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_id` 和 `ai_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:新建对话并流式聊天 ```json // 请求 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:在已有对话中继续聊天 ```json // 请求 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:携带联网搜索内容 ```json // 请求 POST /apiv1/stream/chat-with-db token: <有效Token> Content-Type: application/json { "message": "最新的桥梁施工规范", "online_search_content": "根据2024年最新《公路桥梁施工技术规范》..." } // 预期 SSE 流响应(正常流式输出) ``` #### 用例 4:消息为空 ```json // 请求 POST /apiv1/stream/chat-with-db token: <有效Token> Content-Type: application/json { "message": "" } // 预期响应 (HTTP 200, JSON) { "statusCode": 400, "msg": "消息不能为空" } ``` #### 用例 5:未认证 ```json // 请求 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:正常生成 ```json // 请求 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 不存在 ```json // 请求 POST /apiv1/guess_you_want token: <有效Token> Content-Type: application/json { "ai_message_id": 99999 } // 预期响应 (HTTP 200, 业务码 404) { "statusCode": 404, "msg": "消息不存在" } ``` #### 用例 3:未认证 ```json // 请求 POST /apiv1/guess_you_want // 预期响应 (HTTP 401) { "statusCode": 401, "msg": "未提供认证Token" } ``` --- ### 8. GET `/apiv1/online_search` — 在线搜索 **功能说明:** 先通过 Qwen 提炼关键词,再调用 Dify 工作流执行在线搜索,返回搜索摘要。 **是否需要认证:** 是 **请求方式:** GET **请求参数(Query):** | 字段 | 类型 | 必填 | 说明 | |------|------|------|------| | question | string | 是 | 搜索问题 | **测试用例:** #### 用例 1:正常搜索 ```json // 请求 GET /apiv1/online_search?question=最新桥梁施工安全规范 token: <有效Token> // 预期响应 (HTTP 200) { "statusCode": 200, "msg": "success", "data": { "keywords": "桥梁 施工 安全 规范 最新", "result": "<搜索摘要文本>" } } ``` #### 用例 2:Dify 配置未设置 ```json // 请求(服务端未配置 Dify) GET /apiv1/online_search?question=测试 token: <有效Token> // 预期响应 (HTTP 200, 业务码 500) { "statusCode": 500, "msg": "Dify 配置未设置" } ``` #### 用例 3:未认证 ```json // 请求 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:正常保存 ```json // 请求 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:未认证 ```json // 请求 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:纯意图识别(不保存) ```json // 请求 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 ```json // 请求 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:问候意图 + 关联已有对话 ```json // 请求 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:未认证 ```json // 请求 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:不带参数获取推荐列表 ```json // 请求 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:带关键词查询 ```json // 请求 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:无匹配结果 ```json // 请求 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:正常保存 ```json // 请求 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:未认证 ```json // 请求 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:正常保存 ```json // 请求 POST /apiv1/save_edit_document token: <有效Token> Content-Type: application/json { "ai_message_id": 11, "content": "修改后的文档内容..." } // 预期响应 (HTTP 200) { "statusCode": 200, "msg": "保存成功" } ``` #### 用例 2:未认证 ```json // 请求 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` 列表(注意:此函数在文件中定义但未在任何接口中直接调用)。 |