# AI 助手功能 - 需求规格说明书 | 文档版本 | 修订日期 | 修订人 | 修订内容 | | :------- | :--------- | :----- | :----------------------------------------------------------------------- | | V1.0 | 2026-05-23 | — | 初始版本,定义首页 AI 助手聊天功能需求 | | V1.1 | 2026-05-23 | — | 增加多轮上下文(最多 5 轮/会话)、会话持久化、左侧历史会话列表与回看能力 | | V1.2 | 2026-05-23 | — | 产品约定(D-01~D-06)经确认纳入正式需求,移除待确认表述 | --- ## 1. 项目概述 ### 1.1 功能背景 当前 Jog 博客系统公开站点顶部导航(`BlogLayout`)仅提供 **「首页」** 入口。访客与登录用户无法在站内直接获得与博客、写作相关的智能问答能力。为提升站点互动性与 AI 能力曝光,在首页导航区新增 **「AI 助手」** 入口,提供独立的对话页面:用户可 **多轮追问**(单会话最多 5 轮),问答内容 **持久化存储**;页面 **左侧展示历史会话**,点选可 **完整回看** 当时的全部提问与 AI 回复。 ### 1.2 功能目标 - 在公开站点顶部导航增加「AI 助手」入口,与「首页」并列,便于发现与访问 - 支持 **多轮上下文对话**(单会话最多 **5 轮**),超出后引导用户 **新建会话** 继续提问 - **持久化** 用户每条提问与 AI 每条回复,刷新或换设备登录后可恢复历史 - 页面 **左侧会话列表** + **右侧对话区**,点选历史会话可只读回看完整往来记录 - 复用现有 DashScope(阿里云百炼)OpenAI 兼容接入与 `dashscope.*` 配置 - 通过登录鉴权与用户级限流,控制调用成本与滥用风险 ### 1.3 适用范围 | 角色 | 涉及范围 | | :------------- | :----------------------------------------------------------------------- | | 产品经理 | 验证导航、多轮上限、新会话流程、左侧历史与回看交互 | | 前端开发工程师 | 左右分栏布局、会话列表、多轮发送、新会话、历史回看 | | 后端开发工程师 | 对话 API、会话/消息持久化、多轮上下文组装、Flyway 表结构 | | 测试工程师 | 多轮边界、持久化、历史加载、5 轮上限等用例 | | 运维工程师 | 复用 `DASHSCOPE_API_KEY`;关注对话表容量与备份策略 | ### 1.4 现状说明 | 项目 | 现状 | | :------- | :------------------------------------------------------------------- | | 顶部导航 | `jog-frontend/src/layouts/BlogLayout.vue` 中 `el-menu` 仅含「首页」 | | 路由 | 公开布局子路由仅有 `/` 与 `/article/:id` | | AI 能力 | 已有摘要、润色等 `/api/app/**` 接口,均依赖 DashScope | | 数据存储 | 尚无 AI 对话相关表,需新增 Flyway 迁移 | ### 1.5 改动合理性评估(V1.1) | 改动项 | 合理性说明 | | :--------------- | :------------------------------------------------------------------------- | | 多轮 + 最多 5 轮 | 满足追问与指代理解;限制轮次可控制 Token 成本与模型超时风险,与现有 AI 功能限流思路一致 | | 问答持久化 | 与「左侧历史、完整回看」强依赖;需登录态绑定 `user_id`,避免数据串户 | | 左侧历史列表 | 常见聊天产品形态;列表按 **会话** 聚合(见 2 名词解释),避免每条提问占一行导致列表过长 | | 新会话 | 5 轮上限后的自然出口;同时用于用户主动开启新话题 | --- ## 2. 名词解释 | 术语 | 说明 | | :------------- | :------------------------------------------------------------------------------------- | | AI 助手 | 面向站点用户的通用问答对话功能 | | 会话(Session)| 一次连续对话线程,拥有唯一 `sessionId`;左侧历史列表的 **每一行对应一个会话** | | 轮次(Round) | **1 轮 = 1 条用户提问 + 1 条 AI 回复**(均成功落库后计 1 轮);单会话最多 **5 轮** | | 新会话 | 用户主动点击「新会话」,创建新的 `sessionId`,右侧对话区清空,轮次计数从 0 开始 | | 只读回看 | 从左侧点选 **已满 5 轮** 的会话,或用户仅查看历史时:展示完整消息记录,**不可再发送** | | 继续对话 | 点选 **未满 5 轮** 的历史会话时:加载历史消息,可在该 `sessionId` 下继续追问 | | 会话标题 | 列表展示用,默认取该会话 **首条用户提问** 前 30 字(超出以 `…` 结尾) | | DashScope | 阿里云百炼 API,本系统通过 OpenAI 兼容 `chat/completions` 调用 | --- ## 3. 功能需求清单 ### 3.1 导航入口(前端) (与 V1.0 一致,编号保留。) #### 3.1.2 业务规则 | 编号 | 规则项 | 业务规则说明 | | :------------------- | :----------- | :--------------------------------------------------------------------------------------------------- | | FR-AI-CHAT-NAV-01 | 按钮位置 | 位于 `BlogLayout` 顶部 `el-menu` 内,紧挨「首页」之后,文案为「AI 助手」 | | FR-AI-CHAT-NAV-02 | 路由 | 跳转 `/ai-assistant`,路由 `name` 建议 `AiAssistant`,页面标题「AI 助手」 | | FR-AI-CHAT-NAV-03 | 激活态 | 当前路由为 `/ai-assistant` 时菜单项高亮 | | FR-AI-CHAT-NAV-04 | 可见性 | 未登录可见入口;**发送消息、查看历史列表** 需登录(见 3.2) | | FR-AI-CHAT-NAV-05 | 样式 | 与「首页」一致,可选聊天图标 | #### 3.1.3 验收标准 | 编号 | 验证场景 | 预期结果 | | :------------------- | :--------------------------- | :------------------------------------------------- | | AC-AI-CHAT-NAV-01 | 任意公开页查看顶部导航 | 可见「首页」与「AI 助手」 | | AC-AI-CHAT-NAV-02 | 点击「AI 助手」 | 进入 `/ai-assistant` | | AC-AI-CHAT-NAV-03 | 在 AI 助手页刷新 | 「AI 助手」为激活态 | --- ### 3.2 AI 助手对话页(前端) #### 3.2.1 功能描述 新增 `AiAssistant.vue`(`BlogLayout` 子路由)。页面为 **左右分栏**: - **左侧(约 260px)**:顶部「新会话」按钮 + 当前用户的 **历史会话列表**(按最近更新时间倒序) - **右侧**:当前会话的 **完整消息流**(用户/AI 气泡)+ 底部输入区与「发送」;显示当前会话 **轮次指示**(如 `3/5`) 用户发送问题时携带当前 `sessionId`(新会话时由后端创建或前端先调创建接口获得)。成功后消息入库并在右侧追加。左侧列表在首条提问成功后出现/更新该会话项。 #### 3.2.2 页面布局 ``` ┌────────────────────────────────────────────────────────────────────┐ │ Jog Logo [首页] [AI助手] 登录/用户 │ ├──────────────┬─────────────────────────────────────────────────────┤ │ [ 新会话 ] │ 当前会话标题(首问摘要) 轮次 3/5 │ │──────────────│─────────────────────────────────────────────────────│ │ 历史会话列表 │ · 用户:……(右/主色) │ │ · 如何在… │ · AI:……(左/灰底) │ │ 05-23 14:20│ · 用户:…… │ │ · Spring… │ · AI:…… │ │ 05-22 09:10│─────────────────────────────────────────────────────│ │ (滚动) │ [ 请输入您的问题... ] [ 发送 ] │ └──────────────┴─────────────────────────────────────────────────────┘ ``` #### 3.2.3 业务规则 | 编号 | 规则项 | 业务规则说明 | | :------------------- | :--------------- | :---------------------------------------------------------------------------------------------------------------------- | | FR-AI-CHAT-FE-01 | 分栏布局 | 左侧固定宽约 260px,右侧 `flex:1`;小屏(<768px)可折叠左侧为抽屉(可选,首版允许左右同时展示并横向滚动) | | FR-AI-CHAT-FE-02 | 新会话按钮 | 左侧顶部主按钮「新会话」;点击后:清空右侧消息、清空当前 `sessionId`、轮次显示 `0/5`、输入区可用;**不删除** 已有历史会话 | | FR-AI-CHAT-FE-03 | 历史列表项 | 展示会话标题(首问前 30 字)+ 最后更新时间;当前选中会话高亮;默认进入页面时选中 **最近一次活跃会话**(若无则空状态) | | FR-AI-CHAT-FE-04 | 点选历史 | 调用 `GET .../sessions/{id}/messages` 加载 **该会话全部消息**(按时间正序),右侧完整展示用户提问与 AI 回复全文 | | FR-AI-CHAT-FE-05 | 继续 vs 只读 | 若该会话 `roundCount < 5`:加载后可继续发送(`sessionId` 设为当前);若 `roundCount >= 5`:**只读回看**,输入区禁用,展示提示条 | | FR-AI-CHAT-FE-06 | 五轮上限提示 | 当前会话已达 5 轮时:禁用发送;提示「当前会话已满 5 轮,请点击左侧 **新会话** 开始新的对话」;`ElMessage.warning` 可选 | | FR-AI-CHAT-FE-07 | 轮次指示 | 右侧标题栏展示 `当前轮次/5`(仅统计已成功完成的 user+assistant 一对) | | FR-AI-CHAT-FE-08 | 输入框 | `textarea`,占位符「请输入您的问题…」,1~6 行自适应 | | FR-AI-CHAT-FE-09 | 发送 | 主按钮「发送」;`Ctrl/Cmd+Enter` 发送;trim 后 1~2000 字符 | | FR-AI-CHAT-FE-10 | 登录校验 | 未登录:左侧列表为空或展示「登录后查看历史」;发送时提示并跳转 `/login?redirect=/ai-assistant` | | FR-AI-CHAT-FE-11 | Loading | 请求中发送按钮 loading、输入只读;列表末「正在思考…」 | | FR-AI-CHAT-FE-12 | 消息展示 | 用户/AI 分色气泡;AI 文本 `pre-wrap`;首版不做 Markdown | | FR-AI-CHAT-FE-13 | 失败处理 | 若 AI 调用失败:**不** 将本轮计入 `roundCount`;用户消息可保留为「发送失败」状态或允许重试(见 3.3 FR-AI-CHAT-BE-15) | | FR-AI-CHAT-FE-14 | 刷新恢复 | 刷新页面后重新拉取会话列表与当前/最近会话消息,**不依赖** localStorage 存对话正文 | | FR-AI-CHAT-FE-15 | 空状态 | 无会话时右侧展示欢迎文案;左侧列表空状态「暂无对话,点击新会话开始」 | | FR-AI-CHAT-FE-16 | 列表分页 | 会话列表默认加载最近 **50** 条,底部「加载更多」分页(`page`/`size`) | | FR-AI-CHAT-FE-17 | 滚动 | 右侧消息区内部滚动;新消息后滚到底部;回看历史会话打开时滚到底部 | #### 3.2.4 验收标准 | 编号 | 验证场景 | 预期结果 | | :------------------- | :----------------------------------------- | :--------------------------------------------------------------- | | AC-AI-CHAT-FE-01 | 已登录,点击「新会话」后连续问答 3 次 | 右侧 3 组问答,指示 `3/5`,左侧出现 1 个会话项 | | AC-AI-CHAT-FE-02 | 当前会话第 5 轮成功后尝试第 6 次发送 | 发送被拦截,提示点击「新会话」 | | AC-AI-CHAT-FE-03 | 点击「新会话」后再提问 | 新会话独立入库,左侧新增或切换到新会话项 | | AC-AI-CHAT-FE-04 | 点选已满 5 轮的历史会话 | 完整展示 5 轮共 10 条消息,输入区禁用,提示只读 | | AC-AI-CHAT-FE-05 | 点选仅 2 轮的历史会话并继续提问 | 加载 2 轮历史后继续,第 3 轮成功后为 `3/5` | | AC-AI-CHAT-FE-06 | 刷新页面 | 会话列表与最近会话消息与刷新前一致 | | AC-AI-CHAT-FE-07 | 未登录进入页面 | 无法发送;引导登录 | --- ### 3.3 AI 对话与会话接口(后端) #### 3.3.1 功能描述 提供会话管理与多轮对话能力: 1. 创建/继续会话并发送消息(调用大模型时带入 **当前会话已成功落库的全部历史**,最多 **5 轮 = 10 条消息**) 2. 查询当前用户会话列表 3. 查询指定会话下的全部消息(供左侧点选回看) 所有问答 **写入数据库**;仅允许操作 **当前登录用户** 自己的会话。 #### 3.3.2 数据模型(Flyway) 建议新增迁移 `V5__ai_chat_tables.sql`(版本号以仓库实际递增为准): **表 `ai_chat_session`** | 字段 | 类型 | 说明 | | :------------- | :------------- | :---------------------------------------- | | id | BIGINT PK | 会话 ID | | user_id | BIGINT | 所属用户,索引 | | title | VARCHAR(64) | 会话标题,首条用户消息截断 | | round_count | TINYINT | 已完成轮次,0~5 | | last_message_at| DATETIME | 最后一条消息时间,列表排序用 | | created_at | DATETIME | 创建时间 | | updated_at | DATETIME | 更新时间 | | deleted | TINYINT | 逻辑删除,默认 0(与项目 `@TableLogic` 一致) | **表 `ai_chat_message`** | 字段 | 类型 | 说明 | | :---------- | :------------ | :---------------------------------- | | id | BIGINT PK | 消息 ID | | session_id | BIGINT | 外键 → `ai_chat_session.id` | | role | VARCHAR(16) | `user` / `assistant` | | content | TEXT | 提问或回复 **全文** | | sort_order | INT | 会话内顺序,从 1 递增 | | created_at | DATETIME | 创建时间 | #### 3.3.3 接口清单 | 方法 | 路径 | 说明 | | :----- | :------------------------------------- | :----------------------------- | | POST | `/api/app/ai/sessions` | 创建空会话,返回 `sessionId` | | GET | `/api/app/ai/sessions` | 分页查询当前用户会话列表 | | GET | `/api/app/ai/sessions/{sessionId}/messages` | 查询会话全部消息(正序) | | POST | `/api/app/ai/chat` | 在指定会话中发送用户消息并返回 AI 回复 | #### 3.3.4 发送消息接口 `POST /api/app/ai/chat` **请求体** ```json { "sessionId": 1001, "message": "那和 GraphQL 有什么区别?" } ``` | 字段 | 必填 | 说明 | | :---------- | :--- | :------------------------------------------------------------------- | | sessionId | 是 | 会话 ID;须属于当前用户且 `round_count < 5` | | message | 是 | 本条用户提问,1~2000 字符(trim 后) | **处理流程** 1. 校验登录、`sessionId` 归属、`round_count < 5`、`message` 合法 2. 插入 `ai_chat_message`(role=`user`) 3. 读取该会话已有消息(最多 5 轮历史),按 `sort_order` 组装 `messages` 数组送入 DashScope(含 System Prompt) 4. 调用模型,得到 `reply` 5. 插入 `ai_chat_message`(role=`assistant`,content=全文) 6. 更新 `session.round_count += 1`、`last_message_at`;若为首条用户消息则更新 `title` 7. 返回 `reply` 及更新后的 `roundCount` **响应 `data` 示例** ```json { "sessionId": 1001, "reply": "GraphQL 与 REST 的主要区别在于……", "roundCount": 3, "maxRounds": 5, "model": "qwen-turbo", "promptTokens": 512, "completionTokens": 180, "costMillis": 2400 } ``` #### 3.3.5 业务规则 | 编号 | 规则项 | 业务规则说明 | | :------------------- | :--------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------ | | FR-AI-CHAT-BE-01 | 鉴权 | 全部接口需登录;`sessionId` 必须属于当前 `userId`,否则 `403` 或 `404` | | FR-AI-CHAT-BE-02 | 轮次上限 | `round_count >= 5` 时拒绝 `POST /chat`,返回 `400`,message:「当前会话已满 5 轮,请新建会话」 | | FR-AI-CHAT-BE-03 | 多轮上下文 | 调用模型时传入:System Prompt + 该会话已落库的 user/assistant 消息(按时间序),**最多 10 条**(5 轮);不含本次尚未入库的 user 消息则先入库 user 再调模型 | | FR-AI-CHAT-BE-04 | 上下文长度保护 | 组装后的总字符数超过 `dashscope.max-input`(默认 8000)时,**从最早的用户/助手对开始丢弃**,直至不超限(System 与当前 user 消息保留) | | FR-AI-CHAT-BE-05 | 存储 | 用户 `message` 与 AI `reply` **全文** 写入 `ai_chat_message.content`,供历史回看 | | FR-AI-CHAT-BE-06 | 会话列表 | `GET /sessions` 按 `last_message_at` 降序;返回 `id, title, roundCount, lastMessageAt`;默认 `size=50` | | FR-AI-CHAT-BE-07 | 消息列表 | `GET /sessions/{id}/messages` 返回 `id, role, content, sortOrder, createdAt` 全量正序 | | FR-AI-CHAT-BE-08 | 新会话 | `POST /sessions` 创建 `round_count=0` 的记录;前端「新会话」优先调此接口拿到 `sessionId` 再提问 | | FR-AI-CHAT-BE-09 | 模型与 Key | 复用 `DashScopeProperties`;Key 缺失返回既有 `AI_API_KEY_MISSING` | | FR-AI-CHAT-BE-10 | System Prompt | 与 V1.0 一致,并补充:「当前为同一会话的多轮对话,请结合上文回答,避免重复已说过的内容。」 | | FR-AI-CHAT-BE-11 | 回复截断 | 单条 `reply` 超过 4000 字符截断并 `…` | | FR-AI-CHAT-BE-12 | 超时/限流/异常 | 超时 30s → `5002`;用户每分钟最多 **10 次** `POST /chat`(Redis `ai_chat:rate:`);DashScope 失败 → `5001` | | FR-AI-CHAT-BE-13 | 日志 | 日志中 **不打印** 问答全文,可记录 `sessionId`、轮次、字符长度、token、耗时 | | FR-AI-CHAT-BE-14 | 软删除 | 会话表支持逻辑删除(为后续「删除历史」预留);首版前端 **不提供** 删除入口 | | FR-AI-CHAT-BE-15 | 失败不落 assistant | 模型调用失败时:已插入的 user 消息 **保留**;不插入 assistant、不增加 `round_count`;返回错误码,前端允许用户 **重试发送**(同 `sessionId` 不重复插 user,或后端按幂等键去重——首版允许重复 user 消息则需在需求中禁止,**采用:失败时仅重试模型,不重复插入 user**) | **FR-AI-CHAT-BE-15 实现约定**:发送接口在步骤 2 插入 user 后若模型失败,user 消息保留;重试时若上一条为未配对的 user,则 **不再插入新 user**,仅重新请求模型并写入 assistant。 #### 3.3.6 错误码补充 | 错误码 / HTTP | 描述 | 触发条件 | | :------------ | :----------------------- | :-------------------------------- | | 400 | 参数错误 | message 非法 | | 400 | 会话轮次已满 | `round_count >= 5` | | 403/404 | 会话不存在或无权访问 | `sessionId` 非本人 | | 401 | 未登录 | 无 Token | | 429 | 请求过于频繁 | 限流 | | 5001/5002 | AI 不可用 / 超时 | DashScope 异常 | #### 3.3.7 验收标准 | 编号 | 验证场景 | 预期结果 | | :------------------- | :------------------------------------ | :-------------------------------------------- | | AC-AI-CHAT-BE-01 | 新会话连续 5 次成功 `/chat` | `round_count=5`,第 6 次返回 400 | | AC-AI-CHAT-BE-02 | 第 3 轮提问含指代「它」 | 模型请求体含前 2 轮共 4 条历史消息 | | AC-AI-CHAT-BE-03 | `GET .../messages` | 返回条数与轮次一致,content 为当时全文 | | AC-AI-CHAT-BE-04 | 用户 A 访问用户 B 的 sessionId | 403/404 | | AC-AI-CHAT-BE-05 | 模型失败后重试 | 不重复 user 行,成功后 round_count 正确 +1 | --- ### 3.4 配置与部署 仍复用 `dashscope.*` 与 `DASHSCOPE_API_KEY`。新增表由 Flyway 自动迁移。部署后验证:多轮对话、刷新恢复、第 5 轮后新建会话。 --- ## 4. 交互流程 ### 4.1 首次提问(新会话) ``` 1. 用户进入 /ai-assistant(已登录) 2. 点击「新会话」→ POST /sessions → 获得 sessionId 3. 输入问题 → POST /chat { sessionId, message } 4. 后端存 user → 调模型(仅 System + 本条 user)→ 存 assistant → roundCount=1 5. 右侧展示问答;左侧列表出现该会话(标题=首问截断) ``` ### 4.2 同会话多轮追问 ``` 1. 用户在当前 session 继续输入(roundCount < 5) 2. POST /chat → 后端载入已有消息作为上下文 → 返回 reply 3. roundCount 递增;满 5 后 FR-AI-CHAT-FE-06 拦截发送 ``` ### 4.3 回看历史会话 ``` 1. 用户点击左侧某会话项 2. GET /sessions/{id}/messages → 右侧展示全部 user/assistant 全文 3. 若 roundCount < 5:可继续发送;若 = 5:只读 + 提示新会话 ``` ### 4.4 满 5 轮后继续提问 ``` 1. 用户在本会话第 5 轮成功后再次发送 2. 前端拦截并提示「请点击新会话」 3. 用户点击「新会话」→ 新 sessionId → 重新开始 0/5 ``` --- ## 5. 接口与代码对齐(规划) ### 5.1 后端 | 模块 | 文件 | 变更说明 | | :-------------- | :---------------------------------------------------------------- | :-------------------------------- | | jog-admin | `db/migration/V5__ai_chat_tables.sql` | 会话与消息表 | | jog-module-blog | `entity/AiChatSession.java`, `AiChatMessage.java` | 实体 | | jog-module-blog | `mapper/...` | MyBatis-Plus Mapper | | jog-module-blog | `dto/*`, `vo/*` | 请求/响应对象 | | jog-module-blog | `service/AiChatService` + `impl` | 多轮组装、持久化、DashScope 调用 | | jog-module-blog | `controller/AiChatController.java` | 上述 REST 接口 | ### 5.2 前端 | 模块 | 文件 | 变更说明 | | :----------- | :---------------------------------------- | :-------------------------- | | jog-frontend | `layouts/BlogLayout.vue` | 「AI 助手」导航 | | jog-frontend | `router/index.js` | `/ai-assistant` | | jog-frontend | `views/AiAssistant.vue` | 左右分栏主页面 | | jog-frontend | `components/AiChatSidebar.vue`(可选拆分)| 左侧列表 + 新会话 | | jog-frontend | `components/AiChatPanel.vue`(可选拆分) | 右侧消息 + 输入 | | jog-frontend | `api/aiChat.js` | 会话与 chat API 封装 | --- ## 6. 非功能性需求 | 编号 | 类型 | 需求说明 | | :----- | :------- | :----------------------------------------------------------------------- | | NFR-01 | 性能 | 95 分位 ≤ 15s;5 轮上下文受 `max-input` 截断保护 | | NFR-02 | 可用性 | AI 不可用时仍可回看已存历史;失败可重试当前轮 | | NFR-03 | 安全性 | 会话严格按 `user_id` 隔离;API Key 不入库/日志/仓库;HTTPS 传输 | | NFR-04 | 隐私 | 对话正文存业务库,仅本人可读;管理后台 **不提供** 查看他人工单(首版) | | NFR-05 | 可扩展性 | 轮次上限、列表条数可配置化;预留流式 SSE、删除会话 | | NFR-06 | 容量 | 首版会话列表 **仅分页加载**(默认每页 50 条),不设单用户数量硬上限,**不自动删除** 历史会话 | | NFR-07 | 可观测性 | 记录 sessionId、轮次、耗时、token;日志不打印全文 | --- ## 7. 范围外(后续迭代) | 项 | 说明 | | :----------- | :----------------------------------------- | | 流式输出 | SSE 逐字展示回复 | | 删除会话 | 前端删除 + 逻辑删除接口 | | 未登录试用 | 公开 API + IP 限流 | | Markdown渲染 | AI 回复富文本(需 XSS 防护) | | 会话重命名 | 用户自定义左侧标题 | | 导出对话 | 导出 Markdown/PDF | --- ## 8. 风险与缓解 | 风险 | 影响 | 缓解措施 | | :------------------- | :---------------- | :-------------------------------------------- | | 5 轮上下文 Token 过大 | 超时/费用 | `max-input` 截断最早轮次 | | 历史表数据增长 | 存储成本 | 分页列表;后续归档策略 | | 满 5 轮体验断裂 | 用户困惑 | 明确提示 +「新会话」一键 | | 失败重试重复 user | 轮次与展示错乱 | BE-15 幂等重试约定 | --- ## 9. 产品约定(已确认) 以下约定已于 **2026-05-23** 与产品方确认,作为 V1.1 正式需求的一部分,开发与测试须按此实现。 | 编号 | 约定项 | 需求说明 | | :--- | :----- | :------- | | D-01 | 左侧列表按 **会话** 聚合 | 每会话占列表一行;标题取首条用户提问前 30 字;点选后右侧展示该会话下 **全部** 问答全文(非按单条提问分行) | | D-02 | 历史会话可继续对话 | `roundCount < 5` 的会话:加载历史后 **可继续发送**;`roundCount >= 5` 的会话:**只读回看**,输入区禁用并引导「新会话」 | | D-03 | 新会话不删除旧数据 | 点击「新会话」仅创建并切换至新 `sessionId`,清空右侧编辑区;**不删除、不覆盖** 已有会话数据 | | D-04 | 首版不提供删除与重命名 | 前端无删除会话、无重命名入口;`ai_chat_session.deleted` 字段预留供后续迭代(见 §7) | | D-05 | 失败不计轮、支持同轮重试 | 模型调用失败时不插入 assistant、不增加 `round_count`;允许对未配对的 user 消息重试,**不重复插入** user(见 FR-AI-CHAT-BE-15) | | D-06 | 进入页默认最近会话 | 已登录用户进入 `/ai-assistant` 时,默认选中 **最近更新** 的会话并加载其消息;无任何会话时展示空状态,等同待「新会话」 | | D-07 | 会话保留策略 | 历史会话 **仅分页展示**(默认每页 50 条),**不设** 单用户数量硬上限,**不自动删除** 最旧会话(与 NFR-06 一致) | --- **编写人:** AI 助手 **评审人:** 已确认(产品约定 D-01~D-07) **生效日期:** 2026-05-23