☰
微信在线AI客服系统全解析:PHP+大模型API落地实战
2026/9/26 3:02:06 网站建设 项目流程

简介:一套基于PHP开发的微信在线AI客服系统源码,面向需要快速搭建7×24小时智能客服平台的中小企业、开发者与运维人员。系统原生对接企业微信,既能进行文本对话、图片分析和视频分析,也内置对话管理、人工转接、咨询提醒等功能,可根据关键词与语境自动判断并作答,遇到AI无法解决的情况可一键转人工,保障服务闭环;对话管理支持同时跟踪多个会话进度,咨询提醒能及时唤起用户关注,提升互动体验。资源包为zip压缩包,共38个文件,大小约20.57MB;其中31个PHP源码文件是主体,实现微信接口、AI服务、会话管理等核心逻辑,同时附带配置文件、配置示例、系统功能介绍文档、基础网页文件及多份配置备份,便于二次开发、部署调试与快速回滚。源码以模块化方式组织,微信交互、AI算法、会话流程相互独立又互相配合,可按业务需求自由裁剪或扩展;配套的安装说明与配置示例覆盖从环境初始化到回复策略调整的关键步骤,降低落地门槛。已有65人学习/下载,适合具备一定PHP基础、想在真实项目中运用智能客服能力的开发团队参考。

1. 微信在线AI客服系统是什么:别把它当成“聊天机器人套壳”

搜「2026最新微信在线AI客服系统源码」的人,十有八九已经试过直接把大模型API怼到公众号里,结果不是回复超时就是被微信风控。这个标题背后的真实需求,不是要一个能聊天的机器人,而是要在微信生态里做一个合规、能落地、不会被封的自动客服通道:用户从公众号菜单或小程序里发一句话,系统在几秒内用AI生成回答,答不了的转人工,全程有会话记录可查、可复盘。和普通聊天机器人最大的区别在于,微信在线客服系统要同时处理三件事:微信消息链路的合法性、AI回复的时效性、以及人工兜底的可靠性。缺一个,这套系统就只配待在本地演示,上不了生产。

适合谁读?准备给公司公众号或小程序接AI客服的PHP后端开发,以及想评估「自己搭还是买SaaS」的技术负责人。下面整套方案我按最稳妥的落地路径来拆:公众号做承载,PHP做服务端,大模型API做大脑,数据表把每一次问答都存下来。这套组合能覆盖微信客服场景里九成以上的需求,而且每一步的坑都是可控的。

2. 从用户消息到AI回复:整个系统的链路与选型理由

2.1 三种消息通道怎么选:被动回复、客服消息、小程序云调用

微信生态里给用户发消息,常见做法是三种通道混用,很多人第一次搭就栽在通道选错上。

第一种是被动回复。用户发消息到公众号,微信服务器把消息POST到你的回调URL,你需要在5秒内返回一条XML响应,否则微信会报「该公众号暂时无法提供服务」。5秒对于大模型调用来说几乎不可能完成,除非你只回一句固定话术。

第二种是客服消息接口。用户主动发消息后的48小时内,你可以通过API主动推送消息给用户,不需要等用户再发。这个通道没有5秒限制,这是AI客服真正的主通道——先快速回一句「正在为您查询」,然后异步调大模型,再通过客服消息把结果推给用户。

第三种是小程序云调用。如果你做的是微信小程序客服,可以用云开发或云函数来收发客服消息,不走HTTP回调那一套。但云开发的冷启动延迟不可控,而且和小程序订阅消息的权限绑定较深,适合小程序重度业务但不适合通用客服场景。

我的选型结论是:公众号被动回复做兜底,客服消息做主力。被动回复保证用户感知到「系统有反应」,客服消息承载真正的AI结果。这套链路在微信里是合规的,也是目前做微信在线AI客服最主流的架构。

2.2 数据层的取舍:会话、上下文与消息表怎么设计

AI客服和普通聊天机器人最大的不同是:每次对话都要落库。微信允许你主动给用户发消息的时间窗口是48小时,但如果用户隔两天又来问,你需要从库里恢复上下文,不能每次都当成新用户。

我一般会建三张表:一张存用户会话,一张存消息流水,一张存AI配置。

-- 会话表:每个openid一条活跃会话 CREATE TABLE wx_session ( id INT AUTO_INCREMENT PRIMARY KEY, openid VARCHAR(64) NOT NULL, status TINYINT DEFAULT 1 COMMENT '1=机器人接待 2=人工接待', last_active_at INT DEFAULT 0 COMMENT '最后活跃时间戳', UNIQUE KEY idx_openid (openid) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 消息流水表:所有用户消息和AI回复都落这里 CREATE TABLE wx_message ( id INT AUTO_INCREMENT PRIMARY KEY, session_id INT NOT NULL, role VARCHAR(16) NOT NULL COMMENT 'user/assistant/system', content TEXT NOT NULL, msg_id VARCHAR(64) DEFAULT '', created_at INT DEFAULT 0, KEY idx_session (session_id), KEY idx_created (created_at) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 配置表:存当前使用的模型、提示词、开关 CREATE TABLE wx_ai_config ( id INT AUTO_INCREMENT PRIMARY KEY, config_key VARCHAR(32) NOT NULL UNIQUE, config_value TEXT, updated_at INT DEFAULT 0 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

这张会话表里status字段很关键:AI答不了的转人工,就是把这个字段从1改成2,同时把后续消息转发给客服工作台。消息流水表不存二进制文件,只存纯文本,方便做后续的数据分析和模型微调。msg_id存微信消息的唯一ID,用来去重——微信回调在网络抖动时会重复推送,这是实测翻车率最高的点之一。

2.3 AI侧接入:为什么推荐OpenAI兼容接口而不是直接SDK

现在市面上的大模型API,绝大多数都兼容OpenAI的/chat/completions接口格式。这意味着你用一套HTTP调用代码就能切换不同的模型供应商。

我建议把AI调用封装成一个独立函数,把base_url和api_key放在配置里,而不是今天用A家SDK、明天换B家SDK。搜索词里那些「ai编程」「ai agent」相关的需求也印证了这一点:大家都想用一套代码接多家模型。

class AiClient { private string $apiKey; private string $baseUrl; private string $model; public function __construct(string $apiKey, string $baseUrl, string $model) { $this->apiKey = $apiKey; $this->baseUrl = rtrim($baseUrl, '/'); $this->model = $model; } public function chat(array $messages, float $temperature = 0.7, int $maxTokens = 800): string { $payload = [ 'model' => $this->model, 'messages' => $messages, 'temperature' => $temperature, 'max_tokens' => $maxTokens, 'stream' => false, ]; $ch = curl_init($this->baseUrl . '/chat/completions'); curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_POSTFIELDS => json_encode($payload, JSON_UNESCAPED_UNICODE), CURLOPT_HTTPHEADER => [ 'Content-Type: application/json', 'Authorization: Bearer ' . $this->apiKey, ], CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 10, ]); $response = curl_exec($ch); $status = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); if ($status !== 200) { throw new RuntimeException('AI接口返回异常,HTTP状态码:' . $status); } $data = json_decode($response, true); return $data['choices'][0]['message']['content'] ?? ''; } }

这里CURLOPT_TIMEOUT我固定在10秒,因为客服消息接口虽然不受5秒限制,但用户等太久体验会很差。temperature在客服场景里不要超过0.7,超过这个值AI会开始「发挥」,容易说出不严谨的承诺。max_tokens按客服回复的常规长度设置在800左右,既够用又不浪费token成本。

3. 搭建最小可用版本:回调验证、数据库初始化与AI对接

3.1 第一步:服务端回调与Token验证

微信公众号后台配置服务器URL时,会往你的接口发一个GET请求,带signature、timestamp、nonce、echostr四个参数。你的代码需要校验签名,然后把echostr原样返回,否则配置不通过。这个步骤几乎没有技术难度,但坑全在细节里。

// callback.php 微信服务器配置验证 public function verify() { $signature = $_GET['signature'] ?? ''; $timestamp = $_GET['timestamp'] ?? ''; $nonce = $_GET['nonce'] ?? ''; $echostr = $_GET['echostr'] ?? ''; $token = 'your_wechat_token_here'; // 与公众号后台填写的一致 // 微信签名规则:token、timestamp、nonce按字典序排序后sha1 $tmpArr = [$token, $timestamp, $nonce]; sort($tmpArr, SORT_STRING); $tmpStr = sha1(implode($tmpArr)); if ($tmpStr === $signature) { echo $echostr; return; } http_response_code(403); }

这里有两个容易翻车的点。第一,sort必须用SORT_STRING,用默认的SORT_REGULAR在纯数字字符串上可能排序结果不一致。第二,文件里不能有任何BOM头或多余输出,echo $echostr之前不能有空行、不能有<?php以外的内容。你在本地测试没毛病,一上生产就验证失败,八成就是BOM的问题。另外,公众号后台填的Token和代码里的$token必须完全一致,这个属于低级错误,但频率高得离谱。

3.2 第二步:建三张表存会话和上下文

表结构在前面已经给了SQL,这里说怎么用。收到用户消息后的处理顺序是:先查会话表,没有就插入新会话;再把用户消息插入消息流水表;然后取最近N轮历史消息拼成上下文;最后调用AI并保存回复。

// 收到微信消息后的主处理逻辑 public function handleMessage(array $xml) { $openid = $xml['FromUserName']; $content = trim($xml['Content'] ?? ''); $msgId = $xml['MsgId'] ?? ''; // 1. 按msgId去重,微信回调可能重复推送 if ($this->isDuplicate($msgId)) { return $this->emptyReply(); } // 2. 获取或创建会话 $session = $this->getOrCreateSession($openid); $this->saveMessage($session['id'], 'user', $content, $msgId); // 3. 检查是否命中人工接待状态 if ($session['status'] == 2) { // 转给人工客服工作台,这里写入待处理队列 $this->pushToAgentQueue($session['id'], $content); return $this->xmlReply('已为您转接人工客服,请稍候。'); } // 4. 组装上下文,调AI,存AI回复 $history = $this->getRecentHistory($session['id'], 10); $reply = $this->askAi($history, $content); $this->saveMessage($session['id'], 'assistant', $reply, ''); // 5. 先快速回复,再异步推送完整结果 return $this->xmlReply('正在为您查询,请稍候...'); }

getRecentHistory返回最近10轮对话,这个数字不是拍脑袋定的——10轮覆盖了绝大多数客服咨询场景的上下文长度,同时也控制住了每次请求的token消耗。如果你做的是售后查询类场景,可以适当放宽到15轮;如果是闲聊场景,5轮就够。

3.3 第三步:异步调大模型并主动推送回复

被动回复的5秒限制在这里发挥关键作用。你上面代码里xmlReply('正在为您查询,请稍候...')就是被动回复的快速响应。真正的大模型调用和客服消息推送,需要放到一个异步任务里执行。

// 异步任务:调用AI并通过客服消息接口推送 public function asyncReplyWithAi(int $sessionId, string $openid, string $content) { // 组装messages数组 $messages = []; $messages[] = ['role' => 'system', 'content' => $this->getSystemPrompt()]; $history = $this->getRecentHistory($sessionId, 10); foreach ($history as $msg) { $messages[] = [ 'role' => $msg['role'] === 'user' ? 'user' : 'assistant', 'content' => $msg['content'], ]; } // 调AI $ai = new AiClient($this->config['api_key'], $this->config['base_url'], $this->config['model']); try { $reply = $ai->chat($messages); } catch (Exception $e) { $reply = '抱歉,服务暂时繁忙,请稍后再试。'; } // 存库 $this->saveMessage($sessionId, 'assistant', $reply, ''); // 通过客服消息接口推送 $this->sendCustomMessage($openid, $reply); } // 客服消息接口调用 private function sendCustomMessage(string $openid, string $content): bool { $accessToken = $this->getAccessToken(); $url = 'https://api.weixin.qq.com/cgi-bin/message/custom/send?access_token=' . $accessToken; $data = [ 'touser' => $openid, 'msgtype' => 'text', 'text' => ['content' => $content], ]; $result = $this->httpPost($url, json_encode($data, JSON_UNESCAPED_UNICODE)); $res = json_decode($result, true); // errcode为0表示成功,40001表示token失效需重试 return ($res['errcode'] ?? -1) === 0; }

客服消息接口的完整URL是https://api.weixin.qq.com/cgi-bin/message/custom/send,这是微信官方文档公开的接口路径。调用前必须先拿到access_token——这里有个宝典:access_token有效期7200秒,必须用文件或Redis缓存起来,不能每次调用都去微信换取。多实例部署时还要做锁,不然并发刷新token会导致一个实例的token把另一个的挤下线。这是微信接口调用里最经典的坑,后面避坑章节详细说。

4. 把“会说话”调成“会做客服”:指令、上下文与人工兜底

4.1 系统提示词决定客服性格,写不对就翻车

很多AI客服翻车,不是AI不行,而是系统提示词没写好。用大模型API的时候,system角色的消息就是客服的「岗位说明书」。你写「你是一个智能客服」,AI会给出教科书式的回答;你写清楚业务范围和边界,AI才会像个真正干活的客服。

private function getSystemPrompt(): string { $goods = $this->config['goods_info'] ?? '(商品或服务描述)'; return <<<PROMPT 你是在线客服小薇,为【{$goods}】提供售前咨询和售后支持。 规则: 1. 用简洁、友好的中文回复,每条回复不超过150字。 2. 只回答与业务相关的问题,包括:商品规格、价格、发货时间、退换货政策、优惠活动。 3. 不知道答案时,明确说「这个需要帮您核实一下」,不要猜测,不要编造。 4. 用户情绪激动或要求投诉时,回复「非常理解您的心情,我已为您记录,会安排专员跟进」,并简短安抚。 5. 严禁承诺任何未在业务资料中明确写到的赔偿、折扣或时间节点。 PROMPT; }

最后一条「严禁承诺」是真实业务里血泪换来的。AI在temperature偏高时容易顺着用户的话说「可以的亲,这边为您申请补偿」,结果客服工作台被用户投诉淹没。把这条写进system prompt还不够,建议在代码层再叠一层拦截,后面第6章会讲。

4.2 上下文窗口参数怎么设,才不烧钱也不失忆

大模型调用的成本里,上下文的token占比经常被忽略。每轮客服对话,你都要把最近10轮历史拼进请求里,假设每轮平均100 token,一次请求光历史就有1000 token。一天1000次咨询,就是100万token的上下文开销。

我的建议是按业务场景动态调整轮数:

场景历史轮数理由
售前咨询(规格、价格)5轮用户问题独立性强,上下文长了反而干扰判断
售后问题(退换货、物流)10轮需要追溯用户之前描述的问题细节
复杂工单类15轮封顶再多就超出窗口性价比,且容易让模型「遗忘」早期信息

代码实现时注意一点:拼messages数组时,system消息必须在最前面,后面按时间顺序排列user和assistant交替。有些模型对消息顺序敏感,连续两条user或连续两条assistant会导致输出质量下降。

4.3 关键词与人工转接:让AI知道什么时候该闭嘴

AI客服再聪明,遇到「人工」「投诉」「退款」「转专员」这些词时必须让路。这套逻辑不能靠模型自觉,要用关键词表硬编码并且在调AI之前就拦截。

// 转人工关键词表,命中即进入人工队列 private array $humanHandoverKeywords = [ '人工', '真人', '投诉', '12315', '赔偿', '退款', '转专员', '经理', ]; public function shouldHandoverToHuman(string $content): bool { foreach ($this->humanHandoverKeywords as $keyword) { if (mb_strpos($content, $keyword) !== false) { return true; } } return false; }

mb_strpos必须用多字节版本,普通的strpos在中文编码下会切错位置。命中关键词后,把会话状态改成人工接待,同时把这条消息推进客服工作台的待处理队列。这里注意不要直接调用客服消息接口推送「已转人工」,因为用户可能还没等到客服响应就开始反复追问,导致多条推送堆积。稳妥的做法是改状态后,由客服工作台端到端接管,系统不再对这条会话做AI回复。

5. 微信在线AI客服的常见坑与排查清单

5.1 回调验证一直失败,URL明明没错

现象:公众号后台配置服务器URL时,点击「提交」立刻提示「Token验证失败」。代码里echo了echostr,浏览器直接访问URL也能看到一串字符,但微信就是验证不过。

原因:八成是文件编码问题。PHP文件如果带BOM头,echo输出前会先输出不可见字符,微信拿到的echostr就多了前缀。剩下两成是Token不一致,或者sort比较方式不对。

解决:用十六进制编辑器检查PHP文件头部是否有EF BB BF,有就去掉;代码里sort($tmpArr, SORT_STRING)别省第二个参数;最后把<?php写到文件第一行第一列,前面不能有任何空行。

5.2 用户收到「该公众号暂时无法提供服务」

现象:用户发消息后,公众号回复这条系统提示,服务端日志里看不到任何回调记录。

原因:微信服务器在5秒内没收到你的响应。这类情况常见于回调URL执行了耗时操作——比如在handleMessage里直接同步调了大模型API,或者数据库查询特别慢。微信不是「5秒超时才报错」,而是「3秒就开始报错」,保险起见要在2秒内返回响应。

解决:回调入口只做三件事:解析XML、存消息落库、返回被动回复XML。把大模型调用和客服消息推送丢进队列或后台任务。落库操作也要做性能检查,单条INSERT通常在毫秒级,但如果你的服务器在海外或者数据库没走内网,这个延迟会放大。

5.3 回复内容带Emoji就报错45009

现象:AI生成回复后调用客服消息接口,返回errcode 45009,提示接口调用超过限额。但看调用频率并不高。

原因:45009在微信客服消息里有两个触发条件:一个是频率超限,另一个是回复内容格式非法。AI生成的内容里如果带了特殊Emoji或XML不支持的字符,微信会统一报45009。最隐蔽的是公众号被动回复场景里的XML转义问题,内容里有&或<符号没转义,微信直接把整条消息丢弃。

解决:客服消息接口(JSON格式)对Emoji的兼容性还行,但被动回复的XML必须严格转义。建议在输出前统一处理:把&、<、>替换成XML实体,同时过滤掉生僻Unicode字符。如果错误日志里频繁出现45009且内容和时间都对不上,优先怀疑字符问题而不是频率问题。

5.4 access_token并发刷新导致接口报错40001

现象:客服消息接口随机返回40001 invalid credential,过一会儿又自己恢复。多实例部署后出现频率明显上升。

原因:access_token是全局唯一的,每次调用token接口都会让旧token失效。多个Worker进程同时发现token过期,各自刷新,后刷新的把先刷新的顶掉了,持有先刷新token的实例就报40001。

解决:把access_token存Redis,加分布式锁。获取时先查缓存,没有才去微信换取并写回缓存,缓存过期时间设为7000秒而不是7200秒,留出刷新余量。还要强调手动刷新token接口一天有调用次数限制,所以绝对不能每次请求都调。

5.5 AI答非所问,日志里却一切正常

现象:AI调用成功、消息推送成功、用户也能收到回复,但内容明显答非所问——用户问退货流程,AI回答商品产地。

原因:这里大概率是上下文拼装顺序出问题了。我在排查时发现,getRecentHistory返回的消息按数据库自增ID排序,但多轮对话里用户连续发了两条消息才轮到AI回复,导致拼出来两条相邻的user消息。部分模型对这种情况的处理会混乱。

解决:拼装messages时做一个严格校验,相邻两条消息的role必须不同。如果出现连续user,把后一条合并到前一条的content里,用换行分隔。另外检查数据库里是否存了系统自动发的「正在为您查询」这类被动回复——这类消息不该进入上下文,否则模型会在「思考」里看到自己的交互词汇,产生混乱。给消息流水表加一个source字段,标记是user_input、ai_reply还是system_prompt,AI上下文只取前两种。

6. 让系统更像一个客服:敏感词过滤、会话复盘与限流

6.1 回复前先过一道本地规则

依赖模型自觉是不现实的,我在AI回复推送给用户前,会强制跑一遍本地规则过滤器。规则分两层:硬性拦截词和软性提示词。硬性拦截词包括「最」「第一」「绝对」等广告法违禁词,命中就把整条回复替换为兜底话术;软性提示词包括「随时」「肯定」「保证」这类模糊承诺,命中会在回复末尾追加一句「具体以门店/平台政策为准」。

// 硬性拦截,命中直接替换 private array $blockedWords = ['绝对', '百分百', '全网最低', '根治', '无效退款']; public function filterReply(string $reply): string { foreach ($this->blockedWords as $word) { if (mb_strpos($reply, $word) !== false) { return '抱歉,我刚才的表述不够严谨,请您以我们的官方客服确认为准。'; } } return $reply; }

这个过滤器要放在AI结果出来之后、推送之前。实测它对减少客诉非常有效,因为AI在输出时永远不知道你平台的红线在哪。广告法违禁词是重灾区,电商类客服系统尤其要注意。

6.2 每一天的对话记录导出来看趋势

客服系统的价值不只在回复用户,更在于每天沉淀的对话数据。我习惯每天凌晨跑一个统计脚本,从wx_message表里拉取前一天的数据,按问题类型做一个粗粒度的聚类。常见做法是:把每条用户消息的先验关键词命中情况记下来,比如包含「发货」归为物流类,包含「退」归为售后类,然后按类统计数量。

这能让你一眼看到今天的咨询热点是什么。举个例子:某天「退款」类消息突然涨了3倍,多半是订单系统出了问题,这时候在AI的system prompt里临时加一句「当前退款系统维护中,请引导用户登记并等待专员联系」,比让客服团队手动回复几百个人要高效得多。这个操作不需要复杂的NLP,关键词聚合在这个场景下够用了。

6.3 给AI加一个“限速器”

大模型API是按token计费的,客服场景又容易遇到恶意刷消息。我见过凌晨3点一个openid连续刷了200条消息,当天账单直接翻倍。

限流方案分两层:全局IP/QPS限流和单用户频控。单用户频控比较实用:同一个openid在60秒内只响应第一条消息,后续消息进队列但不调AI,直接返回「您的问题已收到,正在处理中」。

// 单用户限流:60秒内只处理一次AI调用 public function isRateLimited(string $openid, int $windowSeconds = 60): bool { $key = 'rate_limit:' . $openid; $lastTime = $this->redis->get($key); $now = time(); if ($lastTime !== false && ($now - (int)$lastTime) < $windowSeconds) { return true; } $this->redis->set($key, $now, $windowSeconds); return false; }

这个限速的意义不只省成本。微信对客服消息接口本身有每日次数限制,单用户频控能显著降低触发接口上限的概率。我见过不止一个项目因为没做限流,上午刚上线下午就被刷到接口锁定,解封流程要提工单,这就是没有后悔药的场景。

做这套系统半年后回头看,最值钱的不是AI调用代码,而是数据表和限流策略。只要你把上下文存干净、把人工兜底留好、把限速加上,这套微信在线AI客服就不会出大乱子。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询