コードから Naia モデルを利用するための開発者ガイド。 4.4 Naia モデルダウンロードに従ってモデルを実行した後、ローカルで提供される OpenAI 互換 API (ゲートウェイなし、キューなし)をそのまま利用します。あらゆる OpenAI SDK やツールで、baseURL を向けるだけです。
naia-os / シェル専用ではありません — OpenAI Realtime/Chat/Audio/Embeddings を話すあらゆるコードがそのまま接続でき、 このモデルの上に新しいアプリケーションを構築して実行できます。
1. 接続・認証
- REST ベース:
http://<host>:8892/v1(同じ PC では127.0.0.1) - Realtime(WS):
ws://<host>:8892/v1/realtime(裸のws://<host>:8892も動作 — パス/v1/realtime+ デフォルトモデルが自動適用) - 接続: ローカル(
127.0.0.1)/ Tailscale では認証は不要です — コンテナがライセンスを自己検証します。キーフィールドを必要とするクライアント(OpenAI SDK など)は任意の値(naia)を渡せます。リモートに公開する場合は、前段に §4.4 の Tailscale/VPN を置いてください。
🔑 キーは 1 つ — 購読キー
- 購読キー — ポータルから取得する購読キー。コンテナの実行時(有効化)にのみ使用します(
-e NAIA_ACCOUNT_TOKEN=<subscription-key>)。購読を確認し、期限付きライセンス(証明書)を取得します。 - 別途の接続キーはありません。 一度有効化されると、コンテナは証明書でローカルに自己検証するため、クライアント(naia-os、OpenAI SDK)は URL で接続するだけで済みます — 同じ PC では
127.0.0.1、別の端末からは Tailscale/VPN(§4.4)。接続ごとにゲートウェイを呼び出すことはありません。 - 以下の例の
api_keyはプレースホルダーです(OpenAI SDK がフィールドを要求するため)— オフラインコンテナはこれをチェックしないので、"naia"のような任意の値で動作します。
2. エンドポイント(OpenAI 互換)
| エンドポイント | 用途 |
|---|---|
GET /health | 準備状態 {"ready":true,"services":{tts,stt,llm},"vad":true}(認証なし) |
GET /v1/models | モデル一覧 |
WS /v1/realtime | リアルタイム音声セッション(VAD、バージイン、感情) |
POST /v1/chat/completions | チャット(ストリーミング) |
POST /v1/audio/speech | テキスト読み上げ(TTS) |
POST /v1/audio/transcriptions | 音声認識(STT) |
POST /v1/embeddings | 埋め込み |
Chat(curl):
curl -s http://127.0.0.1:8892/v1/chat/completions \
-H "Authorization: Bearer naia" -H "Content-Type: application/json" \
-d '{"model":"naia-0.9-omni-24g","messages":[{"role":"user","content":"hi"}],"stream":false}'
OpenAI SDK(Python)— baseURL を差し替えるだけ:
from openai import OpenAI
client = OpenAI(base_url="http://127.0.0.1:8892/v1", api_key="naia")
print(client.chat.completions.create(
model="naia-0.9-omni-24g",
messages=[{"role": "user", "content": "hi"}],
).choices[0].message.content)
文字起こし(STT):
curl -s http://127.0.0.1:8892/v1/audio/transcriptions \
-H "Authorization: Bearer naia" \
-F file=@sample.wav -F model=naia-0.9-omni-24g
3. リアルタイム音声 — 接続フロー(WS)
4.3 ライブデモと同じフローです。(オフラインはゲートウェイのキュー / 割り当てがなく、すぐに開始します。)
-
接続 —
ws://<host>:8892を開きます。 -
最初のフレーム(認証・言語) — ブラウザの WebSocket はヘッダーを送れないため、最初のメッセージとして送信します:
{ "setup": { "apiKey": "naia", "locale": "en" } } -
サーバーが
session.createdを送ってきたら、session.updateでセッションを設定します:{ "type": "session.update", "session": { "modalities": ["text", "audio"], "input_audio_format": "pcm16", "output_audio_format": "pcm16", "instructions": "<persona instructions>", "turn_detection": { "type": "server_vad" }, "input_audio_transcription": { "language": "en" }, "ref_audio_url": "<URL of a voice sample to mimic (optional)>" } } -
やり取り
クライアント → サーバー 音声入力 {"type":"input_audio_buffer.append","audio":"<base64 PCM16 24kHz>"}(サーバー VAD が発話の終わりを検出)テキスト入力 conversation.item.createの後にresponse.createバージイン response.cancelサーバー → クライアント response.audio.deltabase64 PCM16 24kHz の音声チャンク response.audio_transcript.delta/response.text.delta回答テキスト(ストリーミング) conversation.item.input_audio_transcription.completedあなたの発話の文字起こし emotion.updated感情 / プロソディタグ(§5) response.done1 ターンの終了
4. 言語 — 30 言語(デフォルト = 自動 / グローバル)
モデルは 30 言語に対応します(アラビア語、ビルマ語、中国語、デンマーク語、オランダ語、英語、フィンランド語、フランス語、 ドイツ語、ギリシャ語、ヘブライ語、ヒンディー語、インドネシア語、イタリア語、日本語、クメール語、韓国語、ラオ語、マレー語、ノルウェー語、 ポーランド語、ポルトガル語、ロシア語、スペイン語、スワヒリ語、スウェーデン語、タガログ語、タイ語、トルコ語、ベトナム語)。
- デフォルト(未設定)= グローバル / 自動 — あなたが話した言語を検出し、その言語で返答します(ターンごと)。
- 特定の言語に固定するには、
setup.localeまたはsession.updateのinput_audio_transcription.languageに ISO-639-1 コード(例:ko/en/ja)を指定します。
5. 出力フォーマット(感情・プロソディタグ)
音声会話向けに出力フォーマットが調整されています — クライアントがこれを理解していれば、より豊かに表現できます。
- プロソディタグ: 回答テキストには、感情が変化する箇所に
[laughing]、[sigh]、[breath]、[pause]、[hesitation]のような小文字の英語ブラケットタグが混在します(音声プロソディのため)。モデルは[웃음]のような韓国語タグ、(smiling)のような括弧付きのト書き、*smiles*のようなアスタリスクを使わないよう 指示されています。既知の語彙:laughing/laugh/laughter/chuckle/giggle · sigh/exhale · breath/inhale · pause · hesitation · gasp/cough/sneeze/yawn/sniff/hum · cry/sob/moan/whisper/shout/cheer(その他のタグはそのまま通過します)。 - 各タグについて、サーバーは 1:1 の
emotion.updatedイベントを送ります(state== タグ名、小文字):{ "type": "emotion.updated", "state": "laughing", "tag": "[laughing]", "known": true } - TTS パスはタグを保持し、音声プロソディのために合成に渡しますが、chat の
text.deltaはタグを取り除いたクリーンなテキストを送ります。(出力には絵文字、マークダウン、括弧付きの自己ナレーションはありません。) - クライアントのマッピング(naia-os リファレンス):
emotion.updated.state(プロソディタグ)をアバターの表情にマッピングします —laughing/chuckle/giggle/cheer → happy、sigh/exhale/cry/sob → sad、gasp → surprised、shout → angry、hesitation → think。breath・pauseのような感情を伴わないプロソディは表情を変えません (直前のものを維持 — 呼吸のたびにニュートラルに点滅しないように)。 - 堅牢な処理を推奨: LLM の出力は常に正確とは限りません。
emotion.updatedを優先しつつ、それが 欠けている場合は、文字起こしそのものの中のタグ(大文字の[HAPPY]/ 小文字のプロソディタグ)や 漏れたト書き((smiles)・*sigh*)を自動検出して表情に反映します。手がかりがなければ、 現在の表情を維持します(cf. naia-osshell/src/lib/vrm/expression.tsのextractExpression)。
6. 対話モデルの切り替え・新バージョンへの更新(運用)
コマンドラインで直接切り替えるための詳細ガイドです。個人の購読者もそのまま利用でき(キー不要)、共有・キオスク運用のためのロックオプションも含みます。かんたんな要約は 4.4 オフラインにあります。
6.1 対話モデルの切り替え(0.91 以降)
コンテナはそのままに、対話を担うモデルだけを実行中に切り替えます。音声(発話・聞き取り)とウォーターマーク、購読認証はそのまま維持されます。
まず知っておきたい 3 点:
- デフォルトモデルは内蔵のオープンLLMです。切り替えても、いつでもデフォルトに戻せます。
- 新しく載せるモデルは GGUF 形式である必要があります。また、音声機能がメモリを約 10GB 使用しているため、対話モデルはおよそ 14GB まで載せられます。それより大きいモデルは拒否され、万一読み込みに失敗しても、それまで使っていたモデルへ自動的に戻ります(対話は途切れません)。
- 個人の購読者は別途のキーは不要です。 自分のマシンの購読認証(ライセンス)がそのまま権限になるため、以下のコマンドでそのまま切り替えられます — 音声にキーが不要なのと同じです。(複数人で共有する共有・キオスクボックスでのみ、運用者が起動時に
-e NAIA_ADMIN_KEY=決めたパスワードでロックをかけることができ、その場合はリクエストに-H "Authorization: Bearer 決めたパスワード"も付けて送ります。)
実習 — アドレスだけ決めておきます:
BASE=http://127.0.0.1:8892 # 別の端末から行う場合は §4.4 の https アドレス(例: ...:8443)
① 今どのモデルで、メモリがどれだけ残っているかを見ます:
curl -s $BASE/admin/llm/status
② モデルを切り替えます — ダブルクォート内のモデル部分だけを置き換えて貼り付けてください。
HuggingFace のモデルカード URL(https://huggingface.co/Qwen/Qwen2.5-7B-Instruct-GGUF)またはその id(Qwen/Qwen2.5-7B-Instruct-GGUF)をそのまま指定すれば動きます:
curl -s -X POST $BASE/admin/llm/swap \
-H "Content-Type: application/json" \
-d '{"model":"Qwen/Qwen2.5-7B-Instruct-GGUF","pull":true}'
hf.co/ の接頭辞や量子化(quant)は自動で付与されます(デフォルトは Q4_K_M)。特定の量子化を指定したい場合は Qwen/Qwen2.5-7B-Instruct-GGUF:Q5_K_M のように後ろに記します。モデルを初めてダウンロードするときは数十秒から数分かかります。
②-オフライン — インターネットなしで、手元にある GGUF ファイルで切り替える。
展示・相談のようにインターネットがない場合は、HuggingFace から取得せず、すでに手元にある GGUF ファイルを登録して切り替えます。(見分けルール: 名前に 組織/リポジトリ のようにスラッシュがあれば HuggingFace オンライン、スラッシュのない単純な名前ならローカルモデル。)
1 行ずつコピーして貼り付けてください。mymodel の箇所に好きな名前、mymodel.gguf の箇所に実際のファイル名を記します:
podman cp ./mymodel.gguf naia-omni:/app/models/mymodel.gguf
podman exec naia-omni sh -lc 'printf "FROM /app/models/mymodel.gguf\n" > /tmp/Modelfile && ollama create mymodel -f /tmp/Modelfile'
curl -s -X POST $BASE/admin/llm/swap -H "Content-Type: application/json" -d '{"model":"mymodel:latest","pull":false}'
⚠️ 自分で変換・マージした GGUFは、チャットテンプレートが欠けていて応答が支離滅裂になったり途切れたりすることがあります。その場合は手順 2 の Modelfile にモデル系列のチャットテンプレート(
TEMPLATE)と停止トークン(PARAMETER stop)を追加して登録してください — 開発者向けの詳細は [リファレンス実装 §7]。(HuggingFace 公式の Instruct GGUF は通常内蔵されているため、そのまま使えます。)
③ デフォルトモデルに戻します:
curl -s -X POST $BASE/admin/llm/restore
共有・キオスクボックス(運用者が
NAIA_ADMIN_KEYを設定した場合)では、上記の各コマンドに-H "Authorization: Bearer 決めたパスワード"を追加してください。個人の購読者には不要です。
切り替えた後も、naia-os のようなアプリは同じアドレスのまま接続すれば大丈夫です(再接続は不要)。再起動や更新の後も引き続きそのモデルで起動したい場合は、コンテナを起動するときに -e NAIA_LLM_MODEL=Qwen/Qwen2.5-7B-Instruct-GGUF でデフォルトモデルを指定しておいてください。
6.2 新バージョンへの更新
新しいバージョンが出たら、イメージ(バージョン)だけを変えて、購読・設定はそのまま残します。 新しいバージョンを初めて起動するとき、コンテナがインターネット経由で自動的に再認証します(既存の購読・端末はそのまま — キーを手動で再入力する必要はありません)。そのため、更新するときはインターネットに接続されている必要があります。
podman pull ghcr.io/nextain/naia-0.9-omni-24g:latest # 最新バージョンを取得
podman stop naia-omni && podman rm naia-omni # コンテナだけ整理(下記の注意)
# 初回インストール時に使った実行コマンドをそのまま再実行してください — 同じライセンスボリュームをそのまま割り当てれば完了です。
⚠️ 更新するときに「端末解除(release)」を押さないでください。 解除は、使っているコンピューターを別のコンピューターに移すときにのみ使う機能です。更新しようとして解除すると、最初から認証し直す必要があります。更新はライセンスボリュームさえそのまま残せば、購読と端末登録が維持されます。
すでに認証済みのユーザーは、上記のように最新バージョンを取得して再起動するだけで、モデルを切り替えられる新しいバージョンへそのまま移行します(認証は維持)。特定のバージョンを名指しで取得したい場合は、:latest の代わりに :0.91 のようにバージョン番号を記します。
7. 関連情報
- リファレンス実装 / サンプルコード(オープンソース): naia-os の音声クライアント
shell/src/lib/voice/(Apache 2.0)— この API と通信する実際のクライアント(naia-omni.ts)と、感情 / プロソディの処理 (emotion-tags.ts; 表情マッピングと堅牢な抽出はvrm/expression.ts)が含まれています。 新しいモデルのテストや Tauri アプリ構築の出発点として利用してください。4.3 ライブデモでライブにお試しいただけます。 - ラインアップと価格: 4.1 モデル価格
- クラウド(予定): 4.6 オンライン