【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 提供商共存时,按工具名路由即可。
相关推荐
唠点键盘之外的18 小时前
10 Spring Boot + 大模型:Java 项目接入 AI 的三种姿势
java·spring boot·aigc·cursor
NWU_LK18 小时前
【WebFlux】第五篇 —— 两种编程模型与 WebClient
java
增量星球18 小时前
《持续交付2.0系列六》业务需求协作管理
java·运维·自动化·devops·持续部署·持续集成
重生之我是Java开发战士19 小时前
【Java EE】Spring IoC 与 DI
java·spring·java-ee
霸道流氓气质19 小时前
SpringBoot+Vue通过ModbusTCP协议实现PLC 设备连接、重连实时控制
vue.js·spring boot·后端
SimonKing19 小时前
阿里要求全员卸载 Claude Code:事件始末与深层逻辑
java·后端·程序员
云烟成雨TD19 小时前
Agent Scope Java 2.x 系列【42】2.0 GA 正式发布:从透明开发迈向智能体系统工程
java·人工智能·agent
猫猫不是喵喵.19 小时前
SpringBoot自动装配原理
java·spring boot·后端
刘小八19 小时前
Redis 缓存一致性:一文讲透延时双删原理、并发时序
java·数据库·redis·缓存