facc04aa65
本提交为 .git 对象库损坏后的重建提交,内容等价于原先三个本地提交 (5e2fd4c2 / 57a53c98 / 5d7e1873)的累积结果,未做任何额外改动。 一、docs 结构整改(整改 #14) 根因:重构时新结构为 untracked 文件,执行 git stash(未带 -u)未纳入, 随后 git reset 拉回 HEAD 旧 tracked 树,导致旧树复活、新旧两棵目录 树并存于 docs/,共 791 文件、双分类体系冲突。 修复动作: - b2 同名异主题文件改名迁移保全 9 个 - C 类 39 个孤立文件按主题正确归类 - A/B1 类 222 个重复文件删除(新结构已有内容副本) - 9 个旧独有空目录删除 - 270 处内部引用按 verified 映射改写 - 整改记录 #14 登记于 04-运维文档/部署运维 结果:docs 791 → 569 文件,顶层仅规范 8 类 + 治理文件,单树恢复。 残留:约 20 处指向从未存在文件的陈旧死链,归入独立文档卫生任务。 二、compose 双目录对齐(消除踩坑 A) - docker-compose.yml:nginx 前端挂载全部由根目录 frontend-*/dist 改为 src/frontend-*/dist(h5 / agent / admin / terminal) - docker-compose.dev.yml:dev 服务 build context 与卷同步改 src/ - 效果:本地 docker compose up 不再把根目录 stale dist 挂回, 与线上一致,分叉隐患消除(已 docker compose config 校验通过) 防复发铁律: - 重构须提交;仓库修复须 git stash -u 或先 commit - 新结构须 git add 并提交,避免再次 untracked 复活 - H5 改动只动 src/frontend-h5/,禁改根目录遗留 frontend-*/
1239 lines
58 KiB
Markdown
1239 lines
58 KiB
Markdown
# 增量设计:知识库迭代 — 开发任务分解
|
||
|
||
> **文档版本**:v1.0
|
||
> **创建日期**:2026-07-12
|
||
> **文档类型**:增量系统设计 + 开发任务分解
|
||
> **前置文档**:
|
||
> - 技术方案:`docs/02-技术文档/技术架构/增量设计-知识库迭代与痛点缓解-20260711.md`
|
||
> - 原型图:`docs/01-产品文档/07-知识库/知识库迭代-未实现功能原型设计-20260711.md`
|
||
> **技术栈**:FastAPI + SQLAlchemy + PostgreSQL + Redis + Neo4j / Vue3 + Element Plus + Vant4 + ECharts / Dify
|
||
|
||
---
|
||
|
||
## 目录
|
||
|
||
1. [设计概述](#1-设计概述)
|
||
2. [分诊交互 — 增量设计](#2-分诊交互--增量设计)
|
||
3. [拓扑预览 — 增量设计](#3-拓扑预览--增量设计)
|
||
4. [代答排除 — 增量设计](#4-代答排除--增量设计)
|
||
5. [数据库迁移方案](#5-数据库迁移方案)
|
||
6. [Dify 分诊应用配置规格](#6-dify-分诊应用配置规格)
|
||
7. [文件清单](#7-文件清单)
|
||
8. [开发任务列表](#8-开发任务列表)
|
||
9. [模块依赖关系图](#9-模块依赖关系图)
|
||
10. [共享知识(Shared Knowledge)](#10-共享知识shared-knowledge)
|
||
11. [待明确事项](#11-待明确事项)
|
||
|
||
---
|
||
|
||
## 1. 设计概述
|
||
|
||
### 1.1 背景与决策
|
||
|
||
基于产品经理产出的原型图文档与用户确认的 4 项关键决策 + 9 项次要决策,本文档对已有技术方案文档中三个未实现功能模块(分诊交互、拓扑预览、代答排除)进行增量设计补充,并分解为可执行的开发任务。
|
||
|
||
#### 4 项关键决策
|
||
|
||
| 决策项 | 确认结果 | 设计影响 |
|
||
|--------|---------|---------|
|
||
| Dify 应用 | **新建独立分诊应用** | 需产出 Dify 应用配置规格,不复用 Wingman |
|
||
| 坐席看板 | **本期完整实现** | 原型图有 2 页坐席端看板,需新增 7 个 API 端点 |
|
||
| 拓扑预览 | **只读图谱 + 搜索筛选** | 不实现节点编辑/添加/删除写操作,复用已有 GET /graph API |
|
||
| 代答排除 | **4 种匹配方式全做** | 关键词 + 正则 + 意图(Dify) + 分类(依赖分诊) |
|
||
|
||
#### 9 项次要决策
|
||
|
||
| # | 决策项 | 确认结果 |
|
||
|---|--------|---------|
|
||
| 1 | 分诊数据存储 | 新建 `triage_sessions` 表 |
|
||
| 2 | VIP 判断 | 本期不做,后续接入 eHR |
|
||
| 3 | 紧急度判断 | 关键词规则("紧急/马上/宕机/无法工作"→高) |
|
||
| 4 | 分诊超时 | 5 秒,超时自动转人工 |
|
||
| 5 | 专家模式 | 展示所有步骤预览 |
|
||
| 6 | 拓扑预览 | Relation 节点类型和 DERIVED_FROM 关系类型不新增,筛选中置灰 |
|
||
| 7 | 排除检查时机 | AI 回复前(消息接收到后、调用 Dify 前) |
|
||
| 8 | 导入规则 | 本期不实现 |
|
||
| 9 | 命中后动作 | 4 种全实现(转人工/转人工+上下文/仅提示/静默转人工) |
|
||
|
||
### 1.2 三模块范围对比
|
||
|
||
| 维度 | 分诊交互 | 拓扑预览 | 代答排除 |
|
||
|------|---------|---------|---------|
|
||
| 涉及端 | H5 + 坐席端 + Dify | 管理后台 | 管理后台 + H5(消息流) |
|
||
| 后端新增 | API 11 个 + 服务 2 个 | 无新增端点 | API 8 个 + 服务 1 个 + 匹配器 4 个 |
|
||
| 前端新增 | H5 组件修改 + 坐席页面 1 个 + 组件 3 个 | 管理后台页面 1 个 + 组件 3 个 | 管理后台页面 1 个 + 组件 2 个 |
|
||
| DB 新增表 | triage_sessions | 无 | exclusion_rules + exclusion_logs |
|
||
| Dify 依赖 | 新建独立分诊应用 | 无 | 复用审批意图识别 Dify 链路 |
|
||
| 可并行性 | 基础,其他依赖它 | **完全独立可并行** | 依赖分诊(分类匹配) |
|
||
|
||
---
|
||
|
||
## 2. 分诊交互 — 增量设计
|
||
|
||
### 2.1 范围差异说明
|
||
|
||
已有技术方案文档(§3.1)仅设计了 H5 端 4 个 API(start/step/skip/transfer),但用户决策"坐席看板本期完整实现",原型图有 2 页坐席端分诊看板。需补充:
|
||
|
||
1. **坐席端分诊看板 API**(7 个新增端点)
|
||
2. **Dify 分诊应用**配置规格(新建独立应用,不复用 Wingman)
|
||
3. **triage_sessions 表**结构设计
|
||
|
||
### 2.2 H5 端 API 设计(已有方案,补充 complete 端点)
|
||
|
||
已有技术方案定义了 4 个 H5 端 API,现补充第 5 个:
|
||
|
||
| 方法 | 路径 | 请求 | 响应 | 说明 |
|
||
|------|------|------|------|------|
|
||
| POST | `/api/h5/triage/start` | `{conversation_id, question}` | `{triage_id, steps: TriageStep[], total: int, confidence, urgency, suggested_route}` | 发起分诊 |
|
||
| POST | `/api/h5/triage/step` | `{triage_id, step_index, selected_label}` | `{next_step: TriageStep, collected_context: string[]}` | 提交步骤选择 |
|
||
| POST | `/api/h5/triage/skip` | `{triage_id, step_index}` | `{next_step: TriageStep}` | 跳过步骤 |
|
||
| POST | `/api/h5/triage/transfer` | `{triage_id, context: string[]}` | `{conversation_id, status: "waiting_agent"}` | 转人工 |
|
||
| POST | `/api/h5/triage/complete` | `{triage_id, context: string[]}` | `{reply: string, confidence: float}` | 分诊完成生成最终回复 |
|
||
|
||
> **设计变更**:`/start` 响应新增 `triage_id`(分诊会话唯一 ID),后续所有操作通过 `triage_id` 关联,而非 `conversation_id`。
|
||
|
||
### 2.3 坐席端分诊看板 API 设计(新增 7 个端点)
|
||
|
||
路由前缀:`/api/agent/triage`
|
||
|
||
| 方法 | 路径 | 请求 | 响应 | 说明 |
|
||
|------|------|------|------|------|
|
||
| GET | `/api/agent/triage/pending` | `?urgency=&problem_type=&page=&page_size=` | `{code:0, data:{total, items: TriageSession[]}}` | 待分诊列表(按紧急度排序) |
|
||
| GET | `/api/agent/triage/stats` | — | `{code:0, data:{pending_total, today_triaged, ai_self_count, human_count, auto_approval_count, avg_duration_sec}}` | 分诊看板统计概要 |
|
||
| GET | `/api/agent/triage/{triage_id}` | — | `{code:0, data: TriageSessionDetail}` | 分诊详情(含用户画像、问题描述、AI分析、已收集上下文) |
|
||
| POST | `/api/agent/triage/{triage_id}/route` | `{route_action: "ai_self"\|"human"\|"auto_approval"\|"skip", route_note?: string}` | `{code:0, data: TriageSession}` | 坐席路由操作(覆盖 AI 建议) |
|
||
| GET | `/api/agent/triage/history` | `?date_from=&date_to=&route_action=&page=&page_size=` | `{code:0, data:{total, items: TriageSession[]}}` | 已分诊历史列表 |
|
||
| GET | `/api/agent/triage/export` | `?date_from=&date_to=` | `文件流 (xlsx)` | 导出分诊记录 |
|
||
| POST | `/api/agent/triage/{triage_id}/exclude-options` | `{excluded_labels: string[], recommended_label?: string}` | `{code:0, data:{excluded: true}}` | 坐席排除/推荐分诊选项(WS 推送到 H5) |
|
||
|
||
### 2.4 triage_sessions 表结构
|
||
|
||
```sql
|
||
CREATE TABLE triage_sessions (
|
||
id VARCHAR(36) PRIMARY KEY DEFAULT gen_random_uuid()::text,
|
||
conversation_id VARCHAR(36) NOT NULL,
|
||
user_id VARCHAR(100) NOT NULL,
|
||
user_name VARCHAR(100),
|
||
user_dept VARCHAR(100),
|
||
user_level VARCHAR(20),
|
||
device_info VARCHAR(200),
|
||
request_title VARCHAR(200) NOT NULL,
|
||
request_content TEXT NOT NULL,
|
||
source VARCHAR(50) DEFAULT 'wecom_h5',
|
||
-- AI 分诊分析结果
|
||
problem_type VARCHAR(50),
|
||
problem_category VARCHAR(100),
|
||
confidence FLOAT,
|
||
urgency VARCHAR(20) DEFAULT 'medium', -- high/medium/low
|
||
suggested_route VARCHAR(50), -- ai_self/human/auto_approval
|
||
matched_knowledge VARCHAR(500),
|
||
match_score FLOAT,
|
||
context_tags JSON DEFAULT '[]',
|
||
-- 分诊步骤数据
|
||
triage_steps JSON DEFAULT '[]', -- [{question, options:[{label,probability}]}]
|
||
collected_context JSON DEFAULT '[]', -- ["Outlook 2019", "Windows 10"]
|
||
-- 状态与路由
|
||
status VARCHAR(30) DEFAULT 'pending', -- pending/triaging/routed/skipped/timeout
|
||
route_action VARCHAR(50), -- ai_self/human/auto_approval/skip
|
||
route_note TEXT,
|
||
operator_id VARCHAR(100),
|
||
operated_at TIMESTAMP,
|
||
-- 时间戳
|
||
created_at TIMESTAMP DEFAULT NOW(),
|
||
updated_at TIMESTAMP DEFAULT NOW()
|
||
);
|
||
|
||
-- 索引
|
||
CREATE INDEX idx_triage_status ON triage_sessions(status);
|
||
CREATE INDEX idx_triage_urgency ON triage_sessions(urgency);
|
||
CREATE INDEX idx_triage_conversation ON triage_sessions(conversation_id);
|
||
CREATE INDEX idx_triage_created ON triage_sessions(created_at);
|
||
CREATE INDEX idx_triage_user ON triage_sessions(user_id);
|
||
```
|
||
|
||
### 2.5 分诊主流程时序图
|
||
|
||
```mermaid
|
||
sequenceDiagram
|
||
participant H5 as H5 员工端
|
||
participant API as FastAPI 后端
|
||
participant TriSvc as TriageService
|
||
participant DifyTri as Dify 分诊应用
|
||
participant DB as PostgreSQL
|
||
participant AG as 坐席端看板
|
||
participant WS as WebSocket
|
||
|
||
rect rgb(255, 243, 224)
|
||
Note over H5,DB: 阶段1:发起分诊
|
||
H5->>API: POST /api/h5/triage/start {conversation_id, question}
|
||
API->>TriSvc: start_triage(conversation_id, question)
|
||
TriSvc->>DB: INSERT triage_sessions (status=triaging)
|
||
TriSvc->>DifyTri: 调用分诊 prompt(拆分问题为分步选择题)
|
||
alt Dify 5秒内响应
|
||
DifyTri-->>TriSvc: {steps[], confidence, urgency, suggested_route, problem_type}
|
||
TriSvc->>DB: UPDATE triage_sessions SET triage_steps, confidence, urgency, suggested_route
|
||
TriSvc-->>API: {triage_id, steps, total, confidence, urgency, suggested_route}
|
||
API-->>H5: {triage_id, steps, total, ...}
|
||
else Dify 超时(>5s)
|
||
TriSvc->>DB: UPDATE triage_sessions SET status=timeout
|
||
TriSvc-->>API: 超时,自动转人工
|
||
API-->>H5: 分诊超时,已转人工
|
||
end
|
||
end
|
||
|
||
rect rgb(227, 242, 253)
|
||
Note over H5,DB: 阶段2:分步选择 + 坐席协同
|
||
loop 每一步
|
||
H5->>API: POST /api/h5/triage/step {triage_id, step_index, selected_label}
|
||
API->>TriSvc: submit_step(triage_id, step_index, selected_label)
|
||
TriSvc->>DB: 记录 collected_context
|
||
TriSvc->>DifyTri: 根据选择动态调整后续步骤
|
||
DifyTri-->>TriSvc: next_step
|
||
TriSvc-->>API: {next_step, collected_context}
|
||
API-->>H5: {next_step, collected_context}
|
||
end
|
||
|
||
Note over AG: 坐席看板实时查看分诊进度
|
||
AG->>API: GET /api/agent/triage/pending
|
||
API-->>AG: 待分诊列表
|
||
AG->>API: GET /api/agent/triage/{triage_id}
|
||
API-->>AG: 分诊详情
|
||
|
||
opt 坐席排除选项
|
||
AG->>API: POST /api/agent/triage/{triage_id}/exclude-options {excluded_labels}
|
||
API->>WS: WS 推送 excluded_labels 到 H5
|
||
WS-->>H5: {type: "triage_exclude", excluded_labels}
|
||
H5->>H5: TriageCard.setExcludedOptions(labels)
|
||
end
|
||
end
|
||
|
||
rect rgb(232, 245, 233)
|
||
Note over H5,DB: 阶段3:分诊完成 / 转人工
|
||
alt 所有步骤完成
|
||
H5->>API: POST /api/h5/triage/complete {triage_id, context}
|
||
API->>TriSvc: complete_triage(triage_id, context)
|
||
TriSvc->>DifyTri: 根据收集的上下文生成最终回复
|
||
DifyTri-->>TriSvc: {reply, confidence}
|
||
TriSvc->>DB: UPDATE triage_sessions SET status=routed, route_action=ai_self
|
||
TriSvc-->>API: {reply, confidence}
|
||
API-->>H5: AI 回复
|
||
else 转人工
|
||
H5->>API: POST /api/h5/triage/transfer {triage_id, context}
|
||
API->>TriSvc: transfer_to_human(triage_id, context)
|
||
TriSvc->>DB: UPDATE triage_sessions SET status=routed, route_action=human
|
||
TriSvc-->>API: 转人工成功
|
||
API-->>H5: 已转人工
|
||
end
|
||
end
|
||
```
|
||
|
||
### 2.6 坐席端看板组件设计
|
||
|
||
| 组件 | 文件路径 | 说明 |
|
||
|------|---------|------|
|
||
| 分诊看板主页面 | `frontend-agent/src/views/TriageDashboard.vue` | 左列表+右详情双栏布局 |
|
||
| 统计概要栏 | `frontend-agent/src/components/triage/TriageStatsBar.vue` | 6 项统计卡片 |
|
||
| 待分诊列表 | `frontend-agent/src/components/triage/TriagePendingList.vue` | 紧急度排序+筛选 |
|
||
| 分诊详情面板 | `frontend-agent/src/components/triage/TriageDetailPanel.vue` | 用户画像+问题描述+AI分析+上下文+路由操作 |
|
||
| 路由操作栏 | 内嵌于 TriageDetailPanel | 4 个路由按钮 + 备注 + 排除选项 |
|
||
|
||
### 2.7 紧急度判断规则(决策 #3)
|
||
|
||
```python
|
||
# 关键词规则,在 TriageService 中实现
|
||
URGENCY_HIGH_KEYWORDS = ["紧急", "马上", "宕机", "无法工作", "崩溃", "死机", "蓝屏"]
|
||
URGENCY_MEDIUM_KEYWORDS = ["报错", "失败", "连不上", "打不开", "不能用"]
|
||
|
||
def determine_urgency(question: str, confidence: float) -> str:
|
||
"""根据关键词 + 置信度判断紧急度。"""
|
||
if any(kw in question for kw in URGENCY_HIGH_KEYWORDS):
|
||
return "high"
|
||
if confidence < 0.5:
|
||
return "high" # 低置信也视为紧急
|
||
if any(kw in question for kw in URGENCY_MEDIUM_KEYWORDS):
|
||
return "medium"
|
||
return "low"
|
||
```
|
||
|
||
### 2.8 分诊超时处理(决策 #4)
|
||
|
||
```python
|
||
# TriageService.start_triage 中
|
||
import asyncio
|
||
|
||
try:
|
||
result = await asyncio.wait_for(
|
||
dify_triage_service.analyze(question, context),
|
||
timeout=5.0 # 5秒超时
|
||
)
|
||
except asyncio.TimeoutError:
|
||
# 超时自动转人工
|
||
await self._transfer_to_human_on_timeout(triage_id)
|
||
return {"status": "timeout", "message": "分诊超时,已自动转人工"}
|
||
```
|
||
|
||
---
|
||
|
||
## 3. 拓扑预览 — 增量设计
|
||
|
||
### 3.1 范围确认
|
||
|
||
| 维度 | 确认结果 |
|
||
|------|---------|
|
||
| 交互模式 | **只读** — 不实现节点编辑/添加/删除写操作 |
|
||
| 后端端点 | **不新增** — 复用已有 `GET /api/admin/knowledge-iteration/graph?issue_name=xxx` |
|
||
| 节点类型 | Issue + Action(Relation 节点类型和 DERIVED_FROM 关系类型不新增,筛选中置灰) |
|
||
| 布局 | 力导向图(ECharts graph),层次树布局预留但不实现 |
|
||
| 搜索 | 节点名称模糊搜索 + 高亮定位 |
|
||
| 筛选 | 节点类型(Issue/Action)+ 关系类型(LEADS_TO/RELATES_TO/CAN_JUMP_TO) |
|
||
|
||
### 3.2 前端组件清单
|
||
|
||
| 组件 | 文件路径 | 说明 |
|
||
|------|---------|------|
|
||
| 拓扑预览主页面 | `frontend-admin/src/views/TopologyPreview.vue` | 页面容器,统计栏+工具栏+画布+详情面板 |
|
||
| 图谱画布 | `frontend-admin/src/components/topology/GraphCanvas.vue` | ECharts 力导向图渲染 + 搜索高亮 + 缩放控制 |
|
||
| 节点详情面板 | `frontend-admin/src/components/topology/NodeDetailPanel.vue` | 节点属性 + 关联关系 + 高亮路径(只读) |
|
||
| 工具栏 | `frontend-admin/src/components/topology/GraphToolbar.vue` | 搜索框 + 节点类型筛选 + 关系类型筛选 + 缩放控制 |
|
||
| API 封装 | `frontend-admin/src/api/topology.ts` | 封装 GET /graph 调用 |
|
||
|
||
### 3.3 数据流
|
||
|
||
```mermaid
|
||
sequenceDiagram
|
||
participant Page as TopologyPreview.vue
|
||
participant API as topology.ts
|
||
participant Backend as FastAPI
|
||
participant Neo4j as Neo4jClient
|
||
|
||
Page->>API: fetchGraph({issue_name?, limit?})
|
||
API->>Backend: GET /api/admin/knowledge-iteration/graph?issue_name=xxx&limit=200
|
||
Backend->>Neo4j: query_full_graph() 或 query_issue_subgraph()
|
||
Neo4j-->>Backend: {nodes:[], links:[]}
|
||
Backend-->>API: {code:0, data:{nodes, links}}
|
||
API-->>Page: graphData
|
||
|
||
Page->>Page: ECharts setOption(graphData)
|
||
Note over Page: 力导向渲染,节点按类型着色
|
||
|
||
opt 用户搜索节点
|
||
Page->>Page: 前端过滤 nodes.filter(n => n.name.includes(keyword))
|
||
Page->>Page: 匹配节点高亮脉冲,非匹配变暗
|
||
Page->>Page: 右侧浮层展示搜索结果
|
||
end
|
||
|
||
opt 用户点击节点
|
||
Page->>Page: 选中节点高亮 + 路径高亮
|
||
Page->>Page: NodeDetailPanel 显示详情(从已有数据取,不发请求)
|
||
end
|
||
|
||
opt 用户筛选节点类型
|
||
Page->>Page: ECharts legend 过滤 Issue/Action
|
||
Note over Page: Relation 类型 checkbox 置灰(disabled)
|
||
end
|
||
```
|
||
|
||
### 3.4 ECharts 配置要点
|
||
|
||
```typescript
|
||
// GraphCanvas.vue 核心配置
|
||
const chartOption = {
|
||
tooltip: {
|
||
formatter: (params) => `${params.data.name} (${params.data.type})`
|
||
},
|
||
legend: {
|
||
data: [
|
||
{ name: 'Issue', itemStyle: { color: '#07C160' } },
|
||
{ name: 'Action', itemStyle: { color: '#f59e0b' } },
|
||
{ name: 'Relation', itemStyle: { color: '#9ca3af' }, itemStyle: { opacity: 0.3 } } // 置灰
|
||
]
|
||
},
|
||
series: [{
|
||
type: 'graph',
|
||
layout: 'force',
|
||
roam: true, // 支持缩放平移
|
||
focusNodeAdjacency: true, // 悬停高亮相邻
|
||
force: {
|
||
repulsion: 300,
|
||
gravity: 0.1,
|
||
edgeLength: [100, 250]
|
||
},
|
||
categories: [
|
||
{ name: 'Issue' }, // 绿色 #07C160, symbolSize=28
|
||
{ name: 'Action' }, // 橙色 #f59e0b, symbolSize=22
|
||
],
|
||
data: nodes.map(n => ({
|
||
id: n.id,
|
||
name: n.name,
|
||
category: n.type === 'issue' ? 0 : 1,
|
||
symbolSize: n.type === 'issue' ? 28 : 22,
|
||
itemStyle: { color: n.type === 'issue' ? '#07C160' : '#f59e0b' }
|
||
})),
|
||
links: links.map(l => ({
|
||
source: l.source,
|
||
target: l.target,
|
||
lineStyle: {
|
||
color: '#c0c4cc',
|
||
width: Math.max(0.5, (l.weight || 1) * 1.5),
|
||
curveness: 0.3
|
||
}
|
||
}))
|
||
}]
|
||
}
|
||
```
|
||
|
||
### 3.5 搜索高亮实现
|
||
|
||
搜索在前端完成(不发后端请求),对已加载的节点数据做名称模糊匹配:
|
||
|
||
```typescript
|
||
function highlightSearch(keyword: string) {
|
||
if (!keyword) {
|
||
// 恢复所有节点正常样式
|
||
restoreAllNodes()
|
||
return
|
||
}
|
||
const matched = nodes.filter(n => n.name.includes(keyword))
|
||
// 匹配节点:蓝色描边 + 脉冲动画
|
||
// 非匹配节点:降低透明度
|
||
chart.setOption({
|
||
series: [{
|
||
data: nodes.map(n => ({
|
||
...n,
|
||
itemStyle: matched.includes(n)
|
||
? { borderColor: '#3b82f6', borderWidth: 3, shadowBlur: 10, shadowColor: '#3b82f6' }
|
||
: { opacity: 0.15 }
|
||
}))
|
||
}]
|
||
})
|
||
// 右侧浮层展示搜索结果
|
||
searchResults.value = matched
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 4. 代答排除 — 增量设计
|
||
|
||
### 4.1 架构设计:策略模式 + 责任链
|
||
|
||
代答排除采用 **策略模式** 定义 4 种匹配器,**责任链模式** 按优先级依次检查规则。
|
||
|
||
```mermaid
|
||
graph TB
|
||
subgraph "代答排除匹配引擎"
|
||
Engine[ExclusionService<br/>匹配引擎入口]
|
||
Chain[ExclusionChain<br/>责任链调度]
|
||
|
||
subgraph "策略模式 - 4种匹配器"
|
||
M1[KeywordMatcher<br/>关键词匹配]
|
||
M2[RegexMatcher<br/>正则匹配]
|
||
M3[IntentMatcher<br/>意图匹配]
|
||
M4[CategoryMatcher<br/>分类排除]
|
||
end
|
||
|
||
Engine --> Chain
|
||
Chain --> M1
|
||
Chain --> M2
|
||
Chain --> M3
|
||
Chain --> M4
|
||
end
|
||
|
||
subgraph "外部依赖"
|
||
Dify[Dify 意图识别<br/>复用审批意图链路]
|
||
TriageDB[triage_sessions<br/>分诊结果]
|
||
RuleDB[(exclusion_rules)]
|
||
LogDB[(exclusion_logs)]
|
||
end
|
||
|
||
M3 -->|调用| Dify
|
||
M4 -->|查询| TriageDB
|
||
Engine -->|读取规则| RuleDB
|
||
Engine -->|记录命中| LogDB
|
||
|
||
style M1 fill:#dbeafe,stroke:#3b82f6
|
||
style M2 fill:#fce7f3,stroke:#ec4899
|
||
style M3 fill:#ede9fe,stroke:#8b5cf6
|
||
style M4 fill:#dcfce7,stroke:#07C160
|
||
```
|
||
|
||
### 4.2 匹配器接口设计
|
||
|
||
```python
|
||
# backend/app/services/matchers/base.py
|
||
from abc import ABC, abstractmethod
|
||
from dataclasses import dataclass
|
||
from typing import Optional
|
||
|
||
@dataclass
|
||
class MatchResult:
|
||
"""匹配结果。"""
|
||
matched: bool
|
||
matched_detail: str = "" # 命中的关键词/正则/意图/分类
|
||
match_position: str = "" # 匹配位置(用于测试展示)
|
||
|
||
class BaseMatcher(ABC):
|
||
"""匹配器基类 — 策略模式接口。"""
|
||
|
||
@abstractmethod
|
||
async def match(self, message: str, condition: str, context: dict = None) -> MatchResult:
|
||
"""检查消息是否匹配规则条件。
|
||
|
||
Args:
|
||
message: 用户消息文本
|
||
condition: 匹配条件(关键词列表/正则表达式/意图ID列表/分类名称列表)
|
||
context: 上下文(含 conversation_id, triage_result 等)
|
||
|
||
Returns:
|
||
MatchResult
|
||
"""
|
||
...
|
||
```
|
||
|
||
### 4.3 四种匹配器实现要点
|
||
|
||
| 匹配器 | 文件 | 匹配逻辑 | 外部依赖 |
|
||
|--------|------|---------|---------|
|
||
| KeywordMatcher | `matchers/keyword_matcher.py` | 逗号分隔关键词列表,消息包含任一即命中 | 无 |
|
||
| RegexMatcher | `matchers/regex_matcher.py` | Python `re.search(pattern, message)`,正则编译缓存 | 无 |
|
||
| IntentMatcher | `matchers/intent_matcher.py` | 调用 Dify 意图识别 API,检查返回意图是否在排除列表中 | Dify(复用审批意图链路 `approval_dify_base_url`) |
|
||
| CategoryMatcher | `matchers/category_matcher.py` | 查询 `triage_sessions` 表获取分诊结果的 `problem_category`,检查是否在排除分类列表中 | triage_sessions 表(依赖分诊模块) |
|
||
|
||
### 4.4 意图匹配与 Dify 对接方案
|
||
|
||
**复用现有审批意图识别 Dify 链路**,不新建 Dify 应用:
|
||
|
||
```python
|
||
# backend/app/services/matchers/intent_matcher.py
|
||
from app.config import settings
|
||
import httpx
|
||
|
||
class IntentMatcher(BaseMatcher):
|
||
async def match(self, message: str, condition: str, context: dict = None) -> MatchResult:
|
||
# 1. 调用 Dify 意图识别(复用 approval_dify_base_url)
|
||
intent = await self._recognize_intent(message)
|
||
|
||
# 2. 检查意图是否在排除列表中
|
||
excluded_intents = [s.strip() for s in condition.split(",")]
|
||
if intent in excluded_intents:
|
||
return MatchResult(matched=True, matched_detail=f"意图: {intent}")
|
||
return MatchResult(matched=False)
|
||
|
||
async def _recognize_intent(self, message: str) -> str:
|
||
"""调用 Dify 意图识别 API。"""
|
||
async with httpx.AsyncClient(timeout=settings.approval_dify_timeout) as client:
|
||
resp = await client.post(
|
||
f"{settings.approval_dify_base_url}/v1/chat/completions",
|
||
headers={"Authorization": f"Bearer {settings.approval_dify_api_key}"},
|
||
json={
|
||
"model": "intent-recognition",
|
||
"messages": [
|
||
{"role": "system", "content": "识别用户消息的意图类别,输出意图ID。"},
|
||
{"role": "user", "content": message}
|
||
],
|
||
"temperature": 0.1
|
||
}
|
||
)
|
||
return resp.json()["choices"][0]["message"]["content"].strip()
|
||
```
|
||
|
||
### 4.5 分类排除与分诊的依赖关系
|
||
|
||
CategoryMatcher 依赖分诊结果:
|
||
|
||
```python
|
||
# backend/app/services/matchers/category_matcher.py
|
||
class CategoryMatcher(BaseMatcher):
|
||
async def match(self, message: str, condition: str, context: dict = None) -> MatchResult:
|
||
conversation_id = context.get("conversation_id") if context else None
|
||
if not conversation_id:
|
||
return MatchResult(matched=False) # 无会话上下文,跳过分类匹配
|
||
|
||
# 查询 triage_sessions 获取分诊结果
|
||
from app.models.triage_session import TriageSession
|
||
result = await db.execute(
|
||
select(TriageSession).where(
|
||
TriageSession.conversation_id == conversation_id
|
||
).order_by(TriageSession.created_at.desc()).limit(1)
|
||
)
|
||
triage = result.scalar_one_or_none()
|
||
if not triage or not triage.problem_category:
|
||
return MatchResult(matched=False) # 无分诊结果,跳过
|
||
|
||
excluded_categories = [s.strip() for s in condition.split(",")]
|
||
if triage.problem_category in excluded_categories:
|
||
return MatchResult(
|
||
matched=True,
|
||
matched_detail=f"分类: {triage.problem_category}"
|
||
)
|
||
return MatchResult(matched=False)
|
||
```
|
||
|
||
> **设计说明**:分类排除是软依赖——如果分诊模块尚未运行或无分诊结果,CategoryMatcher 返回未命中,不影响其他匹配器执行。
|
||
|
||
### 4.6 排除检查时机与消息流集成(决策 #7)
|
||
|
||
排除检查发生在 **AI 回复前**(消息接收到后、调用 Dify 前),嵌入 `AIHandler` 处理链:
|
||
|
||
```mermaid
|
||
flowchart TD
|
||
A[用户发送消息] --> B[AIHandler.handle_message]
|
||
B --> C{打招呼/呼叫人工?}
|
||
C -->|是| D[引导话术]
|
||
C -->|否| E[代答排除检查]
|
||
|
||
E --> F[ExclusionService.check_exclusions]
|
||
F --> G{命中排除规则?}
|
||
G -->|是| H[执行命中后动作]
|
||
G -->|否| I[正常 AI 回复流程]
|
||
|
||
H --> J{action_type}
|
||
J -->|transfer_human| K[转人工坐席]
|
||
J -->|transfer_human_with_context| L[携带上下文转人工]
|
||
J -->|prompt_transfer| M[展示提示语 等待确认]
|
||
J -->|silent_transfer| N[静默转人工]
|
||
|
||
K --> O[记录 exclusion_logs]
|
||
L --> O
|
||
M --> O
|
||
N --> O
|
||
O --> P[更新 exclusion_rules.hit_count]
|
||
|
||
I --> Q[调用 Dify AI 生成回复]
|
||
Q --> R[返回 AI 回复]
|
||
```
|
||
|
||
### 4.7 命中后动作实现(决策 #9 — 4 种全实现)
|
||
|
||
| 动作类型 | 标识 | 实现方式 | 员工体验 |
|
||
|---------|------|---------|---------|
|
||
| 转人工坐席 | `transfer_human` | 会话状态→waiting_agent,推送给坐席队列 | 看到"已转接人工"提示 |
|
||
| 转人工+上下文 | `transfer_human_with_context` | 同上 + 附带 collected_context 快照 | 看到"已转接人工"提示 |
|
||
| 仅提示转人工 | `prompt_transfer` | 返回 transfer_message,等待用户确认 | 看到提示语,可选择确认转人工 |
|
||
| 静默转人工 | `silent_transfer` | 会话状态→waiting_agent,不提示用户 | 无感知,直接进入人工队列 |
|
||
|
||
### 4.8 DB 表设计
|
||
|
||
**exclusion_rules 表**:
|
||
|
||
```sql
|
||
CREATE TABLE exclusion_rules (
|
||
id VARCHAR(36) PRIMARY KEY DEFAULT gen_random_uuid()::text,
|
||
rule_name VARCHAR(200) NOT NULL UNIQUE,
|
||
rule_description TEXT,
|
||
priority VARCHAR(5) NOT NULL DEFAULT 'P2', -- P0/P1/P2/P3
|
||
match_type VARCHAR(20) NOT NULL, -- keyword/regex/intent/category
|
||
match_condition TEXT NOT NULL, -- 关键词列表/正则/意图ID列表/分类名称列表
|
||
match_scope JSON NOT NULL DEFAULT '["ai_auto_reply"]',
|
||
action_type VARCHAR(50) NOT NULL DEFAULT 'transfer_human',
|
||
transfer_message TEXT,
|
||
status VARCHAR(10) NOT NULL DEFAULT 'enabled', -- enabled/disabled
|
||
hit_count INTEGER DEFAULT 0,
|
||
created_by VARCHAR(100) NOT NULL,
|
||
created_at TIMESTAMP DEFAULT NOW(),
|
||
updated_at TIMESTAMP DEFAULT NOW()
|
||
);
|
||
|
||
CREATE INDEX idx_exclusion_rules_status ON exclusion_rules(status);
|
||
CREATE INDEX idx_exclusion_rules_priority ON exclusion_rules(priority);
|
||
CREATE INDEX idx_exclusion_rules_match_type ON exclusion_rules(match_type);
|
||
```
|
||
|
||
**exclusion_logs 表**:
|
||
|
||
```sql
|
||
CREATE TABLE exclusion_logs (
|
||
id VARCHAR(36) PRIMARY KEY DEFAULT gen_random_uuid()::text,
|
||
rule_id VARCHAR(36) NOT NULL,
|
||
rule_name VARCHAR(200),
|
||
conversation_id VARCHAR(36),
|
||
user_id VARCHAR(100),
|
||
message_content TEXT,
|
||
match_type VARCHAR(20),
|
||
matched_detail TEXT,
|
||
action_type VARCHAR(50),
|
||
action_result VARCHAR(50) DEFAULT 'success', -- success/failed
|
||
created_at TIMESTAMP DEFAULT NOW()
|
||
);
|
||
|
||
CREATE INDEX idx_exclusion_logs_rule ON exclusion_logs(rule_id);
|
||
CREATE INDEX idx_exclusion_logs_created ON exclusion_logs(created_at);
|
||
CREATE INDEX idx_exclusion_logs_conversation ON exclusion_logs(conversation_id);
|
||
```
|
||
|
||
### 4.9 管理后台 API 设计
|
||
|
||
路由前缀:`/api/admin/exclusion-rules`
|
||
|
||
| 方法 | 路径 | 请求 | 响应 | 说明 |
|
||
|------|------|------|------|------|
|
||
| GET | `/api/admin/exclusion-rules` | `?status=&match_type=&priority=&keyword=&page=&page_size=` | `{code:0, data:{total, items}}` | 规则列表(分页+筛选) |
|
||
| POST | `/api/admin/exclusion-rules` | `ExclusionRuleCreate` | `{code:0, data: ExclusionRule}` | 新建规则 |
|
||
| GET | `/api/admin/exclusion-rules/{id}` | — | `{code:0, data: ExclusionRule}` | 规则详情 |
|
||
| PUT | `/api/admin/exclusion-rules/{id}` | `ExclusionRuleUpdate` | `{code:0, data: ExclusionRule}` | 编辑规则 |
|
||
| DELETE | `/api/admin/exclusion-rules/{id}` | — | `{code:0, message:"删除成功"}` | 删除规则 |
|
||
| POST | `/api/admin/exclusion-rules/{id}/toggle` | `{status: "enabled"\|"disabled"}` | `{code:0, data: ExclusionRule}` | 启用/停用 |
|
||
| POST | `/api/admin/exclusion-rules/test` | `{message: string, rule_id?: string}` | `{code:0, data:{matched, matched_detail, rule_name, action_type}}` | 测试匹配 |
|
||
| GET | `/api/admin/exclusion-rules/stats` | — | `{code:0, data:{enabled_count, disabled_count, monthly_hits, monthly_transfers}}` | 统计概要 |
|
||
|
||
---
|
||
|
||
## 5. 数据库迁移方案
|
||
|
||
### 5.1 迁移文件
|
||
|
||
新增迁移版本:`045_triage_exclusion_tables.py`
|
||
|
||
```python
|
||
"""triage_sessions + exclusion_rules + exclusion_logs
|
||
|
||
Revision ID: 045_triage_exclusion
|
||
Revises: 044_automation
|
||
Create Date: 2026-07-12
|
||
"""
|
||
from alembic import op
|
||
import sqlalchemy as sa
|
||
|
||
revision = "045_triage_exclusion"
|
||
down_revision = "044_automation"
|
||
|
||
def upgrade():
|
||
# 1. triage_sessions 表
|
||
op.create_table(
|
||
"triage_sessions",
|
||
sa.Column("id", sa.String(36), primary_key=True),
|
||
sa.Column("conversation_id", sa.String(36), nullable=False),
|
||
sa.Column("user_id", sa.String(100), nullable=False),
|
||
sa.Column("user_name", sa.String(100)),
|
||
sa.Column("user_dept", sa.String(100)),
|
||
sa.Column("user_level", sa.String(20)),
|
||
sa.Column("device_info", sa.String(200)),
|
||
sa.Column("request_title", sa.String(200), nullable=False),
|
||
sa.Column("request_content", sa.Text, nullable=False),
|
||
sa.Column("source", sa.String(50), server_default="wecom_h5"),
|
||
sa.Column("problem_type", sa.String(50)),
|
||
sa.Column("problem_category", sa.String(100)),
|
||
sa.Column("confidence", sa.Float),
|
||
sa.Column("urgency", sa.String(20), server_default="medium"),
|
||
sa.Column("suggested_route", sa.String(50)),
|
||
sa.Column("matched_knowledge", sa.String(500)),
|
||
sa.Column("match_score", sa.Float),
|
||
sa.Column("context_tags", sa.JSON, server_default="[]"),
|
||
sa.Column("triage_steps", sa.JSON, server_default="[]"),
|
||
sa.Column("collected_context", sa.JSON, server_default="[]"),
|
||
sa.Column("status", sa.String(30), server_default="pending"),
|
||
sa.Column("route_action", sa.String(50)),
|
||
sa.Column("route_note", sa.Text),
|
||
sa.Column("operator_id", sa.String(100)),
|
||
sa.Column("operated_at", sa.DateTime),
|
||
sa.Column("created_at", sa.DateTime, server_default=sa.func.now()),
|
||
sa.Column("updated_at", sa.DateTime, server_default=sa.func.now()),
|
||
)
|
||
op.create_index("idx_triage_status", "triage_sessions", ["status"])
|
||
op.create_index("idx_triage_urgency", "triage_sessions", ["urgency"])
|
||
op.create_index("idx_triage_conversation", "triage_sessions", ["conversation_id"])
|
||
op.create_index("idx_triage_created", "triage_sessions", ["created_at"])
|
||
op.create_index("idx_triage_user", "triage_sessions", ["user_id"])
|
||
|
||
# 2. exclusion_rules 表
|
||
op.create_table(
|
||
"exclusion_rules",
|
||
sa.Column("id", sa.String(36), primary_key=True),
|
||
sa.Column("rule_name", sa.String(200), nullable=False, unique=True),
|
||
sa.Column("rule_description", sa.Text),
|
||
sa.Column("priority", sa.String(5), nullable=False, server_default="P2"),
|
||
sa.Column("match_type", sa.String(20), nullable=False),
|
||
sa.Column("match_condition", sa.Text, nullable=False),
|
||
sa.Column("match_scope", sa.JSON, nullable=False, server_default='["ai_auto_reply"]'),
|
||
sa.Column("action_type", sa.String(50), nullable=False, server_default="transfer_human"),
|
||
sa.Column("transfer_message", sa.Text),
|
||
sa.Column("status", sa.String(10), nullable=False, server_default="enabled"),
|
||
sa.Column("hit_count", sa.Integer, server_default="0"),
|
||
sa.Column("created_by", sa.String(100), nullable=False),
|
||
sa.Column("created_at", sa.DateTime, server_default=sa.func.now()),
|
||
sa.Column("updated_at", sa.DateTime, server_default=sa.func.now()),
|
||
)
|
||
op.create_index("idx_exclusion_rules_status", "exclusion_rules", ["status"])
|
||
op.create_index("idx_exclusion_rules_priority", "exclusion_rules", ["priority"])
|
||
op.create_index("idx_exclusion_rules_match_type", "exclusion_rules", ["match_type"])
|
||
|
||
# 3. exclusion_logs 表
|
||
op.create_table(
|
||
"exclusion_logs",
|
||
sa.Column("id", sa.String(36), primary_key=True),
|
||
sa.Column("rule_id", sa.String(36), nullable=False),
|
||
sa.Column("rule_name", sa.String(200)),
|
||
sa.Column("conversation_id", sa.String(36)),
|
||
sa.Column("user_id", sa.String(100)),
|
||
sa.Column("message_content", sa.Text),
|
||
sa.Column("match_type", sa.String(20)),
|
||
sa.Column("matched_detail", sa.Text),
|
||
sa.Column("action_type", sa.String(50)),
|
||
sa.Column("action_result", sa.String(50), server_default="success"),
|
||
sa.Column("created_at", sa.DateTime, server_default=sa.func.now()),
|
||
)
|
||
op.create_index("idx_exclusion_logs_rule", "exclusion_logs", ["rule_id"])
|
||
op.create_index("idx_exclusion_logs_created", "exclusion_logs", ["created_at"])
|
||
op.create_index("idx_exclusion_logs_conversation", "exclusion_logs", ["conversation_id"])
|
||
|
||
def downgrade():
|
||
op.drop_table("exclusion_logs")
|
||
op.drop_table("exclusion_rules")
|
||
op.drop_table("triage_sessions")
|
||
```
|
||
|
||
### 5.2 迁移顺序
|
||
|
||
```
|
||
044_automation (已有)
|
||
↓
|
||
045_triage_exclusion (本次新增 — 3 张表一次性创建)
|
||
```
|
||
|
||
> 三张表在一个迁移中创建,因为 exclusion_logs 外键引用 exclusion_rules,triage_sessions 被 exclusion 的 CategoryMatcher 引用,三者紧密关联。
|
||
|
||
---
|
||
|
||
## 6. Dify 分诊应用配置规格
|
||
|
||
### 6.1 应用基本信息
|
||
|
||
| 配置项 | 值 |
|
||
|--------|-----|
|
||
| 应用名称 | IT-智能分诊引擎 |
|
||
| 应用类型 | 聊天助手 (Chat Assistant) |
|
||
| 模型 | 与 Wingman 相同的 LLM(如 Qwen2.5-72B) |
|
||
| 温度 | 0.3(偏确定性输出) |
|
||
| 最大 token | 2000 |
|
||
| 超时 | 5 秒(后端 asyncio.wait_for 控制) |
|
||
|
||
### 6.2 输入参数 Schema
|
||
|
||
```json
|
||
{
|
||
"conversation_id": "string — 会话ID",
|
||
"question": "string — 员工问题文本(必填)",
|
||
"collected_context": "array — 已收集的上下文标签(分步选择中累积)",
|
||
"excluded_options": "array — 坐席已排除的选项标签(避免AI后续推荐)",
|
||
"step_index": "int — 当前步骤序号(0=首次分诊)"
|
||
}
|
||
```
|
||
|
||
### 6.3 输出格式(JSON)
|
||
|
||
```json
|
||
{
|
||
"triage_type": "confirm|transfer|approval",
|
||
"confidence": 0.85,
|
||
"urgency": "high|medium|low",
|
||
"problem_type": "硬件|软件|网络|安全|账号|其他",
|
||
"problem_category": "Outlook",
|
||
"suggested_route": "ai_self|human|auto_approval",
|
||
"matched_knowledge": "Outlook登录失败 → 密码过期检查",
|
||
"match_score": 0.89,
|
||
"context_tags": ["Outlook 2019", "Windows 10", "AD域用户"],
|
||
"triage_steps": [
|
||
{
|
||
"question": "Outlook是否弹出了具体的错误提示?",
|
||
"options": [
|
||
{"label": "弹出了,提示无法连接到服务器", "probability": 0.68},
|
||
{"label": "弹出了,提示密码错误", "probability": 0.22},
|
||
{"label": "没有弹出任何提示,直接闪退", "probability": 0.08},
|
||
{"label": "其他情况", "probability": 0.02}
|
||
]
|
||
}
|
||
],
|
||
"total_steps": 3,
|
||
"reply": "AI回复文本(当triage_type=confirm时,引导员工选择)"
|
||
}
|
||
```
|
||
|
||
### 6.4 Prompt 设计要点
|
||
|
||
| 要点 | 说明 |
|
||
|------|------|
|
||
| **角色定义** | 你是IT服务台智能分诊引擎,负责分析员工IT问题并拆分为分步选择题 |
|
||
| **任务目标** | 1. 识别问题类型和分类 2. 评估置信度和紧急度 3. 拆分复杂问题为分步选择题 4. 推荐路由渠道 |
|
||
| **复杂度自适应** | 简单问题1-2步,复杂问题3-5步,每步最多4个选项 |
|
||
| **概率分配** | 每个选项分配概率(0-1),所有选项概率之和为1,最高概率项可标记推荐 |
|
||
| **紧急度规则** | 消息含"紧急/马上/宕机/无法工作/崩溃/死机/蓝屏"→high;含"报错/失败/连不上"→medium;其余→low |
|
||
| **排除选项** | excluded_options 中的选项不出现在后续步骤中 |
|
||
| **输出约束** | 必须输出合法JSON,不要输出解释性文字 |
|
||
| **置信度评估** | 基于知识库匹配度、问题清晰度、上下文完整度综合评估 |
|
||
|
||
### 6.5 后端对接方式
|
||
|
||
| 配置项 | 环境变量 | 默认值 | 说明 |
|
||
|--------|---------|--------|------|
|
||
| API URL | `DIFY_TRIAGE_API_URL` | "" | Dify OpenAI 兼容接口地址(如 http://yw-dify/dc.servyou-it.com/dify2openai) |
|
||
| API Key | `DIFY_TRIAGE_API_KEY` | "" | 格式:base_url\|app_id\|app_name |
|
||
| 超时 | `DIFY_TRIAGE_TIMEOUT` | 5 | 秒,超时自动转人工 |
|
||
|
||
```python
|
||
# backend/app/config.py 新增配置项
|
||
# ----------------------------------------------------------------------
|
||
# AI 分诊服务配置(Dify Agent 3 — 独立分诊应用)
|
||
# ----------------------------------------------------------------------
|
||
dify_triage_api_url: str = ""
|
||
dify_triage_api_key: str = ""
|
||
dify_triage_timeout: int = 5
|
||
```
|
||
|
||
```python
|
||
# backend/app/services/dify_triage_service.py 对接方式
|
||
class DifyTriageService:
|
||
async def analyze(self, question: str, context: list = None,
|
||
excluded_options: list = None, step_index: int = 0) -> dict:
|
||
"""调用 Dify 分诊应用。"""
|
||
async with httpx.AsyncClient(timeout=settings.dify_triage_timeout) as client:
|
||
resp = await client.post(
|
||
f"{settings.dify_triage_api_url}/v1/chat/completions",
|
||
headers={"Authorization": f"Bearer {settings.dify_triage_api_key}"},
|
||
json={
|
||
"model": "triage-engine",
|
||
"messages": [
|
||
{"role": "system", "content": TRIAGE_SYSTEM_PROMPT},
|
||
{"role": "user", "content": json.dumps({
|
||
"question": question,
|
||
"collected_context": context or [],
|
||
"excluded_options": excluded_options or [],
|
||
"step_index": step_index
|
||
})}
|
||
],
|
||
"temperature": 0.3
|
||
}
|
||
)
|
||
content = resp.json()["choices"][0]["message"]["content"]
|
||
return json.loads(content) # 解析 JSON 输出
|
||
```
|
||
|
||
---
|
||
|
||
## 7. 文件清单
|
||
|
||
### 7.1 后端文件
|
||
|
||
| # | 文件路径 | 操作 | 模块 | 说明 |
|
||
|---|---------|------|------|------|
|
||
| B01 | `backend/migrations/versions/045_triage_exclusion_tables.py` | [新建] | DB | 迁移:3 张新表 |
|
||
| B02 | `backend/app/config.py` | [修改] | 配置 | 新增 dify_triage_* 配置项 |
|
||
| B03 | `backend/app/models/triage_session.py` | [新建] | 分诊 | TriageSession ORM 模型 |
|
||
| B04 | `backend/app/models/exclusion_rule.py` | [新建] | 排除 | ExclusionRule ORM 模型 |
|
||
| B05 | `backend/app/models/exclusion_log.py` | [新建] | 排除 | ExclusionLog ORM 模型 |
|
||
| B06 | `backend/app/schemas/triage.py` | [新建] | 分诊 | 请求/响应 Schema(H5端+坐席端) |
|
||
| B07 | `backend/app/schemas/exclusion.py` | [新建] | 排除 | 请求/响应 Schema |
|
||
| B08 | `backend/app/api/triage.py` | [新建] | 分诊 | 分诊 API(H5端 5 个 + 坐席端 7 个) |
|
||
| B09 | `backend/app/api/exclusion_rules.py` | [新建] | 排除 | 代答排除管理 API(8 个端点) |
|
||
| B10 | `backend/app/services/triage_service.py` | [新建] | 分诊 | 分诊业务逻辑服务 |
|
||
| B11 | `backend/app/services/dify_triage_service.py` | [新建] | 分诊 | Dify 分诊应用对接 |
|
||
| B12 | `backend/app/services/exclusion_service.py` | [新建] | 排除 | 代答排除匹配引擎 |
|
||
| B13 | `backend/app/services/matchers/__init__.py` | [新建] | 排除 | 匹配器包初始化 |
|
||
| B14 | `backend/app/services/matchers/base.py` | [新建] | 排除 | 匹配器基类 + MatchResult |
|
||
| B15 | `backend/app/services/matchers/keyword_matcher.py` | [新建] | 排除 | 关键词匹配器 |
|
||
| B16 | `backend/app/services/matchers/regex_matcher.py` | [新建] | 排除 | 正则匹配器 |
|
||
| B17 | `backend/app/services/matchers/intent_matcher.py` | [新建] | 排除 | 意图匹配器(Dify) |
|
||
| B18 | `backend/app/services/matchers/category_matcher.py` | [新建] | 排除 | 分类匹配器(依赖分诊) |
|
||
| B19 | `backend/app/api/router.py` | [修改] | 路由 | 注册 triage + exclusion_rules 路由 |
|
||
| B20 | `backend/app/services/ai_handler.py` | [修改] | 排除 | 消息流集成排除检查(AI回复前) |
|
||
| B21 | `backend/app/models/__init__.py` | [修改] | DB | 导出新模型 |
|
||
|
||
### 7.2 前端文件
|
||
|
||
| # | 文件路径 | 操作 | 模块 | 说明 |
|
||
|---|---------|------|------|------|
|
||
| F01 | `frontend-h5/src/api/triage.ts` | [新建] | 分诊-H5 | H5 端分诊 API 封装 |
|
||
| F02 | `frontend-h5/src/components/TriageCard.vue` | [修改] | 分诊-H5 | 对接后端 API(已有组件 530 行) |
|
||
| F03 | `frontend-h5/src/composables/useTriage.ts` | [新建] | 分诊-H5 | 分诊状态管理 composable |
|
||
| F04 | `frontend-agent/src/views/TriageDashboard.vue` | [新建] | 分诊-坐席 | 分诊看板主页面 |
|
||
| F05 | `frontend-agent/src/api/triage.ts` | [新建] | 分诊-坐席 | 坐席端分诊 API 封装 |
|
||
| F06 | `frontend-agent/src/components/triage/TriageStatsBar.vue` | [新建] | 分诊-坐席 | 统计概要栏 |
|
||
| F07 | `frontend-agent/src/components/triage/TriagePendingList.vue` | [新建] | 分诊-坐席 | 待分诊列表 |
|
||
| F08 | `frontend-agent/src/components/triage/TriageDetailPanel.vue` | [新建] | 分诊-坐席 | 分诊详情+路由操作 |
|
||
| F09 | `frontend-agent/src/router/index.ts` | [修改] | 分诊-坐席 | 新增分诊看板路由 |
|
||
| F10 | `frontend-admin/src/views/TopologyPreview.vue` | [新建] | 拓扑 | 拓扑预览主页面 |
|
||
| F11 | `frontend-admin/src/components/topology/GraphCanvas.vue` | [新建] | 拓扑 | ECharts 力导向图画布 |
|
||
| F12 | `frontend-admin/src/components/topology/NodeDetailPanel.vue` | [新建] | 拓扑 | 节点详情面板(只读) |
|
||
| F13 | `frontend-admin/src/components/topology/GraphToolbar.vue` | [新建] | 拓扑 | 搜索+筛选+缩放工具栏 |
|
||
| F14 | `frontend-admin/src/api/topology.ts` | [新建] | 拓扑 | 封装 GET /graph 调用 |
|
||
| F15 | `frontend-admin/src/router/index.ts` | [修改] | 拓扑 | 新增拓扑预览路由 |
|
||
| F16 | `frontend-admin/src/components/Sidebar.vue` | [修改] | 拓扑 | 侧边栏新增"拓扑预览"菜单项 |
|
||
| F17 | `frontend-admin/src/views/ExclusionRules.vue` | [新建] | 排除 | 代答排除规则列表页 |
|
||
| F18 | `frontend-admin/src/components/exclusion/ExclusionRuleForm.vue` | [新建] | 排除 | 新建/编辑规则弹窗 |
|
||
| F19 | `frontend-admin/src/components/exclusion/ExclusionTestDialog.vue` | [新建] | 排除 | 测试匹配弹窗 |
|
||
| F20 | `frontend-admin/src/api/exclusion.ts` | [新建] | 排除 | 代答排除 API 封装 |
|
||
| F21 | `frontend-admin/src/router/index.ts` | [修改] | 排除 | 新增代答排除路由 |
|
||
|
||
### 7.3 Dify / 配置文件
|
||
|
||
| # | 文件路径 | 操作 | 模块 | 说明 |
|
||
|---|---------|------|------|------|
|
||
| D01 | `dify/triage-app-prompt.md` | [新建] | Dify | 分诊应用 Prompt 完整文本 |
|
||
| D02 | `.env.example` | [修改] | 配置 | 新增 DIFY_TRIAGE_* 环境变量示例 |
|
||
|
||
### 7.4 文件统计
|
||
|
||
| 类别 | 新建 | 修改 | 合计 |
|
||
|------|------|------|------|
|
||
| 后端 | 16 | 5 | 21 |
|
||
| 前端 | 16 | 5 | 21 |
|
||
| Dify/配置 | 1 | 1 | 2 |
|
||
| **合计** | **33** | **11** | **44** |
|
||
|
||
---
|
||
|
||
## 8. 开发任务列表
|
||
|
||
### T01:项目基础设施(数据库迁移 + 配置 + 数据模型 + 路由注册)
|
||
|
||
| 维度 | 内容 |
|
||
|------|------|
|
||
| **任务 ID** | T01 |
|
||
| **任务名称** | 项目基础设施:数据库迁移 + 配置 + 数据模型 + 路由注册 |
|
||
| **涉及文件** | `backend/migrations/versions/045_triage_exclusion_tables.py` [新建], `backend/app/config.py` [修改], `backend/app/models/triage_session.py` [新建], `backend/app/models/exclusion_rule.py` [新建], `backend/app/models/exclusion_log.py` [新建], `backend/app/models/__init__.py` [修改], `backend/app/schemas/triage.py` [新建], `backend/app/schemas/exclusion.py` [新建], `backend/app/api/router.py` [修改], `.env.example` [修改] |
|
||
| **依赖** | 无 |
|
||
| **优先级** | P0 |
|
||
| **预估工作量** | M |
|
||
| **关键实现要点** | 1. 创建 3 张新表的 Alembic 迁移(triage_sessions + exclusion_rules + exclusion_logs)<br>2. config.py 新增 `dify_triage_api_url`、`dify_triage_api_key`、`dify_triage_timeout` 三个配置项<br>3. 定义 3 个 ORM 模型,字段与 DDL 对齐<br>4. 定义 Pydantic Schema(请求/响应模型),含 TriageStep、TriageOption、ExclusionRuleCreate/Update 等<br>5. router.py 注册 `triage_router`(prefix 无,内部定义)和 `exclusion_rules_router`(prefix `/admin/exclusion-rules`)<br>6. `.env.example` 补充环境变量示例 |
|
||
|
||
### T02:分诊交互模块(后端 API + 服务 + Dify 分诊 + H5 前端 + 坐席端前端)
|
||
|
||
| 维度 | 内容 |
|
||
|------|------|
|
||
| **任务 ID** | T02 |
|
||
| **任务名称** | 分诊交互模块:H5 端分诊 API + 坐席端看板 API + Dify 分诊服务 + H5/坐席前端 |
|
||
| **涉及文件** | `backend/app/api/triage.py` [新建], `backend/app/services/triage_service.py` [新建], `backend/app/services/dify_triage_service.py` [新建], `frontend-h5/src/api/triage.ts` [新建], `frontend-h5/src/components/TriageCard.vue` [修改], `frontend-h5/src/composables/useTriage.ts` [新建], `frontend-agent/src/views/TriageDashboard.vue` [新建], `frontend-agent/src/api/triage.ts` [新建], `frontend-agent/src/components/triage/TriageStatsBar.vue` [新建], `frontend-agent/src/components/triage/TriagePendingList.vue` [新建], `frontend-agent/src/components/triage/TriageDetailPanel.vue` [新建], `frontend-agent/src/router/index.ts` [修改], `dify/triage-app-prompt.md` [新建] |
|
||
| **依赖** | T01 |
|
||
| **优先级** | P0 |
|
||
| **预估工作量** | L |
|
||
| **关键实现要点** | 1. **TriageService**:实现 start_triage(含 5 秒超时自动转人工)、submit_step、skip_step、complete_triage、transfer_to_human、determine_urgency(关键词规则)、坐席端 list_pending/get_detail/route/override<br>2. **DifyTriageService**:对接 Dify OpenAI 兼容接口,解析 JSON 输出,降级处理(Dify 不可用时返回友好错误)<br>3. **triage.py API**:H5 端 5 个端点(start/step/skip/transfer/complete)+ 坐席端 7 个端点(pending/stats/detail/route/history/export/exclude-options)<br>4. **H5 TriageCard.vue**:已有 530 行组件,修改为对接后端 API,通过 useTriage composable 管理状态<br>5. **坐席端 TriageDashboard.vue**:左列表+右详情双栏布局,复用原型图设计<br>6. **WS 推送**:坐席排除选项后通过 WS 推送到 H5 端,H5 调用 `TriageCard.setExcludedOptions()`<br>7. **Dify Prompt**:按 §6.4 要点编写完整 prompt,保存到 `dify/triage-app-prompt.md` |
|
||
|
||
### T03:拓扑预览模块(纯前端页面 + 组件,复用已有 API)
|
||
|
||
| 维度 | 内容 |
|
||
|------|------|
|
||
| **任务 ID** | T03 |
|
||
| **任务名称** | 拓扑预览模块:管理后台只读图谱页面 + 搜索筛选 + 详情面板 |
|
||
| **涉及文件** | `frontend-admin/src/views/TopologyPreview.vue` [新建], `frontend-admin/src/components/topology/GraphCanvas.vue` [新建], `frontend-admin/src/components/topology/NodeDetailPanel.vue` [新建], `frontend-admin/src/components/topology/GraphToolbar.vue` [新建], `frontend-admin/src/api/topology.ts` [新建], `frontend-admin/src/router/index.ts` [修改], `frontend-admin/src/components/Sidebar.vue` [修改] |
|
||
| **依赖** | T01(仅需确认路由注册格式,无 DB 依赖) |
|
||
| **优先级** | P1 |
|
||
| **预估工作量** | M |
|
||
| **关键实现要点** | 1. **复用已有 API**:`GET /api/admin/knowledge-iteration/graph?issue_name=xxx&limit=200`,不新增后端端点<br>2. **GraphCanvas.vue**:ECharts `graph` 类型力导向布局,Issue 绿色 #07C160 / Action 橙色 #f59e0b,roam=true 支持缩放平移,focusNodeAdjacency 悬停高亮<br>3. **搜索高亮**:前端过滤节点名称,匹配节点蓝色描边+脉冲,非匹配降低透明度,右侧浮层展示结果<br>4. **筛选器**:节点类型 checkbox(Issue/Action),Relation 类型置灰 disabled;关系类型下拉(LEADS_TO/RELATES_TO/CAN_JUMP_TO),DERIVED_FROM 置灰<br>5. **NodeDetailPanel.vue**:只读展示节点属性+关联关系+高亮路径,不显示编辑/添加/删除按钮<br>6. **GraphToolbar.vue**:搜索框+筛选+缩放控制(放大/缩小/重置)<br>7. **降级处理**:Neo4j 不可用时 API 返回空数组,前端展示空状态引导<br>8. **Sidebar.vue**:新增"拓扑预览"菜单项,图标 🔮 |
|
||
|
||
### T04:代答排除模块(后端匹配引擎 + API + 管理后台前端 + 消息流集成)
|
||
|
||
| 维度 | 内容 |
|
||
|------|------|
|
||
| **任务 ID** | T04 |
|
||
| **任务名称** | 代答排除模块:4 种匹配引擎 + 管理 API + 管理后台前端 + 消息流集成 |
|
||
| **涉及文件** | `backend/app/api/exclusion_rules.py` [新建], `backend/app/services/exclusion_service.py` [新建], `backend/app/services/matchers/__init__.py` [新建], `backend/app/services/matchers/base.py` [新建], `backend/app/services/matchers/keyword_matcher.py` [新建], `backend/app/services/matchers/regex_matcher.py` [新建], `backend/app/services/matchers/intent_matcher.py` [新建], `backend/app/services/matchers/category_matcher.py` [新建], `backend/app/services/ai_handler.py` [修改], `frontend-admin/src/views/ExclusionRules.vue` [新建], `frontend-admin/src/components/exclusion/ExclusionRuleForm.vue` [新建], `frontend-admin/src/components/exclusion/ExclusionTestDialog.vue` [新建], `frontend-admin/src/api/exclusion.ts` [新建], `frontend-admin/src/router/index.ts` [修改] |
|
||
| **依赖** | T01(DB 表 + 模型 + Schema),T02(CategoryMatcher 软依赖 triage_sessions 数据) |
|
||
| **优先级** | P0 |
|
||
| **预估工作量** | L |
|
||
| **关键实现要点** | 1. **策略模式**:BaseMatcher 抽象基类 + 4 种实现(KeywordMatcher/RegexMatcher/IntentMatcher/CategoryMatcher)<br>2. **责任链调度**:ExclusionService 按优先级排序规则,依次调用对应 Matcher,命中即停止并记录日志<br>3. **IntentMatcher**:复用 `approval_dify_base_url` + `approval_dify_api_key` 调用 Dify 意图识别<br>4. **CategoryMatcher**:查询 triage_sessions 获取分诊结果的 problem_category,软依赖(无分诊结果时跳过)<br>5. **消息流集成**:修改 `ai_handler.py`,在 AI 回复前插入排除检查,命中后执行 4 种动作(transfer_human/transfer_human_with_context/prompt_transfer/silent_transfer)<br>6. **管理 API**:8 个端点(CRUD + toggle + test + stats)<br>7. **前端**:规则列表页(表格+筛选+分页+批量操作)+ 新建/编辑弹窗(匹配方式切换表单)+ 测试匹配弹窗<br>8. **命中日志**:每次命中记录 exclusion_logs,更新 hit_count |
|
||
|
||
### 任务汇总表
|
||
|
||
| 任务 ID | 任务名称 | 文件数 | 依赖 | 优先级 | 工作量 |
|
||
|---------|---------|--------|------|--------|--------|
|
||
| T01 | 项目基础设施 | 10 | — | P0 | M |
|
||
| T02 | 分诊交互模块 | 13 | T01 | P0 | L |
|
||
| T03 | 拓扑预览模块 | 7 | T01 | P1 | M |
|
||
| T04 | 代答排除模块 | 14 | T01, T02(软) | P0 | L |
|
||
|
||
---
|
||
|
||
## 9. 模块依赖关系图
|
||
|
||
```mermaid
|
||
graph TB
|
||
subgraph "T01: 项目基础设施"
|
||
DB[数据库迁移<br/>3张新表]
|
||
CFG[config.py<br/>新增配置项]
|
||
MODEL[ORM 模型<br/>3个]
|
||
SCHEMA[Pydantic Schema<br/>2个文件]
|
||
ROUTER[router.py<br/>路由注册]
|
||
end
|
||
|
||
subgraph "T02: 分诊交互模块"
|
||
TRI_API[分诊 API<br/>12个端点]
|
||
TRI_SVC[TriageService<br/>+ DifyTriageService]
|
||
H5_FE[H5 前端<br/>TriageCard + useTriage]
|
||
AG_FE[坐席端前端<br/>TriageDashboard + 3组件]
|
||
DIFY_APP[Dify 分诊应用<br/>Prompt + 配置]
|
||
end
|
||
|
||
subgraph "T03: 拓扑预览模块"
|
||
TOPO_FE[管理后台前端<br/>TopologyPreview + 3组件]
|
||
TOPO_API[复用已有<br/>GET /graph]
|
||
end
|
||
|
||
subgraph "T04: 代答排除模块"
|
||
EXC_API[排除管理 API<br/>8个端点]
|
||
EXC_SVC[ExclusionService<br/>+ 4个Matcher]
|
||
EXC_FE[管理后台前端<br/>ExclusionRules + 2组件]
|
||
MSG_INT[消息流集成<br/>ai_handler.py]
|
||
end
|
||
|
||
%% 依赖关系
|
||
DB --> TRI_API
|
||
DB --> EXC_API
|
||
CFG --> TRI_SVC
|
||
MODEL --> TRI_API
|
||
MODEL --> EXC_API
|
||
SCHEMA --> TRI_API
|
||
SCHEMA --> EXC_API
|
||
ROUTER --> TRI_API
|
||
ROUTER --> EXC_API
|
||
|
||
TRI_API --> TRI_SVC
|
||
TRI_SVC --> H5_FE
|
||
TRI_SVC --> AG_FE
|
||
TRI_SVC --> DIFY_APP
|
||
|
||
EXC_API --> EXC_SVC
|
||
EXC_SVC --> EXC_FE
|
||
EXC_SVC --> MSG_INT
|
||
|
||
%% 软依赖
|
||
TRI_SVC -.->|"CategoryMatcher<br/>软依赖分诊结果"| EXC_SVC
|
||
|
||
%% 拓扑预览独立
|
||
TOPO_FE --> TOPO_API
|
||
|
||
%% 并行标注
|
||
style T03 fill:#e8f5e9,stroke:#4caf50,stroke-dasharray: 5 5
|
||
style TOPO_FE fill:#e8f5e9,stroke:#4caf50,stroke-dasharray: 5 5
|
||
style TOPO_API fill:#e8f5e9,stroke:#4caf50,stroke-dasharray: 5 5
|
||
|
||
style DB fill:#e3f2fd,stroke:#2196f3
|
||
style CFG fill:#e3f2fd,stroke:#2196f3
|
||
style MODEL fill:#e3f2fd,stroke:#2196f3
|
||
style SCHEMA fill:#e3f2fd,stroke:#2196f3
|
||
style ROUTER fill:#e3f2fd,stroke:#2196f3
|
||
```
|
||
|
||
**并行开发说明**:
|
||
|
||
| 并行组 | 可并行任务 | 前置条件 |
|
||
|--------|-----------|---------|
|
||
| 组 1 | T02(分诊)+ T03(拓扑) | T01 完成 |
|
||
| 组 2 | T04(排除)可与 T02 后半段并行 | T01 完成;T04 的 CategoryMatcher 部分可先实现框架,分诊结果接入后联调 |
|
||
|
||
> T03(拓扑预览)**完全独立**,与 T02、T04 无交叉依赖,可全程并行开发。
|
||
|
||
---
|
||
|
||
## 10. 共享知识(Shared Knowledge)
|
||
|
||
### 10.1 API 响应格式
|
||
|
||
所有 API 响应统一使用 `{code, message, data}` 格式:
|
||
|
||
```json
|
||
{
|
||
"code": 0,
|
||
"message": "success",
|
||
"data": { ... }
|
||
}
|
||
```
|
||
|
||
- `code: 0` 表示成功,非 0 表示错误
|
||
- 错误时 `data` 为 null,`message` 包含错误描述
|
||
|
||
### 10.2 路由前缀规范
|
||
|
||
| 端 | 前缀 | 说明 |
|
||
|----|------|------|
|
||
| H5 员工端 | `/api/h5/triage/*` | 分诊交互(员工发起分诊) |
|
||
| 坐席端 | `/api/agent/triage/*` | 分诊看板(坐席查看/路由) |
|
||
| 管理后台-知识迭代 | `/api/admin/knowledge-iteration/*` | 已有,拓扑预览复用 |
|
||
| 管理后台-代答排除 | `/api/admin/exclusion-rules/*` | 新增 |
|
||
|
||
### 10.3 认证方式
|
||
|
||
| 端 | 认证方式 | 装饰器 |
|
||
|----|---------|--------|
|
||
| H5 端 | H5 用户 token(企微 OAuth) | `Depends(get_current_user)` |
|
||
| 坐席端 | 坐席 token | `Depends(get_current_agent)` |
|
||
| 管理后台 | 管理员 token + IP 白名单 | `@require_admin` |
|
||
|
||
### 10.4 Dify 对接统一模式
|
||
|
||
所有 Dify 调用统一使用 OpenAI Chat Completions 兼容接口:
|
||
|
||
```
|
||
POST {dify_base_url}/v1/chat/completions
|
||
Authorization: Bearer {api_key}
|
||
Content-Type: application/json
|
||
|
||
{
|
||
"model": "{app_identifier}",
|
||
"messages": [
|
||
{"role": "system", "content": "{system_prompt}"},
|
||
{"role": "user", "content": "{user_input}"}
|
||
],
|
||
"temperature": 0.3
|
||
}
|
||
```
|
||
|
||
| Dify 应用 | 配置项前缀 | 用途 |
|
||
|-----------|-----------|------|
|
||
| Agent 1(员工端 AI) | `dify_api_*` | AI 对话回复 |
|
||
| Agent 2(Wingman) | `dify_wingman_*` | 坐席草稿/摘要/标签 |
|
||
| Agent 3(分诊引擎) | `dify_triage_*` | **本次新增** — 分诊分析 |
|
||
| 审批意图识别 | `approval_dify_*` | 审批路由 + 排除意图匹配 |
|
||
|
||
### 10.5 数据库模型规范
|
||
|
||
- 主键:`id VARCHAR(36)`,默认 `gen_random_uuid()::text`(与现有 KnowledgeSuggestion 一致)
|
||
- 时间戳:`created_at` / `updated_at`,默认 `NOW()`
|
||
- JSON 字段:使用 SQLAlchemy `JSON` 类型,PostgreSQL 原生 JSON
|
||
- 软删除:本期不实现软删除,直接物理删除(排除规则)
|
||
|
||
### 10.6 前端 API 封装规范
|
||
|
||
所有前端 API 封装统一使用 axios 实例(已有),响应拦截器统一处理 `{code, data}` 解包:
|
||
|
||
```typescript
|
||
// 示例:frontend-admin/src/api/exclusion.ts
|
||
import request from './index'
|
||
|
||
export function getExclusionRules(params: ExclusionRuleQuery) {
|
||
return request.get('/admin/exclusion-rules', { params })
|
||
}
|
||
```
|
||
|
||
### 10.7 WebSocket 消息格式
|
||
|
||
坐席排除选项推送到 H5 端的 WS 消息:
|
||
|
||
```json
|
||
{
|
||
"type": "triage_exclude",
|
||
"data": {
|
||
"triage_id": "xxx",
|
||
"excluded_labels": ["弹出了,提示密码错误"],
|
||
"recommended_label": "弹出了,提示无法连接到服务器"
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 11. 待明确事项
|
||
|
||
| # | 问题 | 影响范围 | 建议处理方式 |
|
||
|---|------|---------|-------------|
|
||
| 1 | Dify 分诊应用的 `dify2openai` 代理地址是否与 Wingman 共用?还是需要独立部署? | T02 DifyTriageService | 建议共用同一个 dify2openai 代理,通过不同 API Key 区分应用。需运维确认代理是否支持多应用路由。 |
|
||
| 2 | 意图匹配复用审批意图识别 Dify 应用,但审批意图的意图分类体系(12种/18流程)是否覆盖代答排除需要的意图(如 data_deletion, data_modification)? | T04 IntentMatcher | 建议在审批意图识别应用中扩展意图分类,或确认现有意图体系已覆盖。需与 Dify 管理员确认。 |
|
||
| 3 | 坐席端分诊看板的 WS 实时推送是否复用现有 `ws_manager.py` 的 WebSocket 连接?还是需要新建独立 WS 通道? | T02 坐席端排除选项推送 | 建议复用现有 WS 连接,通过 `type` 字段区分消息类型。需确认现有 WS 消息协议是否支持扩展。 |
|
||
| 4 | 分诊看板"导出"功能导出的 xlsx 包含哪些字段?是否需要包含分诊步骤详情? | T02 导出 API | 建议导出基础字段(标题/用户/部门/问题类型/置信度/紧急度/路由/时间),分诊步骤详情可选。 |
|
||
| 5 | 代答排除的"仅提示转人工"动作,用户确认转人工后是否需要再次调用后端 API?还是前端直接调用已有的转人工接口? | T04 消息流集成 | 建议前端收到 `prompt_transfer` 类型的排除命中后,展示提示语 + 确认按钮,用户确认后调用已有 `POST /api/conversations/{id}/transfer`。 |
|
||
| 6 | 拓扑预览页面是否需要权限控制?所有管理员都能查看,还是需要新增 RBAC 权限项? | T03 路由配置 | 建议复用 `@require_admin` 装饰器,所有管理员可查看。如需细粒度控制,后续新增 `topology:read` 权限。 |
|
||
|
||
---
|
||
|
||
> **文档结束**
|
||
> 本文档基于已有技术方案文档和原型图文档,结合用户确认的 4 项关键决策 + 9 项次要决策,产出增量系统设计和开发任务分解。
|
||
> 工程师拿到后可直接按 T01→T02/T03(并行)→T04 的顺序开始开发。
|