SUNOAIの歌詞を洗練させるボット

<a href="https://it.blogmura.com/ranking/in?p_cid=11119283" target="_blank"><img src="https://b.blogmura.com/it/88_31.gif" width="88" height="31" border="0" alt="にほんブログ村 IT技術ブログへ" /></a><br />
## はじめに

先週のライブ会場で紹介した、AI作曲ツール「SUNO」で作ったアルバム「Hiking Song」。
収録曲「新道と古道」は、自分でも気に入っていたのですが、案の定、女性二名から絶賛の声が届きました！

https://suno.com/playlist/c35fdc89-85e4-4553-8cc4-585bfc553b73
https://www.youtube.com/watch?v=QYrHWe_eLZA

やはりAI作曲のポイントは作詞で、**どこまで人の心に響く歌詞を作れるか**にかかっているのだと改めて実感しました。

そこで、AIに曲の歌詞をブラッシュアップさせるボットを作ってみることに！
GoogleのAI Studioが無料で使えて便利なので、今回はその開発ダイジェストと、なぜか完成してしまったジョーク作品をご紹介します。
SUNOの歌詞を洗練させるつもりが、なぜかお笑いの道に進んでしまったという…
そんな開発の舞台裏を覗いてみてください！


## SUNOAIの歌詞を洗練させるボットとは？

「SUNOAIの歌詞を洗練させるボット」は、その名の通り、SUNOで作成した歌詞をより洗練されたものにすることを目的としたAIボットです。

例えば、

* より自然な日本語表現にする
* 表現を豊かにする
* 歌詞に深みを与える
* 特定のテーマに沿った歌詞にする

といったことをAIの力を借りて行います。


## 開発ダイジェスト

Google AI Studioを使って開発を進めています。

２つのＡＩエージェントにロールを与えて会話をしてもらうだけの機能です。

{knowledge_source_1}の部分は将来的には、ＲＡＧでロールにあったスキルプロンプトをデータベースから引っ張る予定です。

https://qiita.com/EasyCording/items/d25c9a0814380e0f93ec

```python
import tkinter as tk
from tkinter import scrolledtext
import requests
import json
from dotenv import load_dotenv
import os
import threading
import time

load_dotenv()
API_TOKEN_1 = os.getenv("API_TOKEN_1")
API_TOKEN_2 = os.getenv("API_TOKEN_2")

conversation_history = []

def log_request(data, api_token):
    """Log the API request data to a file."""
    with open("api_requests.log", "a", encoding="utf-8") as log_file:
        log_file.write(f"API Token: {api_token}\n")
        log_file.write(f"Request Data: {json.dumps(data, ensure_ascii=False, indent=2)}\n")
        log_file.write("\n")

def generate_response(prompt, api_token):
    url = 'https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash-latest:generateContent'
    headers = {'Content-Type': 'application/json'}
    data = {
        "contents": [{"parts": [{"text": prompt}]}]
    }
    params = {'key': api_token}

    log_request(data, api_token)

    try:
        response = requests.post(url, headers=headers, params=params, data=json.dumps(data))
        response.raise_for_status()
        result = response.json()
        if 'candidates' in result and result['candidates']:
            return result['candidates'][0]['content']['parts'][0]['text']
        else:
            return "Error: Unable to generate response"
    except requests.RequestException as e:
        return f"Error: {str(e)}"

def continue_conversation():
    global conversation_history

    repeat_count = int(repeat_entry.get())

    for _ in range(repeat_count):
        for agent_num, api_token, agent_prompt in [(1, API_TOKEN_1, agent_1_prompt_entry.get("1.0", tk.END).strip()), (2, API_TOKEN_2, agent_2_prompt_entry.get("1.0", tk.END).strip())]:
            update_chat_history(f"エージェント{agent_num}が考え中...\n")
            full_prompt = agent_prompt
            
            truncated_history = conversation_history[-5:]
            for message in truncated_history:
                full_prompt += f"{message['speaker']}: {message['content']}\n"

            agent_response = generate_response(full_prompt, api_token)
            conversation_history.append({"speaker": f"agent_{agent_num}", "content": agent_response})
            update_chat_history(f"エージェント{agent_num}: {agent_response}\n")
            
            time.sleep(2)

    update_chat_history("会話は終了しました。\n\n")
    input_field.config(state="normal")
    send_button.config(state="normal")

def send_message():
    user_message = input_field.get()
    input_field.delete(0, tk.END)
    input_field.config(state="disabled")
    send_button.config(state="disabled")

    conversation_history.append({"speaker": "user", "content": user_message})
    update_chat_history(f"あなた: {user_message}\n")

    threading.Thread(target=continue_conversation, daemon=True).start()

def update_chat_history(message):
    chat_history.config(state="normal")
    chat_history.insert(tk.END, message)
    chat_history.see(tk.END)
    chat_history.config(state="disabled")

def maximize_window():
    window.state('zoomed')

def clear_chat_history():
    global conversation_history
    conversation_history = []
    chat_history.config(state="normal")
    chat_history.delete(1.0, tk.END)
    chat_history.config(state="disabled")

window = tk.Tk()
window.title("チャットアプリ")

chat_history = scrolledtext.ScrolledText(window, state="disabled", height=20, width=50)
chat_history.pack(padx=10, pady=10)

input_field = tk.Entry(window, width=50)
input_field.pack(padx=10, pady=(0, 10))

send_button = tk.Button(window, text="送信", command=send_message)
send_button.pack(pady=(0, 10))

repeat_label = tk.Label(window, text="リピート回数:")
repeat_label.pack()
repeat_entry = tk.Entry(window, width=5)
repeat_entry.insert(0, "10")
repeat_entry.pack()

agent_1_prompt_label = tk.Label(window, text="エージェント1のプロンプト:")
agent_1_prompt_label.pack()
agent_1_prompt_entry = tk.Text(window, height=5, width=50)
agent_1_prompt_entry.insert(tk.END, "あなたは放送作家です。\n{knowledge_source_1}")
agent_1_prompt_entry.pack()

agent_2_prompt_label = tk.Label(window, text="エージェント2のプロンプト:")
agent_2_prompt_label.pack()
agent_2_prompt_entry = tk.Text(window, height=5, width=50)
agent_2_prompt_entry.insert(tk.END, "あなたはプロデューサーです。\n{knowledge_source_2}")
agent_2_prompt_entry.pack()

clear_button = tk.Button(window, text="チャット履歴をクリア", command=clear_chat_history)
clear_button.pack(pady=(10, 0))

maximize_button = tk.Button(window, text="最大化", command=maximize_window)
maximize_button.pack(pady=(10, 10))

window.mainloop()
```

## まとめ

SUNOAIの歌詞を洗練させるボットは、現在も開発中です。
将来的には、

* より高度な歌詞生成機能
* 様々な音楽ジャンルへの対応
* ユーザーインターフェースの改善

などを行い、より使いやすいボットを目指していきます。

今回の開発を通して、AIは単なるツールではなく、**人間の創造性を刺激するパートナー**になり得ると感じました。
今後もAIの可能性を探求し、音楽制作の可能性を広げていきたいと思います。


## 最後に

今回の開発は、まだまだ始まったばかりです。
SUNOAIの歌詞を洗練させるボットが、どのように進化していくのか、楽しみにしていてください！

また、この記事を読んで、AI作曲に興味を持った方は、ぜひSUNOを試してみてください。
きっと、あなただけのオリジナルソングが作れるはずです。

**追伸**

開発を進めるうちに、**SUNOが生成する歌詞は、時に予想外の面白さやユーモアを持っている**ことに気づきました。

そこで、方向転換！
歌詞を洗練させるだけでなく、その面白さをさらに引き出すようなボットを作ることにしました。

どんなプロンプトを使ったかは、ご想像にお任せします。

https://www.youtube.com/watch?v=F4dnKVJopbM