# AI 对话链路全栈改造实施计划 > **版本**: v1.2 > **日期**: 2026-07-24 > **作者**: 宋献 (Simon) + Duckula > **状态**: ✅ 已实施并部署(Phase 1-6 全部完成,2026-07-13 01:38 生产部署) --- ## 部署记录 ### 2026-07-13 01:38 生产部署(v5) | 组件 | 版本 | 部署内容 | 验证 | |------|------|---------|------| | **后端** | v5 | 6 个 Python 文件部署到 `/opt/wecom-it-desk/app/` | `docker compose restart backend` → healthy ✅ | | **H5 前端** | v4 | dist 部署到 `/opt/wecom-it-desk/frontend-h5/dist/` | JS hash `index-B6dzwk-X.js` ✅ | | **Agent 前端** | v5 | dist 部署到 `/opt/wecom-it-desk/frontend-agent/dist/` | JS hash `index-2BTn4SZz.js` ✅ | | **Nginx** | - | `nginx -s reload` | healthy ✅ | **后端变更文件清单**(旧文件备份在 `/tmp/backend_bak_v5/`): | 文件 | 变更内容 | |------|---------| | `app/tasks/h5_ai_task.py` | VisionService 接入 + 图片消息融合 + ai_thinking 双推 + diagnosis_stage 存储 | | `app/api/h5.py` | `process_h5_ai_reply()` 调用新增 `msg_type` + `media_url` 参数 | | `app/services/ai_service.py` | `get_structured_reply()` blocking 模式 + JSON 解析 + `response_time_ms` 计时 + 慢响应告警 | | `app/services/closing_service.py` | 新增 `check_diagnosis_stage()` + `get_diagnosis_summary()` 辅助方法 | | `app/services/vision_service.py` | 已有实现,本次接入主链路 | | `app/api/websocket.py` | `ai_thinking` 同时推员工和坐席 + `dynamic_recommend` 推送 | **前端变更文件清单**: | 前端 | 文件 | 变更内容 | |------|------|---------| | H5 | `MessageBubble.vue` | 新增 `ai_structured` 渲染分支(文字 + 选项按钮 + 脉冲动画) | | H5 | `DynamicRecommend.vue` | **新建** — 右边栏动态推荐卡片(3 种类型 approval/action/info) | | H5 | `RightPanel.vue` | 重写为 v2 手风琴布局(设备信息 / 自助诊断 / 底部标签页) | | H5 | `useH5WebSocket.ts` | 新增模块级 `sendWsMessage()` 导出函数 + `ai_thinking` / `dynamic_recommend` case | | H5 | `conversation.ts` | `sendOptionSelect()` WS + HTTP 降级;删除 `checkApprovalIntent` | | H5 | `api/conversation.ts` | `MsgContentType` 新增 `'ai_structured'` | | Agent | `MessageBubble.vue` | 新增 `ai_structured` 只读渲染 + `byod_card` 渲染分支 | | Agent | `ChatArea.vue` | 新增 AI 思考指示器 UI(脉冲动画) | | Agent | `useWebSocket.ts` | 新增 `ai_thinking` case | | Agent | `conversation.ts` | 新增 `aiThinkingConversations` + `handleAiThinking()` + `handleNewMessage` 透传修复 | **部署方式**:通过 `v2_ops.py`(jumpserver-V2 技能)JumpServer REST API + plink PTY 执行 **验证结果**: - 5 个容器全部 healthy(backend / nginx / redis / neo4j / postgres) - 后端 `/health` 返回 200 - 后端日志正常(调度任务运行,无报错) - H5 / Agent 前端新 JS hash 已就位 --- ## 一、背景与目标 ### 1.1 改造背景 当前 IT 智能服务台存在以下核心问题: 1. **Dify 工作流臃肿**:85 个节点,推理延迟高,维护困难 2. **审批意图识别质量差**:回复太快、无关触发、精度低、准确率低 3. **卡片与文字"两张皮"**:审批卡片由前端异步独立推送,与 AI 文字回复无关联、时间不同步 4. **图片消息处理缺失**:`VisionService` 完整实现但零调用,员工发图片 AI "失明" 5. **右边栏布局分散**:自助诊断、软件下载、资源申请三个独立模块,无动态推荐区 ### 1.2 改造目标 | 目标 | 衡量标准 | |------|---------| | Dify 节点精简 | 85 → ~35 节点,推理延迟降低 40%+ | | 审批意图准确率 | 误触发率从 ~60% 降至 ~15% | | 卡片与文字融合 | 同一次推理、同一时刻到达、语义强关联 | | 图片消息可用 | 员工发截图 → AI 能"看懂"并回复 | | 右边栏统一 | 手风琴折叠 + 智能推荐默认页 | --- ## 二、当前架构分析 ### 2.1 当前消息流:两条平行轨道 ``` ┌─ 通道 A(审批卡片)──────────────────────────────────┐ │ 前端 checkApprovalIntent() │ │ → POST /approval/detect-intent │ │ → Dify 意图识别 (blocking, 同一个 Dify 应用) │ │ → 前端 messages.value.push(approvalCardMessage) │ │ ※ 异步 fire-and-forget,与 AI 回复独立 │ └───────────────────────────────────────────────────────┘ ┌─ 通道 B(AI 文字回复)────────────────────────────────┐ │ 后端 process_h5_ai_reply() │ │ → routing_keyword_prefilter → detect_routing_intent │ │ → ai_service.get_reply_stream() │ │ → Dify 主对话应用 (streaming, dify2openai 代理) │ │ → WS: ai_reply_chunk → ai_reply │ │ ※ 前端逐字渲染,与卡片无关联 │ └───────────────────────────────────────────────────────┘ ``` **问题**:两条通道各自调用 Dify,结果可能矛盾;卡片随机插入聊天流,打断阅读节奏。 ### 2.2 关键代码位置 | 模块 | 文件路径 | 关键行号 | 说明 | |------|---------|---------|------| | 后端消息处理 | `backend/app/tasks/h5_ai_task.py` | 368-474 | `process_h5_ai_reply()` 主函数 | | 路由预过滤 | `backend/app/services/routing_service.py` | 46-57, 80-94 | `ROUTING_PREFILTER_KEYWORDS` + `routing_keyword_prefilter()` | | 审批预过滤 | `backend/app/api/approval.py` | 213-242, 957-976 | `APPROVAL_PREFILTER_KEYWORDS` + `_keyword_prefilter()` | | BYOD 预过滤 | `backend/app/api/byod.py` | 102-113 | `BYOD_PREFILTER_KEYWORDS` | | AI 流式调用 | `backend/app/services/ai_service.py` | 197-292 | `get_reply_stream()` — OpenAI 兼容格式 | | AI 非流式调用 | `backend/app/services/ai_service.py` | 83-192 | `get_reply()` — 降级用 | | AI 处理器 | `backend/app/services/ai_handler.py` | 199-329 | `handle_message()` + `AIReplyResult` | | 视觉服务 | `backend/app/services/vision_service.py` | 全文件 | 完整实现但零调用 | | RAGFlow 客户端 | `backend/app/integrations/ragflow/client.py` | 全文件 | 完整实现但未接入主链路 | | RAGFlow 旧客户端 | `backend/app/core/clients/ragflow.py` | 全文件 | 依赖未配置环境变量 | | 前端审批检测 | `frontend-h5/src/stores/conversation.ts` | 794-820 | `checkApprovalIntent()` — 异步推送卡片 | | 前端审批调用 | 同上 | 458-460 | `sendNewMessage()` 中 fire-and-forget | | 前端 WS 处理 | `frontend-h5/src/composables/useH5WebSocket.ts` | 301-436 | `handleMessage()` — 消息类型路由 | | 前端 AI 回复 | `frontend-h5/src/stores/conversation.ts` | 1029-1127 | `handleAiReplyChunk()` + `handleAiReply()` | | 前端右边栏 | `frontend-h5/src/components/assistant/RightPanel.vue` | 15-77 | 模板结构 | | Dify 意图 Prompt | `docs/02-技术文档/实现配置/dify_unified_intent_prompt_v3.md` | 全文件 | v3 统一意图识别 Prompt | | BYOD 意图 Prompt | `docs/02-技术文档/实现配置/dify_byod_intent_prompt.md` | 全文件 | BYOD 意图扩展 | ### 2.3 当前三套预过滤关键词系统 | 系统 | 变量名 | 位置 | 词数 | 问题 | |------|--------|------|------|------| | 审批预过滤 | `APPROVAL_PREFILTER_KEYWORDS` | `approval.py:213-242` | ~40 个 | 过于宽泛,"设备""电脑""邮箱""权限"等高频词几乎覆盖所有 IT 场景 | | 路由预过滤 | `ROUTING_PREFILTER_KEYWORDS` | `routing_service.py:46-57` | ~22 个 | 合理,仅非 IT 业务词 | | BYOD 预过滤 | `BYOD_PREFILTER_KEYWORDS` | `byod.py:102-113` | ~10 个 | 合理,仅 BYOD 专用词 | ### 2.4 两套 Dify API 调用模式 | 维度 | AI 回复 (ai_service.py) | 意图识别 (approval.py / routing_service.py) | |------|------------------------|-------------------------------------------| | **API 端点** | `dify_api_url` (dify2openai 代理) | `approval_dify_base_url + /v1/chat-messages` (Dify 原生) | | **请求格式** | OpenAI 兼容 (`messages`, `stream`) | Dify 原生 (`inputs`, `query`, `response_mode`) | | **响应格式** | `choices[0].delta.content` (SSE) | `answer` 字段含 JSON 字符串 | | **流式** | 是 | 否 (blocking) | | **用途** | IT 知识库问答 | 审批/路由意图分类 | | **配置** | `dify_api_url`, `dify_api_key` | `approval_dify_base_url`, `approval_dify_api_key` | --- ## 三、差距分析(11 项) ### 3.1 改造方案能解决的(3 项) | # | 差距 | 解决程度 | 说明 | |---|------|---------|------| | 1 | Dify 节点精简 | 完全解决 | 85→35 节点,删除冗余分支 | | 5 | 保留 RAGFlow 节点 | 完全解决 | Dify 内部知识检索不丢失 | | 6 | 保留 Vision 节点 | 完全解决 | Dify Vision Workflow 节点保留 | ### 3.2 方案部分覆盖但有风险的(3 项) #### 差距 ①:JSON 解析链路断裂 [P0] **问题**:Dify Prompt 改为输出 JSON 后,后端和前端都没有解析能力。 - `ai_service.py:232-263` — `get_reply_stream()` 把 `choices[0].delta.content` 作为纯文本逐 chunk 透传 - `ai_handler.py:278-282` — `handle_message()` 把 `ai_result["content"]` 直接放入 `AIReplyResult.content` - `conversation.ts:1029-1054` — `handleAiReplyChunk()` 直接将 `data.chunk` 追加到 `content` 字段 - `MessageBubble.vue` — 对 `msg_type === 'text'` 用 `white-space: pre-wrap` 纯文本渲染 **需要补的**:全链路结构化改造——Dify 输出 JSON → 后端解析提取 → WS 推送结构化消息 → 前端新增渲染组件。 #### 差距 ②:Dify 不支持图片输入 [P1] **问题**:`ai_service.py:217-226` 的 payload 用 OpenAI 兼容格式,`content` 只接受字符串,不能传图片。 **正确路径**:图片 → `VisionService.analyze_screenshot()` 预分析 → 生成文字描述 → 拼接到 Dify 文本输入中。 #### 差距 ③:消息融合的边界场景 [P2] **问题**: - 超时风险:用户发图后思考 10 秒再打字,5 秒窗口已关闭 - 多图处理:连续发 3 张截图 + 一段文字,合并还是分别处理? - 乱序到达:文字先到、图片后到 - 窗口内多条文字:多条短消息需要合并 ### 3.3 方案完全未涉及的(5 项) #### 差距 ④:VisionService 完整实现但零调用 [P0] **问题**:`vision_service.py` 有完整的 `analyze_screenshot()` + `inject_to_conversation_context()` 实现,但只通过独立 REST 端点暴露,`process_h5_ai_reply` 中没有调用。 员工发图片 → 不触发视觉分析 → Dify 只收到空文字 → AI 回复"请问您遇到了什么问题?" **需要补的**:在 `process_h5_ai_reply` 中增加图片分支——检测到图片 → 调 `VisionService.analyze_screenshot()` → 描述拼接到用户文字中 → 传给 Dify。 #### 差距 ⑤:选项回传链路 [P1] **问题**:AI 回复包含选项按钮,用户点击后无链路回传。 - WS 消息类型缺失:没有 `option_select` 类型 - 后端处理缺失:没有 API 端点接收用户选项选择 - Dify 对话续接缺失:选择后需要用 `conversation_id` 继续对话 **完整链路**:用户点击选项 → 前端发 WS `option_select` → 后端接收 → 转化为 Dify user message → `get_reply_stream()` → 推送新 AI 回复 → 前端渲染下一张卡片。 #### 差距 ⑥:坐席端可见性 [P2] **问题**:坐席端 WS 消息处理只识别 `ai_reply`(纯文本),不识别卡片/选项类型消息,坐席端前端也没有渲染组件。 #### 差距 ⑦:错误处理与降级 [P0] | 失败场景 | 当前行为 | 期望降级 | |---------|---------|---------| | Dify 返回非 JSON | 前端渲染原始文本(暴露 JSON 源码) | 后端检测 → 降级为纯文本回复 | | VisionService 失败 | 未接入,不触发 | 降级为"我收到了您的截图,请描述一下问题" | | 消息融合超时 | 未定义 | 超时后单独处理已有消息 | | Dify 响应超时 | 无超时保护 | 15 秒超时 → "正在思考" → 30 秒 → 建议转人工 | #### 差距 ⑧:诊断闭环与 Dify 的协调 [P3] **问题**:Queue/Quiz/Closing 系统与 Dify 对话流是两条独立轨道,改造后信息锁定条件如何判定需要明确。 --- ## 四、审批意图识别问题分析(4 问题 + 3 根因) ### 4.1 四个问题的根因 #### 问题 1:「回复太快了」 **根因**:审批卡片推送走前端异步 fire-and-forget 路径。 ```typescript // conversation.ts:458-460 checkApprovalIntent(content).catch(...) // 异步触发,不等待 ``` Dify 意图识别返回后(3-15 秒),卡片突然插入消息流中,与正在流式输出的 AI 回复交错出现。无任何确认步骤。 #### 问题 2:「无关性」 **根因**:`APPROVAL_PREFILTER_KEYWORDS`(`approval.py:213-242`)有 ~40 个词,包含"设备""电脑""邮箱""权限""软件""报修"等高频 IT 词,几乎覆盖所有 IT 相关消息。 例如员工说"我的邮箱登不上"——命中"邮箱" → 触发 Dify 意图识别 → 可能误判为"公共邮箱账号申请" → 推送审批卡片。 #### 问题 3:「精度差」 **根因**:一次性判断 12 种审批类型,无层级分类。LLM 容易混淆相似类型("会议室故障报修" vs "员工IT支持与故障报修")。且每次只看单条消息,不看对话历史。 #### 问题 4:「准确率低」 **根因**:前后端双重调用同一 Dify 意图应用但不共享结果: - 前端调 `POST /approval/detect-intent` → Dify 判断审批意图 - 后端 `routing_keyword_prefilter` 命中 → `detect_routing_intent` → 同一个 Dify 判断路由意图 两者可能给出不一致的判断。 ### 4.2 三个额外根因及解决方案 #### 根因 A:关键词预过滤过于宽泛(代码层) **当前**:`APPROVAL_PREFILTER_KEYWORDS` 有 ~40 词。 **改造**:收窄到仅强意图词—— ```python # 改造后:只保留明确表达"申请/提交"意图的词 APPROVAL_PREFILTER_KEYWORDS = [ "申请", "审批", "提交", "表单", "走流程", "帮我申请", "我要申请", "需要申请", ] # 去掉:设备、电脑、邮箱、权限、软件、报修、变更等高频词 ``` **效果**:预过滤命中率从 ~60% 降到 ~15%,减少 75% 的无效 Dify 调用。 #### 根因 B:前后端双重调用未统一(架构层) **当前**:前端 `checkApprovalIntent()` + 后端 `_handle_routing()` 各自调 Dify。 **改造**:统一为后端单一入口——前端删除 `checkApprovalIntent()`,所有意图判断由后端在 `process_h5_ai_reply` 中统一处理,结果通过 WebSocket 推送。 #### 根因 C:12 种审批类型一次性分类(Prompt 层) **当前**:Dify 意图识别 Prompt 要求 LLM 一次性从 12 种类型中选择。 **改造**:改为两级分类—— ``` 第一级(粗分,4类): - 设备类(设备申请/资产变更/资产处置) - 账号权限类(账号权限/VPN/公共邮箱) - 软件应用类(软件服务/企业应用管理) - 服务支持类(故障报修/会议室/活动支持) 第二级(细分,仅当第一级命中后触发): - 在粗分结果范围内做精确匹配 - 配合 few-shot examples 强化边界 ``` --- ## 五、统一消息架构设计 ### 5.1 核心设计:一次推理,双通道交付 ``` 用户消息 → 后端 process_h5_ai_reply() │ ├─ 1. 统一意图识别(一次 Dify 调用,blocking) │ → 判定 intent_type: approval / it_consult / non_it_routing / chitchat │ ├─ 2. 根据意图分流: │ ├─ approval → Dify 主对话应用(输出 JSON: text + action) │ ├─ it_consult → Dify 主对话应用(输出 JSON: text + options) │ ├─ non_it_routing → 推送名片 + 正常 AI 回复 │ └─ chitchat → 正常 AI 回复 │ └─ 3. 后端解析 Dify JSON,同时发两条 WS 消息: ├─ WS: ai_reply → 聊天气泡(text + options) └─ WS: dynamic_recommend → 侧边栏推荐(action 卡片) ``` **关键**:两条 WS 消息由后端在同一时刻发出,文字和卡片零时间差到达,文字明确引用侧边栏内容(如"右侧已为您准备好入口")。 ### 5.2 Dify JSON 输出格式 #### 场景 1:审批意图(文字 + 卡片推荐) ```json { "text": "您想申请 VPN 账号?点击右侧卡片快速提交,一般 1-2 个工作日审批完成。", "action": { "type": "approval_card", "approval_type": "账号权限申请", "title": "VPN 账号申请", "template_id": "tpl_vpn_001", "confidence": 0.92 }, "options": null } ``` #### 场景 2:交互式排查(文字 + 选项) ```json { "text": "电脑蓝屏了?我来帮您排查。蓝屏时有错误代码吗?", "action": null, "options": [ {"label": "有错误代码", "value": "has_code"}, {"label": "没有", "value": "no_code"}, {"label": "不确定", "value": "unsure"} ] } ``` #### 场景 3:纯文字回复 ```json { "text": "好的,VPN 账号一般 1-2 个工作日审批完成,届时会通过企微通知您。", "action": null, "options": null } ``` ### 5.3 WS 消息格式 #### 聊天气泡消息 ```json { "type": "ai_reply", "data": { "message_id": "msg_xxx", "content": "您想申请 VPN 账号?点击右侧卡片快速提交...", "msg_type": "ai_structured", "extra_data": { "options": [ {"label": "有错误代码", "value": "has_code"} ] } } } ``` #### 侧边栏推荐消息 ```json { "type": "dynamic_recommend", "data": { "recommend_id": "rec_xxx", "card_type": "approval_card", "title": "VPN 账号申请", "description": "1-2 个工作日审批完成", "action": { "type": "approval_card", "approval_type": "账号权限申请", "template_id": "tpl_vpn_001" }, "confidence": 0.92 } } ``` ### 5.4 流式 vs 阻塞决策 | 方案 | 用户体验 | 实现复杂度 | 延迟感知 | |------|---------|-----------|---------| | A. 全阻塞 | 等 3-8 秒 → 文字+卡片同时出现 | 最简单 | 有等待感 | | **B. 阻塞 + 思考指示(推荐)** | 立即显示"正在思考..." → 3-8 秒后同时出现 | 简单 | 等待感降低 | | C. 混合流式 | Dify 先返回 text → 流式推文字 → 再返回 action | 最复杂 | 体验最好但 Dify 不支持分段 JSON | **选择方案 B**:阻塞模式 + "正在思考..."指示。理由: 1. 改造后消息变短,3-8 秒等待可接受 2. 文字和卡片同时出现,满足"发送时间一致" 3. 实现最简单,不需要改 Dify 的流式机制 4. 之前审批意图独立调 Dify 也是 blocking 模式,用户已习惯 ### 5.5 图片消息处理路径 ``` 员工发图片 (msg_type=image) │ ├─ 1. 后端 process_h5_ai_reply 检测到 msg_type=image │ ├─ 2. 调用 VisionService.analyze_screenshot(media_url) │ → Pillow 预处理 (resize + JPEG 压缩) │ → base64 编码 POST 到 Dify Vision Workflow (Qwen3-VL-8B) │ → 返回结构化描述 {description, confidence, metadata} │ ├─ 3. 将描述作为上下文前缀拼接到用户文字中: │ "[图片分析] 用户发送了一张截图,内容为:{description}\n用户消息:{text}" │ └─ 4. 传给 Dify 主对话应用 → 正常 JSON 输出流程 ``` **降级**:VisionService 失败时,回复"我收到了您的截图,但暂时无法识别内容,请描述一下您遇到的问题"。 ### 5.6 消息融合机制(5 秒窗口) ``` 消息到达 → 加入待处理队列 │ ├─ 队列为空 → 启动 5 秒计时器 ├─ 队列有消息 → 重置计时器(最多 3 次重置 = 15 秒上限) │ └─ 计时器到期 → 合并队列所有消息,一次性传给 Dify ├─ 图片 → VisionService 分析 → 描述文字 ├─ 文字 → 直接拼接 └─ 多张图片 → 依次分析,描述拼接 ``` **边界处理**: - 超时(15 秒上限):强制提交已有消息 - 多条文字:用换行符拼接 - 图片 + 文字:图片描述作为前缀,用户文字在后 --- ## 六、右边栏 v2 设计 ### 6.1 布局结构 ``` 右边栏 ├── ① 设备信息(默认折叠) │ ├── 当前设备:设备名 + IP(始终可见,一行) │ └── [展开] CPU / 内存 / 硬盘指标 │ └── 其他设备列表 │ ├── ② 自助诊断(默认折叠,手风琴互斥) │ ├── 标签页:网络联通 / 账号权限 / 设备硬件 │ └── 标签内容:检测结果 + 重新检测按钮 │ ├── ③ 统一标签页区域(始终可见,不参与手风琴) │ ├── 智能推荐(默认选中,带 Badge 红点) │ │ └── AI 动态推荐卡片(审批/操作入口) │ ├── 软件安装 │ │ └── 6 常用软件 + 安装状态 │ └── 资源权限 │ └── 6 个申请入口卡片 │ └── ④ 排队等待(独立,折叠态) ``` ### 6.2 手风琴交互逻辑 | 区域 | 默认状态 | 交互行为 | |------|---------|---------| | 设备信息 | 折叠(CPU/内存/硬盘隐藏) | 点击展开 → 显示硬件指标;再点或点击自助诊断 → 自动收回 | | 自助诊断 | 折叠 | 点击展开 → 显示标签页,默认选中"网络联通";再点或点击设备信息 → 自动收回 | | 统一标签页 | 智能推荐默认选中 | 始终可见,标签切换,不参与手风琴 | **互斥规则**:设备信息和自助诊断之间互斥——同一时间只有一个展开。 ### 6.3 智能推荐组件行为 - **空状态**:不显示(不占空间) - **有推荐**:淡入显示,最多 3 张卡片,每张带图标 + 标题 + 一句话描述 + 操作按钮 - **点击操作按钮**:触发企微审批 `wx.invoke('thirdPartyOpenPage', ...)` - **自动过期**:对话话题切换后,旧推荐淡出消失 - **手动关闭**:每张卡片右上角有 × 按钮 - **Badge 红点**:有新推荐时显示数量 ### 6.4 组件变更 | 原组件 | 变更 | 新组件 | |--------|------|--------| | `BasicInfoCard.vue` | 增加折叠/展开逻辑 | `BasicInfoCard.vue` (改造) | | `SelfDiagnosis.vue` | 增加标签页 + 折叠逻辑 | `SelfDiagnosis.vue` (改造) | | `SoftwareAndApply.vue` | 拆分为标签页形式 | `SoftwareInstall.vue` + `ResourcePermission.vue` | | 无 | 新增 | `DynamicRecommend.vue` (新建) | | `RightPanel.vue` | 整体布局重构 | `RightPanel.vue` (改造) | --- ## 七、实施计划 ### 7.1 阶段划分 > **实施状态**:Phase 1-6 全部完成,2026-07-13 01:38 生产部署验证通过。 > **v1.3 补充交付**:2026-07-25,AI 回复打字机逐字显示效果(前端 useTypewriter composable),H5 + Agent 双端已编译验证通过。 ``` Phase 1: 基础设施层(P0,前置条件)✅ 已完成 ├─ 1A. Dify System Prompt 改造(JSON 输出)✅ ├─ 1B. 后端统一消息处理(解析 JSON + 双 WS 推送)✅ ├─ 1C. 后端错误降级机制(30s 超时 / 15s still_thinking)✅ └─ 1D. Dify 节点精简(85→35)✅ Phase 2: 意图识别优化(P0,并行于 Phase 1)✅ 已完成 ├─ 2A. 收窄审批关键词预过滤(~40→~25 强意图词)✅ ├─ 2B. 统一前后端意图调用(删除前端 checkApprovalIntent)✅ └─ 2C. 两级审批类型分类 Prompt v4.0 ✅ Phase 3: 前端改造(P0-P1,依赖 Phase 1)✅ 已完成 ├─ 3A. WS 消息类型扩展(ai_thinking + dynamic_recommend + option_select)✅ ├─ 3B. 聊天气泡渲染改造(文字 + 选项按钮)✅ ├─ 3C. 右边栏 v2 重构(手风琴 + 智能推荐默认页)✅ └─ 3D. 选项回传链路 ✅ Phase 4: 图片处理(P0-P1,依赖 Phase 1)✅ 已完成 ├─ 4A. VisionService 接入 process_h5_ai_reply ✅ └─ 4B. 消息融合机制(5 秒窗口)✅ Phase 5: 坐席端适配(P2,依赖 Phase 1+3)✅ 已完成 ├─ 5A. 坐席端 WS 消息类型扩展 ✅ └─ 5B. 坐席端卡片渲染组件 ✅ Phase 6: 收尾(P3)✅ 已完成 ├─ 6A. 诊断闭环与 Dify 协调 ✅ └─ 6B. 性能优化与监控 ✅ ``` ### 7.2 各阶段详细任务 #### Phase 1: 基础设施层 | 任务 | 文件 | 具体改动 | |------|------|---------| | **1A. Dify Prompt 改造** | Dify 后台 + `docs/02-技术文档/实现配置/dify_unified_intent_prompt_v3.md` | 将主对话应用 System Prompt 改为输出 `{text, action, options}` JSON 格式;意图识别应用 Prompt 改为两级分类 | | **1B. 后端统一消息处理** | `backend/app/services/ai_service.py` | `get_reply_stream()` 改为 blocking 模式调用 Dify;解析返回 JSON,提取 `text`/`action`/`options`;返回结构化结果而非纯文本 | | | `backend/app/services/ai_handler.py` | `AIReplyResult` 增加 `action` 和 `options` 字段;`handle_message()` 返回结构化结果 | | | `backend/app/tasks/h5_ai_task.py` | `process_h5_ai_reply()` 解析结构化结果;同时发送 `ai_reply`(文字+选项)和 `dynamic_recommend`(卡片)两条 WS 消息 | | | `backend/app/api/websocket.py`(或对应 WS 管理文件) | 新增 `dynamic_recommend` WS 消息类型 | | **1C. 错误降级** | `backend/app/services/ai_service.py` | Dify 返回非 JSON → 降级为纯文本回复;15 秒超时 → "正在思考...";30 秒超时 → 建议转人工 | | **1D. Dify 节点精简** | Dify 后台 | 85→~35 节点,保留 RAGFlow 和 Vision 节点 | #### Phase 2: 意图识别优化 | 任务 | 文件 | 具体改动 | |------|------|---------| | **2A. 收窄审批关键词** | `backend/app/api/approval.py:213-242` | `APPROVAL_PREFILTER_KEYWORDS` 从 ~40 词缩减到 ~7 个强意图词 | | **2B. 统一前后端意图调用** | `backend/app/tasks/h5_ai_task.py` | 在 `process_h5_ai_reply` 中统一调用 Dify 意图识别,不再由前端独立调用 | | | `frontend-h5/src/stores/conversation.ts:458-460, 794-820` | **删除** `checkApprovalIntent()` 函数及其调用 | | | `frontend-h5/src/api/approval.ts`(如存在) | 移除或标记 `detectApprovalIntent` 为废弃 | | **2C. 两级审批分类 Prompt** | `docs/02-技术文档/实现配置/dify_unified_intent_prompt_v3.md` | Prompt 改为两级分类:第一级 4 类粗分,第二级细分到 12 类 | #### Phase 3: 前端改造 | 任务 | 文件 | 具体改动 | |------|------|---------| | **3A. WS 消息类型扩展** | `frontend-h5/src/composables/useH5WebSocket.ts:301-436` | `handleMessage()` 新增 `dynamic_recommend` 和 `option_select` case | | | `frontend-h5/src/stores/conversation.ts` | 新增 `handleDynamicRecommend()` 和 `sendOptionSelect()` 方法 | | **3B. 聊天气泡渲染** | `frontend-h5/src/components/` | 新增 `AiStructuredMessage.vue` 组件——渲染文字 + 选项按钮 | | | `MessageBubble.vue`(或对应渲染组件) | 增加 `msg_type === 'ai_structured'` 分支 | | **3C. 右边栏 v2** | `frontend-h5/src/components/assistant/RightPanel.vue` | 整体重构为手风琴布局 | | | 新建 `DynamicRecommend.vue` | 智能推荐组件——接收 WS `dynamic_recommend` 消息,渲染推荐卡片 | | | 改造 `SelfDiagnosis.vue` | 增加标签页 + 折叠逻辑 | | | 改造 `BasicInfoCard.vue` | 增加 CPU/内存/硬盘折叠/展开 | | | 新建 `SoftwareInstall.vue` + `ResourcePermission.vue` | 从 `SoftwareAndApply.vue` 拆分 | | **3D. 选项回传链路** | `frontend-h5/src/stores/conversation.ts` | `sendOptionSelect(optionValue)` → WS 发送 `option_select` 消息 | | | `backend/app/api/websocket.py` | 接收 `option_select` → 转化为 Dify user message → `get_reply_stream()` | #### Phase 4: 图片处理 | 任务 | 文件 | 具体改动 | |------|------|---------| | **4A. VisionService 接入** | `backend/app/tasks/h5_ai_task.py` | `process_h5_ai_reply()` 增加图片分支——检测 `msg_type=image` → 调 `vision_service.analyze_screenshot()` → 描述拼接到用户文字 | | | `backend/app/services/vision_service.py` | 确认接口完整,增加错误降级 | | **4B. 消息融合** | `backend/app/tasks/h5_ai_task.py` 或新建 `backend/app/services/message_fusion.py` | 5 秒窗口合并机制——图片+文字融合后一次性传给 Dify | #### Phase 5: 坐席端适配 | 任务 | 文件 | 具体改动 | |------|------|---------| | **5A. 坐席端 WS 扩展** | 坐席端 WS 处理器 | 新增 `ai_structured` 和 `dynamic_recommend` 消息类型处理 | | **5B. 坐席端卡片渲染** | 坐席端 Vue 组件 | 新增 AI 结构化消息渲染组件(文字+选项+卡片缩略) | #### Phase 6: 收尾 | 任务 | 文件 | 具体改动 | |------|------|---------| | **6A. 诊断闭环协调** | `backend/app/services/closing_service.py` | 明确信息锁定条件——Dify JSON 中增加 `diagnosis_stage` 字段 | | **6B. 性能优化** | Dify + 后端 | 节点精简后实测延迟;增加 Dify 响应时间监控 | ### 7.3 优先级与依赖关系 ``` Phase 1 (P0) ──┬── 1A. Dify Prompt ──────── 无依赖 ├── 1B. 后端消息处理 ──────── 依赖 1A ├── 1C. 错误降级 ────────── 依赖 1B └── 1D. Dify 节点精简 ────── 无依赖 Phase 2 (P0) ──┬── 2A. 收窄关键词 ────────── 无依赖 ├── 2B. 统一意图调用 ──────── 依赖 1B └── 2C. 两级分类 Prompt ────── 无依赖 Phase 3 (P0-P1) ─ 全部依赖 Phase 1 Phase 4 (P0-P1) ─ 依赖 Phase 1 Phase 5 (P2) ──── 依赖 Phase 1 + Phase 3 Phase 6 (P3) ──── 依赖 Phase 1-5 完成 ``` **可并行**:Phase 1 和 Phase 2 的部分任务可并行(1A/1D/2A/2C 无相互依赖)。 --- ## 八、风险与回滚 ### 8.1 风险评估 | 风险 | 概率 | 影响 | 缓解措施 | |------|------|------|---------| | Dify JSON 输出不稳定 | 高 | AI 回复无法解析 | 后端 JSON 解析失败 → 降级纯文本 | | 阻塞模式延迟过长 | 中 | 用户体验下降 | 15 秒超时 + "正在思考" 指示 | | VisionService 准确率不足 | 中 | 图片描述错误导致误导 | confidence < 0.6 时不注入描述 | | 前端删除 checkApprovalIntent 后审批功能中断 | 低 | 审批入口消失 | 后端统一推送 `dynamic_recommend` 确保卡片到达 | | Dify 节点精简导致能力缺失 | 中 | 知识覆盖减少 | 保留 RAGFlow + Vision 节点;分批删除,每批验证 | ### 8.2 回滚方案 | 回滚级别 | 触发条件 | 回滚操作 | |---------|---------|---------| | L1 | Dify JSON 输出频繁失败 | 后端关闭 JSON 解析,降级为纯文本模式 | | L2 | 意图识别准确率下降 | 恢复 `APPROVAL_PREFILTER_KEYWORDS` 原列表 | | L3 | 前端渲染异常 | 回退前端 dist 到上一版本 | | L4 | 整体功能不可用 | 回退后端 Docker 镜像 + 前端 dist + Dify 应用 DSL | ### 8.3 验证清单 > **验证状态**:2026-07-13 01:38 生产部署后全部通过。 每个 Phase 完成后需验证: - [x] Dify API 返回格式正确(JSON 可解析) - [x] 后端 WS 消息格式正确(`ai_reply` + `dynamic_recommend`) - [x] 前端聊天气泡渲染正确(文字 + 选项按钮) - [x] 前端侧边栏推荐渲染正确(卡片 + 操作按钮) - [x] 选项点击后回传正常(WS `option_select` → 新 AI 回复) - [x] 图片消息处理正常(VisionService 分析 → 描述注入) - [x] 错误降级正常(Dify 超时/非 JSON → 降级回复) - [x] 坐席端可见 AI 结构化消息 --- ## 九、附录 ### A. 审批类型两级分类映射 | 第一级(粗分) | 第二级(细分) | 关键词线索 | |--------------|-------------|-----------| | **设备类** | 设备申请 | 领用、借用、升级、新设备 | | | 资产变更确认 | 变更、确认 | | | 资产处置申请 | 外修、报废、退还 | | **账号权限类** | 账号权限申请 | VPN、外联、零信任 | | | 公共邮箱账号申请 | 公共邮箱、共享邮箱 | | | 终端设备网络准入 | 网络准入、终端准入 | | **软件应用类** | 软件服务申请 | 软件授权、商业软件 | | | 企业应用管理 | 应用开通、应用管理 | | **服务支持类** | 员工IT支持与故障报修 | 故障、报修、技术支持 | | | 会议室故障报修 | 会议室、投影仪 | | | 活动与会议技术支持 | 活动支持、技术保障 | | | 办公用品申请 | 办公用品、超额 | ### B. WS 消息类型完整清单(改造后) | 类型 | 方向 | 说明 | |------|------|------| | `new_message` | 后端→前端 | 新消息(坐席/用户消息) | | `ai_reply_chunk` | 后端→前端 | AI 流式回复 chunk(降级模式保留) | | `ai_reply` | 后端→前端 | AI 终态回复(含 text + options) | | `ai_reply_failed` | 后端→前端 | AI 回复失败 | | `ai_structured` | 后端→前端 | **新增** AI 结构化回复(文字+选项,阻塞模式) | | `dynamic_recommend` | 后端→前端 | **新增** 侧边栏动态推荐卡片 | | `option_select` | 前端→后端 | **新增** 用户选择选项 | | `queue_position_update` | 后端→前端 | 排队位置更新 | | `conversation_resolved` | 后端→前端 | 会话关闭 | | `pending_close_request` | 后端→前端 | 坐席结单请求 | | `quiz_diagnostic_answer` | 后端→前端 | 诊断答题 | | `participant_*` | 后端→前端 | 参与者变更 | | `pong` | 后端→前端 | 心跳响应 | ### C. 改造前后对比 | 维度 | 改造前 | 改造后 | |------|--------|--------| | Dify 节点数 | 85 | ~35 | | Dify 调用次数/消息 | 2-3 次(前端意图+后端路由+后端回复) | 1-2 次(统一意图+条件性回复) | | 审批关键词数 | ~40 | ~7 | | 审批类型分类 | 一级 12 类 | 两级 4→12 类 | | 卡片推送方式 | 前端异步独立推送 | 后端统一推送,与文字同步 | | 图片处理 | 不可用 | VisionService 接入 | | 消息格式 | 纯文本 | 结构化 JSON | | 右边栏布局 | 三独立模块 | 手风琴折叠 + 智能推荐默认页 | | 错误降级 | 无 | 三级降级(JSON→纯文本→转人工) | --- ## 十、边缘情况与隐式设计说明 > **版本**: v1.2 > **日期**: 2026-07-24 > **说明**: 补充文档中未明确说明的隐式设计决策及边缘情况 ### 10.1 选项消息的前端本地添加(已废弃) **原设计(有问题)**: ``` 用户点击选项 → 前端本地立即添加消息(message_id = option_select_${timestamp}) → 后端接收 WS option_select → 存储到数据库(message_id = UUID) → 后端广播 new_message → 前端收到后再添加一次 → 结果:同一条消息显示两次 ``` **问题**:前端和后端使用不同的 message_id,前端的去重机制(基于 message_id)失效。 **修复方案(v1.2)**: ``` 用户点击选项 → 前端只发送 WS,不本地添加消息 → 后端接收 → 存储到数据库(message_id = UUID) → 后端广播 new_message → 前端收到后添加 → 结果:消息来源唯一,无重复 ``` **隐式设计意图(原本未文档化)**: - 前端本地添加消息的目的是提升用户体验:用户点击选项后,立即在界面上显示消息,让用户知道"我已经发送了",避免等待网络响应的 3 秒延迟 - 但这个设计在文档中没有明确说明,属于「实现时随手加的」 ### 10.2 消息轮询机制 **设计**: - 前端使用 3 秒间隔的轮询(poll)作为 WebSocket 的补充 - 当 WebSocket 消息丢失或延迟时,轮询作为兜底机制确保消息不丢失 **考量**: - 轮询间隔 3 秒是体验和服务器压力的平衡点 - 过短会增加服务器压力,过长会影响体验 ### 10.3 Dify 工作流变更 **重要**:Dify 的工作流(Workflow)修改后,必须手动点击「发布」按钮,否则更改不生效。 **验证方法**: - 在 Dify 后台修改工作流后,保存 ≠ 发布 - 必须点击「发布」按钮,新版本才会生效 - 未发布时调用 API 会返回旧版本的结果 **故障表现**:修改了 Dify Prompt 或节点逻辑,但测试接口时发现行为未变化——通常是忘记点击「发布」。 ### 10.4 消息 ID 去重机制 **设计**: - 前端使用 `processedMessageIds` Set 记录已处理的消息 ID - `handleNewMessage()` 中检查消息是否已处理,避免重复渲染 **局限**: - 只基于 message_id 去重,不支持内容级别的去重 - 如果后端生成的 UUID 与前端生成的临时 ID 不同,去重会失效(参见 10.1) ### 10.5 WebSocket 与 HTTP 降级 **设计**: - 发送消息时优先使用 WebSocket - 如果 WebSocket 未连接(`sent = false`),降级为 HTTP API 调用 **代码位置**:`conversation.ts:1453-1460` --- ## 十一、变更日志 | 版本 | 日期 | 变更内容 | |------|------|---------| | v1.0 | 2026-07-13 | 初始版本 | | v1.1 | 2026-07-13 | 补充部署记录 | | v1.2 | 2026-07-24 | 新增第十章:边缘情况与隐式设计说明 | | v1.3 | 2026-07-25 | AI回复打字机逐字显示效果交付(前端 H5 + Agent 双端 useTypewriter composable) |