第二十章 文字转语音 TTS:用 @Tool 包装上游 SDK,框架不再内置 TTS
原因:TTS 的业务差异太大。有的团队用火山引擎,有的用阿里云 CosyVoice,有的用 OpenAI。硬塞进框架等于绑死用户。
2.0 的做法:你自己包装上游 SDK 为一个
@Tool,agent 调工具拿到音频字节,框架只管调度、不管语音合成。
20.1 包装上游 TTS SDK 为 @Tool
业务方做三件事:
- 引入上游 SDK(火山 / 阿里云 CosyVoice / OpenAI TTS / Azure Speech)
- 写一个
@Tool调用该 SDK - 注册到 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 提供商共存时,按工具名路由即可。