【免费下载链接】answer-me-with-html

Answer me with HTML — an agent skill that answers hard questions with a one-page HTML you can actually read. 让 AI Agent 用一页 HTML 回答复杂问题。

项目地址: https://gitcode.com/gh_mirrors/an/answer-me-with-html
点击查看 免费下载

am video 可以把 Markdown 稿件渲染成带旁白的解释视频,而 ElevenLabs 是它可选的云端配音后端:只要设置了 ELEVENLABS_API_KEY,旁白就会自动交给 ElevenLabs 合成;没有 key 时则回退到系统语音,因此整套配置是可选项。本文覆盖 ElevenLabs 接入的完整流程——创建最小权限 API key、三个环境变量(ELEVENLABS_API_KEY / ELEVENLABS_VOICE_ID / ELEVENLABS_MODEL_ID)的取值与默认值、按声音/模型/文本三级维度的本地缓存如何让你只为改动的句子付费,以及 401/402/超时三类报错的源码级来源与排查方法。

blueprint 风格解释视频中的四帧:片头、高亮 Server 的时序图、流程图、对比表

配音在 am video 中的位置:一条有明确回退顺序的语音管线

在动手配置之前,先理解 ElevenLabs 在整体配音链路里的位置。src/video/tts.js 文件头的注释写明了回退顺序:

ElevenLabs(设置了 ELEVENLABS_API_KEY 时)→ 系统 TTS(macOS say / Linux espeak-ng)→ 仅字幕。

--voice 参数共有五个取值:auto | elevenlabs | local | system | off。pickProvider 函数实现了具体决策逻辑,其中与 ElevenLabs 直接相关的分支有两条:

  • 显式指定 --voice elevenlabs 时,如果 ELEVENLABS_API_KEY 缺失会直接抛出 voice=elevenlabs needs the ELEVENLABS_API_KEY environment variable,即文档里"没有 key 时直接报错"的行为;
  • 默认 --voice auto 时(也是 src/config.js 中 voice 配置项的默认值),只要检测到 ELEVENLABS_API_KEY 就选用 ElevenLabs,否则继续回退到系统语音。

这也解释了文档里"有 key 就自动用 ElevenLabs"这一句话背后的真实判断链。

配置步骤:最小权限 key + 一个环境变量

官方配置指南见 docs/elevenlabs.zh-CN.md(英文版 docs/elevenlabs.md),完整步骤如下。

  1. 在 ElevenLabs 后台创建 API key。只勾选 Text to Speech(语音合成)权限就够了,am 不调用别的接口——从源码看,elevenLabs 提供者 只向一个端点发请求:POST https://api.elevenlabs.io/v1/text-to-speech/{voice_id}?output_format=pcm_22050,认证方式是请求头 xi-api-key,请求体只有 text 和 model_id 两个字段,确实不需要其他任何 API 权限。

  2. 设置环境变量,再渲染:

    export ELEVENLABS_API_KEY="sk_..."     # 想长期生效,就把这行写进 ~/.zshrc
    am video draft.md                      # 有 key 就自动用 ElevenLabs
    am video draft.md --voice elevenlabs   # 没有 key 时直接报错
    

两点实操注意事项,文档里明确强调了:

  • Agent 只能读到启动它之前已经设好的变量。如果你是在 Agent 会话中途 export 的 key,Agent 的子进程未必能继承到;
  • 想把 ElevenLabs 固定为默认配音,运行 am config set voice elevenlabs。该命令写入的是 ~/.answer-me-with-html/config.json(可用 AM_HOME 改位置),voice 键在 CONFIG_KEYS 中被声明为枚举类型,合法值即 auto | elevenlabs | local | system | off,test/config.test.js 验证了默认值为 auto。这样无论环境变量如何变化,渲染都会强制走 ElevenLabs 分支,缺 key 时报错而不是静默回退。

选声音和模型(可选):两个变量与它们的默认值

声音和模型适合的语言、风格因人而异,请按自己的需求选:

变量作用不设时
ELEVENLABS_VOICE_ID声音。在 ElevenLabs 的声音页复制 IDbIHbv24MWmeRgasZH58o(Will,官方声音)
ELEVENLABS_MODEL_ID模型,如 eleven_v4_turbo、eleven_flash_v2_5、eleven_multilingual_v2eleven_v4_turbo

这两个默认值在源码中是硬编码常量 ELEVEN_DEFAULT_VOICE / ELEVEN_DEFAULT_MODEL,读取逻辑就是简单的 env.ELEVENLABS_VOICE_ID || ELEVEN_DEFAULT_VOICE。am help video 的输出中也同样说明了这两个变量及 eleven_v4_turbo 的默认模型。

关于默认声音的语言能力,文档给出了一个容易踩坑的细节:

  • 默认值(Will + eleven_v4_turbo)在免费套餐下可用,中英文稿件都能念;
  • Will 母语是英语,ElevenLabs 只在 eleven_multilingual_v2 下把中文列为它的已验证语言。想要听起来是母语的中文,请自己选中文声音;
  • 公共声音库里的声音需要付费套餐,免费套餐调用时 API 会返回 402 paid_plan_required。

各模型支持哪些语言,以 ElevenLabs 官方的模型列表为准(其文档在 ElevenLabs 官网可查,此处不附外部链接)。

费用与缓存:每句旁白只合成一次

ElevenLabs 按字符计费。为了让"改一句只花一句的钱"成立,am 把每句旁白单独合成并缓存:

  • 缓存目录是 ~/.answer-me-with-html/cache/tts/(即 AM_HOME/cache/tts/),每个缓存文件是以哈希值命名的 .pcm 文件;
  • 从 synthAll 的实现看,缓存键是 sha1(provider.id + 文本),而 ElevenLabs 的 provider.id 形如 elevenlabs:{voice_id}:{model_id}(见 tts.js 第 54 行)——也就是说缓存确实按"声音 + 模型 + 文本"三个维度区分。只有改过的句子,或换了声音、模型,才会重新请求计费;
  • 音频统一为 22050 Hz 单声道 16 位 PCM,ElevenLabs 的请求直接在 URL 上要求 output_format=pcm_22050,省去转码;
  • 缓存写入采用"先写临时文件再 rename"的方式(writeCache),中断不会留下半个缓存文件;损坏(空文件、奇数字节)的缓存会被当作未命中重新合成(readCache);
  • 多个句子并发合成,ElevenLabs 提供者的并发上限为 2(tts.js 第 55 行)。

顺带一提,缓存是"可再生的",所以 src/housekeeping.js 中的 am clean 会直接清空整个 cache/ 目录(连同超过 30 天的页面和视频),配置文件不受影响;test/housekeeping.test.js 对这一行为有测试覆盖。

报错排查:三种消息对应的三种原因

命令行提示是英文的,下面把文档的对照表与源码中的抛出点放在一起:

报错原因源码出处
voice=elevenlabs needs the ELEVENLABS_API_KEY environment variable显式指定了 --voice elevenlabs 但 key 没设pickProvider
ElevenLabs returned 401 或 missing_permissionskey 不对,或没有语音合成权限HTTP 状态透传
ElevenLabs returned 402,含 paid_plan_required用了声音库里的声音,而你是免费套餐;换官方声音或升级套餐同上
Cannot connect to ElevenLabs60 秒内没有响应,或没有网络;加 --voice off 只出字幕fetch 超时分支

两点补充:

  • 60 秒超时来自常量 ELEVEN_TIMEOUT_MS = 60000(tts.js 第 15 行),通过 AbortSignal.timeout 实现;超时与网络不可达都会被包装成 Cannot connect to ElevenLabs,并在超时场景下追加 no response within 60 seconds 的原因说明。
  • --voice off 会让 pickProvider 返回 null,即纯字幕模式。测试 test/cli.test.js 第 428 行 还验证了一个相关行为:用 --voice off 生成的视频页面在后续 am patch 时保持无声,不会因为全局 config 改了配音而"被重新配音"。

延伸阅读

【免费下载链接】answer-me-with-html

Answer me with HTML — an agent skill that answers hard questions with a one-page HTML you can actually read. 让 AI Agent 用一页 HTML 回答复杂问题。

项目地址: https://gitcode.com/gh_mirrors/an/answer-me-with-html
点击查看 免费下载
Logo

邀请您加入社区

更多推荐