All checks were successful
Deploy API Server / build-and-deploy (push) Successful in 46s
## 数据模型 - ChatSession +13 字段 (scopeType/scopeId/parentKnowledgeBaseId/createdFrom/isPinned/isArchived/isDeleted/modelMode/modelId/lastMessageAt) - ChatMessage +scopeSnapshot (消息级 scope 快照) - ChatCitation +lineStart/lineEnd +sourceId 索引 - 5 个新查询索引 ## 核心能力 - open-or-create: 同 scope 继续会话 (200) / 新建 (201) - scope 级检索: global/knowledge_base/material/knowledge_item/folder - listSessions: scope 过滤 + isDeleted 排除 + isPinned 排序 + 分页元数据 - 自动标题: 首条消息截取 + 词边界处理 - 软删除 + 置顶/归档 - scope 字段创建后不可修改 - 全部端点 userId 鉴权 ## 文档 - docs/chat-scope-design.md (设计文档 + 决策表) - docs/chat-scope-api-contract.md (API 契约) - docs/chat-scope-test-plan.md (33 条测试用例) - prisma/migrations/backfill_chat_scope.sql (旧数据回填) ## Bug 修复 - #104: KnowledgeItem.sourceRef 填充 (material scope 检索修复) - #102: sendMessageStream aiGateway null 保护 - listSessions isDeleted/isArchived 过滤 + 分页 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
16 KiB
16 KiB
ChatScope API Contract
CHAT-002 | 版本 v1.0 | 2026-06-06
本文档是 ChatScope 前后端接口的唯一权威契约。 所有响应 shape、错误码、SSE 格式以本文档为准。 设计逻辑参见 ChatScope 设计文档。
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
type ChatScopeType =
| "knowledge_base"
| "folder"
| "material"
| "knowledge_item"
| "global";
2.2 CreatedFrom
type CreatedFrom =
| "knowledge_base_detail"
| "material_detail"
| "material_reader"
| "knowledge_item_detail"
| "folder_detail"
| "global_ai_entry"
| "legacy_migration";
2.3 ModelMode
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:
{
"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必须是合法枚举值,否则 400scopeType === "global"时,scopeId必须为 nullscopeType !== "global"时,scopeId不能为空(404 或 400)
Behavior (open-or-create):
IF 存在 userId + scopeType + scopeId 完全匹配 + isDeleted=false 的会话
→ 返回该会话 (HTTP 200)
ELSE
→ 创建新会话并返回 (HTTP 201)
Response 200 (已有会话):
{
"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 (新建会话):
{
"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:
{
"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:
[
{
"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:
{
"content": "TCP 三次握手的过程是什么?"
}
| 字段 | 类型 | 必需 | 说明 |
|---|---|---|---|
| content | string | 是 | 消息正文(最长 10000 字符) |
Response 200:
{
"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 (被拦截):
{
"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 类型定义:
interface SSEChunk {
type: "thinking" | "content" | "citations" | "done" | "error";
content?: string;
citations?: ChatCitation[];
error?: string;
}
| type | 说明 | content | citations | error |
|---|---|---|---|---|
| thinking | 思考过程片段 | 思考文本 | — | — |
| content | 回答正文片段 | 增量文本 | — | — |
| citations | 引用信息(流末尾) | — | 引用数组 | — |
| done | 流结束 | — | — | — |
| error | 流错误 | — | — | 错误信息 |
调用方必须:
- 逐行读取
data: {...}\n\n - JSON.parse 每行
data:后的内容 - 累积
thinkingchunk → 思考过程 - 累积
contentchunk → 回答正文 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 (全部可选):
{
"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:
{
"success": true,
"message": "会话已删除"
}
5. 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<T> {
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)
// 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. 错误响应格式
所有错误统一格式:
{
"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 入口接入
本文档是前后端接口的唯一权威契约。如有冲突,以本文档为准。