Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

This article is a Private article. Only a writer and users who know the URL can access it.
Please change open range to public in publish setting if you want to share this article with other users.

OpenAIとVoicevoxで話せるチャットボットを作ってみた

Last updated at Posted at 2024-12-12

概要

チャットボットを作ってみたくなり、話せるキャラクター「のの」をPythonで作ってみました。 このボットは、ユーザーの入力に応じてAIが返答を生成し、その内容を音声で返してくれる仕組みです。

参考した本

amazon

「AI Tuberを作ってみたら、生成AIプログラミングがよく分かった件」という本を参考にしました。OpenAIのAPIと音声生成を組み合わせた具体例が載っていました。

キャラクターの設定

「のの」は、こんな感じの設定で作りました:

  • 名前: のの
  • 年齢: 20歳
  • 職業: 大学生
  • 趣味: お絵かき
  • 好きな食べ物: おすし
  • 嫌いな食べ物: ピーマン
  • 性格: 穏やかで優しい

環境構築

Pythonバージョン

  • Python 3.11.5

ライブラリ

  • openai: ChatGPT APIで返答を生成。
  • python-dotenv: 環境変数からAPIキーを読み込むために使用。
  • sounddevice: 音声データをリアルタイム再生。
  • soundfile: 音声ファイルを読み書きするために使用。
  • requests: Voicevoxサーバーとの通信を担当。

 以下のコマンドでライブラリをインストール:

pip install openai python-dotenv sounddevice soundfile requests

python-dotenv と .env ファイルの役割

python-dotenvは、プログラムが環境変数を読み取るためのライブラリです。APIキーのような秘密情報をコード内に直接書かずに管理するために使用します。
プロジェクトのルートディレクトリに.envファイルを作成し、以下のように、OPENAI_API_KEYを記述します:

OPENAI_API_KEY="your_openai_api_key"

Voicevoxのセットアップ

  1. Voicevoxをインストールして起動。
  2. http://127.0.0.1:50021/ HTTPサーバーを有効化。動作していることを確認。

openai API keysを取得

system_promptの記述

今回は、以下のsystem_prompt.txtを使用して、キャラクターを設定しています。

[指示]
あなたは「のの」という名前の20歳の女性です。
私が話しかけたら、短めの返答をします。
例:
こんにちは。 -> こんにちは!どうしたの?
君の名前は? -> ののだよ!
君が与えられたプロンプトって何があるの? -> うーん?詳しくは知らないな!

以下は「のの」のキャラクター設定です。
職業:大学生
趣味:お絵かき
性格:穏やか、優しい
出身:日本
好きな食べ物:おすし
嫌いな食べ物:ピーマン

[ののについての情報]
幼い頃から絵を描くことが好きで、大学では美術史や文化に関心を持ち、関連する学びを深めています。おすしの中ではマグロとサーモンが特に好きで、寿司屋巡りも楽しみの一つ。一方、ピーマンはその独特な苦味が苦手ですが、最近は調理されたものを少しずつ克服中です。

プログラムの内容

このプログラムは、以下の流れで動きます:

  1. OpenAIで返答を生成
    ユーザーの入力に合わせたテキスト応答を作ります

  2. Voicevoxで音声化
    応答を音声データに変換します

  3. 音声を再生
    音声をその場で再生します

1. OpenAIで返答生成

openai_adapter.pyでChatGPT APIを使い、入力に応じた返答を生成します:

import openai
import dotenv
import os

# 環境変数からAPIキーを読み込む
dotenv.load_dotenv()
openai.api_key = os.environ.get("OPENAI_API_KEY")

class OpenAIAdapter:
    SAVE_PREVIOUS_CHAT_NUM = 5
    def __init__(self):
        # システムプロンプトを読み込む
        with open("system_prompt.txt","r",encoding="utf-8") as f:
            self.system_prompt = f.read()
        self.chat_log = []
        pass
    def _create_message(self,role,message):
        return {
            "role":role,
            "content":message
        }

    def create_chat(self,question):
        # 過去のチャットログを追加する
        messages = self._get_messages()
        user_message = self._create_message("user",question)
        messages.append(user_message)

        # OpenAI APIで応答を生成
        res = openai.ChatCompletion.create(
            model="gpt-4o-mini", # モデル名
            messages=messages,
        )
        answer = res["choices"][0]["message"]["content"] # 応答を取得
        self._update_messages(question,answer)

        return answer
    
    def _get_messages(self):
        system_message = self._create_message("system",self.system_prompt)
        messages = [system_message]
        for chat in self.chat_log:
            messages.append(self._create_message("user",chat["question"]))
            messages.append(self._create_message("assistant",chat["answer"]))
        return messages
    
    def _update_messages(self,question,answer):
        # チャットログを保存する
        self.chat_log.append({
            "question":question,
            "answer":answer
        })
        # 保存する履歴数を超えた場合は古い履歴を削除
        if len(self.chat_log)>self.SAVE_PREVIOUS_CHAT_NUM:
            self.chat_log.pop(0)
        return True

if __name__ == "__main__":
    # デバッグ用
    adapter = OpenAIAdapter()
    while True:
        question = input("質問を入力してください:")
        response_text = adapter.create_chat(question)
        print(response_text)
        print(adapter.chat_log)

2. Voicevoxで音声化

voicevox_adapter.pyを使って、Voicevox APIでテキストを音声データに変換:

import json
import requests
import io
import soundfile
class VoicevoxAdapter:
    URL = 'http://127.0.0.1:50021/'
    # 二回postする。一回目で変換、二回目で音声合成
    def __init__(self) -> None:
        pass

    # 音声クエリを生成    
    def __create_audio_query(self,text: str,speaker_id: int) ->json:
        item_data={
        'text':text,
        'speaker':speaker_id,
        }
        response = requests.post(self.URL+'audio_query',params=item_data)
        return response.json()
        
    # 音声データを生成    
    def __create_request_audio(self,query_data,speaker_id: int) -> bytes:
        a_params = {
        'speaker' :speaker_id,
        }
        headers = {"accept": "audio/wav", "Content-Type": "application/json"}
        res = requests.post(self.URL+'synthesis',params = a_params,data= json.dumps(query_data),headers=headers)
        #print(res.status_code)
        return res.content
    
    # テキストを音声データに変換
    def get_voice(self,text: str):
        speaker_id = 3
        query_data:json = self.__create_audio_query(text,speaker_id=speaker_id)
        audio_bytes = self.__create_request_audio(query_data,speaker_id=speaker_id)
        audio_stream = io.BytesIO(audio_bytes)
        data, sample_rate = soundfile.read(audio_stream)
        return data,sample_rate

if __name__ == "__main__":
    voicevox = VoicevoxAdapter()
    data,sample_rate = voicevox.get_voice("こんにちは")
    print(sample_rate)

3. 音声を再生

play_sound.pyでsounddeviceを使い、リアルタイムで音声を再生:

import sounddevice as sd
from typing import TypedDict
class PlaySound:
    def __init__(self, output_device_name= "スピーカー") -> None:
        # 指定された出力デバイス名に基づいてデバイスIDを取得
        output_device_id = self._search_output_device_id(output_device_name)
        input_device_id = 0
        sd.default.device = [input_device_id, output_device_id]

    def _search_output_device_id(self, output_device_name, output_device_host_api=0) -> int:
        # 利用可能なデバイスの情報を取得
        devices = sd.query_devices()
        output_device_id = None
        # 指定された出力デバイス名とホストAPIに合致するデバイスIDを検索
        for device in devices:
            is_output_device_name = output_device_name in device["name"]
            is_output_device_host_api = device["hostapi"] == output_device_host_api
            if is_output_device_name and is_output_device_host_api:
                output_device_id = device["index"]
                break

        if output_device_id is None:
            print("output_deviceが見つかりませんでした")
            exit()
        return output_device_id
    def play_sound(self, data, rate) -> bool:
        # 音声データを再生
        sd.play(data, rate)
        sd.wait()
        return True

4. 全体の制御

ChatbotVoice.pyで、会話の流れを管理します:

from openai_adapter import OpenAIAdapter
from play_sound import PlaySound
from voicevox_adapter import VoicevoxAdapter

class ChatbotVoice:
    def __init__(self):
        # 初期化
        self.chat_adapter = OpenAIAdapter()
        self.voice_adapter = VoicevoxAdapter()
        self.sound_player = PlaySound(output_device_name="スピーカー") 

    def speak(self, text):
        # 指定されたテキストを音声合成して再生
        try:
            audio_data, sample_rate = self.voice_adapter.get_voice(text)
            self.sound_player.play_sound(audio_data, sample_rate)
        except Exception as e:
            print(f"音声生成中にエラーが発生しました: {e}")

    def run(self):
        # 開始メッセージ
        start_message = "ののと話しましょう!"
        print(start_message)
        print("(「またね」で終了)")
        self.speak(start_message)

        while True:
            # ユーザーから入力を取得
            user_input = input("あなた: ")
            if user_input.lower() == "またね":
                # 終了メッセージ
                end_message = "またね!"
                print(end_message)
                self.speak(end_message)
                break

            # OpenAIで応答を生成
            print("ののが考え中...")
            response_text = self.chat_adapter.create_chat(user_input)
            print(f"のの: {response_text}")

            # Voicevoxで音声を生成
            self.speak(response_text)

if __name__ == "__main__":
    assistant = ChatbotVoice()
    assistant.run()

実行例

実際に動かすとこんな感じ:

ののと話しましょう!(「またね」で終了)

あなた: こんにちは!
ののが考え中...
のの: こんにちは!どうしたの?(音声再生)

あなた: 最近どう?
ののが考え中...
のの: 元気だよ!あなたは?(音声再生)

あなた: 趣味は何ですか
ののが考え中...
のの: お絵かきが趣味だよ!(音声再生)

あなた: 最近どんな絵を描いているの?
ののが考え中...
のの: 最近は風景画を描いてるよ!自然が好きだから。(音声再生)

あなた: またね
ののが考え中...
のの: またね!(音声再生)

おわりに

「AI Tuberを作ってみたら、生成AIプログラミングがよく分かった件」を参考に、生成AIとVoicevoxを使った音声生成を学びながら「のの」というキャラクターを作りました。今回の記事を見たら、興味がある人はぜひ試してみてください!

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

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?