Alexa TTS API 実装記録 (2026-03-02)

Alexa TTS API マスタードキュメント

最終更新: 2026-03-03 状態: サーバーからの日本語TTS未解決(調査中)
------------
2026/03/03 10:24 akira記録
akiraが下記の変更をしましたので、内容を読んでください。

1) 構成とサーバーへのファイル受け渡し方法を変更しました
/home/claude/windmill_workflow
に、https://gitea.keinafarm.net/akira/windmill_workflow.gitをcloneしました
これにより、
C:\Users\akira\Develop\windmill_workflow
とのやり取りはgiteaを使って出来るようになります。

2) docker compose up -dで、「 Docker 内部の IP アドレスが変わり、Traefik のルーティングが古い IP を参照したまま 502/504 エラーになる」のは、原因があるはずです。(他のコンテナで、問題になっていないので)
調査して、Traefik 再起動が不必要になるようにしたいです



------------


概要

Windmill から家中の Echo デバイスに任意のテキストを読み上げさせる API サーバー。Node.js + Express で実装し、Docker コンテナとして Windmill サーバー上で稼働する。

⚠ 現在の問題: ローカル PC からは日本語TTS動作確認済み。しかしサーバー(keinafarm.net)のコンテナからリクエストすると日本語文字が Amazon 側で除去されて発話されない。原因は Amazon の IP ベースフィルタリング(海外IPからは日本語 textToSpeak を無視する模様)。調査継続中。

ファイル構成

ファイル 場所 役割 備考
server.js alexa-api/(リポジトリ) Express API サーバー本体 本番コード。変更したらビルド・再デプロイが必要
Dockerfile alexa-api/(リポジトリ) Docker イメージ定義 node:20-alpine ベース。server.js と package*.json をコピー
docker-compose.yml alexa-api/(リポジトリ) コンテナ起動設定 windmill_windmill-internal ネットワーク接続。外部ポート非公開
package.json / package-lock.json alexa-api/(リポジトリ) npm 依存関係 本番: express のみ。devDeps に alexa-remote2(不使用)
.env.example alexa-api/(リポジトリ) 環境変数テンプレート ALEXA_COOKIE=xxx の形式
.env alexa-api/(リポジトリ、.gitignore 対象) 実際の Cookie 保管 Git にコミットしない。ローカル作業後に scp でサーバーへ転送
auth4.js alexa-api/(リポジトリ) Amazon 認証・Cookie 取得スクリプト ローカルのみで実行(Windows PC)
auth.js / auth2.js / auth3.js alexa-api/(リポジトリ) auth4.js の旧バージョン 参考用。実際は auth4.js を使う
test_tts.js alexa-api/(リポジトリ) ローカルテスト用スクリプト 直接 alexa.amazon.co.jp を叩いて動作確認

サーバー上のファイル場所: /home/claude/alexa-api/(git リポジトリとは別にコピーして管理)

サーバーへのデプロイ手順

server.js や Dockerfile、package.json を変更した場合は以下の手順でサーバーに反映する。

Step 1: ローカルでファイルを編集

リポジトリ(c:\Users\akira\Develop\windmill_workflow\alexa-api\)でファイルを編集する。

Step 2: scp でサーバーに転送

変更したファイルをサーバーに scp で転送する:

# server.js を変更した場合 scp alexa-api/server.js keinafarm-claude:/home/claude/alexa-api/server.js # Dockerfile や package.json を変更した場合 scp alexa-api/Dockerfile keinafarm-claude:/home/claude/alexa-api/Dockerfile scp alexa-api/package.json keinafarm-claude:/home/claude/alexa-api/package.json scp alexa-api/package-lock.json keinafarm-claude:/home/claude/alexa-api/package-lock.json # .env を更新した場合(Cookie 更新時など) scp alexa-api/.env keinafarm-claude:/home/claude/alexa-api/.env

Step 3: サーバーでビルドして再起動

⚠ 重要: docker compose restart はイメージをリビルドしない。server.js 等を変更した場合は必ず build + up -d を実行すること。

# SSH でサーバーに接続してビルド+起動 ssh keinafarm-claude cd /home/claude/alexa-api # イメージをビルド(server.js 等の変更を反映) sudo docker compose build # コンテナを再作成して起動 sudo docker compose up -d # Traefik を再起動(コンテナ再作成後は必須) sudo docker restart traefik

Step 4: 動作確認

# ヘルスチェック(Windmill ワーカーコンテナ内から) curl http://alexa_api:3500/health # ログ確認 sudo docker logs alexa_api -f # デバイス一覧確認 curl http://alexa_api:3500/devices

Cookie だけ更新する場合(server.js 変更なし)

# .env をサーバーに転送 scp alexa-api/.env keinafarm-claude:/home/claude/alexa-api/.env # コンテナを再起動(restart で OK → イメージのリビルド不要) ssh keinafarm-claude 'sudo docker compose -f /home/claude/alexa-api/docker-compose.yml restart' # ※ Traefik の再起動は不要(コンテナ再作成しないため)

Traefik 再起動が必要な理由

docker compose up -d はコンテナを「再作成」する(docker compose restart は既存コンテナを再起動するだけ)。コンテナが再作成されると Docker 内部の IP アドレスが変わり、Traefik のルーティングが古い IP を参照したまま 502/504 エラーになる。

対処: sudo docker restart traefik で Traefik に新しい IP を再検出させる。

この問題は Traefik の設定で watch: true にすれば自動解消できるが、現状はコンテナ再作成のたびに手動で Traefik を再起動する運用としている。

docker-compose.yml の内容

services:  alexa-api:    build: .    container_name: alexa_api    restart: unless-stopped    env_file:      - .env    environment:      - PORT=3500    networks:      - windmill_windmill-internal    # 外部には公開しない(Windmill ワーカーから内部ネットワーク経由でのみアクセス)    # デバッグ時は以下のコメントを外す:    # ports:    #   - "127.0.0.1:3500:3500" networks:  windmill_windmill-internal:    external: true

認証方法(auth4.js)

Amazon Japan OpenID フローを自前で実装。ローカル PC(Windows)でのみ実行する:

# ローカルPC の alexa-api ディレクトリで実行 cd alexa-api AMAZON_EMAIL="メールアドレス" AMAZON_PASSWORD="パスワード" node auth4.js

成功すると alexa-api/.env が生成または更新される。

ログインフローの概要:

  1. GET https://www.amazon.co.jp/ap/signin?openid.assoc_handle=amzn_dp_project_dee_jp
  2. hidden フィールド(anti-csrftoken-a2z, appActionToken, workflowState 等)を抽出
  3. POST でメール/パスワードを送信
  4. alexa.amazon.co.jp/api/apps/v1/token へのリダイレクトをたどる
  5. 取得した Cookie(at-acbjp, sess-at-acbjp, sst-acbjp, session-token 等)を .env に保存

TTS の仕組み(server.js)

alexa-remote2 は使わない直接 API 実装。Endpoints:

内部の API 呼び出し順序:

  1. GET /api/language → Set-Cookie: csrf=XXXXX を取得(毎リクエストごと)
  2. GET /api/bootstrap → customerId を取得(キャッシュ: 永続)(A1AE8HXD8IJ61L)
  3. GET /api/devices-v2/device → デバイス一覧(5分キャッシュ)
  4. POST /api/behaviors/preview にシーケンス JSON を送信

POST /api/behaviors/preview のボディ構造:

{  behaviorId: "PREVIEW",  sequenceJson: JSON.stringify({    "@type": "com.amazon.alexa.behaviors.model.Sequence",    startNode: {      "@type": "com.amazon.alexa.behaviors.model.OpaquePayloadOperationNode",      type: "Alexa.Speak",      operationPayload: {        deviceType: "...",        deviceSerialNumber: "...",        customerId: "A1AE8HXD8IJ61L",        locale: "ja-JP",    // ← 重要(下記参照)        textToSpeak: "発話内容"      }    }  }),  status: "ENABLED" }

ヘッダーに csrf: XXXXX と Cookie に csrf=XXXXX の両方が必要。Content-Length は Buffer.byteLength で計算(マルチバイト文字対応)。

⚠ locale パラメータについて(重要・未解決)

locale 値 ローカル PC から サーバー(keinafarm.net)から
""(空文字) ✅ 日本語・英語・漢字全て発話 ❌ 英語TTSになり日本語部分が発話されない
"ja-JP" ❌ 一瞬音が出るだけ(失敗) ❌ 日本語文字が Amazon 側で除去され英字のみ発話

現在 server.js では locale: "ja-JP" に設定している。

仮説: Amazon が海外IP(keinafarm.net = 非日本IP)からのリクエストを IP ベースでフィルタリングし、textToSpeak の日本語文字を除去している。Alexa.TextCommand は同じ問題がない(異なる API パス)。

確認済み事実: alexa_api の server.js ログには日本語テキストが正しく届いている。除去は Amazon サーバー側で発生。

次の調査候補:

デバイス一覧(Echo デバイスのみ)

名前 deviceType serialNumber
プレハブ A4ZXE0RM7LQ7A G0922H085165007R
リビングエコー1 ASQZWP4GPYUT7 G8M2DB08522600RL
リビングエコー2 ASQZWP4GPYUT7 G8M2DB08522503WF
オフィスの右エコー A4ZXE0RM7LQ7A G0922H08525302K5
オフィスの左エコー A4ZXE0RM7LQ7A G0922H08525302J9
寝室のエコー ASQZWP4GPYUT7 G8M2HN08534302XH

Windmill スクリプト(u/admin/alexa_speak)

export async function main(device: string, text: string) {  const res = await fetch("http://alexa_api:3500/speak", {    method: "POST",    headers: { "Content-Type": "application/json" },    body: JSON.stringify({ device, text }),  });  if (!res.ok) throw new Error("alexa-api error " + res.status);  return res.json(); }

device はデバイス名(日本語)またはシリアル番号で指定可能。Windmill ワーカーから http://alexa_api:3500 でアクセス(windmill_windmill-internal ネットワーク経由)。

Cookie の更新手順

Cookie は数日〜数週間で期限切れ。切れたら:

# 1. ローカル PC で Cookie を取得 cd alexa-api AMAZON_EMAIL="メールアドレス" AMAZON_PASSWORD="パスワード" node auth4.js # → alexa-api/.env が更新される # 2. サーバーに .env を転送 scp alexa-api/.env keinafarm-claude:/home/claude/alexa-api/.env # 3. コンテナを再起動(restart で OK、リビルド不要) ssh keinafarm-claude 'sudo docker compose -f /home/claude/alexa-api/docker-compose.yml restart' # ※ Traefik 再起動は不要(コンテナ再作成なし)

既知の問題・落とし穴

サーバー上の運用コマンド一覧

# コンテナ状態確認 sudo docker ps | grep alexa # リアルタイムログ確認 sudo docker logs alexa_api -f # コンテナ停止 sudo docker compose -f /home/claude/alexa-api/docker-compose.yml stop # ビルド+起動(コード変更後) cd /home/claude/alexa-api sudo docker compose build sudo docker compose up -d sudo docker restart traefik # Cookie 更新時(再起動のみ) sudo docker compose -f /home/claude/alexa-api/docker-compose.yml restart