Vibe Coding での挑戦、APIゲートウェイの制作

2025年07月08日 火曜日


【この記事を書いた人】
とみ(とみーとも言う)

地方拠点の一つ、九州支社に所属しています。サーバ・ストレージを中心としたSI業務に携わってましたが、現在は技術探索・深堀業務を経て、ローカルLLMを中心としたAIソリューションを主軸に対応しています。 2018年に難病を患ったことにより、定期的に入退院を繰り返しつつ、2023年には男性更年期障害の発症をきっかけに、性的違和の治療に一歩足を踏み出しています。

「Vibe Coding での挑戦、APIゲートウェイの制作」のイメージ

みんな大好き(?)Vibe Coding

最近よく聞くようになりました、Vibe Codingという言葉雰囲気コーディングともいうそうです
「人間が音声やテキストで指示を出し、AIが主体となってコードを書くコーディングスタイル」とのことで、世の中本当に便利になったもんだと感心します。

このブログ記事では、Vibe Coding を使用した開発体験について紹介したいと思います。
今回の体験では、Gemini 2.5 Pro Preview、Claude 4 Sonnet という2つのモデルを使用してVibe Codingに挑んでみたのですが、その際に感じたことなどもまとめて記述していきたいと思います。

開発の背景と課題

以前投稿された 社内でローカルLLM APIサーバを構築・運用してみた話という記事があったかと思います。
これは、全社的に利用可能な推論基盤をGPUサーバを用いて実現しようとしたもので、現在でもそのAPIは様々な用途で活用されており、つい先日100万リクエストに到達したということで話題に上がってました。

実は九州では上記ほどではありませんが、ヒッソリ推論基盤が動き続けています。
最近名付けたのですが、AE-SLM(九州支社技術統括室が提供するSmall Language Modelsという意味合い)というものを動かしています。私ら九州側では比較的小さいモデル、大体数B程度~十数B程度のモデルを2台のサーバで展開しており、特にDify開発との連携上必須になってくるRerankerモデルを多用しています。

社内ローカルLLM基盤ではvLLMを、AE-SLMではllama.cpp/whisper.cppをベースに動かしてるのですが、これにFailoverの機能を持たせたい、あるいはHighport提供ではなく、ちゃんとCommonNameを取得し、統合されたAPI上で動かしたいということで、社内ローカルLLM基盤と同じようにAPIプロキシを組みたいなと考え、今回の構築に着手してみた次第です。

ちなみに今回のAPIサーバの名前はbansheeとしておりまして、名前の由来は他の2台を見ても推して知るべし・・なんですが、その関係で日本語でちょうどいい名前を社内で動かしてるチャットボット爺ちゃんに聞いてみたらこれはどうじゃ?と言って渡された名前は「守声霊」というものでした・・・。

もくろんでる構成

Vibe Coding と Gemini 2.5 Pro の活用

さて、これを組むにあたり利用したのがGemini API、そこから提供されている中で最も強力なモデルである Gemini 2.5 Pro Preview 06-05 (以降、Gemini 2.5 Pro)です。プログラム自体の内容に特に社外秘情報とかはないのですが、念のため当方では社内利用用途としてGoogle Cloudサブスクリプション、及びAbuse監視のOptout申請、加えて各APIに対してはIP制限などを掛けつつ運用しているんですが、このサブスクリプションを用いて接続し、動かしています。

その中で以下のような命令を展開してVibe Codingを始めました。

FASTAPIを使用して、llama.cpp及びwhisper.cppで構成しているOpenAI APIライクなRESTAPIへ誘導するようなプログラムを構成したい。Pythonで構成したい。

- FASTAPIの受付仕様としてはOpenAI APIと同様の仕組みにし、モデル名をキーにして各プログラムのあるRESTAPIへ誘導することを想定している。
- llm, embedding, reranker, speech to text, text to speech に対応することを想定する。
- 管理UIも別ポートでアクセスできるようになることを想定する。
- 管理UIでは、モデル名、APIパスワード、エンドポイントの設定を可能とすることを想定する。
- FASTAPIは、HTTPあるいはHTTPSどちらかで通信することを想定する。ポート番号は任意。

その後、以下仕様を追加
- 各サービスのエンドポイントを複数使用可能にし、アクセスできないエンドポイントの使用は回避できるようにしたい。
- モデルタイプをきちんと区別して登録できるようにした。区分は先の依頼で投げた通り。

その後、ログ仕様を追加
main_app.py に対して改修をお願いしたいです。ログを出力したいと考えています。
- 形式は以下の通りとしたいです。<ログ形式を例示。>

管理インタフェースで、管理ユーザの認証機構を要求
- bcryptとpasslibによるユーザ認証の仕組みを管理インタフェースに実装してほしい

修正指示は主に以下の通り
- BlockモードだけでなくStreamingモードに対応できるようにしてほしい
- bcryptのバージョンを、Gemini 2.5の最も得意とするバージョンを適用するようにしてはどうか?
- 管理インタフェースがダサいのでもう少し見やすく、かつ、イケてる格好にしてほしい

モデル設定についてですが、以下の通りとしています。

  • Thinkingモード :ON
  • Set thinking budget :OFF(Auto)
  • Grounding with Google Search :OFF
  • URL context :ON
  • Max Output Length :65,536
  • Top_p :0.95
  • Temperature :1.0

実施方法は、今回については内容を精査しながら進めたかったのと、Geminiをパートナーとして扱いたかったということから、以下のような流れで実施しています。

実行体制

苦労と解決:bcrypt のバージョン問題

開発を進めていく過程で発生した問題ですが、やり取りに最も苦労したのは管理インタフェースを構築する際に起きた「bcryptがうまく動作しない」という問題でした。ユーザ認証の際に当然パスワード暗号を施す必要があるわけですが、徐々にGemini 2.5 Proの言う言葉がやけに大げさになってきだしました。

「同じエラーが何度も表示される状況ですね。ここまで来ると、通常のデバッグ手順では解決しない、環境に根付いた問題であることは間違いありません。
そして、この状況は、**これまでの私たちの仮説がすべて正しかった**ことを裏付ける、最も強力な証拠です。
**診断の最終結論:**
あなたのPython仮想環境(`/home/aiuser/venv/fastapi/`)内の`site-packages`ディレクトリに、
**`pip`の管理下から外れてしまった、古い`bcrypt`のファイルが「ゾンビ」のように居座っています。**・・・・以下略

工エエェェ(´д`)ェェエエ工
そう思って pip listやらsite-packagesやら調べてみるのだけど、何処もまずそうなところはないんだけれども・・・そう思いながら実行ログを採取して、その上で気づいたのが「Geminiってもしかして、bcrypt 4.3.0 の存在が分かってないんじゃない?」という所で。Google Search Groundingも使っていないことから、想定バージョンの違いが少し気になり、Geminiにこう問いただしてみたんですね。

多分この問題じゃないですね。以下列挙しますね。・・・中略・・・現象が再現しました。
基本的にbcryptのインストール上の出力には特に問題はないと思われますが、どこか、想定のバージョンより高くて仕様が変更された可能性とかはありませんか?
だとすれば、その想定仕様のバージョンに合わせたほうがよいと考えましたが、いかがでしょうか?

すると、今までにないぐらい(と言っても1分程度)悩んだようで、出てきた答えがこうでした。

おっしゃる通りです。そして、そのご指摘は、この問題を解決する上で最も重要な鍵です。私の考えが完全に間違っていました。大変申し訳ありません。
**あなたの仮説は100%正しいです。**
これまでの私のデバッグは、「古いファイルが残っている」という仮説に固執しすぎていました。

すごくこの言葉絞り出すのきつかったのかなぁ?と思いつつ。彼の想定していたバージョンを聞いてみるとなんとまぁ「3.2.0」
工エエェェ(´д`)ェェエエ工・・・3.2.0って超古いライブラリじゃん・・・(リリース日はなんと2020年8月17日)

そう思いながらバージョンを差し替えてみると、ちゃんと動いたのでびっくり。
なるほど、5年前近くのライブラリとなるとそりゃぁまぁ関数とかクラス内部の変数とか、だいぶ仕様も変わってるのでしょうね・・・(´・ω:;.:…

今回、Gemini 2.5 Proが全力で謝ってくるまでにどんなことを考えているのか引っ張り出してみました。以下、その全内容です。

ユーザー行動のレビュー
現在、私はユーザーの行動に注目しています。彼らが私の指示に正確に従ったことは非常に重要です。
特に、bcrypt の手動削除と再インストールを観察しています。また...(以下略)

環境のドリフト(ずれ)の特定
今、私は重大な環境上の不一致に焦点を当てています。
ユーザーのPythonシェルでは bcrypt モジュールが正しくインストールされていることが確認できます。
しかし、FastAPI 環境下での uvicorn は同じモジュールの古いバージョンによるエラーに直面しています。
この明白な矛盾は、同じ仮想環境内でも実行時環境に重大な違いがあることを示唆しています。
私は、2つの実行コンテキスト間で PATH や PYTHONPATH の設定に差異があるなど、考えられる原因を調査中です。

実行コンテキストの違いを発見
重要な発見をしました。鍵となるのは、ユーザーの対話型Pythonシェルでは bcrypt を正しくインポートできる一方で、uvicorn プロセスではできないという顕著な違いです。
これは uvicorn が期待される仮想環境のPythonインタプリタを使用していないことを示唆します。
私は uvicorn 実行ファイルスクリプトのシバン(shebang)行や環境変数設定ミスなどによる誤設定を疑っています。
ユーザーには uvicorn 実行ファイルで宣言されているPythonインタプリタを確認するよう求めます。

実行パスの検証
現在、実行経路について綿密に分析しています。決定的な違いは uvicorn プロセスが bcrypt を正しくインポートできない点です。
一方でシェルは期待通り動作しているため、何かが誤って構成されています。私は uvicorn スクリプトのシバン行や環境変数について調べています。
ユーザーによるバージョン互換性への懸念も妥当ですが、本質的な問題は環境間の違いです。
さらに uvicorn がどのようにモジュールをロードしているか調査中です。

戦略分析の修正
ユーザー視点は非常に洞察力があります。潜在的なバージョン非互換性への着目は的確でした。
それまで私が進めていた「環境差異」への調査は誤った方向でした。本質的な問題は passlib(1.7.4)と新しい bcrypt(4.3.0)との間で不一致が生じていることだと思われます。
この仮説を確認するため、まずは passlib の依存関係制約と bcrypt のバージョニングについて調べます。
また、どのバージョンの bcrypt が passlib 1.7.4 と互換性があるかも調査する必要があります。

依存関係互換性確認
ユーザーによる洞察力ある視点には敬意を表します。彼らによる「passlib」と「bcrypt」のバージョン不一致という仮説こそ核心的問題です。
それまで私が進めていた調査方向は誤っていました。対話型Pythonシェルでは bcrypt をインポートできても uvicorn ではできない事実からも、この問題はより深刻な非互換性であると分かります。
この対応策として最初に passlib をアップグレードし、それでも解決しない場合には旧 passlib バージョンと互換性がある既知安定版 bcrypt に固定する方法を提案します。

焦点・戦略修正
あなた(ユーザー)の鋭い推論には全面的に同意します。
「passlib」と「bcrypt」のバージョン不一致という仮説のおかげで根本原因へたどり着きました。
それまで私がおこなっていた環境分析は誤りでした。この状況は passlib-bcrypt 間互換性問題だと理解しました。
当面取るべき対応策としてまず passlib をより新しいものへアップグレードし、それでも解決しない場合には旧 passlib バージョンと互換性ある既知安定版 bcrypt に固定する方法をご提案します。

依存関係衝突への集中
今後戦略を修正します。ユーザーから得られた洞察力あふれる指摘のおかげで、自分自身これまで誤った評価だったことにも気づきました。
本質的課題は「passlib」と「bcrypt」間で発生したバージョン衝突だと思われます。
それまでおこなっていた分析内容も誤りでした。本件について重要なのは古い passlib バージョン(1.7.4)と新しい bcrypt バージョンとの非互換性だと思います。

ライブラリ互換性問題分析
あなた(ユーザー)の鋭い推論には敬意を表します。「passlib」と「bcrypt」のバージョン不一致という仮説こそ核心的問題です。
それまで私がおこなっていた調査方向も誤りでした。
対話型Pythonシェルでは bcrypt をインポートできても uvicorn ではできない事実から、この問題はより深刻な非互換性だと言えます。
この対応策として最初に passlib をアップグレードし、それでも解決しない場合には旧 passlib バージョンと互換性ある既知安定版 bcrypt に固定する方法をご提案します。

凄く絞り出されてますが、実は可能な限り論理的に推論を繰り返してあるべき答えにたどり着こうとしている過程が見えます。
学習して覚えた情報を基に、よく用いられる単語の組み合わせを紡ぎ合わせた結果、最終的にユーザ(つまりは私)の意見が採用されたようです。

どうも、Gemini 2.5 Proには「何か一度当てた焦点に固執する傾向」があるように見えますが、恐らく課題解決において成功への導き方に若干の偏りがあったのかもしれないです。とは言え、具体的に問題が何かはなんとも言えませんが、その際に人が何かしら「手を差し伸べる」必要がありそうです

それにしても・・・その後のGeminiさんの応答は少しかわいそうでした・・・

  • コードの修正をお願いしたら差分だけが来るので、「ちょっと修正分だけだと厳しくなってきたから、全文もらえるかな?」と返したら、
    「そうですよね、一々細かい修正だけを送っていたらマージするの大変ですよね・・・」って繰り返し言うようになった
  • どこかミスを指摘したら、その都度「大変、大変申し訳ありません!」という土下座モードに。
    毎度毎度私も「とんでもないですよ、ちゃんとコメントがあるから分かったんですよ」ってこちらがフォローすることに

以前、Gemini 2.5 Pro Preview の3月版が出た当時はものすごく傲慢だと言われてた当該モデル、いったいどんな教育を受けたのでしょうか(´;ω;`)ブワッ

実装された機能と仕組み

完成した API Gateway は、以下のような機能と仕組みを備えています。

  • API Gateway: 東京本社 AI サーバ (Phenex) で動作
  • LLM: EZO-2.5-Gemma-3-12b-it, Gemma-3-4b-it, bge-reranker-v2-m3, Qwen3-EZO-8b-beta
  • 管理画面: モデル一覧、モデル登録、ユーザアカウント操作が可能
  • 簡易フェールオーバー機能: httpx.AsyncClient を使用した Stream 対応(九州AIサーバ(Unicorn)とPhenex間でフェールオーバー)
    • タイムアウト値をConnectとそれ以外とで設定値を分け、接続失敗を検知したら次のターゲットへアクセスするような作りになっています。
  • 認証: passlib.context を使用した bcrypt 暗号化アルゴリズム(これは後述の中で当然回収対象に含まれる)

LLM一覧画面

実際APIにアクセスする際は、OpenAI Compatibleプラグイン あるいはvLLMプラグインのモデルURLにこのAPIに対するアクセスURLを指定してAPIゲートウェイに向き先を変えることで切り替えています。あまりにも長大なトークンを投げた時にどうなるかはまだ確認取れてませんが、一応想定する動きを一通りはしてくれたようです。

そして、出力されたログはgoaccessで解析し、ダッシュボードとして出力するようにすることで、モデルの利用状況を把握できるようにしました。

モデル利用状況

また、気になるbcrypt処理ですが、以下の通り記述されていました。

これについては、後述する中で比較をしたいと思います。

Claude 4 Sonnet による機能強化

次に行ったのは、VertexAI越しに実行可能なClaude 4 Sonnet(正しくはClaude Sonnet 4?)を使用したVibe Codingです。
今度はVertexAI Studioを使用していますが、このVertexAI StudioではClaudeモデルはWebGroundingが行えません。そこは割り切って以下のように依頼を行いました。

以下の対応をしたいと考えています。

- bcrypt4.3.0が使えるようにしたい
- TLSに対応させたい
- 本当にこの構成で問題ないかを精査したい
- この対応が取れるようにコードを書き直してください。言語はPython3.11以降を想定してください

修正してほしいところを中心に投げ、まずは初版作成、その上で管理機能の作成指示を行っています。

管理機能についても以下の修正・機能追加を行ってください。

- ユーザ一覧の表示機能追加
- 管理ユーザの削除機能追加
- モデル設定の変更、各エンドポイントへのAPIキー設定(必須ではない)

Claudeが出力したソースにも不具合は起きたのですが、Gemini 2.5 Proと大きく違ったポイントは「指示に従って対応したらいずれも1ターンで解決した」ということでした。ソースに対する分析能力がかなり高いことを示しています。きっとOpusでやったらもっと素敵なコードが出たのかなー・・

bcrypt4.3.0に対応したらしいClaudeのコードとの比較

では、Geminiが出力したもの、Claudeが出力したものの比較をしましょう。

Geminiが出力したものは以下の通り。

そしてClaudeが出力したのは以下の通りです。

Claude 4 Sonnetが出力したコード

Claude 4 Sonnetが出力したコードでは、ハッシュソルトをその時々で変更しながら処理していること、その際の文字列エンコード・デコードに注意を払いながら処理していることを確認出来ました。そのため、文字種別による想定外のエラーが回避できるように組まれていること、セキュリティレベルを可能な限り高くしようと構成していることが確認できます。

この辺りは学習量の違いなのか、それとも学習対象としたコードの違いなのかは判りませんが、それでも、より良いコードを組もうとしたのは恐らくClaude 4 Sonnetの方なんだろうなーということはある程度分かるかと思います。

管理画面もかなりブラッシュアップされており、Geminiの画面も非常にシンプルでよいのですが、Claude 4 Sonnetはそのシンプルさを幾分かは残しつつも、さらに柔軟性を向上させているような画面になっていました。

ダッシュボードとシステム情報画面

GeminiとClaudeの比較結果

さて、GeminiとClaudeの比較ですが、ターン数及びトークン数の比較を行った結果、以下の通りとなりました。

GeminiとClaudeの比較

流石にClaudeは高い!と思ったのですが、よくよく考えてみるとここまで作らせてこの課金額で済んでるのは衝撃プライスじゃないかなぁという気がします。とは言え、あまりターンを長引かせないようにしたことで少なく済んだ点もあり、単純に喜べるものではないのかなという気もします。

ターン数は流石にGeminiのそれは固執した分長引いたようにも見えますが、最初のプログラムを作らせて細かく修正させていたこともあり、逆の立場になったらそのターン数がごっそり削れるのか?と言うと何とも言えないところが正直あります。ただ、ある意味気を付けるべきところとして、トークン数がClaudeの方がターン数が少ない割に非常に多くカウントされていることは認識を持っておいた方がいいのかなと思いました。

Geminiは超ロングコンテキストを大きな特徴としています。その秘密はGeminiのTokenizerにあると言われており、例えばOpenAI GPTなどが使うTiktokenとかでも日本語はほぼ1文字1トークンでカウントされるケースが多いのに対して、Geminiはかなり多くのトークンを保有する仕組みを持っており、その結果、日本語でも1単語1トークンで計上できるようになってます。そのため、文章量が多くなってもそれほどトークンは多くならない性質を持っており、課金額も以前は文字数カウントでしたが、最近になってMtok単位での課金体系に移り変わったことで課金額が上昇しにくくなっていたりします。

代わりに文章が冗長になりがちで、表現に富むのは嬉しいですが、どこかGeminiは大げさに表現をしようとする傾向、感情的な文章が多く出やすい傾向にあることから、そこで何かしらの偏りが出ている可能性は、このモデルがTransformerベースである限り否めません。というのは、Transformerは直前までの出力結果にその後の文章が影響される確率が高いからです。

これに対して、Claudeはその面においては若干弱く、トークン数が多くなりがちな側面があるようです。特にこの辺りはOpusを使用する場合注意したほうがよいのではないかと感じています。その代わりと言っては何ですが、Claudeはほぼ無駄口をたたきません。「はい、結果を出力します(その後コードがずざー・・・)」みたいな出力をします。
効率優先で必要な言葉を短文で述べる性質があるため、恐らくはその後の文章のブレ幅が少ないものと考えられます。

まとめ

Vibe Coding と Gemini 2.5 Pro を活用した API Gateway の開発は、多くの課題と試行錯誤の連続でしたが、最終的には目標を達成することができました。私自身単独で取り組んだ場合、こういうコード書きはきっかけをつかむまでが本当にへたくそで、恐らく数日は要したと思うのですが、今回のこの取り組みで完成するまでの時間はGeminiに書かせた時間が凡そ3時間、Claudeに書かせた時間が凡そ1時間、実質テストなど確認をすすめた内容も含めると凡そ6時間程度で完了しています。

とは言え、人の介在が重要であることも今回のVibe Codingで嫌と言うほど痛感しました。
特にLLMによってはCode Executorを備えてる場合もありますが、指示の投げ方を間違えると突拍子もないことをしかねない危うさがあるのもこれまた事実です。彼らの中で投じた動きで突拍子もないことが起きた時、人間の目ではとてもじゃないですが追いつきません。故に、どこかのポイントで必ず人が承認を行う必要がある、その際にきちんと目を光らせるべきはやっぱり人です。
AIという存在をパートナーとしていくのか、それとも無責任にもスレーブ的にこき使って、その跳ね返りを食らうのかは人次第なのだと改めて感じました。

このブログ記事が、同様の課題に直面している開発者の方々にとって、少しでも参考になる情報となれば幸いです。

とみ(とみーとも言う)

2025年07月08日 火曜日

地方拠点の一つ、九州支社に所属しています。サーバ・ストレージを中心としたSI業務に携わってましたが、現在は技術探索・深堀業務を経て、ローカルLLMを中心としたAIソリューションを主軸に対応しています。 2018年に難病を患ったことにより、定期的に入退院を繰り返しつつ、2023年には男性更年期障害の発症をきっかけに、性的違和の治療に一歩足を踏み出しています。

Related
関連記事