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?

パソナX-TECHAdvent Calendar 2024

Day 7

Azure OpenAI ServiceのGPT-4o Realtime Audioをdocker環境で動かしてみる

Last updated at Posted at 2024-12-10

こちらの記事は2024/12/09時点の内容となります。

はじめに

プレビュー段階ではありますが、Azure OpenAI ServiceのGPT-4o Realtime Audioが使用できるようになりました。
GPT-4oモデルの一種であり、音声での対話が低遅延で行えるモデルです。

これまでは、音声入力した内容を文字起こしする → 生成AIに回答させる → 回答を音声化する という手順を踏む必要があったのですが、このタイムラグが軽減されることになるため、音声を使って生成AIとやりとりするプロダクトには欠かせない存在になりそうです。

やりたいこと

aoai-realtime-audio-sdk のPythonサンプルコードをvenvではなくdocker環境で動かすことが目標です。

README.mdを見てみると、venvを使った環境構築手順が紹介されています。
私が関わっている案件ではdockerを使った開発がメインであるため、dockerで動かせるように調整してみたいと思います。

やらないこと

公式の手順で構築したものとの完全な比較は行いません。
最低限、AOAIの gpt-4o-realtime-preview モデルとやり取りし、結果が返ってくるところまでを目指します。
また、この記事は 2024/12/09時点 の内容であり、積極的に更新は行わない方針ですのでご了承ください。

(前提)docker環境について

今回構築する環境は、python3.11 + FastAPI が動作するdocker環境を想定しています。
具体的な内容は下記を参考にしてください。

⚠️ 抜粋して記載しているため、このまま実行しても動作しませんのでご了承ください。

ディレクトリ構成

.
├── docker_images/
│   └── app/
│       └── Dockerfile
├── src/
│   ├── bff/                        # サーバサイドのルート
│   │   ├── api/
│   │   │   └── api_v1/
│   │   │       ├── endpoints/
│   │   │       │    └── sample.py  # APIの処理を記述
│   │   │       └── api.py          # APIのルーティング処理を記述
│   │   ├── core/
│   │   │    └── config.py          # サーバサイドの基本設定・定数の管理
│   │   ├── public/                 # フロントエンドのビルド結果が格納される
│   │   ├── schemas/                # APIのリクエスト・レスポンスの型定義
│   │   ├── services/               # APIが呼び出すビジネスロジックを記述
│   │   ├── utils/                  # Azureサービスの操作・汎用的な処理など
│   │   ├── main.py                 # サーバサイドで最初に呼び出される
│   │   └── requirements.txt
│   └── frontend/                   # フロントエンドのルート
│                                    (※)フロントエンドのコードは今回の本題から逸れるため割愛
├── .env                            # 環境変数を格納するファイル(gitignoreすること!)
├── compose.yml
└── Makefile

主要ファイル

docker_images/app/Dockerfile
ARG DISTROLESS_VER

FROM python:3.11-slim AS builder

WORKDIR /app/src

COPY src/bff/ ./

RUN set -x \
 && apt update -y \
 && apt upgrade -y \
 && \
 : "package install" \
 && pip --no-cache-dir install --upgrade pip \
 && mv requirements.txt ../ \
 && cd .. \
 && pip install --no-cache-dir --target py-packages -r requirements.txt \
 && \
 : "権限を変更" \
 && chown -R 1000:1000 /app

FROM gcr.io/distroless/python3:${DISTROLESS_VER}

WORKDIR /app/src
COPY --from=builder /app ../
ENV PYTHONPATH=/app/py-packages

EXPOSE 8000

CMD ["/app/py-packages/bin/uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]

src/bff/api/api_v1/api.py
from api.api_v1.endpoints import sample
from fastapi import APIRouter

api_router = APIRouter()
api_router.include_router(sample.router, prefix="", tags=["sample"])

src/bff/core/config.py
from pydantic_settings import BaseSettings

class Settings(BaseSettings):
    API_V1_STR: str = "/api/v1"

    class Config:
        case_sensitive = True


settings = Settings()

src/bff/main.py
from api.api_v1.api import api_router
from fastapi import FastAPI
from core.config import settings

app = FastAPI()

app.include_router(api_router, prefix=settings.API_V1_STR)
app.mount("/", StaticFiles(directory="public", html=True), name="static")

src/bff/requirements.txt
fastapi==0.115.*
uvicorn==0.32.*
pydantic-settings==2.6.*
compose.yml
services:
  app:
    build:
      context: .
      dockerfile: ./docker_images/app/Dockerfile
      args:
        DISTROLESS_VER: "debug-nonroot"
    container_name: "sample-app"
    volumes:
      - ./src/bff:/app/src:cached
    command: "/app/py-packages/bin/uvicorn main:app --host 0.0.0.0 --port 8000 --reload"
    ports:
      - 8000:8000
    tty: true
    env_file:
      - .env

aoai-realtime-audio-sdkから必要なものを移植する

ようやく本題です。
aoai-realtime-audio-sdk のPythonサンプルコードのうち、/python/samples/client_sample.py を動かすことを考えます。

1. client_sample.pyを移植

今回のディレクトリ構成の場合、API内で実行するビジネスロジックは src/bff/services/ へ置くという決まりにしているので、ここに /python/samples/client_sample.py の内容をそのまま移植します。

...
├── src/
│   ├── bff/   
│   │   └── services/
│   │       └── client_sample.py   # client_sample.pyをそのまま移植
...

2. 環境変数を追加

client_sample.pywith_azure_openai メソッドで、下記の環境変数が呼ばれています。

    endpoint = get_env_var("AZURE_OPENAI_ENDPOINT")
    key = get_env_var("AZURE_OPENAI_API_KEY")
    deployment = get_env_var("AZURE_OPENAI_DEPLOYMENT")

これらに相当する値を .env ファイルに追加しておきます。
⚠️ このファイルはgitignoreするなどして適切に管理するようにしてください。

3. rtclientを移植

さて、client_sample.py の内容を眺めると、下記のようなimportがあります。

aoai-realtime-audio-sdk/python/samples/client_sample.py
from rtclient import (
    InputAudioTranscription,
    RTAudioContent,
    RTClient,
    RTFunctionCallItem,
    RTInputAudioItem,
    RTMessageItem,
    RTResponse,
    ServerVAD,
)

どうやら /python/rtclient の内容をimportしているようです。
必要そうなので、このディレクトリも丸ごと移植します。

...
├── src/
│   ├── bff/   
│   │   └── rtclient/   # 丸ごと移植
...

4. requirements.txtを移植

追加でライブラリが必要になりそうな予感がするので /python/samples/requirements.txt を確認してみたところ、下記の4つのライブラリが含まれていました。

aoai-realtime-audio-sdk/python/samples/requirements.txt
python-dotenv
soundfile
numpy
scipy

環境変数の展開は compose.yml でまとめて行いたいので、python-dotenv は今回追加しないでおきます。

src/bff/requirements.txt
fastapi==0.115.*
uvicorn==0.32.*
pydantic-settings==2.6.*
soundfile
numpy
scipy

5. APIの追加

パッと移植できるところは移植してしまったので、とりあえずAPIが実行できるように下記のコードを追加します。

src/bff/api/api_v1/endpoints/sample.py
from fastapi import (APIRouter, File, Form, UploadFile)
from fastapi.responses import JSONResponse
from services.client_sample import with_azure_openai

router = APIRouter()

@router.post("/process_audio/")
async def process_audio(audio_file: UploadFile = File(...), out_dir: str = Form(...)):
    temp_file_path = f"/tmp/{audio_file.filename}"
    with open(temp_file_path, "wb") as temp_file:
        temp_file.write(await audio_file.read())

    if not os.path.exists(out_dir):
        os.makedirs(out_dir)

    try:
        await with_azure_openai(temp_file_path, out_dir)
    except Exception as e:
        return JSONResponse(status_code=500, content={"message": str(e)})

    return {"message": "Processing completed successfully"}

6. 環境立ち上げ & エラー解消

docker compose up --build コマンドでdocker環境を立ち上げます。
下記のようなエラーが出ました。

...
   File "/app/src/services/interview.py", line 10, in <module>
     from azure.core.credentials import AzureKeyCredential
 ModuleNotFoundError: No module named 'azure'

調べてみると、azure-core というライブラリが必要なようです。
src/bff/requirements.txt に追加して再度docker環境を立ち上げると、次のエラーが出ました。

...
   File "/app/src/rtclient/low_level_client.py", line 10, in <module>
     from aiohttp import ClientSession, WSMsgType, WSServerHandshakeError
 ModuleNotFoundError: No module named 'aiohttp'

aiohttp も追加して再度docker環境を立ち上げます。
二度あることは三度ある。またエラーが出ました。

...
 RuntimeError: Form data requires "python-multipart" to be installed.
 You can install "python-multipart" with:

 pip install python-multipart

ご丁寧に python-multipart をインストールせよとのことなので、これもsrc/bff/requirements.txt に追加します。
ここまで対応すると、ようやくdocker環境が正常に起動しました!

最終的に src/bff/requirements.txt はこのような状態になりました。
それぞれ最新バージョンの指定を追加してあります。

src/bff/requirements.txt
fastapi==0.115.*
uvicorn==0.32.*
pydantic-settings==2.6.*
soundfile==0.12.1
numpy==2.1.3
scipy==1.14.1
azure-core==1.32.0
aiohttp==3.11.10
python-multipart==0.0.19

本来であれば公式の手順に従って .whl ファイルを展開すべきところを、無理やり強行突破したような感じです。
sdkに含まれる他の機能も試す場合は、更に必要なライブラリが出てくるかもしれません。

7. 実行

APIの実行はFastAPIの 自動ドキュメント生成 の機能を使って行います。
前段でdocker環境が起動していれば、http://localhost:8000/docs にアクセスすることでSwagger UIの画面が表示されます。

記事内のAPIパスと若干異なりますが、下記のようなイメージで無事に実行確認することができました👏

スクリーンショット 2024-12-10 2.09.00.png
スクリーンショット 2024-12-10 2.09.16.png

リクエストに使うファイルは、 aoai-realtime-audio-sdk/python/samples/input 内にあったファイルを拝借しました。
また、リクエストとして設定した out_dir 内には 結果ファイル(.wav.txt の2種類)が出力されていました。
.wav の音声を文字起こしした内容が .txt に反映されているようです。

さいごに

とりあえず動かすところまでは確認できたので、今後はフロントエンドとの繋ぎこみやストリーミングに対応していきたいと思います。
API自体がプレビュー段階なので、今後SDKもより使いやすいものになっていくかな…?と期待しています。

参考文献

下記の記事を参考にさせていただきました!ありがとうございます。

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?