【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 提供商共存时,按工具名路由即可。
相关推荐
鹿角片ljp3 小时前
LeetCode 64:最小路径和复盘|二维 DP 与 ACM 模式完整写法
java·数据结构·算法
+VX:Fegn08954 小时前
计算机毕业设计|基于springboot + vue蛋糕店管理系统(源码+数据库+文档)
前端·数据库·vue.js·spring boot·课程设计
泡海椒4 小时前
PDF 表格样式优化:jquick-pdf 边框、圆角、背景色
java·开发语言·pdf
景熙55236 小时前
15.Java 8 Stream 流入门到实战
java·开发语言·数据结构
wno7046 小时前
Spring Security权限控制
java·python·spring
wno7046 小时前
Spring Boot整合Kafka
spring boot·kafka
杨运交7 小时前
[071][验证码模块]基于Spring拦截器的验证码认证设计思想
java·后端·spring
Geek-Chow7 小时前
CountDownLatch in Java
java
SL_staff8 小时前
从RBAC到场景化授权:《无忧·企业文档》三级权限模型的技术实践解析
java·开源·产品