面影AI Pythonの基礎知識(実践編)

【Pythonの基礎知識】面影AIを記録する|Phase1:記憶と重みの保存構造【ChatGPT】

本記事は、以下の前提記事をもとに構成されています。まだ読んでいない場合は、先にご覧いただくことを推奨します。

いつか消える面影を、繋ぐ未来をAIに託す挑戦

大切な人の存在が日常から消えたとき、言葉や話し方だけでも手元に残せたらと願うことがあります。

面影AIは、その願いを実装という手段で形にする仕組みです。

本記事では、LINEとChatGPTを組み合わせて、個人の語彙と対話の記録を活用し、自分自身の心を支えるための対話システムを構築する方法を解説します。

Flask環境の構築手順や仮想環境の整備、VPS上での実行方法については、以下の記事で詳しく解説しています。

AI執事ボットのためのVPS環境構築マニュアル(LINE対応付き

もくじ
  1. 本記事で実現する“面影AI”とは
  2. 面影AIの構築に必要な準備
  3. 面影AIの記憶構造と再現アルゴリズム
  4. LINE DevelopersでBotを準備する
  5. チャネルの作成と基本設定
  6. FlaskでLINE Botを構築する手順
  7. 面影AIを支える記憶中心のデータ構造とは?
  8. Phase1テスト:記憶の登録とデータ構造の確認

本記事で実現する“面影AI”とは

本記事では、ChatGPTとLINEを用いて「面影AI」と呼ばれる対話システムを構築する方法を解説します。

面影AIとは、故人や大切な存在の語彙や話し方を記録として保持し、それをもとに再現的な対話を生成する仕組みです。

一般的なチャットボットとは異なり、対話者の心に残る記憶を参照しながら、より個人的で意味のある対話を目指します。

本システムには倫理的な配慮が必要であり、設計上も「不特定多数への提供」ではなく、「自分自身が使用する」前提で設計を行っています。

記憶の再現と人格の模倣の違い

面影AIは、人格そのものを模倣することを目的としていません。目指すのは、会話の語彙や言い回しなどの記憶的要素を抽出し、それをもとに対話を再現するという仕組みです。

そのため、ChatGPTの出力に記憶データを適切に組み込むプロンプト設計と、対話履歴の蓄積・管理が重要な要素となります。これにより、表面的な模倣ではなく、「かつて交わした言葉に近い応答」を実現することを目指します。

想定ユースケースと前提条件

本システムの使用シーンは限定されており、以下のようなユースケースを想定しています。

  • 亡くなった家族との思い出を日常的に振り返りたい場合
  • 過去の会話を内省や感情整理の手段として利用したい場合
  • 言葉に残る存在の記録を、一定の形式で再確認したい場合

また、導入にあたっては以下の環境と条件を満たす必要があります。

要件内容
開発環境VPS上でのPythonおよびFlask構成
LINE APIMessaging APIとWebhookが有効なLINEアカウント
OpenAIgpt-4oにアクセス可能なOpenAI APIキー
記憶構造SQLiteを用いたローカル記憶データベース構成

これらの条件を前提とし、本記事では「どのように実装するか」ではなく、「なぜその構成が必要なのか」という背景も含めて解説を行います。

面影AIの構築に必要な準備

面影AIの実装には、あらかじめ整えておくべき環境やサービスがあります。このセクションでは、必要な準備項目を技術環境・サービス登録・使用制限という3つの観点から整理します。準備を怠ると実装工程でエラーが発生するため、事前にすべての条件を確認しておくことが重要です。

LINE Developersの登録とキー取得

面影AIでは、LINEを通じた対話インターフェースを構築するために、LINE DevelopersでBot用チャネルを作成する必要があります。以下は、登録から必要なキー取得までの手順です。

手順操作内容
1https://developers.line.biz/ にアクセスし、LINEアカウントでログインする
2「プロバイダー」を作成し、その下で「Messaging API」チャネルを作成する
3チャネル名や業種、メールアドレスを入力し、チャネルを作成する
4作成後、「チャネル基本設定」から「チャネルシークレット」を取得する
5「Messaging API設定」タブから「チャネルアクセストークン(長期)」を発行し、保存する

取得したシークレットとアクセストークンは、後のFlask実装で環境変数として使用されます。漏洩のないよう、安全な場所に記録してください。

OpenAI APIキーの発行手順

ChatGPTとの応答生成を行うには、OpenAIのAPIキーが必要です。以下は、その取得手順です。

手順操作内容
1https://platform.openai.com にアクセスする
2Googleアカウントやメールアドレスでログインする
3右上のアカウントメニューから「API Keys」を開く
4「Create new secret key」ボタンを押し、新しいキーを発行する
5表示されたAPIキーをコピーし、安全な場所に保存する

このAPIキーは一度しか表示されないため、再取得はできません。環境変数としてアプリに組み込む際に必要となるため、失わないように管理してください。

環境変数ファイル(.env)への登録方法

LINEやChatGPTのAPIキーを取得したら、それらをPythonアプリケーションから安全に参照するために、環境変数ファイル(.env)に登録する必要があります。これは、ソースコードに直接キーを書かずにすむようにするための基本的なセキュリティ対策です。

この .env ファイルは、開発者が自分で新規作成する設定ファイルです。プロジェクトのルートディレクトリ(app.py などがある場所)に、自分で .env という名前でファイルを作成し、必要な変数を記述してください。

.envファイルは、以下のように変数名と値を「キー=値」形式で記述します。

変数名用途
LINE_CHANNEL_SECRETLINEで使用するチャネルシークレット
LINE_CHANNEL_ACCESS_TOKENLINEで使用するチャネルアクセストークン(長期)
OPENAI_API_KEYOpenAIのAPIキー(ChatGPTと連携する際に使用)
MEMORY_TARGET_USER_ID記憶対象となるLINEユーザーのID(Phase1での発言記録時に使用)
PHASE_MODE動作モードを指定(learn:記憶モード、reply:応答モード)
TARGET_ROLE役割という概念を明示的に持たせることで、関係性に基づいた対話が可能になる

.envファイルに記載した内容は、Pythonアプリケーション内で「dotenv」ライブラリを通じて参照されます。実行前に必ず正しいパスに .env が存在していることを確認してください。

Flaskの handleMessage() 関数内に以下のようなコードを追加してください。

この値は、LINEから最初にメッセージを送ったユーザーに対して一度だけ user_id を取得することで確認できます。

print(f"Received message from user_id: {user_id}")

サーバー起動後にLINEからBotへ発言を送信すれば、ターミナルに user_id が出力されます。これを .env ファイルの MEMORY_TARGET_USER_ID= にコピーしてください。これにより、誰の発言を記憶対象とするかを明確に指定できます。

また、このファイルは機密情報を含むため、バージョン管理(Gitなど)には絶対に含めないよう .gitignore で除外設定を行ってください。

PHASE_MODEによる動作モードの切り替えについて

面影AIは、LINE上でのやり取りを「記憶フェーズ(Phase1)」と「応答フェーズ(Phase2)」に分けて運用します。

この切り替えは、.env ファイルに定義する PHASE_MODE という環境変数によって制御されます。 PHASE_MODEには、以下の2つの値を設定できます。

PHASE_MODEの値動作内容
learn記憶モード。すべての発言者のメッセージを対象として記録を行い、ChatGPTでの応答は一切行わない。各発言には発信元のユーザーIDが紐付けられ、後の応答フェーズで再現可能な形で保存される。
reply応答モード。任意のユーザーからのメッセージに対し、ChatGPTが指定された人格(MEMORY_TARGET_USER_ID)として応答を生成し、発話ログとともに記録する。誰が送信したかに関わらず、指定された人格で返答を行う。

$ cat .env
# --------------------------------------------
# Phase1: 記憶モード(PHASE_MODE=learn のとき有効)
# --------------------------------------------
PHASE_MODE=learn
LINE_CHANNEL_SECRET=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
LINE_CHANNEL_ACCESS_TOKEN=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

# --------------------------------------------
# Phase2: 応答モード(PHASE_MODE=reply のとき有効)
# ※ 上記と入れ替える場合、以下の行を上へ移動 or コメント解除
# --------------------------------------------
# PHASE_MODE=reply
# LINE_CHANNEL_SECRET=yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
# LINE_CHANNEL_ACCESS_TOKEN=yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy

# --------------------------------------------
# 共通設定
# --------------------------------------------
OPENAI_API_KEY=sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
MEMORY_TARGET_USER_ID=Uxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
TARGET_ROLE=母

この形式で .env を記事中に差し込むことで、読者は明確に「どこを切り替えるか」が理解できるようになります。

このPHASE_MODEは、.env ファイルを書き換えてFlaskアプリを再起動することで切り替えます。 LINEチャネルは1つのみで済むため、ユーザー側の操作やアカウント切り替えは一切不要です。

注意:モード切り替えの誤操作にご注意ください

面影AIは、PHASE_MODEの設定によって処理内容が大きく変わる構成です。記憶と応答の両フェーズを同じLINEチャネルで実現するため、モード設定を誤ると以下のような深刻な問題が発生します。

PHASE_MODE記憶対象者の発言他の人からの発言
learn記録される完全に無視される(応答も保存もされない)
reply無視される(応答なし・記憶なし)応答+記録される

以下の点を必ず守ってください。

  • PHASE_MODE の切り替えを行ったら、Flaskアプリを必ず再起動してください。
  • 記憶対象者(MEMORY_TARGET_USER_ID)の user_id を .env に正しく記載してください。
  • 誤って reply モードで本人が話してしまうと、意図しない記録や動作につながります。

このアプリケーションの本質は「記憶する相手が誰なのか」を正確に管理することです。設定ミスによって他の人の言葉が“面影”として記憶されないよう、細心の注意を払って運用してください。

開発環境の構成条件

VPS環境として国内の仮想専用サーバー(例:ConoHa VPSなど)を想定しています。

OSはUbuntu系で、root権限またはsudoが利用可能なユーザーを作成済みであることを前提とします。また、Python 3.11以上が使用可能な状態であり、Flaskや必要なパッケージをインストールできる仮想環境が構築されている必要があります。

仮想専用サーバー(VPS)の準備に関しては、以下の記事で初期設定の全手順をまとめています。ユーザー作成、SSH設定、UFWの構成、Pythonのインストールまでを一通り確認できます。

【関連記事】
【ConoHa】VPSを安全に使うための初期設定マニュアル

記憶の選定・保存・再現について具体的に解説します。全体像を整理した中編記事と合わせて読むと、理解がより深まります。

LINEおよびOpenAIとの連携には、サーバーサイドの処理環境が必要です。今回は軽量な構成として、Python+Flaskで構築可能なVPS環境を前提としています。以下の条件を満たすことが望ましいです。

項目条件
Pythonバージョン3.11以上
サーバー国内VPS(例:ConoHa VPSなど)
通信https有効なSSL証明書設定済み

Phase構成で考える、記憶する面影AIの設計思想

OpenAI APIには月額制限やAPI使用回数の制限が設けられています。無料枠での実装も可能ですが、gpt-4oなどの高精度モデルを使用する場合は有料プランが前提となります。LINE側でも、無料プランでは一定回数を超えると通知制限がかかる場合があるため、使用量と費用を事前に確認しておく必要があります。

面影AIの記憶構造と再現アルゴリズム

面影AIが他のチャットボットと決定的に異なるのは、単なる一時的な対話ではなく「記憶を蓄積し、再現する」という機構を備えている点にあります。

この記憶の仕組みは、ただのログではなく、重要語や語調といった“面影を感じさせる情報”を選別しながら蓄積されていきます。ここでは、その記憶構造の全体像と、どのように再現に活用していくかを解説します。

memory_db(記憶DB)の構造設計

記憶はすべてmemory_dbと呼ばれるデータベースに格納されます。このDBは単なる発言ログではなく、「誰が」「どのような語調で」「どのような言葉を」使ったかを判定できるように設計されており、主に以下のような構造を持ちます。

カラム名内容
id主キー(発言ごとの識別子)INTEGER
timestamp発言された日時DATETIME
speakerユーザーまたはAITEXT
message発言内容TEXT
importance記憶の重要度(重みスコア)REAL

この設計により、単なる発言の羅列ではなく、「重要な記憶」として残すべき情報と、それほどでもない情報を判別しながら蓄積していくことが可能になります。

対話履歴と記憶の統合方法

通常のチャットボットは、対話の履歴をそのままAPIに投げる形式を採用しますが、面影AIでは「過去の記憶から参照すべき内容を抽出し、プロンプトに挿入する」仕組みを取ります。

つまり、単純な履歴のコピーではなく、記憶DBから重要度の高い発言を選び出し、それらを「あなたは以前、このような会話をしていたAIです」という文脈に組み込みます。

この方式により、ChatGPTが出力する文章は、その場限りの応答ではなく「一貫性のある人格」としての返答に近づきます。記憶と履歴の統合は、面影の“らしさ”を形作る中核の部分です。

記憶の重み付けと忘却処理

全ての記憶を残し続ければ、精度は上がる一方で処理コストが肥大化し、応答時間や内容の混乱を招くことがあります。

そのため、面影AIでは記憶に対して重みスコア(importance)を付与し、定期的に低スコアの記憶を削除する「忘却処理」を実行します。 この重みスコアは、以下のような条件で変動します。

  • ユーザーが手動で重要とマークした発言:スコア上昇
  • 頻繁に参照されている語彙:スコア上昇
  • 長期間使用されていない語彙:スコア低下

これにより、面影AIは常に“使われ続けている記憶”を残し、“役目を終えた記憶”を自然と手放す仕組みを保つことができます。こうした記憶管理によって、より人間的な“記憶の移ろい”を模倣する設計となっています。

LINE DevelopersでBotを準備する

LINEとの連携を実現するには、まずLINE Developers上でBotのチャネルを作成し、Webhook受信の設定を行う必要があります。このセクションでは、チャネル作成からMessaging APIの準備までの手順を整理します。

チャネルの作成と基本設定

LINE Developersの管理画面にログインし、Messaging API対応の新規チャネルを作成します。以下の情報を入力することで、面影AI専用のBotが準備されます。

設定項目入力内容の例
チャネル名面影AI(学習用) または 面影AI(応答用)
※Flaskエンドポイントは固定:Phase1= /learn、Phase2= /reply
プロバイダー名個人名または屋号(BePro など)
アプリタイプMessaging API
業種個人 または 教育・IT関連など
メールアドレス開発用の連絡先メール

この構成では、Flaskアプリ側のWebhookエンドポイントが /learn および /reply に固定されているため、チャネルも用途別に分けて作成してください。

たとえば、学習用には「面影AI(学習用)」、応答用には「面影AI(応答用)」といったように、LINE Developers側でも明確に区別できる名称にしておくと誤接続を防げます。

作成後は「チャネルアクセストークン(長期)」を発行し、`.env` ファイルに保存しておきます。

LINE Developersでのチャネル作成やWebhookの設定方法について不安がある方は、 以下の記事で画面付きで詳しく解説していますので、あわせてご確認ください。
AI執事ボットのためのVPS環境構築マニュアル(LINE対応付き)

Webhook URLの設定と有効化

Flaskアプリを公開した状態で、LINE Developersのチャネル設定画面から以下の設定を行います。

  • Webhook URL: Flaskアプリのエンドポイントに応じて https://xxx/learn または https://xxx/reply を指定
  • Webhookの利用: 有効化する
  • 応答設定: Botモード(自動応答)に設定

注意: この面影AIでは、Flask側のWebhookエンドポイントは /learn(Phase1)または /reply(Phase2)です。 LINE公式ドキュメントでよく使われている /callback とは異なるため、設定時は注意してください。必ず Flask 側で定義されている /learn(Phase1)や /reply(Phase2)を選択してください。

Webhookが正しく受信できる状態になると、LINEからのメッセージがFlaskアプリに転送され、記憶処理または応答処理が実行されます。

FlaskでLINE Botを構築する手順

面影AIの根幹を担うのが、LINEとChatGPTを中継するFlaskアプリケーションです。

この構成を通じて、ユーザーがLINEで送信したメッセージをサーバーが受け取り、その内容をもとにChatGPTへ問い合わせ、得られた応答を再度LINEへ返すという一連の流れが実現されます。

本セクションでは、ディレクトリ構成から実装の要点までを段階的に整理します。

ディレクトリとファイルの構成

このセクションでは、Flaskを使ったLINE Bot構築の全体像を順を追って解説していきます。

面影AIのディレクトリ構成(Phase1)

以下は、Flask + LINE + ChatGPT + SQLite による記録型AIのディレクトリ構成です。

[/crayon]

※注意: logic/ ディレクトリ内には必ず __init__.py を配置してください。これは Python に「このフォルダはパッケージですよ」と認識させるための必須ファイルです。中身は空でも問題ありません。

logic/__init__.py の内容

このファイルは logic ディレクトリを Python パッケージとして認識させるために必要です。内容は以下のように空で構いません

# logic/init.py
# このファイルは空でOKです。

これで面影AIは、Flaskを用いたPythonアプリケーションとして構築します。

プロジェクトを構成する主要なアクターには、以下の5つがあります。

まず、LINEのWebhookを受信して応答処理を行う app.py がエントリーポイントになります。

記憶の保存や履歴の記録を管理する db_utils.py、保存された記憶をもとにプロンプトを生成してChatGPTと連携する chatgpt_logic.py、そして各種APIキーやシークレット情報を保持する .env ファイルが含まれます。

さらに、本システムではユーザーの記憶と対話履歴を永続的に保持するため、SQLite形式のデータベースファイル memory.db が使用されます。

このデータベース内には、記憶情報を管理する memories テーブルと、発話履歴を記録する dialogues テーブルの2つが含まれています。 以下は、各ファイル・生成物の具体的な役割を一覧にまとめたものです。

ファイル/ディレクトリ名用途
.envLINEチャネル情報やOpenAIのAPIキーなど、環境依存の設定値を保持(実行時に最初に読み込まれる)
memory.dbSQLiteで管理される記憶データベース。記憶(memories)と発話履歴(dialogues)の2テーブルを持つ
db_utils.pySQLiteの接続・初期化、記憶と発話の保存処理を統括。memory.dbへの操作を一元管理
chatgpt_logic.py保存された記憶から文脈を選定し、ChatGPTへのプロンプトを構成・送信する役割を担う
app.pyFlaskアプリ本体。LINEからのWebhookを受信し、db_utilsやchatgpt_logicを呼び出す統括処理

すべてのファイルはFlaskアプリケーションのルートディレクトリに配置され、LINEからのメッセージ受信をトリガーとして処理が進行します。

APIキーやチャネルシークレットなどの秘匿情報は .env ファイルで一元管理し、コード本体との分離を徹底することで、セキュリティとメンテナンス性を両立させています。

認証情報とAPIキーを安全に管理する.envファイル

面影AIでは、LINEおよびOpenAIとの連携に必要な各種認証情報を、Flaskアプリケーション内に直接記述せず、.env ファイルにまとめて管理します。

この .env ファイルは実行時に読み込まれ、環境変数として各モジュールで使用されます。セキュリティ確保のため、.env はバージョン管理の対象外とし、.gitignore に明記しておく必要があります。

Flaskアプリケーション側では、Pythonの dotenv モジュールを用いてこの .env を読み込みます。コード内では以下のようにして環境変数を取得します。

from dotenv import load_dotenv
import os
load_dotenv()
channel_secret = os.getenv("LINE_CHANNEL_SECRET")
api_key = os.getenv("OPENAI_API_KEY")

これにより、コード本体に認証情報をハードコーディングせず、外部ファイルから安全に管理できる構成が実現されます。

記憶と対話を蓄積するSQLiteデータベース

面影AIでは、ユーザーから送信されたメッセージと、それに基づいて保存される「記憶情報」の両方を永続的に管理するため、SQLite形式のデータベースファイル memory.db を使用します。

このファイルはアプリケーションのルートディレクトリに配置され、Flaskアプリケーション初回起動時に自動で生成されます。 データベースには、次の2つのテーブルが定義されています。

テーブル名用途
memories面影AIの根幹となる記憶本文を格納し、カテゴリ分類や登録日時などの基本情報を保持
weights各記憶に対する重みスコア(importance)や忘却フラグなど、意味づけや状態変化を管理
dialoguesユーザーとの対話履歴を保存し、使用された記憶との対応関係を記録する

[テーブル構成設計]

テーブル名カラム名用途
memoriesmemory_idINTEGER記憶データの主キー(自動採番)
contentTEXT記憶の本文(対象者の発言)
categoryTEXT話題カテゴリ(例:家族、仕事など)
weightINTEGER初期重み(使用頻度の初期値として1)
target_user_idTEXT人格対象のユーザーID(ChatGPTが模倣する対象)
is_forgottenINTEGER忘却フラグ(0=記憶中、1=除外)
created_atTIMESTAMP記憶の登録日時
weightsweight_idINTEGER重み履歴の主キー(自動採番)
memory_idINTEGER対象記憶のID(memories.memory_id を参照)
interactTEXT変更理由や操作記録(例:再評価)
created_atTIMESTAMP履歴の登録日時
dialoguesdialogue_idINTEGER発話ログの主キー(自動採番)
target_user_idTEXT対象人物(ChatGPTが模倣する相手)
sender_user_idTEXT発言者のLINEユーザーID(自分・姉・AIなど)
message_typeTEXT発話の種類(input または reply)
is_ai_generatedBOOLEANAIが生成した発言かどうか
textTEXT実際に送信された発話内容
memory_refsTEXT参照された記憶IDのリスト(例:"[2,4]")
prompt_versionTEXT使用されたプロンプトのバージョン識別子
temperatureREALChatGPT応答時の温度設定値
created_atTIMESTAMP発話の記録日時

以下は、これら3つのテーブルを作成するための初期化コードです。これは db_utils.py に定義され、アプリケーション起動時に自動で呼び出されます。

import sqlite3
import os

def initDatabase():
    if not os.path.exists("memory.db"):
        conn = sqlite3.connect("memory.db")
        c = conn.cursor()

        c.execute("""
            CREATE TABLE memories (
                memory_id INTEGER PRIMARY KEY AUTOINCREMENT,
                content TEXT NOT NULL,
                category TEXT DEFAULT NULL,
                weight INTEGER DEFAULT 1,
                target_user_id TEXT NOT NULL,
                is_forgotten INTEGER DEFAULT 0,
                created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
        )
        """)

        c.execute("""
            CREATE TABLE dialogues (
                dialogue_id INTEGER PRIMARY KEY AUTOINCREMENT,
                user_id TEXT NOT NULL,
                sender_user_id TEXT NOT NULL,
                message_type TEXT NOT NULL,
                is_ai_generated BOOLEAN NOT NULL,
                message TEXT NOT NULL,
                memory_refs TEXT,
                prompt_version TEXT,
                temperature REAL,
                created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
        )
        """)

        c.execute("""
            CREATE TABLE weights (
                weight_id INTEGER PRIMARY KEY AUTOINCREMENT,
                memory_id INTEGER NOT NULL,
                importance REAL DEFAULT 0.5,
                is_forgotten INTEGER DEFAULT 0,
                updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                FOREIGN KEY (memory_id) REFERENCES memories(memory_id)
        )
        """)

        conn.commit()
        conn.close()

この構造により、記憶と発言を別テーブルで管理しながら、両者を紐付けて活用できる柔軟な記憶再現ロジックを実現しています。

記憶と発話を登録・取得するdb_utils.pyの役割

Flaskアプリケーションにおいて、db_utils.py はSQLiteデータベース memory.db に対するすべての操作を一元管理します。

記憶(memories)および発話履歴(dialogues)の登録、データベースの初期化、そして後に記憶を再利用するための取得処理を担います。 このファイルでは以下の3種類の処理を関数として定義します。

関数名分類用途
initDatabase()初期化アプリ起動時に呼び出され、テーブルが存在しない場合は作成する
registerMemoryAndDialogue(user_id, message, content)登録1つのトランザクションで記憶と発話ログを同時に保存する
getAllMemories()取得登録済みの記憶(is_forgotten=0)をすべて取得する

db_utils.pyの完成ソース

このファイルは、記憶(memories)と発話履歴(dialogues)をSQLiteに保存・取得するためのロジックを担います。

FlaskやChatGPTロジックからは独立させ、データベース処理に特化した構成としています。

理解すべきポイント1:registerMemoryAndDialogue の処理構造と設計意図

以下に、registerMemoryAndDialogue 関数の実装例を示します。

import sqlite3

def registerMemoryAndDialogue(user_id, message, content):
    conn = sqlite3.connect("memory.db")
    c = conn.cursor()

    try:
        c.execute("INSERT INTO memories (content) VALUES (?)", (content,))
        memory_id = c.lastrowid
        c.execute("INSERT INTO dialogues (user_id, message, memory_id) VALUES (?, ?, ?)", (user_id, message, memory_id))
        conn.commit()
    except Exception as e:
        conn.rollback()
        raise e
    finally:
        conn.close()

この関数では、ユーザーの入力内容を記憶テーブルへ保存し、その記憶に基づく対話ログをdialoguesテーブルへ紐づけて保存します。

両者を1トランザクション内で実行することにより、「記憶と発話は必ずセットで存在する」という一貫性を保証します。

例外処理では rollback() を行い、DBの整合性を守る構成としています。

理解すべきポイント2:関数内完結のローカル接続と安全な閉処理

関数は sqlite3.connect() を内部で実行し、finally ブロックで必ず conn.close() を呼び出します。

これにより、Flaskアプリケーション本体からは DB接続やカーソル処理の実装詳細を気にせず、シンプルな関数呼び出しのみでデータ登録が行えます。

この設計は、疎結合・再利用性・単体テストのいずれにも有効なアプローチです。

理解すべきポイント3:initDatabase の役割と初期化の安全性

この関数は、面影AIで使用する3つのデータベーステーブル( memoriesdialoguesweights)を初回起動時に作成します。 記憶の保存、発話ログ、記憶の評価履歴という3つの役割を、それぞれ独立したテーブルで担う構成です。

import sqlite3
import os

def initDatabase():
    if not os.path.exists("memory.db"):
        conn = sqlite3.connect("memory.db")
        c = conn.cursor()

        c.execute("""
            CREATE TABLE memories (
                memory_id INTEGER PRIMARY KEY AUTOINCREMENT,
                content TEXT NOT NULL,
                category TEXT DEFAULT NULL,
                weight INTEGER DEFAULT 1,
                target_user_id TEXT NOT NULL,
                is_forgotten INTEGER DEFAULT 0,
                created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
        )
        """)

        c.execute("""
            CREATE TABLE dialogues (
                dialogue_id INTEGER PRIMARY KEY AUTOINCREMENT,
                user_id TEXT NOT NULL,
                sender_user_id TEXT NOT NULL,
                message_type TEXT NOT NULL,
                is_ai_generated BOOLEAN NOT NULL,
                message TEXT NOT NULL,
                memory_refs TEXT,
                prompt_version TEXT,
                temperature REAL,
                created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
        )
        """)

        c.execute("""
            CREATE TABLE weights (
                weight_id INTEGER PRIMARY KEY AUTOINCREMENT,
                memory_id INTEGER NOT NULL,
                importance REAL DEFAULT 0.5,
                is_forgotten INTEGER DEFAULT 0,
                updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                FOREIGN KEY (memory_id) REFERENCES memories(memory_id)
        )
        """)

        conn.commit()
        conn.close()

memories テーブルには「発言内容」「カテゴリ」「重み」などの本体記憶が、 dialogues テーブルにはユーザーごとの発話ログが、 weights テーブルには記憶ごとの評価・操作履歴(初期登録、強調、再評価など)が記録されるように設計されています。

なお、この関数は**Phase1 / Phase2 を問わず毎回実行されますが、既存の memory.db が存在すれば何も変更を加えません。** そのため、デプロイや検証で繰り返し再起動しても、登録済みの記憶データが消えることはありません。

理解すべきポイント4:getAllMemories の取得条件と記憶選定の前提

この関数は is_forgotten = 0 の記憶を全件取得します。 今後は category による抽出処理が getValidMemories() 側で実装される予定であり、より精度の高い記憶選定が可能になる基盤処理となります。

def getAllMemories():
    conn = sqlite3.connect("memory.db")
    c = conn.cursor()
    c.execute("SELECT memory_id, content, category, weight FROM memories WHERE is_forgotten = 0")
    results = c.fetchall()
    conn.close()
    return results

戻り値としては [(memory_id, content, category, weight), ] の形でリストが返されます。 現時点では一部カラムしか利用していませんが、今後の応答処理において category によるフィルタや weight による優先制御を行うための基盤設計となっています。

本関数は読み取り専用に設計されており、他関数と同様に conn.close() を明示してDB接続が確実に解放される構造となっています。

記憶をもとにChatGPTの返答を生成するchatgpt_logic.pyの役割

このファイルは、保存された記憶をもとにChatGPTへ送信するプロンプトを組み立て、対話に必要な自然な応答を生成する役割を担います。

chatgpt_logic.pyの完成ソース

Flaskアプリケーションからはこのファイルの関数を通じて、記憶ベースの返答処理が実行されます。

事前に os.path.exists() によってDBの存在をチェックしているため、すでに存在する環境で誤って上書きするリスクがありません。

さらに、記憶の重み(weight)や忘却フラグ(is_forgotten)など、後続のChatGPT連携で活用するメタ情報もあらかじめテーブル設計に組み込まれており、

設計の初期段階から拡張性と運用を見越した構成となっています。

理解すべきポイント1:ChatGPTへの送信処理 getChatGptReply()

def getChatGptReply(user_message):
    memories = getValidMemories()
    prompt = buildPrompt(memories, user_message)
    response = openai.ChatCompletion.create(
        model="gpt-4",
        messages=[
            {"role": "system", "content": "あなたは過去の記憶を踏まえて人間らしく返答するAIです。"},
            {"role": "user", "content": prompt}
        ]
    )
    return response["choices"][0]["message"]["content"]

この関数では、まず getValidMemories() により忘却されていない記憶を取得し、それとユーザーの発言内容を buildPrompt() に渡してプロンプトを構築しています。

その後、OpenAIの ChatCompletion.create() に system ロールと組み合わせて送信し、最終的な応答メッセージ(content)だけを抽出して返しています。

この構造により、外部のFlaskアプリケーションはこの関数を呼び出すだけで、記憶ベースの応答生成を一括して実行できる設計になっています。

理解すべきポイント2:プロンプト構築処理 buildPrompt()

def buildPrompt(memories, user_message):
    memory_section = "\n".join(f"- {m}" for m in memories)
    prompt = f"""
以下は過去に記録された重要な記憶です:
{memory_section}
この記憶をもとに、以下の発言に自然に返答してください:
「{user_message}」
"""
    return prompt.strip()

この関数では、記憶リストを1行ずつ箇条書きとして整形し、ユーザーの発話を文末に付加する形でプロンプト全体を構成しています。

構造的には「前提(記憶)→指示(自然に返答せよ)→入力(ユーザー発言)」という順序で構築されており、ChatGPTが文脈を理解しやすいよう配慮されたレイアウトになっています。

また、記憶リストはすべて - 記号付きで列挙されるため、ChatGPTにとっては「文脈的な知識群」として処理されやすく、システムとしての応答精度にも寄与する構造です。

理解すべきポイント3:ChatGPTによるカテゴリ分類処理 getCategoryByGpt()

def getCategoryByGpt(message):
    system_prompt = (
        "以下のユーザー発言に対して、最も適切なカテゴリを1単語で返してください。\n"
        "候補カテゴリには「家族」「仕事」「感情」「趣味」「健康」「その他」があります。\n"
        "出力はカテゴリ名のみで、他の説明を含めないでください。"
    )

    response = openai.ChatCompletion.create(
        model="gpt-4",
        messages=[
            {"role": "system", "content": system_prompt},
            {"role": "user", "content": message}
        ]
    )

    category = response["choices"][0]["message"]["content"].strip()
    return category if category else "uncategorized"

この関数では、ユーザーが発言したメッセージを ChatGPT に送信し、 その内容がどのカテゴリ(家族・仕事・感情・趣味・健康・その他)に該当するかを1単語で判定させています。

構造的には「対象発言 → systemロールの制約付きプロンプト → 1単語の返答」という流れになっており、 ChatGPTが余計な説明や文脈を付加せず、明確に分類することを意図した設計です。

また、万一ChatGPTが空文字などの無効な返答をした場合に備えて、uncategorized をフォールバックとして返す処理が組み込まれています。

この関数により、Phase1で登録されるすべての記憶にカテゴリが付与されるため、 Phase2での記憶抽出の最適化(プロンプトトークンの節約・応答精度の向上)につながる仕組みとなっています。

Webhookを起点に記憶と応答を統括するapp.pyの役割

このファイルは、LINEのメッセージを受け取り、記憶データの登録とChatGPTによる返答を統括的に制御するエントリーポイントです。 FlaskによるWebhookルーティングを中心に、各処理を役割ごとに外部ファイルへ分離した構成になっています。

app.pyの完成ソース

理解すべきポイント1:FlaskアプリケーションとLINE連携の初期化構造

app = Flask(__name__)
handler = WebhookHandler(channel_secret)
configuration = Configuration(access_token=access_token)
messaging_api = MessagingApi(ApiClient(configuration))

initDatabase() この構成では、Flaskアプリのインスタンスを生成した後、 LINEのWebhookハンドラーとメッセージAPIクライアントを初期化しています。

channel_secret および access_token は .env ファイルから取得されており、 コード上にハードコードせず、秘匿性を保った設計になっています。

initDatabase() はアプリ起動時に1度だけ呼び出され、必要なテーブルが存在しない場合に初期化処理を行います。 これにより、サーバー起動後すぐにWebhook受信処理が開始できる状態が整います。

理解すべきポイント2:ユーザーの種別による処理分岐とChatGPT応答の制御

この処理では、フェーズ(learn or reply)に応じて動作が完全に分岐されます。

  • Phase1(learn)では対象ユーザーからの発言を記憶し、ChatGPTでカテゴリを自動分類したうえでDBへ格納
  • Phase2(reply)では対象者以外からの発言にのみ反応し、ChatGPTによる応答生成 → DBへ保存 → LINEで返信

@handler.add(MessageEvent, message=TextMessageContent)
def handleMessage(event):
    user_id = event.source.user_id
    message = event.message.text

    if phase_mode == "learn":
        if user_id == memory_target_user_id:
            category = getCategoryByGpt(message)
            registerMemoryAndDialogue(user_id, message, message, category)
            print(f"Memory recorded with category: {category}")
        else:
            print("Ignored: Not memory target (LEARN mode)")

    elif phase_mode == "reply":
        if user_id == memory_target_user_id:
            print("Ignored: memory_target_user_id should not speak in REPLY mode")
            return
        reply_text = getChatGptReply(message)
        registerMemoryAndDialogue(user_id, message, reply_text, "応答")
        reply = ReplyMessageRequest(
            reply_token=event.reply_token,
            messages=[TextMessage(text=reply_text)]
        )
        messaging_api.reply_message(reply)
        print("Reply sent and recorded (REPLY mode)")

この構造により、面影AIの根幹方針「記憶される本人には応答しない」がコード上でも明確に反映されるだけでなく、カテゴリによる記憶分類という設計思想が、実装全体に一貫して統合されています。

記憶対象者の user_id は .env ファイルで定義されており、アプリケーションの設定を変更するだけで、誰を記憶対象にするかを切り替えることが可能です。
この方式により、環境に応じて対象ユーザーを動的に変更できる柔軟な設計が実現されています。

Webhook受信とメッセージ処理の実装

LINEから送信されたメッセージは、LINE Developersで指定したWebhook URLにPOSTリクエストとして届きます。Flaskのルーティング機能を利用してこのリクエストを受け取り、署名検証を行ったうえで、ユーザーのテキストメッセージを抽出します。

この段階では、以下の要素が最低限必要となります。

・Python用 LINE SDK v3(linebot-sdk-python)の導入とインポート確認
・.env からアクセストークンとチャネルシークレットの読み込み
・テキストメッセージであることの判定(@handler.addによる条件登録)

@handler.add(MessageEvent, message=TextMessageContent) により、画像・スタンプなどは除外され、
テキストメッセージのみがChatGPT連携処理へと進みます。

ChatGPT連携部分の詳細実装

ChatGPTとの連携部分では、記憶DBに格納された情報を取得し、プロンプトに組み込んでChatGPT APIへリクエストを送信します。得られた応答はLINEへ返信されるだけでなく、ユーザーの発言とともに「新たな記憶」としてデータベースに保存されます。

記憶の選定ロジック

現時点の実装では、is_forgotten = 0 の記憶をすべて対象にChatGPTへプロンプトを構築しています。 将来的には以下のような条件に基づく選定ロジック(重要度や関連性の重み付け)を導入することで、 より精度の高い返答が可能になります。

これにより、面影AIの応答は常に関連性の高い情報をもとに生成されるようになります。

ChatGPTとの基本的な接続方法やAPI設定の流れは、過去記事に詳しくまとめています。すでに設定済みの読者は、このセクションからプロンプト設計に進んでください。

基準項目選定条件
importanceスコア閾値以上の記憶のみを対象とする
発言の新しさ直近の対話履歴を優先して抽出
話題の一致今回の入力文に含まれるキーワードと一致する記憶を抽出

【関連記事】
AI執事ボットのためのVPS環境構築マニュアル(LINE対応付き)

再現応答の生成プロンプト設計

プロンプトには記憶された情報だけでなく、「あなたは◯◯という人物の話し方を再現するAIです」といった文言を含めることで、ChatGPTの出力を特定の人格に寄せていきます。プロンプト設計では以下の要素が重要になります。

  • 記憶DBからの抽出結果(複数行にまとめて提示)
  • 応答のトーン(丁寧・フレンドリーなど)の指示
  • ユーザーの過去発言に合わせた話題の流れ

こうしたプロンプトを構成することで、ChatGPTの出力が一貫性を持ち、かつ“あの人らしさ”を感じさせる再現性を実現できます。

面影AIを支える記憶中心のデータ構造とは?

面影AIでは、「記憶」を中心にすべてのデータが管理される構成を採用しています。 ユーザーの発言、記憶の評価、履歴の追跡まですべてが一貫した設計で記録され、Phase2以降の機能拡張にも柔軟に対応できる仕組みとなっています。

3つのテーブルはすべて memory_id でつながっている

面影AIで使用されるデータベースには、以下の3つのテーブルが存在します。

1. memories(記憶の本体)

  • 主キー: memory_id
  • 記憶そのもののテキスト、カテゴリ、重み(weight)、忘却フラグなどを含みます
  • 全ての情報の中心に位置する、データ構造の核です

2. dialogues(LINEからの発言ログ)

  • 外部キー: memory_id → memories.memory_id
  • LINEで受信したユーザーの発言を記録します
  • その発言が「どの記憶として保存されたか」を追跡するために紐づけます

3. weights(記憶の評価・履歴)

  • 外部キー: memory_id → memories.memory_id
  • 記憶に対する評価・変更・人間による再解釈の履歴などを記録します
  • 将来的に記憶の再評価や優先度制御に活用される拡張テーブルです

テーブル同士の関係を視覚的に整理すると

記憶(memories)を中心に、発言(dialogues)と評価履歴(weights)が周囲に紐づいている構造です。

なぜこの構成が重要なのか?

面影AIの特徴は、「記録されたテキスト」だけでなく、それがどんな文脈でどんな価値で保存されたのかまで含めて記録することにあります。 このテーブル設計により、以下がすべて可能になります:

  • どの発言が、どの記憶に変換されたか追跡
  • 記憶がいつ・どのように評価され直したか確認
  • Phase2以降で重みや忘却フラグを活用し、柔軟な記憶の制御ができる

ただ記録するだけでなく、「意味を持って残す」ための構造。それがこの記憶中心のDB設計です。

Phase1テスト:記憶の登録とデータ構造の確認

ここでは、実際に面影AIの記録モード(Phase1)を稼働させ、LINEから送信した発言がどのように「記憶」として登録され、 またそれがどのようにデータベースに保存されたかをテストしました。 本機能が正しく機能しているかを確認する、重要な検証です。

テスト実行時のLINE送信とFlaskログ出力

開発サーバ上で面影AIを起動し、LINEから複数の自然文メッセージを送信しました。 Flaskのログ出力には、以下のようにユーザーIDの検出・カテゴリ分類・記憶登録が一連の流れとして出力されています。

データベースに保存された記憶の内容

SQLiteのメモリデータベース( memory.db)に対して、CLIから実際に中身を確認しました。 3つのテーブル memories, dialogues, weights の内容は以下の通りです。

📄 memories テーブル(記憶の本体)

🗣️ dialogues テーブル(発言ログ)

⚖️ weights テーブル(記憶の重み履歴)

weights テーブルは、各記憶に対して「どのような評価・扱いをされたか」を履歴として記録するための補助テーブルです。 ユーザーによる強調指示、再評価、フィルタリング対象などの操作を記録しておくことで、将来的な記憶の再評価や抽出精度向上に活用できます。

テーブル構成(weights)
記録される主な情報
  • memory_id: 対象となる記憶のID
  • interact: 操作・評価の内容(例:初期登録、再評価、優先付与など)
  • created_at: その記録が保存された日時

なお、 importanceupdated_at などのスコア要素は今後の設計次第で導入予定ですが、現時点ではテーブルに含まれていません。

このように、記憶DBに保存された過去の会話や登録フレーズが、ChatGPTのプロンプトに反映されることで“面影”としての一貫性が生まれます。

よくあるエラーと対処方法

面影AIの構築では、主に環境設定やAPI連携部分においてエラーが発生しやすい傾向があります。以下は、代表的なエラーとその対処法をまとめたものです。

エラー内容原因対処方法
LINEから応答が返ってこないWebhook URLが無効または未登録LINE DevelopersでURLを再設定し、正常なHTTPレスポンスを確認する
ChatGPTからエラーが返るAPIキーの誤設定または制限超過.envファイルを確認し、gpt-4oのAPI使用条件を満たしているか確認する
記憶が反映されないmemory_dbへの書き込み処理が正常に行われていないDB書き込み関数と読み出しロジックのログを確認し、保存条件を見直す

接続不良やWebhookの設定ミスについては、下記の構築マニュアルでも具体例を挙げて対処法を紹介しています。必要に応じて確認してください。

【関連記事】
AI執事ボットのためのVPS環境構築マニュアル(LINE対応付き)

これらのエラーは、事前に構成ミスを防ぐことで回避できるものが多いため、動作確認時にはサーバーログやAPIレスポンスのステータスコードを丁寧に追跡することが求められます。

次はいよいよ、記録した“面影”が語り出す──Phase2として蓄積された記憶をもとに応答を返すAIを実装していきます。
▶︎【Pythonの基礎知識】面影AIが応答する|Phase2:記憶をもとに返す言葉

【倫理的制限に関する注意】
本ソースコードには、性的・恋愛的・ロールプレイ的な応答を抑制するための明示的な制御処理が組み込まれています。
これらの制限を解除・改変した上で利用された場合、その結果生じるすべての責任は、改変および利用を行った個人に帰属するものとします。
本プロジェクト開発者は、意図的な制限解除による悪用・誤用・不適切な利用に対し、一切の責任を負いません。

よく読まれている記事

1

IT入門シリーズ 🟢 STEP 1: ITの基礎を知る(ITとは何か?) 📌 IT初心者が最初に学ぶべき基本知識。ITの概念、ネットワーク、OS、クラウドの仕組みを学ぶ ...

2

「私たちが日々利用しているスマートフォンやインターネット、そしてスーパーコンピュータやクラウドサービス――これらの多くがLinuxの力で動いていることをご存じですか?無料で使えるだけでなく、高い柔軟性 ...

3

この記事は、Linuxについて勉強している初心者の方向けに「Shellスクリプト」について解説します。最後まで読んで頂けましたら、Shellスクリプトはどのような役割を担っているのか?を理解出来るよう ...

-面影AI, Pythonの基礎知識(実践編)