【AgentScope Java新手村系列】(20)文字转语音TTS

第二十章 文字转语音 TTS:用 @Tool 包装上游 SDK,框架不再内置 TTS

原因:TTS 的业务差异太大。有的团队用火山引擎,有的用阿里云 CosyVoice,有的用 OpenAI。硬塞进框架等于绑死用户。

2.0 的做法:你自己包装上游 SDK 为一个 @Tool,agent 调工具拿到音频字节,框架只管调度、不管语音合成。

20.1 包装上游 TTS SDK 为 @Tool

业务方做三件事:

  1. 引入上游 SDK(火山 / 阿里云 CosyVoice / OpenAI TTS / Azure Speech)
  2. 写一个 @Tool 调用该 SDK
  3. 注册到 agent / subagent

例:包装阿里云 CosyVoice SDK 为工具:

ini 复制代码
import com.alibaba.dashscope.audio.tts.SpeechSynthesisParam;
import com.alibaba.dashscope.audio.tts.SpeechSynthesizer;
import io.agentscope.core.message.DataBlock;
import io.agentscope.core.message.Base64Source;
import io.agentscope.core.tool.Tool;
import io.agentscope.core.tool.ToolParam;

import java.nio.ByteBuffer;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Base64;

public class TtsTool {

    private final String apiKey;

    public TtsTool(String apiKey) {
        this.apiKey = apiKey;
    }

    @Tool(name = "tts_synthesize", description = "把文字转成 mp3 音频,返回 DataBlock")
    public DataBlock synthesize(
            @ToolParam(name = "text") String text,
            @ToolParam(name = "voice", required = false) String voice) {

        SpeechSynthesisParam param = SpeechSynthesisParam.builder()
                .apiKey(apiKey)
                .model("cosyvoice-v1")
                .text(text)
                .build();

        SpeechSynthesizer synthesizer = new SpeechSynthesizer();
        ByteBuffer result = synthesizer.call(param);
        byte[] mp3 = new byte[result.remaining()];
        result.get(mp3);

        String b64 = Base64.getEncoder().encodeToString(mp3);
        return DataBlock.builder()
                .source(Base64Source.builder()
                        .data(b64)
                        .mediaType("audio/mp3")
                        .build())
                .name("tts.mp3")
                .build();
    }

    @Tool(name = "tts_to_file", description = "把文字转成 mp3 并写入文件")
    public String synthesizeToFile(
            @ToolParam(name = "text") String text,
            @ToolParam(name = "outPath") String outPath) {

        SpeechSynthesisParam param = SpeechSynthesisParam.builder()
                .apiKey(apiKey)
                .model("cosyvoice-v1")
                .text(text)
                .build();

        SpeechSynthesizer synthesizer = new SpeechSynthesizer();
        ByteBuffer result = synthesizer.call(param);
        byte[] mp3 = new byte[result.remaining()];
        result.get(mp3);

        Files.write(Path.of(outPath), mp3);
        return "已写入 " + outPath;
    }
}

注册到 agent:

scss 复制代码
Toolkit toolkit = new Toolkit();
toolkit.registerTool(new TtsTool());                                 // 注册 TTS 包装工具

HarnessAgent agent = HarnessAgent.builder()
        .name("voice_assistant")
        .sysPrompt("你是语音助理,可以用 tts_to_file 把文字转成 mp3 文件。")  // 告诉 LLM 有 TTS 工具可用
        .model(model())
        .workspace(Path.of("./workspace"))
        .toolkit(toolkit)                                            // 传入注册好的工具
        .build();

LLM 看到用户要"念给我听"时,自动 调用 tts_to_file 工具------生成的 tts_output.mp3 直接用播放器打开就能听到。

提示:tts_synthesize 返回 DataBlock,适合把音频推给前端播放;tts_to_file 写入磁盘,适合本地验证。

20.2 与前端的流式推送

Web 端拿到 DataBlock 后:

ini 复制代码
ws.onmessage = (event) => {
  const data = JSON.parse(event.data);
  if (data.toolName === "tts_synthesize") {
    const audioB64 = data.result.media.base64;
    const audioBlob = base64ToBlob(audioB64, "audio/mp3");
    const audio = new Audio(URL.createObjectURL(audioBlob));
    audio.play();
  }
};

agent.streamEvents(...) 会把 ToolResultBlock 推给前端;前端按 mediaType 决定是否转成 <audio>

20.3 多 TTS 提供商的策略

如果业务同时接火山、OpenAI、CosyVoice,可以在 tools.json 里按工具名分别包装:

json 复制代码
{
  "tools": [
    {
      "name": "tts_cosyvoice",
      "class": "demo.tts.CosyVoiceTool",
      "description": "中文 TTS(CosyVoice)"
    },
    {
      "name": "tts_openai",
      "class": "demo.tts.OpenAiTtsTool",
      "description": "英文 TTS(OpenAI)"
    }
  ]
}

主 agent 在 system prompt 里描述路由规则:

复制代码
中文场景用 tts_cosyvoice,英文场景用 tts_openai。

20.4 完整可运行示例

本地验证时,建议让 agent 直接调用 tts_to_file,把 mp3 写到磁盘,这样就能用播放器听到真实语音:

typescript 复制代码
public class Chapter20_FullTts {

    public static void main(String[] args) {
        TtsTool ttsTool = new TtsTool();                            // 业务方包装的 TTS 工具(见 20.1 节)

        Toolkit toolkit = new Toolkit();
        toolkit.registerTool(ttsTool);                              // 通过 Toolkit 注册

        String outPath = Path.of("./workspace").resolve("tts_output.mp3").toString();

        HarnessAgent agent = HarnessAgent.builder()
                .name("voice_assistant")
                .sysPrompt("""
                        你是语音助理。
                        用户让你"念" / "读" / "播放"某段文字时,
                        调 tts_to_file 把 mp3 保存到:
                        """ + outPath)
                .model(model())
                .workspace(Path.of("./workspace"))
                .toolkit(toolkit)                                   // 传入注册好的工具
                .build();

        String reply = agent.call(
                        new UserMessage("把'杭州今天 22 度'念给我听,保存成 mp3 文件。"),
                        RuntimeContext.empty())                     // 无 session,单次调完即走
                .block()
                .getTextContent();

        System.out.println(reply);
        System.out.println("音频已保存:" + outPath);                  // 可直接用播放器打开
    }
}

运行后打开 ./workspace/tts_output.mp3 即可听到合成语音。

20.5 本章小结

  • 2.0 移除了内置 TTS 模块。推荐业务方包装上游 SDK 为 @Tool
  • 本地验证用 tts_to_file 把 mp3 写到磁盘,可直接播放;上线后可用 tts_synthesize 返回 DataBlock 推给前端。
  • 多个 TTS 提供商共存时,按工具名路由即可。
相关推荐
码农颜1 天前
5.4.1 锁分类
java·数据库·mysql
linux-hzh1 天前
百日算法修炼 · Day 03
java·算法
不负岁月无痕1 天前
简单理解操作系统结构
java·linux·c语言·开发语言·c++·面试
mister_guo1 天前
Java线程池参数应该如何设置(ThreadPoolExecutor)
java
余额瞒着我当琳1 天前
C++模板初阶与STL初探
android·java·c++
淡海水1 天前
15-02-YooAsset面试篇-Unity架构设计与源码
java·unity·面试·c#·游戏引擎·yooasset
qq_448011161 天前
C语言中的指针函数和函数指针
java·c语言·开发语言
weixin_BYSJ19871 天前
springboot酒店管理系统--附源码27436
java·spring boot·python·随机森林·贪心算法·eclipse·django
2401_894915531 天前
GEO 源码部署如何实现精准地域分发?核心配置参数深度讲解
java·运维·服务器·后端·开源
evans在进步1 天前
LeetCode 2 两数相加:链表模拟加法,Java 图解进位过程
java·leetcode·链表