はじめに
学校で実施する自由記述アンケートには、子どもの小さな「SOS」が紛れていることがあります。しかし、何十枚もの回答を先生が一人で丁寧に読み込むのは現実的に難しい。
そこで、Googleフォームで集めた自由記述回答をAI(Gemini API)が一件ずつ分析し、危険度・感情傾向・気になる言葉を可視化する補助ツールを作りました。
このツールは診断ツールではありません。最終判断は必ず人(先生・支援者)が行うものとして、AIはあくまで「気づくための補助線」を引く役割に徹しています。
本記事では、もともとブラウザで完結する単一HTMLファイルだったものを、FastAPI + Gemini API + Docker 構成のWebアプリへと作り直し、Google Cloud Run へ本番デプロイするまでの技術プロセスを記録します。
- 公開URL: https://sos-analyzer-29956449147.asia-northeast1.run.app/
- コード(GitHub Public): https://github.com/teshiokayumi/sosapp
なお、私はエンジニア畑の出身ではなく、AIとの対話を通じてアプリを組み上げる非エンジニア開発者です。「初めてのDocker」「初めてのArtifact Registry」という視点で詰まりどころも含めて書いています。
このツールでできること
Googleフォームで集めた自由記述アンケートの回答を読み込み、AI(Gemini API)が一件ずつ分析します。
- 危険度(高・中・低)の判定
- 感情傾向の推定(不安・恐怖・怒り・諦め・平穏など)
- SOSにつながりそうな言葉のタグ化
- クラス全体の傾向を一覧で俯瞰
- 結果のExcel出力
「みんな笑っているけど私は怖いと思った」――そんな、言葉が穏やかでも内側に抱えている違和感を拾い上げ、先生が面談で確かめるべき「気づき」として提示することを目指しています。
なぜブラウザ版からアプリ版に作り直したか
もともとは、ブラウザ上だけで完結する単一HTMLファイルとして作りました。サーバー不要・インストール不要で、ファイルを開けばすぐ使える手軽さが利点でした。
ただ、実際に使う先生方は必ずしもIT操作に慣れているわけではなく、ブラウザ版には次のような引っかかりがありました。
- 利用者自身がAPIキーを取得し、画面に貼り付ける必要があった
- APIキーがブラウザ側に保持されるため、扱いに気を遣う構成だった
- 「HTMLファイルをダブルクリックして開く」導線が分かりにくい
そこで、操作に不慣れな先生でも迷わず使えるよう、Webアプリとして作り直しました。
| 項目 | ブラウザ版(旧) | アプリ版(新) |
|---|---|---|
| 形態 | 単一HTMLファイルを開く | URLにアクセスするだけ |
| APIキー | 利用者が取得し画面に入力 | サーバー側で安全に保持(利用者の入力不要) |
| キーの扱い | ブラウザ側に保持 | クライアントには一切渡らない |
| 個人情報 | ― | 生徒名は分析本文に含めず送信 |
| 想定利用者 | 自分で設定できる人 | 操作に不慣れな先生でもそのまま使える |
「URLを開いて、フォーム回答のファイルを読み込み、ボタンを押すだけ」――そこまで導線を削ることを目指しました。
システム構成
フロントエンド
- HTML / JavaScript(単一ファイル構成)
- 結果のExcel出力に SheetJS を利用
バックエンド
- Python / FastAPI
- ブラウザからの分析リクエストをサーバー側で受け、APIキーを安全に保持したままAIを呼び出す「後から叩く」設計
- 生徒名は分析本文に含めず、サーバー側でも保持しない
AI解析
- Google Gemini API
- 回答ごとに危険度・感情・SOSの兆候を JSON形式 で判定
実行環境
- Google Cloud Run(コンテナとしてデプロイ)
開発支援
- Claude(設計・コード作成の壁打ち相手)
- Gemini(旧式版制作の設計・壁打ち相手)
デプロイ手順
ローカル環境で検証したシステムを、Google Cloud(Cloud Run × Artifact Registry) を使って世界中から安全にアクセスできるWebアプリへと昇華させました。
特に、初めてのDockerイメージ化と、APIキーをブラウザ側に露出させない「シークレット管理」にこだわりました。以下、デプロイとトラブルシューティングの全記録です。
1. ローカル環境でのコンテナ化(Docker)
FastAPIのバックエンドと静的フロントエンド(static/index.html)を1つの軽量なコンテナにパッケージングしました。
ベースイメージには軽量な python:3.12-slim を採用し、Cloud Runの仕様に合わせて環境変数 PORT を動的にリッスンするシェル形式で起動するように設計しました。
FROM python:3.12-slim
ENV PYTHONUNBUFFERED=1 \
PYTHONDONTWRITEBYTECODE=1
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY main.py .
COPY static ./static
# Cloud Run のポート指定(既定 8080)に対応
ENV PORT=8080
EXPOSE 8080
CMD exec uvicorn main:app --host 0.0.0.0 --port ${PORT}
2. Artifact Registry でのリポジトリ作成と認証
ビルドしたイメージをGoogle Cloudに預けるため、Artifact Registryに専用のDockerリポジトリを作成しました。
# 1. 目的のリージョン(東京)を変数に定義
$REGION = "asia-northeast1"
# 2. Artifact Registry に Docker 形式のリポジトリ「sos-repo」を作成
gcloud artifacts repositories create sos-repo `
--repository-format=docker `
--location=$REGION
認証情報の管理には、Google Cloud SDKの「Credential Helper(認証ヘルパー)」を活用し、ローカルのDocker環境とGoogle Cloudの認証基盤を安全にバインディングしました。
gcloud auth configure-docker ${REGION}-docker.pkg.dev
3. イメージのビルドとArtifact Registryへのプッシュ
プロジェクトIDを含んだ正式なレジストリアドレスをイメージのタグとして焼き付け、クラウドへ送信しました。
# 1. プッシュ先となるフルアドレスを定義(バージョン: v1)
$IMAGE = "${REGION}-docker.pkg.dev/grade-management-XXXXXX/sos-repo/sos-analyzer:v1"
# 2. タグを指定してDockerイメージをビルド
docker build -t $IMAGE .
# 3. Artifact Registry へ安全にプッシュ
docker push $IMAGE
4. Secret Manager による APIキーの隠蔽
セキュリティ最大化(「後から叩く安心設計」)のため、Gemini APIキーをソースコードや環境変数に直接書き込まず、Google Cloudの Secret Manager に隠蔽して暗号化保管しました。
# 1. APIキーを一時ファイル経由で Secret Manager に登録
$key = "AIzaSy..." # 実際のGemini APIキー
$key | Out-File -FilePath "$env:TEMP\gemini-key.txt" -Encoding ascii -NoNewline
gcloud secrets create gemini-api-key --data-file="$env:TEMP\gemini-key.txt"
Remove-Item "$env:TEMP\gemini-key.txt" -Force
# 2. Cloud Run のサービスアカウントに secretAccessor 権限を付与
$PROJECT_NUMBER = "XXXXXXXXXXX"
gcloud secrets add-iam-policy-binding gemini-api-key `
--member="serviceAccount:${PROJECT_NUMBER}-compute@developer.gserviceaccount.com" `
--role="roles/secretmanager.secretAccessor"
5. Cloud Run へのデプロイ
プッシュしたコンテナイメージと、隠蔽したシークレット(GEMINI_API_KEY)を結合してCloud Runへデプロイしました。
gcloud run deploy sos-analyzer `
--image=$IMAGE `
--region=$REGION `
--allow-unauthenticated `
--set-secrets=GEMINI_API_KEY=gemini-api-key:latest
⚠️ つまずいた点と解決:502 Bad Gateway
デプロイ直後、内部API(/api/analyze)が 502 Bad Gateway を返す現象に直面しました。
ヘルスチェック(/healthz)の応答などから、原因が「旧世代モデル(gemini-2.0-flash 等)の指定と、従量課金(支払い設定)の切り替わりタイミングによるエンドポイントの乖離」にあると切り分け。
このシステムの強みである 「コンテナを再ビルドすることなく、環境変数だけでAIモデルを切り替えられる設計」 を活かし、gcloud コマンドから即座に最新の高速軽量モデル gemini-2.5-flash-lite へ環境変数を更新しました。
# 最新モデルへ環境変数を切り替えつつ Cloud Run へ反映
gcloud run services update sos-analyzer `
--region=asia-northeast1 `
--update-env-vars=GEMINI_MODEL=gemini-2.5-flash-lite
ルーティングの反映とともに502エラーは解消。/healthz の200 OKが通り、本番環境のブラウザからGemini APIを中継した分析が安定して動作するようになりました。
設計上の学び:モデル名をハードコードせず環境変数で差し替えられるようにしておいたことで、提供終了に差しかかったモデルからコードを書き換えずに移行できました。AIモデルの更新サイクルが速い今、この「差し替え可能性」を設計に組み込んでおく価値は大きいと感じます。
使い方
- Googleフォームで自由記述アンケートを実施する
- 回答をスプレッドシート化する(フォーム上部の「スプレッドシートにする」ボタン)
- スプレッドシートをCSVとして書き出す
- アプリのURLを開き、CSVファイルを読み込む
- 「AI分析を開始」を押すと、危険度・感情・気になる言葉が一覧表示される
- 必要に応じてExcelでダウンロードする
CSVは「1列目=名前、2列目以降=回答内容、1行目はヘッダー行」の形式を想定しています。
おわりに
これまでCloud Runへのデプロイは何度か経験がありましたが、今回初めて Artifact Registry や Docker を経由する形 に挑戦しました。コンテナイメージをビルドしてレジストリに登録し、そこからCloud Runへ展開する一連の流れは今までと違い、最初はかなり戸惑いました。
それでも、非エンジニアでもAIとの壁打ちを重ねながら、APIキーを露出させない実運用クオリティの構成までたどり着けたのは大きな手応えでした。
このツールは「子どものSOSを見落とさないための補助線」です。AIが子どもを判定するのではなく、最終的に向き合うのは必ず人である――その前提を崩さない設計を、これからも大事にしていきたいと思います。
注意事項
本ツールは診断ツールではありません。医療・心理的な確定判断を行うものではなく、最終判断は必ず人(先生・支援者)が行ってください。個人情報の取り扱いには十分ご注意ください。