feat: 初始化考培练系统项目
- 从服务器拉取完整代码 - 按框架规范整理项目结构 - 配置 Drone CI 测试环境部署 - 包含后端(FastAPI)、前端(Vue3)、管理端 技术栈: Vue3 + TypeScript + FastAPI + MySQL
This commit is contained in:
256
docs/规划/全链路联调/Ai工作流/dify/Dify_API_Keys_配置管理经验.md
Normal file
256
docs/规划/全链路联调/Ai工作流/dify/Dify_API_Keys_配置管理经验.md
Normal file
@@ -0,0 +1,256 @@
|
||||
# Dify API Keys 配置管理经验
|
||||
|
||||
## 📅 更新时间
|
||||
2025-10-16
|
||||
|
||||
## 🎯 经验背景
|
||||
|
||||
在全链路联调阶段,发现代码中存在多处硬编码的 Dify API Keys,不利于维护和安全管理。本文档总结了统一配置管理的实施经验。
|
||||
|
||||
## ✅ 问题发现
|
||||
|
||||
### 硬编码问题
|
||||
|
||||
**发现位置**:`app/api/v1/exam.py`
|
||||
|
||||
```python
|
||||
# ❌ 硬编码的 API Key
|
||||
headers = {
|
||||
"Authorization": "Bearer app-tDlrmXyS9NtWCShsOx5FH49L",
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
```
|
||||
|
||||
**问题影响**:
|
||||
1. 更换 API Key 需要搜索全部代码
|
||||
2. 敏感信息暴露在代码中
|
||||
3. 不同环境无法灵活配置
|
||||
4. 版本控制中包含敏感信息
|
||||
|
||||
## 🔧 解决方案
|
||||
|
||||
### 1. 配置文件集中管理
|
||||
|
||||
**文件**:`app/core/config.py`
|
||||
|
||||
```python
|
||||
class Settings(BaseSettings):
|
||||
# Dify API 基础配置
|
||||
DIFY_API_BASE: Optional[str] = Field(default="http://dify.ireborn.com.cn/v1")
|
||||
|
||||
# 各工作流 API Keys
|
||||
DIFY_API_KEY: Optional[str] = Field(default="app-LZhZcMO6CiriLMOLB2PwUGHx") # 上传知识库
|
||||
DIFY_EXAM_GENERATOR_API_KEY: str = Field(default="app-tDlrmXyS9NtWCShsOx5FH49L") # 试题生成器
|
||||
DIFY_ANSWER_JUDGE_API_KEY: str = Field(default="app-FvMdrvbRBz547DVZEorgO1WT") # 答案判断器
|
||||
DIFY_PRACTICE_API_KEY: Optional[str] = Field(default="app-rYP6LNM4iPmNjIHns12zFeJp") # 陪练场景提取
|
||||
DIFY_PRACTICE_ANALYSIS_API_KEY: str = Field(default="app-9MWaCEiRegpYGQLov4S9oQjh") # 陪练分析报告
|
||||
DIFY_COURSE_CHAT_API_KEY: str = Field(default="app-lJzD6COkL8z7Eez8t6ZrYoJS") # 与课程对话
|
||||
DIFY_YANJI_ANALYSIS_API_KEY: str = Field(default="app-g0I5UT8lBB0fvuxGDOqrG8Zj") # 智能工牌分析
|
||||
```
|
||||
|
||||
### 2. 代码重构
|
||||
|
||||
**重构前**:
|
||||
```python
|
||||
headers = {
|
||||
"Authorization": "Bearer app-tDlrmXyS9NtWCShsOx5FH49L",
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
```
|
||||
|
||||
**重构后**:
|
||||
```python
|
||||
from app.core.config import settings
|
||||
|
||||
headers = {
|
||||
"Authorization": f"Bearer {settings.DIFY_EXAM_GENERATOR_API_KEY}",
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 配置验证脚本
|
||||
|
||||
创建验证脚本 `verify_dify_config.py`:
|
||||
|
||||
```python
|
||||
from app.core.config import settings
|
||||
|
||||
def verify_dify_config():
|
||||
"""验证所有 Dify 配置"""
|
||||
configs = [
|
||||
("API Base URL", "DIFY_API_BASE", settings.DIFY_API_BASE),
|
||||
("上传知识库", "DIFY_API_KEY", settings.DIFY_API_KEY),
|
||||
("试题生成器", "DIFY_EXAM_GENERATOR_API_KEY", settings.DIFY_EXAM_GENERATOR_API_KEY),
|
||||
# ... 其他配置项
|
||||
]
|
||||
|
||||
all_valid = True
|
||||
for name, var_name, value in configs:
|
||||
if not value:
|
||||
print(f"❌ {name}: 配置缺失")
|
||||
all_valid = False
|
||||
else:
|
||||
print(f"✅ {name}: 已配置")
|
||||
|
||||
return all_valid
|
||||
```
|
||||
|
||||
## 📋 完整工作流清单
|
||||
|
||||
| 工作流名称 | 配置变量 | API Key |
|
||||
|-----------|---------|---------|
|
||||
| 上传知识库 | `DIFY_API_KEY` | `app-LZhZcMO6CiriLMOLB2PwUGHx` |
|
||||
| 试题生成器 | `DIFY_EXAM_GENERATOR_API_KEY` | `app-tDlrmXyS9NtWCShsOx5FH49L` |
|
||||
| 答案判断器 | `DIFY_ANSWER_JUDGE_API_KEY` | `app-FvMdrvbRBz547DVZEorgO1WT` |
|
||||
| 陪练场景提取 | `DIFY_PRACTICE_API_KEY` | `app-rYP6LNM4iPmNjIHns12zFeJp` |
|
||||
| 陪练分析报告 | `DIFY_PRACTICE_ANALYSIS_API_KEY` | `app-9MWaCEiRegpYGQLov4S9oQjh` |
|
||||
| 与课程对话 | `DIFY_COURSE_CHAT_API_KEY` | `app-lJzD6COkL8z7Eez8t6ZrYoJS` |
|
||||
| 智能工牌分析 | `DIFY_YANJI_ANALYSIS_API_KEY` | `app-g0I5UT8lBB0fvuxGDOqrG8Zj` |
|
||||
|
||||
## 💡 最佳实践
|
||||
|
||||
### 1. 命名规范
|
||||
- 统一前缀:`DIFY_`
|
||||
- 功能描述:`EXAM_GENERATOR`、`ANSWER_JUDGE`
|
||||
- 后缀:`_API_KEY`
|
||||
- 示例:`DIFY_EXAM_GENERATOR_API_KEY`
|
||||
|
||||
### 2. 类型注解
|
||||
```python
|
||||
# ✅ 推荐:使用明确的类型注解
|
||||
DIFY_EXAM_GENERATOR_API_KEY: str = Field(default="app-xxx")
|
||||
|
||||
# ⚠️ 可选配置使用 Optional
|
||||
DIFY_API_KEY: Optional[str] = Field(default="app-xxx")
|
||||
```
|
||||
|
||||
### 3. 环境变量覆盖
|
||||
```bash
|
||||
# .env 文件
|
||||
DIFY_EXAM_GENERATOR_API_KEY=app-new-key-for-dev
|
||||
DIFY_ANSWER_JUDGE_API_KEY=app-new-key-for-dev
|
||||
```
|
||||
|
||||
### 4. 安全管理
|
||||
```gitignore
|
||||
# .gitignore 必须包含
|
||||
.env
|
||||
.env.local
|
||||
.env.*.local
|
||||
```
|
||||
|
||||
## 🔍 排查经验
|
||||
|
||||
### 1. 查找硬编码 API Keys
|
||||
|
||||
```bash
|
||||
# 搜索所有 app- 开头的字符串
|
||||
grep -r "app-[A-Za-z0-9]" --include="*.py" app/
|
||||
|
||||
# 搜索 Bearer 后跟 app-
|
||||
grep -r "Bearer app-" --include="*.py" app/
|
||||
```
|
||||
|
||||
### 2. 验证配置生效
|
||||
|
||||
```bash
|
||||
# 运行验证脚本
|
||||
python verify_dify_config.py
|
||||
|
||||
# 预期输出
|
||||
✅ 所有 Dify 配置验证通过!
|
||||
```
|
||||
|
||||
### 3. 调试配置加载
|
||||
|
||||
```python
|
||||
# 临时添加调试日志
|
||||
import logging
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
logger.debug(f"DIFY_EXAM_GENERATOR_API_KEY: {settings.DIFY_EXAM_GENERATOR_API_KEY[:20]}...")
|
||||
```
|
||||
|
||||
## ⚠️ 注意事项
|
||||
|
||||
### 1. 不要提交敏感信息
|
||||
```bash
|
||||
# 检查暂存区
|
||||
git diff --cached | grep -i "app-"
|
||||
|
||||
# 如果误提交,使用 git filter-branch 清理历史
|
||||
```
|
||||
|
||||
### 2. 环境隔离
|
||||
- 开发环境:使用测试 API Keys
|
||||
- 生产环境:使用正式 API Keys
|
||||
- 通过环境变量区分
|
||||
|
||||
### 3. API Key 轮换
|
||||
定期轮换 API Keys,步骤:
|
||||
1. 在 Dify 平台生成新 Key
|
||||
2. 更新配置文件
|
||||
3. 重启服务验证
|
||||
4. 废弃旧 Key
|
||||
|
||||
### 4. 权限最小化
|
||||
- 每个工作流使用独立 API Key
|
||||
- 便于权限管理和问题追踪
|
||||
- 避免一个 Key 的泄露影响所有功能
|
||||
|
||||
## 📊 实施效果
|
||||
|
||||
### 改进前
|
||||
- ❌ 3 处硬编码 API Keys
|
||||
- ❌ 修改需要搜索全部代码
|
||||
- ❌ 无法按环境区分配置
|
||||
- ❌ 安全风险高
|
||||
|
||||
### 改进后
|
||||
- ✅ 统一在配置文件管理
|
||||
- ✅ 一处修改全局生效
|
||||
- ✅ 支持环境变量覆盖
|
||||
- ✅ 提供验证脚本
|
||||
- ✅ 完善的文档说明
|
||||
|
||||
## 🔗 相关文档
|
||||
|
||||
- [Dify API Keys 配置说明](/root/aiedu/kaopeilian-backend/docs/dify_api_keys.md)
|
||||
- [Dify 系统对接分析报告](/root/aiedu/Dify系统对接分析报告.md)
|
||||
- [配置更新总结](/root/aiedu/DIFY_API_KEYS_UPDATE_SUMMARY.md)
|
||||
|
||||
## 📝 更新日志
|
||||
|
||||
| 日期 | 变更内容 | 影响范围 |
|
||||
|------|---------|---------|
|
||||
| 2025-10-16 | 添加试题生成器和答案判断器 API Keys | `app/api/v1/exam.py` |
|
||||
| 2025-10-16 | 移除硬编码,统一使用配置变量 | `app/api/v1/exam.py` |
|
||||
| 2025-10-16 | 创建配置验证脚本 | 新增 `verify_dify_config.py` |
|
||||
| 2025-10-16 | 创建配置文档 | 新增 `docs/dify_api_keys.md` |
|
||||
|
||||
## 🎓 经验总结
|
||||
|
||||
1. **早期规划很重要**:在项目初期就应该统一配置管理规范
|
||||
2. **代码审查必不可少**:定期检查是否有新的硬编码出现
|
||||
3. **自动化验证**:使用脚本自动验证配置完整性
|
||||
4. **文档同步更新**:配置变更时必须同步更新文档
|
||||
5. **安全意识**:敏感信息绝不提交到版本控制
|
||||
|
||||
## 🚀 后续优化建议
|
||||
|
||||
1. **CI/CD 集成**:
|
||||
- 在部署流程中添加配置验证步骤
|
||||
- 配置缺失时阻止部署
|
||||
|
||||
2. **密钥管理服务**:
|
||||
- 考虑使用 AWS Secrets Manager 或 HashiCorp Vault
|
||||
- 实现动态密钥轮换
|
||||
|
||||
3. **监控告警**:
|
||||
- API Key 即将过期时发送告警
|
||||
- API 调用失败时检查 Key 是否有效
|
||||
|
||||
4. **权限审计**:
|
||||
- 定期审计 API Key 使用情况
|
||||
- 发现异常调用及时处理
|
||||
|
||||
623
docs/规划/全链路联调/Ai工作流/dify/Dify系统对接分析报告.md
Normal file
623
docs/规划/全链路联调/Ai工作流/dify/Dify系统对接分析报告.md
Normal file
@@ -0,0 +1,623 @@
|
||||
# 考培练系统与Dify平台对接深度分析报告
|
||||
|
||||
## 目录
|
||||
1. [系统概述](#系统概述)
|
||||
2. [Dify API接口分析](#dify-api接口分析)
|
||||
3. [前端页面对接实现](#前端页面对接实现)
|
||||
4. [业务流程分析](#业务流程分析)
|
||||
5. [技术架构图](#技术架构图)
|
||||
6. [配置参数详解](#配置参数详解)
|
||||
7. [数据流向分析](#数据流向分析)
|
||||
8. [错误处理机制](#错误处理机制)
|
||||
9. [性能优化建议](#性能优化建议)
|
||||
|
||||
## 系统概述
|
||||
|
||||
本考培练系统是一个基于 **Python + Vue3 + MySQL + FastAPI** 架构的智能教育平台,与两个主要的AI平台进行深度对接:
|
||||
|
||||
- **Dify平台**:用于动态题目生成和知识提取
|
||||
- **Coze平台**:用于AI陪练和智能对话
|
||||
|
||||
### 核心功能模块
|
||||
- 动态考试题目生成(基于Dify工作流)
|
||||
- 知识点提取与分析(基于Dify工作流)
|
||||
- AI智能陪练(基于Coze智能体)
|
||||
- 三轮考试机制(错题重练)
|
||||
|
||||
## Dify API接口分析
|
||||
|
||||
### 1. 主要接口端点
|
||||
|
||||
系统中使用了 **1个核心Dify API端点**:
|
||||
|
||||
```
|
||||
POST https://aiedu.ireborn.com.cn/v1/workflows/run
|
||||
```
|
||||
|
||||
### 2. 使用的工作流Token
|
||||
|
||||
系统中发现了 **2个不同的工作流Token**:
|
||||
|
||||
#### 2.1 动态题目生成工作流
|
||||
- **Token**: `app-tDlrmXyS9NtWCShsOx5FH49L`
|
||||
- **用途**: 根据考试ID和错题信息生成动态题目
|
||||
- **文件位置**: `ExamsSystem/frontend/src/views/system/exams/start_exams.vue`
|
||||
|
||||
#### 2.2 知识提取工作流
|
||||
- **Token**: `app-LZhZcMO6CiriLMOLB2PwUGHx`
|
||||
- **用途**: 从考试附件中提取知识点
|
||||
- **文件位置**: `ExamsSystem/frontend/src/views/system/exams/index.vue`
|
||||
|
||||
### 3. API请求参数详解
|
||||
|
||||
#### 3.1 动态题目生成API参数
|
||||
|
||||
```javascript
|
||||
const payload = {
|
||||
inputs: {
|
||||
examsId: examId, // 考试ID(必需)
|
||||
error: errorNums // 错题编号(可选,用于第二轮、第三轮)
|
||||
},
|
||||
response_mode: "blocking", // 同步模式
|
||||
user: "abc-123" // 用户标识
|
||||
};
|
||||
```
|
||||
|
||||
**参数说明:**
|
||||
- `examsId`: 当前考试的唯一标识符,用于工作流识别要生成哪个考试的题目
|
||||
- `error`: 错题编号字符串,格式为逗号分隔的知识点编号,用于生成针对性的错题练习
|
||||
- `response_mode`: 固定为"blocking",表示同步等待工作流执行完成
|
||||
- `user`: 用户标识,固定为"abc-123"
|
||||
|
||||
#### 3.2 知识提取API参数
|
||||
|
||||
```javascript
|
||||
const payload = {
|
||||
inputs: {
|
||||
examsTitle: exams_title, // 考试标题
|
||||
file: [file], // 文件信息数组
|
||||
examsId: row.id // 考试ID
|
||||
},
|
||||
response_mode: "blocking",
|
||||
user: "abc-123"
|
||||
};
|
||||
|
||||
// 文件对象结构
|
||||
const file = {
|
||||
transfer_method: "remote_url",
|
||||
url: fileUrl, // 完整的文件URL
|
||||
type: "document" // 文件类型
|
||||
};
|
||||
```
|
||||
|
||||
**参数说明:**
|
||||
- `examsTitle`: 考试名称,帮助工作流理解文档内容的上下文
|
||||
- `file`: 文件信息数组,支持PDF等文档格式的知识提取
|
||||
- `transfer_method`: 固定为"remote_url",表示通过URL方式传递文件
|
||||
- `url`: 文件的完整访问URL,支持相对路径自动补全为绝对路径
|
||||
- `type`: 固定为"document",表示文档类型
|
||||
|
||||
### 4. API响应数据结构
|
||||
|
||||
#### 4.1 成功响应结构
|
||||
|
||||
```javascript
|
||||
{
|
||||
data: {
|
||||
status: "succeeded", // 执行状态
|
||||
outputs: {
|
||||
result: [...] // 工作流输出结果
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 4.2 题目数据结构
|
||||
|
||||
动态题目生成的响应数据中,`result`字段包含题目数组:
|
||||
|
||||
```javascript
|
||||
[
|
||||
{
|
||||
topic: {
|
||||
title: "题目内容", // 题目文本
|
||||
options: {
|
||||
opt1: "选项A",
|
||||
opt2: "选项B",
|
||||
opt3: "选项C",
|
||||
opt4: "选项D"
|
||||
}
|
||||
},
|
||||
correct: "A", // 正确答案
|
||||
analysis: "解析内容", // 题目解析
|
||||
know_title: "知识点编号" // 知识点标识
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
## 前端页面对接实现
|
||||
|
||||
### 1. 核心页面文件
|
||||
|
||||
#### 1.1 考试开始页面 (`start_exams.vue`)
|
||||
- **路径**: `ExamsSystem/frontend/src/views/system/exams/start_exams.vue`
|
||||
- **功能**: 动态题目生成、三轮考试机制、错题统计
|
||||
- **关键函数**: `callDifyWorkflow()`
|
||||
|
||||
#### 1.2 考试管理页面 (`index.vue`)
|
||||
- **路径**: `ExamsSystem/frontend/src/views/system/exams/index.vue`
|
||||
- **功能**: 知识提取、考试管理
|
||||
- **关键函数**: `update_know()`
|
||||
|
||||
#### 1.3 AI陪练页面 (`training.vue`)
|
||||
- **路径**: `ExamsSystem/frontend/src/views/system/exams/training.vue`
|
||||
- **功能**: 嵌入Coze聊天界面
|
||||
- **实现方式**: iframe嵌入
|
||||
|
||||
### 2. 前端调用实现
|
||||
|
||||
#### 2.1 动态题目生成调用
|
||||
|
||||
```javascript
|
||||
async function callDifyWorkflow(error = '') {
|
||||
loading.value = true;
|
||||
const url = "https://aiedu.ireborn.com.cn/v1/workflows/run";
|
||||
const token = 'app-tDlrmXyS9NtWCShsOx5FH49L';
|
||||
|
||||
const payload = {
|
||||
inputs: { examsId: examId },
|
||||
response_mode: "blocking",
|
||||
user: "abc-123"
|
||||
};
|
||||
|
||||
// 错题重练时添加错题参数
|
||||
if (error) {
|
||||
payload.inputs.error = error;
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await fetch(url, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Authorization": `Bearer ${token}`,
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
body: JSON.stringify(payload)
|
||||
});
|
||||
|
||||
const data = await res.json();
|
||||
if (data.data.status != 'succeeded') throw new Error("请求失败");
|
||||
|
||||
questions.value = data.data.outputs.result;
|
||||
loading.value = false;
|
||||
return data;
|
||||
} catch (err) {
|
||||
console.error("Dify 工作流调用异常:", err);
|
||||
loading.value = false;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.2 知识提取调用
|
||||
|
||||
```javascript
|
||||
async function update_know(row) {
|
||||
// 获取考试附件信息
|
||||
const exams_title = row.title || '';
|
||||
const fileList = Array.isArray(row.attachmentList) ? row.attachmentList : [];
|
||||
|
||||
if (!fileList.length) {
|
||||
proxy.$modal.msgWarning("该考试没有附件,无法提取知识!");
|
||||
return;
|
||||
}
|
||||
|
||||
// 构建文件对象
|
||||
const fileUrl = fileList[0].fileUrl || fileList[0].url || '';
|
||||
const file = {
|
||||
transfer_method: "remote_url",
|
||||
url: fileUrl.startsWith('http') ? fileUrl : `https://aiedu.ireborn.com.cn${fileUrl}`,
|
||||
type: "document"
|
||||
};
|
||||
|
||||
const payload = {
|
||||
inputs: {
|
||||
examsTitle: exams_title,
|
||||
file: [file],
|
||||
examsId: row.id
|
||||
},
|
||||
response_mode: "blocking",
|
||||
user: "abc-123"
|
||||
};
|
||||
|
||||
const token = "app-LZhZcMO6CiriLMOLB2PwUGHx";
|
||||
const url = "https://aiedu.ireborn.com.cn/v1/workflows/run";
|
||||
|
||||
proxy.$modal.loading("正在提取知识,请稍候...");
|
||||
try {
|
||||
const res = await fetch(url, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Authorization": `Bearer ${token}`,
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
body: JSON.stringify(payload)
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
proxy.$modal.msgError("知识提取失败!");
|
||||
return;
|
||||
}
|
||||
|
||||
const data = await res.json();
|
||||
proxy.$modal.msgSuccess("知识提取成功!");
|
||||
console.log("Dify知识提取结果:", data);
|
||||
} catch (err) {
|
||||
proxy.$modal.closeLoading();
|
||||
proxy.$modal.msgError("知识提取异常!");
|
||||
console.error("Dify知识提取异常:", err);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 前端状态管理
|
||||
|
||||
#### 3.1 考试状态管理
|
||||
|
||||
```javascript
|
||||
// 核心状态变量
|
||||
const questions = ref([]); // 题目数组
|
||||
const loading = ref(true); // 加载状态
|
||||
const currentIndex = ref(0); // 当前题目索引
|
||||
const selected = ref(''); // 选中答案
|
||||
const answered = ref(false); // 是否已答题
|
||||
const score = ref(0); // 当前分数
|
||||
const wrongQuestions = ref([]); // 错题记录
|
||||
const round = ref(1); // 当前轮次 (1,2,3)
|
||||
|
||||
// 三轮成绩记录
|
||||
const firstRoundScore = ref(0);
|
||||
const firstRoundTime = ref(null);
|
||||
const secondRoundScore = ref(0);
|
||||
const secondRoundTime = ref(null);
|
||||
const thirdRoundScore = ref(0);
|
||||
const thirdRoundTime = ref(null);
|
||||
```
|
||||
|
||||
#### 3.2 轮次流转逻辑
|
||||
|
||||
```javascript
|
||||
// 第二轮:基于第一轮错题
|
||||
function restartWithWrongQuestions() {
|
||||
const errorNums = wrongQuestions.value.map(item => item.title).join(',');
|
||||
// 重置状态
|
||||
currentIndex.value = 0;
|
||||
score.value = 0;
|
||||
wrongQuestions.value = [];
|
||||
round.value = 2;
|
||||
// 调用Dify生成针对性题目
|
||||
callDifyWorkflow(errorNums);
|
||||
}
|
||||
|
||||
// 第三轮:基于第二轮错题
|
||||
function restartWithThirdQuestions() {
|
||||
const errorNums = wrongQuestions.value.map(item => item.title).join(',');
|
||||
// 重置状态
|
||||
currentIndex.value = 0;
|
||||
score.value = 0;
|
||||
thirdWrongQuestions.value = [...wrongQuestions.value];
|
||||
wrongQuestions.value = [];
|
||||
round.value = 3;
|
||||
// 调用Dify生成针对性题目
|
||||
callDifyWorkflow(errorNums);
|
||||
}
|
||||
```
|
||||
|
||||
## 业务流程分析
|
||||
|
||||
### 1. 动态考试流程
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
A[用户选择考试] --> B[获取考试ID]
|
||||
B --> C[调用Dify工作流]
|
||||
C --> D[生成第一轮题目]
|
||||
D --> E[用户答题]
|
||||
E --> F[记录错题]
|
||||
F --> G{是否完成所有题目}
|
||||
G -->|否| E
|
||||
G -->|是| H[显示第一轮成绩]
|
||||
H --> I{用户选择第二轮}
|
||||
I -->|是| J[传递错题信息给Dify]
|
||||
J --> K[生成第二轮针对性题目]
|
||||
K --> L[用户答题]
|
||||
L --> M[记录错题]
|
||||
M --> N{是否完成所有题目}
|
||||
N -->|否| L
|
||||
N -->|是| O[显示第二轮成绩]
|
||||
O --> P{用户选择第三轮}
|
||||
P -->|是| Q[传递第二轮错题给Dify]
|
||||
Q --> R[生成第三轮题目]
|
||||
R --> S[完成三轮考试]
|
||||
S --> T[保存最终成绩]
|
||||
```
|
||||
|
||||
### 2. 知识提取流程
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
A[管理员上传考试附件] --> B[点击知识提取按钮]
|
||||
B --> C[获取附件URL]
|
||||
C --> D[构建文件对象]
|
||||
D --> E[调用Dify知识提取工作流]
|
||||
E --> F[Dify处理PDF文档]
|
||||
F --> G[提取知识点]
|
||||
G --> H[返回提取结果]
|
||||
H --> I[前端显示成功消息]
|
||||
```
|
||||
|
||||
### 3. AI陪练流程
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
A[用户进入陪练页面] --> B[iframe加载Coze聊天界面]
|
||||
B --> C[用户发送语音/文本]
|
||||
C --> D[Coze智能体处理]
|
||||
D --> E[返回AI回复]
|
||||
E --> F[支持语音合成]
|
||||
F --> G[用户继续对话]
|
||||
G --> C
|
||||
```
|
||||
|
||||
## 技术架构图
|
||||
|
||||
### 1. 整体架构
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
subgraph "前端层 (Vue3)"
|
||||
A1[考试管理页面]
|
||||
A2[动态考试页面]
|
||||
A3[AI陪练页面]
|
||||
end
|
||||
|
||||
subgraph "后端层 (FastAPI)"
|
||||
B1[考试管理API]
|
||||
B2[成绩管理API]
|
||||
B3[文件管理API]
|
||||
end
|
||||
|
||||
subgraph "AI平台层"
|
||||
C1[Dify工作流]
|
||||
C2[Coze智能体]
|
||||
end
|
||||
|
||||
subgraph "数据层"
|
||||
D1[MySQL数据库]
|
||||
D2[文件存储]
|
||||
end
|
||||
|
||||
A1 --> B1
|
||||
A2 --> C1
|
||||
A3 --> C2
|
||||
A2 --> B2
|
||||
B1 --> D1
|
||||
B2 --> D1
|
||||
B3 --> D2
|
||||
C1 --> D2
|
||||
```
|
||||
|
||||
### 2. Dify集成架构
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
subgraph "前端"
|
||||
A[Vue组件]
|
||||
end
|
||||
|
||||
subgraph "Dify平台"
|
||||
B[工作流引擎]
|
||||
C[题目生成工作流]
|
||||
D[知识提取工作流]
|
||||
end
|
||||
|
||||
A -->|HTTP POST| B
|
||||
B --> C
|
||||
B --> D
|
||||
C -->|题目数据| A
|
||||
D -->|知识点数据| A
|
||||
```
|
||||
|
||||
## 配置参数详解
|
||||
|
||||
### 1. 系统配置
|
||||
|
||||
#### 1.1 域名配置
|
||||
- **主域名**: `https://aiedu.ireborn.com.cn`
|
||||
- **API端点**: `/v1/workflows/run`
|
||||
- **文件服务**: `/dev-api/profile/upload/`
|
||||
|
||||
#### 1.2 工作流配置
|
||||
|
||||
| 功能 | Token | Bot ID | 用途 |
|
||||
|------|-------|--------|------|
|
||||
| 题目生成 | app-tDlrmXyS9NtWCShsOx5FH49L | - | 根据考试ID和错题生成动态题目 |
|
||||
| 知识提取 | app-LZhZcMO6CiriLMOLB2PwUGHx | - | 从PDF文档中提取知识点 |
|
||||
| 高情商回复 | - | 7509380917472280617 | AI智能回复 |
|
||||
| 咨询师陪练 | - | 7509379008556089379 | 语音陪练 |
|
||||
| 动态考题 | - | 7509379046204162074 | 动态题目生成 |
|
||||
|
||||
### 2. 环境配置
|
||||
|
||||
#### 2.1 前端配置 (`vite.config.js`)
|
||||
```javascript
|
||||
server: {
|
||||
host: '0.0.0.0',
|
||||
port: 80,
|
||||
proxy: {
|
||||
'/dev-api': {
|
||||
target: 'https://aiedu.ireborn.com.cn',
|
||||
changeOrigin: true,
|
||||
rewrite: (p) => p.replace(/^\/dev-api/, '')
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.2 后端配置 (`config/env.py`)
|
||||
```python
|
||||
app_host: str = 'https://aiedu.ireborn.com.cn/'
|
||||
```
|
||||
|
||||
## 数据流向分析
|
||||
|
||||
### 1. 题目生成数据流
|
||||
|
||||
```
|
||||
用户操作 → Vue组件状态 → Dify API调用 → 工作流处理 → 题目数据返回 → 前端渲染
|
||||
```
|
||||
|
||||
**详细流程:**
|
||||
1. 用户点击开始考试
|
||||
2. 获取URL参数中的`examId`
|
||||
3. 调用`callDifyWorkflow(examId)`
|
||||
4. 发送POST请求到Dify工作流
|
||||
5. 工作流根据`examsId`生成题目
|
||||
6. 返回JSON格式的题目数组
|
||||
7. 前端解析数据并渲染题目界面
|
||||
|
||||
### 2. 错题重练数据流
|
||||
|
||||
```
|
||||
错题收集 → 错题编号拼接 → Dify API调用(带error参数) → 针对性题目生成 → 前端渲染
|
||||
```
|
||||
|
||||
**详细流程:**
|
||||
1. 第一轮答题过程中收集错题
|
||||
2. 将错题的`know_title`字段拼接成字符串
|
||||
3. 调用`callDifyWorkflow(errorNums)`
|
||||
4. Dify工作流根据错题信息生成针对性题目
|
||||
5. 返回专门针对薄弱知识点的题目
|
||||
6. 前端进入第二轮/第三轮答题模式
|
||||
|
||||
### 3. 知识提取数据流
|
||||
|
||||
```
|
||||
文件上传 → 附件URL获取 → Dify API调用 → PDF解析 → 知识点提取 → 结果返回
|
||||
```
|
||||
|
||||
**详细流程:**
|
||||
1. 管理员在考试管理页面上传PDF附件
|
||||
2. 系统生成文件访问URL
|
||||
3. 点击"知识提取"按钮触发`update_know()`
|
||||
4. 构建包含文件URL的请求参数
|
||||
5. 调用Dify知识提取工作流
|
||||
6. 工作流下载并解析PDF文档
|
||||
7. 提取关键知识点并返回结果
|
||||
|
||||
## 错误处理机制
|
||||
|
||||
### 1. API调用错误处理
|
||||
|
||||
#### 1.1 网络错误处理
|
||||
```javascript
|
||||
try {
|
||||
const res = await fetch(url, options);
|
||||
const data = await res.json();
|
||||
// 处理成功响应
|
||||
} catch (err) {
|
||||
console.error("Dify 工作流调用异常:", err);
|
||||
loading.value = false;
|
||||
return null;
|
||||
}
|
||||
```
|
||||
|
||||
#### 1.2 业务错误处理
|
||||
```javascript
|
||||
if (data.data.status != 'succeeded') {
|
||||
throw new Error("请求失败");
|
||||
}
|
||||
```
|
||||
|
||||
#### 1.3 用户提示机制
|
||||
```javascript
|
||||
// 成功提示
|
||||
proxy.$modal.msgSuccess("知识提取成功!");
|
||||
|
||||
// 警告提示
|
||||
proxy.$modal.msgWarning("该考试没有附件,无法提取知识!");
|
||||
|
||||
// 错误提示
|
||||
proxy.$modal.msgError("知识提取失败!");
|
||||
|
||||
// 加载提示
|
||||
proxy.$modal.loading("正在提取知识,请稍候...");
|
||||
proxy.$modal.closeLoading();
|
||||
```
|
||||
|
||||
### 2. 数据验证机制
|
||||
|
||||
#### 2.1 前端验证
|
||||
- 检查考试ID是否存在
|
||||
- 验证附件列表是否为空
|
||||
- 确认URL格式正确性
|
||||
|
||||
#### 2.2 响应数据验证
|
||||
- 检查`data.data.status`是否为"succeeded"
|
||||
- 验证`data.data.outputs.result`是否存在
|
||||
- 确保题目数据结构完整
|
||||
|
||||
## 性能优化建议
|
||||
|
||||
### 1. 前端优化
|
||||
|
||||
#### 1.1 请求优化
|
||||
- **缓存机制**: 对相同考试ID的题目进行本地缓存
|
||||
- **请求去重**: 防止用户快速点击导致的重复请求
|
||||
- **超时处理**: 设置合理的请求超时时间
|
||||
|
||||
#### 1.2 用户体验优化
|
||||
- **加载状态**: 显示详细的加载进度和状态
|
||||
- **错误重试**: 提供手动重试机制
|
||||
- **离线支持**: 缓存已生成的题目支持离线答题
|
||||
|
||||
### 2. 后端优化
|
||||
|
||||
#### 2.1 API性能
|
||||
- **连接池**: 使用HTTP连接池减少连接开销
|
||||
- **异步处理**: 对于知识提取等耗时操作使用异步处理
|
||||
- **结果缓存**: 缓存Dify工作流的执行结果
|
||||
|
||||
#### 2.2 监控告警
|
||||
- **API监控**: 监控Dify API的响应时间和成功率
|
||||
- **错误日志**: 记录详细的错误日志便于问题排查
|
||||
- **性能指标**: 统计题目生成时间和知识提取效率
|
||||
|
||||
### 3. Dify工作流优化
|
||||
|
||||
#### 3.1 工作流设计
|
||||
- **参数验证**: 在工作流中添加输入参数验证
|
||||
- **错误处理**: 完善工作流内部的错误处理逻辑
|
||||
- **性能调优**: 优化工作流的执行效率
|
||||
|
||||
#### 3.2 资源管理
|
||||
- **并发控制**: 控制同时执行的工作流数量
|
||||
- **资源限制**: 设置合理的内存和CPU使用限制
|
||||
- **成本优化**: 监控和优化AI模型的调用成本
|
||||
|
||||
## 总结
|
||||
|
||||
本考培练系统通过与Dify平台的深度对接,实现了智能化的题目生成和知识提取功能。系统采用了成熟的技术架构,具备良好的扩展性和可维护性。主要特点包括:
|
||||
|
||||
1. **智能题目生成**: 基于考试内容和学员错题情况动态生成个性化题目
|
||||
2. **三轮考试机制**: 通过多轮练习帮助学员巩固薄弱知识点
|
||||
3. **知识自动提取**: 从PDF文档中自动提取关键知识点
|
||||
4. **AI智能陪练**: 结合Coze平台提供语音陪练功能
|
||||
|
||||
系统在实现上注重用户体验和错误处理,具备较强的实用性和稳定性。建议在后续开发中进一步优化性能和扩展功能模块。
|
||||
|
||||
---
|
||||
|
||||
**文档版本**: v1.0
|
||||
**生成时间**: 2025年9月20日
|
||||
**分析范围**: ExamsSystem、coze-chat-backend、coze-chat-frontend模块
|
||||
69
docs/规划/全链路联调/Ai工作流/dify/README.md
Normal file
69
docs/规划/全链路联调/Ai工作流/dify/README.md
Normal file
@@ -0,0 +1,69 @@
|
||||
# Dify工作流集成文档
|
||||
|
||||
**版本:** v2.0
|
||||
**状态:** ✅ 已完成并验证
|
||||
**最后更新:** 2025-10-12
|
||||
|
||||
---
|
||||
|
||||
## 文件说明
|
||||
|
||||
### 核心文档
|
||||
|
||||
| 文件 | 说明 |
|
||||
|------|------|
|
||||
| `考试工作流-最终版.md` | 考试工作流完整实现(推荐阅读) |
|
||||
| `知识拆解工作流.md` | 知识点分析工作流配置 |
|
||||
| `试题生成器的核心提示词与输出示例.md` | Dify提示词和返回格式参考 |
|
||||
| `考试工作流联调文档.md` | 详细联调文档(备查) |
|
||||
| `考试工作流联调-原版.md` | 原始需求(禁止修改) |
|
||||
|
||||
### 数据库API服务
|
||||
|
||||
| 文件 | 说明 |
|
||||
|------|------|
|
||||
| `数据库api 服务/README.md` | 快速配置指南 |
|
||||
| `数据库api 服务/openapi_sql_executor.json` | OpenAPI Schema文件 |
|
||||
|
||||
---
|
||||
|
||||
## 快速开始
|
||||
|
||||
### 1. 考试工作流
|
||||
阅读:`考试工作流-最终版.md`
|
||||
|
||||
**关键信息:**
|
||||
- 试题生成器Token:`app-tDlrmXyS9NtWCShsOx5FH49L`
|
||||
- 答案判断器Token:`app-FvMdrvbRBz547DVZEorgO1WT`
|
||||
- 后端接口:`/api/v1/exams/*`
|
||||
- 测试页面:`http://localhost:3001/trainee/exam?courseId=1`
|
||||
|
||||
### 2. 知识拆解工作流
|
||||
阅读:`知识拆解工作流.md`
|
||||
|
||||
**关键信息:**
|
||||
- API Key:`app-LZhZcMO6CiriLMOLB2PwUGHx`
|
||||
- 响应模式:streaming(无需轮询)
|
||||
- 状态映射:succeeded→分析完成,failed→分析失败
|
||||
|
||||
### 3. 数据库API服务
|
||||
阅读:`数据库api 服务/README.md`
|
||||
|
||||
**关键信息:**
|
||||
- API Key:`dify-2025-kaopeilian`
|
||||
- 端点:`/sql/execute-simple`
|
||||
- 服务器:`http://120.79.247.16:8000/api/v1`
|
||||
|
||||
---
|
||||
|
||||
## 验证状态
|
||||
|
||||
- ✅ 考试工作流:三轮考试流程完整可用
|
||||
- ✅ 知识拆解工作流:单个/批量分析正常
|
||||
- ✅ 数据库API服务:SQL执行器正常工作
|
||||
|
||||
---
|
||||
|
||||
**文档维护:** 开发团队
|
||||
**技术支持:** 参考 `考培练系统规划/全链路联调/联调经验汇总.md`
|
||||
|
||||
500
docs/规划/全链路联调/Ai工作流/dify/对话流/Dify对话流API文档.md
Normal file
500
docs/规划/全链路联调/Ai工作流/dify/对话流/Dify对话流API文档.md
Normal file
@@ -0,0 +1,500 @@
|
||||
# Dify 对话流 API 官方文档
|
||||
|
||||
> 文档来源:https://dify.ireborn.com.cn/app/4bea851a-7f24-47bd-9d0b-1d74f69ba603/develop
|
||||
> 导出时间:2025-10-14
|
||||
|
||||
## 工作流编排对话型应用 API
|
||||
|
||||
对话应用支持会话持久化,可将之前的聊天记录作为上下文进行回答,可适用于聊天/客服 AI 等。
|
||||
|
||||
### 基础 URL
|
||||
|
||||
```
|
||||
http://dify.ireborn.com.cn/v1
|
||||
```
|
||||
|
||||
### 鉴权
|
||||
|
||||
Service API 使用 `API-Key` 进行鉴权。
|
||||
|
||||
**强烈建议开发者把 `API-Key` 放在后端存储,而非分享或者放在客户端存储,以免 `API-Key` 泄露,导致财产损失。**
|
||||
|
||||
所有 API 请求都应在 `Authorization` HTTP Header 中包含您的 `API-Key`,如下所示:
|
||||
|
||||
```
|
||||
Authorization: Bearer {API_KEY}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## POST /chat-messages - 发送对话消息
|
||||
|
||||
创建会话消息。
|
||||
|
||||
### Request Body
|
||||
|
||||
| Name | Type | Description |
|
||||
|------|------|-------------|
|
||||
| `query` | string | 用户输入/提问内容。 |
|
||||
| `inputs` | object | 允许传入 App 定义的各变量值。inputs 参数包含了多组键值对(Key/Value pairs),每组的键对应一个特定变量,每组的值则是该变量的具体值。如果变量是文件类型,请指定一个包含以下 `files` 中所述键的对象。默认 `{}` |
|
||||
| `response_mode` | string | `streaming` 流式模式(推荐)。基于 SSE([Server-Sent Events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events))实现类似打字机输出方式的流式返回。<br>`blocking` 阻塞模式,等待执行完毕后返回结果。(请求若流程较长可能会被中断)。由于 Cloudflare 限制,请求会在 100 秒超时无返回后中断。 |
|
||||
| `user` | string | 用户标识,用于定义终端用户的身份,方便检索、统计。由开发者定义规则,需保证用户标识在应用内唯一。服务 API 不会共享 WebApp 创建的对话。 |
|
||||
| `conversation_id` | string | (选填)会话 ID,需要基于之前的聊天记录继续对话,必须传之前消息的 conversation_id。 |
|
||||
| `files` | array[object] | 文件列表,适用于传入文件结合文本理解并回答问题,仅当模型支持 Vision/Video 能力时可用。<br><br>**文件类型:**<br>- `document`: TXT, MD, MARKDOWN, MDX, PDF, HTML, XLSX, XLS, VTT, PROPERTIES, DOC, DOCX, CSV, EML, MSG, PPTX, PPT, XML, EPUB<br>- `image`: JPG, JPEG, PNG, GIF, WEBP, SVG<br>- `audio`: MP3, M4A, WAV, WEBM, MPGA<br>- `video`: MP4, MOV, MPEG, WEBM<br>- `custom`: 其他文件类型<br><br>**transfer_method (string)** 传递方式:<br>- `remote_url`: 文件地址<br>- `local_file`: 上传文件<br><br>- `url`: 文件地址(仅当传递方式为 remote_url 时)<br>- `upload_file_id`: 上传文件 ID(仅当传递方式为 local_file 时) |
|
||||
| `auto_generate_name` | bool | (选填)自动生成标题,默认 true。若设置为 false,则可通过调用会话重命名接口并设置 auto_generate 为 true 实现异步生成标题。 |
|
||||
| `workflow_id` | string | (选填)工作流ID,用于指定特定版本,如果不提供则使用默认的已发布版本。 |
|
||||
| `trace_id` | string | (选填)链路追踪ID。适用于与业务系统已有的trace组件打通,实现端到端分布式追踪等场景。如果未指定,系统会自动生成trace_id。支持以下三种方式传递,具体优先级依次为:<br>1. Header:通过 HTTP Header X-Trace-Id 传递,优先级最高。<br>2. Query 参数:通过 URL 查询参数 trace_id 传递。<br>3. Request Body:通过请求体字段 trace_id 传递(即本字段)。 |
|
||||
|
||||
### Response
|
||||
|
||||
当 `response_mode` 为 `blocking` 时,返回 **ChatCompletionResponse** object。
|
||||
|
||||
当 `response_mode` 为 `streaming` 时,返回 **ChunkChatCompletionResponse** object 流式序列。
|
||||
|
||||
#### ChatCompletionResponse(阻塞模式)
|
||||
|
||||
返回完整的 App 结果,Content-Type 为 `application/json`。
|
||||
|
||||
| 字段 | 类型 | 描述 |
|
||||
|------|------|------|
|
||||
| `event` | string | 事件类型,固定为 `message` |
|
||||
| `task_id` | string | 任务 ID,用于请求跟踪和下方的停止响应接口 |
|
||||
| `id` | string | 唯一ID |
|
||||
| `message_id` | string | 消息唯一 ID |
|
||||
| `conversation_id` | string | 会话 ID |
|
||||
| `mode` | string | App 模式,固定为 `chat` |
|
||||
| `answer` | string | 完整回复内容 |
|
||||
| `metadata` | object | 元数据 |
|
||||
| `usage` | Usage | 模型用量信息 |
|
||||
| `retriever_resources` | array[RetrieverResource] | 引用和归属分段列表 |
|
||||
| `created_at` | int | 消息创建时间戳,如:1705395332 |
|
||||
|
||||
#### ChunkChatCompletionResponse(流式模式)
|
||||
|
||||
返回 App 输出的流式块,Content-Type 为 `text/event-stream`。
|
||||
|
||||
每个流式块均为 `data:` 开头,块之间以 `\n\n` 即两个换行符分隔,如下所示:
|
||||
|
||||
```
|
||||
data: {"event": "message", "task_id": "900bbd43-dc0b-4383-a372-aa6e6c414227", "id": "663c5084-a254-4040-8ad3-51f2a3c1a77c", "answer": "Hi", "created_at": 1705398420}\n\n
|
||||
```
|
||||
|
||||
**流式块中根据 `event` 不同,结构也不同:**
|
||||
|
||||
##### event: workflow_started
|
||||
workflow 开始执行
|
||||
|
||||
| 字段 | 类型 | 描述 |
|
||||
|------|------|------|
|
||||
| `task_id` | string | 任务 ID,用于请求跟踪和下方的停止响应接口 |
|
||||
| `workflow_run_id` | string | workflow 执行 ID |
|
||||
| `event` | string | 固定为 `workflow_started` |
|
||||
| `data` | object | 详细内容 |
|
||||
| `data.id` | string | workflow 执行 ID |
|
||||
| `data.workflow_id` | string | 关联 Workflow ID |
|
||||
| `data.created_at` | timestamp | 开始时间 |
|
||||
|
||||
##### event: node_started
|
||||
node 开始执行
|
||||
|
||||
| 字段 | 类型 | 描述 |
|
||||
|------|------|------|
|
||||
| `task_id` | string | 任务 ID,用于请求跟踪和下方的停止响应接口 |
|
||||
| `workflow_run_id` | string | workflow 执行 ID |
|
||||
| `event` | string | 固定为 `node_started` |
|
||||
| `data` | object | 详细内容 |
|
||||
| `data.id` | string | workflow 执行 ID |
|
||||
| `data.node_id` | string | 节点 ID |
|
||||
| `data.node_type` | string | 节点类型 |
|
||||
| `data.title` | string | 节点名称 |
|
||||
| `data.index` | int | 执行序号,用于展示 Tracing Node 顺序 |
|
||||
| `data.predecessor_node_id` | string | 前置节点 ID,用于画布展示执行路径 |
|
||||
| `data.inputs` | object | 节点中所有使用到的前置节点变量内容 |
|
||||
| `data.created_at` | timestamp | 开始时间 |
|
||||
|
||||
##### event: node_finished
|
||||
node 执行结束,成功失败同一事件中不同状态
|
||||
|
||||
| 字段 | 类型 | 描述 |
|
||||
|------|------|------|
|
||||
| `task_id` | string | 任务 ID,用于请求跟踪和下方的停止响应接口 |
|
||||
| `workflow_run_id` | string | workflow 执行 ID |
|
||||
| `event` | string | 固定为 `node_finished` |
|
||||
| `data` | object | 详细内容 |
|
||||
| `data.id` | string | node 执行 ID |
|
||||
| `data.node_id` | string | 节点 ID |
|
||||
| `data.index` | int | 执行序号,用于展示 Tracing Node 顺序 |
|
||||
| `data.predecessor_node_id` | string | optional 前置节点 ID,用于画布展示执行路径 |
|
||||
| `data.inputs` | object | 节点中所有使用到的前置节点变量内容 |
|
||||
| `data.process_data` | json | Optional 节点过程数据 |
|
||||
| `data.outputs` | json | Optional 输出内容 |
|
||||
| `data.status` | string | 执行状态 running / succeeded / failed / stopped |
|
||||
| `data.error` | string | Optional 错误原因 |
|
||||
| `data.elapsed_time` | float | Optional 耗时(s) |
|
||||
| `data.execution_metadata` | json | 元数据 |
|
||||
| `data.execution_metadata.total_tokens` | int | optional 总使用 tokens |
|
||||
| `data.execution_metadata.total_price` | decimal | optional 总费用 |
|
||||
| `data.execution_metadata.currency` | string | optional 货币,如 USD / RMB |
|
||||
| `data.created_at` | timestamp | 开始时间 |
|
||||
|
||||
##### event: workflow_finished
|
||||
workflow 执行结束,成功失败同一事件中不同状态
|
||||
|
||||
| 字段 | 类型 | 描述 |
|
||||
|------|------|------|
|
||||
| `task_id` | string | 任务 ID,用于请求跟踪和下方的停止响应接口 |
|
||||
| `workflow_run_id` | string | workflow 执行 ID |
|
||||
| `event` | string | 固定为 `workflow_finished` |
|
||||
| `data` | object | 详细内容 |
|
||||
| `data.id` | string | workflow 执行 ID |
|
||||
| `data.workflow_id` | string | 关联 Workflow ID |
|
||||
| `data.status` | string | 执行状态 running / succeeded / failed / stopped |
|
||||
| `data.outputs` | json | Optional 输出内容 |
|
||||
| `data.error` | string | Optional 错误原因 |
|
||||
| `data.elapsed_time` | float | Optional 耗时(s) |
|
||||
| `data.total_tokens` | int | Optional 总使用 tokens |
|
||||
| `data.total_steps` | int | 总步数(冗余),默认 0 |
|
||||
| `data.created_at` | timestamp | 开始时间 |
|
||||
| `data.finished_at` | timestamp | 结束时间 |
|
||||
|
||||
##### event: message
|
||||
LLM 返回文本块事件,即:完整的文本以分块的方式输出。
|
||||
|
||||
| 字段 | 类型 | 描述 |
|
||||
|------|------|------|
|
||||
| `task_id` | string | 任务 ID,用于请求跟踪和下方的停止响应接口 |
|
||||
| `message_id` | string | 消息唯一 ID |
|
||||
| `conversation_id` | string | 会话 ID |
|
||||
| `answer` | string | LLM 返回文本块内容 |
|
||||
| `created_at` | int | 创建时间戳,如:1705395332 |
|
||||
|
||||
##### event: message_file
|
||||
文件事件,表示有新文件需要展示
|
||||
|
||||
| 字段 | 类型 | 描述 |
|
||||
|------|------|------|
|
||||
| `id` | string | 文件唯一ID |
|
||||
| `type` | string | 文件类型,目前仅为image |
|
||||
| `belongs_to` | string | 文件归属,user或assistant,该接口返回仅为 assistant |
|
||||
| `url` | string | 文件访问地址 |
|
||||
| `conversation_id` | string | 会话ID |
|
||||
|
||||
##### event: message_end
|
||||
消息结束事件,收到此事件则代表流式返回结束。
|
||||
|
||||
| 字段 | 类型 | 描述 |
|
||||
|------|------|------|
|
||||
| `task_id` | string | 任务 ID,用于请求跟踪和下方的停止响应接口 |
|
||||
| `message_id` | string | 消息唯一 ID |
|
||||
| `conversation_id` | string | 会话 ID |
|
||||
| `metadata` | object | 元数据 |
|
||||
| `usage` | Usage | 模型用量信息 |
|
||||
| `retriever_resources` | array[RetrieverResource] | 引用和归属分段列表 |
|
||||
|
||||
##### event: tts_message
|
||||
TTS 音频流事件,即:语音合成输出。内容是Mp3格式的音频块,使用 base64 编码后的字符串,播放的时候直接解码即可。(开启自动播放才有此消息)
|
||||
|
||||
| 字段 | 类型 | 描述 |
|
||||
|------|------|------|
|
||||
| `task_id` | string | 任务 ID,用于请求跟踪和下方的停止响应接口 |
|
||||
| `message_id` | string | 消息唯一 ID |
|
||||
| `audio` | string | 语音合成之后的音频块使用 Base64 编码之后的文本内容,播放的时候直接 base64 解码送入播放器即可 |
|
||||
| `created_at` | int | 创建时间戳,如:1705395332 |
|
||||
|
||||
##### event: tts_message_end
|
||||
TTS 音频流结束事件,收到这个事件表示音频流返回结束。
|
||||
|
||||
| 字段 | 类型 | 描述 |
|
||||
|------|------|------|
|
||||
| `task_id` | string | 任务 ID,用于请求跟踪和下方的停止响应接口 |
|
||||
| `message_id` | string | 消息唯一 ID |
|
||||
| `audio` | string | 结束事件是没有音频的,所以这里是空字符串 |
|
||||
| `created_at` | int | 创建时间戳,如:1705395332 |
|
||||
|
||||
##### event: message_replace
|
||||
消息内容替换事件。开启内容审查和审查输出内容时,若命中了审查条件,则会通过此事件替换消息内容为预设回复。
|
||||
|
||||
| 字段 | 类型 | 描述 |
|
||||
|------|------|------|
|
||||
| `task_id` | string | 任务 ID,用于请求跟踪和下方的停止响应接口 |
|
||||
| `message_id` | string | 消息唯一 ID |
|
||||
| `conversation_id` | string | 会话 ID |
|
||||
| `answer` | string | 替换内容(直接替换 LLM 所有回复文本) |
|
||||
| `created_at` | int | 创建时间戳,如:1705395332 |
|
||||
|
||||
##### event: error
|
||||
流式输出过程中出现的异常会以 stream event 形式输出,收到异常事件后即结束。
|
||||
|
||||
| 字段 | 类型 | 描述 |
|
||||
|------|------|------|
|
||||
| `task_id` | string | 任务 ID,用于请求跟踪和下方的停止响应接口 |
|
||||
| `message_id` | string | 消息唯一 ID |
|
||||
| `status` | int | HTTP 状态码 |
|
||||
| `code` | string | 错误码 |
|
||||
| `message` | string | 错误消息 |
|
||||
|
||||
##### event: ping
|
||||
每 10s 一次的 ping 事件,保持连接存活。
|
||||
|
||||
### Errors
|
||||
|
||||
- `404` - 对话不存在
|
||||
- `400, invalid_param` - 传入参数异常
|
||||
- `400, app_unavailable` - App 配置不可用
|
||||
- `400, provider_not_initialize` - 无可用模型凭据配置
|
||||
- `400, provider_quota_exceeded` - 模型调用额度不足
|
||||
- `400, model_currently_not_support` - 当前模型不可用
|
||||
- `400, workflow_not_found` - 指定的工作流版本未找到
|
||||
- `400, draft_workflow_error` - 无法使用草稿工作流版本
|
||||
- `400, workflow_id_format_error` - 工作流ID格式错误,需要UUID格式
|
||||
- `400, completion_request_error` - 文本生成失败
|
||||
- `500` - 服务内部异常
|
||||
|
||||
### Request 示例
|
||||
|
||||
```bash
|
||||
curl -X POST 'http://dify.ireborn.com.cn/v1/chat-messages' \
|
||||
--header 'Authorization: Bearer {api_key}' \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
"inputs": {},
|
||||
"query": "What are the specs of the iPhone 13 Pro Max?",
|
||||
"response_mode": "streaming",
|
||||
"conversation_id": "",
|
||||
"user": "abc-123",
|
||||
"files": [
|
||||
{
|
||||
"type": "image",
|
||||
"transfer_method": "remote_url",
|
||||
"url": "https://cloud.dify.ai/logo/logo-site.png"
|
||||
}
|
||||
]
|
||||
}'
|
||||
```
|
||||
|
||||
### Response 示例
|
||||
|
||||
#### 阻塞模式
|
||||
|
||||
```json
|
||||
{
|
||||
"event": "message",
|
||||
"task_id": "c3800678-a077-43df-a102-53f23ed20b88",
|
||||
"id": "9da23599-e713-473b-982c-4328d4f5c78a",
|
||||
"message_id": "9da23599-e713-473b-982c-4328d4f5c78a",
|
||||
"conversation_id": "45701982-8118-4bc5-8e9b-64562b4555f2",
|
||||
"mode": "chat",
|
||||
"answer": "iPhone 13 Pro Max specs are listed here:...",
|
||||
"metadata": {
|
||||
"usage": {
|
||||
"prompt_tokens": 1033,
|
||||
"prompt_unit_price": "0.001",
|
||||
"prompt_price_unit": "0.001",
|
||||
"prompt_price": "0.0010330",
|
||||
"completion_tokens": 128,
|
||||
"completion_unit_price": "0.002",
|
||||
"completion_price_unit": "0.001",
|
||||
"completion_price": "0.0002560",
|
||||
"total_tokens": 1161,
|
||||
"total_price": "0.0012890",
|
||||
"currency": "USD",
|
||||
"latency": 0.7682376249867957
|
||||
},
|
||||
"retriever_resources": [
|
||||
{
|
||||
"position": 1,
|
||||
"dataset_id": "101b4c97-fc2e-463c-90b1-5261a4cdcafb",
|
||||
"dataset_name": "iPhone",
|
||||
"document_id": "8dd1ad74-0b5f-4175-b735-7d98bbbb4e00",
|
||||
"document_name": "iPhone List",
|
||||
"segment_id": "ed599c7f-2766-4294-9d1d-e5235a61270a",
|
||||
"score": 0.98457545,
|
||||
"content": "\"Model\",\"Release Date\",\"Display Size\",\"Resolution\",\"Processor\",\"RAM\",\"Storage\",\"Camera\",\"Battery\",\"Operating System\"\n\"iPhone 13 Pro Max\",\"September 24, 2021\",\"6.7 inch\",\"1284 x 2778\",\"Hexa-core (2x3.23 GHz Avalanche + 4x1.82 GHz Blizzard)\",\"6 GB\",\"128, 256, 512 GB, 1TB\",\"12 MP\",\"4352 mAh\",\"iOS 15\""
|
||||
}
|
||||
]
|
||||
},
|
||||
"created_at": 1705407629
|
||||
}
|
||||
```
|
||||
|
||||
#### 流式模式
|
||||
|
||||
```
|
||||
data: {"event": "workflow_started", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "workflow_id": "dfjasklfjdslag", "created_at": 1679586595}}
|
||||
data: {"event": "node_started", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "node_id": "dfjasklfjdslag", "node_type": "start", "title": "Start", "index": 0, "predecessor_node_id": "fdljewklfklgejlglsd", "inputs": {}, "created_at": 1679586595}}
|
||||
data: {"event": "node_finished", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "node_id": "dfjasklfjdslag", "node_type": "start", "title": "Start", "index": 0, "predecessor_node_id": "fdljewklfklgejlglsd", "inputs": {}, "outputs": {}, "status": "succeeded", "elapsed_time": 0.324, "execution_metadata": {"total_tokens": 63127864, "total_price": 2.378, "currency": "USD"}, "created_at": 1679586595}}
|
||||
data: {"event": "workflow_finished", "task_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "workflow_run_id": "5ad498-f0c7-4085-b384-88cbe6290", "data": {"id": "5ad498-f0c7-4085-b384-88cbe6290", "workflow_id": "dfjasklfjdslag", "outputs": {}, "status": "succeeded", "elapsed_time": 0.324, "total_tokens": 63127864, "total_steps": "1", "created_at": 1679586595, "finished_at": 1679976595}}
|
||||
data: {"event": "message", "message_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "conversation_id": "45701982-8118-4bc5-8e9b-64562b4555f2", "answer": " I", "created_at": 1679586595}
|
||||
data: {"event": "message", "message_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "conversation_id": "45701982-8118-4bc5-8e9b-64562b4555f2", "answer": "'m", "created_at": 1679586595}
|
||||
data: {"event": "message", "message_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "conversation_id": "45701982-8118-4bc5-8e9b-64562b4555f2", "answer": " glad", "created_at": 1679586595}
|
||||
data: {"event": "message", "message_id": "5ad4cb98-f0c7-4085-b384-88c403be6290", "conversation_id": "45701982-8118-4bc5-8e9b-64562b4555f2", "answer": " to", "created_at": 1679586595}
|
||||
data: {"event": "message", "message_id" : "5ad4cb98-f0c7-4085-b384-88c403be6290", "conversation_id": "45701982-8118-4bc5-8e9b-64562b4555f2", "answer": " meet", "created_at": 1679586595}
|
||||
data: {"event": "message", "message_id" : "5ad4cb98-f0c7-4085-b384-88c403be6290", "conversation_id": "45701982-8118-4bc5-8e9b-64562b4555f2", "answer": " you", "created_at": 1679586595}
|
||||
data: {"event": "message_end", "id": "5e52ce04-874b-4d27-9045-b3bc80def685", "conversation_id": "45701982-8118-4bc5-8e9b-64562b4555f2", "metadata": {"usage": {"prompt_tokens": 1033, "prompt_unit_price": "0.001", "prompt_price_unit": "0.001", "prompt_price": "0.0010330", "completion_tokens": 135, "completion_unit_price": "0.002", "completion_price_unit": "0.001", "completion_price": "0.0002700", "total_tokens": 1168, "total_price": "0.0013030", "currency": "USD", "latency": 1.381760165997548}, "retriever_resources": [{"position": 1, "dataset_id": "101b4c97-fc2e-463c-90b1-5261a4cdcafb", "dataset_name": "iPhone", "document_id": "8dd1ad74-0b5f-4175-b735-7d98bbbb4e00", "document_name": "iPhone List", "segment_id": "ed599c7f-2766-4294-9d1d-e5235a61270a", "score": 0.98457545, "content": "\"Model\",\"Release Date\",\"Display Size\",\"Resolution\",\"Processor\",\"RAM\",\"Storage\",\"Camera\",\"Battery\",\"Operating System\"\n\"iPhone 13 Pro Max\",\"September 24, 2021\",\"6.7 inch\",\"1284 x 2778\",\"Hexa-core (2x3.23 GHz Avalanche + 4x1.82 GHz Blizzard)\",\"6 GB\",\"128, 256, 512 GB, 1TB\",\"12 MP\",\"4352 mAh\",\"iOS 15\""}]}}
|
||||
data: {"event": "tts_message", "conversation_id": "23dd85f3-1a41-4ea0-b7a9-062734ccfaf9", "message_id": "a8bdc41c-13b2-4c18-bfd9-054b9803038c", "created_at": 1721205487, "task_id": "3bf8a0bb-e73b-4690-9e66-4e429bad8ee7", "audio": "qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq"}
|
||||
data: {"event": "tts_message_end", "conversation_id": "23dd85f3-1a41-4ea0-b7a9-062734ccfaf9", "message_id": "a8bdc41c-13b2-4c18-bfd9-054b9803038c", "created_at": 1721205487, "task_id": "3bf8a0bb-e73b-4690-9e66-4e429bad8ee7", "audio": ""}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## POST /files/upload - 上传文件
|
||||
|
||||
上传文件并在发送消息时使用,可实现图文多模态理解。支持您的应用程序所支持的所有格式。上传的文件仅供当前终端用户使用。
|
||||
|
||||
该接口需使用 `multipart/form-data` 进行请求。
|
||||
|
||||
### Request Body
|
||||
|
||||
| Name | Type | Description |
|
||||
|------|------|-------------|
|
||||
| `file` | file | 要上传的文件。 |
|
||||
| `user` | string | 用户标识,用于定义终端用户的身份,必须和发送消息接口传入 user 保持一致。 |
|
||||
|
||||
### Response
|
||||
|
||||
成功上传后,服务器会返回文件的 ID 和相关信息。
|
||||
|
||||
| 字段 | 类型 | 描述 |
|
||||
|------|------|------|
|
||||
| `id` | uuid | ID |
|
||||
| `name` | string | 文件名 |
|
||||
| `size` | int | 文件大小(byte) |
|
||||
| `extension` | string | 文件后缀 |
|
||||
| `mime_type` | string | 文件 mime-type |
|
||||
| `created_by` | uuid | 上传人 ID |
|
||||
| `created_at` | timestamp | 上传时间 |
|
||||
|
||||
### Errors
|
||||
|
||||
- `400, no_file_uploaded` - 必须提供文件
|
||||
- `400, too_many_files` - 目前只接受一个文件
|
||||
- `400, unsupported_preview` - 该文件不支持预览
|
||||
- `400, unsupported_estimate` - 该文件不支持估算
|
||||
- `413, file_too_large` - 文件太大
|
||||
- `415, unsupported_file_type` - 不支持的扩展名,当前只接受文档类文件
|
||||
- `503, s3_connection_failed` - 无法连接到 S3 服务
|
||||
- `503, s3_permission_denied` - 无权限上传文件到 S3
|
||||
- `503, s3_file_too_large` - 文件超出 S3 大小限制
|
||||
|
||||
### Request 示例
|
||||
|
||||
```bash
|
||||
curl -X POST 'http://dify.ireborn.com.cn/v1/files/upload' \
|
||||
--header 'Authorization: Bearer {api_key}' \
|
||||
--form 'file=@localfile;type=image/[png|jpeg|jpg|webp|gif]' \
|
||||
--form 'user=abc-123'
|
||||
```
|
||||
|
||||
### Response 示例
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "72fa9618-8f89-4a37-9b33-7e1178a24a67",
|
||||
"name": "example.png",
|
||||
"size": 1024,
|
||||
"extension": "png",
|
||||
"mime_type": "image/png",
|
||||
"created_by": 123,
|
||||
"created_at": 1577836800
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## POST /chat-messages/:task_id/stop - 停止响应
|
||||
|
||||
仅支持流式模式。
|
||||
|
||||
### Path
|
||||
|
||||
| 参数 | 类型 | 描述 |
|
||||
|------|------|------|
|
||||
| `task_id` | string | 任务 ID,可在流式返回 Chunk 中获取 |
|
||||
|
||||
### Request Body
|
||||
|
||||
| Name | Type | Description |
|
||||
|------|------|-------------|
|
||||
| `user` | string | Required 用户标识,用于定义终端用户的身份,必须和发送消息接口传入 user 保持一致。API 无法访问 WebApp 创建的会话。 |
|
||||
|
||||
### Response
|
||||
|
||||
| 字段 | 类型 | 描述 |
|
||||
|------|------|------|
|
||||
| `result` | string | 固定返回 success |
|
||||
|
||||
### Request 示例
|
||||
|
||||
```bash
|
||||
curl -X POST 'http://dify.ireborn.com.cn/v1/chat-messages/:task_id/stop' \
|
||||
-H 'Authorization: Bearer {api_key}' \
|
||||
-H 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
"user": "abc-123"
|
||||
}'
|
||||
```
|
||||
|
||||
### Response 示例
|
||||
|
||||
```json
|
||||
{
|
||||
"result": "success"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 其他接口
|
||||
|
||||
文档还包含以下接口(此处仅列出标题,详细内容请查看原始文档):
|
||||
|
||||
- POST /messages/:message_id/feedbacks - 消息反馈(点赞)
|
||||
- GET /app/feedbacks - 获取APP的消息点赞和反馈
|
||||
- GET /messages/{message_id}/suggested - 获取下一轮建议问题列表
|
||||
- GET /messages - 获取会话历史消息
|
||||
- GET /conversations - 获取会话列表
|
||||
- DELETE /conversations/:conversation_id - 删除会话
|
||||
- POST /conversations/:conversation_id/name - 会话重命名
|
||||
- GET /conversations/:conversation_id/variables - 获取对话变量
|
||||
- PUT /conversations/:conversation_id/variables/:variable_id - 更新对话变量
|
||||
- POST /audio-to-text - 语音转文字
|
||||
- POST /text-to-audio - 文字转语音
|
||||
- GET /info - 获取应用基本信息
|
||||
- GET /parameters - 获取应用参数
|
||||
- GET /meta - 获取应用Meta信息
|
||||
- GET /site - 获取应用 WebApp 设置
|
||||
- GET /apps/annotations - 获取标注列表
|
||||
- POST /apps/annotations - 创建标注
|
||||
- PUT /apps/annotations/{annotation_id} - 更新标注
|
||||
- DELETE /apps/annotations/{annotation_id} - 删除标注
|
||||
- POST /apps/annotation-reply/{action} - 标注回复初始设置
|
||||
- GET /apps/annotation-reply/{action}/status/{job_id} - 查询标注回复初始设置任务状态
|
||||
|
||||
---
|
||||
|
||||
## 关键说明
|
||||
|
||||
### 流式模式事件流程
|
||||
|
||||
对于工作流编排的对话应用,典型的事件流程如下:
|
||||
|
||||
1. `workflow_started` - 工作流开始(包含 conversation_id)
|
||||
2. `node_started` - 节点开始执行
|
||||
3. `node_finished` - 节点执行完成(可能包含输出数据)
|
||||
4. `workflow_finished` - 工作流完成(包含最终输出)
|
||||
5. `message` - LLM 文本块(逐字返回,可能有多个)
|
||||
6. `message_end` - 消息结束
|
||||
|
||||
### conversation_id 管理
|
||||
|
||||
- **首次对话**:不传 `conversation_id`,系统会在 `workflow_started` 事件中返回新的 `conversation_id`
|
||||
- **续接对话**:传入之前获取的 `conversation_id`,保持上下文连续性
|
||||
|
||||
### 重要注意事项
|
||||
|
||||
1. API Key 必须放在后端,不要暴露在客户端
|
||||
2. 流式模式使用 SSE(Server-Sent Events)协议
|
||||
3. 每个事件块以 `data:` 开头,块之间用 `\n\n` 分隔
|
||||
4. Cloudflare 有 100 秒超时限制(阻塞模式)
|
||||
5. 流式模式每 10 秒发送一次 ping 事件保持连接
|
||||
|
||||
283
docs/规划/全链路联调/Ai工作流/dify/对话流/与课程对话功能实施总结.md
Normal file
283
docs/规划/全链路联调/Ai工作流/dify/对话流/与课程对话功能实施总结.md
Normal file
@@ -0,0 +1,283 @@
|
||||
# 与课程对话功能实施总结
|
||||
|
||||
> 完成时间:2025-10-14
|
||||
> 功能状态:✅ 已完成实施
|
||||
|
||||
## 📋 功能概述
|
||||
|
||||
基于 Dify 对话流实现了与课程的智能对话功能,用户可以在课程中心点击"对话"按钮,与课程内容进行智能问答互动。
|
||||
|
||||
## 🎯 技术方案
|
||||
|
||||
### 架构设计
|
||||
|
||||
```
|
||||
前端 chat-course.vue
|
||||
↓ (SSE)
|
||||
后端 /api/v1/course/chat
|
||||
↓ (HTTP Stream)
|
||||
Dify 对话流 API
|
||||
```
|
||||
|
||||
### 核心特性
|
||||
|
||||
1. **流式响应**:使用 SSE(Server-Sent Events)实现实时对话
|
||||
2. **会话管理**:conversation_id 由前端管理,支持多轮对话
|
||||
3. **无持久化**:对话历史由 Dify 托管,系统不存储
|
||||
4. **纯文本**:当前版本仅支持文本对话
|
||||
|
||||
## 📁 已修改文件
|
||||
|
||||
### 后端
|
||||
|
||||
1. **配置文件**:`kaopeilian-backend/app/core/config.py`
|
||||
- 添加 `DIFY_COURSE_CHAT_API_KEY = "app-lJzD6COkL8z7Eez8t6ZrYoJS"`
|
||||
|
||||
2. **API 接口**:`kaopeilian-backend/app/api/v1/course_chat.py`(新建)
|
||||
- `POST /api/v1/course/chat` - 与课程对话接口
|
||||
- 实现 SSE 流式代理
|
||||
- 事件转换:Dify → 前端友好格式
|
||||
|
||||
3. **路由注册**:`kaopeilian-backend/app/api/v1/__init__.py`
|
||||
- 注册 `course_chat_router` 到 `/course` 前缀
|
||||
|
||||
### 前端
|
||||
|
||||
1. **API 封装**:`kaopeilian-frontend/src/api/courseChat.ts`(新建)
|
||||
- `courseChatApi.sendMessage()` - 发送消息并返回 ReadableStream
|
||||
- TypeScript 类型定义:`CourseChatEvent`
|
||||
|
||||
2. **对话页面**:`kaopeilian-frontend/src/views/trainee/chat-course.vue`
|
||||
- 删除 Coze 集成代码
|
||||
- 改用 Dify 对话流
|
||||
- 前端管理 `conversationId`
|
||||
- SSE 事件处理(conversation_started / message_content / message_end)
|
||||
|
||||
### 测试
|
||||
|
||||
1. **测试脚本**:`test_course_chat.py`(新建)
|
||||
- 测试登录 → 首次对话 → 续接对话
|
||||
- 验证 SSE 事件流
|
||||
- 验证会话管理
|
||||
|
||||
## 🔄 SSE 事件流程
|
||||
|
||||
### Dify 原始事件
|
||||
|
||||
```
|
||||
workflow_started → node_finished → workflow_finished → message_end
|
||||
```
|
||||
|
||||
### 后端转换后的事件
|
||||
|
||||
```json
|
||||
// 1. 会话开始(首次对话)
|
||||
{"event": "conversation_started", "conversation_id": "xxx"}
|
||||
|
||||
// 2. 消息块(逐字返回,实现打字机效果)
|
||||
{"event": "message_chunk", "chunk": "这"}
|
||||
{"event": "message_chunk", "chunk": "门"}
|
||||
{"event": "message_chunk", "chunk": "课"}
|
||||
...
|
||||
|
||||
// 3. 消息结束
|
||||
{"event": "message_end"}
|
||||
|
||||
// 4. 错误(如有)
|
||||
{"event": "error", "message": "错误信息"}
|
||||
```
|
||||
|
||||
## 📊 数据流
|
||||
|
||||
### 首次对话
|
||||
|
||||
```
|
||||
用户输入问题
|
||||
↓
|
||||
前端调用 courseChatApi.sendMessage({course_id, query})
|
||||
↓
|
||||
后端转发到 Dify (无 conversation_id)
|
||||
↓
|
||||
Dify 创建新会话
|
||||
↓
|
||||
SSE: conversation_started → 前端保存 conversation_id
|
||||
SSE: message_content → 前端显示答案
|
||||
SSE: message_end → 对话完成
|
||||
```
|
||||
|
||||
### 续接对话
|
||||
|
||||
```
|
||||
用户输入问题
|
||||
↓
|
||||
前端调用 courseChatApi.sendMessage({course_id, query, conversation_id})
|
||||
↓
|
||||
后端转发到 Dify (带 conversation_id)
|
||||
↓
|
||||
Dify 基于上下文回答
|
||||
↓
|
||||
SSE: message_content → 前端显示答案
|
||||
SSE: message_end → 对话完成
|
||||
```
|
||||
|
||||
## 🧪 测试步骤
|
||||
|
||||
### 1. 启动服务
|
||||
|
||||
```bash
|
||||
# 后端
|
||||
cd kaopeilian-backend
|
||||
docker-compose up -d
|
||||
|
||||
# 前端
|
||||
cd kaopeilian-frontend
|
||||
npm run dev
|
||||
```
|
||||
|
||||
### 2. 运行测试脚本
|
||||
|
||||
```bash
|
||||
python test_course_chat.py
|
||||
```
|
||||
|
||||
### 3. 手动测试
|
||||
|
||||
1. 登录系统
|
||||
2. 进入课程中心:http://localhost:3001/trainee/course-center
|
||||
3. 点击课程卡片的"对话"按钮
|
||||
4. 输入问题并发送
|
||||
5. 验证:
|
||||
- AI 回复显示正常
|
||||
- 可以进行多轮对话
|
||||
- 点击"清空对话"后会话重置
|
||||
|
||||
## 🎨 UI 特性
|
||||
|
||||
- ✅ 欢迎界面(首次进入时显示)
|
||||
- ✅ 快速提问(预设问题点击发送)
|
||||
- ✅ 消息加载动画(三个点跳动)
|
||||
- ✅ 消息复制功能
|
||||
- ✅ 消息收藏功能
|
||||
- ✅ 侧边栏(知识要点、对话历史)
|
||||
- ✅ 响应式设计(移动端适配)
|
||||
|
||||
## ⚠️ 注意事项
|
||||
|
||||
### 1. conversation_id 管理
|
||||
|
||||
- 前端使用 `ref<string>` 保存
|
||||
- 页面刷新后丢失(符合"每次进入对话页面都创建新会话"的需求)
|
||||
- 点击"清空对话"时重置
|
||||
|
||||
### 2. 流式打字机效果
|
||||
|
||||
- ✅ 已实现!Dify streaming 模式支持 `event: message` 逐字返回
|
||||
- 前端通过 `message_chunk` 事件逐字追加文本
|
||||
- 实现类似 ChatGPT 的实时打字效果
|
||||
|
||||
### 3. 超时设置
|
||||
|
||||
- 后端:180 秒(httpx.Timeout)
|
||||
- 前端:依赖浏览器默认(通常无限制)
|
||||
|
||||
### 4. 错误处理
|
||||
|
||||
- 网络错误:显示友好提示
|
||||
- Dify API 错误:记录日志并返回错误事件
|
||||
- 解析错误:跳过当前行,继续处理
|
||||
|
||||
## 📝 API 接口文档
|
||||
|
||||
### POST /api/v1/course/chat
|
||||
|
||||
**请求体:**
|
||||
|
||||
```json
|
||||
{
|
||||
"course_id": 1,
|
||||
"query": "这门课程讲什么?",
|
||||
"conversation_id": "可选,续接对话时传入"
|
||||
}
|
||||
```
|
||||
|
||||
**响应(SSE):**
|
||||
|
||||
```
|
||||
data: {"event":"conversation_started","conversation_id":"xxx"}
|
||||
|
||||
data: {"event":"message_content","answer":"这门课程..."}
|
||||
|
||||
data: {"event":"message_end"}
|
||||
```
|
||||
|
||||
## 🔍 关键代码片段
|
||||
|
||||
### 后端 SSE 生成
|
||||
|
||||
```python
|
||||
async def generate_stream():
|
||||
async with client.stream("POST", url, headers=headers, json=payload) as response:
|
||||
async for line in response.aiter_lines():
|
||||
if line.startswith("data: "):
|
||||
event_data = json.loads(line[6:])
|
||||
# 处理事件...
|
||||
yield f"data: {json.dumps(frontend_event)}\n\n"
|
||||
```
|
||||
|
||||
### 前端 SSE 消费
|
||||
|
||||
```typescript
|
||||
const stream = await courseChatApi.sendMessage({...})
|
||||
const reader = stream.getReader()
|
||||
const decoder = new TextDecoder()
|
||||
|
||||
while (true) {
|
||||
const { done, value } = await reader.read()
|
||||
if (done) break
|
||||
|
||||
const text = decoder.decode(value)
|
||||
// 解析 SSE 事件...
|
||||
}
|
||||
```
|
||||
|
||||
## ✅ 验收标准
|
||||
|
||||
- [x] 后端配置添加完成
|
||||
- [x] 后端 API 接口实现
|
||||
- [x] 后端路由注册
|
||||
- [x] 前端 API 封装
|
||||
- [x] 前端页面改造
|
||||
- [x] SSE 流式响应正常
|
||||
- [x] 会话管理正常
|
||||
- [x] 错误处理完善
|
||||
- [x] 无 linter 错误
|
||||
- [x] 测试脚本创建
|
||||
|
||||
## 🚀 下一步
|
||||
|
||||
### 可选优化
|
||||
|
||||
1. **Markdown 渲染**:如果 Dify 返回 Markdown 格式,前端可添加 Markdown 渲染器
|
||||
2. **会话持久化**:如需要持久化历史,可在后端存储 conversation_id 与用户/课程的映射
|
||||
3. **实时打字效果**:如 Dify 支持逐字返回,可修改事件处理逻辑
|
||||
4. **语音对话**:未来可集成语音输入/输出
|
||||
|
||||
## 📚 相关文档
|
||||
|
||||
- [Dify 对话流 API 文档](./Dify对话流API文档.md)
|
||||
- [实施计划](../../../------dify---.plan.md)
|
||||
- [规范与约定-团队基线](../../规范与约定-团队基线.md)
|
||||
- [联调经验汇总](../../联调经验汇总.md)
|
||||
|
||||
## 🎉 总结
|
||||
|
||||
成功将与课程对话功能从 Coze 迁移到 Dify 对话流,实现了:
|
||||
|
||||
✅ 完整的 SSE 流式对话
|
||||
✅ 会话持续性(conversation_id 管理)
|
||||
✅ 前后端解耦(API 代理模式)
|
||||
✅ 良好的错误处理
|
||||
✅ 友好的用户界面
|
||||
|
||||
代码质量:无 linter 错误,代码结构清晰,注释完善。
|
||||
|
||||
76
docs/规划/全链路联调/Ai工作流/dify/数据库api 服务/README.md
Normal file
76
docs/规划/全链路联调/Ai工作流/dify/数据库api 服务/README.md
Normal file
@@ -0,0 +1,76 @@
|
||||
# 数据库API服务配置指南
|
||||
|
||||
**用途:** Dify工作流访问考培练系统数据库
|
||||
|
||||
---
|
||||
|
||||
## 快速配置
|
||||
|
||||
### 服务器信息
|
||||
- **地址:** http://120.79.247.16:8000/api/v1
|
||||
- **备用:** http://aiedu.ireborn.com.cn/api/v1
|
||||
|
||||
### Dify配置步骤
|
||||
|
||||
1. **导入OpenAPI Schema**
|
||||
- 文件:`openapi_sql_executor.json`
|
||||
- 位置:工具 → 导入OpenAPI
|
||||
|
||||
2. **配置认证**
|
||||
- 鉴权类型:请求头
|
||||
- 头部前缀:Custom
|
||||
- 键:`X-API-Key`
|
||||
- 值:`dify-2025-kaopeilian`
|
||||
|
||||
3. **选择端点**
|
||||
- `/sql/execute-simple`
|
||||
|
||||
---
|
||||
|
||||
## 常用SQL语句
|
||||
|
||||
### 查询知识点
|
||||
```sql
|
||||
SELECT kp.id, kp.name, kp.description, kp.topic_relation
|
||||
FROM knowledge_points kp
|
||||
INNER JOIN course_materials cm ON kp.material_id = cm.id
|
||||
WHERE kp.course_id = ? AND kp.is_deleted = FALSE AND cm.is_deleted = FALSE
|
||||
ORDER BY RAND() LIMIT 10
|
||||
```
|
||||
|
||||
### 查询岗位信息
|
||||
```sql
|
||||
SELECT id, name, description, skills, level
|
||||
FROM positions
|
||||
WHERE id = ? AND is_deleted = FALSE
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 测试验证
|
||||
|
||||
```bash
|
||||
curl -X POST http://120.79.247.16:8000/api/v1/sql/execute-simple \
|
||||
-H "X-API-Key: dify-2025-kaopeilian" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"sql": "SELECT COUNT(*) as total FROM users"}'
|
||||
```
|
||||
|
||||
**预期结果:**
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "SQL 执行成功",
|
||||
"data": {
|
||||
"type": "query",
|
||||
"columns": ["total"],
|
||||
"rows": [{"total": 8}],
|
||||
"row_count": 1
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**最后更新:** 2025-10-12
|
||||
|
||||
664
docs/规划/全链路联调/Ai工作流/dify/数据库api 服务/openapi_sql_executor.json
Normal file
664
docs/规划/全链路联调/Ai工作流/dify/数据库api 服务/openapi_sql_executor.json
Normal file
@@ -0,0 +1,664 @@
|
||||
{
|
||||
"openapi": "3.1.0",
|
||||
"info": {
|
||||
"title": "KaoPeiLian SQL Executor API",
|
||||
"description": "SQL 执行器 API,专门为 Dify 平台集成设计,支持对考陪练系统数据库执行查询和写入操作。\n\n## 主要功能\n- 执行 SQL 查询和写入操作\n- 支持参数化查询防止 SQL 注入\n- 获取数据库表列表和表结构\n- SQL 语句验证\n\n## 安全说明\n所有接口都需要 JWT Bearer Token 认证。请先通过登录接口获取访问令牌。",
|
||||
"version": "1.0.0",
|
||||
"contact": {
|
||||
"name": "KaoPeiLian Tech Support",
|
||||
"email": "support@kaopeilian.com"
|
||||
}
|
||||
},
|
||||
"servers": [
|
||||
{
|
||||
"url": "http://120.79.247.16:8000/api/v1",
|
||||
"description": "考陪练系统服务器"
|
||||
},
|
||||
{
|
||||
"url": "http://aiedu.ireborn.com.cn/api/v1",
|
||||
"description": "域名访问"
|
||||
}
|
||||
],
|
||||
"security": [
|
||||
{
|
||||
"bearerAuth": []
|
||||
}
|
||||
],
|
||||
"paths": {
|
||||
"/auth/login": {
|
||||
"post": {
|
||||
"tags": ["认证"],
|
||||
"summary": "用户登录",
|
||||
"description": "获取访问令牌,用于后续 API 调用",
|
||||
"security": [],
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/LoginRequest"
|
||||
},
|
||||
"examples": {
|
||||
"admin": {
|
||||
"summary": "管理员登录",
|
||||
"value": {
|
||||
"username": "admin",
|
||||
"password": "admin123"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "登录成功",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/LoginResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "用户名或密码错误",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ErrorResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/sql/execute": {
|
||||
"post": {
|
||||
"tags": ["SQL执行器"],
|
||||
"summary": "执行 SQL 语句",
|
||||
"description": "执行查询或写入 SQL 语句。\n\n**查询操作**: SELECT, SHOW, DESCRIBE\n**写入操作**: INSERT, UPDATE, DELETE, CREATE, ALTER, DROP\n\n支持参数化查询,使用 `:param_name` 格式定义参数。",
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/SqlExecuteRequest"
|
||||
},
|
||||
"examples": {
|
||||
"simpleQuery": {
|
||||
"summary": "简单查询",
|
||||
"value": {
|
||||
"sql": "SELECT id, username, role FROM users LIMIT 5"
|
||||
}
|
||||
},
|
||||
"parameterizedQuery": {
|
||||
"summary": "参数化查询",
|
||||
"value": {
|
||||
"sql": "SELECT * FROM courses WHERE category = :category AND status = :status",
|
||||
"params": {
|
||||
"category": "护肤",
|
||||
"status": "active"
|
||||
}
|
||||
}
|
||||
},
|
||||
"insertData": {
|
||||
"summary": "插入数据",
|
||||
"value": {
|
||||
"sql": "INSERT INTO knowledge_points (title, content, course_id) VALUES (:title, :content, :course_id)",
|
||||
"params": {
|
||||
"title": "面部护理基础",
|
||||
"content": "面部护理的基本步骤...",
|
||||
"course_id": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "SQL 执行成功",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"oneOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/QueryResponse"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/ExecuteResponse"
|
||||
}
|
||||
]
|
||||
},
|
||||
"examples": {
|
||||
"queryResult": {
|
||||
"summary": "查询结果",
|
||||
"value": {
|
||||
"code": 200,
|
||||
"message": "SQL 执行成功",
|
||||
"data": {
|
||||
"type": "query",
|
||||
"columns": ["id", "username", "role"],
|
||||
"rows": [
|
||||
{
|
||||
"id": 1,
|
||||
"username": "admin",
|
||||
"role": "admin"
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"username": "user1",
|
||||
"role": "trainee"
|
||||
}
|
||||
],
|
||||
"row_count": 2
|
||||
}
|
||||
}
|
||||
},
|
||||
"executeResult": {
|
||||
"summary": "写入结果",
|
||||
"value": {
|
||||
"code": 200,
|
||||
"message": "SQL 执行成功",
|
||||
"data": {
|
||||
"type": "execute",
|
||||
"affected_rows": 1,
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "请求参数错误",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ErrorResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "未认证或认证失败",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ErrorResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "SQL 执行错误",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ErrorResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/sql/validate": {
|
||||
"post": {
|
||||
"tags": ["SQL执行器"],
|
||||
"summary": "验证 SQL 语法",
|
||||
"description": "验证 SQL 语句的语法正确性,不执行实际操作",
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/SqlValidateRequest"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "验证完成",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ValidateResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/sql/tables": {
|
||||
"get": {
|
||||
"tags": ["SQL执行器"],
|
||||
"summary": "获取表列表",
|
||||
"description": "获取数据库中所有表的列表",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "成功获取表列表",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/TablesResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "未认证",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ErrorResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/sql/table/{table_name}/schema": {
|
||||
"get": {
|
||||
"tags": ["SQL执行器"],
|
||||
"summary": "获取表结构",
|
||||
"description": "获取指定表的结构信息,包括字段名、类型、约束等",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "table_name",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"description": "表名",
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"pattern": "^[a-zA-Z_][a-zA-Z0-9_]*$"
|
||||
},
|
||||
"example": "users"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "成功获取表结构",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/TableSchemaResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "无效的表名",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ErrorResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "未认证",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ErrorResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"securitySchemes": {
|
||||
"bearerAuth": {
|
||||
"type": "http",
|
||||
"scheme": "bearer",
|
||||
"bearerFormat": "JWT",
|
||||
"description": "使用登录接口返回的 access_token。\n格式: Bearer {access_token}"
|
||||
}
|
||||
},
|
||||
"schemas": {
|
||||
"LoginRequest": {
|
||||
"type": "object",
|
||||
"required": ["username", "password"],
|
||||
"properties": {
|
||||
"username": {
|
||||
"type": "string",
|
||||
"description": "用户名",
|
||||
"example": "admin"
|
||||
},
|
||||
"password": {
|
||||
"type": "string",
|
||||
"format": "password",
|
||||
"description": "密码",
|
||||
"example": "admin123"
|
||||
}
|
||||
}
|
||||
},
|
||||
"LoginResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"code": {
|
||||
"type": "integer",
|
||||
"example": 200
|
||||
},
|
||||
"message": {
|
||||
"type": "string",
|
||||
"example": "登录成功"
|
||||
},
|
||||
"data": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"user": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "integer",
|
||||
"example": 1
|
||||
},
|
||||
"username": {
|
||||
"type": "string",
|
||||
"example": "admin"
|
||||
},
|
||||
"role": {
|
||||
"type": "string",
|
||||
"example": "admin"
|
||||
}
|
||||
}
|
||||
},
|
||||
"token": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"access_token": {
|
||||
"type": "string",
|
||||
"description": "JWT 访问令牌",
|
||||
"example": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9..."
|
||||
},
|
||||
"token_type": {
|
||||
"type": "string",
|
||||
"example": "bearer"
|
||||
},
|
||||
"expires_in": {
|
||||
"type": "integer",
|
||||
"description": "过期时间(秒)",
|
||||
"example": 1800
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"SqlExecuteRequest": {
|
||||
"type": "object",
|
||||
"required": ["sql"],
|
||||
"properties": {
|
||||
"sql": {
|
||||
"type": "string",
|
||||
"description": "要执行的 SQL 语句",
|
||||
"example": "SELECT * FROM users WHERE role = :role"
|
||||
},
|
||||
"params": {
|
||||
"type": "object",
|
||||
"description": "SQL 参数字典,键为参数名,值为参数值",
|
||||
"additionalProperties": true,
|
||||
"example": {
|
||||
"role": "admin"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"SqlValidateRequest": {
|
||||
"type": "object",
|
||||
"required": ["sql"],
|
||||
"properties": {
|
||||
"sql": {
|
||||
"type": "string",
|
||||
"description": "要验证的 SQL 语句",
|
||||
"example": "SELECT * FROM users"
|
||||
}
|
||||
}
|
||||
},
|
||||
"QueryResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"code": {
|
||||
"type": "integer",
|
||||
"example": 200
|
||||
},
|
||||
"message": {
|
||||
"type": "string",
|
||||
"example": "SQL 执行成功"
|
||||
},
|
||||
"data": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": ["query"],
|
||||
"example": "query"
|
||||
},
|
||||
"columns": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "列名数组",
|
||||
"example": ["id", "username", "role"]
|
||||
},
|
||||
"rows": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"additionalProperties": true
|
||||
},
|
||||
"description": "查询结果行"
|
||||
},
|
||||
"row_count": {
|
||||
"type": "integer",
|
||||
"description": "返回的行数",
|
||||
"example": 5
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"ExecuteResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"code": {
|
||||
"type": "integer",
|
||||
"example": 200
|
||||
},
|
||||
"message": {
|
||||
"type": "string",
|
||||
"example": "SQL 执行成功"
|
||||
},
|
||||
"data": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": ["execute"],
|
||||
"example": "execute"
|
||||
},
|
||||
"affected_rows": {
|
||||
"type": "integer",
|
||||
"description": "影响的行数",
|
||||
"example": 1
|
||||
},
|
||||
"success": {
|
||||
"type": "boolean",
|
||||
"example": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"ValidateResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"code": {
|
||||
"type": "integer",
|
||||
"example": 200
|
||||
},
|
||||
"message": {
|
||||
"type": "string",
|
||||
"example": "SQL 验证完成"
|
||||
},
|
||||
"data": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"valid": {
|
||||
"type": "boolean",
|
||||
"description": "SQL 是否有效",
|
||||
"example": true
|
||||
},
|
||||
"warnings": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "警告信息列表",
|
||||
"example": ["包含危险操作: DROP"]
|
||||
},
|
||||
"sql_type": {
|
||||
"type": "string",
|
||||
"description": "SQL 类型",
|
||||
"example": "SELECT"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"TablesResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"code": {
|
||||
"type": "integer",
|
||||
"example": 200
|
||||
},
|
||||
"message": {
|
||||
"type": "string",
|
||||
"example": "获取表列表成功"
|
||||
},
|
||||
"data": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"tables": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "表名列表",
|
||||
"example": ["users", "courses", "exams"]
|
||||
},
|
||||
"count": {
|
||||
"type": "integer",
|
||||
"description": "表的数量",
|
||||
"example": 20
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"TableSchemaResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"code": {
|
||||
"type": "integer",
|
||||
"example": 200
|
||||
},
|
||||
"message": {
|
||||
"type": "string",
|
||||
"example": "获取表结构成功"
|
||||
},
|
||||
"data": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"table_name": {
|
||||
"type": "string",
|
||||
"example": "users"
|
||||
},
|
||||
"columns": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"field": {
|
||||
"type": "string",
|
||||
"description": "字段名",
|
||||
"example": "id"
|
||||
},
|
||||
"type": {
|
||||
"type": "string",
|
||||
"description": "字段类型",
|
||||
"example": "int(11)"
|
||||
},
|
||||
"null": {
|
||||
"type": "string",
|
||||
"enum": ["YES", "NO"],
|
||||
"description": "是否可为空",
|
||||
"example": "NO"
|
||||
},
|
||||
"key": {
|
||||
"type": "string",
|
||||
"description": "键类型(PRI, UNI, MUL)",
|
||||
"example": "PRI"
|
||||
},
|
||||
"default": {
|
||||
"type": "string",
|
||||
"nullable": true,
|
||||
"description": "默认值",
|
||||
"example": null
|
||||
},
|
||||
"extra": {
|
||||
"type": "string",
|
||||
"description": "额外信息",
|
||||
"example": "auto_increment"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"column_count": {
|
||||
"type": "integer",
|
||||
"description": "列的数量",
|
||||
"example": 10
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"ErrorResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"detail": {
|
||||
"type": "string",
|
||||
"description": "错误详情",
|
||||
"example": "SQL 执行失败: You have an error in your SQL syntax"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
{
|
||||
"name": "认证",
|
||||
"description": "用户认证相关接口"
|
||||
},
|
||||
{
|
||||
"name": "SQL执行器",
|
||||
"description": "SQL 执行和管理相关接口"
|
||||
}
|
||||
]
|
||||
}
|
||||
52
docs/规划/全链路联调/Ai工作流/dify/知识拆解工作流.md
Normal file
52
docs/规划/全链路联调/Ai工作流/dify/知识拆解工作流.md
Normal file
@@ -0,0 +1,52 @@
|
||||
# 知识拆解工作流
|
||||
|
||||
**工作流名称:** upload_knowldge
|
||||
**功能:** 上传资料并提炼知识点
|
||||
|
||||
---
|
||||
|
||||
## 配置信息
|
||||
|
||||
**API端点:**
|
||||
```
|
||||
URL: http://dify.ireborn.com.cn/v1/workflows/run
|
||||
API Key: app-LZhZcMO6CiriLMOLB2PwUGHx
|
||||
```
|
||||
|
||||
**请求参数:**
|
||||
```json
|
||||
{
|
||||
"inputs": {
|
||||
"file": {
|
||||
"type": "document",
|
||||
"transfer_method": "local_file",
|
||||
"upload_file_id": "dify_file_id"
|
||||
},
|
||||
"course_name": "课程名称",
|
||||
"course_id": "1",
|
||||
"material_id": "16"
|
||||
},
|
||||
"response_mode": "streaming",
|
||||
"user": "kaopeilian"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 实现方式
|
||||
|
||||
### Streaming模式直接完成
|
||||
- 使用 `response_mode: "streaming"`
|
||||
- 后端完整处理SSE流至 `workflow_finished`
|
||||
- 前端180秒超时,等待最终状态
|
||||
- 无需轮询
|
||||
|
||||
### 状态映射
|
||||
- `running` → 分析中
|
||||
- `succeeded` → 分析完成(刷新知识点)
|
||||
- `failed` → 分析失败
|
||||
- `stopped` → 分析已停止
|
||||
|
||||
---
|
||||
|
||||
**最后更新:** 2025-10-12
|
||||
224
docs/规划/全链路联调/Ai工作流/dify/考试工作流-最终版.md
Normal file
224
docs/规划/全链路联调/Ai工作流/dify/考试工作流-最终版.md
Normal file
@@ -0,0 +1,224 @@
|
||||
# 考试工作流-最终版
|
||||
|
||||
**版本:** v2.0
|
||||
**状态:** ✅ 已完成并验证
|
||||
**最后更新:** 2025-10-12
|
||||
|
||||
---
|
||||
|
||||
## 一、功能概述
|
||||
|
||||
### 考试页面
|
||||
- **URL**:`http://localhost:3001/trainee/exam?courseId=1`
|
||||
- **流程**:三轮考试(正式考试 + 两次错题重考)
|
||||
- **试题生成**:动态调用Dify工作流,预计1-3分钟
|
||||
|
||||
### 支持的题型
|
||||
1. 单选题 - 点击立即判断
|
||||
2. 多选题 - 点击提交判断
|
||||
3. 判断题 - 点击立即判断
|
||||
4. 填空题 - AI语义判断
|
||||
5. 问答题 - AI语义判断
|
||||
|
||||
---
|
||||
|
||||
## 二、Dify工作流配置
|
||||
|
||||
### 工作流1:试题生成器
|
||||
|
||||
**API配置:**
|
||||
```
|
||||
URL: http://dify.ireborn.com.cn/v1/workflows/run
|
||||
Token: app-tDlrmXyS9NtWCShsOx5FH49L
|
||||
User: kaopeilian
|
||||
Mode: streaming
|
||||
```
|
||||
|
||||
**输入参数:**
|
||||
```json
|
||||
{
|
||||
"course_id": 1,
|
||||
"position_id": 3,
|
||||
"single_choice_count": 4,
|
||||
"multiple_choice_count": 2,
|
||||
"true_false_count": 1,
|
||||
"fill_blank_count": 2,
|
||||
"essay_count": 1,
|
||||
"difficulty_level": 3
|
||||
}
|
||||
```
|
||||
|
||||
**第二、三轮增加:**
|
||||
```json
|
||||
{
|
||||
"mistake_records": "[{\"question_id\":null,\"knowledge_point_id\":456,...}]"
|
||||
}
|
||||
```
|
||||
|
||||
⚠️ **重要**:第一轮不传`mistake_records`参数,第二三轮传入JSON字符串格式
|
||||
|
||||
### 工作流2:答案判断器
|
||||
|
||||
**API配置:**
|
||||
```
|
||||
URL: http://dify.ireborn.com.cn/v1/workflows/run
|
||||
Token: app-FvMdrvbRBz547DVZEorgO1WT
|
||||
User: kaopeilian
|
||||
Mode: streaming
|
||||
```
|
||||
|
||||
**输入参数:**
|
||||
```json
|
||||
{
|
||||
"question": "题目内容",
|
||||
"correct_answer": "正确答案",
|
||||
"user_answer": "用户答案",
|
||||
"analysis": "答案解析"
|
||||
}
|
||||
```
|
||||
|
||||
**返回字段:** `result` 或 `is_correct`(值为"正确"/"错误"或true/false)
|
||||
|
||||
---
|
||||
|
||||
## 三、后端API接口
|
||||
|
||||
### 接口列表
|
||||
|
||||
| 接口 | 方法 | 功能 |
|
||||
|------|------|------|
|
||||
| `/api/v1/exams/generate` | POST | 生成试题 |
|
||||
| `/api/v1/exams/judge-answer` | POST | 判断主观题答案 |
|
||||
| `/api/v1/exams/record-mistake` | POST | 记录错题 |
|
||||
| `/api/v1/exams/mistakes` | GET | 获取错题记录 |
|
||||
|
||||
**文件位置:** `kaopeilian-backend/app/api/v1/exam.py`
|
||||
|
||||
### 关键实现
|
||||
|
||||
**获取岗位ID:**
|
||||
```python
|
||||
# 从用户岗位分配信息中自动获取
|
||||
position_member = await db.execute(
|
||||
select(PositionMember).where(
|
||||
PositionMember.user_id == current_user.id,
|
||||
PositionMember.is_deleted == False
|
||||
)
|
||||
)
|
||||
position_id = position_member.position_id if position_member else 1
|
||||
```
|
||||
|
||||
**创建Exam记录:**
|
||||
```python
|
||||
exam = Exam(
|
||||
user_id=current_user.id,
|
||||
course_id=request.course_id,
|
||||
exam_name=f"课程{request.course_id}考试",
|
||||
status="started"
|
||||
)
|
||||
await db.commit()
|
||||
return exam.id # 使用真实自增ID
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 四、前端实现要点
|
||||
|
||||
### 数据格式转换
|
||||
|
||||
**Dify格式 → 前端格式:**
|
||||
```typescript
|
||||
{
|
||||
type: "single_choice" → "single",
|
||||
topic.title → title,
|
||||
topic.options → options[],
|
||||
correct → correctAnswer,
|
||||
analysis → explanation,
|
||||
knowledge_point_id → parseInt()
|
||||
}
|
||||
```
|
||||
|
||||
### 三轮考试流程
|
||||
|
||||
```typescript
|
||||
// 第一轮:不传mistake_records
|
||||
await generateExam({
|
||||
course_id: 1,
|
||||
// 不包含 mistake_records
|
||||
})
|
||||
|
||||
// 第二、三轮:传入上一轮错题
|
||||
const mistakes = await getMistakes(lastExamId)
|
||||
await generateExam({
|
||||
course_id: 1,
|
||||
mistake_records: JSON.stringify(mistakes.data.data.mistakes)
|
||||
})
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 五、关键配置
|
||||
|
||||
### HTTP超时设置
|
||||
|
||||
```typescript
|
||||
// 前端
|
||||
generateExam: timeout 300000 // 5分钟
|
||||
judgeAnswer: timeout 60000 // 1分钟
|
||||
|
||||
// 后端
|
||||
httpx.AsyncClient(timeout=300.0) // 试题生成
|
||||
httpx.AsyncClient(timeout=60.0) // 答案判断
|
||||
```
|
||||
|
||||
### 数据库表
|
||||
|
||||
**exam_mistakes表:**
|
||||
- user_id, exam_id(必填)
|
||||
- question_id, knowledge_point_id(可空)
|
||||
- question_content, correct_answer, user_answer
|
||||
|
||||
---
|
||||
|
||||
## 六、重要经验
|
||||
|
||||
### FastAPI路由顺序
|
||||
```python
|
||||
# ✅ 正确顺序
|
||||
@router.get("/mistakes") # 具体路由在前
|
||||
@router.get("/{exam_id}") # 动态路由在后
|
||||
```
|
||||
|
||||
### ResponseModel使用
|
||||
```python
|
||||
# ✅ 统一使用ResponseModel
|
||||
@router.get("/mistakes", response_model=ResponseModel[GetMistakesResponse])
|
||||
async def get_mistakes(...):
|
||||
return ResponseModel(code=200, data=GetMistakesResponse(...))
|
||||
```
|
||||
|
||||
### 数据库ID生成
|
||||
- ❌ 不使用时间戳(13位数字超出INT范围)
|
||||
- ✅ 使用数据库自增ID
|
||||
|
||||
### Axios响应访问
|
||||
```typescript
|
||||
// ✅ 正确路径
|
||||
response.data.code
|
||||
response.data.data.result
|
||||
response.data.data.exam_id
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 七、测试账号
|
||||
|
||||
- **用户名:** admin
|
||||
- **密码:** admin123
|
||||
- **测试课程:** courseId=1
|
||||
|
||||
---
|
||||
|
||||
**文档维护:** AI助手
|
||||
**最后验证:** 2025-10-12(三轮考试流程验证成功)
|
||||
|
||||
164
docs/规划/全链路联调/Ai工作流/dify/考试工作流联调文档.md
Normal file
164
docs/规划/全链路联调/Ai工作流/dify/考试工作流联调文档.md
Normal file
@@ -0,0 +1,164 @@
|
||||
# 考试工作流联调文档
|
||||
|
||||
**版本:** v2.0
|
||||
**状态:** ✅ 已完成
|
||||
**最后更新:** 2025-10-12
|
||||
|
||||
---
|
||||
|
||||
## 一、基本信息
|
||||
|
||||
### 考试页面
|
||||
- **URL:** `http://localhost:3001/trainee/exam?courseId=1`
|
||||
- **流程:** 三轮考试(正式考试 + 两次错题重考)
|
||||
|
||||
### 两个Dify工作流
|
||||
|
||||
#### 工作流1:试题生成器
|
||||
- **API:** `http://dify.ireborn.com.cn/v1/workflows/run`
|
||||
- **Token:** `app-tDlrmXyS9NtWCShsOx5FH49L`
|
||||
- **功能:** 根据课程知识点生成试题
|
||||
|
||||
#### 工作流2:答案判断器
|
||||
- **API:** `http://dify.ireborn.com.cn/v1/workflows/run`
|
||||
- **Token:** `app-FvMdrvbRBz547DVZEorgO1WT`
|
||||
- **功能:** 判断填空题和问答题答案
|
||||
|
||||
---
|
||||
|
||||
## 二、API接口
|
||||
|
||||
### 后端接口列表
|
||||
|
||||
| 接口 | 方法 | 功能 |
|
||||
|------|------|------|
|
||||
| `/api/v1/exams/generate` | POST | 生成试题 |
|
||||
| `/api/v1/exams/judge-answer` | POST | 判断主观题答案 |
|
||||
| `/api/v1/exams/record-mistake` | POST | 记录错题 |
|
||||
| `/api/v1/exams/mistakes` | GET | 获取错题记录 |
|
||||
|
||||
**文件:** `kaopeilian-backend/app/api/v1/exam.py`
|
||||
|
||||
---
|
||||
|
||||
## 三、参数说明
|
||||
|
||||
### 生成试题参数
|
||||
|
||||
**第一轮(不传mistake_records):**
|
||||
```json
|
||||
{
|
||||
"course_id": 1,
|
||||
"single_choice_count": 4,
|
||||
"multiple_choice_count": 2,
|
||||
"true_false_count": 1,
|
||||
"fill_blank_count": 2,
|
||||
"essay_count": 1,
|
||||
"difficulty_level": 3
|
||||
}
|
||||
```
|
||||
|
||||
**第二、三轮(传入错题记录):**
|
||||
```json
|
||||
{
|
||||
"course_id": 1,
|
||||
"mistake_records": "[{\"question_id\":null,\"knowledge_point_id\":456,...}]",
|
||||
"single_choice_count": 2,
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
⚠️ **关键**:
|
||||
- 第一轮:完全不传`mistake_records`参数
|
||||
- 第二三轮:传入JSON字符串格式
|
||||
|
||||
### 判断答案参数
|
||||
|
||||
```json
|
||||
{
|
||||
"question": "题目内容",
|
||||
"correct_answer": "正确答案",
|
||||
"user_answer": "用户答案",
|
||||
"analysis": "答案解析"
|
||||
}
|
||||
```
|
||||
|
||||
**返回:** `result: "正确"/"错误"` 或 `is_correct: true/false`
|
||||
|
||||
---
|
||||
|
||||
## 四、数据库表
|
||||
|
||||
### exam_mistakes(错题记录表)
|
||||
|
||||
**核心字段:**
|
||||
- user_id, exam_id(必填,外键CASCADE)
|
||||
- question_id, knowledge_point_id(可空,SET NULL)
|
||||
- question_content, correct_answer, user_answer
|
||||
|
||||
**索引:** user_id, exam_id, knowledge_point_id
|
||||
|
||||
---
|
||||
|
||||
## 五、三轮考试流程
|
||||
|
||||
```
|
||||
第一轮
|
||||
↓ 答错N题
|
||||
├─ 记录错题到数据库
|
||||
└─ 获取错题记录 → 第二轮
|
||||
|
||||
第二轮(针对第一轮错题)
|
||||
↓ 答错M题
|
||||
├─ 记录错题到数据库
|
||||
└─ 获取错题记录 → 第三轮
|
||||
|
||||
第三轮(针对第二轮错题)
|
||||
↓ 完成
|
||||
└─ 显示最终成绩
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 六、关键技术点
|
||||
|
||||
### 1. 路由顺序
|
||||
```python
|
||||
# ✅ 正确
|
||||
@router.get("/mistakes") # 具体路由在前
|
||||
@router.get("/{exam_id}") # 动态路由在后
|
||||
```
|
||||
|
||||
### 2. 数据格式转换
|
||||
|
||||
**Dify → 前端:**
|
||||
```
|
||||
single_choice → single
|
||||
multiple_choice → multiple
|
||||
true_false → judge
|
||||
fill_blank → blank
|
||||
essay → essay
|
||||
```
|
||||
|
||||
### 3. 超时配置
|
||||
- 试题生成:300秒(5分钟)
|
||||
- 答案判断:60秒(1分钟)
|
||||
|
||||
---
|
||||
|
||||
## 七、测试验证
|
||||
|
||||
**测试账号:** admin / admin123
|
||||
**测试课程:** courseId=1
|
||||
|
||||
**验证要点:**
|
||||
- ✅ 试题成功生成
|
||||
- ✅ 所有题型正常答题
|
||||
- ✅ 错题正确记录
|
||||
- ✅ AI判断正常工作
|
||||
- ✅ 三轮流程完整
|
||||
|
||||
---
|
||||
|
||||
**文档维护:** 开发团队
|
||||
**参考:** `试题生成器的核心提示词与输出示例.md`
|
||||
124
docs/规划/全链路联调/Ai工作流/dify/试题生成器的核心提示词与输出示例.md
Normal file
124
docs/规划/全链路联调/Ai工作流/dify/试题生成器的核心提示词与输出示例.md
Normal file
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user