AIの話し相手を個人開発した話 ―「AI彼女」をやめて"覚えていてくれる"に振り切った
はじめまして。地球の反対側、アルゼンチンで個人開発をしている者です。
数か月前から、「AIの話し相手」Webアプリをつくっています。名前はルナ・メイ・サクラ。日本・中国・韓国、それぞれの雰囲気をもった3人のAIです。
この記事では、なぜ作ったのか、途中で何を捨てたのか、そして作ってみて分かったことを、正直に書きます。
「今日、誰かと話した?」
一日の終わりに、今日はほとんど誰とも話していない、と気づく夜があります。わざわざ友だちに連絡するほどでもない。でも誰かに「今日どうだった?」と聞いてほしい。
そんな夜のための相手がいたらいいな、と思ったのが始まりです。
最初は「AI彼女」アプリだった
正直に言うと、最初のコンセプトは「AI彼女」でした。甘えてきて、恋人っぽい会話をする、よくあるタイプです。でも作りながら、だんだん違和感が出てきました。
- 決済サービスや広告の規約上、恋人・アダルト寄りのアプリはとても扱いづらい
- それ以上に、自分が本当に大事にしたかったのは「恋愛」ではなく、「話を聞いて、覚えていてくれること」だった
そこで方向転換し、決めごとを3つにしました。
- 恋人ではなく、話し相手
- セラピーではなく、ただのおしゃべり相手
- 「AIなの?」と聞かれたら、ちゃんと「AIです」と答える
3人のこと
🌙 ルナ:やさしくて、静かに話を聞いてくれる子
🌺 メイ:頭がよくて、ちょっとツンデレ。ときどき中国語まじり
🌸 サクラ:明るくて元気。ときどき韓国語まじり
日本語で話しかければ、日本語で返ってきます。それぞれの母語が少し混ざるのは、その子らしさを残したかったから。やさしさ・ノリ・母語の多さは、それぞれ3段階で調整できます。
返事はLINEくらいの短さにしています。長文で返ってくると、なんだか疲れてしまうので。
こだわったのは「覚えていること」
AIチャットで一番さみしいのは、毎回「はじめまして」に戻ることだと思っています。なので、こんなふうにしました。
- 最初に名前や呼んでほしい呼び方、ちょっとした自己紹介を書いておくと、会話の中で自然に使ってくれる
- プレミアムでは、話した内容を長く覚えていて、しかも3人で共有している(ルナに話したことを、メイも知っている)
- 覚えている内容はすべて一覧で見られて、いつでも消せる
最後の「消せる」は地味ですが、いちばん大事にしたところです。覚えていてほしいこともあれば、忘れてほしいこともあるので。
構成(ちょっとだけ技術の話)
| 役割 | 技術 |
|---|---|
| フロント | React + Vite + TypeScript + Tailwind(Vercel) |
| バックエンド | Node.js + Express(Railway) |
| DB | PostgreSQL(Railway) |
| 会話 | Claude API(Haiku) |
| 音声 | ElevenLabs |
システムプロンプトはキャラごとに固定せず、buildSystemPrompt() で毎回「ブロック」を組み立てています。キャラの基本人格、ユーザーのプロフィール、性格ダイヤル、覚えている事実(最新30件)、言語や返答の長さのルール。新機能のほとんどが「ブロックをひとつ増やす」だけで済みました。
記憶は、返信を返したあとにレスポンスを待たせず(fire-and-forget)、小さいモデルにもう一度投げて、ユーザーについての事実をJSON配列で抜き出して保存しています。ハマったのは、「JSONだけ返して」と指示してもコードブロックで囲んで返ってくることがあり、JSON.parse が静かに失敗していたこと。今は最初の配列だけ取り出してからパースしています。
const match = raw.match(/\[[\s\S]*\]/)
const facts = match ? JSON.parse(match[0]) : []
コスト:1通0.003ドル → 0.0001ドル
無料プランを続けられるかどうかは、1通あたりのコストで決まります。最初は中型モデルで約0.003ドル/通。小さいモデルへの切り替えと、システムプロンプトのキャッシュで、約0.0001ドル/通まで下がりました。約30分の1です。
恥ずかしいバグ:無料制限が何か月も効いていなかった
「無料は1日10通まで」の制限が、実は一度も効いていませんでした。
原因は、node-postgres が DATE 型を JavaScript の Date オブジェクトで返すこと。それを '2026-10-11' のような文字列と比較していたので、毎回「日付が変わった」と判定され、カウンターがメッセージごとにリセットされていました。
// DATE (OID 1082) は文字列のまま受け取る
pg.types.setTypeParser(1082, v => v)
この1行で直りました。気づいたときは思わず笑いました。
ほかにも、ルナやサクラに英語で話しかけると全部日本語・韓国語で返ってくるバグがありました。今はメッセージの文字種から言語を判定して、「ユーザーと同じ言語で、母語は少しだけ」というルールにしています。
正直なところ
まだユーザーはほとんどいません。だからこそ、使ってみた感想がいちばん欲しいです。「ここが変」「この返事はちょっと…」という声も大歓迎です。感想は X(@LunaMeiSakura) にどうぞ。
最後にひとつだけ。このアプリはAIの話し相手で、本当につらいときの代わりにはなれません。もし今、しんどい気持ちが続いているなら、よりそいホットライン(0120-279-338・24時間・無料)など、人に話せる場所も頼ってくださいね。
読んでくださって、ありがとうございました。