ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Coze Agent接入微信公众号:Flask中转与消息流转实战

Coze Agent接入微信公众号:Flask中转与消息流转实战 简介该压缩包是一份可直接运行的Coze Agent接入个人微信的完整源码项目面向希望为个人微信搭建智能自动回复助手的开发者解决在群聊与私聊场景中消息自动响应与日常管理问题。包内共3个文件包含HTML交互页面、项目配置文件以及inscode工程相关文件压缩后仅7KB轻量精简便于快速拉取部署、学习与二次修改。项目核心覆盖Coze Bot创建、人设与回复策略设置、API令牌获取、微信机器人服务搭建、Docker安装与docker-compose配置等完整环节并提供了启动微信机器人服务、验证回复效果及调整功能的说明帮助开发者从零跑通整体接入流程。整体源码结构清晰、可运行性强适合有一定编程基础、希望通过低成本方式实践微信智能化自动回复的开发者参考学习也可作为企业客服或个人助理场景的自动化交互基础。目前已有138人学习下载。1. 把 Coze Agent 接进微信这件事听起来是调接口做起来全是胶水代码“把 Coze Agent 接进微信”这件事听起来就是调个接口实际做起来完全是另一回事。微信侧有签名校验、5 秒超时、消息重试Coze 侧有异步轮询、速率限制、会话上下文工作量全在这两层之间的胶水代码上。本文讲一条可运行的落地链路从 Coze 开放平台拿 API Token 和 Bot ID用 Flask 写中转服务接收微信推送、调 Coze 工作流、把结果通过被动回复或客服消息发回用户。适合两类人刚在扣子上搭好智能体、想把它暴露给微信用户的 Agent 开发者接了私域客服、想用 Coze 工作流替代人工应答的运营工程师。读完能跑通最小版本也知道上线前要堵哪些坑。2. 动手前先看清全局Coze 开放 API、微信入口选型与消息流转架构这章先把架构横在面前。不先明白数据往哪流、鉴权怎么过后面写代码就是盲写。很多新手一上来就写路由、写 XML 解析结果连“微信为什么要验签”“Coze 为什么要轮询”都没搞清楚调试时全靠猜。我习惯先把链路画出来再决定每一层用什么方案。2.1 Coze Agent 怎么变成可被调用的 API发布、Token 与 Bot ID在扣子平台上搭好的智能体本质上是一个配置好的对话服务。要让微信侧的代码调用它第一步是在 Coze 的发布设置里选择「API」方式发布发布完成后去扣子开放平台创建访问令牌。这个令牌以pat_开头就是后续请求的 Bearer Token。另一个关键参数是 Bot ID在智能体基本信息页能看到是一串数字。这两个参数一个都不能少Token 负责鉴权Bot ID 决定你调用的是哪个智能体。Coze 开放平台的接口有两代常见做法是主用 v3。v3 的对话接口是异步设计发起请求后服务器先返回一个chat_id和conversation_id真正的回复要拿着这两个 ID 去轮询chat/retrieve接口。为什么这么设计因为 Agent 背后可能挂了工作流、知识库、插件一次对话可能要跑好几秒甚至几十秒同步返回容易把 HTTP 连接拖死。代价是调用方代码稍微复杂一点但换来的是对长任务的容错能力。这里有一个容易混淆的点v1 接口POST /v1/chat是同步返回的有些教程还在用。如果你的 Agent 逻辑简单、响应快v1 确实省事但一旦接入工作流或者知识库检索响应时间很容易超过微信的被动回复限制v3 的异步模式配合后续的客服消息推送反而更稳。我在生产环境里一律用 v3血泪经验同步接口在演示时很爽上线后被超时投诉打脸。2.2 微信侧的三种接法公众号、企业微信与个人号微信生态里能承接 Agent 消息的入口不止一个选错入口后面全是坑。我把常见的三种列一下方便直接对照你的场景。接法官方 API可收发消息适合场景主要限制订阅号/服务号有被动回复 客服消息 模板消息对外客服、公开机器人订阅号接口权限少服务号需企业认证企业微信有应用消息、群机器人、智能机器人内部助手、团队工具需要企业主体外部用户接入复杂个人号无协议号/自动化框架私域个人助理封号风险极高不推荐生产使用我一般首选服务号。理由有三一是认证后客服消息有 48 小时主动推送窗口用户问完问题后Agent 可以慢慢思考再推结果二是公众号的 XML 消息格式简单、文档全网上能查到的踩坑记录最多三是用户侧成本为零扫个码就能对话不用装企业微信。订阅号虽然也能配服务器接口但认证后权限仍比服务号少一截做对外机器人容易碰到接口能力上限。企业微信适合什么场景如果你做的是公司内部的运维助手、数据查询机器人企业微信的应用消息可以直接推给员工还支持 Markdown 格式体验比公众号好。群机器人则适合把 Agent 拉进项目群但群机器人的消息接收是 webhook 单向的要让 Agent 读到群消息再回复得额外做回调服务链路并不比公众号简单。个人号这条路我不建议碰微信官方没有开放个人号接口用协议号做自动化有封号风险Agent 接进去跑了三天号没了得不偿失。2.3 一条微信消息到 Agent 回复的完整流转把两端串起来看一次完整的对话是这个样子的用户在微信里发一句「帮我写个周报」消息先到微信服务器微信服务器把这条消息以 XML 形式 POST 到你配置的回调 URL 上。这个 URL 就是中转服务的入口它要做四件事验签、解析、调 Coze、回消息。验签是微信的安全机制。你的回调 URL 在公众号后台配置时需要填一个 Token微信每次请求都会带上timestamp、nonce和signature三个参数。中转服务要把 Token、timestamp、nonce 按字典序排序、拼接、做 SHA1算出来的值跟 signature 对上才算合法请求。这个步骤不能省否则任何人都能往你的服务里灌假消息。验签通过后解析 XML 取出消息内容、消息类型和发送者 openid。然后拿着这些信息去调 Coze 的 v3 接口轮询拿到 Agent 的回复。最后一步是回消息有两种方式一种是 5 秒内直接返回 XML 被动回复另一种是先返回「收到正在处理」等 Coze 出结果后再通过客服消息接口主动推送给用户。这两种方式的取舍会在第 4 章展开但架构上你要提前留好两条路的接口。3. 跑通最小可运行版本源码结构、签名校验与 Coze API 对接架构清楚了代码就好写了。这章给出一套可以直接落地的最小可运行工程不依赖任何重量级框架Python Flask requests 三件套。工程里微信逻辑和 Coze 逻辑分层放置后续你要换成企业微信或者把 Flask 换成 FastAPI只动对应模块就行。3.1 可运行源码的工程结构与启动前检查先看目录结构。这里的文件划分是我做类似中转服务积累下来的惯例不算复杂但拆得干净coze-wechat-bridge/ ├── app.py # Flask 入口包含 /wechat 回调路由 ├── config.py # 读取环境变量统一管理配置 ├── wechat.py # 微信签名校验、XML 解析、回复组装 ├── coze_client.py # Coze API 封装聊天 轮询 └── requirements.txt # flask, requests, python-dotenv启动前的检查清单Python 版本建议 3.9 以上pip install -r requirements.txt装依赖然后确认四个环境变量已经设置WECHAT_TOKEN、COZE_API_KEY、COZE_BOT_ID、COZE_API_BASE。前三个缺一不可第四个在你有代理或自建网关时才需要改。这里有个细节微信回调要求公网可达的 HTTPS 地址。本地调试时我一般用内网穿透工具把 5000 端口暴露出去但生产环境不要这么做直接部署到云服务器或者容器里配好 Nginx 反代和 SSL 证书。公众号后台「服务器配置」里的 URL 填https://你的域名/wechatToken 填WECHAT_TOKEN的值消息加解密方式选明文模式跑通了再考虑兼容模式。3.2 配置项微信 Token、Coze API Key、Bot ID 与 API 地址配置集中在config.py里用环境变量注入而不是硬编码。这是为了让代码在不同环境本地、测试、生产之间迁移时不用改文件内容也避免把密钥提交到 Git 仓库。# config.py import os WECHAT_TOKEN os.getenv(WECHAT_TOKEN, ) # 公众号后台配置的 Token用于签名校验 COZE_API_KEY os.getenv(COZE_API_KEY, ) # 扣子开放平台的 API Keypat_ 开头 COZE_BOT_ID os.getenv(COZE_BOT_ID, ) # 智能体 Bot ID发布后从控制台获取 COZE_API_BASE os.getenv(COZE_API_BASE, https://api.coze.cn)参数说明WECHAT_TOKEN是你在公众号后台「服务器配置」里自己填的一串字符不是微信号也不是 AppSecret注意区分。COZE_API_KEY以pat_开头在扣子开放平台的「API 授权」页面创建。COZE_BOT_ID是智能体发布 API 后分配的数字 ID复制时别带空格。COZE_API_BASE默认指向国内版的 api.coze.cn如果你用的是海外版环境改成对应的域名即可。requirements.txt里就三样东西flask、requests、python-dotenv。本地调试时用python-dotenv读取.env文件生产环境直接通过容器或系统环境变量注入。不要小看这步很多「源码下载下来跑不起来」的案例八成是环境变量没配代码一启动就报KeyError。3.3 微信签名校验与消息解析两个函数的边界与坑微信的签名校验逻辑非常固定但容易在参数类型上翻车。timestamp和nonce在 HTTP query 里是字符串排序时必须按字符串排序拼接也是字符串拼接最后做 SHA1。如果用int类型去排序结果跟微信服务器算出来的永远对不上。# wechat.py import hashlib import time def verify_signature(token: str, timestamp: str, nonce: str, signature: str) - bool: # 微信签名校验token、timestamp、nonce 字典序排序后拼接做 SHA1 items sorted([token, timestamp, nonce]) raw .join(items) return hashlib.sha1(raw.encode(utf-8)).hexdigest() signature逻辑说明items是对三个字符串做字典序排序后的列表raw是排序后直接拼接的字符串不能加分隔符。这个函数在 GET 请求公众号后台配置 URL 时的验证和 POST 请求真实消息推送里都要用。微信公众号后台的「服务器配置」里有个「提交」按钮提交时微信会发一个 GET 请求到你的 URL带上echostr参数你验签通过后原样返回echostr内容配置才算成功。消息解析用 Python 自带的xml.etree.ElementTree就够了不需要引第三方库。微信的消息 XML 结构不复杂核心字段是ToUserName、FromUserName、CreateTime、MsgType、Content和MsgId。MsgType决定了后续处理分支text走文本对话image走图片处理event可能是关注事件或菜单点击。我习惯把解析结果转成 dict 返回这样上层代码不用频繁碰 XML。import xml.etree.ElementTree as ET def parse_wechat_xml(xml_data: bytes) - dict: # 微信消息 XML 解析只取业务需要的字段 root ET.fromstring(xml_data) msg { to_user: root.findtext(ToUserName, ), from_user: root.findtext(FromUserName, ), msg_type: root.findtext(MsgType, ), content: root.findtext(Content, ).strip(), msg_id: root.findtext(MsgId, ), create_time: root.findtext(CreateTime, ), } return msg解析时的边界情况Content可能为空比如用户发了表情MsgId在事件消息里不存在to_user是你公众号的原始 ID不是开发者 ID。这些字段后续在组装被动回复时要小心处理初学者最容易把ToUserName和FromUserName写反导致微信拒绝应答。3.4 调用 Coze API 并组装回复从发起对话到消息回推Coze 客户端封装是整个工程的核心。我把它单独放在coze_client.py里是为了让上层路由只关心「传入用户文本拿回 Agent 回复」这一个动作不关心 Coze 内部是 v1 还是 v3、是同步还是轮询。后续 Coze 接口升级只改这一个模块。# coze_client.py import requests import time from config import COZE_API_KEY, COZE_BOT_ID, COZE_API_BASE def chat_with_coze(user_id: str, text: str) - str: # 发起 Coze v3 对话并轮询获取最终结果 url f{COZE_API_BASE}/v3/chat headers { Authorization: fBearer {COZE_API_KEY}, Content-Type: application/json, } payload { bot_id: COZE_BOT_ID, user_id: user_id, # 直接传微信 openidCoze 侧天然按用户隔离会话 stream: False, auto_save_history: True, additional_messages: [ {role: user, content: text, content_type: text} ], } try: resp requests.post(url, jsonpayload, headersheaders, timeout10) resp.raise_for_status() data resp.json().get(data, {}) chat_id data.get(id) conversation_id data.get(conversation_id) if not chat_id or not conversation_id: return Agent 暂时无法响应请稍后再试。 except requests.RequestException as exc: # 网络错误统一兜底不要让异常抛到上层 return f调用 Agent 失败{exc} # 轮询 retrieve 接口最多等 30 秒 retrieve_url f{COZE_API_BASE}/v3/chat/retrieve for _ in range(30): try: r requests.get( retrieve_url, headersheaders, params{chat_id: chat_id, conversation_id: conversation_id}, timeout5, ) r.raise_for_status() result r.json().get(data, {}) status result.get(status) if status completed: # 取最后一条 roleassistant 且 typeanswer 的消息 for msg in reversed(result.get(messages, [])): if msg.get(role) assistant and msg.get(type) answer: return msg[content] return Agent 没有返回有效内容。 if status in (failed, requires_action): return Agent 处理失败请换一种问法。 except requests.RequestException: pass # 单次轮询失败先跳过继续下一次 time.sleep(1) return Agent 响应超时请稍后再问。逻辑说明chat_with_coze先发起 v3 对话拿到chat_id和conversation_id后进入轮询循环。轮询间隔 1 秒最多 30 次覆盖大部分 Agent 响应时间。status等于completed时消息列表里可能有多个 assistant 消息只取最后一条typeanswer的作为回复内容避免把中间过程的思考消息推给用户。参数说明user_id这里直接透传微信 openid好处是 Coze 侧的历史记录天然按用户隔离不需要自己维护 user 映射表。auto_save_history设为True让 Coze 自动保存对话历史注意这必须配合conversation_id使用才有意义具体在第 5 章展开。微信回复组装也被放在wechat.py里。被动回复的 XML 结构固定但字段容易写反ToUserName要填原消息里的FromUserName也就是用户的 openidFromUserName填你自己的公众号原始 ID。def build_text_reply(to_user: str, from_user: str, content: str) - str: # 组装微信被动回复 XML注意收件人和发件人互换 return fxml ToUserName![CDATA[{to_user}]]/ToUserName FromUserName![CDATA[{from_user}]]/FromUserName CreateTime{int(time.time())}/CreateTime MsgType![CDATA[text]]/MsgType Content![CDATA[{content}]]/Content /xml最后是 Flask 主路由把前面几个函数串起来。GET 处理公众号后台的 URL 验证POST 处理真实消息。我在 POST 里先验签、再去重、再调 Coze顺序不能乱。如果先调 Coze 再验签恶意请求能拖垮你的服务。# app.py from flask import Flask, request, Response import config from wechat import verify_signature, parse_wechat_xml, build_text_reply from coze_client import chat_with_coze app Flask(__name__) app.route(/wechat, methods[GET, POST]) def wechat_entry(): if request.method GET: # 公众号后台配置 URL 时的验证请求 sig request.args.get(signature, ) ts request.args.get(timestamp, ) nonce request.args.get(nonce, ) if verify_signature(config.WECHAT_TOKEN, ts, nonce, sig): return request.args.get(echostr, ) return Response(verify failed, status403) # 真实消息推送 sig request.args.get(signature, ) ts request.args.get(timestamp, ) nonce request.args.get(nonce, ) if not verify_signature(config.WECHAT_TOKEN, ts, nonce, sig): return Response(signature error, status403) msg parse_wechat_xml(request.data) if msg[msg_type] ! text: return # 非文本消息先不处理返回空串表示已接收 reply chat_with_coze(msg[from_user], msg[content]) xml build_text_reply(msg[from_user], msg[to_user], reply) return Response(xml, content_typeapplication/xml)这段代码里有个隐患chat_with_coze是同步阻塞的如果 Coze 响应超过 5 秒微信会认为服务器无响应并触发重试。这个问题的解法留在第 4 章但先记住一点上面这段是「最小可运行版本」不是「生产可用版本」。你要在本地跑通这个版本足够要上线请先读完下一章。4. 接入微信后必踩的 5 个坑超时、重复、丢上下文、文件消息与限流这一章从我自己的翻车记录里挑出 5 个最典型的坑按「现象 → 原因 → 解决」写。每一条都是真实线上环境遇到的不是理论推演。你在部署时大概率会撞上一两个提前看完能省一晚上排查时间。4.1 被动回复 5 秒超时Coze 响应慢导致微信报错现象公众号后台出现「该公众号暂时无法提供服务」的告警用户消息发出后石沉大海过一会儿又收到一条迟到的回复而且可能收到两遍。原因微信要求被动回复必须在 5 秒内返回 HTTP 响应否则判定超时。Coze v3 接口的轮询周期可能长达十几秒Agent 里挂了工作流或知识库时更慢。上面第 3 章那段代码响应必然超过 5 秒所以线上跑必炸。解决改用「先应答后推送」模式。收到微信消息后先在 5 秒内返回一段空字符串或者提示语「正在思考中」占住微信的超时窗口然后异步调 Coze拿到结果后用公众号的客服消息接口主动推送给用户。客服消息的接口是POST /cgi-bin/message/custom/send需要先通过GET /cgi-bin/token拿 access_token注意这个 token 有效期 7200 秒要缓存起来重复用别每次都去换。代码改造的核心是第 3 章的chat_with_coze移到后台线程或任务队列里执行Flask 路由只负责接消息、立即返回。4.2 消息重复微信重试机制让 Agent 回复两遍现象用户发一条消息Agent 回了两次相同内容偶尔三次。查日志发现 Coze 侧收到了两次相同的请求。原因微信在 5 秒内没收到你的响应会自动重试推送最多重试 3 次。重试的消息MsgId是相同的但你的代码每次收到推送都会调一次 Coze于是重复回复。这个机制本身是微信为了保证消息不丢的兜底但你的服务没有做幂等就把它变成了重复消息的源头。解决维护一个最近处理过的MsgId集合收到消息先查MsgId是否在集合里在就直接返回空串不在才处理并写入集合。集合要有过期时间用一个 Redis 或者进程内带 TTL 的缓存避免无限增长。我一般存在 Redis 里key 用wechat:msg:{msg_id}TTL 设 5 分钟微信的重试窗口最多几十秒5 分钟足够覆盖。注意返回空串不是返回错误码微信收到空串会认为你已经成功接收消息不会再重试。4.3 上下文断裂每条消息都被当成新会话现象用户说「帮我写个周报提纲」Agent 回复了用户接着说「第一点展开讲讲」Agent 回「我不知道你说的是哪个第一点」。原因auto_save_history虽然开启了但如果你每次请求都没传conversation_idCoze 会认为这是新会话历史记录根本关联不上。日志里看 Coze 侧的每次请求conversation_id都是新生成的历史自然断裂。解决在第一次调 Coze 时把返回的conversation_id存起来key 用用户的 openid后续请求带上这个conversation_id并且additional_messages里只放用户新说的这条消息不要重复放历史。这样 Coze 会自动根据conversation_id回溯历史。存储我没用数据库直接 RedisSETEX coze:conv:{openid} {conversation_id} EX 86400过期时间设 24 小时。用户隔一天再问就是新会话这符合大多数客服场景的预期。4.4 图片与文件消息Coze 文件上传的格式兼容问题现象用户发一张图片给 Agent代码什么都没返回Coze 侧报错说消息格式不对。文本消息跑得好好的一碰图片就翻车。原因微信的image消息给的是一个PicUrl临时链接不能直接塞给 Coze。Coze 的 v3 接口对图片类型消息要求传入file_id这个file_id需要通过 Coze 的文件上传接口先上传文件才能拿到。微信的临时链接有效期很短而且图片 URL 指向的是微信的 CDNCoze 服务器直接拉取未必成功。链路多了一道很多教程直接跳过了这个环节导致按教程写出来只支持文本。解决方案是分层处理。收到image消息时先用requests下载PicUrl再调 Coze 的POST /v1/files/upload上传文件拿file_id最后把file_id塞进additional_messages的content字段content_type设为image。注意大文件限制Coze 上传接口一般有大小上限微信侧的图片压缩过通常没问题但用户发原图时可能超限。超限时兜底逻辑是返回「已收到图片但我暂不支持处理原图」。4.5 并发与限流Coze 接口扛不住你想象的压力现象服务上线后前 50 个用户用得好好的第 100 个用户开始收到「Agent 正在思考中」就永远没有下文了。查日志发现 Coze 接口开始返回 429或者响应时间从 2 秒飙到 20 秒。原因Coze API 有 QPS 限制具体配额跟你的账号等级和 Bot 配置相关。免费层级的配额很低几十个用户同时发消息就可能触发限流。另一个隐性原因是你的回调服务本身在同步调 Coze一个请求没结束线程一直占着并发一上来Flask 默认的线程池很快就耗尽新请求排队排到超时。解决从两头堵。Coze 侧在扣子开放平台申请提高 QPS 配额把智能体的发布配置调成适合生产环境的档位。自己这边引入一个任务队列微信回调只负责把消息丢进队列、立即返回后台 worker 按不超过 Coze 限速的节奏消费队列调 Coze 接口。这样即使用户瞬间涌进来Coze 端也只会看到平稳的流量。我用的方案是 Redis List 做队列Python 侧起了 2 个 worker 进程消费单 worker 每秒最多调 1 次 Coze既平滑又可控。同步调用改异步之后还有一个副作用是排队的用户需要等待这正好配合客服消息推送模式先返回「排队中」再推结果。5. 从能跑到好用会话记忆、流式改造与并发预估最后一章聊三个能明显提升体验的改造点每个都不复杂但都属于「新手上路未必想得到、熟手一看就知道必要」的功能。做完这些你的接入方案才算从演示级跨到生产级。5.1 会话记忆用 Redis 把 conversation_id 管起来第 4 章提到上下文断裂的坑解法就是持久化conversation_id。这里补充一段可以直接用的存储逻辑把 Coze 返回的conversation_id按 openid 存进 Redis后续请求带上import redis r redis.Redis(hostlocalhost, port6379, decode_responsesTrue) def save_conversation(openid: str, conversation_id: str): # 24 小时内同一用户保持同一会话过期后自动开新会话 r.setex(fcoze:conv:{openid}, 86400, conversation_id) def get_conversation(openid: str): return r.get(fcoze:conv:{openid})逻辑说明setex的第二个参数是过期秒数86400 表示 24 小时。取到conversation_id后在调v3/chat时把它放进payloadauto_save_history才会真正起作用。这套设计也避免了自己的服务成为历史记录的存储方记忆问题完全交给 Coze 侧解决。5.2 流式输出在微信侧的落地方式Coze 的 v3 接口支持stream: true流式返回可以边生成边输出 token适合网页端。但微信被动回复和客服消息都不支持流式一次只能推一条完整消息。所以常见做法是面向微信用户不强求流式但如果你同时接了 Web 端比如网页版助手可以把流式能力用起来微信端保持整段返回。两套逻辑分开写chat_with_coze里加一个stream参数默认 FalseWeb 端传 True。不要试图给微信做「伪流式」——把一段话拆成多条客服消息连续推送微信会触发频率限制体验反而更差。5.3 并发预估与压测脚本上线前至少做一次简单压测用并发数、错误率、响应时间三个数据说话。我习惯用locust或者干脆写个多线程脚本模拟 20 个用户同时发消息观察 Coze 接口的返回码分布和响应时间。核心指标有两个429 出现的概率、statuscompleted的平均耗时。429 超过 5% 说明限流风险高要提额或加队列平均耗时超过 8 秒说明 Agent 里挂了重逻辑建议拆工作流或加缓存。一个经验数字供参考单独做客服类场景日活 500 人的公众号峰值并发大概在 20 左右Coze 基础配额通常能扛住但一定要在公众号后台排查时预留队列兜底。我自己的项目上线前跑过一轮压测20 并发下 429 比例在 2% 左右加了一层 Redis 队列后降到了 0。这个方案值的核心是Coze 负责智能微信入口负责触达中间的胶水层用队列和缓存兜住抖动。我现在的习惯是每次改完 Agent 配置先跑一轮压测再放量宁可自己先踩坑也不要让用户替我发现问题。希望帮到你。本文还有配套的精品资源点击获取
返回列表