
字
字节笔记本
2026年10月24日 · 约 15 分钟读完
实战一:从 0 写一个客服 Agent
API中转
¥120

本文是《从 0 写 Agent》系列第 45 章。这个系列基于开源项目 agents-go(OpenAI Agents SDK 的 Go 移植版),用 Go 从零实现一个生产级 Agent 运行时;本章讲「实战一:从 0 写一个客服 Agent」。
前 44 章学了所有组件。现在把它们组装起来,做一个真实可用的客服 Agent,能查订单、处理退款、转人工。
这是 Part IX 的第一章,用 agents-go 从 0 搭一个完整客服系统。
需求分析
一个电商客服 Agent 要能做:
- 理解用户问题,退款?物流?产品咨询?投诉?
- 查订单,根据用户信息查订单状态
- 处理退款,符合条件的发起退款(要审批)
- 转人工,处理不了的转人工客服
- 记住对话,多轮上下文
- 安全,不泄露其他用户数据,危险操作要审批
对应前面学的组件:
- 工具(查订单、退款)→ 第 3 章
- Session(记住对话)→ 第 8 章
- HITL(退款审批)→ 第 12 章
- Handoff(转人工)→ 第 15 章
- Guardrails(防数据泄露)→ 第 14 章

第一步:定义工具
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 | 查→执行→回填 |
| Session | 8 | 记住多轮对话 |
| HITL | 12 | 退款审批 |
| RunState | 13 | 审批状态序列化 |
| Guardrails | 14 | 防 injection、防 PII |
| Handoff | 15 | 转人工 |
| Hooks | 19 | 日志、指标 |
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 移植版)。全系列目录见总览。
相关文章
分享:
