Hermes Agent 连飞书群聊:不用 @ 就无响应?feishu.py 配置排查与修复
1. 问题现场群里说话没人理非得 一下才行Hermes Agent 接进飞书群之后最容易踩的一个坑就是你在群里正常发消息机器人像没看见一样只有手动 它它才慢悠悠回你。这个现象在单聊里不会出现偏偏群聊里特别明显很多人第一反应是「是不是 WebSocket 没连上」「是不是权限没开」其实大概率跟连接无关而是卡在了消息过滤这一层。飞书群聊的消息事件推送到你的服务端后Hermes Agent 会先判断「这条消息要不要交给 Agent 处理」。默认逻辑是群聊消息必须显式 机器人才放行因为群里人多嘴杂不加限制的话机器人会对每句话都插嘴。这个设计本身没错但如果你就是想让某个群「免 自由对话」就得改配置和feishu.py里的判断逻辑。这篇聚焦的场景很具体Hermes Agent 接入飞书群聊后消息未 机器人便无响应。我会从feishu.py的事件订阅与消息过滤逻辑切入定位群聊消息的接收条件给出可复制的关键配置片段最后用「发一条不带 的群消息」来验证 Agent 是否正常响应。适合已经在跑 Hermes Agent、飞书群聊能 通但想放开免 的同学。需要说明的是下面涉及的所有模型调用、Agent 推理请求我都会走 TaoToken 的兼容接口来发这样你不用在本地折腾多套鉴权一个 Key 就能把对话和编码两条链路都跑起来。2. 前置把 TaoToken 的 Key 和接入信息准备好在动feishu.py之前先把模型侧的事情理清楚。Hermes Agent 本身是个编排框架它最终还是要调大模型来完成回复。我这边统一用 TaoToken 作为模型入口原因是它同时提供对话和编码两类能力接口兼容主流格式改base_url就能接上。官网入口在这里https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 注册后进控制台创建 API Key。API 基址是 https://taotoken.net/api 注意这个地址不带任何查询参数配置里直接写它就行。创建 Key 的页面在https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi_keysutm_campaignrewrite 进去点新建复制那串sk-开头的字符串。如果你后面要跑长期编码任务或者 Agent 常驻可以看下 Coding Planhttps://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding_planutm_campaignrewrite 它更适合高频调用的场景。接入文档在https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 里面写了不同语言的调用示例。想先在网页里试一下模型通不通可以直接开模型对话https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_contentmodel_chatutm_campaignrewrite 发一句话看有没有正常返回。把 Key 填进 Hermes Agent 的.env里通常长这样# 模型侧TaoToken 兼容接口 OPENAI_API_KEYsk-你的TaoToken密钥 OPENAI_BASE_URLhttps://taotoken.net/api这一步做完模型链路就通了。接下来才是飞书群聊的过滤逻辑也就是「为什么非得 」的根源。3. 可复制配置从 .env 到 feishu.py 的免 改造3.1 先理解默认的 门槛在哪Hermes Agent 处理飞书群消息时会调用一个类似_should_accept_group_message的方法。它的默认行为是群消息里如果没有 机器人直接返回False消息被丢弃Agent 根本收不到。所以你在群里说话没反应不是网络问题是这条消息在入口就被拦了。要放开有两条路一是用环境变量指定「免 白名单群」二是用config.yaml做更细的按群规则。前者改动小后者更灵活。3.2 方式一白名单群免 其他群照旧这种方式适合「只有一个群想免 其他群保持原样」。改.envFEISHU_DOMAINfeishu FEISHU_CONNECTION_MODEwebsocket FEISHU_ALLOW_ALL_USERSfalse FEISHU_ALLOWED_USERS群1id,群2id FEISHU_GROUP_POLICYopen FEISHU_FREE_RESPONSE_CHATS群1id关键是最后一行FEISHU_FREE_RESPONSE_CHATS它是个逗号分隔的群 ID 列表列进去的群免 。注意FEISHU_GROUP_POLICYopen表示允许群聊交互FEISHU_ALLOWED_USERS控制哪些用户能触发。然后改feishu.py在_should_accept_group_message里加一段白名单判断def _should_accept_group_message(self, message: Any, sender_id: Any, chat_id: str ) - bool: 群消息默认需要 白名单群例外。 if not self._allow_group_message(sender_id, chat_id): return False # 免 白名单命中则直接放行 free_response_chats set( item.strip() for item in os.getenv(FEISHU_FREE_RESPONSE_CHATS, ).split(,) if item.strip() ) if chat_id and chat_id in free_response_chats: return True # _all 是飞书的 所有人占位符始终放行 raw_content getattr(message, content, ) or if _all in raw_content: return True mentions getattr(message, mentions, None) or [] if mentions: return self._message_mentions_bot(mentions) normalized normalize_feishu_message( message_typegetattr(message, message_type, ) or , raw_contentraw_content, ) if normalized.mentioned_ids: return self._post_mentions_bot(normalized.mentioned_ids) return False这段逻辑的顺序很重要先过用户权限再过免 白名单最后才走原来的 判断。白名单命中就return True后面的 检查全部跳过。3.3 方式二按群配置 at_only未配置群一律不响应如果你想要更细的控制——比如群 1 免 、群 2 需要 、没配置的群完全不响应——那就用config.yaml的按群规则。先改.envFEISHU_DOMAINfeishu FEISHU_CONNECTION_MODEwebsocket FEISHU_ALLOW_ALL_USERSfalse FEISHU_ALLOWED_USERS群1id,群2id FEISHU_GROUP_POLICYallowlist FEISHU_HOME_CHANNEL群1id再改config.yaml加group_rulesfeishu: extra: group_rules: 群1id: policy: open at_only: false 群2id: policy: open at_only: true default_group_policy: disabledat_only: false表示这个群免 at_only: true表示需要 default_group_policy: disabled表示没在group_rules里出现的群一律不响应。对应的feishu.py要加一个规则类并在判断里读取它dataclass class FeishuGroupRule: 按群策略控制谁能交互、是否需要 。 policy: str at_only: bool True allowlist: set[str] field(default_factoryset) blacklist: set[str] field(default_factoryset) def _load_settings(extra: Dict[str, Any]) - FeishuAdapterSettings: raw_group_rules extra.get(group_rules, {}) group_rules: Dict[str, FeishuGroupRule] {} if isinstance(raw_group_rules, dict): for chat_id, rule_cfg in raw_group_rules.items(): if not isinstance(rule_cfg, dict): continue group_rules[str(chat_id)] FeishuGroupRule( policystr(rule_cfg.get(policy, open)).strip().lower(), at_only_to_boolean(rule_cfg.get(at_only, True)), allowlistset(str(u).strip() for u in rule_cfg.get(allowlist, []) if str(u).strip()), blacklistset(str(u).strip() for u in rule_cfg.get(blacklist, []) if str(u).strip()), ) # 其余初始化逻辑保持不变然后在_should_accept_group_message里插入按群判断def _should_accept_group_message(self, message: Any, sender_id: Any, chat_id: str ) - bool: if not self._allow_group_message(sender_id, chat_id): return False # 按群规则at_onlyfalse 直接放行 rule self._group_rules.get(chat_id) if chat_id else None if rule and not rule.at_only: return True raw_content getattr(message, content, ) or if _all in raw_content: return True mentions getattr(message, mentions, None) or [] if mentions: return self._message_mentions_bot(mentions) normalized normalize_feishu_message( message_typegetattr(message, message_type, ) or , raw_contentraw_content, ) if normalized.mentioned_ids: return self._post_mentions_bot(normalized.mentioned_ids) return False两种方式选一种就行别同时开否则白名单和按群规则会互相干扰排查起来很痛苦。4. 验证发一条不带 的群消息看 Agent 是否响应改完配置和代码重启 Hermes Agent 服务。重启命令取决于你的部署方式如果是 systemdsudo systemctl restart hermes-agent sudo systemctl status hermes-agent --no-pager看状态是active (running)再确认日志里 WebSocket 连上了journalctl -u hermes-agent -n 50 --no-pager | grep -i feishu正常会看到类似feishu websocket connected的输出。然后进飞书群发一条不带任何 的普通消息比如「你好帮我看下今天的待办」。如果配置生效Agent 应该会正常回复。如果没反应先别急着改代码用下面这个最小验证脚本单独测一下模型链路排除是 TaoToken 侧的问题import os from openai import OpenAI client OpenAI( api_keyos.getenv(OPENAI_API_KEY), base_urlhttps://taotoken.net/api, ) resp client.chat.completions.create( modelgpt-4o-mini, messages[{role: user, content: 只回复两个字收到}], ) print(resp.choices[0].message.content)跑出来打印「收到」说明模型侧没问题问题一定在飞书消息过滤。这时候回去看 Agent 日志里有没有group message rejected之类的记录能直接定位是哪一步拦的。5. 本篇常见错排查错误一改了.env但没重启服务。环境变量是进程启动时读的改完不重启等于没改。这是最高频的坑先查这个。错误二群 ID 填错。飞书的群 ID 有oc_开头的 chat_id也有别的形式FEISHU_FREE_RESPONSE_CHATS里必须填消息事件里实际带的那个chat_id。填错一个字符就匹配不上表现就是「配置了但没用」。建议在feishu.py里临时打一行日志把chat_id打出来核对。错误三FEISHU_GROUP_POLICY和group_rules冲突。如果.env里写了FEISHU_GROUP_POLICYopen同时config.yaml里又配了default_group_policy: disabled两者语义会打架。建议二选一用按群规则时就别在.env里设全局策略。错误四at_only读成了字符串。YAML 里at_only: false是布尔值但如果你写成at_only: false_to_boolean没处理好的话会被当成真值。确认你的_to_boolean能正确解析字符串false。错误五白名单和按群规则同时开。前面提过两套逻辑叠加时白名单先return True会让按群规则失效。排查时先把其中一套注释掉确认单套逻辑通了再考虑合并。错误六模型 Key 没配或额度问题。消息过滤放行了但 Agent 调模型失败表现也是「没回复」。这时候看日志里有没有 401 或 429去 TaoToken 控制台确认 Key 状态和余额。6. 收尾把链路固定下来免 这件事本身不复杂难的是定位——很多人一上来就怀疑网络和权限绕一大圈才发现是_should_accept_group_message里的 门槛。我的建议是先在feishu.py的过滤函数入口加一行日志把chat_id、sender_id、mentions都打出来发一条消息看日志走到哪一步返回了False比盲改配置快得多。模型侧统一走 TaoToken 之后对话和编码两条链路共用一个 Key省掉了多套鉴权的维护成本。如果你后面要把 Hermes Agent 接到更多群、跑更重的 Agent 任务可以看下 Coding Planhttps://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding_planutm_campaignrewrite 高频调用下更划算。接入细节和参数说明都在文档里https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 遇到报错先翻文档再改代码能少走不少弯路。