个人微信聊天接口 API:从单条发消息到一段完整对话
多数人对“个人微信聊天接口 API”的第一理解是:一个发消息的接口。但真正做过客服机器人就会知道,发一条消息是最简单的一步——难的是让机器人像人一样完成一段对话。核心原因是聊天是有状态的:一句“那多少钱”本身没有意义,它的含义取决于上一句问了什么,而单个发送接口对此一无所知。
所以个人微信聊天接口 API 实际需要四层能力,缺一层对话就不成立:发送(把消息送出去)、接收(Webhook 拿到对方的话)、上下文(记住这段会话之前聊了什么)、时序(保证同一会话的消息按顺序处理)。这篇按这四层展开,最后给出一个能跑多轮对话的完整实现。
接口来自 WTAPI——基于 iPad/Mac 协议的微信机器人接口服务,把消息、好友、群、朋友圈这些能力做成标准 HTTP API。说直白点:你的程序调用它给的网址,就能用这个微信号收消息/发消息/管群,扫码登录、掉线重连这些协议层的工作由它完成。
先给最基础的收发闭环——这是四层里的前两层,跑通它才有后面的讨论:
# 个人微信聊天接口: 收发最小闭环 | 真实接口, 替换 TOKEN 后可直接运行
# 文档站: weiti.apifox.cn 官网: www.chuapi.com
import os, requests
from flask import Flask, request, jsonify
BASE = os.environ.get("WTAPI_BASE", "https://wx.chuapi.com")
TOKEN, APP_ID = os.environ["WTAPI_TOKEN"], os.environ["WTAPI_APPID"]
HDR = {"Content-Type": "application/json", "X-finder-TOKEN": TOKEN}
seen = set() # 第一层: 去重, 键必须是 Appid + NewMsgId
def send_text(to_wxid: str, content: str):
"""发送接口: toWxid 填好友 wxid 即私聊, 填 xxx@chatroom 即群聊"""
return requests.post(f"{BASE}/finder/v2/api/message/postText",
headers=HDR,
json={"appId": APP_ID, "toWxid": to_wxid, "content": content},
timeout=15).json()
app = Flask(__name__)
@app.route("/webhook", methods=["POST"])
def webhook():
p = request.get_json() or {}
# 先应答: 3 秒内必须返回, 否则平台判定失败并重推
if p.get("TypeName") == "AddMsg":
d = p.get("Data", {})
key = f"{p.get('Appid')}:{d.get('NewMsgId')}"
if key not in seen:
seen.add(key)
from_wxid = d.get("FromUserName", {}).get("string", "")
text = d.get("Content", {}).get("string", "")
is_group = from_wxid.endswith("@chatroom") # 群聊标识看后缀
if is_group and ":\n" in text:
text = text.split(":\n", 1)[1] # 剥发言人前缀
print(f"{'群' if is_group else '私聊'} {from_wxid}: {text[:40]}")
return jsonify({})
app.run(host="0.0.0.0", port=8080)
跑通后,手机发消息,终端能打印出来,说明收发两层已经通了。但如果此时直接接业务,你会发现机器人只能“一问一答”,第二句就断片——因为它没有第三层。
一、聊天接口的四层能力
| 层 | 解决问题 | 缺了会怎样 |
|---|---|---|
| ① 发送 | 把消息送出去 | 完全没有输出 |
| ② 接收 | 拿到对方消息 | 无法感知用户 |
| ③ 上下文 | 记住这段会话历史 | 多轮对话断片,答非所问 |
| ④ 时序 | 同会话消息按序处理 | 回答错乱,回复上上个问题 |
第 ① ② 层是接口能力,第 ③ ④ 层是你要在应用侧做的事。这也解释了为什么很多人“接口都调通了,机器人却很笨”——问题不在接口,在应用层的状态管理。
二、发送接口:一个方法覆盖私聊和群聊
发送这块最容易产生误解的是:私聊和群聊不需要两个接口。postText 的 toWxid 填什么,消息就发到哪:
| toWxid 填什么 | 效果 |
|---|---|
filehelper | 发到自己的文件传输助手,开发自测专用 |
| 好友 wxid | 私聊发给该好友 |
xxx@chatroom | 发到该群 |
同一个接口族里还有图片、文件、链接卡片等消息类型,路径不同但参数结构一致:appId + 目标 + 内容。
发送侧有两个工程要点:
- 必须有超时。不设
timeout,一次网络卡死就会挂住线程,回调队列随之堵死。 - 批量发送必须限流。单号每分钟控制在 30 条以内,多个会话的批量任务逐条错开 3-5 秒——个人号的风控对发送速率比对企业号敏感得多。
三、接收层:聊天和普通接口的本质区别
发送是同步调用,接收是异步回调,这个结构差异决定了聊天接口的特殊性:你无法主动“拉取”消息,只能被动接收。
回调处理有三个硬约束,在聊天场景下尤其不能违反:
| 约束 | 具体要求 | 聊天场景下的后果 |
|---|---|---|
| 3 秒内应答 | 立即返回 200,业务异步 | 超时重推,用户收到重复回复 |
| 幂等去重 | 键为 Appid + Data.NewMsgId | 单独用 NewMsgId,多号场景会撞 |
| 群消息剥前缀 | 正文是 发言人wxid:\n内容 | 群里关键词永远匹配不上 |
第三条是聊天场景独有的坑:私聊聊得通,一进群就失灵,原因几乎都是没剥前缀。
四、上下文层:让对话能接上第二句
这是把“接口”变成“对话”的关键。最小的上下文管理只需要一个按会话 ID 索引的历史队列:
# 第四层: 会话上下文 + 多轮对话 | 需先装 flask requests
from collections import defaultdict, deque
import threading, time
# 会话 ID: 私聊用对方 wxid, 群聊用群 ID + 发言人(群内每人独立上下文)
history = defaultdict(lambda: deque(maxlen=10)) # 每个会话保留最近 10 条
lock = threading.Lock() # 串行保证时序
def conv_key(from_wxid: str, is_group: bool, speaker: str = "") -> str:
return f"{from_wxid}#{speaker}" if is_group else from_wxid
def on_message(from_wxid: str, text: str, is_group: bool, speaker: str = ""):
key = conv_key(from_wxid, is_group, speaker)
with lock: # 第四层: 时序保证
history[key].append({"role": "user", "content": text, "ts": time.time()})
ctx = list(history[key][-6:]) # 取最近 3 轮作为上下文
# 这里接你的意图识别 / 大模型调用
reply = decide(ctx)
history[key].append({"role": "assistant", "content": reply, "ts": time.time()})
send_text(from_wxid, reply) # 锁外发送, 避免阻塞
def decide(ctx: list) -> str:
"""占位: 用规则或大模型替换。上下文 ctx 里就是最近几轮对话"""
last = ctx[-1]["content"] if ctx else ""
if "多少钱" in last or "价格" in last:
return "价格表已私信, 再回复「报价」获取完整方案。"
if "人工" in last:
return "正在为你转接人工, 工作时间 9:00-21:00。"
return "收到, 我看看。"
这段代码里有两个设计决定值得说明:
- 群聊的会话键是“群 ID + 发言人”。如果只用群 ID,群里三个人的对话会混进同一个上下文,机器人会答非所问。这是群聊场景最常见的上下文错误。
- 加锁保证同一会话串行。两个回调并发进来时,不加锁会导致历史顺序错乱,机器人回复上一条的问题。锁只保护内存操作,发送放到锁外,避免接口延迟拖住整个队列。
decide() 用规则占位,换成大模型时只需把 ctx 拼成 messages 数组传入——上下文结构已经是标准格式。
五、群聊和私聊的四处差异
聊天接口在群聊场景下不是“加个参数”那么简单:
| 维度 | 私聊 | 群聊 |
|---|---|---|
| 会话标识 | 对方 wxid | 群 ID(@chatroom 结尾) |
| 正文格式 | 纯文本 | 发言人wxid:\n内容 |
| 上下文隔离 | 一人一份 | 按发言人分别隔离 |
| 响应策略 | 可全量响应 | 建议只响应 / 命令或 @ |
最后一条是运营层面的经验:群聊里如果不加限制地响应所有消息,机器人很快就会因为刷屏被群主踢掉。这也是为什么第四节的实现里,群消息走的是独立分支。
六、接入自检清单
- 发送是否设置了
timeout - 回调是否 3 秒内应答,业务是否异步
- 去重键是否含
Appid - 群消息正文前缀是否剥掉
- 群聊上下文是否按“群 ID + 发言人”隔离
- 同一会话是否有串行保护,避免回复错乱
- 发送是否限流,单号每分钟是否控制在 30 条内
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐


所有评论(0)