用企微 API 接大模型做客服,最大的难点不是"怎么调模型",而是"怎么让模型知道自己该说什么、能做什么"。这篇讲一套能落地的客服 Agent 结构。
Agent 的基本循环
客服 Agent 跑起来就是这样一个循环:
收到客户消息 → 组装上下文 → 调大模型 → 判断是否调工具 → 执行 → 生成回复
落到企微上,入口是 message 回调,出口是 message/sendText。中间那段全是自己的逻辑。
上下文从哪来
大模型要答得准,得知道"这个客户之前聊了什么"。两条路取上下文:
-
短期:本会话最近 N 条,从本地消息流水表取
-
长期:客户档案 + 业务数据,从本地客户表取
def build_context(conversation_id, uin):
history = db.recent_messages(conversation_id, limit=10)
customer = db.get_customer(uin)
return {
"history": [{"role": m["role"], "content": m["content"]} for m in history],
"profile": {"name": customer["name"], "level": customer.get("level")},
}
上下文别无限加长,按时间倒序取最近几条就够。历史越长,成本和延迟越高,效果反而不一定好。
工具调用要白名单
让模型自由决定调什么接口,迟早出事。做法是只给一个白名单:
SAFE_TOOLS = {
"send_text": {"module": "message", "action": "sendText"},
"get_profile": {"module": "contact", "action": "getUserProfileDetail"},
"add_label": {"module": "label", "action": "updateLabel"},
}
模型返回想调的工具名,先查白名单——不在白名单的直接拒绝,回了句"我帮你转人工"。工具名对上了,参数也必须再校验一次(比如 uin 必须是数字、标签必须是已存在的),校验不通过同样拒绝。
白名单 + 参数二次校验是两层保险,缺一层都可能被模型绕过去。
回复怎么发
生成好回复后调 sendText:
def agent_reply(conversation_id, user_text):
ctx = build_context(conversation_id, ctx_uin(conversation_id))
answer = llm.chat(system_prompt, ctx, user_text)
if answer.needs_tool:
result = run_tool(answer.tool_name, answer.tool_args)
answer = llm.chat(system_prompt, {**ctx, "tool_result": result}, user_text)
call("message", "sendText", {
"conversationId": conversation_id,
"content": answer.text,
})
24 小时无人值守靠兜底
Agent 不可能答对所有问题。几种兜底:
|
模型超时/报错 |
回固定话术:"稍等,正在为您转接人工" |
|
触发敏感词 |
不给模型自由发挥,直接转人工 |
|
连续 N 轮没解决 |
升级为人工工单 |
|
非工作时间 |
只答常见问题,其余留消息次日处理 |
兜底话术和转人工阈值建议做成配置,别写死在代码里——业务调整时不用改代码。
几个现实提醒
-
回调 5 秒内返回:模型调用可能超过 5 秒,务必"先 return ok,再异步跑 Agent"
-
自己发的消息不回推:Agent 发的回复不会触发回调,别指望靠回调拿回复记录,回复内容自己存流水
-
限流:并发客户多时按 conversationId 分桶限速,避免 -301002
落到企微上的完整链路
企微 message 回调 → 幂等去重 → 入队 → Agent 循环
↓
上下文组装 → LLM → 工具白名单校验 → 执行
↓
sendText 回复 → 写消息流水
这套结构里,接口部分其实很简单——就是收发两条。真正花功夫的是上下文管理、工具白名单和兜底策略。接口用的是 Eyun 平台的企业微信 API,凭证和路径在官网开通实例时拿到,消息和通讯录字段可以对照开发文档。
AI 客服能不能用得住,看的是兜底做得细不细,不是模型有多聪明。
网硕互联帮助中心



评论前必须登录!
注册