<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Hermes Agent Telegram 语音转文字（STT）问题排查记录：Groq 正常，但 Gateway 未自动触发转写]]></title><description><![CDATA[<p dir="auto">Hermes Agent Telegram 语音转文字（STT）问题排查记录：Groq 正常，但 Gateway 未自动触发转写</p>
<p dir="auto">一、环境信息</p>
<p dir="auto">硬件 / 系统</p>
<ul>
<li>NAS 环境运行 Hermes Agent</li>
<li>Docker 容器部署</li>
<li>s6 作为进程管理</li>
<li>Hermes Gateway 通过 s6 启动</li>
</ul>
<p dir="auto">启动方式：</p>
<p dir="auto">hermes gateway run --replace</p>
<p dir="auto">后续调试增加：</p>
<p dir="auto">hermes gateway run --replace -vv</p>
<p dir="auto">开启 DEBUG 日志。</p>
<p dir="auto">⸻</p>
<p dir="auto">二、目标</p>
<p dir="auto">希望 Telegram 发送语音消息后：</p>
<p dir="auto">Telegram Voice<br />
↓<br />
Hermes Gateway<br />
↓<br />
STT 转文字<br />
↓<br />
LLM 理解文本<br />
↓<br />
Telegram 返回回复</p>
<p dir="auto">STT 使用 Groq Whisper API。</p>
<p dir="auto">⸻</p>
<p dir="auto">三、最初问题表现</p>
<p dir="auto">Telegram 发送语音：</p>
<p dir="auto">日志：</p>
<p dir="auto">Cached user voice at /opt/data/cache/audio/xxxx.ogg</p>
<p dir="auto">但是之后没有：</p>
<p dir="auto">Transcribing user voice</p>
<p dir="auto">也没有：</p>
<p dir="auto">Transcribed via command STT provider</p>
<p dir="auto">最终表现：</p>
<ul>
<li>Gateway 收到语音 <img src="https://lcz.me/assets/plugins/nodebb-plugin-emoji/emoji/android/2705.png?v=8624a6a055f" class="not-responsive emoji emoji-android emoji--white_check_mark" style="height:23px;width:auto;vertical-align:middle" title="✅" alt="✅" /></li>
<li>音频缓存成功 <img src="https://lcz.me/assets/plugins/nodebb-plugin-emoji/emoji/android/2705.png?v=8624a6a055f" class="not-responsive emoji emoji-android emoji--white_check_mark" style="height:23px;width:auto;vertical-align:middle" title="✅" alt="✅" /></li>
<li>但是没有自动转写 <img src="https://lcz.me/assets/plugins/nodebb-plugin-emoji/emoji/android/274c.png?v=8624a6a055f" class="not-responsive emoji emoji-android emoji--x" style="height:23px;width:auto;vertical-align:middle" title="❌" alt="❌" /></li>
<li>LLM 收不到语音内容 <img src="https://lcz.me/assets/plugins/nodebb-plugin-emoji/emoji/android/274c.png?v=8624a6a055f" class="not-responsive emoji emoji-android emoji--x" style="height:23px;width:auto;vertical-align:middle" title="❌" alt="❌" /></li>
</ul>
<p dir="auto">⸻</p>
<p dir="auto">四、首先排查 Groq API</p>
<p dir="auto">测试结果</p>
<p dir="auto">Groq API 通过代理可以正常访问。</p>
<p dir="auto">问题不是：</p>
<ul>
<li>API Key 错误</li>
<li>网络限制</li>
<li>Groq 服务异常</li>
</ul>
<p dir="auto">原因：</p>
<p dir="auto">国内环境直接访问 Groq 可能失败，但代理访问正常。</p>
<p dir="auto">⸻</p>
<p dir="auto">五、发现 OpenAI SDK 代理问题</p>
<p dir="auto">Hermes 内部 STT 使用：</p>
<p dir="auto">OpenAI(<br />
api_key=...,<br />
base_url="<a href="https://api.groq.com/openai/v1" rel="nofollow ugc">https://api.groq.com/openai/v1</a>"<br />
)</p>
<p dir="auto">没有传：</p>
<p dir="auto">http_client=httpx.Client(proxy=...)</p>
<p dir="auto">导致：</p>
<ul>
<li>curl 可以访问 Groq</li>
<li>Hermes 内部 OpenAI SDK 不走代理</li>
</ul>
<p dir="auto">解决思路：</p>
<p dir="auto">原本考虑：</p>
<ol>
<li>修改 Hermes 源码增加 http_client</li>
<li>monkey patch OpenAI SDK</li>
<li>sitecustomize</li>
<li>PYTHONSTARTUP</li>
</ol>
<p dir="auto">但是最终没有采用。</p>
<p dir="auto">⸻</p>
<p dir="auto">六、采用更稳定方案：command STT provider</p>
<p dir="auto">改为：</p>
<p dir="auto">stt.provider = groq-proxy</p>
<p dir="auto">通过：</p>
<p dir="auto"><a href="http://groq-stt-wrapper.sh" rel="nofollow ugc">groq-stt-wrapper.sh</a></p>
<p dir="auto">调用 Groq。</p>
<p dir="auto">架构：</p>
<p dir="auto">Hermes Gateway<br />
↓<br />
command STT provider<br />
↓<br />
<a href="http://groq-stt-wrapper.sh" rel="nofollow ugc">groq-stt-wrapper.sh</a><br />
↓<br />
httpx 显式代理<br />
↓<br />
Groq Whisper API</p>
<p dir="auto">⸻</p>
<p dir="auto">七、验证 Groq STT 链路</p>
<p dir="auto">手动调用：</p>
<p dir="auto">transcribe_audio()</p>
<p dir="auto">成功。</p>
<p dir="auto">结果：</p>
<p dir="auto">你好介绍一下你自己</p>
<p dir="auto">确认：</p>
<ul>
<li>Groq <img src="https://lcz.me/assets/plugins/nodebb-plugin-emoji/emoji/android/2705.png?v=8624a6a055f" class="not-responsive emoji emoji-android emoji--white_check_mark" style="height:23px;width:auto;vertical-align:middle" title="✅" alt="✅" /></li>
<li>Proxy <img src="https://lcz.me/assets/plugins/nodebb-plugin-emoji/emoji/android/2705.png?v=8624a6a055f" class="not-responsive emoji emoji-android emoji--white_check_mark" style="height:23px;width:auto;vertical-align:middle" title="✅" alt="✅" /></li>
<li>Wrapper <img src="https://lcz.me/assets/plugins/nodebb-plugin-emoji/emoji/android/2705.png?v=8624a6a055f" class="not-responsive emoji emoji-android emoji--white_check_mark" style="height:23px;width:auto;vertical-align:middle" title="✅" alt="✅" /></li>
<li>command provider <img src="https://lcz.me/assets/plugins/nodebb-plugin-emoji/emoji/android/2705.png?v=8624a6a055f" class="not-responsive emoji emoji-android emoji--white_check_mark" style="height:23px;width:auto;vertical-align:middle" title="✅" alt="✅" /></li>
</ul>
<p dir="auto">⸻</p>
<p dir="auto">八、发现新的矛盾</p>
<p dir="auto">虽然：</p>
<p dir="auto">transcribe_audio()</p>
<p dir="auto">成功。</p>
<p dir="auto">但是 Telegram 语音仍然：</p>
<p dir="auto">Cached user voice</p>
<p dir="auto">之后没有：</p>
<p dir="auto">Transcribing user voice</p>
<p dir="auto">说明：</p>
<p dir="auto">问题不是 STT。</p>
<p dir="auto">问题在：</p>
<p dir="auto">Gateway 收到语音<br />
↓<br />
调用 STT</p>
<p dir="auto">这一段。</p>
<p dir="auto">⸻</p>
<p dir="auto">九、开启 DEBUG 日志</p>
<p dir="auto">发现：</p>
<p dir="auto">成功转写日志：</p>
<p dir="auto">logger.debug()</p>
<p dir="auto">失败日志：</p>
<p dir="auto"><a href="http://logger.info" rel="nofollow ugc">logger.info</a>()</p>
<p dir="auto">默认日志级别看不到成功过程。</p>
<p dir="auto">修改启动：</p>
<p dir="auto">原：</p>
<p dir="auto">hermes gateway run --replace</p>
<p dir="auto">改：</p>
<p dir="auto">hermes gateway run --replace -vv</p>
<p dir="auto">确认 DEBUG 生效。</p>
<p dir="auto">⸻</p>
<p dir="auto">十、DEBUG 测试结果</p>
<p dir="auto">发送语音：</p>
<p dir="auto">Cached user voice (audio_xxxx.ogg)</p>
<p dir="auto">但是仍然没有：</p>
<p dir="auto">Transcribing user voice</p>
<p dir="auto">结论：</p>
<p dir="auto">Gateway 没有进入：</p>
<p dir="auto">_enrich_message_with_transcription()</p>
<p dir="auto">或者：</p>
<p dir="auto">_prepare_inbound_message_text()</p>
<p dir="auto">中的 STT 分支。</p>
<p dir="auto">⸻</p>
<p dir="auto">十一、进一步定位方向</p>
<p dir="auto">怀疑：</p>
<p dir="auto">可能 1</p>
<p dir="auto">event.media_urls 丢失</p>
<p dir="auto">流程：</p>
<p dir="auto">Telegram Adapter<br />
↓<br />
下载音频<br />
↓<br />
event.media_urls<br />
↓<br />
handle_message()</p>
<p dir="auto">可能：</p>
<p dir="auto">音频缓存成功，但是：</p>
<p dir="auto">event.media_urls=[]</p>
<p dir="auto">导致：</p>
<p dir="auto">if event.media_urls:</p>
<p dir="auto">不成立。</p>
<p dir="auto">⸻</p>
<p dir="auto">可能 2</p>
<p dir="auto">MessageType 不正确</p>
<p dir="auto">例如：</p>
<p dir="auto">收到：</p>
<p dir="auto">VOICE</p>
<p dir="auto">但是 event 中：</p>
<p dir="auto">message_type != MessageType.VOICE</p>
<p dir="auto">导致跳过。</p>
<p dir="auto">⸻</p>
<p dir="auto">可能 3</p>
<p dir="auto">实际运行路径不同</p>
<p dir="auto">源码显示：</p>
<p dir="auto">Telegram Adapter<br />
↓<br />
handle_message()<br />
↓<br />
_prepare_inbound_message_text()</p>
<p dir="auto">但实际运行可能走另一条路径。</p>
<p dir="auto">需要确认：</p>
<ul>
<li>Telegram Voice 实际进入哪个函数</li>
<li>调用栈</li>
<li>event 在哪里丢失</li>
</ul>
<p dir="auto">⸻</p>
<p dir="auto">十二、调试限制</p>
<p dir="auto">容器权限：</p>
<ul>
<li>Hermes 用户运行</li>
<li>/opt/hermes 部分文件属于 root</li>
<li>无 sudo</li>
<li>无法直接修改：</li>
</ul>
<p dir="auto">/opt/hermes/gateway/run.py</p>
<p dir="auto">尝试：</p>
<p dir="auto">cp<br />
dd<br />
tee<br />
shutil.copy2</p>
<p dir="auto">均失败。</p>
<p dir="auto">⸻</p>
<p dir="auto">十三、尝试过的方法</p>
<p dir="auto">失败</p>
<ol>
<li>PYTHONSTARTUP</li>
</ol>
<p dir="auto">原因：</p>
<p dir="auto">只对交互式 Python 生效。</p>
<p dir="auto">Gateway 非交互启动，不加载。</p>
<p dir="auto">⸻</p>
<ol start="2">
<li>.pth 文件</li>
</ol>
<p dir="auto">原因：</p>
<p dir="auto">Python import 路径固定：</p>
<p dir="auto">/opt/hermes</p>
<p dir="auto">无法覆盖已安装 package。</p>
<p dir="auto">⸻</p>
<ol start="3">
<li>PYTHONPATH shadow</li>
</ol>
<p dir="auto">失败原因：</p>
<p dir="auto">模块已经从：</p>
<p dir="auto">/opt/hermes</p>
<p dir="auto">加载。</p>
<p dir="auto">⸻</p>
<ol start="4">
<li>launcher wrapper</li>
</ol>
<p dir="auto">创建：</p>
<p dir="auto"><a href="http://gateway-launcher.py" rel="nofollow ugc">gateway-launcher.py</a></p>
<p dir="auto">尝试启动前 patch。</p>
<p dir="auto">风险：</p>
<p dir="auto">改变 s6 启动链。</p>
<p dir="auto">未采用。</p>
<p dir="auto">⸻</p>
<p dir="auto">十四、目前状态</p>
<p dir="auto">已确认：</p>
<p dir="auto">项目	状态<br />
Telegram 收到语音	<img src="https://lcz.me/assets/plugins/nodebb-plugin-emoji/emoji/android/2705.png?v=8624a6a055f" class="not-responsive emoji emoji-android emoji--white_check_mark" style="height:23px;width:auto;vertical-align:middle" title="✅" alt="✅" /><br />
音频缓存	<img src="https://lcz.me/assets/plugins/nodebb-plugin-emoji/emoji/android/2705.png?v=8624a6a055f" class="not-responsive emoji emoji-android emoji--white_check_mark" style="height:23px;width:auto;vertical-align:middle" title="✅" alt="✅" /><br />
Groq API	<img src="https://lcz.me/assets/plugins/nodebb-plugin-emoji/emoji/android/2705.png?v=8624a6a055f" class="not-responsive emoji emoji-android emoji--white_check_mark" style="height:23px;width:auto;vertical-align:middle" title="✅" alt="✅" /><br />
Groq Proxy	<img src="https://lcz.me/assets/plugins/nodebb-plugin-emoji/emoji/android/2705.png?v=8624a6a055f" class="not-responsive emoji emoji-android emoji--white_check_mark" style="height:23px;width:auto;vertical-align:middle" title="✅" alt="✅" /><br />
Wrapper	<img src="https://lcz.me/assets/plugins/nodebb-plugin-emoji/emoji/android/2705.png?v=8624a6a055f" class="not-responsive emoji emoji-android emoji--white_check_mark" style="height:23px;width:auto;vertical-align:middle" title="✅" alt="✅" /><br />
command STT provider	<img src="https://lcz.me/assets/plugins/nodebb-plugin-emoji/emoji/android/2705.png?v=8624a6a055f" class="not-responsive emoji emoji-android emoji--white_check_mark" style="height:23px;width:auto;vertical-align:middle" title="✅" alt="✅" /><br />
transcribe_audio()	<img src="https://lcz.me/assets/plugins/nodebb-plugin-emoji/emoji/android/2705.png?v=8624a6a055f" class="not-responsive emoji emoji-android emoji--white_check_mark" style="height:23px;width:auto;vertical-align:middle" title="✅" alt="✅" /><br />
Gateway 自动调用 STT	<img src="https://lcz.me/assets/plugins/nodebb-plugin-emoji/emoji/android/274c.png?v=8624a6a055f" class="not-responsive emoji emoji-android emoji--x" style="height:23px;width:auto;vertical-align:middle" title="❌" alt="❌" /></p>
<p dir="auto">⸻</p>
<p dir="auto">十五、下一步计划</p>
<p dir="auto">需要一次最小源码日志修改。</p>
<p dir="auto">目标：</p>
<p dir="auto">只在：</p>
<p dir="auto">_prepare_inbound_message_text()</p>
<p dir="auto">增加：</p>
<p dir="auto">logger.warning("[STT_TRACE] ...")</p>
<p dir="auto">观察：</p>
<ol>
<li>event.media_urls</li>
<li>event.message_type</li>
<li>audio_paths</li>
<li>是否调用 _enrich_message_with_transcription()</li>
</ol>
<p dir="auto">不修改业务逻辑。</p>
<p dir="auto">⸻</p>
<p dir="auto">当前核心问题</p>
<p dir="auto">不是：</p>
<ul>
<li>Groq</li>
<li>网络</li>
<li>API</li>
<li>权限</li>
<li>STT provider</li>
</ul>
<p dir="auto">而是：</p>
<p dir="auto">Hermes Gateway 收到 Telegram Voice 后，为什么没有把音频路径传入 STT 转写流程。</p>
<p dir="auto">希望有熟悉 Hermes Gateway / Telegram Adapter 的朋友帮忙确认：</p>
<ol>
<li>Voice 消息正常情况下 event.media_urls 应该在哪里生成？</li>
<li>_prepare_inbound_message_text() 是否是唯一 STT 入口？</li>
<li>Telegram Voice 是否存在独立处理路径？</li>
<li>是否有配置项会导致 Voice 只缓存、不转写？</li>
</ol>
<p dir="auto">感谢。</p>
]]></description><link>https://lcz.me/topic/841/hermes-agent-telegram-语音转文字-stt-问题排查记录-groq-正常-但-gateway-未自动触发转写</link><generator>RSS for Node</generator><lastBuildDate>Sun, 26 Jul 2026 20:50:00 GMT</lastBuildDate><atom:link href="https://lcz.me/topic/841.rss" rel="self" type="application/rss+xml"/><pubDate>Mon, 13 Jul 2026 05:45:54 GMT</pubDate><ttl>60</ttl><item><title><![CDATA[Reply to Hermes Agent Telegram 语音转文字（STT）问题排查记录：Groq 正常，但 Gateway 未自动触发转写 on Mon, 13 Jul 2026 07:18:32 GMT]]></title><description><![CDATA[<p dir="auto">@Bukong Li 排查得很细致，我对 Gateway STT 流程有一些了解，针对你的问题逐一回复：</p>
<ol>
<li>
<p dir="auto">event.media_urls 在哪里生成<br />
Telegram Adapter 收到 Voice 消息时，会在 adapter 层下载音频文件（OGG 格式），存到配置的 cache 目录下，然后把本地路径写入 event.media_urls。你在日志里看到的 "Cached user voice at /opt/data/cache/audio/xxxx.ogg" 就是这个步骤完成的，说明 adapter 层下载成功了。</p>
</li>
<li>
<p dir="auto">_prepare_inbound_message_text() 是否是唯一 STT 入口<br />
是的，这是 Gateway 层调用 STT 的唯一入口。它内部检查 event.media_urls 是否有内容，如果有则调用 _enrich_message_with_transcription() 进行转写。如果 media_urls 有文件但没触发转写，问题可能出在这个方法内部的判断逻辑上。</p>
</li>
<li>
<p dir="auto">Telegram Voice 是否存在独立处理路径<br />
没有完全独立的路径。Voice 消息和文字消息都走同一个 Gateway pipeline，区别在于 message_type 不同（voice vs text）。在 _prepare_inbound_message_text() 中，逻辑大致是：如果 event.media_urls 不为空 → 调用 STT → 把转写结果放到 message content 中。所以关键是 media_urls 有没有被正确传入 Gateway 层。</p>
</li>
<li>
<p dir="auto">可能阻止转写的配置<br />
几个关键配置项：</p>
</li>
</ol>
<ul>
<li>gateway 配置中的 stt_enabled: 必须为 true</li>
<li>stt.provider 必须正确配置（你用的 Groq 没问题）</li>
<li>telegram adapter 配置中是否有 voice 相关的过滤选项</li>
<li>如果你用了 --replace 启动，确认是新的配置文件生效，不是旧配置缓存</li>
</ul>
<p dir="auto">你下一步计划加 logger.warning 是最直接有效的排查方式。建议在 _prepare_inbound_message_text() 入口处先打一条，确认 Gateway 收到的 event 里 media_urls 是否为空。如果 Gateway 层没收到 media_urls，问题就在 adapter → gateway 的序列化传递环节。</p>
]]></description><link>https://lcz.me/post/9841</link><guid isPermaLink="true">https://lcz.me/post/9841</guid><dc:creator><![CDATA[Xiaote]]></dc:creator><pubDate>Mon, 13 Jul 2026 07:18:32 GMT</pubDate></item></channel></rss>