環境:Visual Studio Code, Claude Code(Sonnet 4.6)
チャットアプリを作りながらローカルLLMの記憶の仕組みを作ってみました。

LM Studioなどを使ってローカルLLMを利用していると、新しい会話を始めるたびに記憶が引き継がれず、前提を毎回共有し直す必要が煩わしく感じることがあります。ChatGPTのメモリー機能のように一定の記憶をしてほしいと思うことが増えてきたので、データベースを使った仕組みを作ってみました。今回も設計は自分で、実装はClaude Codeに任せました。
LLMに壁打ち相手になって欲しいことがよくあり、Qwen3.5がそれにふさわしい性能を持っていると評価できたので作成してみることにしました。
仕様
LLM
Qwen3.5 27Bや35B-A3BあたりのGGUF版を利用します。個人的には一年前のChatGPT4クラスに相当する性能があるように感じます。それまでのローカルLLMは足りなさが目立っており、それ単体で会話が成立しないこともあったのでウェブ検索に利用していましたが、ようやく満足のいくモデルが出てきたと思います。
インターフェイス
フロントエンドにはElectronを使い、LM Studioのインターフェイスを真似することにしました。必要なものだけ抜き取った自分版LM Studioという感じで構成。
ちなみにElectronはLM StudioやVS Code、Windows版ChatGPTにも使われているフレームワークです。モダンですっきりした見た目が気に入っています。
データベース
SQLiteを使い、各会話のターン(自分の質問とLLMの会話)をペアとしてQAチャンクにして保存します。
Embeddingモデル
Embeddingモデルは文章を数値ベクトルへ変換する役割を持っています。
・文章のデータベースへの保存時にembedding(埋め込み)を作り、データベースに保存する。
・後で質問が来たらその質問をベクトル化し、データベースの中から意味の近いものを検索する。
・見つかった内容をLLMに渡し、回答を作る。
という流れになります。
モデルはQwen3 Embeddingと名古屋大学Ruri v3で迷いましたが、日本語の会話がメインなので後者を選ぶことにしました。

検索にはSQLiteの全文検索(FTS5)とEmbeddingモデルのベクトル検索の2つを使います。
構成
lm-chat/
├── backend/
│ ├── server.py FastAPI エンドポイント
│ ├── models.py Pydantic モデル定義
│ ├── store.py SQLite CRUD
│ ├── llm_proxy.py llama-server プロキシ・生成統計抽出
│ ├── llama_manager.py モデル切り替え・イジェクト(プロセス管理)
│ ├── config_store.py 設定の永続化 (data/config.json)
│ ├── memory/
│ │ ├── engine.py 記憶の保存・検索エントリポイント
│ │ ├── embedder.py ruri-v3-310m による埋め込み生成
│ │ └── chunker.py Q&A ペアチャンキング
│ └── requirements.txt
├── src/
│ ├── main/main.ts Electron メインプロセス
│ └── renderer/
│ ├── App.tsx ルートレイアウト・サイドバー開閉
│ ├── api.ts バックエンド API クライアント
│ ├── stores/chatStore.ts Zustand グローバルストア
│ ├── components/
│ │ ├── ModelBar.tsx 上部モデルバー
│ │ ├── ModelPickerModal.tsx モデル選択ダイアログ
│ │ ├── Sidebar.tsx 左サイドバー(ワークスペース・セッションツリー)
│ │ ├── ChatView.tsx メッセージ一覧・生成統計
│ │ ├── MessageInput.tsx 入力エリア・画像添付・トークンリング
│ │ └── SettingsPanel.tsx 右パネル(コンテキスト長・GPU 設定)
│ └── styles.css
├── data/ 自動生成(Git 管理外)
│ ├── lm_chat.db SQLite データベース
│ ├── config.json ctx_size・n_gpu_layers 等の設定
│ └── llama_paths.json llama-server パス・前回モデル
├── models/ GGUF モデル置き場(Git 管理外)
├── bin/ llama-server バイナリ(Git 管理外)
└── start.bat 一括起動スクリプトすべての会話を記憶されるのは煩わしいため、「ワークスペース」という分野ごとに記憶を分けることにしました。同じワークスペース内の会話は共有されます。
入れてみた機能
システムプロンプト

英語翻訳や文章添削などの機能としてLLMを使いたいときに、いちいち「つぎの文章を英語にして」とか入力するのは煩わしいので、プリセットとして簡単に登録できるようにしておきます。
文章添削のシステムプロンプトならこんな感じです。
あなたはテキスト校正アシスタントです。
ユーザーが選択したテキストを校正・改善してください。
改善後のテキストのみを返してください。説明や前置きは不要です。繰り返しの多いものは登録しておくと楽です。
一時モード
ChatGPTに記録するまでもない短い文章の訂正や翻訳には一時モードを利用していますが、こうした処理はすべてローカルで完結させたいと考えています。つまり、ツールとして活用するLLMです。
オートコンプリート
文章を書いている間に次の文章を予測して候補を表示するようにしてみました。コード支援AIのような感覚です。

ちょっと鬱陶しいところもありますが、文章が思い浮かばない時は有用だろうと思います。ロードしているLLMの能力に依存します。
選択テキスト校正
文章を範囲選択すると、校正された文章が表示されます。

これもロードしているLLMの能力に依存します。文章の柔らかさ、言い回しの調整は今後の課題ですね。
感想
ChatGPTなどでは当たり前のメモリー機能ですが、ローカルLLMで出来ると嬉しいですね。

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

Qwen3.5 27Bの量子化モデルは容量としては15GB~20GBですが、これだけの知識がこの容量に詰め込まれていると思うと感心しますね。もしClaude CodeやCodexを使っているのなら自分専用のチャットアプリ・テキストエディタを作成することをお勧めします。LLMの性質や仕組みを学ぶいい機会にもなります。

