0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

5歳の娘向けに、AI家庭教師アプリを作り始めた①

0
Posted at

はじめに

娘(5歳9か月)は、ひらがな・カタカナ、英会話、ピアノを日々がんばっています。
それと、ゲットだぜ!でおなじみのアレにとにかくハマっている、。

どれも「やらされている」というより、できるようになるのが楽しい時期なのかなと思います。ひらがなが少しきれいに書けたり、英会話の先生にほめられたり、ピアノで昨日より少し弾けるようになったりすると、けっこう嬉しそうにしています。

それを見ていて、

娘の好きなこと、夢中になっていることと、生成 AI を掛け合わせたら、娘専用の家庭教師みたいなものを作れるのでは

と思いました。

概要

娘が取り組んでいるのは、主に以下の3つです。

  • ひらがな・カタカナの書き取り練習
  • 英会話
  • ピアノ

それぞれの成果物をアップロードすると、AI が内容を見て3段階で評価します。

  • ひらがな・カタカナ: ドリルの写真
  • 英会話: 音声または動画
  • ピアノ: 練習動画

評価後は、ご褒美としてモンスターのコレクションステッカーをゲット!できます。

名称未設定-1.png

作るうえで意識したこと

最初からきれいな UI や完璧な機能はしんどいので、まずは家庭内で実際に使える状態にして、運用に乗せることを優先しました。UI はこれから整えていく前提です。

また、個人開発なのでコストも大事です。生成 AI の API 以外にはなるべくお金をかけず、Vercel、Supabase、GitHub Actions などの無料枠を使って進めています。

もうひとつ大事にしたのが、生成物の扱いです。このあたりは少し慎重に考えました。

技術者倫理が問われる時代でもありますし、子どもが将来 SNS を使うようになったときに「AI で作ったものを何でも外に出してよいわけではない」という感覚を伝えるきっかけにもできればと思っています。

生成物の扱いをどう考えたか

今回は、某モンスター収集ゲーム風のコレクション要素を家庭内のご褒美として使っています。

ここは正直、雑に扱ってよいところではないと思っています。法律的にも、利用規約的にも、生成 AI のポリシー的にも、少なくとも一度は立ち止まる必要があるところです。

自分の中では、次のように整理しました。

  • 著作権法30条には、家庭内など限られた範囲で使うための複製についての規定がある
  • 権利元の利用規定も、個人的に楽しむ範囲を前提にしていると読める
  • 生成 AI 側でも、知的財産権を侵害する使い方は当然 NG

なので、今回の生成画像は 家庭内だけで使う という前提にしています。公開しません。配布しません。SNS にも上げません。記事にキャプチャを載せる場合も、該当部分はモザイクをかけます。

逆に言うと、この前提が崩れた瞬間に話が変わります。

生成できたから使ってよい、ではありません。AI のフィルタを通ったことと、権利的に問題がないことは別です。

このアプリはあくまで家庭内で完結させます。今後、認証まわりを整えるときも、家族だけが見られる状態にしていくつもりです。

なお、これはあくまで個人的な整理です。とにかくこの先公開や配布を行うことはありません。

なぜステッカーコレクションにしたのか

娘は「集める」「そろえる」が好きなので、何かしらコレクションできる形のほうが続きそうだなと思いました。

そこで、AI の評価結果に応じてステッカーのグレードが変わるようにしました。

評価 ステッカーグレード 見た目
たいへんよくできました 超レア 多層ホログラム
よくできました レア 単層ホログラム
がんばりましょう ふつう マット仕上げ

5歳でも「がんばるとキラキラしたものが出やすい」というルールはわかりやすいです。

もちろん、評価が低かったら残念な気持ちになるので、コメントは必ず前向きに返すようにしています。採点というより、次の練習につながるフィードバックとしたいです。

技術スタック

ベースは Next.js です。

Gemini

AI 評価と画像生成に使っています。

ひらがな・カタカナは画像、英会話は音声・動画、ピアノは動画を扱いたいので、マルチモーダル対応している点が大きいです。

また、ひらがな・カタカナの手書き認識を考えると、日本語 OCR に強いモデルを使いたいという理由もあります。

SDK は @google/genai を使っています。

Supabase

ステッカー情報と生成画像の保存に使っています。

  • PostgreSQL: 獲得したステッカー情報
  • Storage: 生成したステッカー画像

本格的な認証はまだ入れていません。まずは家庭内で使う前提で、動くところを優先しています。

Vercel

Next.js なので、そのまま Vercel にデプロイしています。

全体の流れ

評価フロー

ユーザーがファイルをアップロード
  ↓
POST /api/evaluate/{subject}
  ↓
Gemini でファイルを解析
  ↓
EvaluationResult を返す
  ↓
評価結果とフィードバックを画面に表示

EvaluationResult はこんな形にしています。

type EvaluationResult = {
  rating: "excellent" | "good" | "try";
  ratingLabel: string;
  comment: string;
  feedback: string;
};

ステッカー取得フロー

「ステッカーをGET!」ボタンを押す
  ↓
未取得のキャラクターからランダム抽選
  ↓
評価グレードに応じたプロンプトを作る
  ↓
POST /api/sticker/generate
  ↓
Gemini で画像生成
  ↓
sharp で正方形に加工・圧縮
  ↓
Supabase Storage に保存
  ↓
ステッカー情報を DB に保存

実装: ひらがな・カタカナ評価

最初に実装したのは、ひらがな・カタカナの評価です。

ドリルの写真をアップロードすると、Gemini に画像を渡して、手書き文字を見てもらいます。

プロンプトでは、以下のような観点で評価するようにしました。

const PROMPT = `この画像は子ども(5〜6歳)のひらがな・カタカナ練習ドリルのページです。

【評価してほしいこと】
手書きした文字の品質を以下の観点で評価してください:
- とめ・はね・はらいが適切か
- 文字の形・大きさ・バランスが整っているか
- 線がはっきり書けているか

【出力形式】
必ず以下のJSONのみを返してください:
{"rating":"excellent","comment":"やさしいひとことコメント","feedback":"くわしいひょうか"}`;

評価結果は3段階です。

  • excellent: たいへんよくできました
  • good: よくできました
  • try: がんばりましょう

フィードバックは、ひらがな・カタカナ中心にする

娘が自分でも読めるように、フィードバックはなるべくひらがな・カタカナで返すようにしています。

{
  "rating": "good",
  "comment": "「ま」がとてもじょうずでした!",
  "feedback": "よかったところ:「ま」のまるいぶぶんが、きれいにかけていました。\nもうすこしがんばると よいところ:「お」のさいごのはらいが、もうすこしみぎにのびるといいですよ。\nつぎへのおうえん:つぎもたのしくれんしゅうしよう!"
}

ここはまだ調整中です。

AI はたまに漢字を混ぜてくるので、最終的には出力後のチェックも入れたいと思っています。

実装: ステッカー画像生成

評価後は、ステッカー画像を生成します。

キャラクターと評価グレードをもとに、プロンプトを組み立てます。グレードが高いほどホログラム感を強くするようにしています。(ステッカー生成プロンプトはイメージです)

const ART: Record<Grade, (name: string) => string> = {
  super: (name) =>
    `${name} sticker illustration, dynamic pose, premium multi-layer holographic foil, sparkling background`,
  rare: (name) =>
    `${name} sticker illustration, clean action pose, holographic foil, bright background`,
  normal: (name) =>
    `${name} sticker illustration, playful pose, matte sticker, simple background`,
};

生成した画像はそのままだとサイズが大きいので、sharp で正方形にして JPEG 圧縮しています。

const processedBuffer = await sharp(rawBuffer)
  .resize(1024, 1024, { fit: "cover", position: "centre" })
  .jpeg({ quality: 50 })
  .toBuffer();

だいたい数 MB から数百 KB まで落とせました。

詰まったこと

1. Vertex AI の SDK

CluadeCodeが最初 @google-cloud/vertexai を使おうとしていましたが、現在は @google/genai を使う流れが最新。

npm install @google/genai

このあたりは最初に言ってあげると良いのでしょうが。

2. モデルのリージョン

gemini-3.1-pro-preview を使ったとき、us-central1 では 404 になりました。

このモデルは global リージョンで呼び出す必要がありました。

GOOGLE_CLOUD_LOCATION=global

このあたりはモデルによって変わるので、ドキュメント確認が必要です。

3. Vercel 上で生成画像が壊れる

ローカルでは正常に保存できるのに、Vercel にデプロイすると Supabase Storage に壊れた画像が保存される問題がありました。

保存されたファイルを見ると JPEG として認識されず、バイナリが文字列として扱われたような壊れ方をしていました。

対策として、まず sharp を使う Route Handler は Node.js runtime に固定しました。

export const runtime = "nodejs";

また、Supabase Storage にアップロードするときは、Buffer をそのまま渡すのではなく Blob にして渡すようにしました。

const imageBlob = new Blob([new Uint8Array(processedBuffer)], {
  type: "image/jpeg",
});

await supabase.storage.from("sticker-images").upload(filename, imageBlob, {
  contentType: "image/jpeg",
  upsert: true,
});

next.config.tsserverExternalPackages: ['sharp'] も追加しました。

const nextConfig: NextConfig = {
  serverExternalPackages: ["sharp"],
};

ただ、Next.js 16 のドキュメントを見ると、sharp はもともと自動で external 扱いされるパッケージに含まれていました。なので、これは念のための設定という位置づけです。

今回の問題に効いた本命は、runtime = 'nodejs' の明示と、アップロード時に Blob として渡す変更だと見ています。

4. Supabase の RLS

Supabase は RLS が有効なので、ポリシーを設定しないと読み書きできません。

今回は家庭内で使う前提なので、まずはシンプルなポリシーで進めています。

create policy "allow all" on stickers
  for all using (true) with check (true);

公開サービスとして作るなら当然このままではだめですが、まず家庭内で運用するところを優先しました。

DB マイグレーションを GitHub Actions で自動化

Supabase の SQL を毎回手で流すのは面倒なので、GitHub Actions でマイグレーションを流すようにしました。

on:
  push:
    branches: [main]
    paths:
      - "supabase/migrations/**"

jobs:
  migrate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: supabase/setup-cli@v1
      - run: supabase link --project-ref ${{ vars.SUPABASE_PROJECT_ID }}
      - run: supabase db push --linked

supabase/migrations/ に SQL を追加して main に push すれば、自動で適用されます。

Supabase Branching は有料なので、まずは GitHub Actions で必要十分かなと思っています。

現在できていること

  • ひらがな・カタカナ評価
  • 評価結果のフィードバック表示
  • ステッカー画像の AI 生成
  • Supabase へのデータ・画像保存
  • ステッカーコレクション一覧・詳細画面
  • GitHub Actions による DB マイグレーション自動化
  • 英会話評価
  • ピアノ評価
  • UI作り込み
  • 評価制度と方法のブラッシュアップ(これは永遠に続く)

まとめ

娘の学習に、生成 AI を少し混ぜてみるところから始めた個人開発ですが、思ったより早く「家庭内で試せる」状態までは持っていけました。

まだ UI は粗いですし、評価ロジックも調整したいところがあります。英会話とピアノの評価もこれからです。

ただ、まず運用に乗せるという意味では、最初の一歩としては十分かなと思っています。

生成 AI は、使い方を間違えると危ういところもあります。一方で、家庭内でルールを決めて使う分には、子どもの学習体験を少し楽しくできる可能性もあると思いました

0
0
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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?