企业微信智能客服的挑战与实现(二):专属客户群与拟人化体验
摘要:很多企业在企业微信上服务客户的标准姿势是——每个客户一个专属群:客户在群里提需求,企业的销售、售后、技术各司其职地在群里响应。本文继续以 冰石机器人(官网 icestonebot.com) 为例,拆解两组能力的实现:一是好友通过即自动建专属群(含一个很有味道的工程问题:建群请求超时后怎么"找回"这个群);二是让机器人说话更像真人的两个机制——多条消息合并回复与无回复自动续聊。
本系列共三篇:
① 人机协同——机器人客服与真人客服如何共处一个会话
②(本篇)专属客户群与拟人化体验
③ 知识库与 AI 自主学习——让机器人越用越聪明
一、为什么是"每个客户一个群"
企业客户和 C 端消费者不一样:一个客户背后往往对应企业内部多个角色的服务——
- 销售对接商务和续费;
- 售后处理报修和退换;
- 技术支持解决产品使用问题;
- 机器人 7×24 小时兜底常见问题。
如果客户只加了销售一个人的微信,那么每次售后问题都要销售转述,效率低且容易失真。所以企业微信生态里演化出了一种标准服务形态:客户通过好友后,立刻拉一个专属群——客户 + 销售 + 售后 + 技术 + 机器人,所有沟通都沉淀在这个群里。
好处很实在:
- 服务信息集中:客户所有历史问题都在一个群里,任何同事进来都能看到上下文;
- 责任清晰:@谁谁负责,不存在"客户消息躺在某个同事的私聊里没人知道";
- 机器人天然融入:机器人挂在群里,配合上一篇讲的人机协同机制,和真人同事并肩服务;
- 对客户来说是尊贵感:一进来就有一个以自己命名的服务群,好几个人为他服务。
手工建群显然不现实——客户通过好友的高峰期,运营根本忙不过来,而且建群、改名、拉人、发欢迎语这套动作重复且机械。这正是机器人该干的活。
二、自动建群:配置与流程
冰石机器人的自动拉群配置在「企业微信管理 → 账号配置 → 编辑」里:

核心配置项:
| 配置项 | 说明 | 示例 |
|---|---|---|
| 启用自动拉群 | 总开关,启用后添加好友即自动建群 | 开 |
| 群名称模板 | 支持占位符 {用户名}、{日期}(MM-DD) | 客户群-{用户名}-{日期} |
| 选择联系人 | 建群时要拉入的企业内部/外部成员 | 销售张三、售后李四 |
| 第一/二/三条群消息 | 建群后依次发送的欢迎消息,支持文本/图片/视频,{_member_} 指代新客户名字 | “欢迎 {member}!这是您的专属服务群” |
整体流程:
流程里有三个不太起眼、但都是踩过坑之后才有的实现细节:
细节 1:建群和起名是两步。
企业微信的建群接口只负责"把这些人拉到一个群里",返回群 ID,不能在创建时直接指定群名。所以必须先建群、拿到群 ID,再调改名接口。两步之间还要留缓冲时间,等群在服务端真正就绪。
细节 2:一律创建"外部群"。
企业微信区分内部群(仅企业成员)和外部群(可含企业外联系人)。客户是外部联系人,进不了内部群——所以自动建群硬性规定创建外部群,避免"群建好了客户进不来"的乌龙。
细节 3:群名要过滤特殊字符。
群名模板里的 {用户名} 来自客户的微信昵称,而昵称里什么字符都可能有(表情、特殊符号)。企微对群名字符集有限制,所以渲染完模板后还要做一次白名单过滤,不合法字符替换成下划线,否则改名接口会失败。
def create_auto_group(new_friend, config):
cfg = config["auto_group_config"]
if not cfg["enabled"]:
return
members = cfg["internal_contact_ids"] + cfg["external_contact_ids"]
members.append(new_friend.id)
if len(members) < 2:
return # 只有客户自己,没必要建群
name = (cfg["group_name_template"] or "{用户名}-{日期}") \
.replace("{用户名}", new_friend.name) \
.replace("{日期}", today("MM-DD"))
name = sanitize(name) # 白名单字符过滤
room_id = wecom.create_room(members, outer=True) # 第一步:建群(外部群)
wait_room_ready()
wecom.modify_room_name(room_id, name) # 第二步:改名
wait_a_moment()
for msg in cfg["group_messages"]: # 第三步:欢迎语三连
send(room_id, msg.replace("{_member_}", new_friend.name))
三、幂等的艺术:建群超时之后,怎么"找回"那个群
这是本篇最有工程味道的部分。
问题:建群请求发出去之后,偶尔会超时——接口没有在时限内返回群 ID。但坑爹的是:群往往已经建成功了,只是响应没来得及回来。这时候系统面临一个两难:
- 当作失败重试?→ 客户会被拉进两个一模一样的群,非常尴尬;
- 当作成功放弃?→ 拿不到群 ID,改名和欢迎语都没法发,客户进了一个连名字都没有的裸群。
冰石机器人的解法分三步,本质是一个异步事件找回模式:
第一步:把"没下文的建群请求"登记在案。
超时发生时,把这次请求的关键信息(哪个账号、拉了哪些人)存进一张 pending 表。请求 ID 的生成方式很讲究:
member_key = ";".join(sorted(member_list)) # 成员列表排序 → 顺序无关
request_id = md5(f"{account}_{customer}_{member_key}")
pending[request_id] = {
"members": set(member_list),
"group_name": name,
"welcome_msgs": msgs,
"created_at": now(),
}
成员列表先排序再哈希——同一批人无论以什么顺序发起,生成的 request_id 都一样。这一步天然保证了幂等:就算上层因为超时又发起了一次登记,也只会命中同一条 pending 记录,不会重复。
第二步:等群自己"冒出来"。
群建成功后,企业微信侧会推送一个"群成员变更"事件,事件里携带新群的群 ID 和成员列表。系统拿这个事件去 pending 表里匹配:
def on_group_created_event(event):
callback_members = set(event.member_list)
for req_id, req in pending.items():
if req["members"].issubset(callback_members): # 子集匹配!
finish_group_setup(event.room_id, req) # 补改名 + 补欢迎语
del pending[req_id]
return
匹配规则用的是子集匹配而不是全等匹配:请求时的成员集合 ⊆ 事件里的成员集合即算命中。为什么?因为事件里的成员列表可能多带一个建群者自己(也就是机器人所在的账号),全等匹配会永远匹配不上。
第三步:过期清理。
pending 记录带 5 分钟超时——如果 5 分钟内始终没等到对应的群事件,说明群大概率真的没建成,清掉记录,避免内存里堆积僵尸请求。
这套"同步超时 → 登记待办 → 异步事件找回 → 子集匹配 → 过期兜底"的模式,不只适用于建群——任何"请求方超时但操作实际可能已生效"的异步系统(下单、开票、开通服务)都可以套用。
四、新群零配置:缺省配置继承
自动建出来的群,马上就要开始接待客户了。但问题来了:这个群是全新的,后台的聊天配置表里根本没有它的记录——它的提示词是什么?FAQ 开不开?真人客服名单是谁?
如果每建一个群都要人工去后台配一遍,"自动"就名存实亡了。
冰石机器人的方案是缺省群配置:
- 管理员预先配置一条特殊的聊天配置,作为该企微账号下所有"无主"会话的模板;
- 消息从某个群进来时,先按群 ID 查配置;
- 查不到 → 取缺省配置逐字段深拷贝一份(内存中,不落库),当场作为这个群的配置使用;
- 后续如果管理员想对某个群精细化调整,再单独为它建配置覆盖缺省值。
def get_group_config(account, group_id):
cfg = db.query(group_id=group_id, status="active")
if cfg:
return cfg
default = db.query(group_id=account.wxid) # 约定:群ID=账号自身ID 的那条即缺省配置
if default:
cfg = deep_copy_all_columns(default) # 逐列拷贝,不写库
cfg.group_id = group_id
return cfg
return None
于是每个自动建出来的专属群,从诞生那一刻起就自动继承了完整的机器人能力:提示词、FAQ 开关、真人客服名单、等待秒数……建群 → 接客,全程零人工配置。
五、像真人一样"看完再说":多条消息合并
场景切到日常对话。真实的客户很少一条消息把话说完,更常见的是:
客户:打印机连不上云盒
客户:一直闪红灯
客户:昨天还是好的
普通机器人是一问一答式的:三条消息触发三次回复,机器人连珠炮一样回三段话,而且第一段回复只看到"连不上云盒"、完全没等到"闪红灯"这个关键信息——既蠢又吵。
真人客服的做法是:看对方把话说完,合起来理解,回一条。
冰石机器人用合并等待窗口模拟这个行为,配置项在聊天配置的 AI 大模型配置区:

LLM 多条消息合并等待秒数(默认 3 秒,0 表示不合并):同一个人在窗口期内连续发的多条需要大模型回复的消息,会合并成一次调用。
实现是一个滑动窗口缓冲:
buffers = {} # (账号, 会话, 发送者) -> {"messages": [...], "timer": Timer}
def on_llm_message(key, msg, delay):
if delay <= 0:
llm_reply([msg]); return
buf = buffers.setdefault(key, {"messages": [], "timer": None})
buf["messages"].append(msg)
if buf["timer"]:
buf["timer"].cancel() # 又来一条 → 重置倒计时(滑动窗口)
buf["timer"] = Timer(delay, lambda: flush(key))
buf["timer"].start()
def flush(key):
buf = buffers.pop(key)
with conversation_lock(key): # 同会话串行,避免并发双回
if batch_is_stale(key, buf): # 等锁期间已被别的批次答过 → 丢弃
return
combined = "\n\n".join(buf["messages"])
llm_reply(combined)
三个关键设计:
- 缓冲的 key 精确到"发送者":群里 A 和 B 同时说话,各自有各自的缓冲,不会把两个人的话合并成一段发给大模型;
- 滑动窗口:每来一条新消息就重置倒计时——客户打字慢、边想边发的场景下,窗口会一直顺延到他把话说完;
- 过期批次丢弃:flush 时要拿会话锁排队,如果等锁期间客户的这批消息已经被更晚的批次连带回答过了,当前批次直接丢弃——避免"合并是为了少回话,结果反而连回两条"的自相矛盾。
效果上,开头那三条消息会在最后一条发出 3 秒后,合并成一段完整上下文交给大模型,机器人回一条同时考虑了三个信息点的回复——观感上和真人"看完再说"几乎一致。
六、客户不说话了怎么办:无回复自动续聊
还有一个更进阶的拟人化机制,同样在上面那张截图里:无回复续聊。
场景:机器人回答了客户的问题,客户没再说话。可能是问题解决了,也可能是他没看到、被别的事岔开了。真人销售遇到这种情况,往往会隔几分钟跟一句:“刚才的方法试了吗?还有问题随时说哈。”
冰石机器人的对应配置:
| 配置项 | 默认值 | 说明 |
|---|---|---|
| 无回复续聊等待分钟数 | 0(关闭) | 机器人发出回复后开始计时,客户 N 分钟没有新消息则主动跟进一句 |
| 无回复续聊最大次数 | 1 | 客户持续无回复时最多自动续聊几条,防止变成骚扰 |
机制要点:
- 机器人每发出一条非空回复,就为该会话挂一个倒计时;
- 倒计时期间客户来新消息、真人客服发言、或会话被人工接管,倒计时立即取消——续聊只发生在"真的没人说话"时;
- 到点后,由大模型根据聊天历史生成跟进话术,不是固定模板——上文聊的是安装问题,跟进的就是"安装还顺利吗",而不是干巴巴的"在吗";
- 发送成功后计数 +1,未达上限则再挂一轮。
最有意思的是第 3 步的提示词设计。让大模型"决定要不要续聊"有一个经典难题:模型总想说点什么,哪怕不该说。冰石机器人在续聊提示词里用了一个哨兵串协议:
判断当前是否适合跟进。如果客户已明确表示"不用了"“先不用”"我先看看"等婉拒含义,或对话已自然结束,只输出
(__0__),不要输出任何其他内容。禁止发送活动、试用、优惠等推销内容,禁止反问推销。
系统侧拿到回复后先检查哨兵串:
followup = llm(build_followup_prompt(history))
if followup.strip() == "(__0__)" or not followup.strip():
return # 模型判断不该跟进 → 静默放弃,且不计入续聊次数
send(chat_id, followup)
给模型一个明确的"闭嘴出口",比在提示词里反复强调"不要过度打扰"有效得多——这是大模型应用里很值得推广的一个小技巧。
七、小结
本篇拆解了企微客服的两组能力:
专属客户群自动化:
- 群名模板 + 成员配置 + 欢迎语三连,好友通过即建群;
- 建群/改名两步走、强制外部群、群名字符过滤——都是实战踩出来的细节;
- 建群超时的异步事件找回:成员集排序 MD5 幂等登记 → 群事件子集匹配 → 5 分钟过期兜底;
- 缺省配置深拷贝继承,新群零配置即可接客。
拟人化体验:
- 多条消息合并:按发送者隔离的滑动窗口缓冲 + 会话锁 + 过期批次丢弃,“看完再说”;
- 无回复续聊:大模型生成跟进话术 + 哨兵串闭嘴协议 + 次数上限,“跟进而不骚扰”。
配合第一篇的人机协同机制,一个企微客服群里的完整图景是:客户进来就有专属群,群里真人同事各司其职,机器人安静地兜底常见问题、说话像真人、还会看时机跟进——整体感受是"一个团队在服务我",而不是"我在和机器人对话"。
下一篇(完结篇)我们深入机器人的"大脑":三级应答链路、图文并茂的知识库导入,以及解决"知识库越用越旧"这一行业痛点的 AI 自主学习机制。
冰石机器人官网:icestonebot.com
本文界面截图来自冰石机器人企业微信版实际部署环境(版本 20260827),配置项名称与默认值以实际部署版本为准。请在遵守企业微信平台规范的前提下合规使用。
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐

所有评论(0)