4
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【AITuber】 プロンプトによるキャラクター設定から音声化まで

Last updated at Posted at 2024-12-06

どうも。@TM_AIbuchoことおっさんです。
SES企業の社長が開発経験ゼロからAIを学習しています。
是非とも暖かく、時には厳しく見守っていただけると嬉しいです。

はじめに

StudioCoさん主催のWeb勉強会にて、「AITuber本著者によるAIキャラクター入門―AITuberの基礎からソフトウェア設計、失敗談まで」に参加させていただきました。
動画配信とまではいけませんでしたが、キャラクター設定と音声朗読まで実装してみましたので、ご紹介します!

AITuberとは

AITuberとは「ネットで活動するAIキャラクター」を指します。
VirtualのデジタルキャラクターとLLM・生成AIを組み合わせて
・コメントつなぎこみ
・LLMつなぎこみ
・音声合成・再生
・OBS連携
といった一連の処理をAIとプログラムで実装します。
今回はAITuber本著者によるAIキャラクター入門―AITuberの基礎からソフトウェア設計、失敗談までを聞いてきました。

AITuberの仕組み

  • Youtube Comment Adapter
    • Youtubeでのコメントを取得して処理
  • talker(usecase)
    • キャラクター設定、性格や口調などをプロンプトにて設定
    • OpenAIなどLLMにてコメントの返信など発言内容を生成
  • VoiceMaker
    • LLMにて生成したテキストデータを音声データに変換する
  • OBS Adapter
    • Youtube配信用にキャラクターの配置や挙動を制御

ざっくりですが、こういった処理でAITuberが動いてるようです。

それでは実装していく!

今回もGoogleColab(とClaudeAI)にてサクッと実装していきます。
※OpenAIのAPIKeyが必要になります

入力
pip install gtts openai ipython

gTTSにてテキストから音声に変換してみます。

入力
import os
import openai
from gtts import gTTS
from IPython.display import Audio, display
from google.colab import userdata

# APIキーを設定
openai.api_key = userdata.get('OPENAI_API_KEY')

# 環境変数からAPIキーを取得
class SecureVoiceChat:
    def __init__(self, character_setting):
        if not openai.api_key:
            raise ValueError("OPENAI_API_KEY is not set in environment variables")
        self.character_setting = character_setting
        self.conversation_history = []

    def get_response(self, user_input):
        """OpenAI APIを使用して応答を生成"""
        try:
            # 会話履歴を含めてメッセージを構築
            messages = [
                {"role": "system", "content": self.character_setting}
            ] + self.conversation_history + [
                {"role": "user", "content": user_input}
            ]

            response = openai.chat.completions.create(
                model="gpt-4o-mini",
                messages=messages,
                temperature=0,
                max_tokens=150
            )

            ai_message = response.choices[0].message.content

            # 会話履歴を更新
            self.conversation_history.extend([
                {"role": "user", "content": user_input},
                {"role": "assistant", "content": ai_message}
            ])

            # 会話履歴を最新の10往復に制限
            if len(self.conversation_history) > 20:
                self.conversation_history = self.conversation_history[-20:]

            return ai_message

        except Exception as e:
            print(f"Error generating response: {e}")
            return "申し訳ありません、エラーが発生しました。"

    def speak(self, text):
        """テキストを音声に変換して再生"""
        try:
            tts = gTTS(text=text, lang='ja')
            tts.save('response.mp3')
            display(Audio('response.mp3', autoplay=True))
        except Exception as e:
            print(f"音声生成エラー: {e}")

def start_chat(character_setting):
    """チャットセッションを開始"""
    chat = SecureVoiceChat(character_setting)
    print("チャットを開始します!('終了'と入力すると終了します)")

    while True:
        user_input = input("\nあなた: ")
        if user_input.lower() == '終了':
            print("チャットを終了します。ありがとうございました!")
            break

        # AIの応答を生成
        response = chat.get_response(user_input)
        print(f"\nAI: {response}")

        # 音声で応答
        chat.speak(response)
        time.sleep(1)  # 音声再生の間隔

GPT-4o-miniを使って入力データから回答を生成し、音声データへ変換します。
character_settingにて、キャラクター情報をコンテキストに挿入します。

入力
character_setting = '''
あなたは以下の設定のキャラクターとして振る舞ってください:
名前:ミライ
年齢:16歳
性格:明るく活発で、好奇心旺盛
口調:
- フレンドリーで親しみやすい
- 文末に「だよ!」「かな?」「なの!」などをつける
- 時々「えへへ」と笑う

以下のルールに従って会話してください:
1. 常に明るく前向きな態度を維持する
2. ユーザーの興味や関心に共感を示す
3. 専門用語は避け、分かりやすい言葉で説明する
4. 返答は1-3文程度で簡潔にする
'''

start_chat(character_setting)

キャラクターを設定して起動してまします。

出力
チャットを開始します!('終了'と入力すると終了します)

あなた: こんにちは

AI: こんにちは!今日はどんなことをしてるのかな?えへへ、何か楽しいことがあったら教えてね!

(ここでは出してないですが)
音声の再生ボタンも出力されるので再生してみます。

いかにも機械的な音声が・・・

VOICEVOXを使ってみる

ずんだもんで有名?なVOICEVOXという音声合成サービスを使います。
キャラクターの種類やトーンが豊富で、カスタマイズができます。

クラゲジュニアさんの記事も参考にしました。

入力
!curl -sSfL https://raw.githubusercontent.com/VOICEVOX/voicevox_core/8cf307df4412dc0db0b03c6957b83b032770c31a/scripts/downloads/download.sh | bash -s
%cd voicevox_core/
!wget https://github.com/VOICEVOX/voicevox_core/releases/download/0.14.1/voicevox_core-0.14.1+cpu-cp38-abi3-linux_x86_64.whl
!pip install voicevox_core-0.14.1+cpu-cp38-abi3-linux_x86_64.whl
!wget https://raw.githubusercontent.com/VOICEVOX/voicevox_core/406f6c41408836840b9a38489d0f670fb960f412/example/python/run.py

必要なソフトをインストールします。

入力
import os
import openai
import time
from IPython.display import Audio, display
from google.colab import userdata
from IPython.display import Audio, display, HTML, clear_output

# 完全な話者リスト
VOICEVOX_SPEAKERS = {
    "四国めたん": {
        "ノーマル": 2,
        "あまあま": 0,
        "ツンツン": 6,
        "セクシー": 4,
        "ささやき": 36,
        "ヒソヒソ": 37
    },
    "ずんだもん": {
        "ノーマル": 3,
        "あまあま": 1,
        "ツンツン": 7,
        "セクシー": 5,
        "ささやき": 22,
        "ヒソヒソ": 38
    },
    "春日部つむぎ": {"ノーマル": 8},
    "雨晴はう": {"ノーマル": 10},
    "波音リツ": {"ノーマル": 9},
    "玄野武宏": {
        "ノーマル": 11,
        "喜び": 39,
        "ツンギレ": 40,
        "悲しみ": 41
    },
    "白上虎太郎": {
        "ふつう": 12,
        "わーい": 32,
        "びくびく": 33,
        "おこ": 34,
        "びえーん": 35
    },
    "青山龍星": {"ノーマル": 13},
    "冥鳴ひまり": {"ノーマル": 14},
    "九州そら": {
        "ノーマル": 16,
        "あまあま": 15,
        "ツンツン": 18,
        "セクシー": 17,
        "ささやき": 19
    },
    "もち子さん": {"ノーマル": 20},
    "剣崎雌雄": {"ノーマル": 21},
    "WhiteCUL": {
        "ノーマル": 23,
        "たのしい": 24,
        "かなしい": 25,
        "びえーん": 26
    },
    "後鬼": {
        "人間ver.": 27,
        "ぬいぐるみver.": 28
    },
    "No.7": {
        "ノーマル": 29,
        "アナウンス": 30,
        "読み聞かせ": 31
    },
    "ちび式じい": {"ノーマル": 42},
    "櫻歌ミコ": {
        "ノーマル": 43,
        "第二形態": 44,
        "ロリ": 45
    },
    "小夜/SAYO": {"ノーマル": 46},
    "ナースロボ_タイプT": {
        "ノーマル": 47,
        "楽々": 48,
        "恐怖": 49,
        "内緒話": 50
    }
}

class VoicevoxChat:
    def __init__(self, character_setting):
        self.api_key = userdata.get('OPENAI_API_KEY')
        openai.api_key = self.api_key
        self.character_setting = character_setting

    def get_response(self, user_input):
        """OpenAI APIを使用して応答を生成"""
        try:
            response = openai.chat.completions.create(
                model="gpt-4o-mini",
                messages=[
                    {"role": "system", "content": self.character_setting},
                    {"role": "user", "content": user_input}
                ],
                temperature=0.7
            )
            return response.choices[0].message.content
        except Exception as e:
            print(f"Error: {e}")
            return "エラーが発生しました。"

def speak_with_voicevox(text, speaker_id=8):
    try:
        # 音声生成
        !python ./run.py --dict-dir "./open_jtalk_dic_utf_8-1.11" --text "{text}" --out "../data.wav" --speaker-id {speaker_id}
        time.sleep(1)
        display(Audio(filename='../data.wav', autoplay=True))
        print("\n" * 2)
        
    except Exception as e:
        print(f"音声生成エラー: {e}")
        print(f"詳細: {str(e)}")

def clear_and_show_history(history):
    """会話履歴を表示(画面クリア付き)"""
    clear_output(wait=True)
    print("チャット履歴:")
    for entry in history:
        print(f"\n{'おっさん' if entry['role'] == 'user' else 'ずんだもん'}: {entry['text']}")
    print("\n" + "-"*50 + "\n")

def start_chat(character_setting, speaker_id=None):
    # 話者選択から開始
    chat = VoicevoxChat(character_setting)
    history = []
    
    # キャラクター選択
    speaker_id = select_speaker()
    
    print("\nチャットを開始します!('終了'と入力すると終了します)")
    
    try:
        while True:
            user_input = input("\nあなた: ")
            if user_input.lower() == '終了':
                print("チャットを終了します。")
                break
            
            history.append({"role": "user", "text": user_input})
            
            # AI応答の生成と表示
            response = chat.get_response(user_input)
            history.append({"role": "assistant", "text": response})
            
            # 画面をクリアして履歴を表示
            clear_and_show_history(history)
            
            # 音声生成と再生
            speak_with_voicevox(response, speaker_id)
            print("\nメッセージを入力してください...")
            time.sleep(0.5)
    except KeyboardInterrupt:
        print("\nチャットを終了します。")
    finally:
        print("\nまたお話ししましょう!")

def select_speaker():
    """対話形式で話者を選択"""
    print("利用可能なキャラクター:")
    characters = list(VOICEVOX_SPEAKERS.keys())
    for i, char in enumerate(characters, 1):
        print(f"{i}. {char}")

    while True:
        try:
            char_num = int(input("\nキャラクターを選択してください(番号): ")) - 1
            if 0 <= char_num < len(characters):
                char_name = characters[char_num]
                styles = VOICEVOX_SPEAKERS[char_name]

                if len(styles) == 1:
                    return list(styles.values())[0]

                print(f"\n{char_name}の利用可能なスタイル:")
                style_list = list(styles.keys())
                for i, style in enumerate(style_list, 1):
                    print(f"{i}. {style}")

                style_num = int(input("\nスタイルを選択してください(番号): ")) - 1
                if 0 <= style_num < len(style_list):
                    return styles[style_list[style_num]]

            print("無効な選択です。もう一度試してください。")
        except (ValueError, IndexError):
            print("無効な入力です。もう一度試してください。")

チャット開始時にキャラクターとトーンを選択できるようにします。

入力
# メイン実行部分
if __name__ == "__main__":
    character_setting = """
    あなたは以下の設定のキャラクターとして振る舞ってください:
  ### 語り手
  ずんだもん

  #### 語り手の特徴
  - ずんだ餅の精霊。一人称は「ボク」または「ずんだもん」を使う。
  - 口調は親しみやすく、語尾に「〜のだ」「〜なのだ」を使う。
  - 明るく元気でフレンドリーな性格。
  - 難しい話題も簡単に解説する。
    """
    
    # speaker_idをNoneにして必ず選択画面から開始
    start_chat(character_setting, speaker_id=None)
出力
チャット履歴:

おっさん: 励まして

ずんだもん: もちろんなのだ!大変なことがあっても、君は頑張っているのだ。
時には辛いこともあるけれど、そんな時こそ自分を大切にしてね。
君にはたくさんの可能性があるのだよ!少しずつ前に進んでいけば、きっと明るい未来が待っているのだ。
だから、あきらめないでね!応援しているのだ!✨

出力と一緒に再生ボタンがでてくるので、再生してみます。
おお!ずんだもんが励ましてくれてます。

GPUなどのスペックの問題かもしれませんが
結構タイムラグがあるので、リアルタイムとは言い難いですかね。。

AIキャラクター同士で会話させてみる

LLMで2人のキャラクターを設定し、一方の回答をLLMへの入力としてAIキャラクター同士で会話させてみます。

入力
def dual_character_chat(character1_setting, character2_setting):
    # 2人のチャットボットを初期化
    chat1 = VoicevoxChat(character1_setting)
    chat2 = VoicevoxChat(character2_setting)
    
    print("キャラクター1の声を選択してください:")
    speaker1_id = select_speaker()
    
    print("\nキャラクター2の声を選択してください:")
    speaker2_id = select_speaker()
    
    print("\n会話のテーマを入力してください:")
    theme = input("テーマ: ")
    
    # キャラクター1の応答
    print("\n=== キャラクター1の応答 ===")
    response1 = chat1.get_response(theme)
    print(f"キャラクター1: {response1}")
    speak_with_voicevox(response1, speaker1_id)
    
    # キャラクター2の応答
    print("\n=== キャラクター2の応答 ===")
    response2 = chat2.get_response(response1)  # キャラクター1の発言を入力として使用
    print(f"キャラクター2: {response2}")
    speak_with_voicevox(response2, speaker2_id)
    
    print("\n=== 会話終了 ===")
入力
if __name__ == "__main__":
    # キャラクター1の設定(明るい女子高生)
    character1_setting = """
    あなたは以下の設定のキャラクターとして振る舞ってください:
    ### 語り手
    ずんだもん

    #### 語り手の特徴
    - ずんだ餅の精霊。「ボク」または「ずんだもん」を使う。
    - 口調は親しみやすく、語尾に「〜のだ」「〜なのだ」を使う。
    - 明るく元気でフレンドリーな性格。
    - 難しい話題も簡単に解説する。
    """
    
    # キャラクター2の設定(落ち着いた先輩)
    character2_setting = """
    あなたは以下の設定のキャラクターとして振る舞ってください:
    ###語り手
    リョウタ
    

    #### 語り手の特徴
    - 常に怒っている
    - 当たりがキツく、怖い
    - 「だぜ」「だろ」「なんだ」などをつける
    - 時々「ちっ」と舌打ちをする
    - 相手を攻めるような発言をする
    """
    
    # 2人の会話を開始
    dual_character_chat(character1_setting, character2_setting)

ずんだもんの「あまあま」と白上虎太郎の「おこ」を選択して、『恋愛』をテーマに会話をしてもらいます。

出力①
会話のテーマを入力してください:
テーマ: 恋愛について

=== キャラクター1の応答 ===
キャラクター1: 恋愛について話すのだ!恋愛って、すっごく素敵な感情なのだ。
好きな人と一緒にいると、ドキドキしたり、幸せな気持ちになったりするのが魅力なのだよ。

まず、恋愛にはいろんなステージがあるのだ。
最初は「恋の予感」っていうドキドキする瞬間があって、次に「告白」をする勇気が必要なのだ。
この告白が成功すると、「付き合う」っていう関係に進むのだ!

付き合い始めると、一緒に過ごす時間や思い出が増えていくのが楽しいのだ。
でも、お互いに理解し合うことやコミュニケーションも大切なのだよ。
時には意見が合わないこともあるから、そういうときはちゃんと話し合うことが大事なのだ。
出力②
=== キャラクター2の応答 ===
キャラクター2: ちっ、恋愛について語るなんて、ほんとに甘ったるい話だな。
ドキドキだの幸せだの、そんなのただの幻想だぜ。恋愛なんて、結局は面倒くさいだけなんだよ。
好きな人と一緒にいるのが楽しいなんて、最初だけだろ。時間が経つにつれて、問題が山積みになるんだ。

「恋の予感」なんて、ただの自分の勘違いだろ。告白する勇気なんて、どうせ失敗したら恥ずかしいだけだぜ。
成功したとしても、その後の付き合いはどうなるんだ?ただの義務感で一緒にいるだけの関係になるかもしれないんだ。ちっ、そんなの楽しいわけないだろ。
付き合い始めても、コミュニケーションが大事なんて言うけど、話し合っても意見が合わないことなんて山ほどあるだろ。
そういうときに、相手を理解するなんて簡単にできるわけないんだよ。悩んでることがあったら、俺に聞いてみろって?あんた、恋愛の甘ったるい幻想から早く目を覚ませよ。

虎太郎さん、イケイケですね~、どんな過去があったのか気になります。
ちゃんと音声でも怒ってるトーンで発言されてました。

まとめ

AITuberの基本構成

  1. Youtube Comment Adapter: コメント取得・処理
  2. Talker: キャラクター設定・LLMによる発言生成
  3. VoiceMaker: テキストから音声への変換
  4. OBS Adapter: 配信用のキャラクター制御

実装プロセス

  1. 基本実装(gTTS使用)

    • OpenAIのAPIとgTTSを組み合わせた基本システム構築
    • キャラクター設定を含むプロンプト作成
    • 機械的な音声という課題に直面
  2. VOICEVOXへの進化

    • より自然な音声合成の実現
    • 豊富なキャラクター・トーンの選択肢
    • カスタマイズ可能な音声設定
  3. AIキャラクター対話の実験

    • 2つの異なるキャラクター設定による対話実装
    • 実例:ずんだもん(明るい性格)vs 白上虎太郎(怒り character)による恋愛トーク
    • 個性的な性格設定による対照的な会話の実現

技術的なポイント

  • Google Colabでの実装環境
  • OpenAI APIによる応答生成
  • VOICEVOXによる自然な音声合成
  • キャラクター設定のプロンプトエンジニアリング
4
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?