フロントエンドを中心にエンジニアをしている赤星です。
今回は私が個人開発をしていた際、LLMへのプロンプトを自然言語ではなく、TypeScriptで記述してみたところ、正確性が向上し、一定の効果を実感したのでその内容を記事にしていきます。
プログラミング言語でプロンプトを書こうとしたきっかけ
「頭が赤い魚を食べる猫」問題
みなさんは「頭が赤い魚を食べる猫」という文章をご存知でしょうか?
こんばんは。「頭が赤い魚を食べる猫」の考案者です。最近「頭が赤い魚を食べる猫」を再びツイッター上で話題で話題にしていただけているようなので、再掲しておきますね。 pic.twitter.com/hZLOBMOtW3
— 中村明裕 (@nakamurakihiro) 2020年2月21日
これはどこで文章を区切り、まとめるかで意味が異なってくる文章です。
例えば「頭が赤い魚」を食べる猫とするか「頭が赤い」魚を食べる猫とするかで
- 魚の頭が赤いのか
- 猫の頭が赤いのか
と解釈が変わります。
私はこの問題が、LLMにおいても起こりうるのではないかと考えました。
実際に画像生成AIで試してみる
実際に「頭が赤い魚を食べる猫」というプロンプトをGrok, ChatGPT, Microsoft Designerに送ってみました。すると、それぞれ以下の結果が返ってきました。
GrokとChatGPTは「魚の頭が赤い」と解釈したようですが、Microsoft Designerは「猫の頭が赤い」と解釈したことが分かります。
このように、解釈の曖昧さという問題はAIにおいても起こりえます。
解決のアプローチ
そこで、私はプロンプトをプログラミング言語のように指示すれば良いのでは?と考えました。プログラミング言語は構造化されているため、解釈の曖昧さは起こりにくいと考えられます。そして「どのような順番でAIに動いてもらうか」、「何か問題に当たった時の条件分岐」を正確に書くことができます。実際に、画像生成AIや動画生成AIを使いこなしている方々はJSONでプロンプトを書いているようです。
そこで、私はちょうどGemini CLIの試用ついでに競馬予想プログラムを作らせていたところだったので、作らせたプログラムの評価部分をTypeScriptで指示することとしました。
書いたプロンプトとその解説

基本動作
const 設定 = { 言語: "ja", 基本設定: "修正をする際は人間の判断を仰がず、勝手に判断して修正をすること", }; new GeminiCLI(設定); while (true) { const 仮評価で利益が出たか = プログラムの評価をする(80); if (!仮評価で利益が出たか) continue; console.log("仮評価で利益が出ました。本評価に進みます。"); const 本評価で利益が出たか = プログラムの評価をする(138); if (本評価で利益が出たか) { console.log( "本評価で利益が出ました。プログラムは成功しました。" ); break; } }
new GeminiCLI(設定)でGemini CLIに対して基本設定を教えさせます。
その後、仮評価→本評価の順番で利益が出るまで評価手順を繰り返させます。
プログラムの評価部分
const 過去のレースのURL一覧: string[] = ファイルを読み込む("pastRace.txt"); function プログラムの評価をする(レース数: number): boolean { const 収支: number = シミュレーションをする( 過去のレースのURL一覧.slice(0, レース数) ); if (収支 > 0) return true; 評価アルゴリズムを見直し修正をする(); 動作確認および修正をする(); return false; }
プログラムの評価をする関数の詳細です。
レース数を引数にとり、過去のレースを指定レース数分シミュレーションさせます。(シミュレーションをする関数等、呼び出し先が存在しない関数は関数名自体がプロンプトとなっています)
この際、収益が発生すればtrueを返し、そうでないなら評価アルゴリズムの修正と動作確認をします。
評価アルゴリズムの修正部分
function 評価アルゴリズムを見直し修正をする(): void { const 見直すためにLLMが読む参考資料: string[] = [ "advice1.md", "advice2.md", "スコアリングアルゴリズムにて加算される数字を見直す", "shutuba_XXXXXX.csv(shutuba_202403010511.csv等)に記載されているまだ使われていないカラムを活用する", "競馬の賭け方のコツをGoogle検索しヒットしたサイトを読んで理解する" ]; const 修正内容: string = 評価アルゴリズムを見直す(見直すためにLLMが読む参考資料); console.log("評価アルゴリズムを見直しました:", 修正内容); 見直した評価アルゴリズムを元に修正をする(修正内容); const ファイルデータ: string[] = ファイルを読み込む("teian.txt"); const GEMINIMDのファイルデータ: string[] = ファイルを読み込む("GEMINI.md"); ファイルデータ.push(修正内容); ファイルに書き込む("teian.txt", ファイルデータ); GEMINIMDのファイルデータ.push(修正内容); ファイルに書き込む("GEMINI.md", ファイルデータ); }
評価アルゴリズムを見直し修正をする関数の詳細です。
参考資料一覧の配列を用意しておき、評価アルゴリズムを見直させます。
その後、修正内容をユーザに表示させたのち、修正内容を元にプログラムの修正作業に入らせます。
最後に、teian.txtとGEMINI.mdに修正内容のログを残して終了です。
動作確認部分
function 動作確認および修正をする(): void { while (true) { const エラー: 動作確認をした結果に起こったエラー = 動作確認をする( 過去のレースのURL一覧[0] ); if (!エラー.エラーが起きたか) break; 修正をする(エラー.エラー内容); } }
最後に、動作確認および修正をする関数の詳細です。
動作確認をする関数で過去のレース1レースのみ渡し、エラーなく動くか確認をします。その際に「エラーが起きたかどうか」、「起きた場合はそのエラー内容」を記憶させておき、エラーが起きていなければ関数の終了、そうでなければ修正をする作業を繰り返させます。
実際に動かしてみる
準備
「プロンプト」を書き終えたので、Gemini CLIにこれを読み込ませます。読み込ませる際に指示した内容は以下の通りです。
- gemini.tsを読むこと
- このファイルはプログラムファイルではなく、それに似せたプロンプトであることを理解すること
- このプロンプト通りに評価作業を行うこと(やるべきことをすること)
- やるべきことを理解したら、どのようなことをしようとしているのか提示すること

結果
結果は以下の通りのものが返ってきました。私がして欲しいことを完璧に翻訳してくれました。認識の齟齬はこれを見る限りではなさそうです。

下記画像は仮評価で失敗した際に表示されたもののスクショとなります。評価アルゴリズムを見直し修正をする関数の中身通りに手順を実行していることが分かり、言っただけではなく実際に行っていることも確認できました。

さいごに
かなり指示通りに動いてくれて感動しました。「LLMとの認識の齟齬がよく起きて困っている方」、「人に何か物事を説明したり言語化が苦手で、LLMに苦手意識を持っている方」は特にこの手法を使ってみるのも一つの手かもしれません。
ですが、一方でこの記事では深く言及しませんが、Claude Codeにも似たようなことをさせた結果、無限ループの最中に勝手に推論を止めてしまうことがありました。さらに、評価レース数に大きな数を提示した場合「時間がかかり過ぎているので数を減らします」と表示されたりもしました。

個人的にGemini CLIはプロンプトを愚直(良くも悪くも)に認識する一方で、Claude Codeは臨機応変(良くも悪くも)に認識するように見受けられました。両者の特徴も一長一短だと思うので、頼り過ぎず頼らなさ過ぎず適度な距離感を持ってAI駆動開発をしていくのが良いなと改めて感じました。
おまけ
競馬予想プログラム自体の結果も気になる方もいらっしゃると思うので、結果を提示しておきます😇
これを1週間くらいちょくちょく壁打ちしながら動かしていたのですが、2025年京都新聞杯のシミュレーションにて、
- 単勝2枚
- 複勝2枚
- ワイド6枚
で予想し、
- 単勝
- 複勝2枚
- ワイド3枚
がヒットしました!大勝ちですね。5番人気の馬を綺麗に当てられたのはすごいと思います。
ただ、総合では負けているんですよね…現実はそんなに甘くないということで、今回はここで筆を置かせていただきます。
コメント