6
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?

【Python】VoiSona TalkのAPIを使って、ローカルLLMを喋らせてみた

Posted at

こんにちははやピーです。

外も寒くなり、鍋が恋しい季節となりました
のでこの前公開されたVoiSona Talk 外部連携API機能と、つい最近学校で習ったローカルLLMで遊んでみたいと思います。

※この記事は『ぼすきー Advent Calendar 2025』3日目の記事です。

※ついでに『Qiita全国学生対抗戦 Advent Calendar 2025』も、シリーズ8の2日枠が開いていたので飛び入り参加しています。

材料

  • Python(llama-cpp-python)
  • VoiSona Talk

環境はWindows11(CPU:Intel Core i7-9700F)です。

VoiSona Talk APIを試す

VoiSona Talkとは?

VoiSona Talkは最新のAI技術で人間の話し方をリアルに再現する音声創作ソフトウェアです。

つい最近、待望の外部連携API機能(ベータ版)が公開され、音声合成機能を別のアプリからアクセスすることが可能になりました。

API初期設定

チュートリアルを元に、APIを有効化させます。認証用のパスワードはAPI用のものなので、VoiSonaのアカウントのパスワードである必要はないです。

また、VoiSona Talkのヘルプ>talk API リファレンスより、APIについて詳しいリファレンスを確認することができます(localhostを参照するので、アプリを起動する必要があります)。

喋らせる

早速VoiSonaのキャラたちを喋らせていきたいと思います。
ソースコードは...あんま時間がないので....Geminiに書いてもらいました....。

main.py
import requests
import json

def speak(content):
    # --- 設定情報 ---
    url = "http://localhost:32766/api/talk/v1/speech-syntheses"
    username = "hogehoge@example.com"
    password = "hogehoge"

    # --- 送信するJSONデータ (Pythonの辞書形式) ---
    payload = {
        "force_enqueue": True,
        "destination":"audio_device",
        "language": "ja_JP",
        "text": content,
    }

    # --- requestsモジュールを使ったPOSTリクエストの実行 ---
    try:
        response = requests.post(
            url,
            auth=(username, password),
            json=payload,
            timeout=10 # 任意: タイムアウトを設定
        )

        # --- レスポンスの確認 ---

        # ステータスコードのチェック (200番台以外は例外を発生)
        response.raise_for_status()

        print(f"✅ リクエスト成功!")
        print(f"ステータスコード: {response.status_code}")

        # サーバーからのレスポンスボディを表示
        try:
            print("レスポンスJSON:")
            print(json.dumps(response.json(), indent=4, ensure_ascii=False))
        except requests.exceptions.JSONDecodeError:
            print("レスポンステキスト (JSON形式ではない):")
            print(response.text)

    except requests.exceptions.HTTPError as errh:
        print(f"HTTPエラーが発生しました: {errh}")
    except requests.exceptions.ConnectionError as errc:
        print(f"接続エラーが発生しました: {errc}")
    except requests.exceptions.Timeout as errt:
        print(f"タイムアウトエラーが発生しました: {errt}")
    except requests.exceptions.RequestException as err:
        print(f"その他のエラーが発生しました: {err}")

speak("こんにちは")

実行すると、私の推し「双葉湊音」さんが「こんにちは」と発音しました!!!かわいい!!!!!!
音声ライブラリはvoice_nameで指定しますが、省略した場合、適当なボイスライブラリで喋ってくれます(不明

ちなみに、ボイスキャラの設定周辺は動作が不安定らしく、特定のボイスに喋ってもらいたい場合はvoice_nameだけでなくvoice_versionも設定するとうまくいきます。

destinationaudio_deviceにするとPCの音声として出力され、fileに設定するとwavファイルとして出力されます。

fileに設定した場合、output_file_pathで出力先の絶対パスを指定する必要があります。このとき、.wav拡張子までつける必要があります。(1敗

これ以外にもいろんなパラメーターを設定できるらしいです。詳しくはAPIドキュメントを参照してください。

ボイスライブラリ情報の取得

使用できるボイスライブラリの取得もできるようです。

voices.py
import requests
import json

# --- 設定情報 ---
url = "http://localhost:32766/api/talk/v1/voices"
username = "hogehoge@example.com"
password = "hogehoge"

# --- requestsモジュールを使ったGETリクエストの実行 ---
try:
    response = requests.get(
        url,
        auth=(username, password),
        timeout=10 # 任意: タイムアウトを設定
    )

    # ステータスコードのチェック (200番台以外は例外を発生)
    response.raise_for_status()

    print(f"✅ リクエスト成功!")
    print(f"ステータスコード: {response.status_code}")

    # レスポンスボディを表示
    try:
        # レスポンスがJSON形式の場合、きれいに整形して表示
        print("レスポンスJSON:")
        print(json.dumps(response.json(), indent=4, ensure_ascii=False))
    except requests.exceptions.JSONDecodeError:
        # JSON形式ではない場合、テキストとして表示
        print("レスポンステキスト (JSON形式ではない):")
        print(response.text)

except requests.exceptions.HTTPError as errh:
    print(f"HTTPエラーが発生しました: {errh}")
except requests.exceptions.ConnectionError as errc:
    print(f"接続エラーが発生しました: {errc}")
except requests.exceptions.Timeout as errt:
    print(f"タイムアウトエラーが発生しました: {errt}")
except requests.exceptions.RequestException as err:
    print(f"その他のエラーが発生しました: {err}")
実行結果(例)
{
   "items": [
       {
           "display_name": [
               {
                   "language": "ja_JP",
                   "name": "双葉湊音"
               },
               {
                   "language": "en_US",
                   "name": "Futaba Minato"
               }
           ],
           "languages": [
               "ja_JP"
           ],
           "voice_name": "futaba-minato_ja_JP",
           "voice_version": "2.0.2"
       }
   ]
}

LLMの準備

LLMとはLarge Language Modelの略で、生成AIについてテキスト生成に特化したモデルのことです。

今回はPythonライブラリのllama-cpp-pythonを使って、LLMをローカル環境で動かします。

こいつのインストールにはCmakeやVisual Studio Build Toolsなどのインストールが必要ですので、各自調べながら行ってください。またGPU推論を行う場合もCudaのインストールなどが必要になります(やってみましたがうちのPCだとうまく動かないので、今回はCPU推論を行います)。

また、今回モデルとしてgoogle/gemma-2-2b-jpn-itをベースにしたAXCXEPT/EZO-gemma-2-2b-jpn-itを用います。

LLMの使用には.ggufという形式に変換した、量子化モデルを使用します。HuggingFaceのモデルからの変換が必要なのですが、だいたいは有志の方がやってくれているので、私たちは感謝をしながらダウンロードします。

requirements.txt
langchain
langchain-community
llama-cpp-python
ターミナル
python -m venv venv
.\venv\Scripts\activate
pip install -r requirements.txt

※ソースコードの一部はGeminiに書いてもらった記憶があります。

llm.py
from pydantic import BaseModel
from langchain_community.llms import LlamaCpp

# ==== モデル設定 ====
MODEL_NAME = "EZO-gemma-2-2b-jpn-it.Q4_K_M.gguf"

# LlamaCppモデルを初期化
llm = LlamaCpp(
    model_path=MODEL_NAME,
    n_ctx=8192,
    max_tokens=0,
    use_mlock=True,
)

prompt = "あなたは合成音声キャラクター「双葉湊音」です。自然の多いところに住んでいる15歳の少女。夏が好き。\nあなたは、以下の対話に対する応答を生成してください。\n対話:"

while(True):
    userPrompt = input("prompt:")
    # モデル実行
    result = llm.invoke(prompt + userPrompt +"\n出力:")
    print("minato:" + result)

以前使った際に大量の文章を処理させていたため、トークンの最大数(n_ctx)が限界まで大きく設定されています。必要に応じて、各自で調整してください。

実行例は以下の通りです。

prompt:おはよ~
minato:「おはよう、元気?」
prompt:うん、元気。昨日のテレビ見た~?
minato:「そうだね、見てたよ。面白かったな。」
prompt:ねぇ今日の放課後さ、空いてる?
minato:「もちろんです、ちょっとだけ。」

上には記載していませんが、実際に実行するとLLM実行のログが合間に表示されることがあります。特に問題はないので気にしないでください。

文章は数秒で生成されます。今回はこのあと生成した文章をVoiSonaに喋ってもらうということで、私の推し「双葉湊音」さんっぽく振る舞うよう簡単なプロンプトを書いてみました。軽量LLMにしてはなかなかの出来ではないでしょうか。

本題

main.pyllm.pyの内容を付け加えた上で、プロンプトをちょっとだけ修正し、llmの返答をspeak()させます。

main.py
prompt = "あなたは合成音声キャラクター「双葉湊音」です。自然の多いところに住んでいる15歳の少女。夏が好き。\nあなたは、幼馴染から話しかけられています。対話に対する応答を1文生成し、文章のみを出力してください。解説や「回答:」などは不要です。\n"

while(True):
    userPrompt = input("prompt:")
    # モデル実行
    result = llm.invoke(prompt + userPrompt +"\n出力:")
    print("minato:" + result)
    speak(result)

普段小説とか書いていたらプロンプト書くのも得意に鳴るんでしょうが、今回は時間もないのでこんな適当なプロンプトで実行します。また、LLMが長文生成すると時間がかかることや、変に解説を始めることがあるので、1文のみの生成を指示しました。

prompt:おはよ~湊音、今日も眠いね...
minato:
「おはようございます、元気?」

おおおしゃべった〜〜〜!!!!!

prompt:ねぇねぇ湊音~、sin3xを微分すると3cos3xになるのってなんで~?
mianto:
えー、ちょっと難しいな…。」

「ちょっと難しいな...」←可愛い

prompt:今日の放課後さ、屋上に来てほしいの。
minato:
ああ、屋上にきたら、ちょっと涼しく感じるんじゃないかな。」

その通りです...。

prompt:湊音はさ、なんで夏が好きなの?
mianto:
「夏の太陽が、すごく気持ちいいから。」

終わりに

記事なのでなかなか魅力は伝わりづらいですが、かなりいい感じです。やっぱ推しが自らの意思を持って(?)話してくれるととてもワクワクしますね。また、無調声の音声ではあるものの、ある程度自然に話してくれました。あとはプロンプトが適当すぎるので、そこだけ直したらもっと面白い感じになりそうです(言い訳:時間がなかった)。

また、過去の会話のログは保存されないので、その点も改善できたらもっと面白い会話ができそうです。

VoiSona TalkのAPIを使って何かやりたい!と思って、適当にLLMで動かしてみましたが、これ以外にもいろいろな場面で活用できそうです。暇になったらやってみたいです。

6
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
6
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?