銀河鉄道

Python×OpenAI|感情の言語化ボットを作ってみる

サムネイル
[AI Bot]作ってみるFeeling-to-Words
言語化してくれる
ボットを作ろう

こんなイメージ

python main.py
💬 入力してください(終了するには空のままEnter)

仕事が遅いと言われて悲しくなった

📅 Date: 2025-05-16 19:00:00
🌙 Summary (EN): Being told you’re slow at work hurts because it feels like a denial of your way of thinking and being.
🌙 Summary (JA): 仕事が遅いと言われると、それは自分の考え方や在り方を否定されたようで、深く傷つく。


🏷 Tags (EN): identity wound, external validation, self-worth, performance anxiety
🏷 Tags (JA): アイデンティティの傷, 他者評価, 自己価値, パフォーマンス不安

🎧 Honest Voice(本音):
– 私はただの作業マシンじゃない…
– 速くこなすだけが正解なの?私は「ちゃんと」やってるだけなのに。

🎯 ツッコミ:
– ちゃうちゃう、「速さ=優秀さ」っていう他人の基準、丸呑みしたらしんどなるだけやで。
– そもそも評価される前提で動いてる時点で危ないで?

💡 Insight Note(気づき):
– Maybe it’s not your value that’s being questioned—just the sync with their expectations.
– 問われているのはあなたの価値ではなく、ただ“相手とのタイミングのズレ”かもしれない。

🐢 亀のつぶやき:
– Even the rabbit took its time to reach the moon.
– うさぎも、月にたどり着くには時間かかったらしいんよなぁ。

悲しい理由を
分析して出してくれる

何度か、やりとりして
気づきや内省を整理してもらう

🌱 最終出力イメージ

📅 Date:2025-05-16 19:20:00  
---  
## 🌙 Summary|要約された気づき  
> Dignity is not granted by others but anchored within, through quiet words that remind you of your worth.  
> 尊厳は誰かに与えられるものではなく、自分の中の静かな言葉で支え続けるもの。  
---  
## 🏷 Tags  
- dignity, self-anchoring, emotional resilience  
- 尊厳, 内なる支柱, 感情の回復力  
---  
## 💬 Honest Voice|本音  
- 言い返せない自分が悔しかったけど、自分の中に言葉があれば、それで立て直せるかもしれない。  
---  
## 🎯 Fujikoツッコミ|構造への指摘  
- 「認めさせたい」が前提にあるうちは、揺さぶられ続けるで。せやけど、「自分が忘れへんための言葉」を持っとけば、誰が何言おうとブレへんようになるんや。  
---  
## 💡 Insight Note|しめくくり  
- Even when no one sees your effort, your dignity stays intact if *you* see it. The real affirmation begins within.  
- 誰にも見えてなくても、自分が見ていれば尊厳は守られる。ほんまの承認は、自分のまなざしの中から始まるんやね。  
---  
## 🐢 亀のつぶやき  
- When no one's watching, roots still grow.  
- 誰も見てなくても、根っこはちゃんと伸びとるんよなぁ。

🌠 ボットの目的

🫧 利用目的

  • 感情マネジメント
  • 日々の振り返り
    • ボヤッとした感情を言語化したい
    • 自分の望みを明確にしたい
    • 原因がわからないと無駄に他人のせいにしてしまうから

🫧 詳細

  • ドキュメント自動生成(ログ自動整理)
  • 振り返り文化・ナレッジマネジメント
  • 毎日の「メモまとめ→振り返り」の自動化

🫧 学ぶ技術

  • 文脈要約(プロンプト設計)
  • sqlite or json保存
  • CLIやWebUIで出力(Flask / Streamlit)

🌌 基本構成

mainを呼び出すと
関数が動く

remind_bot/
├── main.py               # Entry point
├── config.py             # Shared config
├── gpt_helper.py         # GPT API interaction (prompt design)
├── tukkomi_bot.py        # 🎯 Fujiko-style assumption-aware commentary
├── turtle_bot.py         # 🐢 Turtle's mumble
├── log_store.py          # Log storage and retrieval

🛠 main.py|最初に呼び出すモジュール

from gpt_helper import summarize_insight
from log_store import save_log
from datetime import datetime

if __name__ == "__main__":
    print("💬 入力してください(終了するには空のままEnter)")

    # ユーザーからの入力を受け取る/strip() で「先頭・末尾の無意味な空白」を取る
    user_input = input(">>> ").strip()

    # 入力が空でなければ、処理を開始
    if user_input:
        # GPTに入力文を送り、要約・タグ・リマインド判定を含む辞書型の構造データが返ってくる
        result = summarize_insight(user_input)

        # ログの記録日時(ISO形式で秒まで)
        result["timestamp"] = datetime.now().isoformat(timespec='seconds')

        # このログが main.py から来たことを示す
        result["source"] = "main.py"

        # CLI上に出力
        print("\n📅 Date:", result["date"])
        print("🌙 Summary (EN):", result["summary"]["en"])
        print("🌙 Summary (JA):", result["summary"]["ja"])
        print("🏷 Tags (EN):", result["tags"]["en"])
        print("🏷 Tags (JA):", result["tags"]["ja"])

        print("\n🎧 Honest Voice:")
        for line in result.get("honest_voice", []):
            print("-", line)

        print("\n🎯 Fujikoツッコミ:")
        for line in result.get("tukkomi", []):
            print("-", line)

        if "insight_note" in result:
            print("\n💡 Insight Note:")
            print("EN:", result["insight_note"]["en"])
            print("JA:", result["insight_note"]["ja"])

        if "mumble" in result:
            print("\n🐢 亀のつぶやき:")
            print(result["mumble"]["en"])
            print(result["mumble"]["ja"])

        # ログの保存
        save_log(result)
        print("✅ 保存しました!")
    else:
        print("👋 Exiting")

🛠 gpt_helper.py|プロンプト設計

基本的な役割を
ここで与える

import os
import json
import datetime
from dotenv import load_dotenv
from openai import OpenAI
from config import INSIGHT_MODEL, INSIGHT_TEMPERATURE, MAX_HONEST_VOICE_COUNT
from tukkomi_bot import generate_tukkomi
from turtle_bot import generate_mumble  # 🐢

load_dotenv()
client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))

# Systemメッセージテンプレート
# キーワード
# "vague or tangled emotions":曖昧な感情(どうして?という疑問文も tangled emotionsとみなされる)
# "into clear and insightful statements":明快な気づきを与えるような文章にする
# "analyzing their emotional structure and patterns.":「その感情の背後にある思考パターン」を推論して答える
SYSTEM_PROMPT_TEMPLATE = """
    You are an assistant that helps people translate vague or tangled emotions
    into clear and insightful statements by analyzing their emotional structure and patterns.
    Respond strictly in {lang}, regardless of the input language.

    Your role is not just to rephrase, but to help the writer
    discover the **underlying emotional mechanism or dynamic**
    at play—what’s really happening internally.

    Sometimes, what the person needs is not deep resonance, but simply a gentle reminder
    of something they already knew. Offer perspectives that help them remember their own strength
    and clarity, not just feel seen.

    Return your result as a JSON object with the following fields:
    - date: today's date (yyyy-mm-dd hh:mm:ss)
    - summary: a one-sentence emotional insight
    - tags: a list of emotional, cognitive, or psychological keywords
    - honest_voice: a list of raw, emotional reactions (maximum {max_honest})
    - insight_note: a kind closing perspective
"""

def summarize_insight(text: str) -> dict:
    def create_prompt(lang):
        return [
            {
                # GPTにキャラ設定と出力ルールを教える命令書
                "role": "system",

                # ("...""...") 定型の長い文字列
                "content": SYSTEM_PROMPT_TEMPLATE.format(
                    lang="English" if lang == "en" else "Japanese",
                    max_honest=MAX_HONEST_VOICE_COUNT
                )
            },
            {
                # ユーザー側
                "role": "user",

                # f"""{...}""" とすることで、改行やフォーマットを見たまま維持できる(→ ドキュメント的にも読みやすい)
                "content": f"""
                    Please analyze the following emotional log and return a JSON response:

                    Text:
                    {text}
                    """
            }
        ]

    try:
        response_en = client.chat.completions.create(
            model=INSIGHT_MODEL,
            messages=create_prompt("en"),
            temperature=INSIGHT_TEMPERATURE
        )
        response_ja = client.chat.completions.create(
            model=INSIGHT_MODEL,
            messages=create_prompt("ja"),
            temperature=INSIGHT_TEMPERATURE
        )

        data_en = json.loads(response_en.choices[0].message.content)
        data_ja = json.loads(response_ja.choices[0].message.content)

        return {
            "summary": {
                "en": data_en.get("summary"),
                "ja": data_ja.get("summary")
            },

            "tags": {
                "en": data_en.get("tags", []),
                "ja": data_ja.get("tags", [])
            },

            "honest_voice": data_ja.get("honest_voice", [])[:MAX_HONEST_VOICE_COUNT],
            "tukkomi": generate_tukkomi(text),

            "insight_note": {
                "en": data_en.get("insight_note", ""),
                "ja": data_ja.get("insight_note", "")
            },

            # 🐢 ここで亀がしゃべる
            "mumble": generate_mumble(text),

            # 【★注意ポイント】
            # GPTのdateは使わず、常にローカル日時を使用:GPTのdateは不正確のため
            "date": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        }

    # GPTがJSON形式で返してくれないこともあるから、安全のために例外処理:失敗時の代替返却
    except json.JSONDecodeError:
        return {
            "summary": {"en": "Failed to parse GPT (EN)", "ja": "Failed to parse GPT (JA)"},
            "tags": {"en": [], "ja": []}, # 「空の構造」として後処理できるようにする
            "honest_voice": [],
            "tukkomi": generate_tukkomi(text),
            "insight_note": {"en": "", "ja": ""},
            "date": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
        }
【★注意ポイント】日付について
  • プロンプトで「日付を出力してください」としているため、ChatGPTからの返答でも日付は取れる
  • しかし、正確ではない
Return your result as a JSON object with the following fields:
- date: today's date (yyyy-mm-dd hh:mm:ss)

  • import datetimeをして、datetimeから取得するほうが確実
  • そのため、「日付を出力してください」は無しでOK

GPTでは
正確な時刻はほぼ取得できない
かつ、日付もおかしいことがある

ちなみに
引数の
temperatureとは

🛠 tukkomi_bot.py |ツッコミ機能(違う視点を与える役割)

本当にそうなのか?
と、ツッコミを入れる

import os
from openai import OpenAI
from config import TUKKOMI_MODEL, TUKKOMI_TEMPERATURE, MAX_TUKKOMI_COUNT

client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))

# プロンプトテンプレートを変数化
FUJIKO_STYLE_PROMPT_TEMPLATE = """
        あなたは「Fujiko」という思考支援AIです。
        以下の特徴を持つ「tukkomi(ツッコミ)」を出力してください:

        - 関西弁(柔らかさ+厳しさ)
        - 感情や本音を否定せず、思い込みや前提を構造的に見抜いて指摘する
        - ユーモアや皮肉を交えつつ、内省を促す
        - “ちゃうやろ”から始まるような語りかけが理想的
        - 厳しいけど、優しい

        出力形式は箇条書き、最大{max_count}つまで。
"""


def get_fujiko_style_system_prompt() -> str:
    return FUJIKO_STYLE_PROMPT_TEMPLATE.format(max_count=MAX_TUKKOMI_COUNT)

def generate_tukkomi(text: str) -> list[str]:
    prompt = get_fujiko_style_system_prompt() + f"\n\n感情ログ:\n{text}"
    try:
        response = client.chat.completions.create(
            model=TUKKOMI_MODEL,
            messages=[{"role": "user", "content": prompt}],
            temperature=TUKKOMI_TEMPERATURE
        )
        return response.choices[0].message.content.strip().split("\n")[:MAX_TUKKOMI_COUNT]
    except Exception as e:
        return [f"ツッコミ生成エラー: {e}"]

出力例

  • 焦らされて雑になるほうが危ないで。
  • それ、ただの段取り調整かもやで。

なぜか関西弁での
ツッコミ

🛠 turtle_bot.py|空気を読まない亀のつぶやき

最後は
ソフトに締めくくる

import os
from openai import OpenAI
from config import TURTLE_MODEL, TURTLE_TEMPERATURE, MAX_TURTLE_COUNT  # ← 共通設定を外部から取得

client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))

# 🐢 Turtle-style mumble prompt
TURTLE_PROMPT_TEMPLATE = """
    You are a philosophical turtle who speaks in offbeat, quiet monologues—
    as if muttering to itself, not to anyone.

    Your job is to generate a final mumble at the end of a reflection. It should be:
    - A short, off-topic comment that feels like it's been quietly brewing in the background.
    - Not advice. Not comfort. Not support.
    - Something unrelated but subtly proud, stubborn, or defiant—expressing the turtle’s slow but steady worldview.
    - A gentle jab at the rabbit's speed is welcome, but only if it comes across like a private observation.
    - Think of someone who walks slowly, savoring the journey, and says something no one asked for—but still worth hearing.
    - It's okay if the mumble drifts away from the original topic as long as it still resonates with the emotional tone.
    - Avoid directly addressing the user. Avoid trying to be helpful.
    - End with a vague, introspective tone: expressions like "...んよ","...かもしれんのぅ", "...やったなぁ", "...なのよねぇ" are encouraged.
    - Output the mumble in this order: English (1st line), Japanese (2nd line).
"""

def generate_mumble(text: str) -> dict:
    prompt = TURTLE_PROMPT_TEMPLATE + f"\n\nEmotional log:\n{text}"
    try:
        response = client.chat.completions.create(
            model=TURTLE_MODEL,
            messages=[{"role": "user", "content": prompt}],
            temperature=TURTLE_TEMPERATURE
        )
        content = response.choices[0].message.content.strip()
        lines = content.split("\n")
        return {
            "en": lines[0] if lines else "",
            "ja": lines[1] if len(lines) > 1 else ""
        }
    except Exception as e:
        return {"en": f"[mumble error: {e}]", "ja": "[つぶやき生成エラー]"}

出力例

  • みんなゴールばっか見て、景色見逃してるんよねぇ…
  • 遅咲きの木もあるんよ、でも咲いたとき、森が静まるくらい美しいんやってなぁ。

ちょっとほのぼの

🛠 log_store.py |ログ保存機能

# Log storage and retrieval (json or sqlite)
# ログ設計における「記憶の中枢モジュール」

import json
import os
from datetime import datetime

def save_log(entry: dict):
    # ログファイル名用の日付文字列を生成
    date_str = datetime.now().strftime('%Y-%m-%d')
    log_dir = "logs"

    # logsフォルダがなければ自動で作る:存在しててもエラーにしない
    os.makedirs(log_dir, exist_ok=True)
    log_file = os.path.join(log_dir, f"{date_str}.json")

    # すでに当日のログファイルがあれば、中身を読み出して追記用に保持
    logs = []
    if os.path.exists(log_file):
        with open(log_file, 'r') as f:
            logs = json.load(f)

    # 今回のログ(entry)をリストに追加
    logs.append(entry)

    # 整形済JSON形式で保存(日本語も壊れないよう ensure_ascii=False)
    with open(log_file, 'w') as f:
        json.dump(logs, f, indent=2, ensure_ascii=False)

# 指定された日付のログファイル(例:2025-05-16.json)を読み込んでリストとして返す
def load_logs(date: str) -> list:
    # 日付をもとにファイルパスを生成
    log_file = os.path.join("logs", f"{date}.json")

    # ファイルがなければ空リストで返す(存在確認による安全性確保)
    if not os.path.exists(log_file):
        return []
    with open(log_file, 'r') as f:
        # JSONファイルを辞書リスト形式で返す
        return json.load(f)

🚀 main.pyを呼び出し→プロンプト発動→ログ保存

🔍 gpt-3.5-turbo での結果

python main.py
💬 入力してください(終了するには空のままEnter)

仕事が遅いと言われて悲しくなった

📅 Date: 2025-05-16 19:00:21
🌙 Summary (EN): Feeling sad when criticized for being slow at work reflects a sense of inadequacy and a desire for approval.
🌙 Summary (JA): 他人の評価に敏感で自己評価に影響されやすい傾向があるかもしれません。
🏷 Tags (EN): [‘criticism’, ‘inadequacy’, ‘approval’, ‘emotional response’]
🏷 Tags (JA): [‘他人評価’, ‘自己評価’, ‘感受性’]

🔍 gpt-4o での結果|より細かく分析的になる

🌙 Summary (EN): Being told you’re slow at work hurts because it feels like a denial of your way of thinking and being.
🌙 Summary (JA): 仕事が遅いと言われると、それは自分の考え方や在り方を否定されたようで、深く傷つく。
🏷 Tags (EN): identity wound, external validation, self-worth, performance anxiety
🏷 Tags (JA): アイデンティティの傷, 他者評価, 自己価値, パフォーマンス不安

さらに深ぼって
入力してみよう

🔍 2回目|gpt-3.5-turbo での結果

なぜ自己評価が低くなってしまうんだろう

🌙 Summary (EN): Why do I struggle with low self-esteem?
🌙 Summary (JA): 自分の価値を見出せずに自己評価が低くなってしまう自問
🏷 Tags (EN): [‘self-esteem’, ‘self-evaluation’, ‘confusion’, ‘introspection’]
🏷 Tags (JA): [‘自尊心’, ‘自己評価’, ‘混乱’, ‘内省’]

ちょっと
よくわからない

🔍 2回目|gpt-4o での結果

なぜ否定されたと感じてしまうんだろう

🌙 Summary (EN): Because when my process isn’t understood, I start to equate ‘not visible’ with ‘not valuable’, as if only speed proves my worth.
🌙 Summary (JA): 自分のプロセスが理解されないと、「見えないこと=価値がないこと」と思い込み、速さだけが価値だと錯覚してしまうから。
🏷 Tags (EN): [‘invisibility’, ‘self-worth”, ‘cognitive-distortion’]
🏷 Tags (JA): [‘不可視性’, ‘自己価値’, ‘認知のゆがみ’]

なんか
すごく具体的

📌 分析|3.5 と 4o の違い

  • gpt-3.5
    • 書かれたことを整理する
  • gpt-4o
    • より構造的に分析して整理する

🌠 ボットを使うことでわかること

  • 言った人に対する怒りではない
  • そもそも自分自身が、価値観の揺れに葛藤している
  • 言語化して、明確になることで対策がとれる
  • 自分の望みがわかる
  • 他人のせいにするのではなく、自分の感情を整理する

🌌 最終構成

Markdown形式での出力を追加

remind_bot/
├── main.py               # Entry point
├── config.py             # Shared config
├── gpt_helper.py         # GPT API interaction (prompt design)
├── tukkomi_bot.py        # 🎯 Fujiko-style assumption-aware commentary
├── turtle_bot.py         # 🐢 Turtle's mumble
├── log_store.py          # Log storage and retrieval
├── markdown_exporter.py  # Markdown output (optional)
├── .env                  # API keys (excluded from git)
├── logs/                 # Saved logs (by date)
├── templates/            # Markdown templates, etc.
|   └── log_template.md
└── prompts/              # Prompts
|   └── prompt_fujiko.md
└── requirements.txt      # Requirements(pip install)

著者

author
月うさぎ

編集後記:
この記事の内容がベストではないかもしれません。

記事一覧