[実践ADK] ADKとLyriaとChainlitで音楽生成エージェント - おまけ Lyria Realtimeによる長めの音楽生成
こんにちは、サントリーこと大橋です。
このシリーズでは、ADK (Agent Development Kit) とLyria、Chainlitを使って音楽生成エージェントを作成する方法を解説してきました。
前回までで、このシリーズとして実践的にADKをGoogle Cloud上でChatUIとともに扱うにはどうすればよいのかという部分については、書き終えています。
今回はおまけ回ということで、Lyria RealTimeについて触れ、音楽生成の別アプローチについて書きたいと思います。
Vertex AIのLyria2 APIの課題
一回目の記事で使用した Vertex AIのLyria2 では以下のような課題がありました。
- 30秒以上の音楽が作成できない。
- ちょこちょこ失敗する
今回はこの課題を解決していくために Lyria RealTime を利用したAI Agentを作成したいと思います。
Lyria RealTime とは?
Lyria RealTimeは Interactive real-time music creation model
と紹介された音楽生成モデルで、
具体的にはWebSocketを利用して、永続的にプロンプトとパラメータを与え続けることで音楽を変化させながら生成し続けることができるモデルです。
Lyria RealTimeは以下のサンプルで試すことができます。
Lyria RealTimeでの音楽生成
Lyria RealTimeは、現状Vertex AIでは利用できずGenerative AI API経由で利用します。
またAPI自体も専用のAPIとなるため以下のように実行方法が異なります。
import asyncio
from google import genai
from google.genai import types
client = genai.Client(api_key=API_KEY, http_options={'api_version': 'v1alpha'})
async def main():
async def receive_audio(session):
"""Example background task to process incoming audio."""
while True:
async for message in session.receive():
audio_data = message.server_content.audio_chunks[0].data
# Process audio...
await asyncio.sleep(10**-12)
async with (
client.aio.live.music.connect(model='models/lyria-realtime-exp') as session,
asyncio.TaskGroup() as tg,
):
# Set up task to receive server messages.
tg.create_task(receive_audio(session))
# Send initial prompts and config
await session.set_weighted_prompts(
prompts=[
types.WeightedPrompt(text='minimal techno', weight=1.0),
]
)
await session.set_music_generation_config(
config=types.LiveMusicGenerationConfig(bpm=90, temperature=1.0)
)
# Start streaming music
await session.play()
if __name__ == "__main__":
asyncio.run(main())
※公式ドキュメントより引用
具体的には上記のように、「音楽データをストリーミングで受信するための非同期タスク」と「Lyria RealTimeにプロンプトを渡し続ける非同期タスク」の2つの処理が必要です。
データ受信非同期タスクでは、WebSocketを通じて送られてくる音楽データを受信し続ける処理を行います。
公式ドキュメントでは永続的に処理を行うためにwhile True
を指定して、永続的にループを実行していますが、実際にはこのwhile True
の部分は必須ではありません。
sessionが継続している限りは音楽は生成され続けます。
while True:
async for message in session.receive():
audio_data = message.server_content.audio_chunks[0].data
# Process audio...
await asyncio.sleep(10**-12)
プロンプトを送信する非同期タスクではLyria RealTime用のプロンプトを作成し、Lyria RealTimeへ送信しています。
Lyria RealTimeではどのような音楽を生成するかを指定するためにWeightedPrompt
のリストをパラメータとして渡します。
await session.set_weighted_prompts(
prompts=[
{"text": "Piano", "weight": 2.0},
types.WeightedPrompt(text="Meditation", weight=0.5),
types.WeightedPrompt(text="Live Performance", weight=1.0),
]
)
このWeightedPrompt
はどのプロンプトをどれぐらいの重さ(強さ?)にするかを定義したパラメータです。
Lyria RealTimeでは連続的にパラメータを渡しますが、急激なプロンプトの変化は不自然な音楽を生成することになります。このため、プロンプトを変化させる場合はweight
を徐々に変化させることで、より自然な音楽の変化を生み出すことができます。
それ以外にもスケールやBPM、ドラムやベースの有無などを調整することが可能です。
具体的な実装例は以下にもありますので参照してみてください。
LongComposerAgent
と LongComposerFlowAgent
今回作成するAI Agent
LongComposerAgent
Lyria RealTime用パラメータ生成Agent 今回作成するAI Agent(LongComposerAgent
)は以下のタスクをもちます。
- ユーザーによって渡された音楽イメージと大まかな時間から、Stanza(節、連)という単位に音楽を分割
- そのStanza毎にユーザの指定した音楽イメージに合っったプロンプト(群)を生成する
- この時生成するプロンプトはLyria RealTimeに沿った
WeightedPrompt
のリストにする - Lyria RealTimeのプロンプトはLyriaとは異なるため注意が必要
- この時生成するプロンプトはLyria RealTimeに沿った
- Lyria RealTimeに連続的に渡すためのパラメータをJSON Arrayで生成する。
なおLongComposerAgent
は前回作成したComposerAgent
のように直接tool
を利用して音楽生成はさせず、別のAgentに任せたいと思います。
これはLyria RealTimeに渡すためのパラメータ群が少し複雑で、「こういうパラメータのtool
があるから呼び出して」としてしまうとパラメータの生成や呼び出しに失敗することが多いためです。
この詳細については、ADKのドキュメントのToolのベストプラクティスを参照してください。
上記を実現するLongComposerAgent
用のプロンプトが以下です。
上記のタスクと、Lyria RealTimeのパラメータについての説明が記載されています。
Agentの作成は以下です。
MusicPlan
というpydanticモデルを生成させmusic_plan
というsession key
に保存させる仕様になっています。
実際にJSONを作成すると以下のようになります。
プロンプト
1分ぐらいの朝に聞く爽やかなプログレッシブハウス 最初はドラムのみで始まって、途中から次第にベースが追加され、 中盤はピアノベースの爽やかなメロディライン 終わりはドラムとベースを消してピアノのみで
生成されるJSON
{
"title": "Morning Progressive House",
"stanzas": [
{
"prompts": [
{
"text": "Upbeat Progressive House",
"weight": 2.0
},
{
"text": "Driving",
"weight": 1.0
},
{
"text": "Upbeat",
"weight": 1.0
},
{
"text": "Danceable",
"weight": 1.0
},
{
"text": "TR-909 Drum Machine",
"weight": 1.0
}
],
"seconds": 15,
"config": {
"bpm": 125,
"mute_bass": True
}
},
{
"prompts": [
{
"text": "Upbeat Progressive House",
"weight": 2.0
},
{
"text": "Driving",
"weight": 1.0
},
{
"text": "Upbeat",
"weight": 1.0
},
{
"text": "Danceable",
"weight": 1.0
},
{
"text": "TR-909 Drum Machine",
"weight": 1.0
},
{
"text": "Boomy Bass",
"weight": 1.0
}
],
"seconds": 15,
"config": {
"bpm": 125
}
},
{
"prompts": [
{
"text": "Upbeat Progressive House",
"weight": 2.0
},
{
"text": "Driving",
"weight": 1.0
},
{
"text": "Upbeat",
"weight": 1.0
},
{
"text": "Danceable",
"weight": 1.0
},
{
"text": "Refreshing Piano Melody",
"weight": 1.5
},
{
"text": "Smooth Pianos",
"weight": 1.0
}
],
"seconds": 15,
"config": {
"bpm": 125
}
},
{
"prompts": [
{
"text": "Upbeat Progressive House",
"weight": 2.0
},
{
"text": "Refreshing Piano Melody",
"weight": 1.5
},
{
"text": "Smooth Pianos",
"weight": 1.0
}
],
"seconds": 15,
"config": {
"bpm": 125,
"mute_bass": True,
"mute_drums": True
}
}
]
}
指示通り序盤はドラムのみで、だんだんベースを入れて、途中からピアノ、終盤はベースとドラムをミュートして終わるようになっています。
LongComposerFlowAgent
Lyria RealTimeで実際に音楽を生成する 次に、以下のLongComposerFlowAgent
を作成します。
LongComposerFlowAgent
は LlmAgent(Agent)
ではなくBaseAgent
を継承し、任意のフロー制御を行うAgentクラスです。
やっていることは
- LongComposerAgentを作成し、
MusicPlan
(音楽生成プラン 上記のStanzaの配列を持つ)を生成 - 生成された音楽生成プランを元に、
genrate_music
で音楽データを作成save_music
でArtifactへ保存(細かな処理はComposerAgent
と同様)
です。
音楽の生成処理は後で細かく説明します。
今回のLongComposerFlowAgent
のようなBaseAgent
を直接継承したAgentは各種LLMAgent
を制御するワークフローAgentを作成する場合に有用です。
今回はLLMAgent
を呼び出しと音楽生成をしているのみですが、ADKのWorkflow Agent
で説明されているSequentialAgent
やParallelAgent
、LoopAgent
を作成し、複雑なワークフローを生成することが可能です。
より複雑なワークフロー制御を行いたい場合はADK公式の以下のドキュメントがわかりやすいので参照してみてください。動的なAgent生成等、非常に重要なワークフロー制御が可能になります。
Lyria RealTimeを利用した音楽の生成
Lyria RealTimeクライアントの初期化
LyriaRealTimeを使うためには、まずLyria Realtimeのクライアントを作成します。
ここはGeminiのクライアントを作成する方法とほぼ同じです。
普通と異なる点は、他のAgentがVertex AI
経由で呼び出しているため、環境変数にGOOGLE_GENAI_USE_VERTEXAI
を指定しているため、vertexai=False
というパラメータを渡している点と、現状ではLyria RealTimeはアルファバージョンのAPIであるため、{'api_version': 'v1alpha'}
を渡す必要があります。
Lyria RealTimeとの非同期双方向通信セッションの作成
次にLyria RealTimeと非同期双方向通信するための、Sessionを作成します。
次はLyria RealTimeとの通信処理です。
上にも書きましたが、Lyria RealTimeでは「生成された音楽データを受信する非同期タスク」と「プロンプトを送信するタスク」が必要です。
Lyria RealTimeとの非同期双方向通信処理 受信側
今回の「生成された音楽データを受信する非同期タスク」ではリアルタイムで音楽を流す必要が無いため、bytearray
に受信したデータを貯めて行くことのみを行います。
なおリアルタイムでブラウザなどに配信する場合はWebSocketを利用したり、何かしらのストリームへの出力が必要です。
この辺りは以下を参考にしてください。
Lyria RealTimeとの非同期双方向通信処理 送信側
「プロンプトを送信するタスク」では
- LLMにより生成された
MusicPlan
内にあるMusicStanza
のリストを順々に送信 - 送信後、
MusicStanza
の時間分待機
ということをしていきます。
BPMやスケールがわかった場合はコンテキストのリセットが必要らしくその処理も記載しています。
MP3化
全てのMusicStanza
を送信し、待機した後、セッション(音楽の生成)とタスクを止めます。
※現在のコードではこの処理がうまく行っていなくて、データ受信側でエラーになっていますが、握りつぶしています。
その後、音楽データのMP3化とArtifactへの保存処理を行っています。
音楽データは以下の形式で送信されており、それに合わせてパラメータを設定、mp3化します。
出力形式: 未加工の 16 ビット PCM 音声
サンプルレート: 48 kHz
チャンネル: 2(ステレオ)
それ以外のArtifactへの保存処理は以前書いたComposerAgent
と同じにすることによって、ディレクターエージェント(オーケストレーターエージェント)側の処理を共通化しています。
ディレクターエージェントの書き換え
最後にLongComposerAgent
をディレクターエージェントに登録して、振り分け処理を行うようにします。
ユーザーの求める長さによってComposerAgent
かLongComposerAgent
かを振り分けます。
実際に作ってみる
では実際に使ってみましょう。面倒なのでChainlitのChatUIではなくadk web
で動かします。
できた曲が以下です。
Lyriaに比べて長い曲が生成できました。
まとめ
今回はLyria RealTimeを利用して、ちょっと眺めの音楽をAI Agent経由で自動生成してみました。
Lyria RealTimeを使うことで、音楽の知識がそれほどなくても、インタラクティブに思ったような音楽が生成できました。
なお、Lyria RealTimeの音楽生成はリアルタイムなので生成する音楽と同じ時間だけ必要です。
ADKを利用する場合、このような長い処理を指せる場合は、 LongRunningFunctionTool
を利用するほうが正しいかなとは思います。
Discussion