Files
zhuangang/scenarios/ticket-system.md
T

89 lines
4.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
id: scenario.ticket-system
title: 工单系统:AI 自动分类与路由、状态机、SLA 超时升级和坐席协作
type: scenario
domain: cross
stack: [postgresql, mq, redis, langchain, gin, fastapi, react]
tags: [工单, 状态机, 自动分类, 路由, SLA, 延迟队列, 审计, 乐观锁]
level: intermediate
prerequisites: [backend.postgresql.index, ai.langchain.index]
est_hours: 3
related: [ai.langchain.scenario.ticket-triage, backend.postgresql.scenario.order-state-audit, backend.mq.scenario.delayed-order-cancel, frontend.react.scenario.long-task-ui]
week: [3, 11, 14]
---
# 工单系统:AI 自动分类与路由、状态机、SLA 超时升级和坐席协作
## 业务背景与问题
客服中心的工单靠人工分派:
1. 分类不准、分派慢,首次响应时间长;
2. 工单状态混乱(已关闭的又被改回处理中),没有操作记录;
3. 超过 SLA 没人知道;
4. 两个坐席同时处理同一工单,互相覆盖。
## 约束
- 分类错误会耽误处理,但人工可以纠正。
- 需要审计:谁在什么时候做了什么。
- AI 不可用时系统照常运行(降级为人工分派)。
## 方案设计
1. **状态机与审计**(PostgreSQL):`new → triaged → assigned → in_progress → waiting_customer → resolved → closed`,只允许合法转换;每次转换在同一事务中写事件表;`version` 字段做乐观锁。见[订单状态与审计](../topics/backend/postgresql/scenarios/order-state-audit.md)。
2. **AI 分类**(LangChain 结构化输出):类别、优先级、建议处理组、置信度、理由;置信度低于阈值进入“待人工分类”;坐席的纠正记录为评测数据。见[工单智能分诊](../topics/ai/langchain/scenarios/ticket-triage.md)。
3. **异步流程**:创建工单后发消息,分类服务消费(重试、死信);回写结果时校验状态和版本,不覆盖人工修改。
4. **SLA**:按优先级计算截止时间,投递延迟消息;到期时**再检查当前状态**,仍未处理才升级。见[延迟消息](../topics/backend/mq/scenarios/delayed-order-cancel.md)。
5. **坐席界面**(React):列表实时更新,显示 AI 分类理由,一键采纳或纠正;处理中用 Redis 软锁(带过期)提示其他人。见[长任务界面](../topics/frontend/react/scenarios/long-task-ui.md)。
6. **降级**:分类服务超时或不可用时,工单直接进入人工分派队列。
## 关键代码
状态转换 + 乐观锁 + 审计(一个事务中):
```sql
UPDATE tickets
SET status = 'assigned', assignee_id = $3, version = version + 1, updated_at = now()
WHERE id = $1 AND version = $2 AND status IN ('new', 'triaged');
-- 影响行数为 0:状态不允许或已被别人修改,返回 409 让前端刷新
INSERT INTO ticket_events (ticket_id, actor, action, from_status, to_status, payload)
VALUES ($1, $4, 'assign', 'triaged', 'assigned', jsonb_build_object('assignee', $3));
```
分类结果结构(示意):
```python
class Triage(BaseModel):
category: Literal["refund", "invoice", "account", "bug", "other"]
priority: Literal["P1", "P2", "P3"]
group: str
confidence: float = Field(ge=0, le=1)
reason: str = Field(max_length=200)
result = triage_chain.invoke({"title": t.title, "body": t.body}) # model.with_structured_output(Triage)
if result.confidence < 0.7:
route_to_manual_queue(t.id, suggestion=result)
```
## 取舍
| 决策 | 选择 | 原因 |
| --- | --- | --- |
| 分类方式 | 大模型结构化输出 + 置信度阈值 | 冷启动快;纠正数据积累后可训练小模型降成本 |
| 并发控制 | 乐观锁 + 软锁提示 | 冲突少,悲观锁会让坐席等待 |
| SLA | 延迟消息 + 到期检查,定时扫描兜底 | 及时且可靠 |
| 语言 | 工单核心用 Go(Gin),AI 分类用 Python | 发挥两者优势;全部 Python 也可以 |
## 易错点
- AI 分类结果覆盖了人工已修改的字段。
- 状态转换只靠前端隐藏按钮,数据库层没有约束。
- 延迟消息到期时不检查当前状态,已处理的工单也被升级。
- 审计日志和业务更新不在同一事务中。
## 面试怎么讲
> “工单系统用状态机约束转换,每次转换在同一事务中写审计事件,乐观锁防止坐席互相覆盖。AI 分类用结构化输出给出类别、优先级、建议组和置信度,低于阈值进入人工队列,纠正记录作为评测数据。SLA 用延迟消息,到期再检查状态决定是否升级。分类服务挂了就直接人工分派,不影响主流程。”