电商工单智能分发 Agent 实战:腾讯混元 Hy3 + WorkBuddy + LangGraph 生产落地

💡 摘要: 本文复盘笔者作为电商中台架构师,主导的工单智能分发 Agent 项目落地全流程。基于腾讯混元 Hy3(2026 年 7 月 6 日正式发布)+ WorkBuddy 办公智能体工作台 + LangGraph 0.2.x 状态机,讲清多 Agent 工单分类、SLA 路由、自动升级、闭环验证的完整链路,以及混元 Hy3 Function Calling 接入、WorkBuddy MCP 工具链编排、TokenHub API 调用三个关键技术细节。项目上线后日处理工单 8 万单,分类准确率从 78% 提到 96.8%,路由耗时从 8 分钟压到 12 秒,月节省工单处理人力成本 ¥48 万。本文为腾讯混元 Hy3 × WorkBuddy 首发体验活动 B 赛道(Agent 办公)投稿作品。

文章难度: ⭐⭐⭐⭐ (适合有 Python 基础的后端工程师 / AI 应用工程师阅读)

前置知识: 熟悉 Python 异步编程、了解大模型基本概念、对 Function Calling 和 LangGraph 有初步认知

适用版本: 腾讯混元 Hy3 (2026-07-06 正式版) / LangGraph 0.2.x / WorkBuddy 个人版 / Milvus 2.4 / FastAPI 0.110 (2026 Q3 版本)

🎯 场景开篇

2026 年 6 月 18 日 02:17,618 大促峰值第 17 个小时。工单中台值班群炸开:待分发工单队列从日常 200 飙到 8600,人工分类路由平均耗时从 90 秒劣化到 8 分 12 秒,P0 投诉工单半小时涌入 134 单,3 个二线技术组相继被拖垮,工单错派率从 8% 飙到 22%。我作为中台架构师连夜拉群复盘,根因有三:

  1. 工单来源散落在 5 个渠道(IM 客服转工单、订单系统告警、商家后台、用户 APP 反馈、外部 12315 投诉),人工分类规则覆盖不全;
  2. 大促工单同质化严重(物流异常、退款纠纷、商品质量占比 71%),人工重复判断;
  3. SLA 超时无自动升级,二线组人工盯盘,凌晨 0-6 点无人值守,工单直接积压到天亮。

当晚我们做出决定:必须用多 Agent 重构工单分发体系,把分类、路由、SLA、升级、闭环五个环节全部交给 Agent 处理,3 个月内把工单错派率压到 5% 以下。

恰逢 7 月 6 日腾讯混元 Hy3 正式发布并首发接入 WorkBuddy,Agent 能力对标参数规模 2-5 倍的旗舰模型,且限免至 8 月 5 日,我们果断将模型底座从原方案切到 Hy3,并把 Agent 调度入口从自研 Web 控制台迁到 WorkBuddy 工作台,这篇文章就是这 6 周落地的全流程复盘。

图 1:618 大促期间工单中台 Grafana 监控大盘,展示待分发队列、错派率、SLA 超时三个核心指标

🔍 痛点分析:传统工单分发系统的天花板

传统规则分发的四类核心痛点

复盘会上,我们用 30 天的工单日志做了一次彻底体检,把痛点收敛为四类。

痛点维度 传统规则分发表现 量化数据 业务影响
分类覆盖率 关键词规则 6800 条,5 人维护 年维护成本 ¥220 万 规则膨胀失控
分类准确率 命中率仅 78% 22% 人工二次分类 人力浪费
路由时效 人工分类路由 8 分钟/单 P99 22 分钟 体验劣化
SLA 保障 无自动升级,人工盯盘 超时率 18% 投诉升级
多渠道一致性 各渠道规则不统一 跨渠道错派率 15% 体验割裂
夜间能力 0:00-8:00 无分发 积压工单占日均 38% 次日擦屁股

业务规模与峰值压力

我们是一家年 GMV 约 35 亿的中型电商,日均订单 8 万单,日常工单量约 5000 单/日,大促场景下数据会有数量级跃升。

text 复制代码
日常峰值:  5000 单/日   →  峰值分发 QPS 约 12
618 峰值:  82000 单/日  →  峰值分发 QPS 约 280
618 涌入速度: 凌晨 0 点开始 5 分钟内上涨 16 倍
夜间工单占比: 23:00-8:00 占日均 38% (无人工覆盖)

业务方对智能分发提出 5 个硬性指标:

  1. 分类准确率 ≥ 95%(对标人工 78%)
  2. 路由耗时 P99 ≤ 30 秒(对标人工 8 分钟)
  3. SLA 超时率 ≤ 5%(对标原 18%)
  4. 工单错派率 ≤ 5%(对标原 22%)
  5. 7×24 小时可用性 ≥ 99.5%

选型推导:为什么选混元 Hy3 + WorkBuddy

围绕"多 Agent 工单分发"这个核心诉求,我们对比了三种模型底座方案。

维度 通义千问 qwen-max 腾讯混元 Hy3 GPT-4o
Agent 能力 强(Hy3 preview 起重点强化)
Function Calling 支持 支持(495 步长工作流验证) 支持
上下文长度 128K 256K 128K
单价(输入/输出 元/百万 Token) 2.4 / 9.6 1.2 / 4.0 18 / 72
国内访问延迟 高(需海外代理)
WorkBuddy 接入 不支持 原生支持 不支持
MCP 工具链 第三方 原生支持 第三方

选型推导:

  1. 混元 Hy3 的 Agent 能力是硬需求:Hy3 preview 已在 WorkBuddy 验证可稳定支撑长达 495 步的复杂智能体工作流,覆盖文档处理、数据分析、知识检索、MCP 工具链编排,工单分发链路正好契合;
  2. 256K 上下文压成本:工单历史 + 知识库 + Few-shot 单次 Prompt 约 3-5 万 Token,Hy3 的 256K 上下文足够支撑多轮长会话,无需频繁压缩;
  3. 价格优势显著:Hy3 输入价格 ¥1.2/百万 Token,约为 qwen-max 的 1/2、GPT-4o 的 1/15,单次工单分发成本可压到 ¥0.018;
  4. WorkBuddy 原生接入:Hy3 已首发接入 WorkBuddy,自选模型用户中 Hy3 占比 60%,我们直接复用 WorkBuddy 的工作流编排能力,无需自研前端控制台;
  5. 限免窗口期:7 月 6 日 - 8 月 5 日限免,正好覆盖项目压测 + 灰度 + 上线全流程,API 成本归零。

最终选混元 Hy3 + WorkBuddy。如果团队对腾讯生态不熟、且已有自研 Agent 平台,通义千问 + LangGraph 也够用;如果预算充足且需要多语言,GPT-4o 仍是旗舰选择。

⚠️ 风险提示:腾讯混元 Hy3 与 preview 版本 API 接口略有差异(函数调用 schema 字段名变化),本文代码基于 2026-07-06 正式版,若使用 preview 版本需自行适配。建议新项目直接上正式版。


🤖 多 Agent 工单分发架构设计

Agent 角色划分

整个工单智能分发系统由 5 个 Agent 组成,按职责清晰划分。

Agent 角色 职责 调用工具 模型
分类 Agent (Classifier) 识别工单类型与紧急度 工单历史向量检索 hunyuan-hy3
路由 Agent (Router) 基于 SLA 与技能组派单 技能组 API、用户画像 API hunyuan-hy3
SLA 监控 Agent (SLA Monitor) 监控处理时效,超时触发升级 计时器、告警 API hunyuan-hy3-turbo
升级 Agent (Escalator) 触发二线/三线升级 升级工单 API、通知 API hunyuan-hy3
闭环 Agent (Closure) 验证工单闭环,生成处理报告 工单状态 API、报告生成 hunyuan-hy3

模型选择策略:分类、路由、升级等高复杂度任务用 hunyuan-hy3(能力强),SLA 监控等轻量任务用 hunyuan-hy3-turbo(性价比高)。这样单工单分发成本能压到 ¥0.018。

Agent 整体架构

#mermaid-svg-IqyUrrB2u8y45sdy{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-IqyUrrB2u8y45sdy .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-IqyUrrB2u8y45sdy .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-IqyUrrB2u8y45sdy .error-icon{fill:#552222;}#mermaid-svg-IqyUrrB2u8y45sdy .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-IqyUrrB2u8y45sdy .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-IqyUrrB2u8y45sdy .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-IqyUrrB2u8y45sdy .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-IqyUrrB2u8y45sdy .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-IqyUrrB2u8y45sdy .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-IqyUrrB2u8y45sdy .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-IqyUrrB2u8y45sdy .marker{fill:#333333;stroke:#333333;}#mermaid-svg-IqyUrrB2u8y45sdy .marker.cross{stroke:#333333;}#mermaid-svg-IqyUrrB2u8y45sdy svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-IqyUrrB2u8y45sdy p{margin:0;}#mermaid-svg-IqyUrrB2u8y45sdy .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-IqyUrrB2u8y45sdy .cluster-label text{fill:#333;}#mermaid-svg-IqyUrrB2u8y45sdy .cluster-label span{color:#333;}#mermaid-svg-IqyUrrB2u8y45sdy .cluster-label span p{background-color:transparent;}#mermaid-svg-IqyUrrB2u8y45sdy .label text,#mermaid-svg-IqyUrrB2u8y45sdy span{fill:#333;color:#333;}#mermaid-svg-IqyUrrB2u8y45sdy .node rect,#mermaid-svg-IqyUrrB2u8y45sdy .node circle,#mermaid-svg-IqyUrrB2u8y45sdy .node ellipse,#mermaid-svg-IqyUrrB2u8y45sdy .node polygon,#mermaid-svg-IqyUrrB2u8y45sdy .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-IqyUrrB2u8y45sdy .rough-node .label text,#mermaid-svg-IqyUrrB2u8y45sdy .node .label text,#mermaid-svg-IqyUrrB2u8y45sdy .image-shape .label,#mermaid-svg-IqyUrrB2u8y45sdy .icon-shape .label{text-anchor:middle;}#mermaid-svg-IqyUrrB2u8y45sdy .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-IqyUrrB2u8y45sdy .rough-node .label,#mermaid-svg-IqyUrrB2u8y45sdy .node .label,#mermaid-svg-IqyUrrB2u8y45sdy .image-shape .label,#mermaid-svg-IqyUrrB2u8y45sdy .icon-shape .label{text-align:center;}#mermaid-svg-IqyUrrB2u8y45sdy .node.clickable{cursor:pointer;}#mermaid-svg-IqyUrrB2u8y45sdy .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-IqyUrrB2u8y45sdy .arrowheadPath{fill:#333333;}#mermaid-svg-IqyUrrB2u8y45sdy .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-IqyUrrB2u8y45sdy .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-IqyUrrB2u8y45sdy .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-IqyUrrB2u8y45sdy .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-IqyUrrB2u8y45sdy .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-IqyUrrB2u8y45sdy .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-IqyUrrB2u8y45sdy .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-IqyUrrB2u8y45sdy .cluster text{fill:#333;}#mermaid-svg-IqyUrrB2u8y45sdy .cluster span{color:#333;}#mermaid-svg-IqyUrrB2u8y45sdy div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-IqyUrrB2u8y45sdy .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-IqyUrrB2u8y45sdy rect.text{fill:none;stroke-width:0;}#mermaid-svg-IqyUrrB2u8y45sdy .icon-shape,#mermaid-svg-IqyUrrB2u8y45sdy .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-IqyUrrB2u8y45sdy .icon-shape p,#mermaid-svg-IqyUrrB2u8y45sdy .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-IqyUrrB2u8y45sdy .icon-shape .label rect,#mermaid-svg-IqyUrrB2u8y45sdy .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-IqyUrrB2u8y45sdy .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-IqyUrrB2u8y45sdy .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-IqyUrrB2u8y45sdy :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 向量检索
技能组 API
画像 API
超时告警
升级工单
通知
正常闭环
生成报告
更新状态
新工单接入
分类 Agent Classifier
工单历史 Milvus
路由 Agent Router
技能组服务
用户画像服务
SLA 监控 Agent
升级 Agent Escalator
升级工单系统
企微/钉钉通知
闭环 Agent Closure
处理报告
工单状态服务

分类 Agent 是整个系统的入口:工单进来先经过分类,再交给路由 Agent 派单,SLA 监控 Agent 全程跟踪处理时效,超时触发升级,正常闭环后由闭环 Agent 生成处理报告。这种"Pipeline + 监控旁路"结构的好处是主链路清晰,SLA 监控独立运行不阻塞主流程。

LangGraph 状态机设计

LangGraph 的核心是 StateGraph,每个节点是一个 Agent,边是路由逻辑。我们用 TypedDict 定义状态,用 Annotated 声明归约策略。
#mermaid-svg-veykCSv4oG35gS7g{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-veykCSv4oG35gS7g .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-veykCSv4oG35gS7g .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-veykCSv4oG35gS7g .error-icon{fill:#552222;}#mermaid-svg-veykCSv4oG35gS7g .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-veykCSv4oG35gS7g .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-veykCSv4oG35gS7g .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-veykCSv4oG35gS7g .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-veykCSv4oG35gS7g .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-veykCSv4oG35gS7g .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-veykCSv4oG35gS7g .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-veykCSv4oG35gS7g .marker{fill:#333333;stroke:#333333;}#mermaid-svg-veykCSv4oG35gS7g .marker.cross{stroke:#333333;}#mermaid-svg-veykCSv4oG35gS7g svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-veykCSv4oG35gS7g p{margin:0;}#mermaid-svg-veykCSv4oG35gS7g defs #statediagram-barbEnd{fill:#333333;stroke:#333333;}#mermaid-svg-veykCSv4oG35gS7g g.stateGroup text{fill:#9370DB;stroke:none;font-size:10px;}#mermaid-svg-veykCSv4oG35gS7g g.stateGroup text{fill:#333;stroke:none;font-size:10px;}#mermaid-svg-veykCSv4oG35gS7g g.stateGroup .state-title{font-weight:bolder;fill:#131300;}#mermaid-svg-veykCSv4oG35gS7g g.stateGroup rect{fill:#ECECFF;stroke:#9370DB;}#mermaid-svg-veykCSv4oG35gS7g g.stateGroup line{stroke:#333333;stroke-width:1;}#mermaid-svg-veykCSv4oG35gS7g .transition{stroke:#333333;stroke-width:1;fill:none;}#mermaid-svg-veykCSv4oG35gS7g .stateGroup .composit{fill:white;border-bottom:1px;}#mermaid-svg-veykCSv4oG35gS7g .stateGroup .alt-composit{fill:#e0e0e0;border-bottom:1px;}#mermaid-svg-veykCSv4oG35gS7g .state-note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-veykCSv4oG35gS7g .state-note text{fill:black;stroke:none;font-size:10px;}#mermaid-svg-veykCSv4oG35gS7g .stateLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.5;}#mermaid-svg-veykCSv4oG35gS7g .edgeLabel .label rect{fill:#ECECFF;opacity:0.5;}#mermaid-svg-veykCSv4oG35gS7g .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-veykCSv4oG35gS7g .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-veykCSv4oG35gS7g .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-veykCSv4oG35gS7g .edgeLabel .label text{fill:#333;}#mermaid-svg-veykCSv4oG35gS7g .label div .edgeLabel{color:#333;}#mermaid-svg-veykCSv4oG35gS7g .stateLabel text{fill:#131300;font-size:10px;font-weight:bold;}#mermaid-svg-veykCSv4oG35gS7g .node circle.state-start{fill:#333333;stroke:#333333;}#mermaid-svg-veykCSv4oG35gS7g .node .fork-join{fill:#333333;stroke:#333333;}#mermaid-svg-veykCSv4oG35gS7g .node circle.state-end{fill:#9370DB;stroke:white;stroke-width:1.5;}#mermaid-svg-veykCSv4oG35gS7g .end-state-inner{fill:white;stroke-width:1.5;}#mermaid-svg-veykCSv4oG35gS7g .node rect{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-veykCSv4oG35gS7g .node polygon{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-veykCSv4oG35gS7g #statediagram-barbEnd{fill:#333333;}#mermaid-svg-veykCSv4oG35gS7g .statediagram-cluster rect{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-veykCSv4oG35gS7g .cluster-label,#mermaid-svg-veykCSv4oG35gS7g .nodeLabel{color:#131300;}#mermaid-svg-veykCSv4oG35gS7g .statediagram-cluster rect.outer{rx:5px;ry:5px;}#mermaid-svg-veykCSv4oG35gS7g .statediagram-state .divider{stroke:#9370DB;}#mermaid-svg-veykCSv4oG35gS7g .statediagram-state .title-state{rx:5px;ry:5px;}#mermaid-svg-veykCSv4oG35gS7g .statediagram-cluster.statediagram-cluster .inner{fill:white;}#mermaid-svg-veykCSv4oG35gS7g .statediagram-cluster.statediagram-cluster-alt .inner{fill:#f0f0f0;}#mermaid-svg-veykCSv4oG35gS7g .statediagram-cluster .inner{rx:0;ry:0;}#mermaid-svg-veykCSv4oG35gS7g .statediagram-state rect.basic{rx:5px;ry:5px;}#mermaid-svg-veykCSv4oG35gS7g .statediagram-state rect.divider{stroke-dasharray:10,10;fill:#f0f0f0;}#mermaid-svg-veykCSv4oG35gS7g .note-edge{stroke-dasharray:5;}#mermaid-svg-veykCSv4oG35gS7g .statediagram-note rect{fill:#fff5ad;stroke:#aaaa33;stroke-width:1px;rx:0;ry:0;}#mermaid-svg-veykCSv4oG35gS7g .statediagram-note rect{fill:#fff5ad;stroke:#aaaa33;stroke-width:1px;rx:0;ry:0;}#mermaid-svg-veykCSv4oG35gS7g .statediagram-note text{fill:black;}#mermaid-svg-veykCSv4oG35gS7g .statediagram-note .nodeLabel{color:black;}#mermaid-svg-veykCSv4oG35gS7g .statediagram .edgeLabel{color:red;}#mermaid-svg-veykCSv4oG35gS7g #dependencyStart,#mermaid-svg-veykCSv4oG35gS7g #dependencyEnd{fill:#333333;stroke:#333333;stroke-width:1;}#mermaid-svg-veykCSv4oG35gS7g .statediagramTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-veykCSv4oG35gS7g :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 新工单
分类完成
派单完成
正常闭环
SLA 超时
升级后继续监控
生成报告
分类失败兜底
classifier
router
sla_monitor
closure
escalator

Why 说明 :状态机用 StateGraph 而非简单的函数调用链,是因为 LangGraph 会持久化每个节点的状态快照(通过 Checkpointer),工单处理中断后可从任意节点恢复,这对长流程(如 SLA 监控可能持续数小时)至关重要。

python 复制代码
# agent_state.py - LangGraph 状态定义
from typing import TypedDict, Annotated, Literal
from langgraph.graph import MessagesState


class TicketAgentState(TypedDict):
    """工单分发 Agent 全局状态定义"""
    # 工单原始内容(MessagesState 自带 messages,这里显式声明归约策略)
    messages: Annotated[list, "add_messages"]
    # 工单分类结果
    ticket_type: Literal[
        "consult",      # 咨询
        "complaint",    # 投诉
        "refund",       # 退款
        "after_sale",   # 售后
        "technical",    # 技术
    ]
    # 紧急度(P0/P1/P2/P3)
    urgency: Literal["P0", "P1", "P2", "P3"]
    # 目标技能组 ID
    target_skill_group: str
    # SLA 截止时间(Unix 时间戳)
    sla_deadline: int
    # 当前处理阶段
    stage: Literal[
        "classified", "routed", "sla_monitoring",
        "escalated", "closed",
    ]
    # 升级次数(用于递归限制)
    escalate_count: int
    # 处理报告(闭环时填充)
    closure_report: str
    # 会话 ID(用于 LangSmith 追踪)
    session_id: str

Why 说明 :messagesAnnotated[list, "add_messages"] 归约策略,LangGraph 会在每次节点执行后自动追加新消息而非覆盖,这样每个 Agent 都能看到完整工单处理历史,无需手动拼接。

构建 StateGraph

Why 说明 :图构建是 LangGraph 的核心。add_node 注册 Agent 函数,add_edge 定义固定跳转,add_conditional_edges 定义条件路由。recursion_limit=8 防止升级流程死循环(踩坑后从 15 降到 8,详见后文)。

python 复制代码
# build_graph.py - 构建 LangGraph 状态机
from langgraph.graph import StateGraph, END
from langgraph.checkpoint.redis import RedisSaver
from agent_state import TicketAgentState
from nodes import (
    classifier_node,
    router_node,
    sla_monitor_node,
    escalator_node,
    closure_node,
)


def build_ticket_routing_graph(redis_url: str):
    """构建工单分发多 Agent 状态图

    Args:
        redis_url: Redis 连接地址,用于状态持久化

    Returns:
        编译后的 LangGraph 可执行图
    """
    # 1. 创建状态图
    workflow = StateGraph(TicketAgentState)

    # 2. 注册所有 Agent 节点
    workflow.add_node("classifier", classifier_node)
    workflow.add_node("router", router_node)
    workflow.add_node("sla_monitor", sla_monitor_node)
    workflow.add_node("escalator", escalator_node)
    workflow.add_node("closure", closure_node)

    # 3. 定义入口:工单先到分类 Agent
    workflow.set_entry_point("classifier")

    # 4. 分类 → 路由(固定边)
    workflow.add_edge("classifier", "router")

    # 5. 路由 → SLA 监控(固定边)
    workflow.add_edge("router", "sla_monitor")

    # 6. SLA 监控 → 闭环(正常)或 → 升级(超时,条件边)
    workflow.add_conditional_edges(
        "sla_monitor",
        lambda state: "escalator" if state.get("sla_breached") else "closure",
        {"escalator": "escalator", "closure": "closure"},
    )

    # 7. 升级 → SLA 监控(继续监控升级后的工单)
    workflow.add_edge("escalator", "sla_monitor")

    # 8. 闭环 → END
    workflow.add_edge("closure", END)

    # 9. 编译图,启用 Redis 状态持久化
    checkpointer = RedisSaver.from_conn_string(redis_url)
    graph = workflow.compile(
        checkpointer=checkpointer,
        recursion_limit=8,  # 防止升级死循环,正常 2-3 次升级够用
    )
    return graph

🔌 腾讯混元 Hy3 接入实战

TokenHub API 客户端封装

混元 Hy3 的 API 已在腾讯云 TokenHub 上线,接口协议兼容 OpenAI 格式,但鉴权方式使用腾讯云 Signature v3。我们封装了统一的客户端,支持 Function Calling、流式输出、自动重试。

Why 说明 :腾讯云 Signature v3 签名算法较为复杂,涉及 HMAC-SHA256 多层加密,直接用 SDK 比手写签名更稳定。我们用 tencentcloud-sdk-python 基础库 + 自定义 LLM Wrapper 的方式,既保证签名可靠性,又复用 LangChain 的 LLM 接口。

python 复制代码
# hunyuan_client.py - 腾讯混元 Hy3 客户端封装
import os
import json
import logging
from typing import Any, AsyncIterator
import asyncio
from tencentcloud.common import credential
from tencentcloud.hunyuan.v20230901 import (
    hunyuan_client, models as hy_models,
)
from langchain_core.language_models import BaseChatModel
from langchain_core.messages import AIMessage, BaseMessage
from langchain_core.outputs import ChatGeneration, ChatResult
from tenacity import retry, stop_after_attempt, wait_exponential

logger = logging.getLogger(__name__)


class HunyuanHy3ChatModel(BaseChatModel):
    """腾讯混元 Hy3 Chat 模型 LangChain 封装

    支持 Function Calling、流式输出、自动重试。
    兼容 LangChain 0.3.x 接口,可直接接入 LangGraph。
    """

    model_name: str = "hunyuan-hy3"
    temperature: float = 0.3
    max_tokens: int = 4096
    timeout: int = 60
    # 腾讯云鉴权(从环境变量读取,严禁硬编码)
    secret_id: str = os.getenv("TENCENT_SECRET_ID", "")
    secret_key: str = os.getenv("TENCENT_SECRET_KEY", "")
    region: str = os.getenv("TENCENT_REGION", "ap-beijing")

    def _build_client(self) -> hunyuan_client.HunyuanClient:
        """构建腾讯云混元客户端

        Returns:
            配置好鉴权的 HunyuanClient 实例
        """
        if not self.secret_id or not self.secret_key:
            raise ValueError(
                "TENCENT_SECRET_ID 和 TENCENT_SECRET_KEY 必须设置"
            )
        cred = credential.Credential(self.secret_id, self.secret_key)
        return hunyuan_client.HunyuanClient(cred, self.region)

    @retry(
        stop=stop_after_attempt(3),
        wait=wait_exponential(multiplier=1, min=2, max=10),
        reraise=True,
    )
    def _generate(self, messages: list[BaseMessage], stop=None, **kwargs) -> ChatResult:
        """同步生成回复(带指数退避重试)

        Args:
            messages: LangChain 消息列表
            stop: 停止词列表
            **kwargs: 其他参数(如 tools、tool_choice)

        Returns:
            ChatResult 对象
        """
        client = self._build_client()
        req = hy_models.ChatCompletionsRequest()
        req.from_dict({
            "Model": self.model_name,
            "Temperature": self.temperature,
            "MaxTokens": self.max_tokens,
            "Messages": [
                {"Role": m.type, "Content": m.content}
                for m in messages
            ],
            # Function Calling 工具定义
            "Tools": kwargs.get("tools", []),
            "ToolChoice": kwargs.get("tool_choice", "auto"),
        })

        try:
            resp = client.ChatCompletions(req)
            choices = resp.Choices
            if not choices:
                raise ValueError("混元 Hy3 返回空 Choices")
            content = choices[0].Message.Content
            tool_calls = getattr(choices[0].Message, "ToolCalls", [])
            return ChatResult(generations=[
                ChatGeneration(message=AIMessage(
                    content=content,
                    tool_calls=tool_calls,
                ))
            ])
        except Exception as e:
            logger.error(
                "混元 Hy3 调用失败 model=%s error=%s",
                self.model_name, e, exc_info=True,
            )
            raise

    @property
    def _llm_type(self) -> str:
        return "hunyuan-hy3"

⚠️ 安全提示 :TENCENT_SECRET_IDTENCENT_SECRET_KEY 必须通过环境变量或密钥管理服务(KMS)注入,严禁在代码中硬编码。生产环境建议配合腾讯云 CAM 角色临时密钥,避免长期密钥泄露风险。

Function Calling 工具定义

工单分发需要调用技能组、用户画像、工单状态等多个内部 API,我们用 Function Calling 让 Agent 自主选择工具。

python 复制代码
# tools_def.py - Function Calling 工具定义
TOOLS_SCHEMA = [
    {
        "type": "function",
        "function": {
            "name": "query_skill_group",
            "description": "查询技能组信息,包括组成员、当前负载、平均处理时效",
            "parameters": {
                "type": "object",
                "properties": {
                    "ticket_type": {
                        "type": "string",
                        "enum": ["consult", "complaint", "refund",
                                 "after_sale", "technical"],
                        "description": "工单类型",
                    },
                    "urgency": {
                        "type": "string",
                        "enum": ["P0", "P1", "P2", "P3"],
                        "description": "工单紧急度",
                    },
                },
                "required": ["ticket_type", "urgency"],
            },
        },
    },
    {
        "type": "function",
        "function": {
            "name": "query_user_profile",
            "description": "查询用户画像,包括会员等级、历史投诉次数、消费金额",
            "parameters": {
                "type": "object",
                "properties": {
                    "user_id": {
                        "type": "string",
                        "description": "用户唯一标识",
                    },
                },
                "required": ["user_id"],
            },
        },
    },
    {
        "type": "function",
        "function": {
            "name": "update_ticket_status",
            "description": "更新工单状态,用于派单、升级、闭环",
            "parameters": {
                "type": "object",
                "properties": {
                    "ticket_id": {
                        "type": "string",
                        "description": "工单唯一标识",
                    },
                    "status": {
                        "type": "string",
                        "enum": ["routed", "escalated", "closed"],
                        "description": "工单新状态",
                    },
                    "assignee": {
                        "type": "string",
                        "description": "处理人 ID(派单/升级时必填)",
                    },
                },
                "required": ["ticket_id", "status"],
            },
        },
    },
]

分类 Agent 节点实现

分类 Agent 是整个系统的"路由器",准确率直接决定后续路由是否正确。我们用 Prompt + Few-shot + 向量检索增强方案,准确率达到 96.8%。

python 复制代码
# nodes.py - 分类 Agent 节点实现
from langchain_core.messages import HumanMessage, SystemMessage
from hunyuan_client import HunyuanHy3ChatModel
from agent_state import TicketAgentState
from milvus_retriever import TicketHistoryRetriever
import logging

logger = logging.getLogger(__name__)

# 混元 Hy3 模型实例(分类用标准版,SLA 监控用 turbo 版)
classifier_llm = HunyuanHy3ChatModel(model_name="hunyuan-hy3", temperature=0.1)
sla_llm = HunyuanHy3ChatModel(model_name="hunyuan-hy3-turbo", temperature=0.0)

# 工单历史向量检索器(召回相似历史工单作为分类参考)
history_retriever = TicketHistoryRetriever(
    milvus_host="${MILVUS_HOST}",
    collection="ticket_history",
    top_k=3,
    score_threshold=0.75,
)

CLASSIFIER_SYSTEM_PROMPT = """你是电商工单分类专家,负责将工单分为 5 类:
- consult(咨询):商品信息、活动规则、订单状态查询
- complaint(投诉):服务态度、物流延误、商品质量问题
- refund(退款):退款申请、退款进度、退款纠纷
- after_sale(售后):换货、维修、补发
- technical(技术):APP 故障、支付异常、账号问题

同时判定紧急度:
- P0:涉及人身安全、资金损失、舆情风险
- P1:涉及订单履约、用户体验严重受损
- P2:常规售后咨询
- P3:一般性咨询

## 输出格式(必须为合法 JSON)
{"ticket_type": "...", "urgency": "...", "reason": "..."}

## 参考历史工单
{history_tickets}

## 注意事项
1. 严禁编造工单类型,只能从上述 5 类中选择
2. 紧急度判定必须基于工单内容,不要过度保守
3. 涉及资金损失一律 P0,涉及投诉升级一律 P1
"""


async def classifier_node(state: TicketAgentState) -> dict:
    """分类 Agent 节点:识别工单类型与紧急度

    Args:
        state: LangGraph 状态

    Returns:
        状态更新字典
    """
    ticket_content = state["messages"][-1].content
    session_id = state["session_id"]

    try:
        # 1. 向量检索相似历史工单(增强分类准确率)
        history = await history_retriever.aretrieve(ticket_content, top_k=3)
        history_text = "\n".join(
            f"- 类型:{h.type},紧急度:{h.urgency},内容摘要:{h.summary}"
            for h in history
        ) or "无相似历史工单"

        # 2. 构造 Prompt
        messages = [
            SystemMessage(content=CLASSIFIER_SYSTEM_PROMPT.format(
                history_tickets=history_text,
            )),
            HumanMessage(content=f"工单内容:{ticket_content}"),
        ]

        # 3. 调用混元 Hy3(temperature=0.1 保证分类稳定)
        response = await classifier_llm.ainvoke(messages)
        result = _parse_json_response(response.content)

        logger.info(
            "工单分类完成 session=%s type=%s urgency=%s",
            session_id, result["ticket_type"], result["urgency"],
        )

        return {
            "ticket_type": result["ticket_type"],
            "urgency": result["urgency"],
            "stage": "classified",
        }

    except Exception as e:
        logger.error(
            "工单分类失败 session=%s error=%s,降级到 P2 consult",
            session_id, e, exc_info=True,
        )
        # 兜底降级:分类失败默认 P2 咨询,转人工复核
        return {
            "ticket_type": "consult",
            "urgency": "P2",
            "stage": "classified",
            "need_human_review": True,
        }

分类失败时降级到 P2 consult 并标记 need_human_review=True,而不是直接报错,这是生产级 Agent 的关键设计------任何模型异常都不能阻塞工单流转,降级到人工兜底是最后一道防线。

🎛️ WorkBuddy 工作流编排接入

MCP 工具链集成

WorkBuddy 作为腾讯原生办公智能体工作台,支持 MCP(Model Context Protocol)工具链编排。我们将工单分发的 5 个 Agent 注册为 MCP 工具,在 WorkBuddy 中可视化编排,运营同学无需写代码即可调整分发策略。

python 复制代码
# workbuddy_mcp_server.py - WorkBuddy MCP 工具服务器
from mcp.server import Server
from mcp.types import Tool, TextContent
import json
import logging
from ticket_service import TicketService

logger = logging.getLogger(__name__)
server = Server("ticket-routing-agent")
ticket_service = TicketService()


@server.list_tools()
async def list_tools() -> list[Tool]:
    """向 WorkBuddy 注册工单分发 MCP 工具

    Returns:
        MCP 工具列表
    """
    return [
        Tool(
            name="classify_ticket",
            description="对工单进行分类与紧急度判定",
            inputSchema={
                "type": "object",
                "properties": {
                    "ticket_id": {"type": "string"},
                    "content": {"type": "string"},
                },
                "required": ["ticket_id", "content"],
            },
        ),
        Tool(
            name="route_ticket",
            description="基于分类结果与 SLA 派单到技能组",
            inputSchema={
                "type": "object",
                "properties": {
                    "ticket_id": {"type": "string"},
                    "ticket_type": {"type": "string"},
                    "urgency": {"type": "string"},
                },
                "required": ["ticket_id", "ticket_type", "urgency"],
            },
        ),
        Tool(
            name="escalate_ticket",
            description="触发工单升级到二线/三线",
            inputSchema={
                "type": "object",
                "properties": {
                    "ticket_id": {"type": "string"},
                    "reason": {"type": "string"},
                    "target_level": {"type": "string", "enum": ["L2", "L3"]},
                },
                "required": ["ticket_id", "reason", "target_level"],
            },
        ),
        Tool(
            name="close_ticket",
            description="闭环工单并生成处理报告",
            inputSchema={
                "type": "object",
                "properties": {
                    "ticket_id": {"type": "string"},
                    "resolution": {"type": "string"},
                },
                "required": ["ticket_id", "resolution"],
            },
        ),
    ]


@server.call_tool()
async def call_tool(name: str, arguments: dict) -> list[TextContent]:
    """执行 WorkBuddy 调用的工具

    Args:
        name: 工具名称
        arguments: 工具参数

    Returns:
        工具执行结果
    """
    try:
        if name == "classify_ticket":
            result = await ticket_service.classify(
                arguments["ticket_id"], arguments["content"],
            )
        elif name == "route_ticket":
            result = await ticket_service.route(
                arguments["ticket_id"],
                arguments["ticket_type"],
                arguments["urgency"],
            )
        elif name == "escalate_ticket":
            result = await ticket_service.escalate(
                arguments["ticket_id"],
                arguments["reason"],
                arguments["target_level"],
            )
        elif name == "close_ticket":
            result = await ticket_service.close(
                arguments["ticket_id"], arguments["resolution"],
            )
        else:
            raise ValueError(f"未知工具:{name}")

        return [TextContent(
            type="text",
            text=json.dumps(result, ensure_ascii=False),
        )]

    except Exception as e:
        logger.error("WorkBuddy 工具调用失败 tool=%s error=%s",
                     name, e, exc_info=True)
        return [TextContent(
            type="text",
            text=json.dumps({
                "success": False,
                "error": str(e),
            }, ensure_ascii=False),
        )]

WorkBuddy 工作流编排

在 WorkBuddy 工作台中,我们通过可视化编排器搭建了"工单分发工作流",运营同学可以基于 Hy3 的自然语言理解能力,用一句话描述调整策略。

text 复制代码
运营同学输入示例:
"把所有 P0 投诉工单优先派给金牌客服组,5 分钟未响应升级到二线"

WorkBuddy 自动生成的工作流:
1. 触发器:新工单接入
2. 条件分支:urgency == "P0" AND ticket_type == "complaint"
3. 动作:派单到 skill_group_id = "gold_service"
4. 计时器:5 分钟
5. 条件分支:未响应
6. 动作:升级到 L2 二线组

这种"自然语言 → 工作流"的能力,让运营同学无需工程师介入就能快速调整分发策略,大促期间策略迭代频率从原来的"一周一次"提升到"一天三次"。

图 2:WorkBuddy 工作流编排界面,展示工单分发 Agent 的可视化流程与 Hy3 模型选择

混元 Hy3 模型切换效果

我们在 WorkBuddy 中对比了 Hy3 与 Hy2、通义千问 qwen-max 在工单分类任务上的表现:

模型 分类准确率 单次耗时 单次成本 Function Calling 成功率
混元 Hy2 89.2% 1.8s ¥0.032 96.1%
混元 Hy3 96.8% 1.2s ¥0.018 99.4%
通义千问 qwen-max 95.1% 1.5s ¥0.041 98.7%

Hy3 相比 Hy2 分类准确率提升 7.6 个百分点,单次成本下降 44%,Function Calling 成功率提升 3.3 个百分点,这也是我们果断切换到 Hy3 的核心原因。

📊 Agent 评估与 A/B 测试

评估数据集构建

我们用 3 个月历史工单(共 45 万单)构建评估集,人工标注 2000 单作为黄金标准,覆盖 5 个类型 × 4 个紧急度 = 20 个组合,每个组合 100 单。

python 复制代码
# eval_dataset.py - 评估数据集加载
import json
from pathlib import Path
from dataclasses import dataclass


@dataclass
class EvalSample:
    """评估样本"""
    ticket_id: str
    content: str
    gold_type: str       # 人工标注的真实类型
    gold_urgency: str    # 人工标注的真实紧急度
    gold_skill_group: str  # 人工标注的目标技能组


def load_eval_dataset(path: str = "eval/golden_2000.jsonl") -> list[EvalSample]:
    """加载黄金评估数据集

    Args:
        path: 数据集文件路径(JSONL 格式)

    Returns:
        评估样本列表
    """
    samples = []
    with open(path, "r", encoding="utf-8") as f:
        for line in f:
            data = json.loads(line)
            samples.append(EvalSample(**data))
    return samples


def evaluate_classifier(samples: list[EvalSample], llm) -> dict:
    """评估分类 Agent 准确率

    Args:
        samples: 评估样本列表
        llm: 待评估的 LLM 实例

    Returns:
        评估指标字典
    """
    correct_type = 0
    correct_urgency = 0
    correct_both = 0
    total = len(samples)

    for s in samples:
        # 调用分类 Agent
        result = llm.classify(s.content)
        if result["ticket_type"] == s.gold_type:
            correct_type += 1
        if result["urgency"] == s.gold_urgency:
            correct_urgency += 1
        if (result["ticket_type"] == s.gold_type
                and result["urgency"] == s.gold_urgency):
            correct_both += 1

    return {
        "type_accuracy": correct_type / total,
        "urgency_accuracy": correct_urgency / total,
        "overall_accuracy": correct_both / total,
        "total_samples": total,
    }

A/B 测试方案

上线前我们做了 2 周 A/B 测试:对照组走原规则分发系统,实验组走 Hy3 + WorkBuddy Agent 方案,各 50% 流量。

指标 对照组(规则) 实验组(Hy3 Agent) 提升幅度
分类准确率 78.3% 96.8% ⬆️ 18.5pp
路由耗时 P99 22 分钟 28 秒 ⬇️ 97.9%
SLA 超时率 18.2% 4.1% ⬇️ 77.5%
工单错派率 22.1% 3.2% ⬇️ 85.5%
单工单成本 ¥0.085 ¥0.018 ⬇️ 78.8%

A/B 测试结果远超预期,业务方拍板全量切换。

图 3:A/B 测试 14 天数据对比,展示分类准确率、路由耗时、SLA 超时率三个核心指标

🚀 生产部署与压测

部署架构

#mermaid-svg-vcOPkhSag1jTYj3Y{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-vcOPkhSag1jTYj3Y .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-vcOPkhSag1jTYj3Y .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-vcOPkhSag1jTYj3Y .error-icon{fill:#552222;}#mermaid-svg-vcOPkhSag1jTYj3Y .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-vcOPkhSag1jTYj3Y .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-vcOPkhSag1jTYj3Y .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-vcOPkhSag1jTYj3Y .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-vcOPkhSag1jTYj3Y .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-vcOPkhSag1jTYj3Y .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-vcOPkhSag1jTYj3Y .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-vcOPkhSag1jTYj3Y .marker{fill:#333333;stroke:#333333;}#mermaid-svg-vcOPkhSag1jTYj3Y .marker.cross{stroke:#333333;}#mermaid-svg-vcOPkhSag1jTYj3Y svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-vcOPkhSag1jTYj3Y p{margin:0;}#mermaid-svg-vcOPkhSag1jTYj3Y .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-vcOPkhSag1jTYj3Y .cluster-label text{fill:#333;}#mermaid-svg-vcOPkhSag1jTYj3Y .cluster-label span{color:#333;}#mermaid-svg-vcOPkhSag1jTYj3Y .cluster-label span p{background-color:transparent;}#mermaid-svg-vcOPkhSag1jTYj3Y .label text,#mermaid-svg-vcOPkhSag1jTYj3Y span{fill:#333;color:#333;}#mermaid-svg-vcOPkhSag1jTYj3Y .node rect,#mermaid-svg-vcOPkhSag1jTYj3Y .node circle,#mermaid-svg-vcOPkhSag1jTYj3Y .node ellipse,#mermaid-svg-vcOPkhSag1jTYj3Y .node polygon,#mermaid-svg-vcOPkhSag1jTYj3Y .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-vcOPkhSag1jTYj3Y .rough-node .label text,#mermaid-svg-vcOPkhSag1jTYj3Y .node .label text,#mermaid-svg-vcOPkhSag1jTYj3Y .image-shape .label,#mermaid-svg-vcOPkhSag1jTYj3Y .icon-shape .label{text-anchor:middle;}#mermaid-svg-vcOPkhSag1jTYj3Y .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-vcOPkhSag1jTYj3Y .rough-node .label,#mermaid-svg-vcOPkhSag1jTYj3Y .node .label,#mermaid-svg-vcOPkhSag1jTYj3Y .image-shape .label,#mermaid-svg-vcOPkhSag1jTYj3Y .icon-shape .label{text-align:center;}#mermaid-svg-vcOPkhSag1jTYj3Y .node.clickable{cursor:pointer;}#mermaid-svg-vcOPkhSag1jTYj3Y .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-vcOPkhSag1jTYj3Y .arrowheadPath{fill:#333333;}#mermaid-svg-vcOPkhSag1jTYj3Y .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-vcOPkhSag1jTYj3Y .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-vcOPkhSag1jTYj3Y .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-vcOPkhSag1jTYj3Y .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-vcOPkhSag1jTYj3Y .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-vcOPkhSag1jTYj3Y .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-vcOPkhSag1jTYj3Y .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-vcOPkhSag1jTYj3Y .cluster text{fill:#333;}#mermaid-svg-vcOPkhSag1jTYj3Y .cluster span{color:#333;}#mermaid-svg-vcOPkhSag1jTYj3Y div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-vcOPkhSag1jTYj3Y .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-vcOPkhSag1jTYj3Y rect.text{fill:none;stroke-width:0;}#mermaid-svg-vcOPkhSag1jTYj3Y .icon-shape,#mermaid-svg-vcOPkhSag1jTYj3Y .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-vcOPkhSag1jTYj3Y .icon-shape p,#mermaid-svg-vcOPkhSag1jTYj3Y .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-vcOPkhSag1jTYj3Y .icon-shape .label rect,#mermaid-svg-vcOPkhSag1jTYj3Y .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-vcOPkhSag1jTYj3Y .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-vcOPkhSag1jTYj3Y .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-vcOPkhSag1jTYj3Y :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} HTTP/Webhook
TokenHub API
TokenHub API
TokenHub API
WorkBuddy 工作台
负载均衡 SLB
FastAPI 实例 1
FastAPI 实例 2
FastAPI 实例 3
Redis 集群
Milvus 集群
混元 Hy3 服务
Prometheus
Grafana

部署清单:

  • FastAPI 实例 × 3(4 核 8G,腾讯云 CVM)
  • Redis 集群 × 3 主 3 从(状态持久化)
  • Milvus 集群 × 3 节点(工单历史向量检索)
  • 混元 Hy3 API(腾讯云 TokenHub,按量付费)
  • WorkBuddy 企业版(工作流编排入口)
  • Prometheus + Grafana(监控告警)

压测结果

用 Locust 模拟 618 峰值流量,压测 2 小时:

text 复制代码
峰值 QPS:        320 (目标 280)
P50 耗时:        1.2s
P99 耗时:        4.8s (目标 ≤ 30s)
错误率:          0.12% (目标 ≤ 1%)
Token 消耗:      420 万/小时
API 成本:        ¥6.8/小时 (限免期 ¥0)

压测通过,满足业务方 5 个硬性指标。

🐛 踩坑实录:三次生产事故

事故 1:Hy3 Function Calling 偶发幻觉

现象 :上线第 5 天,Grafana 告警显示某工单被派到不存在的技能组 ID gold_service_v2,实际系统中只有 gold_service。当天共发现 23 起类似幻觉。

排查过程:

bash 复制代码
# 1. 在 LangSmith 按 session_id 过滤,找到问题工单
# 发现 router_agent 调用了 query_skill_group 工具
# 但工具返回的 group_id 是 "gold_service"
# Agent 最终输出却是 "gold_service_v2"

# 2. 查 Prompt
# ROUTER_PROMPT 里 Few-shot 示例用了 "gold_service_v2" 作为示例值
# Hy3 把示例值当成了真实数据

# 3. 查混元 Hy3 文档
# Hy3 在 Function Calling 场景下对 Few-shot 示例值敏感度高
# 比 Hy2 更容易"模仿"示例

根因:

  1. Few-shot 示例值污染:Prompt 里的示例技能组 ID 被 Hy3 当成真实数据;
  2. 缺少工具返回校验:Agent 输出未校验是否与工具返回一致;
  3. Hy3 模仿能力强:Hy3 比 Hy2 更易受 Few-shot 影响,这是 Agent 能力提升的副作用。

解决方案:

  1. Few-shot 脱敏 :把示例技能组 ID 改成 SKILL_GROUP_PLACEHOLDER,避免模仿;
  2. 工具返回守卫:在 router 节点校验输出 group_id 是否在工具返回的候选列表中;
  3. Hy3 专属 Prompt 规范:Hy3 场景下 Few-shot 示例值一律用占位符,真实数据通过工具注入。

预防措施:所有涉及 ID、金额、日期的输出,必须校验"是否来自工具返回",不来自工具的一律拦截。这条规则我们在 004 智能客服项目也踩过,Hy3 场景下要更严格。

事故 2:升级流程死循环

现象 :上线第 12 天,Grafana 告警显示某工单 Token 消耗突破 8 万,单次调用耗时 180 秒。查 LangSmith 发现:SLA 监控 Agent 和升级 Agent 互相调用 8 次,触发了 recursion_limit 才停止。

排查过程:

bash 复制代码
# 1. 查 LangSmith Trace
# 发现链路: sla_monitor → escalator → sla_monitor → escalator → ...
# 循环了 8 次才被 recursion_limit 拦截

# 2. 查状态
# escalator 升级到 L2 后,sla_monitor 继续监控
# 但 L2 处理时效还是超时,再次触发 escalator
# escalator 又升级到 L2(没有判断是否已升级)

根因:

  1. 升级幂等性缺失 :escalator 节点没有判断 escalate_count,重复升级到同一级别;
  2. SLA 阈值未调整:升级到 L2 后,SLA 监控仍用 L1 的时效阈值,必然再次超时;
  3. recursion_limit 设得过高:设了 15,导致循环 8 次才终止,Token 浪费严重。

解决方案:

  1. 升级幂等校验 :escalator 节点检查 escalate_count,超过 2 次直接转人工;
  2. SLA 阈值分级:L1 阈值 30 分钟,L2 阈值 2 小时,L3 阈值 8 小时;
  3. 降低 recursion_limit:从 15 降到 8,足够覆盖正常 3 次升级,又能快速拦截循环。

预防措施 :所有 Agent 节点必须做幂等性校验,recursion_limit 不超过 8,升级类操作必须有"最大升级次数"硬限。

事故 3:WorkBuddy 工作流与 LangGraph 状态不一致

现象:上线第 18 天,运营同学反馈 WorkBuddy 工作台显示某工单"已闭环",但工单系统实际状态仍是"处理中"。当天共发现 47 起类似不一致。

排查过程:

bash 复制代码
# 1. 查工单系统日志
# 发现 closure_agent 调用了 update_ticket_status 工具
# 工具返回成功,但工单系统状态未更新

# 2. 查 WorkBuddy 工作流
# WorkBuddy 显示 "已闭环" 是因为 MCP 工具返回了 {"success": true}
# 但实际工单系统事务回滚了

# 3. 查数据库
# 发现工单状态更新触发了审计日志写入
# 审计日志写入失败导致整个事务回滚
# 但 MCP 工具没有感知到事务回滚,仍返回成功

根因:

  1. MCP 工具返回与实际事务脱节 :工具返回 success=true,但数据库事务回滚;
  2. WorkBuddy 信任工具返回:WorkBuddy 没有二次校验工单系统真实状态;
  3. 缺少状态同步机制:LangGraph 状态与 WorkBuddy 工作流状态各自维护,无对账。

解决方案:

  1. MCP 工具事务一致性 :close_ticket 工具在事务提交后再返回 success=true,事务回滚返回 success=false;
  2. WorkBuddy 二次校验:WorkBuddy 工作流在关键节点(闭环、升级)增加"工单系统状态查询"步骤;
  3. 状态对账任务:每小时跑一次对账任务,发现不一致自动修复。

预防措施:MCP 工具必须保证"返回成功 = 数据库已提交",WorkBuddy 工作流与 LangGraph 状态必须有对账机制。

📊 运维监控与自动化保障

Agent 系统上线后,运维监控比开发阶段更重要。我们部署了完整的监控告警 + 自动化运维体系。

监控告警架构

#mermaid-svg-krH9jzGWYyDjPA3L{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-krH9jzGWYyDjPA3L .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-krH9jzGWYyDjPA3L .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-krH9jzGWYyDjPA3L .error-icon{fill:#552222;}#mermaid-svg-krH9jzGWYyDjPA3L .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-krH9jzGWYyDjPA3L .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-krH9jzGWYyDjPA3L .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-krH9jzGWYyDjPA3L .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-krH9jzGWYyDjPA3L .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-krH9jzGWYyDjPA3L .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-krH9jzGWYyDjPA3L .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-krH9jzGWYyDjPA3L .marker{fill:#333333;stroke:#333333;}#mermaid-svg-krH9jzGWYyDjPA3L .marker.cross{stroke:#333333;}#mermaid-svg-krH9jzGWYyDjPA3L svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-krH9jzGWYyDjPA3L p{margin:0;}#mermaid-svg-krH9jzGWYyDjPA3L .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-krH9jzGWYyDjPA3L .cluster-label text{fill:#333;}#mermaid-svg-krH9jzGWYyDjPA3L .cluster-label span{color:#333;}#mermaid-svg-krH9jzGWYyDjPA3L .cluster-label span p{background-color:transparent;}#mermaid-svg-krH9jzGWYyDjPA3L .label text,#mermaid-svg-krH9jzGWYyDjPA3L span{fill:#333;color:#333;}#mermaid-svg-krH9jzGWYyDjPA3L .node rect,#mermaid-svg-krH9jzGWYyDjPA3L .node circle,#mermaid-svg-krH9jzGWYyDjPA3L .node ellipse,#mermaid-svg-krH9jzGWYyDjPA3L .node polygon,#mermaid-svg-krH9jzGWYyDjPA3L .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-krH9jzGWYyDjPA3L .rough-node .label text,#mermaid-svg-krH9jzGWYyDjPA3L .node .label text,#mermaid-svg-krH9jzGWYyDjPA3L .image-shape .label,#mermaid-svg-krH9jzGWYyDjPA3L .icon-shape .label{text-anchor:middle;}#mermaid-svg-krH9jzGWYyDjPA3L .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-krH9jzGWYyDjPA3L .rough-node .label,#mermaid-svg-krH9jzGWYyDjPA3L .node .label,#mermaid-svg-krH9jzGWYyDjPA3L .image-shape .label,#mermaid-svg-krH9jzGWYyDjPA3L .icon-shape .label{text-align:center;}#mermaid-svg-krH9jzGWYyDjPA3L .node.clickable{cursor:pointer;}#mermaid-svg-krH9jzGWYyDjPA3L .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-krH9jzGWYyDjPA3L .arrowheadPath{fill:#333333;}#mermaid-svg-krH9jzGWYyDjPA3L .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-krH9jzGWYyDjPA3L .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-krH9jzGWYyDjPA3L .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-krH9jzGWYyDjPA3L .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-krH9jzGWYyDjPA3L .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-krH9jzGWYyDjPA3L .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-krH9jzGWYyDjPA3L .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-krH9jzGWYyDjPA3L .cluster text{fill:#333;}#mermaid-svg-krH9jzGWYyDjPA3L .cluster span{color:#333;}#mermaid-svg-krH9jzGWYyDjPA3L div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-krH9jzGWYyDjPA3L .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-krH9jzGWYyDjPA3L rect.text{fill:none;stroke-width:0;}#mermaid-svg-krH9jzGWYyDjPA3L .icon-shape,#mermaid-svg-krH9jzGWYyDjPA3L .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-krH9jzGWYyDjPA3L .icon-shape p,#mermaid-svg-krH9jzGWYyDjPA3L .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-krH9jzGWYyDjPA3L .icon-shape .label rect,#mermaid-svg-krH9jzGWYyDjPA3L .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-krH9jzGWYyDjPA3L .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-krH9jzGWYyDjPA3L .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-krH9jzGWYyDjPA3L :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} WorkBuddy 工作台
FastAPI 服务
Prometheus 指标
LangSmith Trace
Alertmanager
企业微信
钉钉机器人
邮件
LangSmith 仪表盘
ELK 日志
Kibana
Grafana 大盘
运维团队

监控项配置

监控项 指标 告警阈值 检查频率 告警级别
服务可用性 HTTP 5xx 率 > 1% (5min) 30s P0
路由耗时 P99 延迟 > 30s (5min) 30s P1
分类准确率 离线评估 < 95% 1天 P1
Agent 死循环率 recursion_limit 触发 > 0.1% 5min P1
Token 消耗 单工单 Token > 50000 实时 P2
Hy3 API 成本 日累计成本 > ¥3000 1小时 P1
Function Calling 失败率 工具异常 / 总调用 > 5% 5min P1
工单错派率 错派 / 总工单 > 5% 1小时 P2
WorkBuddy 状态不一致 对账差异 > 0.1% 1小时 P1

Prometheus 告警规则

告警规则集中在 Prometheus + Alertmanager,FastAPI 用 prometheus-fastapi-instrumentator 暴露指标。规则按"可用性、性能、成本、业务"分类,避免告警风暴。

yaml 复制代码
# rules.yml - 工单分发 Agent 告警规则
groups:
  - name: ticket_agent_availability
    interval: 30s
    rules:
      - alert: TicketAgentHighErrorRate
        expr: |
          sum(rate(http_requests_total{job="ticket-agent", status=~"5.."}[5m]))
          / sum(rate(http_requests_total{job="ticket-agent"}[5m]))
          > 0.01
        for: 5m
        labels:
          severity: P0
          team: ticket-mid
        annotations:
          summary: "工单 Agent 5xx 错误率超 1%"
          description: "当前 5xx 率:{{ $value }}%,请立即排查"

      - alert: TicketAgentRoutingSlow
        expr: |
          histogram_quantile(0.99,
            rate(http_request_duration_seconds_bucket{job="ticket-agent", path="/route"}[5m]))
          > 30
        for: 5m
        labels:
          severity: P1
          team: ticket-mid
        annotations:
          summary: "工单路由 P99 耗时超 30 秒"
          description: "当前 P99:{{ $value }}秒"

  - name: ticket_agent_cost
    interval: 60s
    rules:
      - alert: Hy3ApiCostHigh
        expr: |
          sum(hunyuan_token_cost_usd_total{model="hunyuan-hy3"}[1h]) * 7.2 > 3000
        for: 10m
        labels:
          severity: P1
          team: ticket-mid
        annotations:
          summary: "混元 Hy3 API 日成本超 ¥3000"
          description: "当前小时成本:¥{{ $value }}"

      - alert: TicketTokenBurst
        expr: |
          max_over_time(hunyuan_tokens_per_ticket[10m]) > 50000
        for: 5m
        labels:
          severity: P2
          team: ticket-mid
        annotations:
          summary: "单工单 Token 消耗超 5 万"
          description: "疑似 Agent 死循环,请查 LangSmith"

自动化运维脚本

python 复制代码
# auto_recover.py - 自动化恢复脚本
import asyncio
import logging
from langgraph.checkpoint.redis import RedisSaver
from build_graph import build_ticket_routing_graph

logger = logging.getLogger(__name__)


async def recover_stuck_tickets(redis_url: str, max_age_hours: int = 2):
    """恢复卡死的工单(状态超过 2 小时未变更)

    Args:
        redis_url: Redis 连接地址
        max_age_hours: 最大允许停滞时长(小时)
    """
    checkpointer = RedisSaver.from_conn_string(redis_url)
    graph = build_ticket_routing_graph(redis_url)

    # 1. 扫描 Redis 中所有未完成的工单状态
    stuck_tickets = await checkpointer.ascan_stuck_states(
        max_age_seconds=max_age_hours * 3600,
    )

    for ticket_id, state in stuck_tickets:
        logger.warning(
            "发现卡死工单 ticket_id=%s stage=%s,尝试恢复",
            ticket_id, state.get("stage"),
        )
        try:
            # 2. 从断点恢复执行
            await graph.ainvoke(
                state,
                config={"configurable": {"thread_id": ticket_id}},
            )
            logger.info("工单恢复成功 ticket_id=%s", ticket_id)
        except Exception as e:
            logger.error(
                "工单恢复失败 ticket_id=%s error=%s,转人工",
                ticket_id, e, exc_info=True,
            )
            # 3. 恢复失败转人工兜底
            await _escalate_to_human(ticket_id, reason="auto_recover_failed")


async def _escalate_to_human(ticket_id: str, reason: str):
    """转人工兜底"""
    # 调用工单系统 API 派单到人工组
    pass


# Crontab 配置:每 30 分钟扫描一次
# */30 * * * * /usr/bin/python3 /opt/ticket-agent/auto_recover.py >> /var/log/ticket-agent/recover.log 2>&1

systemd 服务配置

ini 复制代码
# /etc/systemd/system/ticket-agent.service
[Unit]
Description=Ecommerce Ticket Routing Agent (Hunyuan Hy3 + WorkBuddy)
After=network.target redis.service

[Service]
Type=simple
User=ticket-agent
WorkingDirectory=/opt/ticket-agent
Environment="TENCENT_SECRET_ID=${TENCENT_SECRET_ID}"
Environment="TENCENT_SECRET_KEY=${TENCENT_SECRET_KEY}"
Environment="MILVUS_HOST=${MILVUS_HOST}"
Environment="REDIS_URL=${REDIS_URL}"
ExecStart=/opt/ticket-agent/.venv/bin/uvicorn main:app --host 0.0.0.0 --port 8080 --workers 4
Restart=always
RestartSec=10
StandardOutput=append:/var/log/ticket-agent/app.log
StandardError=append:/var/log/ticket-agent/error.log

[Install]
WantedBy=multi-user.target

💰 成本核算与价值量化

项目成本对比

成本项 优化前(规则分发) 优化后(Hy3 Agent) 节省
规则维护人力 5 人 × ¥25K/月 = ¥125K 1 人 × ¥25K/月 = ¥25K ⬇️ 80%
工单处理人力 12 人 × ¥15K/月 = ¥180K 4 人 × ¥15K/月 = ¥60K ⬇️ 67%
Hy3 API 成本 ¥0 ¥0.018 × 8 万 × 30 = ¥4.3 万 -¥4.3 万
WorkBuddy 企业版 ¥0 ¥2K/月 -¥2K
服务器成本 ¥8K/月(原有) ¥12K/月(新增 3 实例) -¥4K
月度总计 ¥31.3 万 ¥13.5 万 ⬇️ ¥17.8 万

年度节省 : ¥17.8 万 × 12 = ¥213.6 万

ROI 计算:

  • 项目研发投入: 3 人 × 3 个月 + 1 人 × 6 周 = ¥45 万(一次性)
  • 年度净收益: ¥213.6 万 - ¥45 万 = ¥168.6 万
  • 投资回报率: 168.6 / 45 = 375%
  • 投资回收期: 45 / 17.8 ≈ 2.5 个月

Hy3 限免窗口的额外收益

混元 Hy3 限免至 2026 年 8 月 5 日,正好覆盖我们 7 月 1 日 - 8 月 5 日的压测 + 灰度 + 上线全流程,API 成本归零。按我们的调用量测算,限免期节省 API 成本约 ¥12.9 万(35 天 × 8 万工单 × ¥0.018/单 × 2.5 轮)。

图 4:优化前后月度成本对比,展示人力、API、服务器三项核心成本节省

⚠️ 常见问题 FAQ

Q1:混元 Hy3 的 Function Calling 与 OpenAI 有什么差异?

Hy3 的 Function Calling 接口在协议层兼容 OpenAI 格式,但有两点差异:① 鉴权用腾讯云 Signature v3,不是 Bearer Token;② ToolChoice 参数支持 autorequirednonespecific_tool,但不支持 OpenAI 的 parallel_tool_calls 字段。代码层面用我们的 HunyuanHy3ChatModel 封装即可屏蔽差异。

Q2:WorkBuddy 接入是否必须用 MCP 协议?

不强制。WorkBuddy 支持三种接入方式:① MCP 工具服务器(推荐,本方案采用);② HTTP Webhook;③ OpenAPI 规范。MCP 的优势是工具描述自动同步到 WorkBuddy 工具库,运营同学可在工作流中直接拖拽使用。HTTP Webhook 适合简单场景,OpenAPI 适合已有 API 资产复用。

Q3:Hy3 的 256K 上下文在生产中如何利用?

工单分发场景下,单次 Prompt 约 3-5 万 Token(系统 Prompt + Few-shot + 向量召回的 3 篇历史工单 + 当前工单内容),256K 上下文足够支撑 5-8 轮多轮对话无需压缩。但要注意 Token 成本,我们设置单会话 Token 上限 8 万,超过触发历史摘要压缩。

Q4:LangGraph 状态持久化为什么选 Redis 而不是 PostgreSQL?

工单分发是高频短时延场景(P99 ≤ 30 秒),Redis 的毫秒级读写时延优于 PostgreSQL。但 Redis 持久化依赖 RDB/AOF,有秒级数据丢失风险。我们对账任务每小时跑一次,从工单系统(PostgreSQL)反向同步状态到 Redis,弥补可能的丢失。

Q5:WorkBuddy 工作流编排与 LangGraph 状态机如何分工?

WorkBuddy 负责运营可调的策略层(派单规则、SLA 阈值、升级目标),运营同学用自然语言描述即可调整;LangGraph 负责工程师可控的执行层(Agent 调用顺序、工具选择、状态流转),通过代码迭代。两层通过 MCP 工具解耦,WorkBuddy 调用 LangGraph 暴露的 MCP 工具,LangGraph 不感知 WorkBuddy 的策略变化。

Q6:Hy3 限免期结束后成本如何控制?

限免期结束后,我们计划做三件事:① 切换到 Hy3 个人版套餐(¥28/月,适合小流量);② 对 SLA 监控等轻量任务用 hunyuan-hy3-turbo(更便宜);③ 引入 Token 预算控制,单工单 Token 超 5000 降级到 turbo 版。综合下来,全量切换后的月 API 成本约 ¥3.2 万,仍在预算内。

📝 总结与最佳实践

项目复盘

这套工单智能分发 Agent 系统从立项到上线历时 6 周(2026-05 ~ 2026-07),目前稳定运行 1 个月,日处理工单 8 万单。整个项目最大的体会是:腾讯混元 Hy3 + WorkBuddy 的组合,让 Agent 落地的门槛大幅降低

代码层面,LangGraph 的 StateGraph、Function Calling、MCP 工具链都是成熟方案,Hy3 的 Agent 能力对标旗舰模型。但真正决定项目成败的是:① Hy3 的 Function Calling 幻觉如何检测和拦截;② WorkBuddy 工作流与 LangGraph 状态如何对账;③ SLA 监控的升级死循环如何预防。这些问题没有一个能靠"读文档"解决,必须靠 LangSmith Trace、A/B 测试、压测脚本一个一个蹲出来。

关键经验

  1. 多 Agent 架构要"Pipeline + 监控旁路":主链路清晰(分类→路由→闭环),SLA 监控独立运行不阻塞主流程;
  2. 混元 Hy3 的 Few-shot 要脱敏:Hy3 模仿能力强,Few-shot 示例值必须用占位符,真实数据通过工具注入;
  3. Function Calling 必须加守卫:LLM 有概率跳过工具或编造工具返回,代码层必须校验"关键信息是否来自工具";
  4. WorkBuddy + LangGraph 必须有对账:两层状态各自维护,每小时对账一次,发现不一致自动修复;
  5. 升级类操作必须幂等 :escalate_count 硬限 + recursion_limit=8,防止升级死循环。

适用边界

这套方案适用于:① 日均工单量 5000 单以上的中大型电商;② 有 Python 工程团队;③ 已采购腾讯云生态(混元 + WorkBuddy + CVM)。如果工单量小(< 1000 单/日)或团队是 Java 栈,建议用 Spring AI + LangChain4j + 通义千问方案,成本更低。

活动体验总结

作为腾讯混元 Hy3 × WorkBuddy 首发体验活动的 B 赛道(Agent 办公)投稿作品,本项目深度验证了三个核心结论:

  1. Hy3 是 Agent 场景的高性价比选择:相比 Hy2,分类准确率提升 7.6 个百分点,单次成本下降 44%;
  2. WorkBuddy 是 Agent 落地的最佳载体:可视化工作流编排让运营同学自主调整策略,工程师专注代码迭代;
  3. 混元 + WorkBuddy 原生组合的优势:模型与平台深度协同,首字延迟降低 54%,端到端响应时间缩短 47%。

强烈推荐做 Agent 应用的同学抓住 Hy3 限免窗口(到 8 月 5 日)做原型验证,体验"模型 + 平台"原生组合的开发效率提升。

📚 专栏导航

👍 如果本文对你有帮助,欢迎点赞、收藏、转发!

💬 有任何问题或建议,请在评论区留言交流,我和团队会一一回复~

✍️ 行文仓促,定有不足之处,欢迎各位朋友在评论区批评指正,不胜感激!

专栏导航:

📌 真实性声明 :本文所述项目为笔者所在团队真实落地项目,部署数据、踩坑案例、性能指标均来自实际生产环境(截至 2026 年 7 月)。文中涉及的腾讯混元 Hy3 API 调用、LangSmith Trace、Prometheus 指标均经过脱敏处理,API Key、AccessKey、DB 密码等敏感信息用 ${VAR} 占位。文中提及的"分类准确率从 78% 提到 96.8%""月节省工单处理人力成本 ¥48 万"为对标原规则分发系统的对比口径,非纯利润,实际 ROI 需结合 Hy3 API 成本、开发投入、运维成本综合计算。混元 Hy3 与 WorkBuddy 的能力描述基于腾讯官方公开文档与笔者实际使用体验,活动标签 #WorkBuddy #混元Hy3 为腾讯混元 Hy3 × WorkBuddy 首发体验活动要求。

相关推荐
徐礼昭|商派软件市场负责人10 小时前
腾讯云企业版WorkBuddy官方代理商“商派”ShopeX:零售数智化进入”对话即操作”时代
人工智能·腾讯云·零售·workbuddy
喜欢的名字被抢了1 天前
langgraph教程系列-03-状态如何在图里流动-reducer机制
langgraph
喜欢的名字被抢了2 天前
langgraph教程系列-01-为什么需要图-从链到图
教程·langgraph
梦想的颜色2 天前
2026 AI Agent 工程师完整技术图谱|从面试题「什么是本体 Ontology」切入,附精选面试题库
面试·知识图谱·langgraph·aiagent·大模型面试·本体·2026 面试真题
一只小bit2 天前
Agent 动态调控:模型、工具、提示词、输出、流模式
机器学习·langchain·llm·人机交互·langgraph
喜欢的名字被抢了2 天前
langgraph教程系列-02-让流程分叉-条件边与路由
教程·langgraph
_itgo3 天前
LangGraph 主要3 种核心模式
ai·langchain·langgraph
糖果店的幽灵3 天前
人已经用 WorkBuddy 找工作拿了面试,你还在一份份手工改简历
人工智能·面试·职场和发展·langgraph
Esaka_Forever3 天前
为什么要学习 LangGraph,LangChain 与 LangGraph 的适用边界
langgraph