你不是说去洗澡了吗?
我还没碰键盘,聊天框已经自动回了过去:
刚开完会,晚点跟你说。
看到这句话,我后背一下就凉了。
更要命的是,脚本觉得对方还在追问,隔了几秒又补了一条:
收到,我这边先忙。
一个说去洗澡,一个说刚开完会。再自动回复两轮,我今晚估计就不用写代码了,直接写检讨。
我第一反应不是关微信,而是先把 Python 进程杀掉。
ps -ef | grep wechat_reply
kill -9 28431
进程停了以后,我翻了一下日志,问题很快就露出来了。
sender=小鱼 text=你不是说去洗澡了吗
rule=busy_status reply=刚开完会,晚点跟你说
status_source=local_cache status_age=187min
status_age=187min。
脚本读到的是三个小时前的状态。
这东西原本不是拿来糊弄女朋友的。我白天写代码时不想一直切微信,就写了个简单的自动回复程序。群消息不处理,陌生人不处理,只给几个固定联系人回复“正在开会”“稍后联系”这种状态。
消息读取那层我用的是桌面自动化适配器,没去碰微信协议。真正容易翻车的也不是怎么点发送按钮,而是中间这段判断逻辑。
第一版写得很随意:
defbuild_reply(text: str, current_status: str) -> str | None:
if"在吗"in text:
return"在,稍等一下"
if"干嘛"in text or"忙吗"in text:
return current_status
if"收到没"in text:
return"收到了,我晚点处理"
returnNone
看着没问题,实际上全是问题。
current_status 从本地缓存里读,下午开会时写进去的是“刚开完会,晚点说”。晚上忘了更新,脚本还拿它当真。
另外,“你不是说去洗澡了吗”里面虽然没有“忙吗”,但我在消息预处理时把疑问句统一归类成了状态询问。这个归类白天用着挺省事,放到聊天里就非常欠揍。
我不太信所谓“智能回复”。聊天不是接口调用,对方上一句说了什么、现在几点、你刚才承诺了什么,都会影响下一句。只靠关键词硬匹配,早晚出事。
后面我把自动回复改成了一个很保守的闸门。能确认的才回复,涉及上下文的直接转人工。
from dataclasses import dataclass
from datetime import datetime, timedelta
from pathlib import Path
import json
STATE_FILE = Path("presence.json")
@dataclass
classMessage:
sender: str
text: str
received_at: datetime
MANUAL_WORDS = (
"不是说", "为什么", "在哪",
"截图", "视频", "电话", "生气"
)
defread_presence(now: datetime) -> str | None:
ifnot STATE_FILE.exists():
returnNone
raw = json.loads(STATE_FILE.read_text(encoding="utf-8"))
updated_at = datetime.fromisoformat(raw["updated_at"])
if now - updated_at > timedelta(minutes=20):
returnNone
return raw.get("reply")
defdecide(message: Message) -> tuple[str, str | None]:
text = message.text.strip()
if any(word in text for word in MANUAL_WORDS):
return"manual", None
if text in {"在吗", "方便吗"}:
return"auto", "看到了,我一会儿回你"
if"忙吗"in text or"在干嘛"in text:
presence = read_presence(message.received_at)
if presence isNone:
return"manual", None
return"auto", presence
return"manual", None
这里我宁愿让脚本少回,也不让它瞎回。
状态超过二十分钟直接作废;出现“不是说”“在哪”“电话”这类词,程序只弹提醒,不发送消息。发送前还留了几秒缓冲,键盘一动就取消。
改完以后,日志变成了这样:
sender=小鱼 text=你不是说去洗澡了吗
decision=manual reason=context_risk
action=notify_only
这条日志看着就舒服多了。
后面我又补了一个限制:同一个联系人五分钟内最多自动回复一次。之前脚本连续回两条,不是线程有问题,而是每条新消息都会重新触发规则,程序根本不知道自己刚刚已经说过话。
自动回复最难的地方,从来不是调用发送接口。
接口发错了还能重试,话说错了,重试只会让事情更严重。尤其是亲近的人,聊天里大量信息都藏在上下文里。程序看到的是字符串,对方看到的是态度。
现在这套脚本我还在用,不过只敢处理“在吗”“收到没”这种没什么歧义的消息。
至于“你不是说去洗澡了吗”这种问题,Python最好闭嘴,我自己回。