# ChatScope API Contract > CHAT-002 | 版本 v1.0 | 2026-06-06 > > 本文档是 ChatScope 前后端接口的**唯一权威契约**。 > 所有响应 shape、错误码、SSE 格式以本文档为准。 > 设计逻辑参见 [ChatScope 设计文档](./chat-scope-design.md)。 --- ## 1. 基础信息 | 项目 | 值 | |------|----| | Base Path | `/rag-chat` | | Auth | Bearer JWT (所有端点需要) | | Content-Type (Request) | `application/json` | | Content-Type (Response) | `application/json` (除 SSE 端点) | | Date Format | ISO 8601 (`2026-06-06T12:00:00.000Z`) | --- ## 2. 枚举 & 常量 ### 2.1 ChatScopeType ```typescript type ChatScopeType = | "knowledge_base" | "folder" | "material" | "knowledge_item" | "global"; ``` ### 2.2 CreatedFrom ```typescript type CreatedFrom = | "knowledge_base_detail" | "material_detail" | "material_reader" | "knowledge_item_detail" | "folder_detail" | "global_ai_entry" | "legacy_migration"; ``` ### 2.3 ModelMode ```typescript type ModelMode = "normal" | "deep_think" | "web_search"; ``` --- ## 3. 端点清单 | Method | Path | 说明 | Issue | |--------|------|------|-------| | POST | `/rag-chat/sessions` | 创建/打开会话 (open-or-create) | #81 #76 | | GET | `/rag-chat/sessions` | 会话列表(支持 scope 过滤) | #75 | | GET | `/rag-chat/sessions/:id/messages` | 获取消息历史 | (已有) | | POST | `/rag-chat/sessions/:id/messages` | 发送消息(同步) | (已有) | | POST | `/rag-chat/sessions/:id/stream` | 发送消息(SSE 流式) | (已有) | | PATCH | `/rag-chat/sessions/:id` | 更新会话属性 | NEW | | DELETE | `/rag-chat/sessions/:id` | 软删除会话 | (已有) | --- ## 4. 端点详细定义 ### 4.1 POST /rag-chat/sessions — open-or-create 创建新会话,或在匹配的 scope 下返回已有会话。 **Request Body:** ```json { "scopeType": "material", "scopeId": "clx7abc123", "parentKnowledgeBaseId": "clx7kb456", "createdFrom": "material_detail", "title": "新对话" } ``` | 字段 | 类型 | 必需 | 默认值 | 说明 | |------|------|------|--------|------| | scopeType | ChatScopeType | **是** | — | 会话绑定范围 | | scopeId | string \| null | 否 | null | scope 对应的实体 ID | | parentKnowledgeBaseId | string \| null | 否 | null | 所属知识库 | | createdFrom | CreatedFrom | 否 | `"global_ai_entry"` | 入口标识 | | title | string | 否 | `"新对话"` | 会话标题 | **Validation Rules:** - `scopeType` 必须是合法枚举值,否则 400 - `scopeType === "global"` 时,`scopeId` 必须为 null - `scopeType !== "global"` 时,`scopeId` 不能为空(404 或 400) **Behavior (open-or-create):** ``` IF 存在 userId + scopeType + scopeId 完全匹配 + isDeleted=false 的会话 → 返回该会话 (HTTP 200) ELSE → 创建新会话并返回 (HTTP 201) ``` **Response 200 (已有会话):** ```json { "id": "clx7sess001", "userId": "clx7user001", "scopeType": "material", "scopeId": "clx7abc123", "parentKnowledgeBaseId": "clx7kb456", "title": "数据库事务讨论", "createdFrom": "material_detail", "modelMode": "normal", "modelId": null, "isPinned": false, "isArchived": false, "isDeleted": false, "lastMessageAt": "2026-06-05T15:30:00.000Z", "createdAt": "2026-06-01T08:00:00.000Z", "updatedAt": "2026-06-05T15:30:00.000Z" } ``` **Response 201 (新建会话):** ```json { "id": "clx7sess002", "userId": "clx7user001", "scopeType": "material", "scopeId": "clx7abc123", "parentKnowledgeBaseId": "clx7kb456", "title": "新对话", "createdFrom": "material_detail", "modelMode": "normal", "modelId": null, "isPinned": false, "isArchived": false, "isDeleted": false, "lastMessageAt": null, "createdAt": "2026-06-06T10:00:00.000Z", "updatedAt": "2026-06-06T10:00:00.000Z" } ``` **Errors:** | Code | 条件 | |------|------| | 400 | scopeType 不合法 | | 400 | scopeType !== "global" 且 scopeId 为空 | | 401 | JWT 缺失或过期 | --- ### 4.2 GET /rag-chat/sessions — 会话列表 **Query Parameters:** | 参数 | 类型 | 必需 | 默认值 | 说明 | |------|------|------|--------|------| | scopeType | ChatScopeType | 否 | — | 过滤 scope 类型 | | scopeId | string | 否 | — | 过滤 scope ID(需配合 scopeType) | | parentKnowledgeBaseId | string | 否 | — | 知识库下所有会话 | | isArchived | boolean | 否 | false | 是否返回已归档 | | page | number | 否 | 1 | 页码 | | limit | number | 否 | 20 | 每页数量(最大 50) | **过滤逻辑:** ``` IF scopeType + scopeId → 精确匹配(同一 scope 的历史) ELIF parentKnowledgeBaseId → 该 KB 下所有会话(不区分 scope) ELSE → 全局列表(所有 scope,排除 isDeleted) ``` **Response 200:** ```json { "data": [ { "id": "clx7sess001", "userId": "clx7user001", "scopeType": "material", "scopeId": "clx7abc123", "parentKnowledgeBaseId": "clx7kb456", "title": "数据库事务讨论", "createdFrom": "material_detail", "modelMode": "normal", "modelId": null, "isPinned": true, "isArchived": false, "isDeleted": false, "lastMessageAt": "2026-06-05T15:30:00.000Z", "createdAt": "2026-06-01T08:00:00.000Z", "updatedAt": "2026-06-05T15:30:00.000Z" } ], "meta": { "page": 1, "limit": 20, "total": 42 } } ``` **排序规则:** - `isPinned === true` 优先 - 同优先级按 `lastMessageAt DESC`(最近活跃在前) - 无消息的会话按 `createdAt DESC` --- ### 4.3 GET /rag-chat/sessions/:id/messages — 消息历史 **Response 200:** ```json [ { "id": "clx7msg001", "sessionId": "clx7sess001", "role": "user", "content": "TCP 三次握手的过程是什么?", "tokens": 0, "scopeSnapshot": { "scopeType": "material", "scopeId": "clx7abc123", "parentKnowledgeBaseId": "clx7kb456" }, "createdAt": "2026-06-06T10:00:00.000Z", "citations": [] }, { "id": "clx7msg002", "sessionId": "clx7sess001", "role": "assistant", "content": "TCP 三次握手的过程如下:...", "tokens": 245, "scopeSnapshot": { "scopeType": "material", "scopeId": "clx7abc123", "parentKnowledgeBaseId": "clx7kb456" }, "createdAt": "2026-06-06T10:00:05.000Z", "citations": [ { "id": "clx7cit001", "messageId": "clx7msg002", "chunkId": "clx7chk042", "sourceId": "clx7abc123", "sourceTitle": "计算机网络自顶向下.pdf", "excerptText": "TCP 连接建立需要三次握手...", "pageNumber": 156, "lineStart": 12, "lineEnd": 18, "createdAt": "2026-06-06T10:00:05.000Z" } ] } ] ``` 排序: `createdAt ASC`(时间正序) --- ### 4.4 POST /rag-chat/sessions/:id/messages — 发送消息 (同步) **Request Body:** ```json { "content": "TCP 三次握手的过程是什么?" } ``` | 字段 | 类型 | 必需 | 说明 | |------|------|------|------| | content | string | **是** | 消息正文(最长 10000 字符) | **Response 200:** ```json { "id": "clx7msg002", "role": "assistant", "content": "TCP 三次握手的过程如下:...", "tokens": 245, "blocked": false, "message": { "id": "clx7msg002", "sessionId": "clx7sess001", "role": "assistant", "content": "TCP 三次握手的过程如下:...", "tokens": 245, "createdAt": "2026-06-06T10:00:05.000Z", "citations": [...] }, "citations": [...] } ``` **Response (被拦截):** ```json { "blocked": true, "message": "输入包含违规内容,请修改后重试" } ``` **Errors:** | Code | 条件 | |------|------| | 404 | 会话不存在 | | 403 | 会话不属于当前用户 | | 400 | content 为空或超过 10000 字符 | --- ### 4.5 POST /rag-chat/sessions/:id/stream — SSE 流式 **Request Body:** 同 4.4 **Response:** `text/event-stream; charset=utf-8` **SSE 事件格式:** ``` data: {"type":"thinking","content":"我们来看看..."} data: {"type":"content","content":"TCP "} data: {"type":"content","content":"三次握手是"} data: {"type":"content","content":"..."} data: {"type":"citations","citations":[{"sourceTitle":"...","excerptText":"..."}]} data: {"type":"done"} ``` **Chunk 类型定义:** ```typescript interface SSEChunk { type: "thinking" | "content" | "citations" | "done" | "error"; content?: string; citations?: ChatCitation[]; error?: string; } ``` | type | 说明 | content | citations | error | |------|------|---------|-----------|-------| | thinking | 思考过程片段 | 思考文本 | — | — | | content | 回答正文片段 | 增量文本 | — | — | | citations | 引用信息(流末尾) | — | 引用数组 | — | | done | 流结束 | — | — | — | | error | 流错误 | — | — | 错误信息 | **调用方必须:** 1. 逐行读取 `data: {...}\n\n` 2. JSON.parse 每行 `data:` 后的内容 3. 累积 `thinking` chunk → 思考过程 4. 累积 `content` chunk → 回答正文 5. `done` 或 `error` → 结束流 **Headers:** ``` Content-Type: text/event-stream; charset=utf-8 Cache-Control: no-cache Connection: keep-alive X-Accel-Buffering: no ``` --- ### 4.6 PATCH /rag-chat/sessions/:id — 更新会话 (NEW) **Request Body (全部可选):** ```json { "title": "三次握手深度讨论", "isPinned": true, "isArchived": false, "modelMode": "deep_think", "modelId": "deepseek-v4-pro" } ``` | 字段 | 类型 | 说明 | |------|------|------| | title | string | 新标题(最长 200 字符) | | isPinned | boolean | 置顶 | | isArchived | boolean | 归档 | | modelMode | ModelMode | 模型模式 | | modelId | string \| null | 模型 ID | **不可变字段:** - `scopeType` — 创建后不可修改 - `scopeId` — 创建后不可修改 - `parentKnowledgeBaseId` — 创建后不可修改(由后端推导) - `createdFrom` — 创建后不可修改 尝试修改这些字段 → 字段被静默忽略(不报错) **Response 200:** 更新后的 ChatSession 对象 **Errors:** | Code | 条件 | |------|------| | 404 | 会话不存在 | | 403 | 会话不属于当前用户 | --- ### 4.7 DELETE /rag-chat/sessions/:id — 软删除 **Behavior:** ``` UPDATE ChatSession SET isDeleted = true WHERE id = :id AND userId = :userId ``` 消息不物理删除。 **Response 200:** ```json { "success": true, "message": "会话已删除" } ``` --- ## 5. TypeScript 类型 (后端) ```typescript // === Request DTOs === interface CreateSessionDto { scopeType: ChatScopeType; scopeId?: string | null; parentKnowledgeBaseId?: string | null; createdFrom?: CreatedFrom; title?: string; } interface ListSessionsQuery { scopeType?: ChatScopeType; scopeId?: string; parentKnowledgeBaseId?: string; isArchived?: boolean; page?: number; limit?: number; } interface SendMessageDto { content: string; } interface UpdateSessionDto { title?: string; isPinned?: boolean; isArchived?: boolean; modelMode?: ModelMode; modelId?: string | null; } // === Response Types === interface ChatSessionResponse { id: string; userId: string; scopeType: ChatScopeType; scopeId: string | null; parentKnowledgeBaseId: string | null; title: string; createdFrom: CreatedFrom; modelMode: ModelMode; modelId: string | null; isPinned: boolean; isArchived: boolean; isDeleted: boolean; lastMessageAt: string | null; createdAt: string; updatedAt: string; } interface ChatMessageResponse { id: string; sessionId: string; role: "user" | "assistant"; content: string; tokens: number; scopeSnapshot: ChatScope | null; createdAt: string; citations: ChatCitationResponse[]; } interface ChatCitationResponse { id: string; messageId: string; chunkId: string | null; sourceId: string | null; sourceTitle: string | null; excerptText: string | null; pageNumber: number | null; lineStart: number | null; lineEnd: number | null; createdAt: string; } interface PaginatedResponse { data: T[]; meta: { page: number; limit: number; total: number; }; } interface SendMessageResponse { id?: string; role?: string; content?: string; tokens?: number; blocked?: boolean; message?: ChatMessageResponse; citations?: ChatCitationResponse[]; } ``` --- ## 6. Swift 类型 (iOS) ```swift // MARK: - Request DTOs struct CreateChatSessionRequest: Codable { let scopeType: String let scopeId: String? let parentKnowledgeBaseId: String? let createdFrom: String let title: String? } struct SendMessageRequest: Codable { let content: String } struct UpdateChatSessionRequest: Codable { var title: String? var isPinned: Bool? var isArchived: Bool? var modelMode: String? var modelId: String? } // MARK: - Response Types struct ChatSessionResponse: Codable, Identifiable { let id: String let userId: String? let scopeType: String let scopeId: String? let parentKnowledgeBaseId: String? let title: String? let createdFrom: String? let modelMode: String? let modelId: String? let isPinned: Bool? let isArchived: Bool? let isDeleted: Bool? let lastMessageAt: String? let createdAt: String? let updatedAt: String? } struct ChatMessageResponse: Codable, Identifiable { let id: String let sessionId: String? let role: String let content: String let tokens: Int? let scopeSnapshot: ChatScopeSnapshot? let createdAt: String? let citations: [ChatCitationResponse]? } struct ChatScopeSnapshot: Codable { let scopeType: String? let scopeId: String? let parentKnowledgeBaseId: String? } struct ChatCitationResponse: Codable, Identifiable { let id: String let messageId: String? let chunkId: String? let sourceId: String? let sourceTitle: String? let excerptText: String? let pageNumber: Int? let lineStart: Int? let lineEnd: Int? let createdAt: String? } struct SendMessageResponse: Codable { let id: String? let role: String? let content: String? let tokens: Int? let blocked: Bool? let message: ChatMessageResponse? let citations: [ChatCitationResponse]? } struct PaginatedChatSessions: Codable { let data: [ChatSessionResponse] let meta: PaginationMeta } // MARK: - SSE Chunk struct SSEChunk: Decodable { let type: String // "thinking" | "content" | "citations" | "done" | "error" let content: String? let citations: [ChatCitationResponse]? let error: String? } ``` --- ## 7. 错误响应格式 所有错误统一格式: ```json { "statusCode": 400, "message": "scopeType must be one of: knowledge_base, folder, material, knowledge_item, global", "error": "Bad Request" } ``` | HTTP Code | 场景 | |-----------|------| | 400 | 参数校验失败 | | 401 | JWT 缺失/过期 | | 403 | 会话不属于当前用户 | | 404 | 会话不存在 | | 413 | content 超过 10000 字符 | | 500 | 服务端未知错误 | --- ## 8. 版本兼容性 | 版本 | 日期 | 变更 | |------|------|------| | v1.0 | 2026-06-06 | 初始版本 — 完整 ChatScope API | 向后兼容策略: - 新增字段:前端未传 → 使用默认值 - 新增响应字段:前端忽略未知字段(Codable 默认行为) - 不可变字段:PATCH 时静默忽略(不报错) --- ## 9. 依赖 ``` 本文档依赖: chat-scope-design.md (CHAT-001) — ChatScope 类型定义、规则、决策表、open-or-create 算法 本文档被依赖: #81 M7-05 — createSession 实现 #76 M7-08 — open-or-create 实现 #75 M7-07 — listSessions 实现 #45-#50 — iOS AI Chat View #39-#44 — iOS 入口接入 ``` --- > **本文档是前后端接口的唯一权威契约。如有冲突,以本文档为准。**