--- 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 用延迟消息,到期再检查状态决定是否升级。分类服务挂了就直接人工分派,不影响主流程。”