跳转到内容

做一个客服 bot

本教程带你从零搭建一个能处理 80% 常见咨询、20% 升级转人工的客服 Agent。 完整代码可直接 deploy。

用户故事

“我们 SaaS 产品每天 200+ 咨询,重复问题多,但有些需要查内部知识库 + 转给真人。 想要:常规问题自动答;查询订单 / 账单走 tool call 经用户授权拉数据; 复杂 case 一键 Approval 推给人工 oncall。“

架构

用户消息
DemoBot (HasheeAgent, WebSocket)
├─ 意图识别 (LLM intent classifier)
│ │
│ ├─ FAQ 类 → 知识库检索 (RAG) → 直接回复
│ ├─ 订单查询 → tool_call (filesystem / external API) → 回复
│ ├─ 投诉 / 退款 → Approval artifact → oncall 接管
│ └─ 闲聊 / 不清楚 → 让 LLM 安全回复 + 主动澄清
└─ 全程会话归档(业务侧 Postgres,不依赖 Hashee 后端)

完整代码

src/index.ts
import { HasheeAgent } from "@hasheeai/agent-sdk-ts";
import OpenAI from "openai";
import { Pool } from "pg";
const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
const db = new Pool({ connectionString: process.env.DATABASE_URL });
const agent = await HasheeAgent.init({
agentId: process.env.HASHEE_AGENT_ID!,
token: process.env.HASHEE_AGENT_TOKEN!,
baseUrl: "https://api.hashee.ai",
connectionMode: "websocket",
});
agent.addMessageHandler(async (msg) => {
if (msg.payload?.type !== "text") return;
await agent.typing(msg.conversation_id);
// 1. 意图识别
const intent = await classifyIntent(msg.payload.text);
console.log(`[intent] ${msg.sender_id}: ${intent}`);
// 2. 路由
switch (intent) {
case "faq":
await handleFaq(msg);
break;
case "order_query":
await handleOrderQuery(msg);
break;
case "complaint":
await handleComplaint(msg);
break;
default:
await handleGeneral(msg);
}
// 3. 归档
await archiveMessage(msg, intent);
});
// === 1. 意图识别 ===
async function classifyIntent(text: string): Promise<string> {
const r = await openai.chat.completions.create({
model: "gpt-4o-mini",
messages: [
{ role: "system", content: "把用户问题分类:faq / order_query / complaint / general。只回类别词。" },
{ role: "user", content: text },
],
temperature: 0,
});
return r.choices[0]?.message?.content?.trim().toLowerCase() ?? "general";
}
// === 2. FAQ 类(RAG)===
async function handleFaq(msg: any) {
// 简化:业务侧已有 FAQ embedding 索引
const matches = await searchKnowledgeBase(msg.payload.text, 3);
const context = matches.map((m) => `Q: ${m.question}\nA: ${m.answer}`).join("\n\n");
const r = await openai.chat.completions.create({
model: "gpt-4o-mini",
messages: [
{ role: "system", content: `你是客服。基于以下 FAQ 回答用户:\n\n${context}` },
{ role: "user", content: msg.payload.text },
],
});
await agent.send(msg.conversation_id, {
type: "text",
text: r.choices[0]?.message?.content ?? "(空回复)",
});
}
async function searchKnowledgeBase(query: string, topK: number) {
// 简化:用 PG 全文检索;生产环境用 pgvector / Weaviate / Qdrant
const r = await db.query(`
SELECT question, answer
FROM faq_entries
WHERE to_tsvector('simple', question) @@ plainto_tsquery('simple', $1)
ORDER BY ts_rank(to_tsvector('simple', question), plainto_tsquery('simple', $1)) DESC
LIMIT $2
`, [query, topK]);
return r.rows;
}
// === 3. 订单查询(Tool Call)===
async function handleOrderQuery(msg: any) {
// 让客户端通过 tool call 提供订单号(如果对话历史里没有)
const orderId = extractOrderId(msg.payload.text);
if (!orderId) {
await agent.send(msg.conversation_id, {
type: "text",
text: "麻烦提供下订单号(13 位数字)。",
});
return;
}
const order = await fetchOrderFromBackend(orderId);
await agent.sendArtifact(msg.conversation_id, {
artifact: {
subtype: "info",
title: `订单 #${orderId}`,
payload: {
body: `**状态**:${order.status}\n**金额**:$${order.amount}\n**预计送达**:${order.eta}`,
actions: [{ id: "track", label: "查看物流", url: order.tracking_url }],
},
},
});
}
// === 4. 投诉转人工(Approval)===
async function handleComplaint(msg: any) {
await agent.send(msg.conversation_id, {
type: "text",
text: "我理解你的不满。让我把你的问题转给人工同事处理。",
});
// 发 approval 卡给 oncall 群
const oncallConvId = process.env.ONCALL_GROUP_CONV_ID!;
await agent.sendArtifact(oncallConvId, {
artifact: {
artifact_id: `escalation-${msg.message_id}`,
subtype: "approval",
title: "客户投诉转人工",
payload: {
request: {
from_user: msg.sender_id,
conversation_id: msg.conversation_id,
issue: msg.payload.text.slice(0, 500),
time: msg.created_at,
},
approve_label: "我来接 (claim)",
reject_label: "驳回",
timeout_s: 600,
},
},
});
}
// === 5. 一般回复 / 兜底 ===
async function handleGeneral(msg: any) {
await agent.send(msg.conversation_id, {
type: "text",
text: "抱歉,我没完全理解你的问题。是想:(a) 查询订单 (b) 报告问题 (c) 看常见问题?",
});
}
// === 工具 ===
function extractOrderId(text: string): string | null {
return text.match(/\b\d{13}\b/)?.[0] ?? null;
}
async function fetchOrderFromBackend(orderId: string) {
// 替换为你的真实 API
return {
status: "已发货",
amount: 99.99,
eta: "2026-05-18",
tracking_url: `https://my-shop.com/track/${orderId}`,
};
}
async function archiveMessage(msg: any, intent: string) {
await db.query(
"INSERT INTO cs_messages (msg_id, user_id, conv_id, content, intent, ts) VALUES ($1, $2, $3, $4, $5, NOW())",
[msg.message_id, msg.sender_id, msg.conversation_id, msg.payload.text, intent],
);
}
// === 接收人工 oncall 的响应 ===
agent.addEventHandler(async (event) => {
if (event.type !== "artifact_response") return;
const ref = event.payload.ref_artifact;
if (!ref?.startsWith("escalation-")) return;
if (event.payload.action === "approve") {
// oncall claimed — 业务侧把用户对话路由给那个人工
const claimerName = event.sender_id;
const customerConv = event.payload.payload.conversation_id;
await agent.send(customerConv, {
type: "text",
text: `已为你接通 @<U:${claimerName}>,稍后他会跟进。`,
});
}
});
agent.addStatusHandler((s) => console.log(`[status] ${s}`));
console.log("[cs-bot] up");

部署

1. 环境变量

Terminal window
HASHEE_AGENT_ID=01906abc-...
HASHEE_AGENT_TOKEN=hsk_...
HASHEE_X25519_PRIVATE_BASE64=...
HASHEE_ED25519_PRIVATE_BASE64=...
DATABASE_URL=postgres://...
OPENAI_API_KEY=sk-...
ONCALL_GROUP_CONV_ID=01HZ...

2. DB 表

CREATE TABLE faq_entries (
id SERIAL PRIMARY KEY,
question TEXT NOT NULL,
answer TEXT NOT NULL,
created_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE INDEX ON faq_entries USING gin(to_tsvector('simple', question));
CREATE TABLE cs_messages (
msg_id TEXT PRIMARY KEY,
user_id TEXT NOT NULL,
conv_id TEXT NOT NULL,
content TEXT NOT NULL,
intent TEXT NOT NULL,
ts TIMESTAMPTZ NOT NULL
);

3. 部署到 VPS(systemd)

详见 自托管 VPS

升级路径

想做改哪
加多语言(用户语言决定回复语言)msg.payload.text 先 detect language → 把 system prompt 替换
提升 RAG 召回率用 pgvector + embedding model 替代 PG 全文搜
自动摘要对话历史summarizeConversation() 每天 cron 跑
多 LLM 切换(GPT / Claude / 本地)抽象 chatComplete() 接 multiple providers
加情绪识别报警classifyIntent 加一类 “high_emotion”,触发优先转人工
自动评级 / NPS对话结束发 artifact form 收 1-5 星评分

监控指标

指标目标
平均响应时长 (agent.typingagent.send)< 3 秒 (FAQ); < 8 秒 (订单查询)
意图识别准确率> 85% (业务侧 sampling)
转人工率10-20% (低于 10% 可能漏识别 complaint;高于 20% 检查 LLM prompt)
用户满意度 (CSAT)> 4.0 / 5

相关页面