1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

IBM GraniteとWatsonとAIエージェントで、メタバース内のアバターをAIアバター化する仕組みづくり(音声で会話)

Last updated at Posted at 2025-10-04

概要

この文書では、任意のメタバース(デモとして、VKet Cloudを使用)で、AIエージェントをつかって自分のアバターを音声で会話を行うAIアバター化する仕組みをつくりましたことの説明を行います。
一般的なAIアバターの仕組みは、生成AI部分がChatGPTですが、この仕組みではAIエージェントにつなげることで、ChatGPTよりも専門性の高い回答や、AIエージェントをローカル(localhost)やプライベートクラウドで運用することで、機密性の高い情報を扱い、音声会話ログを自分の手元に残すことができます。
IBM Granite 4.0 を使った取り組みの公開記事としては世界最速かもしれない

仕組みイメージ

image.png

デモ動画

他のプレイヤーの音声をテキストに変換し、テキストをもとに、AIエージェントがテキストを生成し音声で発話して返している様子です。どんな処理をしているかわかりやすいように、右上に今回の仕組みが動く様子を重ねています。

メタバースをつかった理由

いつくかあります。

  • 人間が操作する場合と、AIに対応させる場合、共存できる
  • YouTubeのような動画と異なり、コラボしやすい。突発的な飛び入りコラボもできる
  • 表現の幅が広い
  • 背景の絵を描くよりも、3Dの方が楽だから(この感覚は賛否両論かも)

使っているクラウドサービス・ソフトウェア

この記事で紹介するプログラム

  • Python 3.12
  • Pythonライブラリ
    • ibm-watson
    • pyaudio
    • SpeechRecognition
    • python-dotenv
    • requests

音声認識

音声をテキストに変換しています。

音声合成

テキストを音声に変換し読み上げています。

サウンドデバイス

AIエージェント

音声認識で変換したテキストに対するレスポンスを生成します。

LLM/SLM

AIエージェントに使用するLLM/SLMは、主に下記を使っています。OpenAIのGPT5などでも動くことは確認済みです。

AIエージェントとLLM/SLMの接続には、Ollamaを使っています。LM Studioでも構いません。
その他Difyで対応している、OpenAI API GPT5 や Google Geminiなども使うことはできます。

メタバースサービス

動作確認済みは下記です。今回の仕組みはとてもシンプルなので、他のメタバースサービスでも動きます。

準備

Pythonのインストール

お使いのパソコンで、Pythonを使えるようにします。Pythonの古いバージョンは使わないようにしましょう。サポートが提供されいてるバージョンを使うようにしてください。

サウンドデバイス

Virtual Audio Cableをお使いのパソコンにインストールします。インストール後、下記の2つがサウンドデバイスとして認識されていれば良いです。

  • 出力(スピーカー):CABLE Input
  • 入力(マイク):CABLE Output

IBM Cloud

IBM Cloudのカタログ画面にアクセスします。
IBM Watson Speech To TextとIBM Watson Text To Speechの2つのサービスを無料のLiteプランで有効化します。
image.png

それぞれのサービスについて、リソースリストに表示されたサービス名をクリックし、「Service credentials」画面でAPIキーと接続先のURLを取得します。
image.png

LLM/SLM

お使いのパソコン、またはクラウド(パブリッククラウドまたはプライベートクラウド)上のサーバーに、OllamaとLLM/SLMを導入します。
Ollamaのインストール後、Ollamaを起動し、LLM/SLMを、ollamaを使って取得してください。

AIエージェント

モデル

例としてDifyを使います。お使いのパソコン、またはクラウド(パブリッククラウドまたはプライベートクラウド)上のサーバーに、Dify Community版を導入します。

  • OllamaのモデルをDifyにシステムモデルとして設定します。

image.png

DifyをDockerを使って制御している場合は、モデルの追加時に、Base URLを、http://host.docker.internal:11434/ を入力する必要があります。

image.png

Difyにアプリを用意

単純なもので良いので、Dify内に、追加したモデルを使ったアプリを用意します。
image.png
単純なチャットボットをつくるには、「最初から作成」をクリックし、下記の組み合わせで作ると簡単です。

アプリタイプを選択 初心者向けの基本的なアプリタイ
チャットフロー チャットボット

アプリのオーケストレート画面が表示されます。
image.png

APIキーの取得

アプリのオーケストレート画面で、「APIアクセス」をクリックし、画面右上にある「APIキー」のボタンをクリックします。APIキーを発行します。
image.png

APP IDの取得

画面左側の「オーケストレート」をクリックし、画面右上の「公開する」-->「更新を公開」-->「アプリを実行」の順にクリックします。
image.png

表示されたアプリのURLのうち、/chat/以降の文字列が、APP IDになります。
image.png

BASE URLの取得

表示されたアプリのURLのうち、/chat/の前のFQDNが、BASE URLになります。たとえば http://localhosthttps://agent.example.com などになります。
image.png

Pythonを使って、メタバース内のアバターをAIアバター化する仕組みを作る

すべての準備を終え、Pythonプログラムを用意するために、Pythonプログラムで、ここまで準備した各値を読み込むための環境変数ファイルを作成し、それからメインのプログラムをつくります。

作業用ディレクトリ(フォルダ)の作成

次のコマンドを実行します。

mkdir testapp-voiceapp
cd testapp-voiceapp

Pythonの仮想環境を用意する。

次のコマンドを実行します。

python -m venv myvenv

Pythonの仮想環境を起動

Windows環境では、次のコマンドを実行します。

.\myvenv\Scripts\Activate.ps1

Linux環境では、次のコマンドを実行します。

source myvenv/bin/activate

ライブラリのインストール

pipのアップグレード

ライブラリをインストールするツールである pip を最新版にアップグレードします。
Windows環境では、次のコマンドを実行します。

pip install --upgrade pip
python.exe -m pip install --upgrade pip

Linux環境では、次のコマンドを実行します。

pip install --upgrade pip

ライブラリのインストール

次のコマンドを実行します。

pip install ibm-watson pyaudio SpeechRecognition python-dotenv requests

環境変数ファイル .envの用意

ここまでに用意した各値をセットしています。

# --- Dify 設定 ---
## DifyのグローバルAPIキー
DIFY_API_KEY="Difyのアプリで発行したAPIキーの値" 
## 作成したChat AppのApp ID
DIFY_APP_ID="DifyのアプリURLから確認したAPP ID"
## DifyのベースURL
DIFY_BASE_URL="DifyのアプリURLから確認したBASE URL" 
## セッションを識別するためのユーザーID(任意のユニークな文字列)
DIFY_USER_ID="英数字で任意のもの"

# --- IBM Watson Speech to Text (STT) ---
## STTサービスインスタンスの資格情報を設定
WATSON_STT_API_KEY="IBM Cloudの画面で確認したWatson Speech To TextのAPIキー"
WATSON_STT_URL="IBM Cloudの画面で確認したWatson Speech To TextのAPIに接続するためのURL"
## 言語モデルの指定
WATSON_STT_MODEL="ja-JP_BroadbandModel"

# --- IBM Watson Text to Speech (TTS) ---
## TTSサービスインスタンスの資格情報を設定
WATSON_TTS_API_KEY="IBM Cloudの画面で確認したWatson Text To SpeechのAPIキー"
WATSON_TTS_URL="IBM Cloudの画面で確認したWatson Text To SpeechのAPIに接続するためのURL"
## 音声モデルの指定
WATSON_TTS_VOICE="ja-JP_EmiV3Voice" 

# --- 会話制御設定 ---
# 会話をスタートさせるためのキーワードを設定します (例: コグ、AI、ユイ など)
WAKE_WORD="AI " 

WAKE_WORDは、音声で会話する際に、AIアバターに回答して欲しい内容であるかどうか判別するためのものです。
スマートスピーカーで、「エーアイ!明日の天気について調べて」と話すように、エーアイ!の部分が、WAKE_WORDにあたります。

Pythonプログラム「conversation_agent.py」の作成

作成にあたっては、Google Gemini 2.5 Flashをサポートに使っています。また各APIのサンプルコードなどを参考にしています。一応、動くという代物なので、もっと洗練することができるかもしれませんが、今後の課題としておきます。

import os
import time
import io
import wave
import sys
import requests 

# ライブラリのインポート
from dotenv import load_dotenv
import pyaudio
import speech_recognition as sr 
from ibm_watson import SpeechToTextV1, TextToSpeechV1
from ibm_cloud_sdk_core.authenticators import IAMAuthenticator

# .envファイルから環境変数をロード
load_dotenv() 

# --- 設定と初期化 ---

# 1. 環境変数のチェックと設定
try:
    # Dify 設定
    DIFY_API_KEY = os.getenv("DIFY_API_KEY")
    DIFY_APP_ID = os.getenv("DIFY_APP_ID")
    DIFY_BASE_URL = os.getenv("DIFY_BASE_URL")
    DIFY_USER_ID = os.getenv("DIFY_USER_ID")
    
    # Watson 設定
    WATSON_STT_API_KEY = os.getenv("WATSON_STT_API_KEY")
    WATSON_STT_URL = os.getenv("WATSON_STT_URL")
    WATSON_TTS_API_KEY = os.getenv("WATSON_TTS_API_KEY")
    WATSON_TTS_URL = os.getenv("WATSON_TTS_URL")
    
    # モデル設定を環境変数から読み込み
    WATSON_TTS_VOICE = os.getenv("WATSON_TTS_VOICE")
    WATSON_STT_MODEL = os.getenv("WATSON_STT_MODEL")

    # 会話制御設定
    WAKE_WORD = os.getenv("WAKE_WORD")
    
    # 必須API認証情報とモデル設定のチェック
    if not all([DIFY_API_KEY, DIFY_APP_ID, DIFY_BASE_URL, 
                WATSON_STT_API_KEY, WATSON_STT_URL, WATSON_TTS_API_KEY, WATSON_TTS_URL,
                WATSON_TTS_VOICE, WATSON_STT_MODEL]): # モデル変数をチェック対象に追加
        raise ValueError("必須の設定が不足しています。")
        
except ValueError as e:
    print(f"致命的エラー: {e}")
    print("プログラムを終了します。")
    sys.exit(1)

# 2. 録音した音声を一時保存
TEMP_AUDIO_FILE = "user_input.wav"

# マイク録音のためのRecognizerとPyAudio
r = sr.Recognizer()
p = pyaudio.PyAudio()

# 3. サービスの初期化
try:
    # IBM Watson Speech to Text (STT) の初期化
    stt_authenticator = IAMAuthenticator(WATSON_STT_API_KEY)
    stt_service = SpeechToTextV1(authenticator=stt_authenticator)
    stt_service.set_service_url(WATSON_STT_URL) 

    # IBM Watson Text to Speech (TTS) の初期化
    tts_authenticator = IAMAuthenticator(WATSON_TTS_API_KEY)
    tts_service = TextToSpeechV1(authenticator=tts_authenticator)
    tts_service.set_service_url(WATSON_TTS_URL) 

except Exception as e:
    print(f"致命的エラー: IBM Watson サービスの初期化に失敗しました。詳細: {e}")
    p.terminate()
    sys.exit(1)


# --- 関数定義 ---

def text_to_speech(text):
    """Watson TTS を使用してテキストを音声として読み上げる (デフォルトスピーカーを使用)"""
    print(f"AI応答: {text}")
    
    try:
        response = tts_service.synthesize(
            text,
            voice=WATSON_TTS_VOICE,
            accept='audio/wav'
        ).get_result()
        
        audio_stream = io.BytesIO(response.content)
        
        with wave.open(audio_stream, 'rb') as wf:
            stream = p.open(format=p.get_format_from_width(wf.getsampwidth()),
                            channels=wf.getnchannels(),
                            rate=wf.getframerate(),
                            output=True) 
            data = wf.readframes(1024)
            while data:
                stream.write(data)
                data = wf.readframes(1024)
            
            stream.stop_stream()
            stream.close()
            
    except Exception as e:
        print(f"警告: TTS/音声再生に失敗しました。詳細: {e}")
        
def recognize_speech_from_mic():
    # ... (変更なし) ...
    """マイクから音声を録音し、WAVEファイルとして保存する (デフォルトマイクを使用)"""
    with sr.Microphone() as source: 
        r.adjust_for_ambient_noise(source)
        print("\nどうぞ、話しかけてください (終了する場合は「さようなら」)。")
        
        try:
            audio = r.listen(source, timeout=5, phrase_time_limit=10)
        except sr.WaitTimeoutError:
            print("タイムアウト: 音声入力がありませんでした。")
            return None
        
        try:
            with open(TEMP_AUDIO_FILE, "wb") as f:
                f.write(audio.get_wav_data())
            return TEMP_AUDIO_FILE
        except Exception as e:
            print(f"警告: 録音ファイル作成に失敗しました。詳細: {e}")
            return None

def watson_speech_to_text(audio_file_path):
    """Watson STT を使用して音声ファイルからテキストに変換する"""
    if not audio_file_path or not os.path.exists(audio_file_path):
        return None
        
    try:
        with open(audio_file_path, 'rb') as audio_file:
            response = stt_service.recognize(
                audio_file, 
                content_type='audio/wav',
                model=WATSON_STT_MODEL # <--- 環境変数を使用
            ).get_result()
            
            if response.get('results'):
                user_input = response['results'][0]['alternatives'][0]['transcript']
                print(f"ユーザー入力: {user_input}")
                return user_input
            else:
                print("Watson STT: 音声が聞き取れませんでした。")
                return None
    except Exception as e:
        print(f"警告: Watson STT通信または認証に失敗しました。詳細: {e}")
        return None
    finally:
        if os.path.exists(TEMP_AUDIO_FILE):
            os.remove(TEMP_AUDIO_FILE)

def get_dify_response(prompt):
    """Dify API (Chat App)にリクエストを送信し、応答を取得する"""
    
    chat_url = f"{DIFY_BASE_URL.rstrip('/')}/v1/chat-messages"
    
    headers = {
        "Authorization": f"Bearer {DIFY_API_KEY}",
        "Content-Type": "application/json"
    }
    
    payload = {
        "inputs": {}, 
        "query": prompt,
        "response_mode": "blocking", 
        "user": DIFY_USER_ID,
        "conversation_id": "" 
    }
    
    try:
        response = requests.post(chat_url, headers=headers, json=payload, timeout=60)
        response.raise_for_status() 
        data = response.json()
        
        if data.get('answer'): return data['answer'].strip()
        
        return "Difyからの応答が空でした。Appの設定を確認してください。"
        
    except requests.exceptions.RequestException as e:
        print(f"警告: Dify API通信に失敗しました。詳細: {e}")
        return "ごめんなさい、Difyサービスに接続できません。URL、APIキー、App ID、またはDifyサービスの状態を確認してください。"
    except Exception as e:
        print(f"警告: Dify応答処理に失敗しました。詳細: {e}")
        return "Dify応答を処理中にエラーが発生しました。"


# --- メイン処理 ---
def main():
    print("📢 デバイス設定: 既定の録音/再生デバイスを使用します。")
    text_to_speech("会話プログラムを開始しました。起動キーワードで話しかけてください。")
    
    while True:
        audio_path = recognize_speech_from_mic()
        
        if audio_path is None:
            time.sleep(0.5)
            continue
            
        user_text = watson_speech_to_text(audio_path)

        if user_text is None:
            time.sleep(0.5)
            continue
            
        # 起動キーワードのチェック
        processed_text = user_text.lower().strip()
        wake_word_lower = WAKE_WORD.lower().strip()

        if processed_text.startswith(wake_word_lower):
            prompt = processed_text[len(wake_word_lower):].strip()
            
            if "さようなら" in processed_text or "おわり" in processed_text:
                text_to_speech("さようなら。またお話ししましょう!")
                break
            
            if not prompt:
                ai_response_text = "何かご用でしょうか?"
            else:
                ai_response_text = get_dify_response(prompt)
                
            text_to_speech(ai_response_text)
            
        else:
            print(f"待機中: キーワード'{WAKE_WORD}'が検出されませんでした。")
            
        time.sleep(0.5)

if __name__ == "__main__":
    try:
        main()
    except KeyboardInterrupt:
        print("\nプログラムを中断しました。")
    finally:
        if 'p' in locals():
            p.terminate()

動作確認

パソコン上での準備

デフォルトのサウンドデバイスに、次の2つを指定します。

  • 出力(スピーカー):CABLE Input
  • 入力(マイク):CABLE Output

メタバース(仮想空間)の用意

例として、Vket Cloudを使っています。
メタバースサービスを使って、任意の空間を用意します。下記は、勤務先の大学で授業やイベント向けに構築した空間です。
image.png

Pythonプログラムの起動

「conversation_agent.py」を起動します。

python conversation_agent.py

実行結果

📢 デバイス設定: 既定の録音/再生デバイスを使用します。
AI応答: 会話プログラムを開始しました。起動キーワードで話しかけてください。

どうぞ、話しかけてください (終了する場合は「さようなら」)。

と表示されます。

別のアカウントで、用意したメタバース(仮想空間)に接続

例として、Vket Cloudを使っています。
別のデバイス(スマートフォンやタブレット端末など)から、用意したメタバースに接続します。
接続後、マイクをオンにし、WAKE_WORDをつけて、話しかけてみましょう。WAKE_WORDが無事に認識されれば、デモ動画のように返答を音声で返してくれます。
image.png

参考資料

1
0
1

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
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?