ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

用GPT-6 Astra打造QQ与飞书GitHub查询机器人实战

用GPT-6 Astra打造QQ与飞书GitHub查询机器人实战 前几天我把手头的自动化工具整理了一遍顺手把 GPT-6 Astra 接进了 QQ 和飞书做了一个专门查 GitHub 信息的机器人。这个东西不复杂核心逻辑就是“聊天消息进来 → 识别意图 → 调 GitHub 接口 → 让模型整理成人类读得懂的回答 → 发回群里”。但把它拆开看里面涉及平台接入、消息格式、权限校验、接口限流这一整套东西踩了不少坑也沉淀了不少经验。如果你也想在 QQ 或飞书里挂一个能查仓库、搜 Issue、看用户信息的机器人或者只是想把 AI 模型接进聊天工具里做点自动化这篇文章可以帮你省下大量试错时间。我会从整体设计思路讲起再把飞书和 QQ 的接入机制掰开揉碎说清楚最后给出一版可以直接运行的代码流程以及我实际调试中遇到的高频问题。全程用我真实的搭建过程来讲不是那种只给个 demo 的教程。1. 整体设计思路三件事怎么串成一条链路1.1 一个查询机器人到底解决的是什么问题先聊个场景。你正在飞书群里跟同事讨论一个开源项目想确认这个仓库最近有没有新 release又要切到浏览器打开 GitHub搜索、翻列表、看描述然后再切回聊天窗口复制粘贴。一天重复十几次效率很低。查询机器人要解决的问题天然就是这块。它把 GitHub 这套比较重、比较偏开发者的操作拆成“人话指令”在聊天窗口里直接完成。用户发一句“帮我搜一下关于 RAG 的 Python 仓库按 star 排序”机器人就自动去 GitHub 搜然后把仓库名、简介、星标数、链接整理成一条清晰的回复发回到群里。这里的关键点在于查询本身不难难的是让不熟悉 GitHub 的人也能用让熟悉 GitHub 的人不用离开聊天窗口。所以机器人不只是调接口它还要做“意图理解”和“结果转述”这时候 GPT-6 Astra 这类模型就有了用武之地。1.2 为什么同时接 QQ 和飞书而不是二选一我最初只做了飞书版本因为团队内部都在用接口文档清晰测试也方便。但后来发现身边很多技术社区、学习小组、开源项目群都活跃在 QQ 上尤其是学生群体和兴趣社群QQ 的覆盖率反而更高。两个平台的接入方式完全不一样飞书偏企业应用文档规范回调逻辑清楚QQ 官方机器人有自己的开放平台和沙箱机制接口风格不同消息类型也有限制。如果只做一个平台这套逻辑就白瞎了一半。更重要的是查询机器人本质上是一个“无状态服务”同样一个 GitHub 查询能力完全可以做成底层通用模块上面套两个适配层一个接飞书一个接 QQ。这样维护成本只增加了一次适配而不是开发两套系统。后面搭建的时候我也会直接按这个思路来避免代码写死在某个平台上。1.3 技术选型轮询、Webhook 还是长连接机器人接入聊天平台消息获取方式主要有三条路轮询隔几秒去平台拉一次消息。实现最简单但延迟高、浪费请求而且很多平台压根不提供拉取接口。Webhook 回调平台有新消息时主动往你的服务器推一个 HTTP 请求。实时性好实现简单是目前最主流的方案。长连接 / WebSocket客户端和平台保持一条持久通道平台实时推送。延迟最低但需要处理心跳、重连复杂度高一些。我这次选的是 Webhook 为主。飞书和 QQ 官方机器人当前都支持 HTTP 回调只要有一台有公网地址的服务器把回调地址配好就能跑起来。长连接模式适合对实时性要求极高或者服务器不方便暴露公网端口的场景初版没必要上。服务端我用的 FastAPI纯 Python路由清晰异步能力也足够。后面接什么平台都只是加路由的事。2. 平台接入原理飞书和 QQ 的机器人机制完全不同2.1 飞书机器人自定义机器人和应用机器人是两个物种先说一个最常见的误区。很多人以为飞书机器人就是群里加一个“自定义机器人”拿一个 Webhook 地址就能收发消息。自定义机器人只能发不能收。它可以往群里推送告警、通知但没法接收用户发来的消息并自动回复因为它本质是一个“输出通道”。要做一个能对话、能响应指令的查询机器人必须去飞书开放平台创建企业自建应用。流程大概是在飞书开放平台创建应用拿到App ID和App Secret。给应用开启“机器人”能力。配置事件订阅添加im.message.receive_v1事件意思是“用户给机器人发消息时通知我”。配置回调地址填你的服务器地址例如https://yourdomain.com/feishu/webhook。发布应用版本并在群聊里添加这个机器人为群成员。这个流程和自定义机器人完全是两码事。应用机器人有完整的身份凭证可以调用飞书的服务端接口比如主动给用户或群发消息、读取消息内容等。还有一点要注意飞书事件订阅提供了两种加密方式一种是Verification Token一种是Encrypt Key。初版调试时我建议先不开加密等跑通了再补上。但如果机器人要正式上线签名校验必须做否则任何知道回调地址的人都能伪造消息。发送消息也不是直接调 Webhook而是先拿tenant_access_tokencurl -X POST https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal \ -H Content-Type: application/json \ -d {app_id: 你的AppID, app_secret: 你的AppSecret}拿到 token 后再调用消息发送接口把回复推给用户或群。这个“先换 token 再发消息”的机制飞书、钉钉、企微都类似理解一遍就能举一反三。2.2 QQ 官方机器人事件订阅和会话分发QQ 机器人的接入路径这几年变化很快现在最稳妥的是走 QQ 开放平台的官方机器人能力。整体思路也是创建机器人应用、配置事件回调、接收消息、调用发送接口回复。QQ 官方机器人和飞书有一个明显的不同它区分群聊场景和C2C 单聊场景。同一个机器人在群里通常需要被才会响应在单聊里则是直接对话。这也意味着你的处理逻辑一开始就要把事件类型拆开GROUP_AT_MESSAGE_CREATE群里有人 机器人C2C_MESSAGE_CREATE用户单独找机器人聊天我记得最早踩的一个坑是我在群里发消息没在内容里包含机器人名字结果机器人一直不理我。后来看了文档才知道官方机器人默认只推送被 的消息除非你额外申请了解析全部群消息的权限。这一点直接在流程图里想清楚后面处理逻辑会简单很多。另外QQ 机器人回调同样存在签名校验机制平台会用你在开放平台配置的 Token 和 EncodingAESKey 对回调请求做签名和加密服务器端必须按官方 SDK 的流程解密否则报文根本看不不了。很多人在这一步反复报错其实就是 SHA1 签名拼的顺序不对或者 AES Key 填少了几位。2.3 消息格式是分水岭文本、卡片和富文本平台接入跑通只是第一步真正影响使用体验的是回复格式。飞书支持文本、富文本 post、交互式卡片 interactiveQQ 机器人则更偏向普通文本、图片和部分模板消息。GitHub 查询结果天然是结构化信息——仓库名、描述、语言、星标数、链接如果全部塞进一段纯文本里长截图一样的内容谁都不想看。我的做法是飞书用消息卡片QQ 用分段文本加链接。飞书卡片可以做到很漂亮的排版比如顶部一个大标题下面几行字段信息最后放一个跳转按钮。在 GitHub 搜仓库这种场景卡片能把 star 数、语言、更新时间都整理成一行一个字段阅读体验非常舒服。飞书机器人还能发送“表格型”内容虽然官方叫“富文本”或者“卡片表格组件”但效果上就是聊天框里直接展示结构化表格适合批量列出多个仓库的对比信息。QQ 那边格式能力弱一些我就退而求其次用“标题 列表 链接”的纯文本格式至少在手机上也能快速扫一眼。提示消息格式这块建议先按平台能力做成 adapter适配器不要在核心逻辑里写死哪种格式否则后面再加一个新的聊天平台代码会改到想哭。3. 实操过程从零搭一版可运行的查询机器人3.1 环境准备与依赖我全程用的是 Python 3.10 FastAPI依赖就五个fastapi uvicorn requests openai cachetoolsrequests用来调 GitHub API 和飞书、QQ 的接口openai用来接 GPT-6 Astra 的模型服务cachetools做查询结果缓存避免频繁触发 GitHub 限流。完整 requirements 如下fastapi0.115.6 uvicorn0.32.1 requests2.32.3 openai1.58.0 cachetools5.5.0启动入口直接uvicorn main:app --host 0.0.0.0 --port 8000如果服务器还需要配 HTTPS 才能收平台回调建议在前面挂一层 Nginx 做 TLS 终止应用本身不用管证书。3.2 接入飞书事件订阅、签名校验与自动回复完整代码里最核心的一个文件是飞书适配器。先看回调处理部分import json from fastapi import APIRouter, Request import requests router APIRouter() APP_ID 你的飞书AppID APP_SECRET 你的飞书AppSecret FEISHU_SEND_MSG_URL https://open.feishu.cn/open-apis/im/v1/messages def get_tenant_access_token(): resp requests.post( https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal, json{app_id: APP_ID, app_secret: APP_SECRET}, ) return resp.json()[tenant_access_token] router.post(/feishu/webhook) async def feishu_webhook(request: Request): body await request.json() # 飞书回调验证返回 challenge 原样即可 if body.get(type) url_verification: return {challenge: body[challenge]} # 处理事件 event body.get(event, {}) if event.get(type) im.message.receive_v1: message event.get(message, {}) msg_type message.get(message_type) content json.loads(message.get(content, {})) open_id event.get(sender, {}).get(sender_id, {}).get(open_id, ) text content.get(text, ) # 这里把解析后的文本丢给核心处理模块拿到结果再回复 reply_text handle_core_query(text) send_feishu_text(open_id, reply_text) return {code: 0} def send_feishu_text(open_id: str, text: str): token get_tenant_access_token() resp requests.post( FEISHU_SEND_MSG_URL ?receive_id_typeopen_id, headers{Authorization: fBearer {token}}, json{ receive_id: open_id, msg_type: text, content: json.dumps({text: text}), }, ) print(resp.status_code, resp.text)这里有三个细节值得说challenge 校验是飞书配置回调地址的第一步如果你的接口没有正确处理url_verification请求控制台会一直提示“回调验证失败”。事件结构体里嵌套很深建议调试时先把原始 JSON 打印出来看几遍别凭记忆解析字段。发送消息用的是open_id作为接收人标识不同的应用、不同的用户open_id都不一样所以必须从事件里动态获取不能写死。如果你在飞书开放平台配置了Encrypt Key那么回调 body 里的encrypt字段需要对消息体做 AES 解密才能真正拿到事件内容初版不建议开后面上线前一定要补上。3.3 接入 QQ官方机器人配置与服务端处理QQ 这边我直接用官方机器人的 HTTP 回调模式服务端处理逻辑和飞书类似但字段完全不同。一个简化的处理代码如下from fastapi import APIRouter, Request router APIRouter() router.post(/qq/webhook) async def qq_webhook(request: Request): body await request.json() # 这里按 QQ 官方文档做签名校验省略具体实现 post_type body.get(post_type) # 通常是 message message_type body.get(message_type) # group 或 private raw_message body.get(raw_message, ) # 群里必须 机器人取实际用户输入内容 # QQ 机器人回调里一般会带上 信息剥离后得到真正的指令 command strip_at_qq(raw_message) reply handle_core_query(command) send_qq_reply(body, reply) return {code: 0}QQ 发送消息需要调用开放平台的消息接口和飞书最大的不同是QQ 的回复通常要指定msg_id因为很多场景要求回复必须关联到用户或群发来的那条消息上。简单来说收到事件后先记下msg_id发消息时把它带上平台才知道你这条回复是针对哪条消息的。我曾经忽略了这个参数结果消息能发出去但群里会出现一堆“无头回复”体验非常差。注意不同版本的 QQ 机器人 API 在接口路径和参数命名上会调整一定要以官方开放平台最新文档为准。我这里给的代码是结构示范不是复制就能跑的最终版。3.4 GitHub 查询与 GPT-6 Astra 的协作逻辑查询机器人的“大脑”分两层第一层是 GPT 做意图理解和结果整理第二层是真正的 GitHub API 调用。我的做法是让 GPT-6 Astra 先把用户的话翻译成结构化的工具调用参数然后我这边执行 GitHub 查询拿回 JSON 后再丢给模型组织成聊天回复。先写 GitHub 查询模块import requests GITHUB_SEARCH_REPO_URL https://api.github.com/search/repositories def search_github_repositories(keyword: str, sort: str stars, per_page: int 5): params { q: keyword, sort: sort, per_page: per_page, } resp requests.get(GITHUB_SEARCH_REPO_URL, paramsparams, timeout10) if resp.status_code 403: # 触发限流返回处理 return {error: rate_limit, message: GitHub API 限流了请稍后再试} items resp.json().get(items, []) result [] for repo in items: result.append({ name: repo[full_name], description: repo.get(description), stars: repo[stargazers_count], language: repo.get(language), url: repo[html_url], }) return result这里的关键抉择是为什么不让 GPT 直接去搜 GitHub而是让 GPT 只负责判断参数因为模型本身不掌握实时数据它只能根据训练数据“猜”仓库信息猜出来的 star 数基本是幻觉。所以正确架构是模型负责“理解”代码负责“查证”。这也符合现在工具调用function calling / tool use的主流思路。GPT-6 Astra 这边的调用我做了这样一个函数描述from openai import OpenAI client OpenAI( api_key你的API Key, base_url你的模型服务地址, # 官方服务或私有化部署地址 ) def parse_with_gpt(user_text: str): resp client.chat.completions.create( modelgpt-6-astra, messages[ {role: system, content: 你是 GitHub 查询助手只负责从用户话里提取查询条件不要自己编造事实。用户输入无法匹配时返回 unknown。}, {role: user, content: user_text}, ], tools[ { type: function, function: { name: search_github_repositories, description: 搜索 GitHub 仓库, parameters: { type: object, properties: { keyword: {type: string}, sort: {type: string, enum: [stars, updated, forks]} }, required: [keyword] } } } ], tool_choiceauto, ) return resp.choices[0].message.tool_calls拿到模型返回的tool_calls之后我再去执行search_github_repositories然后把结果拼接成一条结构化文本最终通过平台适配层发出去。这里还有一步我强烈建议加上缓存。GitHub 的未认证请求一小时的配额只有 60 次稍微几个人用就没了。我用cachetools做了 5 分钟的本地缓存同样的关键词第二次查询直接走缓存既加快响应速度又省配额。from cachetools import TTLCache cache TTLCache(maxsize100, ttl300) def cached_github_search(keyword: str): if keyword in cache: return cache[keyword] result search_github_repositories(keyword) cache[keyword] result return result没有做太复杂的功能但这一层缓存对实际使用体验的提升非常明显。4. 常见问题与排查技巧实录4.1 平台回调验证失败的几个典型原因配置飞书或 QQ 回调地址时控制台一直提示验证失败这是新手最容易卡住的环节我总结排查顺序如下现象原因解决办法飞书提示challenge校验失败回调接口没有处理url_verification请求把原始 JSON 打出来检查返回值是否原样返回challenge飞书显示回调失败但不报错回调地址公网访问不通在服务器上curl -X POST 你的地址自测看是否有响应QQ 回调提示签名错误Token、AES Key配置不一致重新核对开放平台配置确认类型和值完全一致服务器日志里能看到请求但接口报 404路径配置多了一层前缀检查 Nginx 转发规则确认请求打到的是 FastAPI 对应路由另一个容易踩的坑是回调地址必须是公网 HTTPS。平台一般不会接受裸 IP 和自签证书。测试阶段可以用内网穿透工具临时暴露但正式跑必须上正规域名和证书。4.2 飞书提示 network unavailable 的排查思路搜索热词里有一条“飞书报错 network unavailable, please go to feishu network diagnosis”这个问题我遇到过一次。字面意思是飞书客户端无法连接服务器但实际上原因多种多样我见过的有三种客户端所在的网络环境无法正常访问飞书服务端换了网络比如从 Wi-Fi 切到手机热点就恢复。飞书网关或中间网络的 DNS 解析异常官方提供了网络诊断工具按提示跑一遍重点关注 DNS 和 TLS 握手阶段。企业自定义网络策略限制了飞书域名访问这个只能找管理员确认。碰到这类问题我的建议是别慌先在飞书客户端里跑官方网络诊断截图看是连接失败还是证书问题。如果只是偶发多半是网络抖动。这个属于客户端问题和机器人代码没关系但容易被误认为是回调的问题浪费半天时间。4.3 GitHub 查询报 403 或 429 限流GitHub API 的限流是所有查询机器人绕不过去的坎。先看响应头里的这几个字段X-RateLimit-Limit: 60 X-RateLimit-Remaining: 57 X-RateLimit-Reset: 1735689600Remaining表示剩余请求数Reset是配额重置时间。如果请求携带Authorization头配额会提高到每小时 5000 次headers { Authorization: Bearer 你的GitHubToken, Accept: application/vnd.githubjson, }就算加了 token我也建议做两件事一是上面的缓存二是对非核心查询做降级。比如机器人搜不到结果时不要反复重试同一个关键词而是提示用户换个说法。实测下来缓存能过滤掉至少一半的重复请求限流发生率大降。另外 GitHub 搜索接口的排序参数值得调优默认是按最佳匹配但用户在聊天场景里通常更关心 star 数所以我把默认排序设成stars这样返回结果更符合直觉。4.4 多平台共用一个机器人服务的最佳姿势如果你也想同时接 QQ 和飞书我强烈建议从一开始就保持模块独立至少要分成这几个文件main.py # FastAPI 入口 core_handler.py # 核心逻辑解析文本、调用 GPT、调 GitHub、组织回复 feishu_adapter.py # 飞书回调、发送消息 qq_adapter.py # QQ 回调、发送消息 config.py # 所有密钥和配置集中管理这样做的好处是核心逻辑完全不感知平台差异。core_handler接收一个纯字符串输入吐出一个纯字符串回复飞书适配器和 QQ 适配器各自负责把纯文本变成对应平台能展示的格式。我实际开发时还加了一个调试接口直接在浏览器里传文本测试核心逻辑from fastapi import Query app.get(/debug/query) def debug_query(q: str Query(...)): return {result: handle_core_query(q)}这个调试接口帮了我大忙很多问题一眼就能看出是核心逻辑出错还是平台适配出错不需要拿着手机在群里反复发消息测试。正式上线时记得关掉或者加鉴权。4.5 消息解析的边界情况别忽略最后补充一个人人都容易忽略的细节。用户发的消息不可能是“搜一下 RAG 仓库”这么标准实际会遇到各种情况只发一个 GitHub 链接比如https://github.com/openai/openai-python这时候应该直接解析出仓库名然后调仓库详情接口。同时问两个问题比如“帮我搜下 RAG 的仓库再看看 OpenAI 这个用户”这时候不能只处理第一个问题。发一句话夹着命令“谁有那个某某项目的链接搜一下”。我的做法是让 GPT-6 Astra 在解析阶段就输出一个结构化的指令数组包含action、target、keyword、sort_by等字段然后后端按数组逐个执行。这个比正则匹配要稳健得多。模型在这里的价值不是替你去查数据而是把你的意图翻译成机器可以执行的动作。这套结构的扩展性也很好后面想加“查某个人”、“查某个 Issue”、“比较两个仓库”都是往工具列表里加一个函数的事不需要拆掉重写。做个总结可能有点多余但我个人的体会是这类聊天机器人项目真正花时间的不是写代码而是把“平台接入机制”和“消息内容解析”这两件事吃透。平台 API 文档更新快参数一直在变但架构思路是通用的——底层一个查询服务上层接多个平台适配器中间用模型做意图翻译。你照着这个思路把第一个机器人跑通之后后面接什么平台都只是再写一个适配层的事。最后再分享一个自测习惯上线前一定要把调试接口留着用真实用户会发的垃圾消息反复喂给核心逻辑而不是只测标准句式很多所谓“机器人发疯”都是这一步没做好。
返回列表