誕生日お祝いアプリの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 にも助けてもらいながら進めます。
やることリスト
ローカル環境で動作確認
誕生日メッセージの文言や振る舞いの微調整
GitHub リポジトリにコミット&プッシュ
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()
