自研 WorkBuddy Skill · 道合君客服值守 · 29 个文件 / 235 KB
这是一个在 Windows 上常驻的值守程序。个人微信里出现新消息,它把「客户说了什么 + 上文下文」交给 WorkBuddy 判断语气和意图,生成贴着原话的回复再发回去;只有课程安排、收费报价、合同方案、投诉这几类必须由人拍板的事,才截图转交。企业微信一个字节都不碰,只在出现未读红点时截图通知,绝不自动回复。
一句话:客户随时说话,程序先接住;拿不准的那几条,才交给人。
做培训、做服务的人都躲不开一件事:客户不问时间。晚上十点四十来一句「老师,那个课怎么报」,第二天早上才看见,人已经凉了。可要真做到秒回,等于把自己钉在屏幕上,一天什么都干不成。
这个值守程序站的就是这个位置。它每 60 秒巡检一次两个微信窗口,读到新消息就判一次:这条能不能自己回。能回的当场回,回不了的截图转给李雪。客户那边永远有人应,人这边不用盯着。
关键的一条设计是回复不靠模板。早先的版本用关键词规则,客户说一句「接口请写在我服务器上,配置在 D 盘那个文档里」,规则引擎只答得出「收到,您说的我记下了」——客户一眼就看出来这是机器人。现在把整段对话交给 WorkBuddy 读一遍,让它判断意图、写一句贴合原话的回复,再发回去。实测一次 33 到 41 秒。
第二条设计是企业微信按只读处理。个人微信能回就回,企业微信一个字节都不写:不点击、不输入、不激活窗口。理由写在下面这一节里,代价是真金白银掉过两次登录。
投诉、起诉、退款、赔偿这类硬拦截,直接转交,不问任何人。剩下的才轮到判断能不能自己回。
人设写在 persona.md 里,称呼「您」、一到两句、结尾不强行追问,给客户的每一句话都要过这道口径。
90 秒内回过的跳过,同一个人一小时最多三条。没有这两道闸,时间读歪的时候同一行会被反复判成新消息。
企微有一套输入注入检测。只要注入事件带上 LLMHF_INJECTED 标记,它就判定「设备环境异常」,直接退回扫码页。已经因此掉过两次登录,两次都发生在做 dry run 的时候——只激活窗口、点一下、输入几个字再清空,连回车都没按。
所以读企微只用 PrintWindow:它不需要窗口在前台,也不产生任何输入事件。写操作入口一律先查 send.WRITABLE = {"wechat": True, "wework": False}。
触发词:微信自动回复 / 监控微信新消息 / 客服值守 / 装一下那个监控 / 企业微信有红点通知李雪 / 转交给梦由 / 这个监控怎么又停了 / 开机自启没生效。
| 场景 | 它交付什么 |
|---|---|
| 下班后被客户堵住 | 客户问在不在、问课程怎么走、问开了没,它当场回一句像人说的话,不用等第二天。 |
| 客户问价格 | 先回「这个我请李雪跟您确认一下,稍后回复您」,同时把对话截图发给李雪,让能拍板的人接手。 |
| 企业微信攒了红点 | 只截图通知,不回一句话。人自己看,人自己回。 |
| 早上开工扫一眼 | 半夜压住没回的消息,早上第一轮巡检重新判成新消息,照常处理。 |
| 程序自己挂了 | 守护循环 20 秒后自动拉起;真起不来,日志里留一行,doctor.py 能查是哪个窗口不可读。 |
不适合拿它做的事也说清楚:它不碰群聊和公众号,只看单聊;不给企业微信发一个字;不报价、不承诺时间、不签任何确认——这三条是口径,写在人设里,任何一层都不许越过;不替人做决定,遇到必须由人拍板的事只转交,不猜。
企业微信能走无障碍接口,个人微信是 Qt 自绘的,只能截屏认字。同一个判断,读法完全两套。
四层任意一层挂了,下面的照常接手。客户永远有人应,程序也不会因为模型超时就装死。
WorkBuddy 是 Electron 应用,聊天文字通过 UI Automation 暴露出来,get_app_state(window_id, mode="text") 能拿到屏幕上全部可见文字,还带坐标。这一段不需要 OCR,准确率 100%——整套方案能成立,靠的就是这一条。
提问的协议也刻意做得简单:只要它回两行中文标签,不要 JSON。中文标签在文字流里不会和正文混淆,正则一抓就准。
# 发给 WorkBuddy 的要求:只回两行 动作:回复 (或者:转交) 内容:给客户看的话,40 字以内
这几十秒里窗口会被切到前台。如果检测到输入框里已经有内容,说明人正在里面打字,这一条就主动让开,退回规则引擎——不抢人的键盘。
| 坑 | 怎么处理 |
|---|---|
| 选中态读不出 | 上次点开的那行是绿底白字,OCR 直接吐空。反相、二值化、加白边、各种倍率都试过,无效。最后一招是绕开:用聊天区顶部的标题当名字。 |
| 时间丢一位 | 21:41 被读成 21 1,解析成 02:11,判成旧消息静默丢掉。现在给时间串打置信度,四位数字对不上就只登记、不推水位。 |
| 偏旁被拆开 | 「你」读成「亻+尔」,码位反而变多,打分逻辑主动选了错的版本。现在给偏旁倒扣分,再按表还原。 |
| 红点找错地方 | 区域开在列表右边缘,永远返回 0。真实位置是头像右上角,行顶往下 12 像素。 |
还有一条比时间更可靠的判据:红点是微信自己画的,不受 OCR 错字影响。红点亮着就强制按新消息处理,宁可多回一次,不许漏一条。
29 个文件,235 KB。主流程一份、人设一份、踩坑记录一份、脚本二十三个。
daohu-cs-monitor/ ├── SKILL.md 主流程:五条铁律 + 判断链路 + 故障对照表(8 KB) ├── config.json 人设、转交对象、安静时段、限频 ├── persona.md 回复口径:硬性规则 + 八种场景话术 ├── install.py 一键部署:复制程序、建 state/、写开机自启 ├── run-monitor.bat 守护循环:进程挂了 20 秒自动重启 ├── references/ │ └── lessons.md 十五条踩坑记录,每条都付过代价(8 KB) └── scripts/ 23 个脚本,5717 行 ├── monitor.py 主循环:巡检 → 判定 → 回复 / 转交(625 行) ├── brain.py 决策:四层判断的具体实现(709 行) ├── wbbrain.py 把 WorkBuddy 当大脑:提问、读回、解析(447 行) ├── send.py 发消息:写操作唯一入口,先查 WRITABLE ├── capture.py PrintWindow 抓图,零触碰 ├── layout.py 自适应布局:窗口尺寸变了不用重标定 ├── ocr.py OCR 封装,返回带坐标的文本行 ├── ledger.py 台账:每个会话处理到哪条、时间置信度 ├── inbox.py / watch.py 巡检:零触碰扫描与被动监控 ├── wx.py / ww.py 两个客户端的各自适配 ├── kimicu.py kimi-cu 的 stdio MCP 客户端封装 ├── llm.py HTTP 模型接口(CLI 那条路已经放弃) ├── doctor.py 开工自检:两个窗口读不读得到 ├── notify.py 给李雪补发一句话,手工救场用 └── winlist / winlib / winfix / act / probe / wwgeo / setkey
| 脚本 | 职责 |
|---|---|
monitor.py | 主循环。每轮先巡检,判出新消息就交给 brain,处理成功了才推水位。 |
brain.py | 四层判断的落地点。硬拦截、快路径、模型、规则,一层层往下走。 |
wbbrain.py | 整套方案的心脏。找到 WorkBuddy 那个任务、把对话喂进去、等它答完、把两行标签读回来。 |
send.py | 所有写操作的唯一出口。发之前先过口径清洗,内部人名一律拦掉。 |
ledger.py | 台账。记水位、记处理次数、记最近一次失败,还负责给 OCR 时间打分。 |
layout.py | 窗口布局自适应。微信换个尺寸、换台显示器,不用重新量坐标。 |
install.py | 换机器时唯一的入口。复制程序、建目录、写启动项,然后打印剩下三件手工活。 |
| 依赖 | 用在哪 |
|---|---|
| Windows 10 / 11 | 整套实现都长在 Win32 上:窗口枚举、PrintWindow、命名互斥体 |
| Python 3.13 | 全部脚本;run-monitor.bat 里写死了 WorkBuddy 自带那个解释器路径 |
| 微信 PC 版 | 保持登录,窗口可见(最小化也认,但是别关掉) |
| 企业微信 | 同上。只读,不产生任何输入事件 |
| WorkBuddy 并登录 | 回复由它生成。没开就退回关键词规则,不装死 |
Pillow + winocr | 截屏与识别,走系统的 OCR 引擎,不要额外装模型 |
kimi-cu.exe | 读窗口文字与点开会话的通道 |
五条铁律,改代码之前先读一遍。每一条下面都压着一次事故。
| # | 铁律 | 为什么 |
|---|---|---|
| 1 | 企业微信一个字节都不许碰 | 注入事件带标记,企微判定设备环境异常,直接踢出登录。掉过两次。 |
| 2 | 企微只认红点,不按时间判 | 人在企微里自己回过消息,列表时间照样前进。按时间判必然误报。 |
| 3 | 企微永不自动回复 | 写操作关死。改这一个开关,等于让企微再掉一次登录。 |
| 4 | 个人微信能回就回 | 默认转交是错的——客户问天气也转给李雪,等于让她当人形应答机。 |
| 5 | 不该回的时候别回 | 22:30 到 08:00 压着不推水位;同一个人一小时最多三条;群聊与公众号不碰。 |
第一道是时间置信度:四位数字对不上,就只登记发现时间,不推水位。第二道是 90 秒冷却,刚回过的那一行直接跳过。第三道是每小时三条上限。三道闸合起来,同一个客户不会连着收到三条一模一样的「在的,您说」。
安静时段那条也值得单独说:半夜给客户回消息,比不回更像机器人。这段时间返回「压住」,但是不推水位,早上第一轮巡检会重新把它判成新消息,照常处理,不算漏。
| 问题 | 处理方式 |
|---|---|
| 转交截图发给了李雪自己的窗口 | 截图文件名写死成同一个,先截客户后截李雪,后者覆盖前者。现在文件名带时间戳和序号,并且当场裁剪落地。 |
| 转交文案里写了同事名字 | 给客户的兜底话术里混进了内部交接说明。现在发给客户的话要过清洗,内部人名一律拦掉。 |
| 发现阶段就推了水位 | 处理失败也当成已经处理,那条消息永远不再处理。现在只有处理成功之后才推水位。 |
| 点开的是别人 | 行号用的是上一轮扫描的位置,只要回过一条,后面全错一格。现在点开前重新扫一遍,按名字定位。 |
| WorkBuddy 的任务名会自己变 | 发完第一句,标题就从设置那句话改成别的。现在反过来记:哪些标题不是我们的,剩下那一个就是。 |
| 一句话被拆成七行 | UI Automation 会把一句话按行吐出来。必须先拼成一条再正则,否则只取第一行就是半句话。 |
| 命令行调不动 WorkBuddy | 桌面版装的是精简 CLI,要靠主程序握手才认身份,外部调用一律返回未登录。这条路已经放弃,改走读屏。 |
解压后先跑 python install.py,程序会自己装到 %USERPROFILE%\WorkBuddy\daohu-msg 并写好开机自启,然后打印剩下要手工做的三件事。
:: 1. 解压,进目录 :: 2. 一键部署(默认装到 %USERPROFILE%\WorkBuddy\daohu-msg) python install.py :: 想换目录就带个参数 python install.py D:\tools\daohu :: 3. 先跑一轮看看日志,不动微信 python scripts\monitor.py --once :: 4. 没问题就前台常驻(或双击 run-monitor.bat) python scripts\monitor.py --interval 60 --auto-reply
部署完还有三件手工活,install.py 会把它们再打印一遍:把微信和企业微信都打开、保持窗口可见;打开 WorkBuddy 并登录;改 config.json 里的 targets.alert_contact,改成那台机器微信里李雪的备注名——必须跟备注一字不差,否则找不到人。
注意:技能目录里的 scripts/ 是可移植母版,不要从技能目录直接启动。它没有 state/,起来以后台账从零开始,会把所有老会话当成新消息再回一遍。请走 install.py 装到独立目录。