# 陪练功能API接口规范 ## 一、通用规范 ### 1.1 请求格式 **Base URL**: - 开发环境:`http://localhost:8000` - 生产环境:`https://aiedu.ireborn.com.cn` **请求头**: ```http Content-Type: application/json; charset=utf-8 Authorization: Bearer ``` ### 1.2 响应格式 **统一响应结构**: ```json { "code": 200, "message": "success", "data": {}, "request_id": "uuid" } ``` **分页响应结构**: ```json { "code": 200, "message": "success", "data": { "items": [], "total": 100, "page": 1, "page_size": 20, "pages": 5 } } ``` ### 1.3 状态码约定 | HTTP状态码 | code | 说明 | |-----------|------|------| | 200 | 200 | 成功 | | 400 | 400 | 请求参数错误 | | 401 | 401 | 未授权(token无效或过期) | | 403 | 403 | 无权限 | | 404 | 404 | 资源不存在 | | 500 | 500 | 服务器错误 | ## 二、数据模型 ### 2.1 场景类型枚举 (SceneType) ```typescript type SceneType = 'phone' | 'face' | 'complaint' | 'after-sales' | 'product-intro' ``` | 值 | 说明 | |----|------| | phone | 电话销售 | | face | 面对面销售 | | complaint | 客户投诉 | | after-sales | 售后服务 | | product-intro | 产品介绍 | ### 2.2 难度等级枚举 (Difficulty) ```typescript type Difficulty = 'beginner' | 'junior' | 'intermediate' | 'senior' | 'expert' ``` | 值 | 说明 | |----|------| | beginner | 入门 | | junior | 初级 | | intermediate | 中级 | | senior | 高级 | | expert | 专家 | ### 2.3 场景状态枚举 (SceneStatus) ```typescript type SceneStatus = 'active' | 'inactive' ``` | 值 | 说明 | |----|------| | active | 启用 | | inactive | 禁用 | ### 2.4 陪练场景对象 (PracticeScene) ```typescript interface PracticeScene { id: number name: string description: string type: SceneType difficulty: Difficulty status: SceneStatus background: string ai_role: string objectives: string[] keywords: string[] duration: number usage_count: number rating: number created_by: number updated_by: number created_at: string updated_at: string } ``` ## 三、场景管理接口(Manager) ### 3.1 获取场景列表 **接口**:`GET /api/v1/manager/practice-scenes` **权限**:manager、admin **请求参数**: | 参数 | 类型 | 必填 | 说明 | 示例 | |------|------|------|------|------| | page | number | 否 | 页码,默认1 | 1 | | size | number | 否 | 每页数量,默认20 | 20 | | type | string | 否 | 场景类型筛选 | phone | | difficulty | string | 否 | 难度筛选 | intermediate | | status | string | 否 | 状态筛选 | active | | search | string | 否 | 关键词搜索(名称、描述) | 销售 | **请求示例**: ```http GET /api/v1/manager/practice-scenes?page=1&size=20&type=phone&difficulty=beginner Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... ``` **响应示例**: ```json { "code": 200, "message": "success", "data": { "items": [ { "id": 1, "name": "初次电话拜访客户", "description": "模拟首次通过电话联系潜在客户的场景", "type": "phone", "difficulty": "beginner", "status": "active", "background": "你是一名销售专员...", "ai_role": "AI扮演一位忙碌的采购经理...", "objectives": ["学会专业的电话开场白", "快速建立信任关系"], "keywords": ["开场白", "需求挖掘"], "duration": 10, "usage_count": 256, "rating": 4.5, "created_at": "2024-01-15T10:30:00", "updated_at": "2024-03-18T15:20:00" } ], "total": 15, "page": 1, "page_size": 20, "pages": 1 } } ``` ### 3.2 获取场景详情 **接口**:`GET /api/v1/manager/practice-scenes/{id}` **权限**:manager、admin **路径参数**: | 参数 | 类型 | 必填 | 说明 | |------|------|------|------| | id | number | 是 | 场景ID | **请求示例**: ```http GET /api/v1/manager/practice-scenes/1 Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... ``` **响应示例**: ```json { "code": 200, "message": "success", "data": { "id": 1, "name": "初次电话拜访客户", "description": "模拟首次通过电话联系潜在客户的场景", "type": "phone", "difficulty": "beginner", "status": "active", "background": "你是一名销售专员,需要通过电话联系一位从未接触过的潜在客户。客户是某公司的采购经理,你需要在短时间内引起他的兴趣。", "ai_role": "AI扮演一位忙碌且略显不耐烦的采购经理,对推销电话比较抵触,但如果销售人员能够快速切入他的需求点,他会愿意继续交谈。", "objectives": ["学会专业的电话开场白", "快速建立信任关系", "有效探询客户需求", "预约下次沟通时间"], "keywords": ["开场白", "需求挖掘", "时间管理", "预约技巧"], "duration": 10, "usage_count": 256, "rating": 4.5, "created_by": 1, "updated_by": 1, "created_at": "2024-01-15T10:30:00", "updated_at": "2024-03-18T15:20:00" } } ``` ### 3.3 创建场景 **接口**:`POST /api/v1/manager/practice-scenes` **权限**:manager、admin **请求体**: ```typescript interface CreateSceneRequest { name: string // 必填,最长200字符 description: string // 必填,场景描述 type: SceneType // 必填 difficulty: Difficulty // 必填 status?: SceneStatus // 可选,默认active background: string // 必填,场景背景 ai_role: string // 必填,AI角色描述 objectives: string[] // 必填,练习目标数组 keywords: string[] // 必填,关键词数组 duration?: number // 可选,预计时长(分钟),默认10 } ``` **请求示例**: ```http POST /api/v1/manager/practice-scenes Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... Content-Type: application/json { "name": "产品功能介绍", "description": "练习向客户详细介绍产品功能特点", "type": "product-intro", "difficulty": "junior", "status": "active", "background": "客户对你们的产品有一定了解,现在希望你详细介绍产品的核心功能和优势。", "ai_role": "AI扮演一位专业的采购人员,会提出具体的技术问题和需求,希望了解产品的详细功能。", "objectives": ["清晰介绍产品功能", "突出产品优势", "回答技术问题"], "keywords": ["产品介绍", "功能展示", "优势分析"], "duration": 12 } ``` **响应示例**: ```json { "code": 200, "message": "创建场景成功", "data": { "id": 6, "name": "产品功能介绍", "description": "练习向客户详细介绍产品功能特点", "type": "product-intro", "difficulty": "junior", "status": "active", "background": "客户对你们的产品有一定了解...", "ai_role": "AI扮演一位专业的采购人员...", "objectives": ["清晰介绍产品功能", "突出产品优势", "回答技术问题"], "keywords": ["产品介绍", "功能展示", "优势分析"], "duration": 12, "usage_count": 0, "rating": 0.0, "created_at": "2025-10-13T18:30:00" } } ``` ### 3.4 更新场景 **接口**:`PUT /api/v1/manager/practice-scenes/{id}` **权限**:manager、admin **路径参数**: | 参数 | 类型 | 必填 | 说明 | |------|------|------|------| | id | number | 是 | 场景ID | **请求体**:与创建场景相同,所有字段可选 **请求示例**: ```http PUT /api/v1/manager/practice-scenes/6 Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... Content-Type: application/json { "name": "产品功能详细介绍", "duration": 15, "status": "active" } ``` **响应示例**: ```json { "code": 200, "message": "更新场景成功", "data": { "id": 6, "name": "产品功能详细介绍", "duration": 15, "updated_at": "2025-10-13T19:00:00" } } ``` ### 3.5 删除场景 **接口**:`DELETE /api/v1/manager/practice-scenes/{id}` **权限**:manager、admin **路径参数**: | 参数 | 类型 | 必填 | 说明 | |------|------|------|------| | id | number | 是 | 场景ID | **请求示例**: ```http DELETE /api/v1/manager/practice-scenes/6 Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... ``` **响应示例**: ```json { "code": 200, "message": "删除场景成功", "data": { "id": 6 } } ``` ### 3.6 切换场景状态 **接口**:`PUT /api/v1/manager/practice-scenes/{id}/toggle-status` **权限**:manager、admin **路径参数**: | 参数 | 类型 | 必填 | 说明 | |------|------|------|------| | id | number | 是 | 场景ID | **请求示例**: ```http PUT /api/v1/manager/practice-scenes/6/toggle-status Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... ``` **响应示例**: ```json { "code": 200, "message": "场景状态已切换", "data": { "id": 6, "status": "inactive" } } ``` ## 四、学员查询接口(Trainee) ### 4.1 获取可用场景列表 **接口**:`GET /api/v1/practice/scenes` **权限**:trainee、manager、admin **请求参数**: | 参数 | 类型 | 必填 | 说明 | |------|------|------|------| | page | number | 否 | 页码,默认1 | | size | number | 否 | 每页数量,默认20 | | type | string | 否 | 场景类型筛选 | | difficulty | string | 否 | 难度筛选 | | search | string | 否 | 关键词搜索 | **说明**:仅返回status=active的场景 **请求示例**: ```http GET /api/v1/practice/scenes?page=1&size=20&type=phone Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... ``` **响应示例**:同管理接口的列表响应 ### 4.2 获取场景详情 **接口**:`GET /api/v1/practice/scenes/{id}` **权限**:trainee、manager、admin **路径参数**: | 参数 | 类型 | 必填 | 说明 | |------|------|------|------| | id | number | 是 | 场景ID | **请求示例**: ```http GET /api/v1/practice/scenes/1 Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... ``` **响应示例**:同管理接口的详情响应 ## 五、陪练对话接口 ### 5.1 开始陪练对话 **接口**:`POST /api/v1/practice/start` **权限**:trainee、manager、admin **协议**:SSE (Server-Sent Events) **请求体**: ```typescript interface StartPracticeRequest { scene_id?: number // 场景ID(陪练中心模式,可选) scene_name: string // 场景名称(必填) scene_description?: string // 场景描述(可选) scene_background: string // 场景背景(必填) scene_ai_role: string // AI角色(必填) scene_objectives: string[] // 练习目标(必填) scene_keywords?: string[] // 关键词(可选) user_message: string // 用户消息(必填) conversation_id?: string // 对话ID(续接对话时必填) is_first: boolean // 是否首次消息(必填) } ``` **⚠️ 重要说明**: - **首次消息 (is_first=true)**:后端会将场景信息(background, ai_role, objectives等)拼接到user_message前面,作为完整的系统提示发送给Coze,让AI理解角色设定 - **后续消息 (is_first=false)**:仅发送user_message,不再重复场景信息 **请求示例**: **首次消息请求示例**: ```http POST /api/v1/practice/start Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... Content-Type: application/json { "scene_id": 1, "scene_name": "初次电话拜访客户", "scene_description": "模拟首次通过电话联系潜在客户的场景", "scene_background": "你是一名销售专员,需要通过电话联系一位从未接触过的潜在客户。客户是某公司的采购经理,你需要在短时间内引起他的兴趣。", "scene_ai_role": "AI扮演一位忙碌且略显不耐烦的采购经理,对推销电话比较抵触,但如果销售人员能够快速切入他的需求点,他会愿意继续交谈。", "scene_objectives": ["学会专业的电话开场白", "快速建立信任关系", "有效探询客户需求"], "scene_keywords": ["开场白", "需求挖掘", "时间管理"], "user_message": "您好,我是XX公司的销售顾问,想占用您几分钟时间", "is_first": true } ``` **后端处理**:首次消息时,后端会将场景信息构建为完整的场景设定文本,拼接到user_message前面发送给Coze。 **实际发送给Coze的内容**: ``` # 陪练场景设定 ## 场景名称 初次电话拜访客户 ## 场景背景 你是一名销售专员,需要通过电话联系一位从未接触过的潜在客户... ## AI角色要求 AI扮演一位忙碌且略显不耐烦的采购经理... ## 练习目标 1. 学会专业的电话开场白 2. 快速建立信任关系 3. 有效探询客户需求 ## 关键词 开场白, 需求挖掘, 时间管理 --- 现在开始陪练对话。请你严格按照上述场景设定扮演角色,与学员进行实战对话练习。 学员的第一句话:您好,我是XX公司的销售顾问,想占用您几分钟时间 ``` **后续消息请求示例**: ```http POST /api/v1/practice/start Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... Content-Type: application/json { "user_message": "我们公司提供的是轻医美整体解决方案", "conversation_id": "conv_abc123", "is_first": false } ``` **后端处理**:后续消息直接发送user_message,不再拼接场景信息。 **SSE事件格式**: #### 事件1:对话创建 ``` event: conversation.chat.created data: {"conversation_id":"conv_abc123","chat_id":"chat_xyz789"} ``` #### 事件2:消息增量(实时打字) ``` event: message.delta data: {"content":"您"} event: message.delta data: {"content":"好"} event: message.delta data: {"content":","} ``` #### 事件3:消息完成 ``` event: message.completed data: {"content":"您好,我现在很忙,你有什么事吗?"} ``` #### 事件4:对话完成 ``` event: conversation.completed data: {"token_count":156} ``` #### 事件5:结束标记 ``` event: done data: [DONE] ``` #### 事件6:错误 ``` event: error data: {"error":"对话失败: Network Error"} ``` **前端处理示例**: ```javascript const response = await fetch('/api/v1/practice/start', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}` }, body: JSON.stringify(requestData) }) const reader = response.body.getReader() const decoder = new TextDecoder() while (true) { const { value, done } = await reader.read() if (done) break const chunk = decoder.decode(value) const lines = chunk.split('\n\n') for (const line of lines) { if (!line.trim() || !line.startsWith('event: ')) continue const [eventLine, dataLine] = line.split('\n') const event = eventLine.replace('event: ', '') const data = JSON.parse(dataLine.replace('data: ', '')) switch (event) { case 'conversation.chat.created': conversationId.value = data.conversation_id break case 'message.delta': aiMessage.content += data.content break case 'message.completed': console.log('消息完成') break case 'conversation.completed': console.log('Token用量:', data.token_count) break case 'error': ElMessage.error(data.error) break } } } ``` ### 5.2 中断对话 **接口**:`POST /api/v1/practice/interrupt` **权限**:trainee、manager、admin **请求体**: ```typescript interface InterruptPracticeRequest { conversation_id: string chat_id: string } ``` **请求示例**: ```http POST /api/v1/practice/interrupt Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... Content-Type: application/json { "conversation_id": "conv_abc123", "chat_id": "chat_xyz789" } ``` **响应示例**: ```json { "code": 200, "message": "对话已中断", "data": { "conversation_id": "conv_abc123", "chat_id": "chat_xyz789" } } ``` ### 5.3 获取对话列表 **接口**:`GET /api/v1/practice/conversations` **权限**:trainee、manager、admin **请求参数**: | 参数 | 类型 | 必填 | 说明 | |------|------|------|------| | page | number | 否 | 页码,默认1 | | size | number | 否 | 每页数量,默认20 | **请求示例**: ```http GET /api/v1/practice/conversations?page=1&size=20 Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... ``` **响应示例**: ```json { "code": 200, "message": "success", "data": { "items": [ { "id": "conv_abc123", "name": "初次电话拜访客户 - 2025-10-13", "created_at": 1697184000000 } ], "has_more": false, "page": 1, "size": 20 } } ``` ## 六、Dify场景提取接口 ### 6.1 从课程提取场景 **接口**:`POST /api/v1/practice/extract-scene` **权限**:trainee、manager、admin **请求体**: ```typescript interface ExtractSceneRequest { course_id: number } ``` **请求示例**: ```http POST /api/v1/practice/extract-scene Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... Content-Type: application/json { "course_id": 5 } ``` **响应示例**: ```json { "code": 200, "message": "场景提取成功", "data": { "scene": { "name": "轻医美产品咨询陪练", "description": "模拟客户咨询轻医美产品的场景,重点练习产品介绍和价格谈判技巧", "type": "product-intro", "difficulty": "intermediate", "background": "客户是一位30岁女性,对面部抗衰项目感兴趣,之前了解过玻尿酸填充,现在想详细咨询你们的产品和价格。", "ai_role": "AI扮演一位对轻医美有一定了解的客户,关注产品安全性和性价比,会提出具体的技术问题和价格异议。", "objectives": [ "了解客户具体需求和预算", "专业介绍产品成分和效果", "处理价格异议,强调价值", "建立客户信任" ], "keywords": ["抗衰", "玻尿酸", "价格", "安全性"] }, "workflow_run_id": "wf_run_abc123", "task_id": "task_xyz789" } } ``` ## 七、错误码说明 | code | message | 说明 | 处理建议 | |------|---------|------|---------| | 200 | success | 成功 | 正常处理 | | 400 | 参数错误 | 请求参数不合法 | 检查参数格式 | | 401 | 未授权 | token无效或过期 | 重新登录 | | 403 | 无权限 | 当前角色无权限 | 提示用户无权限 | | 404 | 资源不存在 | 场景不存在 | 返回列表页 | | 409 | 资源冲突 | 场景名称重复 | 提示修改名称 | | 500 | 服务器错误 | 内部错误 | 提示稍后重试 | | 503 | 服务不可用 | Coze/Dify服务异常 | 提示稍后重试 | ## 八、附录 ### 8.1 完整TypeScript类型定义 ```typescript // 场景类型 type SceneType = 'phone' | 'face' | 'complaint' | 'after-sales' | 'product-intro' type Difficulty = 'beginner' | 'junior' | 'intermediate' | 'senior' | 'expert' type SceneStatus = 'active' | 'inactive' // 场景对象 interface PracticeScene { id: number name: string description: string type: SceneType difficulty: Difficulty status: SceneStatus background: string ai_role: string objectives: string[] keywords: string[] duration: number usage_count: number rating: number created_by: number updated_by: number created_at: string updated_at: string } // API响应 interface ResponseModel { code: number message: string data: T request_id?: string } interface PaginatedResponse { items: T[] total: number page: number page_size: number pages: number } // 请求对象 interface CreateSceneRequest { name: string description: string type: SceneType difficulty: Difficulty status?: SceneStatus background: string ai_role: string objectives: string[] keywords: string[] duration?: number } interface StartPracticeRequest { scene_id?: number scene_name: string scene_background: string scene_ai_role: string scene_objectives: string[] user_message: string conversation_id?: string is_first: boolean } interface ExtractSceneRequest { course_id: number } // SSE事件 interface SSEEvent { event: 'conversation.chat.created' | 'message.delta' | 'message.completed' | 'conversation.completed' | 'error' | 'done' data: any } ``` ### 8.2 API调用示例(完整) ```javascript // src/api/practice.ts import request from '@/utils/request' export const practiceApi = { // 获取场景列表 getScenes(params) { return request.get('/api/v1/practice/scenes', { params }) }, // 获取场景详情 getSceneDetail(id) { return request.get(`/api/v1/practice/scenes/${id}`) }, // 开始陪练(SSE流式) startPractice(data) { // SSE需要特殊处理,不能用普通的request return fetch(`${import.meta.env.VITE_API_BASE_URL}/api/v1/practice/start`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${getToken()}` }, body: JSON.stringify(data) }) }, // 中断对话 interruptPractice(data) { return request.post('/api/v1/practice/interrupt', data) }, // 获取对话列表 getConversations(params) { return request.get('/api/v1/practice/conversations', { params }) }, // 提取场景 extractScene(data) { return request.post('/api/v1/practice/extract-scene', data) } } // 管理接口 export const practiceManagerApi = { // 获取场景列表 getScenes(params) { return request.get('/api/v1/manager/practice-scenes', { params }) }, // 创建场景 createScene(data) { return request.post('/api/v1/manager/practice-scenes', data) }, // 更新场景 updateScene(id, data) { return request.put(`/api/v1/manager/practice-scenes/${id}`, data) }, // 删除场景 deleteScene(id) { return request.delete(`/api/v1/manager/practice-scenes/${id}`) }, // 切换状态 toggleStatus(id) { return request.put(`/api/v1/manager/practice-scenes/${id}/toggle-status`) } } ``` --- **文档版本**:v1.0 **最后更新**:2025-10-13 **维护人**:考培练系统开发团队