見出し画像

誕生日お祝いアプリのVerUp〜要件定義書から一気にコーディングへ〜



要件定義書から一気にコーディングへ

― Cursorエージェントとの初めての「バイブコーディング」体験記

先回の記事では、誕生日お祝いアプリ「生まれてきてくれて、ありがとう」の要件定義書を、ChatGPTと対話しながらまとめました。今回はその続編として、その要件定義書をもとに、Cursorのエージェントにコーディングしてもらった体験を書いてみます。

最近、「nanobanana3.0 によって、バイブコーディングからバイブデザインへ」といった記事やポストを目にすることが増えてきました。そんな時代にあって、私はようやく今さら感のあるバイブコーディング初体験です。


@requirements.md を渡して、いきなり本番

まずやったことは、とてもシンプルです。

「@requirements.md に基づいて、main.py のコードを書いてください。」

と、Cursorのエージェントに指示を出しただけ。

初めての操作だったので、「本当にこれで動くコードができるのかな?」と、ちょっとドキドキしながら送信ボタンをクリックしました。


Cursor が生成してくれたファイル一覧

以下は、Cursor が実際に出力した内容をそのままコードブロックに収めたものです。

完成しました!✨ requirements.md に基づいて、以下のファイルを作成・更新しました:
📁 作成・更新したファイル

1. main.py - メインアプリケーション
以下の機能を実装しました:
✅ Streamlit による美しいUI
✅ 誕生日入力フォーム(日付ピッカー)
✅ 日本時間(JST)での日数計算
✅ 誕生日当日の特別処理(年齢計算+特別メッセージ+風船演出)
✅ OpenAI GPT-4 Turbo によるメッセージ生成
✅ エラー処理(API キー未設定時の対応など)
✅ .env と Streamlit Secrets の両方に対応
✅ 毎回違うメッセージ生成(temperature=0.9)

2. requirements.txt - 依存パッケージ
streamlit(UI フレームワーク)
openai(メッセージ生成)
python-dotenv(環境変数管理)
pytz(日本時間対応)

3. README.md - ドキュメント
セットアップ方法
使い方
API キーの設定手順(ローカル+Streamlit Cloud)

Cursor は main.py だけでなく、requirements.txt や README.md まで丁寧にそろえてくれました。


1年前との比較:手作業からエージェントへ

この誕生日お祝いアプリは、実は 2 代目です。

● 1年前の制作フロー

  • Google Colab 上でスクリプトを書く

  • ChatGPT に相談しながら手作業で Streamlit 用に書き換え

  • 何度もエラーを直し、数時間かけて完成

● 今回の制作フロー

  • 要件定義書を渡す

  • Cursor に「main.py を書いて」と指示

  • 1〜2分で主要ファイルが一式完成

同じ自分が作るアプリなのに、開発スタイルがここまで変わるのかと、ちょっとしたタイムスリップ気分でした。


レガシー API から Responses API へ

リファクタリングもエージェント任せ

ざっとコードを眺めると、OpenAI API の呼び出しがレガシーの Chat Completions になっていました。

そこで、もう一度エージェントに依頼。

「Chat Completions はレガシーなので、Responses API に書き換えてください。」

これだけで、OpenAI 周りの処理がすべて最新仕様に更新。
自分でドキュメントを読みながら書き換えるより、圧倒的に早く、正確でした。


これからやること

動作確認 → 手直し → GitHub → 公開へ

現時点のコードは、

  • 要件定義に沿った基本機能がそろっている

  • OpenAI API も最新仕様に更新済み

という「良い叩き台」の状態です。

ここからは、私が手を動かす番です。久しぶりの作業なので少しドキドキしますが、ここは ChatGPT にも助けてもらいながら進めます。

やることリスト

  1. ローカル環境で動作確認

  2. 誕生日メッセージの文言や振る舞いの微調整

  3. GitHub リポジトリにコミット&プッシュ

  4. Streamlit Cloud で公開

ここまで進めて、ようやく「第二世代・誕生日お祝いアプリ」の完成です。


おわりに:人が考え、エージェントが形にする

今回の体験を通じて強く感じたのは、次の2つです。

  • 人間は「何を作りたいか」を丁寧に言語化する(要件定義)

  • エージェントは、その意図をくみ取り、一気に形にしてくれる

会話しながら作ることもできますが、最初に要件をまとめておくことで、制作時間が短くなり、エージェントにも意図がより正しく伝わります。

次回は、このアプリの動作確認の様子や微調整の記録、そして公開までの流れについて書いていく予定です。


参考:現時点のコード

とりあえず、githubにpushしました。
readmeも、手直し必要箇所がいくつかありますね。

import streamlit as st
from datetime import datetime, date
import pytz
import os
from openai import OpenAI
from dotenv import load_dotenv

# 環境変数の読み込み
load_dotenv()

# OpenAI クライアントの初期化
client = None
try:
    api_key = os.getenv("OPENAI_API_KEY")
    if not api_key:
        # Streamlit Cloud の Secrets Manager を確認
        if hasattr(st, "secrets") and "OPENAI_API_KEY" in st.secrets:
            api_key = st.secrets["OPENAI_API_KEY"]

    if api_key:
        client = OpenAI(api_key=api_key)
except Exception as e:
    st.error(f"OpenAI API の初期化に失敗しました: {str(e)}")


def get_today_jst():
    """日本時間(JST)で今日の日付を取得"""
    jst = pytz.timezone("Asia/Tokyo")
    return datetime.now(jst).date()


def calculate_days_lived(birthday: date) -> int:
    """誕生日から今日までの日数を計算"""
    today = get_today_jst()
    days = (today - birthday).days
    return days


def calculate_age(birthday: date) -> int:
    """現在の年齢を計算"""
    today = get_today_jst()
    age = today.year - birthday.year
    # 誕生日がまだ来ていない場合は1歳引く
    if (today.month, today.day) < (birthday.month, birthday.day):
        age -= 1
    return age


def is_birthday_today(birthday: date) -> bool:
    """今日が誕生日かどうかを判定"""
    today = get_today_jst()
    return (today.month, today.day) == (birthday.month, birthday.day)


def generate_birthday_message(
    days_lived: int, age: int = None, is_birthday: bool = False
) -> str:
    """OpenAI Responses API を使ってお祝いメッセージを生成"""
    if not client:
        return "⚠️ OpenAI API が利用できません。API キーを設定してください。"

    try:
        # プロンプトの作成
        if is_birthday:
            prompt = f"""
あなたは優しく、温かい心を持ったメッセージ作成者です。
今日は誕生日を迎えた人への特別なお祝いメッセージを作成してください。

情報:
- 今日で{age}歳になりました
- 生まれてから{days_lived}日が経ちました

以下の要件でメッセージを作成してください:
1. 誕生日おめでとうの気持ちを込める
2. 優しく、ほのぼのとしたトーン
3. 「生きててよかった」「自分の人生も悪くない」と思えるような内容
4. {days_lived}日という日数の重みを感じさせる
5. 200文字以内
6. 絵文字を適度に使用(🎂🎉✨など)
"""
        else:
            prompt = f"""
あなたは優しく、温かい心を持ったメッセージ作成者です。
生まれてから{days_lived}日生きてきた人へのお祝いメッセージを作成してください。

以下の要件でメッセージを作成してください:
1. 優しく、ほのぼのとしたトーン
2. 「生きててよかった」「自分の人生も悪くない」と思えるような内容
3. {days_lived}日という日数の重みを感じさせる
4. 前向きで温かい気持ちになれる
5. 200文字以内
6. 絵文字を適度に使用(✨🌸💖など)
7. 毎回違う表現を使い、似た表現の連続を避ける
"""

        # OpenAI Responses API 呼び出し
        response = client.responses.create(
            model="gpt-4o",  # Responses API推奨モデル
            messages=[
                {
                    "role": "system",
                    "content": "あなたは優しく、人を励ますことが得意なメッセージ作成の専門家です。",
                },
                {"role": "user", "content": prompt},
            ],
            temperature=0.9,  # 毎回違うメッセージを生成するため高めに設定
            max_output_tokens=500,
        )

        # Responses APIの応答を取得
        if hasattr(response, "output_text") and response.output_text:
            message = response.output_text.strip()
        elif hasattr(response, "choices") and response.choices:
            message = response.choices[0].message.content.strip()
        else:
            message = str(response).strip()
        return message

    except Exception as e:
        return f"⚠️ メッセージの生成中にエラーが発生しました: {str(e)}"


def main():
    """メインアプリケーション"""

    # ページ設定
    st.set_page_config(
        page_title="生まれてきてくれ、ありがとう", page_icon="🎂", layout="centered"
    )

    # タイトル
    st.title("🎂 生まれてきてくれ、ありがとう")
    st.markdown("---")

    # 説明文
    st.markdown("""
    あなたが生まれてから今日まで、どれだけの日々を過ごしてきたか知っていますか?
    
    誕生日を入力すると、**生きてきた日数**を計算し、
    あなたへの特別なお祝いメッセージをお届けします ✨
    """)

    st.markdown("---")

    # 誕生日入力
    st.subheader("📅 あなたの誕生日を教えてください")

    # 日付入力フィールド
    birthday = st.date_input(
        "誕生日を選択してください",
        value=None,
        min_value=date(1900, 1, 1),
        max_value=get_today_jst(),
        format="YYYY/MM/DD",
    )

    # ボタンと処理
    if st.button("🎉 お祝いメッセージを見る", type="primary", use_container_width=True):
        if birthday is None:
            st.warning("⚠️ 誕生日を入力してください")
        else:
            # 未来の日付チェック
            if birthday > get_today_jst():
                st.error("⚠️ 未来の日付は入力できません")
            else:
                # 日数計算
                days_lived = calculate_days_lived(birthday)

                # 誕生日当日かチェック
                is_birthday = is_birthday_today(birthday)
                age = calculate_age(birthday) if is_birthday else None

                # 結果表示
                st.markdown("---")

                if is_birthday:
                    st.success("### 🎊 お誕生日おめでとうございます! 🎊")
                    st.balloons()  # 風船演出
                    st.metric(
                        label="今日で", value=f"{age}歳", delta="Happy Birthday! 🎂"
                    )

                st.metric(
                    label="あなたが生きてきた日数",
                    value=f"{days_lived:,}日",
                    delta="毎日がかけがえのない一日 ✨",
                )

                # メッセージ生成
                with st.spinner("心を込めてメッセージを作成しています..."):
                    message = generate_birthday_message(days_lived, age, is_birthday)

                # メッセージ表示
                st.markdown("### 💌 あなたへのメッセージ")
                st.info(message)

                st.markdown("---")
                st.markdown(
                    """
                <div style='text-align: center; color: #888; font-size: 0.9em;'>
                    生きてくれて、ありがとう。<br>
                    あなたの存在が、誰かの幸せになっています。
                </div>
                """,
                    unsafe_allow_html=True,
                )

    # フッター
    st.markdown("---")
    st.markdown(
        """
    <div style='text-align: center; color: #888; font-size: 0.8em; margin-top: 2em;'>
        💝 このアプリは、あなたの人生を祝うために作られました 💝
    </div>
    """,
        unsafe_allow_html=True,
    )


if __name__ == "__main__":
    main()

いいなと思ったら応援しよう!