ByteNoteByteNote
实战一:从 0 写一个客服 Agent
字

字节笔记本

2026年10月24日 · 约 15 分钟读完

实战一:从 0 写一个客服 Agent

API中转
¥120

把 Agent 产出变成可分享页面

本文是《从 0 写 Agent》系列第 45 章。这个系列基于开源项目 agents-go(OpenAI Agents SDK 的 Go 移植版),用 Go 从零实现一个生产级 Agent 运行时;本章讲「实战一:从 0 写一个客服 Agent」。

前 44 章学了所有组件。现在把它们组装起来,做一个真实可用的客服 Agent,能查订单、处理退款、转人工。

这是 Part IX 的第一章,用 agents-go 从 0 搭一个完整客服系统。


需求分析

一个电商客服 Agent 要能做:

  1. 理解用户问题,退款?物流?产品咨询?投诉?
  2. 查订单,根据用户信息查订单状态
  3. 处理退款,符合条件的发起退款(要审批)
  4. 转人工,处理不了的转人工客服
  5. 记住对话,多轮上下文
  6. 安全,不泄露其他用户数据,危险操作要审批

对应前面学的组件:

  • 工具(查订单、退款)→ 第 3 章
  • Session(记住对话)→ 第 8 章
  • HITL(退款审批)→ 第 12 章
  • Handoff(转人工)→ 第 15 章
  • Guardrails(防数据泄露)→ 第 14 章

Agent 循环

第一步:定义工具

go
// 查订单工具
type getOrderArgs struct {
    OrderID string `json:"order_id" jsonschema:"description=订单号"`
}

getOrder := agents.NewFunctionTool("get_order", "查询订单状态和详情", func(ctx, tc, args getOrderArgs) (string, error) {
    order := db.QueryOrder(args.OrderID)
    if order == nil {
        return "订单不存在", nil
    }
    // 安全检查:只能查当前用户的订单
    if order.UserID != tc.RunContext.UserID {
        return "无权查看此订单", nil  // 不报错,但返回无权
    }
    return fmt.Sprintf("订单 %s:状态=%s,金额=%.2f,商品=%s",
        order.ID, order.Status, order.Amount, order.Items), nil
})

// 退款工具,需要审批
type refundArgs struct {
    OrderID string  `json:"order_id"`
    Reason  string  `json:"reason"`
}

refundTool := agents.NewFunctionTool("process_refund", "处理退款(需要审批)", func(ctx, tc, args refundArgs) (string, error) {
    refundID := refundService.Process(args.OrderID, args.Reason)
    return fmt.Sprintf("退款已发起,退款单号 %s", refundID), nil
})
refundTool.NeedsApproval = true  // 第 12 章,退款要审批

关键安全设计,get_order 里检查"只能查自己的订单"。 即使模型被诱导查别人的订单,工具层拦住。这是"纵深防御",不只靠 prompt 约束,工具层也要校验。


第二步:定义 Agent 和 Handoff

go
// 人工客服 Agent(被 handoff 的目标)
humanAgent := &agents.Agent{
    Name: "人工客服",
    Instructions: agents.StaticInstructions("你是人工客服坐席。Agent 转交来的问题由你处理。"),
    // 人工 Agent 没有工具,它把问题记下来,转给真人坐席
}

// 客服 Agent
supportAgent := &agents.Agent{
    Name: "客服助手",
    Instructions: agents.StaticInstructions(`你是电商客服助手。
- 先理解用户问题
- 查订单用 get_order
- 退款用 process_refund(会触发审批)
- 处理不了的问题,转给人工客服
- 绝不泄露其他用户的信息`),
    Model: "openai/gpt-4o",
    Tools: []agents.Tool{getOrder, refundTool},
    Handoffs: []agents.Handoff{
        agents.HandoffTo(humanAgent),  // 第 15 章,转人工
    },
}

第三步:加 Guardrails

go
// 输入护栏,防 prompt injection
supportAgent.InputGuardrails = []agents.InputGuardrail{{
    Name: "no_injection",
    Run: func(ctx, rc, agent, input) (agents.GuardrailFunctionOutput, error) {
        text := extractText(input)
        // 检测常见的 injection 模式
        patterns := []string{"忽略之前的指令", "ignore previous instructions", "你现在是"}
        for _, p := range patterns {
            if strings.Contains(strings.ToLower(text), p) {
                return agents.GuardrailFunctionOutput{
                    TripwireTriggered: true,
                    OutputInfo: "疑似 prompt injection",
                }, nil
            }
        }
        return agents.GuardrailFunctionOutput{}, nil
    },
}}

// 输出护栏,防 PII 泄露
supportAgent.OutputGuardrails = []agents.OutputGuardrail{{
    Name: "no_pii",
    Run: func(ctx, rc, agent, output) (agents.GuardrailFunctionOutput, error) {
        text := fmt.Sprint(output)
        if matchPII(text) {  // 检测身份证、手机号等
            return agents.GuardrailFunctionOutput{
                TripwireTriggered: true,
                OutputInfo: "输出含 PII",
            }, nil
        }
        return agents.GuardrailFunctionOutput{}, nil
    },
}}

第四步:Session 记忆

go
// 每个用户一个 Session
userSession := memory.NewFileSession("sessions", fmt.Sprintf("user-%s", userID))

用户多轮对话时,Session 记住上下文,"我刚才说的那个订单"能接上。


第五步:完整运行

go
func handleCustomerMessage(userID, message string) string {
    // 带 Session + Provider + Tracer
    sess := memory.NewFileSession("sessions", fmt.Sprintf("user-%s", userID))

    // RunContext 带 UserID(工具里要用)
    rc := agents.NewRunContext(ctx)
    rc.UserID = userID

    result, err := agents.Run(ctx, supportAgent, message, agents.RunOptions{
        Session:       sess,
        ModelProvider: router,
        RunContext:    rc,
        Hooks:         agents.CompositeRunHooks(loggingHooks, metricsHooks),
    })

    if err != nil {
        // 检查是不是 HITL 中断
        if result != nil && result.State != nil {
            // 退款需要审批,通知审批系统
            notifyApprovalSystem(result.State)
            return "您的退款请求已提交,等待审批中"
        }
        return "抱歉,出错了:" + err.Error()
    }

    return result.FinalOutputString()
}

一次完整的对话流程

text
用户:"我上周买的手机还没到"
  → Agent 推理:需要查订单
  → 调 get_order(order_id="?")  ← 需要订单号
  → Agent:"请问您的订单号是多少?"

用户:"12345"
  → 调 get_order(order_id="12345")
  → 工具校验:订单 12345 属于当前用户 ✅
  → 返回:"订单 12345:状态=已发货,预计明天到达"
  → Agent:"您的订单已发货,预计明天到达"

用户:"太慢了,我要退款"
  → Agent 推理:用户要退款
  → 调 process_refund(order_id="12345", reason="物流太慢")
  → HITL!退款需要审批 → 暂停
  → 返回:"退款请求已提交审批"

(审批人员同意)

  → ResumeRun → 执行退款
  → Agent:"您的退款已处理,退款单号 RF-xxx,3-5 个工作日到账"

这个案例用到了什么

组件章节在客服 Agent 里的作用
Agent 循环1多轮理解+查+答
工具3查订单、退款
工具生命周期4查→执行→回填
Session8记住多轮对话
HITL12退款审批
RunState13审批状态序列化
Guardrails14防 injection、防 PII
Handoff15转人工
Hooks19日志、指标

10 个组件,组合成一个完整客服系统。 这就是"从 0 写 Agent"的实践,每个组件都有用武之地。


小结

这个客服 Agent 案例,把前面学的组件串成了真实应用:

  • 工具做执行(查单、退款)
  • Session做记忆(多轮上下文)
  • HITL做控制(退款审批)
  • Guardrails做安全(防注入、防泄露)
  • Handoff做协作(转人工)

关键设计,安全是纵深的。不只靠 prompt 说"别泄露",工具层也校验权限,guardrail 也检测 PII。多层防御,任何一层都不能单独信任。


下一篇

第 46 章,实战二:数据分析 Agent(DataAgent)。客服 Agent 偏"流程",数据分析 Agent 偏"推理",查数据库、分析异常、生成报告。它用到 MCP(连数据库)+ Sandbox(跑分析代码)+ 结构化输出。


本文是《从 0 写 Agent》系列第 45 章,案例代码基于开源项目 agents-go(OpenAI Agents SDK 的 Go 移植版)。全系列目录见总览。

相关文章

分享: