Python 对接 Coze 智能体做微信自动回复:三步调用与并发锁避坑
之前用 Dify 接过一轮微信消息,体验不错,但团队里做运营的同事更熟 Coze——插件市场现成的多,工作流拖拽也顺手。那就把微信消息接到 Coze 智能体上,让机器人回复直接复用 Coze 里配好的知识库和插件。
链路本身不复杂:微信来消息 → Webhook 推给我的 Python 服务 → 调 Coze 对话接口拿到回答 → 调发消息接口回给用户。真正花时间的只有两处:Coze 的非流式调用要分三步走(不是一次请求拿结果),以及同一个用户连发两条消息会直接报 4016。下面按我实际搭的顺序来。
第一步:收微信消息
微信侧用托管 API 的 Webhook 回调,消息来了平台主动 POST 到我起的 HTTP 服务。先起一个最简单的接收端:
# WTAPI 微信机器人接口平台 | 文档: https://weiti.apifox.cn
# 本文依赖:
# - 收消息: Webhook 回调 TypeName=AddMsg (3秒内必须响应, 去重键=Appid+Data.NewMsgId)
# - 发消息: postText (appId/toWxid/content)
# 相关能力: 群管理 / 朋友圈 / 小程序卡片模块见文档
import time
import hashlib
import threading
import requests
from flask import Flask, request
BASE_URL = "https://wx.chuapi.com"
HEADERS = {
"Content-Type": "application/json",
"X-finder-TOKEN": "<your_token>",
}
app = Flask(__name__)
seen_msg_ids = set() # 简单去重,生产环境换 Redis
@app.route("/callback", methods=["POST"])
def callback():
data = request.json
# 先应答,再慢慢处理——平台要求3秒内响应
threading.Thread(target=handle_message, args=(data,)).start()
return ""
两个细节:
- 3 秒响应是硬要求。收到回调先返回空串或 200,处理逻辑扔到线程里慢慢跑。如果同步处理,Coze 响应慢一点就会被平台判定超时重推,一条消息回三遍。
- 去重键是
Appid + Data.NewMsgId,不是 MsgId。网络抖动时平台会重推同一个事件,不去重用户就会收到重复回复。
第二步:解析消息,只处理该处理的
回调报文长这样(示例数据):
{
"TypeName": "AddMsg",
"Appid": "wx_xxxx",
"Wxid": "机器人本号的wxid",
"Data": {
"MsgId": 123456,
"NewMsgId": 789012345,
"FromUserName": {"string": "发送者wxid"},
"MsgType": 1,
"Content": {"string": "你好"},
"CreateTime": 1729000000
}
}
解析时注意三点:
def handle_message(data: dict):
if data.get("TypeName") != "AddMsg":
return # 只处理新消息,Offline/ModContacts 等事件跳过
msg = data["Data"]
msg_key = f'{data["Appid"]}_{msg["NewMsgId"]}'
if msg_key in seen_msg_ids:
return # 重复事件,直接丢弃
seen_msg_ids.add(msg_key)
from_wxid = msg["FromUserName"]["string"]
if from_wxid.endswith("@chatroom"):
return # 这篇先只接私聊,群消息的处理是另一个话题
if msg.get("MsgType") != 1:
return # 只处理文本消息(1),图片语音等先不管
content = msg["Content"]["string"] # 注意 Content 是嵌套对象
reply = ask_coze(from_wxid, content)
send_text(data["Appid"], from_wxid, reply)
Content 是个嵌套对象,取值要 msg["Content"]["string"],直接当字符串用会拿到一个 dict。这个地方我卡了十分钟。
第三步:调 Coze,三步缺一不可
Coze 国内版域名是 api.coze.cn。非流式模式不是"发一个问题等一个回答",而是三步:
POST /v3/chat发起对话,拿到conversation_idGET /v3/chat/retrieve轮询状态,直到completed(轮询间隔至少 1 秒)GET /v3/chat/message/list取回消息列表,找 assistant 角色、type 为answer的那条
COZE_TOKEN = "<coze_pat_token>"
BOT_ID = "<your_bot_id>"
COZE_BASE = "https://api.coze.cn"
def ask_coze(user_wxid: str, question: str) -> str:
headers = {"Authorization": f"Bearer {COZE_TOKEN}"}
# 1. 发起对话 (首轮不传 conversation_id)
# user_id 做哈希,避免把真实 wxid 透传给三方平台
user_id = hashlib.md5(user_wxid.encode()).hexdigest()[:16]
chat = requests.post(
f"{COZE_BASE}/v3/chat",
headers=headers,
json={"bot_id": BOT_ID, "user_id": user_id,
"additional_messages": [{"role": "user", "content": question,
"content_type": "text"}]},
timeout=10,
).json()
conv_id = chat["data"]["conversation_id"]
chat_id = chat["data"]["id"]
# 2. 轮询状态,间隔至少1秒
while True:
time.sleep(1)
status = requests.get(
f"{COZE_BASE}/v3/chat/retrieve",
headers=headers,
params={"conversation_id": conv_id, "chat_id": chat_id},
timeout=10,
).json()
if status["data"]["status"] == "completed":
break
if status["data"]["status"] == "failed":
return "我这边出了点状况,稍后再试"
# 3. 取回答: 找 role=assistant 且 type=answer 的那条
messages = requests.get(
f"{COZE_BASE}/v3/chat/message/list",
headers=headers,
params={"conversation_id": conv_id, "chat_id": chat_id},
timeout=10,
).json()
for m in messages["data"]:
if m["role"] == "assistant" and m["type"] == "answer":
return m["content"]
return "没拿到回复,换个问法试试?"
第四步:4016 并发错误,按用户加锁
跑通之后让同事帮忙测试,他手快连发了两条,第二条直接报错:code=4016,同一会话存在进行中的对话。
原因:Coze 同一个会话不支持并发请求。解决也直接——按 wxid 加一把锁,同一个用户的消息串行处理:
user_locks = {}
locks_guard = threading.Lock()
def get_user_lock(wxid: str) -> threading.Lock:
with locks_guard:
if wxid not in user_locks:
user_locks[wxid] = threading.Lock()
return user_locks[wxid]
def handle_message(data: dict):
# ...前面的解析逻辑不变...
lock = get_user_lock(from_wxid)
with lock: # 同一用户串行,不同用户之间仍然并行
reply = ask_coze(from_wxid, content)
send_text(data["Appid"], from_wxid, reply)
不同用户之间不受影响,同一个用户的消息排队处理——正好也符合真人聊天的节奏。
第五步:把回答发回微信
def send_text(app_id: str, to_wxid: str, content: str):
resp = requests.post(
f"{BASE_URL}/finder/v2/api/message/postText",
headers=HEADERS,
json={"appId": app_id, "toWxid": to_wxid, "content": content},
timeout=10,
).json()
if resp.get("ret") != 200:
print(f"发送失败: {resp}")
跑起来的效果(示例):
用户: 你们的退款政策是什么
机器人: (Coze 知识库命中的回答, 约2秒后到达)
小结
整条链路拆开就五件事:回调收消息(先应答再处理)、Appid+NewMsgId 去重、Coze 三步调用(发起→轮询→取答案)、按 wxid 加锁防 4016、postText 回发。Coze 侧的知识库和插件改动完全不用动 Python 代码,这是接智能体平台比自己裸调 LLM 最省心的一点。
接口路径、参数与回调字段以 WTAPI 文档和 Coze 开放平台文档为准。本文涉及的回调字段与发送接口均已核实,代码中的 token、wxid 为占位符,示例消息内容为演示数据。
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐



所有评论(0)