🙆

スキーマ駆動AIバイブス開発で駅自動放送エンジンを作った話

に公開
2026/04/20

出来上がったもの

作ったきっかけ

案内放送系音鉄として色々楽しんでいて(そこまで熱心でもない)、テンプレ型の自動放送を作ってみるかでとりあえずやってみることにした

参考は過去の産物

過去にReactで簡易だが似たようなものを作っていた
React製の品
この設定の仕様が、パーツリストと流すパーツidの配列によって作成されていたため、ここも参考にする

設定ファイルの仕様を考える

やったこと

とりあえずGemini Flashに壁打ちしつつテンプレートに必要なJSON構造、ロジックを考える
基本はパーツidの配列で、ロジックはJSONがパーツidの代わりに入る構造とした
ロジックとして他の塊を呼びつけるcomponent、指定の配列を回すloop、変数の値で分岐するswitchを決定
テンプレパーサープロトタイプをGeminiに生成させ、GitHub Copilot(Claude Sonnet 4.5)に引き継ぎ

実際のテンプレの例

{
    "main": [
        {
            "function": "component",
            "params": {
                "id": "arrival_base"
            }
        },
        {
            "function": "component",
            "params": {
                "id": "standback"
            }
        },
        {
            "function": "component",
            "params": {
                "id": "stopping_pattern"
            }
        }
    ],
    "arrival_base": [
        "mamonaku",
        "platform{platform}",
        "ni",
        "{dest}",
        "boundfor",
        {
            "function": "component",
            "params": {
                "id": "train_type"
            }
        },
        "ga",
        "{cars}car",
        "de",
        "mairimasu"
    ],
    "train_type": [
        {
            "function": "switch",
            "params": {
                "variable": "type",
                "cases": {
                    "ltdexp": ["ltdexp"],
                    "exp": ["exp"],
                    "local": ["local"],
                    "default": ["local"]
                }
            }
        }
    ],
    "standback": [
        "standbackyellowline"
    ],
    "stopping_pattern": [
        "callingat",
        {
            "function": "loop",
            "params": {
                "list_var": "stations",
                "item_template": [
                    "{item.name}",
                    {
                        "function": "switch",
                        "params": {
                            "variable": "item.is_last",
                            "cases": {
                                "true": ["desu"],
                                "false": []
                            }
                        }
                    }
                ]
            }
        }
    ]
}

開発開始

環境

とりあえず手元にあったPython3.13.2の仮想環境を作成し、使用

コーディング

パーサーを完璧にする

とりあえずパーサーが動くようにGeminiのプロトタイプをresolve.pyとして保存し、main.pyで呼びつけて動作するようCopilotで修正
Geminiが出してきたものはswitch機能でBoolの判定に問題があり、Bool部分はtolowercaseを使用し小文字文字列で判定するよう変更

再生部を作る

player.pyを作成し、main.pyから呼びつけて音声を再生できるようコードをCopilotで作成
デバイス指定に関するロジックは問題が出やすく修正に苦労した
main.pyをFastAPIでサーバーとなるようCopilotに編集させる
最低限放送を要求するエンドポイントを簡易的に自力定義し、Copilotで修正し、他に必要なエンドポイントをCopilotの判断で作成

とりあえず完成

とりあえず放送システムは完成
公開準備としてCopilotでドキュメントを作成しつつ生成コードを検閲し、問題がないことを確認
ユーザー向けにセットアップスクリプト、実行スクリプトを作成

完成

GitHubで公開

設定補助用のWebアプリを作る

色々変数が絡むのでBlocklyが適切と判断
React版での設定エディタはパーツをテキストボックスに打ち込み並べていたが、補完はあったとはいえ面倒で、Blocklyにするとそういった不便がGUIで簡便に解決すると思い、実装を検討していたためBlocklyで決定
設定ファイルの入出力をもとにCopilotで作成
出来上がったもの

感想

仕様を決めてしまえばあとはAIでもまともなものができるんだなぁと感じた
たまに素人がバイブスでセキュリティホールを含んだものに気づかずにリリースしていたり、非効率なコードを鵜呑みにしていたりする記事を目にするが、そういった部分は理解していれば起きないということで理解は重要と思った
とりあえずAIは便利

Discussion