语音合成与视觉模型api接入实现

语音合成与视觉模型api接入实现

读完这篇,你应能按步骤复现 本仓库里的两条能力:火山豆包语音 TTS (文本 → 音频)与 Moonshot 视觉理解 (图 + 文 → 描述)。代码已在仓库中落地,本文侧重为什么要这样拆、先写哪一层、再拼哪一层,方便你搬到自己的项目里。

多模态业务里,「语音」和「看图说话」常来自不同厂商、不同鉴权方式;浏览器又不能随便塞 Secret。做法是:单页只负责表单与展示本机 server.js 当 BFF.env.local、带齐 Header、转发到官方域名。下面按推荐实现顺序写。


你将得到什么

能力 页面 代理路径 上游
语音合成 index-volc-tts.html POST /tts/api/v1/tts openspeech.bytedance.com/api/v1/tts
视觉理解 index-moonshot-vision.html POST /moonshot/v1/chat/completions api.moonshot.cn/v1/chat/completions

效果图:


第 0 步:先画清楚「三层」

无论做哪一条链路,都可以抽象成同一副骨架:

  1. 浏览器 :只认识 http://127.0.0.1:3000(或你的代理地址),用 fetch 发 JSON,不出现厂商密钥
  2. server.js :读 .env.local,补鉴权(Header 或 body 里的 app),fetch 到真实上游,把状态码和 body 原样或略加工返回给浏览器。
  3. 厂商 API :校验通过后返回 JSON(TTS 里是 base64 音频;Chat 里是 choices[0].message.content)。
flowchart LR subgraph browser["浏览器单页"] A[表单 / 选图] B[fetch 本地路径] end subgraph bff["server.js"] C[读环境变量] D[拼上游 URL + Header] end subgraph upstream["厂商 HTTPS"] E[(openspeech / Moonshot)] end A --> B --> C --> D --> E E --> D --> B

实现顺序建议 :先能用 curl 或最小脚本 从本机打到上游(验证密钥与路径),再写 BFF 路由 ,最后写 Vue 单页 把体验补齐。本仓库把后两步都写好了,你可以对照 server.js 里的 proxyVolcengineTtsproxyMoonshotRequest 逆序读回去。


第 1 步:准备账号与密钥(两条线各自一次)

火山(语音)

火山引擎语音活动/实名控制台创建应用,拿到 AppIDAccess Token (测试 Token 常有有效期 ,过期要重新复制)。HTTP 一次性合成文档见 豆包语音 · HTTP 非流式。请求体里的 app.cluster 在常见在线 TTS 场景下为 volcano_tts(若你开通的是其它产品线,以控制台绑定的文档为准)。

Moonshot(视觉)

Moonshot 开放平台 注册,在 API Keys 创建 sk-... 。视觉走 OpenAI 兼容的 /v1/chat/completionsmessagescontent 可为数组:type: image_urlurl 可用 Data URL )+ type: textmodel 须选支持视觉的型号(如 moonshot-v1-8k-vision-preview ),以 官方 Chat 文档 为准。


第 2 步:在 server.js 接通路(先后端,再前端)

2.1 为什么要单独路由,而不是让浏览器直连?

  • 密钥:火山 Token、Moonshot API Key 不能进前端仓库与打包产物。
  • Header 细节 :火山 TTS 要求 Authorization: Bearer;token(分号) ;Moonshot 是常见的 Bearer <空格>token。写进 BFF 可避免前端写错格式。
  • 路径前缀 :本仓库用 /tts/.../moonshot/v1/... 作为「入口命名空间」,与仓库里可灵 /kling 并列,便于在 createServer 里分支维护。

2.2 火山:proxyVolcengineTts 在做什么(实现要点)

  1. 只处理 POST /tts/api/v1/tts(与前端约定死,避免误打到别的服务)。
  2. JSON.parse 请求体后,强制写入 parsed.app = { appid, token, cluster }(来自环境变量),这样即使浏览器带了假 app 也会被覆盖。
  3. 若缺 request.reqid ,服务端用 crypto.randomUUID() 补上,满足「每次合成唯一」。
  4. 向上游 POST https://openspeech.bytedance.com/api/v1/tts (可用 VOLCENGINE_TTS_ORIGIN 覆盖域名),带上 Bearer; 头,把上游响应 原样 写回(状态码 + Content-Type + body)。

环境变量示例(.env.local勿提交):

env 复制代码
VOLCENGINE_TTS_APP_ID=你的AppId
VOLCENGINE_TTS_ACCESS_TOKEN=你的AccessToken
VOLCENGINE_TTS_CLUSTER_ID=volcano_tts

也支持 VITE_APP_ID / VITE_ACCESS_TOKEN / VITE_CLUSTER_ID。启动后看控制台 [Volc TTS] 已配置...

2.3 Moonshot:proxyMoonshotRequest 在做什么

  1. 匹配 /moonshot 前缀 ,剥掉后剩余路径必须以 /v1/ 开头且无 ..,防止开放代理被滥用。
  2. MOONSHOT_API_ORIGIN + restPath ,默认 https://api.moonshot.cn
  3. 请求头 Authorization: Bearer ${MOONSHOT_API_KEY} ,body 透传(密钥不在 body 里)。
env 复制代码
MOONSHOT_API_KEY=sk-你的密钥

也可用 VITE_API_KEY / API_KEY。日志里 [Moonshot] 已配置... 表示就绪。


第 3 步:写 index-volc-tts.html(从文本到能播的音频)

目标:用户输入文案 → 点按钮 → 听到合成声。

  1. 技术栈 :一个 HTML 里 <script type="module">,从 unpkg 引入 Vue 3 ESM ;需要 npx serve . 这类静态服务,避免 file:// 下模块加载失败。
  2. 代理根 proxyBase :所有请求都是 base + '/tts/api/v1/tts',与第 2 步里服务端路由一致;成功请求前写入 localStorage,下次打开少输一次地址。
  3. 请求体 :只组 user / audio / request不要 在浏览器写 app(交给服务端合并)。
  4. reqid:在浏览器生成 UUID,减少与服务端补全的竞态,也符合文档习惯。
  5. 解析返回 :JSON 里 data 是纯 base64;用 atobUint8ArrayBlob ,MIME 用 mimeForEncoding(encoding)mp3audio/mpegogg_opusaudio/ogg),否则容易「有数据但播不出」。
  6. 播放与内存URL.createObjectURL 赋给 <audio>;每次合成前 revokeObjectURL 旧地址,组件卸载时再清一次,避免泄漏。

跑通检查清单:node server.jsnpx serve . → 打开 index-volc-tts.html → 代理填 http://127.0.0.1:3000 → 点 Generate & Play → 听到声音。更多字段以火山文档为准;仓库交叉索引:README.md


第 4 步:写 index-moonshot-vision.html(从本地图片到模型回复)

目标:选一张图 + 一句问法 → 看到模型对图的描述。

  1. 读文件<input type="file"> + FileReader.readAsDataURL ,得到 data:image/...;base64,... ,同时用于 <img :src> 预览请求体(一份数据两处用,避免双份状态)。
  2. isValid :用 computed 判断是否已选图,禁用「提交」,减少空请求。
  3. 请求 URLbase + '/moonshot/v1/chat/completions',对应第 2.3 步的代理前缀。
  4. 请求体stream: falsemessages[0].content 为数组,先图后文 (与多模态习惯一致);model 可做成输入框便于换型号线。
  5. 解析 :取 choices[0].message.content ;HTTP 错误或结构不对时,把截断的 JSON 塞进 Error 文案,便于对照上游 error 字段排错。
  6. 大图:Data URL 会线性撑大 POST body,易触发超时或网关限制------产品上要引导压图或走对象存储 URL,本示例先文档化约束。

跑通检查清单:.env.local 配好 Moonshot Key → node server.js → 打开 index-moonshot-vision.html → 选图 → 提交 → 看到文字回复。


第 5 步:单页内部的共通套路(和 server.js 怎么分工)

环节 单页负责 server.js 负责
密钥 不出现 .env.local,向上游带正确 Header / 火山 app
URL proxyBase + 固定相对路径 拼官方 origin + /api/v1/tts/v1/...
业务 JSON 文案、音色、encoding、图片 Data URL、model 火山:覆盖 app;Moonshot:透传 body
错误展示 try/catch、HTTP 状态、关键字段校验 上游非 2xx 时透传 status + body

一句话 :单页只做 「表单 → JSON → fetch → 解析 → UI」 ;BFF 只做 「鉴权 + 合法路径 + 转发」

index-volc-tts.html 再抠两点

  • status / error:把「进行中 / 成功 / 失败」从控制台搬到页面上,demo 才像产品。
  • await audio.play() :若浏览器拦截自动播放,用户仍可通过 controls 手动点播放。

index-moonshot-vision.html 再抠两点

  • OpenAI 兼容content数组是多模态与纯文本混排的关键形状,顺序影响模型「先看到什么」。
  • 与火山的 Header 差异 :火山是 Bearer; ,Moonshot 是 Bearer ------写博客或接其它厂商时务必按文档逐字核对,不要想当然混用。

排错与扩展

  • 火山 401 / 鉴权失败 :核对 Token 是否过期、Authorization 是否为 Bearer; 、cluster 是否为 volcano_tts(或文档要求值)。
  • Moonshot 4xx:Key 是否有效、模型是否支持视觉、图片是否过大。
  • .env.local :务必重启 node server.js
  • 扩展:TTS 若改流式要换协议;视觉若改流式要解析 SSE / chunk,单页复杂度会明显高于本文的非流式示例。

相关文件(按阅读顺序)

  1. server.js --- 搜 proxyVolcengineTtsproxyMoonshotRequest
  2. index-volc-tts.htmlindex-moonshot-vision.html
  3. README.md --- 环境变量与接口总表

若你还想对照「文生图 + 异步轮询」的另一条 BFF 链路,可继续读 image-model.md 里的 index-keling.html 思路(与本篇的「一次 POST 即返回」形态不同)。

相关推荐
水如烟2 小时前
孤能子视角:“三线模型“,AI“不再““黑箱“?
人工智能
你听得到112 小时前
Get 这波之后,我把 Flutter 状态管理重新看了一遍:新项目到底该选谁?
前端·flutter·架构
打码人的日常分享2 小时前
新型智能建造解决方案
运维·人工智能·安全·系统安全·制造
wayz112 小时前
Day 5:KNN算法与相似K线匹配
人工智能·算法·机器学习
一念春风2 小时前
Qwen2.5 (AI模型 PC搭建)
人工智能·ai·c#·wpf·模型
audyxiao0012 小时前
郑庆华院士:人脑认知启发的机器记忆智能
人工智能·智能系统学报·院士
xinlianyq2 小时前
2026 交互革命:当“图形界面”消亡于智能体(Agent)的语义洪流
人工智能·api
断眉的派大星2 小时前
pytorch中保存训练模型和加载训练模型的用法
人工智能·pytorch·python
墨染天姬2 小时前
【AI】Gemma 4
人工智能