Files
Jog/doc/AI助手功能需求.md
2026-05-23 23:33:27 +08:00

423 lines
31 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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