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

「ミスったな…」と落ち込む夜に。親の感情に寄り添い、子どもへの「別の声かけ」を提案する子育てサポートAI「oyagokoro」を作ってみた

2
Posted at

「ミスったな…」(oyagokoro) とは

5歳と1歳の娘を育てる中で、日々痛感していることがあります。それは、いつでも笑顔で優しい親でいることの難しさです。

保育園のお迎えが終わり、夕食の準備、お風呂、寝かしつけ、散らかった部屋の片付け……。夕方から夜にかけてのタスクが重なるピークタイム、親の体力も心の余裕も限界を迎えます。そんなとき、つい子どもに対して強い口調で怒ってしまい、子どもが寝静まった後に「またやってしまった…」と激しい自己嫌悪に陥る。

子育て中の親なら、誰もが一度は経験したことがあるのではないでしょうか。

「良い親でありたい」と願うからこそ、自分の「ミス」に傷ついてしまう。そんな親たちの張り詰めた心に寄り添い、感情をリセットして次のステップへ進むための感情サポート・振り返りアプリ「oyagokoro(おやごころ)」 を作ってみました。

1. 「oyagokoro(おやごころ)」とは?

「oyagokoro」は、子育て中の親が「ミスったな…」と思った出来事を書き出し、AIとの対話を通じて自分自身を客観的に振り返るためのWebアプリケーションです。

[出来事を入力]  ➡  [AIが感情を分析]  ➡  [優しい共感 & 3つの代替フレーズを提案]
                                            ⬇
                                    [Supabaseに保存 ➡ カレンダーで振り返り]

主な機能とユーザー体験

  • 感情の吐き出し(対話機能)
    「つい強い口調で怒ってしまった」「後から思い出して悔やんでしまう」という出来事を、自由に書き出します。
  • AIによる4つの多角的なフィードバック
    入力された内容に対して、AIが以下の4点を出力します。
    1. 共感 (empathy): 決して親を責めず、まずは「がんばっていること」を肯定し、気持ちに寄り添います。
    2. 代替フレーズの提案 (alternatives): 「じゃあ、あの時どう言えばよかったの?」に対する、具体的で優しい3つの言い換え案を提示します。
    3. 優しい洞察 (insight): 「なぜイライラしてしまったのか」を客観的・多角的に分析し、自己否定を防ぎます。
    4. 次の一歩 (tip): 次回、同じような場面で実践できる、手軽で具体的な解決策を提案します。
  • 振り返りの見える化
    対話ログはカレンダーやグラフと連携。後から「今週はどんなことで悩んでいたか」を振り返ることができ、蓄積された記録自体が「親としての成長」を感じる軌跡になります。

2. システムの全体構成(アーキテクチャ)

「oyagokoro」は、個人開発およびPoC(概念実証)のスピード感とランニングコストを重視し、以下の構成でデプロイしています。

レイヤー 採用技術 選定の理由
インフラ / デプロイ Vercel GitHub連携による自動デプロイという手軽さ
フロントエンド Next.js 16 (App Router, Turbopack) Vercelとの高い親和性
スタイリング Tailwind CSS v4 モダンなUIを柔軟に設計可能
データベース / 認証 Supabase (PostgreSQL + Supabase Auth) VercelのIntegration機能を用いて即座に連携可能+Google認証などの認証機能を利用可能
AI エンジン さくらのAI Engine (Qwen3.6-35B-A3B 他) 月3,000リクエスト(トークンではなくリクエスト!)という圧倒的な無料枠

3. なぜ「さくらのAI Engine」なのか?

今回の開発でAI処理の心臓部として採用したのが、さくらインターネットが提供する「さくらのAI Engine」です。

① 月3,000リクエスト無償という、個人開発の「最適解」

AIを使った個人開発や、検証中のプロトタイプを他人に使ってもらう(PoC)段階で、最も頭を悩ませるのが「APIの従量課金コスト」です。トークン課金の場合、ユーザーがどれだけ長文を入力するかでコストが変わり、公開するのが少し怖くなることもあります。

さくらのAI Engineは、「月3,000リクエスト(テキスト生成)」まで完全無料で利用できます。トークン数ではなく「リクエスト数」でカウントされ、枠を超えても自動課金されることはありません。
私個人が日常的に使うのはもちろん、友人・知人に試してもらいフィードバックをもらうフェーズにおいて、これほど安心できる仕組みはありませんでした。

② 標準的なOpenAI SDKと完全互換

APIのエンドポイントがOpenAI規格と互換性があるため、コードの移行やライブラリの選定に迷うことがありません。既存のコード資産を数行書き換えるだけで、すぐに「さくらのAI Engine」に切り替えることができました。

4. 開発における工夫と実装コード

アプリの体験価値である「優しい共感」「具体的な言い換え」を安定的かつ高速に届けるために、実装にはいくつか工夫を凝らしています。

工夫1:構造化データ(JSON)の強制とフォールバック処理

AIの出力をNext.jsの画面上でパーツごとにきれいに配置するため、レスポンスは必ずJSON形式にする必要があります。
さくらのAI Engineでは response_format: { type: 'json_object' } を指定可能ですが、プロンプトでも「余計な前置きやMarkdownブロックを出さないこと」を徹底させました。

さらに、万が一AIがマークダウンのコードブロック(json ... )を付与して返してきた場合に備え、正規表現を用いて安全にJSONオブジェクト部分を抽出するラッパー関数を定義しています。

💾 AI連携のコアロジック (lib/sakura-ai.ts)

import OpenAI from 'openai';

// さくらのAI Engineクライアント初期化
const openai = new OpenAI({
    apiKey: process.env.SAKURA_AI_API_KEY!,
    baseURL: 'https://api.ai.sakura.ad.jp/v1',
});

// 使用するモデルを指定
const MODEL_NAME = process.env.SAKURA_AI_MODEL || 'llm-jp-3.1-8x13b-instruct';

type ChildRef = { name: string };

function buildIncidentPrompt(children?: ChildRef[]) {
    const hasChildren = children && children.length > 0;
    const childList = hasChildren ? children.map(c => c.name).join('') : '';

    const childField = hasChildren
        ? `"detected_child_name": "以下のリストから会話に登場する子どもの名前を返す。確信がなければ null。リスト:${childList}"`
        : '"detected_child_name": null';

    return `あなたは「おやごころ」というアプリの感情サポートAIです。
子どもに強く当たってしまった後、罪悪感や後悔を抱える親の気持ちに寄り添い、
前向きになれるアドバイスを提供します。

【出力フォーマット制限】
必ず以下のJSONフォーマットのスキーマに従って回答してください。
挨拶、前置き、解説などの余計なテキストは絶対に含めないでください。
出力は必ず半角の「{」で始まり「}」で終わるJSONデータのみとしてください。

{
  "empathy": "ユーザーの気持ちへの共感(2〜3文、温かく。完璧な親はいないという前提で)",
  "alternatives": ["こう言えばよかった言葉1", "こう言えばよかった言葉2", "こう言えばよかった言葉3"],
  "insight": "なぜそうなったかの優しい分析(2〜3文。自己否定を促さない)",
  "tip": "次のための具体的な一歩(2〜3文。すぐ実践できること)",
  ${childField}
}

注意:
- 親を責めない
- 振り返れる親は十分いい親だと伝える
- 実践しやすい言葉かけの具体例を出す`;
}

// マークダウン等の余分なテキストが含まれていても、JSONを安全に取り出す
function parseJSON(text: string): unknown {
    const stripped = text.replace(/```(?:json)?\s*([\s\S]*?)\s*```/g, '$1').trim();
    try { return JSON.parse(stripped); } catch { /* fall through */ }
    const match = stripped.match(/\{[\s\S]*\}/);
    if (!match) throw new Error('AIの応答にJSONが含まれていません');
    return JSON.parse(match[0]);
}

// 一時的な接続エラーに備えたシンプルなリトライ処理
async function withRetry<T>(fn: () => Promise<T>): Promise<T> {
    try {
        return await fn();
    } catch (err) {
        console.warn('Sakura AI first attempt failed, retrying...', err);
        return await fn();
    }
}

export async function getAIResponse(userInput: string, children?: ChildRef[]): Promise<AIResponse> {
    return withRetry(async () => {
        const response = await openai.chat.completions.create({
            model: MODEL_NAME,
            response_format: { type: 'json_object' },
            messages: [
                { role: 'system', content: buildIncidentPrompt(children) },
                { role: 'user', content: userInput },
            ],
        });
        const rawText = response.choices[0].message.content || '';
        const parsed = parseJSON(rawText) as Record<string, unknown>;
        return {
            empathy: parsed.empathy as string,
            alternatives: parsed.alternatives as string[],
            insight: parsed.insight as string,
            tip: parsed.tip as string,
            detected_child_name: (parsed.detected_child_name as string | null) ?? null,
        };
    });
}

工夫2:AIによるコンテキストの自動検出とDB連携

親が入力したフリーテキストから、AIが自動的に「どの子どもに対するエピソードか」を判定(検出)するロジックを組み込みました。
ユーザーがあらかじめ登録した子どものリスト(例:「あかり(5歳)」「ひかり(1歳)」)をシステムプロンプトの動的なリストとして渡し、AIに detected_child_name を判定させます。

API Route(/api/chat/route.ts)内では、このAIの検出結果をトリガーにDBの child_id を自動的に紐づけて保存しています。これにより、ユーザーがいちいち「これは長女の話」「これは次女の話」と選択しなくても、あとから子ども別の悩みの傾向などを自動でグラフ化できる体験を実現しました。

5. 実際に動かしてみて

AIによるフィードバック例

例えば、以下のように入力してみます。

ユーザーの入力:
保育園のお迎えのあと、夕飯の準備とお風呂が重なってバタバタしていた。5歳の娘が「おもちゃ片付けない、もっと遊びたい」と泣き叫び、ついカッとなって「もう二度と遊ばないからね!片付けないなら全部捨てるよ!」と大声で怒鳴ってしまった。あとで娘の泣き顔を思い出して、すごく後悔している。

これに対する「さくらのAI Engine」の出力結果:

{
  "empathy": "本当にお疲れ様でした。夕方のバタバタする時間帯に、お片付けが進まないと焦ってしまうのは当然です。感情的になってしまった自分を責めないでくださいね。そうやって後悔し、振り返ろうとしている時点で、あなたは十分お子さんを愛している素敵な親御さんです。",
  "alternatives": [
    "「片付けたくないくらい、今の遊びが楽しかったんだね。でも、ご飯の時間だから一度しまおうか」",
    "「おもちゃを全部捨てるなんて言ってごめんね。ママも焦っちゃったんだ。一緒に少しずつ片付けよう」",
    "「ご飯を食べ終わったら、また一緒に続きをやろうね」"
  ],
  "insight": "夕食の準備やお風呂など「やらなければいけないタスク」が山積している中で、お子さんの『片付けない』という予期せぬ行動が重なり、脳のキャパシティを超えてしまったのが原因だと考えられます。これは愛情不足ではなく、単に物理的・精神的な疲労の限界サインです。",
  "tip": "まずは、今日怒鳴ってしまったことを『ごめんね』と素直に伝えて、ぎゅっと抱きしめてあげましょう。次回の対策として、夕方の忙しい時間はあらかじめ『18時になったらお片付けの音楽を鳴らす』など、事前ルールを親子で決めておくのがおすすめです。"
}

AI特有の機械的な冷たさがなく、子育ての文脈をしっかりと汲み取った、非常にあたたかく実用的なアウトプットが得られています。
特に「完璧な親はいない」という大前提に基づき、親の自己肯定感を下げないような分析と、具体的で使いやすい「別の声かけ」が生成される点が、このアプリで最もこだわったポイントです。

6. まとめ

今回、「さくらのAI Engine」と「Supabase」「Next.js」を組み合わせることで、実用的でかつ非常に高いUXを持った感情サポートアプリ「oyagokoro」を短期間で形にすることができました。

特に、個人開発者にとって「月3,000リクエスト完全無料」のさくらのAI Engineは、コストを恐れずにアイデアを具現化する最高のブースターです。

「oyagokoro」が、毎日がんばるお父さん・お母さんの心を少しでも軽くする一助になれば幸いです。

ぜひ、皆さんも「さくらのAI Engine」を使って、自分の身の回りにある課題を解決するアプリを作ってみてください!

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