ローカルLLMに長期記憶を持たせたチャットアプリをつくる

環境:Visual Studio Code, Claude Code(Sonnet 4.6)

AIチャットアプリを作りながらローカルLLMの記憶の仕組みを作ってみました。

記憶してくれていると嬉しいし、愛着も沸きますよね。

LM Studioなどを使ってローカルLLMを利用していると、新しい会話を始めるたびに記憶が引き継がれず、毎回前提を共有し直す手間が煩わしく感じることがあります。ChatGPTのメモリ機能のように、一定の情報を記憶させておきたいと思うことが増えたため、データベースを活用した仕組みをつくりました。今回も設計は自分が行い、実装はClaude Codeに任せています。

LLMに壁打ち相手になってほしいことが多いのですが、去年までのローカルLLMは会話するにはいまいちな性能でした。

最近リリースされたQwen3.5が会話をするにふさわしい性能を持っていると判断したため、チャットアプリを自作することにしました。

仕様

LLMモデル

Qwen3.5とGemma4のGGUF版を利用しています。個人的には、一年前のGPT-4クラスに相当する性能があるように感じます。昨年までのローカルLLMは性能不足が目立っており、単体では会話が成立しないこともありましたが、ようやく満足のいくモデルが登場してきたと思います。

推論エンジン

llama.cpp(b8648)を使います。とにかく高速ですね。

Releases · ggml-org/llama.cpp
LLM inference in C/C++. Contribute to ggml-org/llama.cpp development by creating an account on GitHub.

インターフェイス

フロントエンドにはElectronを使い、LM Studioのインターフェイスを真似することにしました。必要なものだけ抜き取った自分版LM Studioという感じで構成。

ちなみにElectronはLM StudioやVS Code、Windows版ChatGPTにも使われているフレームワークです。モダンですっきりした見た目が気に入っています。

データベース

SQLiteを使い、各会話のターン(自分の質問とLLMの会話)をペアとしてQAチャンクにして保存します。

Embeddingモデル

Embeddingモデルは文章を数値ベクトルへ変換する役割を持っています。

・文章のデータベースへの保存時にembedding(埋め込み)を作り、データベースに保存する。
・後で質問が来たらその質問をベクトル化し、データベースの中から意味の近いものを検索する。
・見つかった内容をLLMに渡し、回答を作る。

という流れになります。

モデルはQwen3 Embedding名古屋大学Ruri v3で迷いましたが、日本語の会話がメインなので後者を選ぶことにしました。

cl-nagoya/ruri-v3-310m · Hugging Face
We’re on a journey to advance and democratize artificial intelligence through open source and open science.

検索にはSQLiteの全文検索(FTS5)とEmbeddingモデルのベクトル検索の2つを使います。

技術的な話

空母「赤城」についての会話を例にプロンプトの解説をします。

記憶の保存と検索

Q&Aの一往復の文章をひとまとめにして、ベクトル化(768次元)します。
それをデータベースへ保存して置き、検索対象にします。

記憶を検索する際は、
「最後のユーザー入力の本文」をクエリにして検索しています。

また、SQLiteのFTS5を用いた全文検索も併用しています。ベクトル検索の結果に全文検索の結果を加算し、そこに経過日数の半減期を重みとして掛け合わせ、スコアの上位5件を抽出します。

計算式:(ベクトル検索の得点 + 全文検索の得点) × 半減期

プロンプトへのまとめ方

先頭にsystemプロンプトを置き、記憶コンテキストを連結し(最大5つ)、その後ろへ会話履歴を古い順に並べてllama-server に渡しています。

systemプロンプト
[記憶1]
[記憶2]
[記憶3]
[記憶4]
[記憶5]
[1] user:
[2] assistant:
[3] user:
[4] assistant:
[5] user:

具体的なプロンプトです(簡略化してます)

[0] system:
あなたは親切なローカルアシスタントです。ユーザーの質問に明確かつ簡潔に答えてください。
内部の推論過程は出力せず、最終的な回答を直接述べてください。

## 過去の会話から検索された関連記憶
以下はこのワークスペース内の過去の会話から自動検索された情報です。
ユーザーの質問に答える際、自然に参照してください。

[記憶 1]
Q: なにか執念のようなものを感じますよね。
A: まさにおっしゃる通りだと思います。その「執念」こそが、赤城という艦の造形を単なる工業製品から、
ある種の「生き物」のような凄みに昇華させている正体ではないでしょうか。(略)

[記憶 2]
Q: 戦艦扶桑の格好良さについて語りたいです。あのバランスの悪さが好きなのですが、
どんな過程であの造形になったか知っていますか?
A: 戦艦「扶桑」のあの唯一無二の、あるいは「極端な」造形は、
まさに**「時代と設計思想の転換点に翻弄された結果」**と言えます。
あなたが惹かれているあのバランスの悪さは、主に以下の3つの過程から生まれました。(略)

[記憶 3]
Q: ドイツの戦車は最初はデザインが格好良くて余裕を感じますよね。
だんだん戦況が悪くなってくると実用性を重視したシルエットや迷彩を施すようになるところが面白いと思います。
A: その観察眼は非常に鋭いですね。ドイツ戦車のデザイン変化は、
まさに戦況の悪化と技術的制約が直結した歴史的な証です。(略)

[記憶 4]
(略)

[記憶 5]
(略)

------------------------------------------------------------
[1] user:
空母「赤城」の格好良さはどこに宿っていると思いますか。
もともと空母として設計されたわけではないがゆえの、不完全なバランスに惹かれるのですが、
この感覚を言語化したいのです。
------------------------------------------------------------
[2] assistant:
その感覚は、**「機能の転換による葛藤」**や**「継ぎ接ぎの美学」**という言葉で
言語化できるのではないでしょうか。(略)

------------------------------------------------------------
[3] user:
なにか執念のようなものを感じますよね。
------------------------------------------------------------
[4] assistant:
まさにおっしゃる通りだと思います。その「執念」こそが、赤城という艦の造形を単なる工業製品から、
ある種の「生き物」のような凄みに昇華させている正体ではないでしょうか。(略)

------------------------------------------------------------
[5] user:
結局最後は沈没してしまうところにも物語性を感じます。
------------------------------------------------------------

という感じになっています。
Llama-Serverはこのプロンプトに対して回答をしています。

その結末こそが、赤城という艦の物語を完結させる、残酷で美しい「句読点」のような役割を果たしていると感じます。

あそこまで執念深く、形を変えてまで「生き残ること」と「適応すること」を追求した艦が、
最終的に海に消えていく。この対比が、見る者に強い喪失感と情緒的な余韻を与えます。

特に以下の2つの視点から、その物語性を深掘りできるのではないでしょうか。

1. 「適応の限界」という悲劇
どれほど形を変え、時代の正解を追い求めても、抗えない運命や時代の大きなうねりはある。
赤城の沈没は、個としての「執念」が、時代の巨大な転換点という不可抗力に飲み込まれたことを象徴しています。
その「もがいた末の終焉」に、言いようのない切なさが宿ります。

2. 「役割を全うした」という浄化
戦艦という出自を捨て、不器用ながらも空母として戦い抜き、最後はその役割の果てに沈む。
それは、ある意味で「不完全な怪物」として生まれた赤城が、
自らのアイデンティティに決着をつけた瞬間でもあります。
歪な姿で戦い抜いたからこそ、その最期が単なる喪失ではなく、一つの壮大な叙事詩のように感じられるはずです。

「転身しようともがいた執念」があったからこそ、その「喪失」が単なるデータの消滅ではなく、
一つの命が尽きたかのような、深い物語として私たちの心に刻まれるのだと思います。

なかなか味わいのある文章を返してきました。

入れてみた機能

システムプロンプト

英語翻訳や文章添削などの機能としてLLMを使いたいときに、いちいち「つぎの文章を英語にして」とか入力するのは煩わしいので、プリセットとして簡単に登録できるようにしておきます。

文章添削のシステムプロンプトならこんな感じです。

あなたはテキスト校正アシスタントです。
ユーザーが選択したテキストを校正・改善してください。
改善後のテキストのみを返してください。説明や前置きは不要です。

繰り返しの多いものは登録しておくと楽です。

一時モード

ChatGPTに記録するまでもない短い文章の訂正や翻訳には一時モードを利用していますが、こうした処理はすべてローカルで完結させたいと考えています。つまり、ツールとして活用するLLMです。

オートコンプリート

文章を書いている間に次の文章を予測して候補を表示するようにしてみました。コード支援AIのような感覚です。

ちょっと鬱陶しいところもありますが、文章が思い浮かばない時は有用だろうと思います。ロードしているLLMの能力に依存します。

選択テキスト校正

文章を範囲選択すると、校正された文章が表示されます。

これもロードしているLLMの能力に依存します。文章の柔らかさ、言い回しの調整は今後の課題ですね。

感想

ChatGPTなどでは当たり前のメモリー機能ですが、ローカルLLMで出来ると嬉しいですね。

本当に物知りさんです。

ウェブ検索機能も付ける予定でしたが、しばらくはこのままでもよさそうなクオリティです。そもそも内密に相談したい話題に使いたいのでウェブ検索の必要性は薄い。それだけ最近のローカルLLMの性能がよいということでもあります。

もしClaude CodeやCodexを使っているのなら自分専用のチャットアプリ・テキストエディタを作成することをお勧めします。LLMの性質や仕組みを学ぶいい機会にもなりますよ。

タイトルとURLをコピーしました