
我做客服系统差不多有十年了。从最早网页里的留言板到后来的在线IM再到把客服渠道整体迁到企业微信上最深的感受就是客户越来越没有耐心等一个人了。尤其是深夜十一点之后的咨询——客户端打出一句还有人吗你这边只有一个客服强撑着眼皮在敲键盘。我接手过一个电商客户大促当晚咨询量翻了近十倍三个客服轮班值守平均响应时间从两分钟直接拖到了四十分钟那晚的退单率肉眼可见地往上走。问题很清楚不是他们服务态度不行是人手根本不够。那之后我一直在想能不能把那些标准问题彻底交给机器让真人只处理真正棘手的事。答案就是现在大家常说的AI客服。这套系统不是什么科研项目就是一套能直接跑起来的PHP代码把企业微信和AI大模型接通客户在企业微信里发消息AI自动回复识别不了或超过阈值的问题再转给真人。核心目标就一个——让客服真正实现全天候在线。这篇文章就把这套系统的设计思路、实现过程和踩过的坑完整地讲一遍。1. 为什么偏偏选PHP企业微信来搭建AI客服1.1 客服场景的真实痛点先说业务层面。常规的人工客服有三个绕不开的死穴第一响应速度跟不上。不管你排几个人总有同时涌进来几十条消息的时刻一条一条处理排在后面的人必然不满。第二重复咨询占比太高。我统计过几个项目的数据退换货流程、物流时效、发票开具这类问题占了总咨询量的七八成这些问题回答起来完全没有创造性纯粹消耗人力。第三是夜间覆盖成本你不可能为了每晚零星几条消息养一组夜班客服但放任不管客户连个安抚的回应都等不到。AI客服要解决的就是这三个问题。它不需要睡觉回复是毫秒级的而且对重复问题的处理稳定到可怕——不会因为今天心情不好就少打一个请字。难点反而在集成怎么让AI合理地出现在客户使用的渠道里并且遵循业务规则。1.2 企业微信作为客服渠道的优势聊渠道之前先说一个背景现在的客服系统早就不是让客户下载一个新App去提问了客户在哪里客服就必须在哪里。微信是中国客户最熟悉的入口而企业微信作为合规的对外服务载体可以直接让客户的微信账号和企业员工建立联系。选企业微信还有几个实际原因客户不需要额外装软件微信里直接发起会话。企业微信本身带客户标签、群发、会话存档等能力这些是传统IM工具没有的。接口文档齐全回调消息、主动推送、多媒体消息都有官方支持方便程序化处理。在企业微信里每个外部联系人对应一个唯一的unionid和openid做用户画像和会话跟踪很方便。当然企业微信也有一些限制比如主动给客户发消息的频次限制、欢迎语窗口期只有20秒这些在后面落地时都要专门处理。1.3 为什么后端选PHP而不是Go或Java聊完渠道说技术选型。有朋友问我现在新项目为什么还用PHP直接建Go不香吗我的答案很简单这套系统的核心不是高并发而是业务接入快、迭代快、维护成本低。企业微信API回调和AI大模型的HTTP请求都是典型的IO密集型任务PHP处理这类场景完全够用。另外一个重要原因是生态。企微官方出的加解密SDK就有PHP版本各种SCRM开源项目的核心代码也大多是PHP比如企业微信SCRM圈子里的主流方案经常是PHPMySQLRedis。这意味着你拿到一套PHP源码改造成自己的AI客服系统时无数现成的逻辑都可以复用而不是从零去适配。还有一点非常现实部署成本。PHP-FPMNginx的组合几乎任何一台1核2G的云主机都能跑起来。客户那边如果本身就是做电商或本地服务的他们的运维人员往往更熟悉PHP出了问题好维护。真等业务量到了必须上Go的水平重构网关层也不迟没必要一上来就用重武器。2. 企业微信消息收发的架构设计建立客户端到AI的桥梁2.1 回调消息链路的整体规划系统最核心的环节是企业微信消息的收发。网上很多教程直接让你把回调URL指向AI接口但那只是demo真实场景是要建一条可靠的消息管道。我最终采用的架构是一条复杂的消息链路企业微信服务器把客户消息推送到我的回调URLHTTPS接口PHP校验签名、解密消息体得到原始的XML文本消息将标准化消息丢进Redis队列立即返回success给企业微信避免回调超时后台Worker进程从队列里消费消息根据会话状态和消息内容决定回复策略调用AI大模型接口得到结果后通过企业微信API主动推送回复给客户。为什么中间要加一个Redis队列原因在于大模型接口响应时间通常需要几秒而企业微信的回调要求尽快响应。如果在回调处理里同步等AI结果一旦超时企业微信会以固定间隔重试造成消息重复。队列把接收和处理解耦既保证不丢消息也保证回调永远及时返回成功。2.2 回调URL配置与加解密的细节企业微信后台配置接收消息服务器时需要填一个URL、一个Token和一个EncodingAESKey。这里的坑非常集中几点经验第一URL验证。配置时企业微信用GET请求带一个echostr过来服务端必须解密成功并原样返回明文才算通过验证。很多小白在这里栽了返回的不是纯明文而是带了HTML标签的空格导致验证失败。必须在输出前做trim并且绝不能echo任何调试信息。第二企业微信的加密参数。回调推送过来的XML里所有内容都被AES加密过了。官方SDK里有完整的加解密类例如wxBizMsgCrypt直接使用即可。注意PHP7以上对mcrypt扩展移除了必须改成官方SDK基于openssl的实现否则解密必然失败。第三回调URL本身必须是HTTPS。企业微信要求接收消息服务器必须是企业可信域名且已通过企业微信的域名校验。这里有团队用泛解析证书偷懒结果二级域名证书不对导致消息推不过来排查半天。下面这是回调签名校验和消息解密的示意代码基于官方SDK改写// 回调入口 verifyURL 方法用于配置时的URL验证 $wxCrypt new WXBizMsgCrypt($token, $aesKey, $corpId); // 处理GET验证请求 if ($_GET[echostr]) { $echoStr $_GET[echostr]; $decryptEchoStr ; $errCode $wxCrypt-verifyURL($_GET[msg_signature], $_GET[timestamp], $_GET[nonce], $echoStr, $decryptEchoStr); if ($errCode 0) { echo $decryptEchoStr; exit; } } // 处理POST消息解密得到明文消息XML $postData file_get_contents(php://input); $decryptMsg ; $errCode $wxCrypt-decryptMsg($_GET[msg_signature], $_GET[timestamp], $_GET[nonce], $postData, $decryptMsg); if ($errCode 0) { // $decryptMsg 就是明文的XML消息结构 // 解析XML后交给队列处理 }这里要特别强调一个点file_get_contents(php://input)拿到的XML不一定只有一条消息。批量消息推送时一个包内可能包含多条事件。所以解析时要用能循环读取全部消息节点的写法而不是仅处理第一个节点。2.3 主动发送消息的三种方式客服系统不只是被动回复有时候需要主动给客户推送信息比如订单状态变更。企业微信支持三种方式的主动消息应用消息推送通过企业微信应用API支持给员工或外部联系人发文本、图片、图文等消息。注意外部联系人必须在该应用可见范围内并且有会话否则会返回错误码。群机器人Webhook适合给内部群推告警不能直接给外部客户发。客户联系API官方推荐的正规客户触达方式通过external_userid发送消息。这里有个24小时服务窗口的限制客户主动发消息后有24小时内免费触达窗口如果超出24小时需要企业认证并配置相应能力。我在系统里的做法是主动推送都走客户联系-发送消息接口界面里配置好模板调用时传external_userid和消息内容。一定不要频繁刷接口要严格控制发送频次和内容合规否则容易被限制触达能力。3. AI能力接入选择企业微信接入DeepSeek这条路3.1 大模型选型为什么DeepSeek这类国产模型是主力AI客服的核心当然是大模型。早期做客服系统大家用的是关键词匹配加正则效果非常生硬。后来有了大模型API我才真正体会什么叫客户问的是意思不是字符串。选型上我最推荐的是DeepSeek这类国产大模型。核心原因有三点第一是成本。客服系统是高频调用的场景一天几万次请求很常见如果全用国外顶级模型费用直接爆炸。DeepSeek的API定价明显更便宜量上去之后才知道差距有多大。第二是中文理解能力。客服场景涉及大量中文口语像我东西还没到咋整这种表达通用模型和国产中文模型的语义理解有差距。DeepSeek在处理这类非正式中文输入时回答质量明显更稳定。第三是接入方式。DeepSeek的API兼容OpenAI格式只要改base_url和模型名就能切换对开发者非常友好。我可以把整个AI网关写成通用OpenAI协议底层换哪家模型都不用手改业务代码。下面是我在生产环境用过的模型配置示例return [ default deepseek, providers [ deepseek [ base_url https://api.deepseek.com/v1, api_key getenv(DEEPSEEK_API_KEY), model deepseek-chat, timeout 30, ], openai [ base_url https://api.openai.com/v1, api_key getenv(OPENAI_API_KEY), model gpt-4o-mini, timeout 30, ], ], ];3.2 统一AI网关封装别把模型绑定死上面说到统一网关这是这套系统里我觉得最值得强调的设计。很多项目直接把DeepSeek的SDK调用散落在业务代码里结果后来想换一家模型或做并联降级改起来痛不欲生。我的做法是定义一个AiProvider接口然后为每家模型实现对应的适配器。interface AiProviderInterface { public function chat(array $messages, array $options []): string; public function isAvailable(): bool; }chat()接收统一格式的消息数组返回纯文本回复。DeepSeek、OpenAI、国内其他厂商的模型都是实现这个接口。后面接一个调度类默认走首选模型如果首选超时或返回错误自动切换下一个可用的模型。这个设计一气解决了三个问题切换模型只需改配置文件不用动业务逻辑支持多模型互为备份稳定性翻倍统计各模型的调用量和成本非常容易直接在适配器里埋点即可。3.3 Prompt工程和上下文管理有了模型API效果好不好还得看Prompt和上下文。客服场景里Prompt有一个角色的基础设置我习惯把它写成系统指令你是一个电商企业的客服助手负责回答关于订单、物流、退换货、发票等问题。 回答规则 1. 只依据知识库内容回答不要编造不知道的信息 2. 如果知识库中不存在答案必须回答这个问题我需要帮您转接人工客服不要强行回答 3. 语气专业、简洁少用亲避免机械重复。 4. 涉及订单号、地址、电话等敏感信息时不主动追问交给人工客服处理。上下文管理是另一个大坑。如果直接把全部历史消息塞给模型token消耗会爆炸。我的做法是每个客户会话在Redis里维护最近10轮对话用户说AI回复超出就淘汰最早的定期将历史消息摘要存入数据库客户重新会话时按客户标识加载摘要系统只把最近10轮历史知识库命中内容拼接成请求发给模型。这里贴一段Redis会话管理的核心代码思路// 会话消息入队 $messageKey im:context:{$external_userid}; $message [role user, content $content]; Redis::rpush($messageKey, json_encode($message)); // 只保留最近10轮超出时从头部弹出 if (Redis::llen($messageKey) 20) { Redis::lpop($messageKey); } // 构建请求 $history Redis::lrange($messageKey, 0, -1); $messages array_map(function($item) { return json_decode($item, true); }, $history);注意这里我把用户的每一条消息都存起来了。有些项目为了省Redis空间只存模型回复结果用户第二句话就丢失了上下文AI完全失忆体验非常差。上下文是AI客服的生命线这个成本不能省。4. 客服系统核心功能落地知识库、意图识别、人工接管4.1 知识库的冷启动与持续更新AI回复准确率的上限取决于知识库的质量。很多团队上来就接大模型结果客户问非标准问题时AI编出假政策直接导致投诉。知识库这个问题不解决系统根本无法上线。知识库从哪里来最实用的办法是三步走第一步把历史客服聊天记录导出人工标注常见问题与标准答案生成FAQ表格第二步把官网帮助中心、售后服务政策、价格说明等文档清洗后入库第三步在上线后定期拉取AI答非所问的会话记录补充进知识库。知识库的存储有两种模式。量小的用MySQL全量模糊查询量大的用向量数据库。我们系统早期只有几百条FAQ用MySQL的LIKE查询就能覆盖后来文档化才切了向量检索。建议小项目先用MySQL版跑通流程再升级向量库不要一上来就上重方案。检索命中后将知识内容拼接进系统Prompt。这也是为什么前面说Prompt里要求只依据知识库回答——没有这层约束模型很容易自己编。4.2 意图识别与路由分发AI还是人工得先分清楚不是所有消息都该AI回复。比如客户说我要投诉叫你们领导来这种情绪激烈的消息AI哪怕回答得再好客户也只会更火大。所以系统在AI回复前先过一层意图分类。我当前用的是双路识别第一路是规则引擎检测敏感词、强意图词、事件触发比如出现投诉工商律师这些词直接转人工第二路是大模型分类把用户消息发给一个小模型分类为{售前咨询, 售后问题, 投诉, 闲聊, 需要人工}五类。路由逻辑$intent classifyIntent($userMessage); if ($intent complaint) { transferToHuman($externalUserId, 客户情绪激动疑似投诉); return; } if ($intent chat !$this-businessMode) { // 闲聊模式可自行判断一般不建议AI回复无关话题 transferToHuman($externalUserId, 闲聊请求); return; } $answer $aiGateway-chat($messages);这里要提醒一个容易踩的坑把闲聊类问题也当成业务咨询。客户有时候会问你是机器人吗你吃饭了吗AI回复得再像人也远不如明确告知我是AI助手如需人工请回复人工来得诚实。设定上一定要让客户知道可以随时找真人这是合规和信任的双重底线。4.3 人工接管与坐席工作台的联动设计AI和人工互相配合才叫客服系统。没有人工出口的纯AI客服本质上就是个聊天机器人客户一旦遇到AI解决不了的问题直接流失。人工接管分三档第一档消息识别出强投诉意图系统立即创建工单并通知坐席第二档AI连续两次无法命中知识库自动把当前会话升级为需人工处理在坐席工作台出现红色待办第三档客户主动输入转人工人工客服系统锁定人工坐席分配。转人工时要把会话上下文一并传过去。这里我用的是消息快照把最近10轮对话在数据库存一条快照坐席打开工单时直接看到AI与客人的对话历史避免客户重复描述问题。这个功能坐席评价极高认为终于不用让客户重新说一遍了。坐席工作台我建议做成一个简单的Web页面参考这样一个结构左侧排队列表显示客户头像、最后消息、等待时长中间对话窗口支持快捷回复、转接、结束会话右侧客户资料卡片标签、历史订单、AI摘要。4.4 欢迎语与客户生命周期管理企业微信欢迎语的20秒窗口期是最容易被忽视的隐藏功能。客户加好友后20秒内可以主动给客户发一条消息不需要消耗24小时窗口。这个能力非常适合用来做AI客服的开场白快速发出一条带菜单的欢迎语如回复1查物流回复2查订单回复3转人工既引导客户互动又用菜单降低了AI误答率。我实际测试下来有菜单引导的会话AI兜底率明显比开放式提问更低。客户看到选项后更倾向于用明确指令提问意图识别的准确度也更高。5. 全天候运营的稳定性保障兜底策略、限流机制与数据安全5.1 AI接口的稳定性兜底全天候服务意味着系统必须为大模型的不响应做好预案。AI接口在高并发时段偶尔超时或返回5xx如果直接把错误抛回给客户端客户看到的就是系统开小差了体验极差。我的兜底分三层第一层超时重试。单次请求如果超过20秒切备用模型再发一次第二层延迟回复降级。如果两次都失败在Redis里标记该客户为延迟处理等模型恢复后用主动消息把结果推送给客户而不是当场报错第三层人工兜底。AI连续失败的会话直接插入人工工单队列。实际运营中晚高峰瞬间流量会造成模型排队。这时延迟回复策略特别好用——客户不会感知到系统闪断只是想怎么回复稍慢一点几分钟后答案照样送到。5.2 限流与黑名单机制AI客服接入企业微信后必然要面对两类恶意行为一类是刷接口的另一类是高频骚扰的。没有限流机制一个客户一分钟发100条消息直接把你的模型成本烧穿。我的做法在消息入口处做流量控制$key rate_limit:{$external_userid}; $count Redis::incr($key); if ($count 1) { Redis::expire($key, 60); // 60秒内 } if ($count 10) { // 进入黑名单直接转人工不再消耗AI logRiskUser($external_userid); clickSendTemplateMessage($external_userid, 请您稍等当前咨询人数较多。); return; }这里不是粗暴封禁而是把高频用户自动切到人工通道。因为有一部分高频咨询是真实需求的客户比如正在退单、正在投诉“封杀”反而会惹恼他们。谨慎起见可以把频次阈值设置得宽一些一分钟超过15条再建议转人工。5.3 敏感信息脱敏与审计客户消息里经常包含手机号、地址、订单号等敏感信息。如果直接把这些信息回传给大模型的API接口会带来数据安全风险。我在入站时做了一层脱敏处理识别出手机号、银行卡、身份证号等模式替换成***后再发往大模型但回传到人工坐席工作台时保持原始数据可用。这样设计意味着AI只负责处理业务逻辑不应该拿到客户的隐私细节涉及隐私的操作全部转到安全的内部数据库处理。同时所有会话消息在企业微信后台都有会话存档配合后台审计完全满足合规要求。5.4 运营监控与自动告警全天候系统的另一个关键是得让系统自己看着自己。我搭了一套极简监控每分钟跑一次定时任务检查Redis队列积压数超过500就推送告警每十分钟统计一次AI调用成功率低于95%就通知管理员把每次AI调用的耗时时长存入日志表画出耗时曲线便于发现模型性能劣化。告警通道直接用企业微信群机器人出了问题群里立刻出现消息。做到这一步系统的无人值守才算真正合格。6. 源码结构、部署实践与踩坑实录6.1 推荐的项目目录结构如果是从零写或者拿开源源码改我建议按下面这个结构组织项目分清晰了后面维护省心得多app/ ├── Controller/ # 控制器层处理HTTP回调 │ ├── WecomController.php │ ├── MessageController.php ├── Service/ # 业务服务层 │ ├── MessageService.php # 消息收发 │ ├── AiGateway.php # AI统一网关 │ ├── KnowledgeService.php # 知识库检索 │ ├── IntentService.php # 意图识别 │ └── TransferService.php # 人工转接 ├── Model/ # 数据实体 ├── Config/ # 配置文件 ├── Job/ # 队列任务 │ ├── HandleMessageJob.php │ └── SendMessageJob.php ├── route.php # 路由定义 └── public/index.php # 入口文件Controller只做参数解析和签名校验业务逻辑全部放在Service层。别把大模型Prompt、知识库检索代码堆在控制器里那样后期改一个需求就要翻遍所有文件。6.2 环境部署的推荐组件部署推荐Nginx PHP 8.1/8.2 MySQL 5.7以上 Redis。整套装下来2核4G的云主机就能扛住日均几万条会话消息。有几个组件配置值得注意PHP-FPM启用pm dynamic并设置pm.max_children约为内存大小的四分之一避免同时并发超量导致内存溢出Nginx设置client_max_body_size 16m避免企业微信推送的大文件卡住所有与企微API的通信必须走HTTPS证书过期前30天设置定时提醒。6.3 上线前后的三大坑这套系统上线前后我踩过几个印象深刻的坑写出来供大家参考。第一个坑回调消息重复处理。企业微信兜底重试机制是回调返回非200或超时会隔一段时间重新推送同一消息。我的队列里明明消费成功了但因为返回给企业微信的响应里多了空格触发了重试导致数据库里出现重复会话。解决办法回调处理结束立刻exit(success)消费队列任务设置消息幂等依据MsgId去重。第二个坑AI返回结果包含Markdown格式。大模型的回复经常带**加粗**或- 列表直接发给客户就是一堆乱码符号。我写了一个清洗函数把Markdown标记统一去掉再把超长文案按企业微信的2000字上限截断分段发送。注意分段流的顺序别让客户先看到后半段。第三个坑并发请求下Redis归属混乱。早期直接用用户的昵称当Redis键两个重名客户上下文串了。后来一律改用企业微信返回的external_userid作为唯一键彻底解决。第四个坑也提醒所有同行不要图省事用非官方多开工具同时登多个企业微信号。现在风控很严一旦封号所有的客户关系、消息记录全没了。企业微信本身支持多身份认证和多企业切换按官方机制管理账号才是正道。做客服系统的朋友迟早会被问能不能帮我把几个号同时挂机我建议直接拒绝。6.4 源码开源还是自用我的建议最后说源码这件事。网上流传的企业微信AI客服系统源码很多是半成品要么没接大模型要么只做了回调接收。建议拿到源码后先按上述链路检查消息接收-队列-意图识别-AI调用-回复发送-人工兜底是否完整。自己写一遍核心逻辑比直接改别人的bug效率高得多。如果业务规模不大直接用我上面给的代码骨架扩展一周就能上线MVP。如果打算做产品化那务必把知识库、工单系统、数据看板做成可配置化模块后面对接不同客户会很省力。这套系统跑起来之后客户那边最大的感受是半夜两点问我的快递到哪了三秒内就能得到答复。以前列出的非工作时间无法响应的免责说明现在可以删掉了。AI值班不光是省成本更重要的是真正把客户的等待时间压缩到近乎为零。有空我也会继续往里面加多模型协作、自动生成知识库条目的功能这套东西的想象空间还很大。最后再分享一个我的小习惯每次上线新话术或知识库调整都要在测试环境里先用真实客户的问题跑一遍对比回答准确率。AI客服不是上线就完事它跟真人客服一样需要持续培训和考核。系统的下一期优化不能只靠程序员闭门造车一定要把客服团队的经验不断沉淀进知识库AI才会越用越聪明。