Files
zhuangang/topics/ai/langchain/scenarios/ticket-triage.md
T

4.6 KiB

id, title, type, domain, stack, tags, level, prerequisites, est_hours, related, week
id title type domain stack tags level prerequisites est_hours related week
ai.langchain.scenario.ticket-triage 工单自动分类服务:结构化输出、批处理、重试降级和人工复核 scenario ai
langchain
fastapi
postgresql
结构化输出
批处理
重试
降级
人工复核
成本
intermediate
ai.langchain.lcel-structured-agent
2.5
ai.langchain.interview
ai.llm-serving.index
6
11

工单自动分类服务:结构化输出、批处理、重试降级和人工复核

业务背景与问题

客服每天收到约 5000 张工单,人工分派到退款、技术、物流、投诉等 8 个队列,平均分派耗时 20 分钟。希望用大模型自动分类、判断紧急程度、提取订单号。试运行问题:

  1. 模型偶尔输出“类别:退款”这样的自然语言,解析失败;
  2. 模型给出了不存在的类别“售后”;
  3. 高峰期模型接口限流,工单堆积;
  4. 历史 50 万张工单要回填分类,逐条调用太慢、太贵;
  5. 分类错误的工单被分到错误的队列,没人发现。

约束

  • 分派延迟目标:1 分钟内。
  • 分类结果要可追溯(模型版本、提示词版本)。

方案设计

  1. 结构化输出:用 Schema 定义输出(类别枚举、紧急程度、订单号格式),优先使用模型的原生结构化输出(with_structured_output),并用 pydantic 校验;校验失败重试一次,仍失败进入“待人工分派”队列。
  2. 置信度和人工复核:输出中包含置信度(或用规则判断:订单号缺失、类别为“其他”),低置信度进入人工复核;每天抽检 2% 自动分类的工单。
  3. 重试和降级:限流 / 超时用指数退避重试;主提供方持续失败时切换备用提供方;都失败时进入人工队列,不阻塞工单创建。
  4. 批处理:历史回填走离线任务,用 batch 控制并发(或厂商的批处理接口,通常更便宜,以厂商文档为准),按天分片、断点续跑。
  5. 可追溯:每条结果记录模型名、提示词版本、原始输出、耗时。

关键代码

class Triage(BaseModel):
    category: Literal["refund", "tech", "logistics", "complaint", "invoice", "account", "presale", "other"]
    urgency: Literal["low", "normal", "high"]
    order_id: str | None = Field(default=None, pattern=r"^A\d{4,}$")
    confidence: float = Field(ge=0, le=1)


classifier = (
    prompt | primary_model.with_structured_output(Triage)
).with_retry(stop_after_attempt=2).with_fallbacks([prompt | backup_model.with_structured_output(Triage)])


async def triage(ticket: Ticket) -> Triage | None:
    try:
        result = await classifier.ainvoke({"title": ticket.title, "body": ticket.body[:2000]})
    except Exception:
        await queue.put_manual(ticket.id, reason="模型失败")
        return None
    if result.confidence < 0.7 or result.category == "other":
        await queue.put_review(ticket.id, result)
    await save_result(ticket.id, result, model=MODEL_NAME, prompt_version=PROMPT_VERSION)
    return result


# 历史回填:控制并发
results = await classifier.abatch(inputs, config={"max_concurrency": 8}, return_exceptions=True)

基础写法的可运行示例:examples/lc-basics。

取舍

选择 优点 缺点
大模型分类(本方案) 新增类别只改提示词和 Schema;能同时提取信息 有成本和延迟;需要评测和复核
传统文本分类模型 便宜、快 需要标注数据训练,类别变化要重新训练
关键词规则 简单可解释 覆盖率低
规则 + 大模型 明确的用规则(便宜),模糊的用模型 两套逻辑要维护

易错点

  • 让模型自由输出类别名,再用字符串匹配。
  • 模型失败时阻塞工单创建:分类应该是异步的,失败可降级为人工。
  • 回填时并发过高触发限流,或者没有断点续跑,失败后从头再来。
  • 模型自报的置信度并不可靠,要和评测集的实际准确率对照校准。
  • 换模型或改提示词后没有跑评测集。

面试怎么讲

“工单分类我用结构化输出,类别是枚举、订单号有格式校验,失败重试一次,再失败进人工队列,不阻塞工单创建。主提供方限流时自动切换备用提供方。低置信度和‘其他’类进入人工复核,每天再抽检 2%。历史回填用批处理控制并发、断点续跑。每条结果记录模型和提示词版本,改动前后跑评测集对比准确率。”