3
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?

Blazor + OpenAI で「カスの噓」生成アプリを作った

Last updated at Posted at 2025-12-12

このアプリはここから遊べます。

課題の要件

私は普段某トノサマバッタの専門学校で学生をしております
このアプリは、その学校の課題として制作しました。そのときに課された条件は以下のとおりです。

タイトル 季節の表現
内容 月の入力に対して、季節が表現される
ペルソナ ターゲットは自分で選定(発表時に説明すること)
入力方法 使いやすければどんなコントロールでもOK
プロジェクト Windowsフォームアプリケーション (.NET Framework) 限定
言語 C#は絶対使用(+αはOK)

最初、この「Windowsフォームアプリケーション限定」という条件を盛大に見落とし、
「とりあえず C# で作れそうな何か」を探しはじめてしまいました。

最終的には要件は一応クリアできたのですが、
フレームワーク的な意味での「+α」がだいぶ盛り過ぎな構成になりました。

背景

  • 以前から OpenAI API を使って何か作ってみたかった
  • どうせなら C# でマルチプラットフォームもやってみたい

という2つのモチベーションがあったので、

  • C# でマルチプラットフォーム → まずは .NET MAUI が候補に上がる
  • ただし今回のアプリは「気軽に開いてすぐ使える」ことが重要
  • 「インストール」という行為自体が、ユーザーにとって導入障壁が高い

という理由から MAUI は却下。

そこで「Web 技術でマルチプラットフォームを実現する C# のソリューション」を探していて見つけたのが Blazor でした。

Blazor とは

Microsoft が開発した次世代 Web UI フレームワーク
C# / .NET を用いてクライアント/サーバー両方の Web アプリを構築可能

ざっくり言うと、こんな感じのフレームワークです。

  • フルスタック C#

    • UI もロジックも C# で完結
    • JavaScript をほぼ書かなくても Web アプリが作れる
  • ホスティングモデル

    • Blazor Server

      • サーバー側でレンダリング → SignalR 経由で DOM 差分を送信
    • Blazor WebAssembly

      • ブラウザ上で .NET ランタイムを動かす → オフライン対応も可能
  • .NET らしいエコシステム

    • Razor コンポーネント / DI / 認証・認可 / データバインディング / NuGet etc.
  • メリット

    • .NET 開発者の学習コストが低い
    • 既存の .NET ライブラリ資産をそのまま活用しやすい
    • 社内業務アプリや SPA / PWA と相性がいい

要は今まで

フロントエンド:JavaScript
バックエンド:C# など

と2言語以上を覚えるのが普通だった Web アプリ開発において、

C# を覚えればフロントもバックも行ける

という、JSアレルギーの人間にとってはありがたい世界を作ってくれるフレームワークです。

授業で C# は触っていたので、導入の学習コストはそこまで高くありませんでした。
その中で今回は Blazor Server を採用しています。

カスの噓とは何か

ところでみなさん、「カスの噓」 をご存じでしょうか?

ダウナー系お姉さんに毎日カスの嘘を流し込まれる話
生倉のゑる(著)・はるばーど屋(原作)KADOKAWA〈MFC〉

作中で出てくる「カスの嘘」は、例えばこんな感じです。

  • 豆電球はつけると少し甘い匂いがする
  • 歴史とか得意だよ。徳川将軍第31代まで全部言える
  • ハチミツはストローで飲むと苦い
  • らっきょうってあるでしょ?あれ、玉ねぎの赤ちゃん
  • 泥試合はdrawから来ている
  • 過去のだんじりを全て見れるスティック型ストリーミングデバイスがある

……などなど。

絶妙に信憑性があったりなかったりする「嘘豆知識」をひたすら流し込んでくるギャグ作品です

です。

この 「永遠に嘘の豆知識を供給してくる感じ」 と、
LLM が時々放ってくる「それっぽいけど嘘」 の相性があまりにも良いと感じたため、

「LLM でカスの嘘を無限に生成するアプリを作ろう」

という方向性が固まりました。

なお、成果発表の数日前に先行研究を見つけましたが、精神衛生のため見なかったことにしています。

全体構成

大まかなアーキテクチャはこんな感じです。
途中で触れますが、今回 CI/CD も構築しています。

ここで出てくる WebView2 は後で重要になるので、頭の片隅に置いておいてください。

サーバーサイド

  • 上流回線:オプテージ eo光 5Gbps
  • ルーター:NEC IX3315(一家に一台)
  • ポートフォワード:22 / 80 / 443 をサーバーに転送
  • HTTPS 終端:Nginx
  • アプリケーション:Blazor Server
  • 監視基盤:Grafana + Prometheus

ページのアクセス数や OpenAI API のレスポンス時間、Linux のシステムリソースなどもメトリクスとして収集しています。

「もし全部 AWS でやるなら多分こう」という図も描いてみましたが、
先輩曰く「ゲートウェイの位置が間違ってる」とのこと。

image.png

せっかくオンプレで完結できるのに全部クラウドに載せるのも、
個人的にはちょっと面白くないな……というのもあり、今回はオンプレ構成にしました。

WebView2 で WinForms 要件を「満たす」

ここで今回の課題の最大の敵、
「Windowsフォームアプリケーション (.NET Framework) 限定」 という条件と対峙します。

そこで使ったのが WebView2 です。

Microsoft Edge (Chromium) ベースのデスクトップアプリ向け Web コンポーネント
ネイティブアプリに埋め込んで Web 技術で作った UI を表示できる

つまり、

「特定の URL だけ開ける専用ブラウザ」 的な WinForms アプリを作り、
その中で Blazor アプリを表示させる

という構成にしました。

結果として、

  • プロジェクトとしては Windowsフォームアプリケーション
  • 実態としては Blazor Server の Web アプリ

という、レギュレーションのギリギリを攻めるスタイルで要件をクリアしました。
(つま先くらいはちゃんとルールに触れている、ということで……)

OpenAI API の使い方

  • モデル: gpt-4.1-mini
  • temperature: 1.0
  • 1リクエストあたり最大 1000 トークン(上限)

料金ざっくり試算:

  • 1000 トークンあたり $0.001 ≒ 0.15 円

  • 実際の利用は 300 トークン前後

    • 1回あたり $0.00033… ≒ 0.049 円

今回は 10ドル課金しているので、
33,000 リクエスト分 の余裕があります。

ドル円換算するとき、いまだに 0 を 2つ足したくなる自分がいる……

SNS 共有機能

生成された「カスの噓」は、そのまま各種 SNS に共有できます。

  • 対応サービス

    • Twitter (自称X)
    • Reddit
    • LINE
  • 共有方法

    • 各サービス用の URL クエリを組み立てて遷移するだけ → 手軽
  • 例外:Meta(Facebook)

    • API を叩くのにアカウント登録などが必要で、少々重い
    • 「そこまでやりたくない」ので、Meta だけは クリップボードコピー対応

マネタイズ(という名の保険)

もし Twitter などで万が一バズった場合、
API 利用料で破産する未来が見えたので、

  • 一応 PayPal の送金ボタンを設置して、気持ちばかりのマネタイズを入れています。

CI/CD の構築

  • GitHub Actions を使用
  • main ブランチが更新されると自動でデプロイ

このアプリでは OpenAI API キーをハードコーディングしたくなかったため、

  • API キーは外部ファイルとして持つ
  • デプロイのたびにそのファイルを配置し直す必要がある

という事情がありました。

そこで Actions secrets に API キーを登録し、

  1. ビルド後にリダイレクトで外部ファイルを生成
  2. scp でサーバーにコピー

というフローを GitHub Actions に載せています。

正直、これがなかったら途中の試行錯誤フェーズで心が折れていました。
自動化は正義。

悪夢の始まり:HTTPS と SignalR

HTTPS 対応をしようとして Nginx 周りをいじっていたところ、

SignalR の通信がまともに動かない

という問題が発生しました。

  • 新規プロジェクトを作成
  • 正常に動くところから少しずつ移植
  • 最終的に力技で復旧

という荒技で何とか解決しましたが、
根本原因は完全には特定できていません。

状況からすると、

  • アプリ側で謎の HTTPS エンドポイントが生えていた
  • それと Nginx 側の HTTPS 終端設定がバッティングしていた

あたりが怪しいです。

この対応だけで 30コミット近く 積み上がっているので、
CI/CD を事前に構築していなければ確実に詰んでいました。

「季節の表現」の話はどこへ?

さて、ここまで読んでいただいて、
「お前、季節の表現の課題どこ行った?」と思われた方もいるかもしれません。

ちゃんとやっています(ギリギリ)。

ページ下部に 「月を選択」 というプルダウンがあり、
ここでユーザーが月を指定できるようになっています。

  • まず、入力されたテキストから 季語を抽出して季節を推定
  • それでも判定できない場合に、selectedMonth の値を参照

という仕様になっており、
一応課題要件の「月の入力に対して季節を表現」は満たしています。
たぶん。

自宅サーバー環境

APC_0183.jpg

以前の構成

  • Raspberry Pi 4B (2GB) + PoE HAT
  • FS L2+ PoE スイッチ
  • NEC IX3315
  • オプテージ 5Gbps 回線

現在の構成

  • CPU: Intel Core i3-8100
  • メモリ: DDR4-2400 32GB
  • ストレージ: KIOXIA 512GB SSD

当初はラズパイで公開する予定でしたが、
メモリが致命的に足りず没になりました。

使っていなかった、自作の古い PC を発掘してサーバー化

という流れになっています。

メモリの余裕は心の余裕。全人類 64GB 積もう。

ラズパイ時代の地獄

  • ARM 版 Debian に MongoDB パッケージが無い
    → Ubuntu Server に入れ替え
  • Elasticsearch + MongoDB だけで 1.5GB 以上メモリを消費
  • SSH で打った文字が反映されるまで 2秒かかる

などなど、ARM ならではの面倒事が大量発生し、
結果として今でも若干 ARM アレルギー気味です。

機材調達と組み立て

足りなかったのは SSD と電源ケーブルだけだったので、Amazon で調達。
「最悪、まな板運用でもいいか」とも思いましたが、
さすがにそれはアレなのでケースも購入。

たのしい構築大会 in 苦行センター
IMG_0370.JPEG
IMG_0373.JPEG

ヤーポンは滅ぶべき

と心の中で唱えながら組み立てましたが、
今のところ 絶賛安定稼働中です。

まとめ

反省点

  • 課題の要件はちゃんと最初に確認しよう
  • 「やりたいこと」が先行して、要件のクリアが後回しになっていた
  • 着手が遅く、駆け込み実装になってしまった
    → もっと早く始めていれば、もう少し余裕を持って作れたかも

よかった点・得られたもの

  • CI/CD に挑戦し、最後まで投げ出さずに運用まで持っていけた

  • Blazor の可能性を体感できた

    • GitHub の issue が 3.8k だったり、検索サジェストに「流行らない」と出てきたりはするけれど、個人的にはかなり好き
  • オンプレミスで実装・運用する楽しさを味わえた

  • Git の署名付きコミットに挑戦できた

  • README を真面目に書いた

ここまで読んでくださりありがとうございました。

参考文献

伊藤 稔, 大田 一希, 小山 崇, 辻本 海成, 久野 太三 (著)
赤間信幸, 井上章 (著,監修)
出版:インプレス

3
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
3
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?