Files
my-springboot-project/docs/AI流式问答与出题接口-前端使用指南.md

1163 lines
34 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 流式问答与出题接口 - 前端使用指南
> **适用范围**:知识库问答、会话管理、题目生成、答案批改
> **Base URL**`http://localhost:8080/api`
> **认证方式**`Authorization: Bearer <JWT Token>`
> **接口版本**v2.17.0
> **更新时间**2026-06-05
---
## 目录
1. [快速开始](#一快速开始)
2. [AI 流式问答接口](#二ai-流式问答接口)
3. [AI 会话管理接口](#三ai-会话管理接口)
4. [题目生成接口](#四题目生成接口)
5. [题目批改接口](#五题目批改接口)
6. [答题记录接口](#六答题记录接口)
7. [前后端字段对照表](#七前后端字段对照表)
8. [常见问题 FAQ](#八常见问题-faq)
9. [附录cURL 完整示例](#九附录curl-完整示例)
---
## 一、快速开始
### 1.1 认证流程
所有接口(登录接口除外)均需携带 JWT Token
```http
POST /api/auth/login
Content-Type: application/json
{
"username": "admin",
"password": "123456"
}
```
**响应**
```json
{
"code": 200,
"data": {
"token": "eyJhbGciOiJIUzUxMiJ9...",
"user": {
"id": 1,
"username": "admin",
"role": 1
}
}
}
```
后续所有接口请求头需添加:
```
Authorization: Bearer <token>
```
### 1.2 接口总览
| 模块 | 接口 | 方法 | 功能 |
|------|------|------|------|
| **流式问答** | `/ai/chat/stream` | `POST` | 发起 AI 流式问答 |
| **中断问答** | `/ai/chat/stop?sessionId=xxx` | `POST` | 中断流式回答 |
| **会话列表** | `/ai/sessions?page=1&pageSize=10` | `GET` | 获取当前用户的会话列表 |
| **会话详情** | `/ai/session/{sessionId}` | `GET` | 获取指定会话的消息记录 |
| **消息引用** | `/ai/message/{messageId}/references` | `GET` | 获取某条消息的引用列表 |
| **消息文件来源** | `/ai/message/{messageId}/sources` | `GET` | 获取某条消息引用的文件来源(去重) |
| **会话引用** | `/ai/session/{sessionId}/references` | `GET` | 获取会话的全部引用 |
| **删除会话** | `/ai/session/{sessionId}` | `DELETE` | 删除单个会话及相关数据 |
| **批量删除会话** | `/ai/sessions` | `DELETE` | 批量删除多个会话 |
| **生成题目** | `/exam/generate` | `POST` | 根据文档生成题目(异步) |
| **生成试卷** | `/exam/paper/generate` | `POST` | 动态生成试卷 |
| **开始考试** | `/exam/paper/{paperId}/start` | `POST` | 获取试卷题目 |
| **考试结果** | `/exam/record/{recordId}/result` | `GET` | 获取批阅结果 |
| **正式考试批改** | `/exam/record/{recordId}/grade` | `POST` | 正式考试提交并批改 |
| **批改答案** | `/exam/grade` | `POST` | 批改学生答案(通用) |
| **查询答题记录** | `/exam/answers/query` | `POST` | 查询用户答题记录 |
| **考试记录列表** | `/exam/records?type=practice` | `GET` | 获取考试/练习记录列表 |
---
## 二、AI 流式问答接口
### 2.1 发起流式问答
**接口**`POST /api/ai/chat/stream`
**Content-Type**`application/json`
**返回类型**`text/event-stream` (Server-Sent Events)
#### 请求体
| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `message` | string | ✅ | 用户提问内容 |
| `session_id` | string | ❌ | 会话ID不传则自动生成传空字符串 `""` 也会自动生成 |
| `id` | string | ❌ | 自定义请求ID用于追踪 |
**请求示例**
```json
{
"message": "请介绍一下知识库管理系统",
"session_id": "sess_1780410472658_9qg9a9hhp"
}
```
#### 内部逻辑说明
- 后端自动获取用户可访问的知识库(`collections`),无需前端传递
- 后端自动从数据库加载该会话的历史消息作为上下文
- 流式返回过程中finish 事件到达时会自动保存:
- 用户提问 → `context_message`role=user
- AI 回答 → `context_message`role=assistant含 answer、images 等)
- 引用切片 → `context_reference`(自动从 RAG 获取 preview 字段)
- 图片 → `context_image`(保留 score 字段)
#### 流式事件类型
| 事件 type | 说明 | 是否入库 |
|-----------|------|----------|
| `start` | 对话开始事件 | ❌ |
| `connected` | RAG 服务连接成功 | ❌ |
| `thinking` | AI 思考中 | ❌ |
| `searching` | 知识库检索中 | ❌ |
| `sources` | 检索到的候选来源信息 | ❌ 仅前端展示,**不写入数据库** |
| `chunk` | 流式内容片段 | ❌ 前端拼接显示 |
| `result` / `finish` | 最终完整结果(最重要) | ✅ 存入数据库 |
| `error` | 错误信息 | ❌ |
#### finish 事件核心字段
```json
{
"type": "finish",
"answer": "根据【参考资料】...完整的AI回答内容其中包含 [ref:文档名_86] 这样的引用标记。",
"duration_ms": 75512,
"citations": [
{
"chunk_id": "文档名_86",
"chunk_index": 86,
"source": "文档名.docx",
"collection": "dept_1_kb",
"doc_type": "other",
"section": "3.1 权重计算公式",
"chunk_type": "text",
"page": 1,
"score": 0.838,
"preview": "【系统自动从 RAG 接口获取的目标切片完整正文】",
"content": {
"stem": "切片摘要内容",
"data": {}
}
}
],
"images": [
{
"image_id": "49d2910b.jpg",
"path": "/api/image/49d2910b.jpg/data",
"source": "来源文档.docx",
"page": 1,
"chunk_type": "image",
"description": "图片描述",
"score": 0.85,
"collection": "dept_1_kb"
}
]
}
```
> **重要说明**
> - `answer` 中的 `[ref:chunk_id]` 标记:前端需解析为可点击链接,点击后跳转/展示对应引用信息
> - `preview` 字段:后端写入 `context_reference` 表时,会自动调用 RAG 预览接口 `GET /documents/{collection}/{source}/preview?chunk_index=N&context=0` 填充该字段;若接口报错,则填入错误信息
> - `context_message.answer` 字段保留完整的 `[ref:chunk_id]` 标记文本
#### answer 字段引用标记解析
前端需解析 `[ref:chunk_id]` 为可点击元素:
```text
"市场状态 = Σ(二级指标均值 × 权重)[ref:文档名_86]。
此外一级指标合计权重不低于70%[ref:文档名_93]。"
```
#### 前端 JavaScript 调用示例fetch + ReadableStream
```javascript
// 发起流式问答
async function chatStream(message, sessionId, token) {
const response = await fetch('/api/ai/chat/stream', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + token
},
body: JSON.stringify({ message, session_id: sessionId })
});
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = '';
let fullAnswer = '';
while (true) {
const { value, done } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const lines = buffer.split('\n');
buffer = lines.pop() || ''; // 保留未完成的行
for (const line of lines) {
if (!line.startsWith('data:')) continue;
try {
const data = JSON.parse(line.slice(5).trim());
handleEvent(data); // 分发到事件处理器
} catch (e) {
console.warn('解析失败:', line);
}
}
}
}
// 事件处理器(前端自行实现 UI 更新)
function handleEvent(data) {
switch (data.type) {
case 'chunk':
// 实时拼接显示到对话框
fullAnswer += data.content;
// renderAnswer(fullAnswer);
break;
case 'finish':
// 对话完成,拿到完整 answer、citations、images
console.log('完整回答:', data.answer);
console.log('引用列表:', data.citations);
console.log('图片列表:', data.images);
break;
case 'error':
console.error('AI 错误:', data.message);
break;
}
}
```
> **为什么不用 EventSource**EventSource 只能 GET 请求,且无法自定义请求头(无法传 Token。推荐使用 `fetch + ReadableStream` 方式。
### 2.2 中断流式问答
**接口**`POST /api/ai/chat/stop?sessionId=sess_xxx`
**响应**
```json
{
"success": true,
"message": "已中断AI回答"
}
```
---
## 三、AI 会话管理接口
> 统一响应结构(`Map`
> ```json
> {
> "success": true,
> "data": [...],
> "total": 10,
> "page": 1,
> "pageSize": 10,
> "pages": 1
> }
> ```
### 3.1 获取用户会话列表
**接口**`GET /api/ai/sessions?page=1&pageSize=10`
**查询参数**
| 参数 | 类型 | 必填 | 默认值 | 说明 |
|------|------|------|--------|------|
| `page` | int | ❌ | 1 | 页码 |
| `pageSize` | int | ❌ | 10 | 每页数量 |
**响应 data 元素字段**ChatSession
| 字段 | 类型 | 说明 |
|------|------|------|
| `id` | long | 数据库主键 |
| `sessionId` | string | 会话唯一ID |
| `userId` | long | 用户ID |
| `createTime` | datetime | 创建时间 |
### 3.2 获取会话详情(消息记录)
**接口**`GET /api/ai/session/{sessionId}?page=1&pageSize=20`
**权限检查**:后端自动校验该会话是否属于当前用户
**响应 data 元素字段**ChatMessage
| 字段 | 类型 | 说明 |
|------|------|------|
| `id` | long | 消息ID |
| `conversationId` | long | 会话ID数据库主键 |
| `role` | string | `user` / `assistant` |
| `content` | string | 用户提问原文role=user或 AI 回答内容role=assistant 时通常与 answer 一致) |
| `messageType` | string | `rag` 等 |
| `isFinished` | int | 1=完成 |
| `answer` | string | AI 完整回答(含 `[ref:chunk_id]` 标记),仅 role=assistant 时有值 |
| `images` | string(JSON数组) | 图片列表JSON 字符串,需前端解析) |
| `durationMs` | long | 耗时(毫秒) |
| `createTime` | datetime | 创建时间 |
> **注意**`ChatMessage` 实体中已**移除 `sources` 字段**。引用信息需通过 `/ai/message/{messageId}/references` 接口单独获取。
### 3.3 获取消息引用列表
**接口**`GET /api/ai/message/{messageId}/references?docName=xxx`
**查询参数**
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `docName` | string | ❌ | 按文档名称过滤引用 |
**响应 data 元素字段**ChatReference
| 字段 | 类型 | 说明 |
|------|------|------|
| `id` | long | 引用记录ID |
| `messageId` | long | 关联的消息ID |
| `chunkId` | string | 切片唯一标识 `{docName}_{chunkIndex}` |
| `chunkIndex` | int | 切片序号 |
| `collection` | string | 向量库名称 |
| `docName` | string | 文档名称 |
| `docType` | string | 文档类型 |
| `page` | int | 页码 |
| `section` | string | 章节/段落位置 |
| `chunkType` | string | `text` / `image` / `table` |
| `excerpt` | string | 切片摘要内容 |
| **`preview`** | string | **目标切片完整正文(系统自动从 RAG 获取,失败则填错误信息)** |
| `score` | decimal | 相关性评分 0-1 |
| `createTime` | datetime | 创建时间 |
### 3.4 获取消息文件来源列表(去重)
**接口**`GET /api/ai/message/{messageId}/sources`
**用途**:对同一条消息的引用按文档名做去重汇总,用于展示"参考文档列表"侧栏
**响应 data 元素字段**
| 字段 | 类型 | 说明 |
|------|------|------|
| `docName` | string | 文档名称(去重后的唯一标识) |
| `docType` | string | 文档类型 |
| `referenceCount` | int | 该文档被引用的次数 |
| `pages` | string | 页码范围,如 `"5"``"1-10"` |
**响应示例**
```json
{
"success": true,
"data": [
{
"docName": "薪酬制度.docx",
"docType": "other",
"referenceCount": 5,
"pages": "1-8"
},
{
"docName": "考核办法.docx",
"docType": "other",
"referenceCount": 3,
"pages": "3"
}
],
"total": 2
}
```
### 3.5 获取会话全部引用
**接口**`GET /api/ai/session/{sessionId}/references`
**用途**:返回整个会话中所有 AI 回答的引用汇总,字段与单条消息引用接口一致
### 3.6 删除单个会话
**接口**`DELETE /api/ai/session/{sessionId}`
删除指定会话及其所有消息和引用数据。后端自动校验会话所属权。
### 3.7 批量删除会话
**接口**`DELETE /api/ai/sessions`
**请求体**
```json
["sess_xxx1", "sess_xxx2", "sess_xxx3"]
```
**响应**
```json
{
"success": true,
"message": "会话批量删除成功",
"deletedCount": 3
}
```
---
## 四、题目生成接口
> 本模块接口统一响应结构(`Result<T>`
> ```json
> {
> "code": 200,
> "message": "操作成功",
> "data": { ... }
> }
> ```
### 4.1 生成题目(异步)
**接口**`POST /api/exam/generate`
**Content-Type**`application/json`
#### 请求体字段
| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `file_id` | Long | ⭐二选一 | 文件ID系统自动从 `knowledge_file` 表查询向量库信息(**推荐** |
| `file_path` | string | ⭐二选一 | 文件路径(如 `dept_1_kb/薪酬制度.docx` |
| `collection` | string | file_path 必填 | 向量库名称(使用 file_path 时必须提供) |
| `question_types` | object | ❌ | 指定题型及数量;不传则使用 AI 智能生成模式 |
| `question_scores` | object | ❌ | 自定义题型分数配置 |
| `difficulty` | int | ❌ | 难度等级 1-5默认 3 |
| `request_id` | string | ❌ | 请求ID用于幂等性 |
#### question_types 可选值
| 题型 | key | 说明 |
|------|-----|------|
| 单选题 | `single_choice` | 四个选项单选 |
| 多选题 | `multiple_choice` | 四个选项多选 |
| 判断题 | `true_false` | 对/错两选 |
| 填空题 | `fill_blank` | 按空格填写答案,支持同义词 |
| 简答题 | `subjective` | 开放回答,由 LLM 评分 |
#### 请求示例:指定题型模式
```json
{
"file_id": 1,
"question_types": {
"single_choice": 3,
"multiple_choice": 2,
"true_false": 2,
"fill_blank": 2,
"subjective": 1
},
"difficulty": 3,
"request_id": "gen_req_001"
}
```
#### 请求示例:智能生成模式(推荐)
不提供 `question_types` 参数时,系统调用 AI 智能出题接口,自动根据文档内容判断题型和数量:
```json
{
"file_id": 1,
"difficulty": 3
}
```
#### 响应示例(成功)
```json
{
"code": 200,
"message": "收到请求",
"data": {
"requestId": "gen_req_001",
"fileId": 1,
"fileMatchStatus": "success",
"matchedCollection": "dept_1_kb",
"documentName": "薪酬制度.docx",
"collectionFilled": true,
"asyncTaskStarted": true,
"nextStep": "请通过文件状态接口查询生成进度",
"status": "success"
}
}
```
#### 响应示例(文件验证失败)
```json
{
"code": 400,
"message": "文件不存在,请确认 file_id 是否正确",
"data": {
"status": "error",
"errorType": "FILE_VALIDATION_ERROR",
"errorDetail": "文件不存在",
"suggestion": "请检查 file_id 是否正确、文件是否已删除、或文件是否已完成向量化"
}
}
```
**fileMatchStatus 可能值**
| 值 | 说明 | HTTP 状态码 |
|----|------|------------|
| `success` | 文件验证通过,启动异步任务 | 200 |
| `FILE_NOT_FOUND` | 文件不存在 | 400 |
| `FILE_DELETED` | 文件已被删除 | 400 |
| `NOT_VECTORIZED` | 文件尚未完成向量化 | 400 |
| `PARAM_MISSING` | 参数不完整 | 400 |
| `INTERNAL_ERROR` | 服务器内部错误 | 500 |
### 4.2 轮询题目生成进度
**接口**`GET /api/file/{fileId}`
**关键字段**
| 字段 | 说明 |
|------|------|
| `processStatus` | 文件处理状态(如 `INDEXED` |
| `examStatus` | **题目生成状态**`UNGENERATED` / `GENERATING` / `GENERATED` / `FAILED` |
| `processMessage` | 生成详情,包含题目数量和 AI 分析结果 |
**examStatus 流转**
```
UNGENERATED ──► GENERATING ──► GENERATED
└──────► FAILED
```
**轮询策略建议**:初始间隔 2-3 秒;超时 5-10 分钟examStatus 达到终态则停止。
### 4.3 生成试卷
**接口**`POST /api/exam/paper/generate`
**请求体**
```json
{
"single_choice_count": 5,
"multiple_choice_count": 3,
"true_false_count": 2,
"fill_blank_count": 2,
"subjective_count": 1,
"difficulty": 3,
"include_personal": false,
"file_ids": [1, 2, 3]
}
```
**响应 data 字段**`ExamPaperGenerateResponse`
| 字段 | 类型 | 说明 |
|------|------|------|
| `paperId` | string | 试卷ID |
| `paperTitle` | string | 试卷标题 |
| `totalScore` | decimal | 总分 |
| `questionCount` | int | 题目总数 |
| `generatedAt` | datetime | 生成时间 |
| `questions` | array | 题目列表 |
---
## 五、题目批改接口
### 5.1 批改答案(日常练习 / 模拟考 / 正式考试通用)
**接口**`POST /api/exam/grade`
**请求体字段**
| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `request_id` | string | ❌ | 请求ID |
| `type` / `answerType` | string | ❌ | `practice` / `mock_exam` / `formal_exam` |
| `record_id` | string | ❌ | 正式考试记录ID提供时走正式考试提交/批改分支 |
| `paper_id` | string | ❌ | 试卷ID |
| `session_id` | string | ❌ | 会话ID |
| `answers` | array | ✅ | 答案列表 |
**answers 数组元素字段**
| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `question_id` | string | ✅ | 题目ID |
| `question_type` | string | ✅ | `single_choice` / `multiple_choice` / `true_false` / `fill_blank` / `subjective` |
| `question_content` | object | ✅ | 题目内容(含 stem / options / answer 等) |
| `student_answer` | any | ✅ | 学生提交的答案 |
| `max_score` | number | ✅ | 满分 |
**请求示例**
```json
{
"request_id": "grade_req_001",
"answer_type": "practice",
"answers": [
{
"question_id": "q_001",
"question_type": "single_choice",
"question_content": {
"stem": "根据公司规定,员工薪资由哪几部分组成?",
"data": {
"options": [
{ "key": "A", "content": "基本工资+奖金" },
{ "key": "B", "content": "基本工资+绩效奖金+津贴补贴" }
]
},
"answer": "B"
},
"student_answer": "B",
"max_score": 5.0
},
{
"question_id": "q_002",
"question_type": "multiple_choice",
"question_content": {
"stem": "以下哪些属于绩效奖金的评定因素?",
"data": { "options": [...] },
"answer": ["A", "B", "D"]
},
"student_answer": ["A", "B"],
"max_score": 4.0
},
{
"question_id": "q_003",
"question_type": "true_false",
"question_content": {
"stem": "公司规定员工每月绩效奖金上限为工资的20%。",
"answer": "F"
},
"student_answer": "T",
"max_score": 2.0
},
{
"question_id": "q_004",
"question_type": "fill_blank",
"question_content": {
"stem": "员工薪资由___、___和___三部分组成。",
"data": { "blank_count": 3 },
"answer": [["基本工资"], ["绩效奖金", "绩效"], ["津贴补贴", "补贴"]]
},
"student_answer": ["基本工资", "绩效奖金", "交通补贴"],
"max_score": 6.0
},
{
"question_id": "q_005",
"question_type": "subjective",
"question_content": {
"stem": "请简述公司薪酬制度的核心原则。",
"data": {
"scoring_points": [
{ "point": "公平性", "weight": 0.3 },
{ "point": "激励性", "weight": 0.3 },
{ "point": "竞争力", "weight": 0.2 },
{ "point": "合法性", "weight": 0.2 }
]
},
"answer": "公司薪酬制度遵循公平、激励、竞争、合法四大原则..."
},
"student_answer": "我认为公司的薪酬制度应该公平公正...",
"max_score": 10.0
}
]
}
```
**响应 data 字段**`ExamGradeResponse`
| 字段 | 类型 | 说明 |
|------|------|------|
| `success` | boolean | 是否成功 |
| `totalScore` | decimal | 得分 |
| `totalMaxScore` | decimal | 满分 |
| `scoreRate` | decimal | 得分率(百分比) |
| `results` | array | 每题批改结果 |
**results 元素字段**
| 字段 | 类型 | 说明 |
|------|------|------|
| `questionId` | string | 题目ID |
| `questionType` | string | 题型 |
| `score` | decimal | 本题得分 |
| `maxScore` | decimal | 本题满分 |
| `correct` / `isCorrect` | boolean | 是否答对 |
| `studentAnswer` | any | 学生答案 |
| `correctAnswer` | any | 正确答案 |
| `feedback` | string | 批改反馈 |
**批阅规则**
| 题型 | 批阅方式 | 得分规则 |
|------|----------|----------|
| 单选题 | 精确匹配 | 正确得满分,错误得 0 |
| 多选题 | 集合比对 | 全对满分,少选一半,错选得 0 |
| 判断题 | 精确匹配 | 正确得满分,错误得 0 |
| 填空题 | 多答案匹配 | 每空独立评分,支持同义词匹配 |
| 简答题 | LLM 评分 | 根据得分点评分,不超过 max_score |
### 5.2 正式考试提交并批改
**接口**`POST /api/exam/record/{recordId}/grade`
**路径参数**`recordId`正式考试记录ID
**请求体**`ExamSaveRequest`
```json
{
"answers": [
{
"question_id": "q_001",
"question_type": "single_choice",
"answer": "B"
}
]
}
```
---
## 六、答题记录接口
### 6.1 开始考试(获取题目)
**接口**`POST /api/exam/paper/{paperId}/start`
系统自动查找或创建考试记录,返回试卷题目内容。
### 6.2 获取考试结果
**接口**`GET /api/exam/record/{recordId}/result`
**权限检查**:后端校验该记录是否属于当前用户
**响应 data 字段**
| 字段 | 类型 | 说明 |
|------|------|------|
| `recordId` | string | 记录ID |
| `paperId` | string | 试卷ID |
| `paperTitle` | string | 试卷标题 |
| `status` | string | `submitted` 等 |
| `totalScore` | decimal | 得分 |
| `totalMaxScore` | decimal | 满分 |
| `scoreRate` | decimal | 得分率 |
| `submitTime` | datetime | 提交时间 |
| `answers` | array | 每题结果 |
### 6.3 查询用户答题记录
**接口**`POST /api/exam/answers/query`
**请求体**`UserAnswerQueryRequest`
| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `paper_id` | string | ⭐二选一 | 按试卷ID查询 |
| `session_id` | string | ⭐二选一 | 按会话ID查询 |
| `answer_type` | string | ❌ | 过滤类型practice / mock_exam / formal_exam |
| `page` | int | ❌ | 页码,默认 1 |
| `page_size` | int | ❌ | 每页数量,默认 20 |
**请求示例**
```json
{
"paper_id": "paper_abc123",
"answer_type": "practice",
"page": 1,
"page_size": 20
}
```
### 6.4 获取考试记录列表
**接口**`GET /api/exam/records?type=practice`
**查询参数**
| 参数 | 类型 | 必填 | 默认值 | 说明 |
|------|------|------|--------|------|
| `type` | string | ❌ | `practice` | `practice` / `mock_exam` / `formal_exam` |
**分支逻辑**
- `type=practice` → 返回日常练习答题记录(`UserAnswerQueryResponse`
- `type=mock_exam``formal_exam` → 返回试卷考试记录列表(`List<ExamRecordResponse>`
---
## 七、前后端字段对照表
### 7.1 ChatReference 引用字段映射
| 后端字段 | 前端字段AI返回 | 类型 | 说明 |
|----------|-------------------|------|------|
| `chunkId` | `chunk_id` | string | 切片唯一标识 `{docName}_{chunkIndex}` |
| `chunkIndex` | `chunk_index` | int | 切片序号 |
| `collection` | `collection` | string | 向量库名称 |
| `docName` | `source` / `doc_name` | string | 文档名称 |
| `docType` | `doc_type` | string | 文档类型 |
| `page` | `page` | int | 页码 |
| `section` | `section` | string | 章节/段落位置 |
| `chunkType` | `chunk_type` | string | `text` / `image` / `table` |
| `excerpt` | `content.stem` | string | 切片摘要 |
| **`preview`** | —(系统自动填充) | string | **目标切片完整正文**,由后端从 RAG 预览接口获取,失败则填错误信息 |
| `score` | `score` | decimal | 相关性评分 0-1 |
| `createTime` | — | datetime | 创建时间 |
### 7.2 ChatMessage 消息字段映射
| 后端字段 | 前端字段 | 类型 | 说明 |
|----------|----------|------|------|
| `id` | `message_id` | long | 消息唯一ID |
| `conversationId` | `conversation_id` | long | 会话数据库主键ID |
| `role` | `role` | string | `user` / `assistant` |
| `content` | `content` | string | 用户提问原文 / AI 回答 |
| `answer` | `answer` | string | AI 完整回答(含 `[ref:chunk_id]` 标记role=assistant 有效 |
| `messageType` | `message_type` | string | `rag` 等 |
| `isFinished` | `is_finished` | int | 1=完成 |
| `images` | `images` | string(JSON数组) | 图片列表JSON 字符串,前端需 JSON.parse |
| `durationMs` | `duration_ms` | long | 耗时(毫秒) |
| `createTime` | `create_time` | datetime | 创建时间 |
> **已移除字段**`sources`。引用信息需通过 `/ai/message/{messageId}/references` 接口获取。
### 7.3 Image 图片字段映射context_image 表)
| 后端字段 | 前端字段AI返回 | 类型 | 说明 |
|----------|-------------------|------|------|
| `imageId` | `image_id` | string | 图片标识 |
| `path` | `path` / `url` | string | 图片访问路径,如 `/api/image/xxx/data` |
| `source` | `source` | string | 来源文档 |
| `page` | `page` | int | 页码 |
| `section` | `section` | string | 章节位置 |
| `chunkType` | `chunk_type` | string | 切片类型image |
| `description` | `description` | string | 图片描述 |
| `score` | `score` | decimal | 相关性评分(**保留** |
| `collection` | `collection` | string | 向量库名称 |
> **已移除字段**`filename`、`size_bytes`、`format`、`page_end`、`type`、`full_description`
---
## 八、常见问题 FAQ
### Q1: answer 中的 `[ref:chunk_id]` 标记如何解析?
**答**:前端需要在渲染 `answer` 时,使用正则匹配 `\[ref:([^\]]+)\]`,将其替换为可点击的引用链接:
```javascript
const renderAnswer = (answer) => {
const regex = /\[ref:([^\]]+)\]/g;
return answer.replace(regex, (match, chunkId) => {
return `<a class="ref-link" data-chunk-id="${chunkId}">[引用]</a>`;
});
};
```
### Q2: 流式问答如何处理?为什么不用 EventSource
**答**:由于 EventSource 只能 GET 请求,且无法自定义请求头(无法传 Token推荐使用 `fetch + ReadableStream` 方式:
```javascript
const response = await fetch('/api/ai/chat/stream', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + token
},
body: JSON.stringify({ message, session_id: sessionId })
});
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = '';
let fullAnswer = '';
while (true) {
const { value, done } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const lines = buffer.split('\n');
buffer = lines.pop() || '';
for (const line of lines) {
if (!line.startsWith('data:')) continue;
const dataStr = line.slice(5).trim();
if (!dataStr) continue;
try {
const data = JSON.parse(dataStr);
if (data.type === 'chunk') {
fullAnswer += data.content;
// renderAnswer(fullAnswer); // 更新 UI
} else if (data.type === 'finish') {
console.log('引用:', data.citations);
console.log('图片:', data.images);
}
} catch (e) {
console.warn('解析失败:', dataStr);
}
}
}
```
### Q3: 题目生成接口为什么是异步的?如何获取生成结果?
**答**:题目生成需要调用 AI LLM 进行内容理解和题目构造,耗时较长(通常 10-60 秒)。使用异步方式可避免前端超时等待。
获取结果方式:
1. 调用 `POST /api/exam/generate` 提交任务,得到 `fileId`
2. 轮询 `GET /api/file/{fileId}` 检查 `examStatus` 字段
3.`examStatus` 变为 `GENERATED` 时,通过题目列表接口查看题目
### Q4: citations 和 sources 有什么区别?
**答**
- **citations**finish 事件中AI 回答中真正引用到的切片。会写入 `context_reference` 表,可通过引用列表接口查询
- **sources**(流式事件中):检索阶段命中的候选来源文档。**仅用于前端展示****不写入数据库**`context_message` 表也没有 `sources` 字段
### Q5: context_reference.preview 字段是怎么来的?
**答**:当 citations 写入 `context_reference` 表时,后端会对每条引用自动调用 RAG 预览接口:
```
GET /documents/{collection}/{source}/preview?chunk_index=N&context=0
```
获取该切片的完整正文写入 `preview` 字段。若接口调用失败,`preview` 填入错误信息(如"预览获取失败: ...")。前端可直接展示 `preview` 内容,无需再调用 RAG 接口。
### Q6: 同一会话多次提问,如何区分不同消息的引用?
**答**`context_reference` 表通过 `messageId` 字段关联到具体消息。每个消息的引用列表独立。可通过:
- `/ai/message/{messageId}/references` — 查询某条消息的引用
- `/ai/session/{sessionId}/references` — 查询整个会话的所有引用
### Q7: context_image 表中的 score 字段有什么用?
**答**:保留 score 字段用于记录 AI 返回的图片相关性评分0-1。前端可根据 score 排序展示图片,或过滤低分图片。
---
## 九、附录cURL 完整示例
> 以下示例均需替换 `<token>` 为实际登录获取的 JWT Token
### 9.1 登录获取 Token
```bash
curl -X POST "http://localhost:8080/api/auth/login" \
-H "Content-Type: application/json" \
-d '{"username":"admin","password":"123456"}'
```
### 9.2 发起流式问答
```bash
curl -X POST "http://localhost:8080/api/ai/chat/stream" \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{"message":"市场状态评价指标体系中一级指标有哪些","session_id":""}' \
--no-buffer
```
### 9.3 中断流式问答
```bash
curl -X POST "http://localhost:8080/api/ai/chat/stop?sessionId=sess_xxx" \
-H "Authorization: Bearer <token>"
```
### 9.4 获取会话列表
```bash
curl -X GET "http://localhost:8080/api/ai/sessions?page=1&pageSize=10" \
-H "Authorization: Bearer <token>"
```
### 9.5 获取会话详情
```bash
curl -X GET "http://localhost:8080/api/ai/session/sess_1780410472658_9qg9a9hhp" \
-H "Authorization: Bearer <token>"
```
### 9.6 获取消息引用列表
```bash
curl -X GET "http://localhost:8080/api/ai/message/178/references" \
-H "Authorization: Bearer <token>"
```
### 9.7 获取消息文件来源列表(去重)
```bash
curl -X GET "http://localhost:8080/api/ai/message/178/sources" \
-H "Authorization: Bearer <token>"
```
### 9.8 删除单个会话
```bash
curl -X DELETE "http://localhost:8080/api/ai/session/sess_1780410472658_9qg9a9hhp" \
-H "Authorization: Bearer <token>"
```
### 9.9 批量删除会话
```bash
curl -X DELETE "http://localhost:8080/api/ai/sessions" \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '["sess_xxx1","sess_xxx2"]'
```
### 9.10 生成题目(指定题型)
```bash
curl -X POST "http://localhost:8080/api/exam/generate" \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"file_id": 1,
"question_types": {
"single_choice": 3,
"multiple_choice": 2,
"true_false": 2,
"fill_blank": 2,
"subjective": 1
},
"difficulty": 3,
"request_id": "gen_req_001"
}'
```
### 9.11 生成题目(智能模式)
```bash
curl -X POST "http://localhost:8080/api/exam/generate" \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{"file_id":1,"difficulty":3}'
```
### 9.12 轮询题目生成状态
```bash
curl -X GET "http://localhost:8080/api/file/1" \
-H "Authorization: Bearer <token>"
```
### 9.13 生成试卷
```bash
curl -X POST "http://localhost:8080/api/exam/paper/generate" \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"single_choice_count": 5,
"multiple_choice_count": 3,
"true_false_count": 2,
"fill_blank_count": 2,
"subjective_count": 1,
"difficulty": 3,
"include_personal": false
}'
```
### 9.14 批改答案(日常练习)
```bash
curl -X POST "http://localhost:8080/api/exam/grade" \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"request_id": "grade_req_001",
"answer_type": "practice",
"answers": [
{
"question_id": "q_001",
"question_type": "single_choice",
"question_content": {
"stem": "题目内容...",
"data": { "options": [...] },
"answer": "B"
},
"student_answer": "B",
"max_score": 5.0
}
]
}'
```
### 9.15 开始考试(获取题目)
```bash
curl -X POST "http://localhost:8080/api/exam/paper/paper_xxx/start" \
-H "Authorization: Bearer <token>"
```
### 9.16 获取考试结果
```bash
curl -X GET "http://localhost:8080/api/exam/record/record_xxx/result" \
-H "Authorization: Bearer <token>"
```
### 9.17 查询答题记录
```bash
curl -X POST "http://localhost:8080/api/exam/answers/query" \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{"paper_id":"paper_xxx"}'
```
### 9.18 获取考试记录列表
```bash
# 日常练习
curl -X GET "http://localhost:8080/api/exam/records?type=practice" \
-H "Authorization: Bearer <token>"
# 正式考试
curl -X GET "http://localhost:8080/api/exam/records?type=formal_exam" \
-H "Authorization: Bearer <token>"
```
---
**文档版本**v2.17.0
**最后更新**2026-06-05
**适用代码版本**:与当前 Spring Boot 后端(`AIChatController` / `ExamController`)同步