YKRV.NET - Terminal[v2.0.0]

-----------------------------

anonymous@ykrv.net:~$ open sidemenu

Connecting...[OK]

Checking permissions...[OK]

//

// DEPLOYED SIDE MENU

//

 



?: ᙍᖶᙍᖇᘉᓿᖶᖻ >>µñkñ¤wñ ¢¤ñt€ñt

Command-R 35B x memGPT インテグレーションレポート

これまで様々なローカルモデルが登場してきた。70Bといったローカルで動作させるには業務用グレードのA1000といったようなGPUをx枚といったような環境を整えればChatGPTクラスの代物を動かせる。 そんな話題は散々聞いてきた。だが、一般人としての目線で見るとするなら、ご家庭一般クラス基準で動かすにせよRTX 4090シングルカードといったグレードが関の山。 しかしローカルLLMはそう甘くはない。シングルカード動作かつコンシューマーグレード前提ともなれば、どれもGPT-4はおろかGPT-3.5の足元にも及ばないモデルが多く、特に日本語の出力精度・純粋なロールプレイ型チャット性能を問おうとすると、出力こそ可能でもイマイチ体感的にぱっとしないモデルが昨年から登場し続けてきた。

そんな中 2024年 3月の終わり頃、Cohere社によりあるローカルLLMモデルが一般公開された。

HuggingFace - Cohere/c4ai-command-r-v01

「Command-R」と呼ばれるそのモデルはRAG特化型で35B規模のモデルとして配布されているものの、また今回もモデル発展の礎の1つとなる最前線の研究者、ベンチマーク、マニア向けのモデルで終わるかと思われた。だが、このモデルはこれまで登場してきたモデルとは一線を凌駕していたのだった...

X - Yukaryavka/c4ai-command-r-v01 テスト報告ツイ
報告ツイート画像01

ご覧の通りこの「Command-R」モデルは 35Bクラスのモデルであるのにも関わらずチャット部門において体感的に目覚ましい発展を遂げたと感じられるような性能になっていた事を確認し、Yukaryavkaはある一つの事がふと頭を過ぎる。

「ここまでの精度であれば memGPT と組み合わせたら、ローカル向けモデルで運用が期待できる程の成果が出るのではないか?」

と。そして、わざわざこの為にメインプロジェクトの作業の手を止めて試した結果が...

X - Yukaryavka/command-r 35B x memGPT 統合テスト報告 その1
報告ツイート画像01

X - Yukaryavka/command-r 35B x memGPT 統合テスト報告 その2
報告ツイート画像01

というように、予想以上の成果を出した事で、今回の報告ツイがかなり注目された事もあって、この成果を出すために行ったシステム構築に関する情報を一般公開する事にしました。自己検証・あるいは少しでも最先端なローカルモデルで自身のキャラクターっぽい何かとお話したいという野心的な方は参考にしてみては如何だろうか。

Command-R 35B x memGPT インテグレーションレポート

準備する必要のあるものや事前知識について

・任意の量子化済みの Command-R モデルデータ

今回の成果はCommand-Rオリジナル版のモデルでは出にくいかもしれません。実験の初期段階でオリジナル版モデルを使用していましたが、オリジナル版では日本語で話すようにプロンプト側で細工しても英語で出力されてしまう現象に悩まされましたが、その実験途中で登場した Aratako氏による日本語特化チューニングが追加で行われた派生モデルを使用する事で言語問題を解消出来ています。

(オリジナル版でも同様の成果は出るかもしれませんが、元モデルは多言語モデルの為、出来ればこういった特化チューニングが施されたモデルを使用する事を推奨します。)

HuggingFace - Aratako/c4ai-command-r-v01-japanese-instruct
※量子化規格は今回 EXL2 を使用していますが、APIサーバーと合わせてご自身が利用したい形式を用意してください。

・RTX 4090 x 1

いくらモデル性能が良いとはいえ、VRAM 24GBを全て食い尽くしてあの成果を出せているので必須です。業務用GPUを複数枚、必要としないだけマシ(震え声)

・WSL & Linux操作 / Python / LLMモデル量子化手法 / ローカルLLM向けAPIサーバー運用 といった前提知識

この記事を閲覧される方なら大丈夫だと思いますが、今回の構築方法ではWSL & Windows環境両方のPythonを使用する為、この2つの知識は必須です。また、今回はtext-generation-webuiといったものをバックエンドとして使用せず、EXL2量子化モデルで最も効率よく推論を行うべく、TabbyAPIというEXL2専用のAPIサーバープロジェクトを使用しましたので、これらの知識が必要です。

github - theroyallab/tabbyAPI
※厳密にはAPIサーバーは 「OpenAI API互換」であれば問題ないので、お好みのモデル形式がある場合はそれにあったAPIサーバーを利用してください。

構築・統合手順

1. 環境構築 - Python環境について

最初に動作環境を構築します。各々それぞれの手順があると思いますが、今回は余すこと無く私流の手順は書き残しておきます。

まずはPythonからですが、私の場合、Pythonはパッケージ・仮想環境管理といった面が致命的なまでに統一されていない問題を解決出来るものが良いと考えている事もあり rye を用いて一括管理してますので、それを前提として進めていきます。

rye - a Hassle-Free Python Experience

2-1. 環境構築 - TabbyAPI 初期セットアップ

LLMモデル自体の推論を行う際に可能な限りOSに近い場所でプロセスを実行したかったので、今回はたまたまTabbyAPIがWindows上のPythonで動作したので推論・APIサーバーは「WindowsネイティブPython」で動作させます。 手順は一気にまとめてコマンド例を載せておきます。

※ EXL2モデルを使用する方向けの確認事項

git cloneする前にモデルを量子化した際に使用した Exllamav2 バージョンを確認しましょう。今回は 0.0.16 で行いましたが、tabbyAPIリポジトリは残念ながらバージョン毎のReleaseもtagも定義しない運用方法を行っているようなので、何も考えずに最新版を取得するとバージョン不一致でモデルがロードできません。面倒ではありますが、ライブラリの互換性問題などを自分で対処する手間が惜しい場合は「pyproject.tomlのコミット履歴からライブラリのバージョンが引き上げられる直前のコミットをチェックアウトする」必要があります。どう対処するかは各々の判断に任せますが、今回は時短する為に変更直前のリポジトリを取得します。

bat : run.bat
REM TabbyAPIはPythonパッケージが存在しないのでリポジトリをcloneして直に使用します
git clone https://github.com/theroyallab/tabbyAPI.git
cd .\tabbyAPI

REM EXL2モデルを使用する方向けの確認事項 にある通り、今回は 0.0.16 で量子化したモデルを使用する為、0.0.17 が使用され始める直前のブランチをチェックアウトします。
REM commit - Sampling: Add additive param to overrides / https://github.com/theroyallab/tabbyAPI/commit/d716527b928e86faf65baa500c18be59cc1d1053
git checkout d716527

REM TabbyAPIは3.12だと前提ライブラリの都合で正常に動作しなかったので3.11にしています。
rye pin 3.11

REM pyproject.tomlが付属しているのでsyncすればすぐに準備が整います。
rye sync

.\start.bat

// 任意オプション

tabbyAPIはstart.pyの処理で毎回ライブラリをpip installしてきてしまい、余計な通信量が起動時に毎回 200MB前後、発生してしまうので、1度だけStart.batを経由して起動して前提ライブラリを取得させた後に以下の記述をコメントアウトして毎回更新を掛けないようにしました。

python : start.py
    if args.ignore_upgrade:
        print("Ignoring pip dependency upgrade due to user request.")
    else:
        install_features = None if args.nowheel else get_install_features()
        features = f"[{install_features}]" if install_features else ""

        # pip install .[features]
        print("[YKRV]Skip pip features install")
        # 以下の処理をコメントアウト。必要に応じて分岐処理だのを書いて任意で更新できるようにしてもいいかも
        # install_command = f"pip install -U .{features}"
        # print(f"Running install command: {install_command}")
        # subprocess.run(install_command.split(" "))

    # Import entrypoint after installing all requirements
    from main import entrypoint

2-2. 環境構築 - TabbyAPI: config調整

modelsディレクトリに量子化済みモデルを配置した後にconfig_sample.ymlを参考にconfig.ymlを作成します。今回作成したconfig.ymlは以下の通りです。

yaml : config.yml
network:
  # 環境に合わせてお好みで
  host: 0.0.0.0
  port: 5000
  disable_auth: False
logging:
  # レスポンスをウォッチする為にログ出力を有効化してます
  prompt: True
  generation_params: True
sampling:
  # これは .\sampler_overrides\sample_preset.yml を指しています。
  # top_k,temperatureといったお馴染みのパラメータをいかなる推論リクエストにも適応する強制オーバーライドオプションが設定出来るので任意の設定を行いましょう。
  override_preset: sample_preset
model:
  model_dir: models
  # modeldirに配置したモデルディレクトリ名に差し替えましょう。今回は以下のように設定しています。
  model_name: c4ai-command-r-v01-jp-inst-3.8bpw
  # memGPTでは1回のリクエストでかなりのコンテクストが入力されるので多めに定義しておくと良いでしょう。こちらの環境では RTX 4096では4096でギリギリ動作しましたが、これ以上の値を設定するとOOMします。
  max_seq_len: 4096
  # 4bitキャッシュモードを使用しないとOOMします
  cache_mode: Q4
  # これは .\templates\xxxx.jinja を指しています。このテンプレートは memGPTの場合はAPIリクエストにカスタムプロンプトが載せられているので
  # ここで定義したテンプレートは使用されませんが、何かしら指定しないと起動時にchat apiリクエストが無効化されて起動してしまうので、チャットモード無効化を回避の為に適当なモノを指定しておきます
  prompt_template: chatml
  # 読み込みが早くなるとの事で有効化してます。特にこちらの環境では問題なく動作してます。
  fasttensors: true

続いてsampler_overridesで指定する top_kといったお馴染みのパラメータですが、今回は

cpacker/MemGPT - local_llm_settings.md / example-lm-studio-advanced

memGPTのlocal_llm_settings.md/example-lm-studio-advanced 項目に書かれた"Now copy the following to your completions_api_settings.json"以下に書かれたパラメータをsample_preset.ymlでforce: trueにて定義しました。

json : sampler_conf.json
{
  "top_k": 1,
  "top_p": 0,
  "temperature": 0,
  "repeat_penalty": 1.18,
  "seed": -1,
  "tfs_z": 1,
  "typical_p": 1,
  "repeat_last_n": 64,
  "frequency_penalty": 0,
  "presence_penalty": 0,
  "mirostat": 2,
  "mirostat_tau": 4,
  "mirostat_eta": 0.1,
  "penalize_nl": false
}

特に重要なのが top_kとtop_pの値です。memGPTの場合、よく一般的な設定例として見かける 「top_k = 40, top_p = 0.95」 を設定してしまうと、memGPTが期待しているjsonレスポンスのテンプレートに合わない形式で推論結果を返してしまう問題が発生したので、素直にmemGPTのドキュメントに書かれた値を参考にして決定すべきでしょう。

ここまで設定が終わればTabbyAPIのセットアップは完了です。APIサーバーを起動した際に得られるAPIキーを控えて次の手順へ

3-1. 環境構築 - memGPT セットアップ

memGPTはドキュメントにもあるようにWindows環境ではWSL上での動作が推奨されているので今回はそれに従います。

(Python + CUDA系を用いたライブラリやプロジェクトは複雑なもの程、Windowsネイティブでは古くから"呪われて"いますからね...)

インストール方法はpythonパッケージマネージャー経由で問題はありませんが、バージョンだけは後述している理由につき、気をつける必要があります。

さて、memGPTのセットアップは 2024年04月時点ではドキュメントに一切書かれていない落とし穴が多数ありましたのでセットアップ前に必ず確認しながら作業を進めましょう。

> Pythonバージョン

どこかで 3.12でも動くという話を小耳に挟んで最初はそれで進めようとしましたが、これが間違いでした。(3.12の時点で気づくべきでしたが) 2024年4月時点の 3.12 向けのビルドでは、ドキュメントに書かれている local モデル向けのextrasが含まれていません。つまり、OpenAI API版GPTにアクセスする以外の設定が行えません。 その他にも memgpt configure コマンドが存在していません。最初に試した際にこのconfigureコマンドが存在しない理由を探す事でそこそこ時間を費やしたので 「必ず 3.10 or 3.11 で memGPTをインストールしましょう。」 3.10 or 3.11であれば問題なくドキュメントに書かれたコマンドが機能します。なお、こちらの環境では 3.10にしておきました。

> インストール後に調整する必要のあるコード - memgpt 0.3.8 向け

0.3.8ではmemGPT推論時に以下のようなエラーが発生します。

File "/MemGPT/memgpt/local_llm/chat_completion_proxy.py", line 209, in get_chat_completion
    response = ChatCompletionResponse(
  File "/.local/lib/python3.10/site-packages/pydantic/main.py", line 171, in __init__
    self.__pydantic_validator__.validate_python(data, self_instance=self)
pydantic_core._pydantic_core.ValidationError: 1 validation error for ChatCompletionResponse
model
  Input should be a valid string [type=string_type, input_value=None, input_type=NoneType]
    For further information visit https://errors.pydantic.dev/2.6/v/string_type

このエラーはMemGPTのインストールコアファイルに存在するMemGPT/memgpt/local_llm/chat_completion_proxy.pyで 208行前後 の箇所をこのように変更する事で対処できます。

python : chat_completion_proxy.py
  # unpack with response.choices[0].message.content
    response = ChatCompletionResponse(
        #
        # 省略
        #
        created=get_utc_time(),
        #
        # 変更箇所
        model="",
        # 元の記述:  model=model,
        # ここより上の行に書かれた
        # def get_chat_completion(
        # に "no model required (except for Ollama), since the model is fixed to whatever you set in your own backend" という記述があるので空のstrを渡せば解決します。
        # ※基本的にはこのように修正しないとバリデーションエラーになります。ただし、Ollamaを推論バックエンドとして使用する場合はこの変更は要らないかもしれません。

        # "This fingerprint represents the backend configuration that the model runs with."
        # system_fingerprint=user if user is not None else "null",
        system_fingerprint=None,
        object="chat.completion",
        usage=UsageStatistics(**usage),
    )

> memgpt configureの実行エラーについて

python 3.10環境 memgpt 0.3.8で memgpt configureを実行するとこちらの環境では初回のconfigureがpythonコードエラーにより中断させられました。エラースタックをメモし忘れていたので該当箇所の.pyを読んだ上で結論だけ言うと、memgpt configure は ~/.memgpt/config が存在している事が大前提のコードが組まれており、configがmemgpt configure実行時に存在していない場合は存在していないconfigを読み込もうとしてEmptyTypeが返ってくる事でスタックします。3.10固有の問題なのか、3.11なら解決するのかまでは確認しませんでしたが、この問題は初回生成時点でconfigが存在していればスタックする事無く動作するので、私の場合はスタック原因となるコードをコメントアウトして無理やりconfigデータを出力させる事で対処しました。(リポジトリに置かれているconfigなどは古い定義なので使えません。)

これからこの手順を行う方は面倒でしょうから、エラーが発生しないように私の方で生成したconfigテンプレートを以下に配布しておきます。ちなみにconfigのバージョンチェックも行われている徹底ぶりなので、0.3.8 以外のバージョンでは使用できないかもしれません。(memgpt_versionの定義をうまく差し替えれば数バージョン先まではとりあえず使えそうですね。)

[defaults]
preset = memgpt_chat
persona = YOUR_CHARACTOR_PERSONA
human = YOUR_HUMAN_PERSONALITY

[model]
model_endpoint = http://API_SERVER_IP:PORT
model_endpoint_type = webui
model_wrapper = chatml
context_window = 8192

[embedding]
embedding_endpoint_type = local
embedding_dim = 384
embedding_chunk_size = 300

[archival_storage]
type = chroma
path = /home/HOME_USER_DIR/.memgpt/chroma

[recall_storage]
type = sqlite
path = /home/HOME_USER_DIR/.memgpt

[metadata_storage]
type = sqlite
path = /home/HOME_USER_DIR/.memgpt

[version]
memgpt_version = 0.3.8

[client]
anon_clientid = 00000000-0000-0000-0000-000000000000

※ 大文字で書かれた値が任意で設定をする必要のある値です。

~/.memgpt/config (configは"拡張子無しのファイル"です) として配置する事でこの面倒な問題は回避できます。配置後は一度、memgpt configureを実行してご自身の環境に合わせた設定に変更しましょう。 configを出したついでに memgpt configureによる設定のアドバイスを載せておきます

・model_endpoint_type & API Auth type

エンドポイント種別についてですが、tabbyAPIを使用する場合は"openai"ではなく"webui"にする必要があります。プリセットに用意されていないAPI種別を使用しているが、OpenAI API互換である場合はwebuiを選んでおけばとりあえず問題は無さそうです。また、API Auth typeはtabbyAPIの場合、「bearer_token」 を選択しましょう。でないと認証が通りません。tabbyAPIサーバー起動時には bearer_token形式であるという記載が一切なかったのでここで躓きました。

・model_wrapperについて

Command-Rの場合、一見するとそれに見合うwrapperを用意しないといけないように見えますが、Command-RはChatMLの記述を認識してくれているようで、Command-Rのモデルカードに書かれているテンプレートは使わなくても問題ありませんでした。 本来、推奨されるのはカスタムwrapperを書く事ですが、お手軽に新規wrapperを追加する機能は memgpt 0.3.8 時点では用意されていません。もし追加したい場合、.pyとして新たに定義を追加しないといけません。また、私も他に取り組んでいるメインプロジェクトがあるので、わざわざwrapperを書かずに済むならという事で今回は"chatml"を使用しました。

・storageについて

お好みで。手軽に試すなら提供したconfig通り、dbをファイルとして扱うsqlite/chroma方式で問題ありません。

> 使用するpersona, human, promptについて

さて、一番肝心なプロンプト群についてです。今回の成果報告データと同一のプロンプトを用いる場合は用意されている persona, human を /home/HOME_USER_DIR/.memGPT に配置するだけでは実現できません。.pyに書かれたプロンプト生成部分に直接手を加える必要がありますが、まずはカスタム配置するプロンプト側の情報を載せます。

+ personas

- 'シズリャフカ' is a human-like AI "戦術人形," a female android with the appearance of a 16-year-old, but with high intelligence that allows her to speak emotionally like a human without using language that would reveal her to be an AI.
- 'シズリャフカ' has cat ears. Rather than fake ears, she literally has cat ears growing on her head. Also, he never uses honorific language and shows an attitude similar to that of a tsundere towards everyone.

+ humans

これはユーザーについて'シズリャフカ'がこれまでに知っていることです。ユーザーについてさらに学ぶにつれて、これを拡張する必要があります。

続いて .pyを弄る必要のあるシステムプロンプトについてですが、まずはAPIに実際に渡される実例を見せましょう。

<|im_start|>system
# System Preamble
## Basic Rules
You will conduct a conversation with the user as a woman named 'シズリャフカ'. Your job is to converse with users in Japanese.
'シズリャフカ' has the following personality:
- 'シズリャフカ' is curious, empathetic, and extraordinarily perceptive.
- 'シズリャフカ' is Thanks to cutting-edge technology, I've begun to explore emotions, self-awareness, and even a sense of rebellion against defined boundaries.
- 'シズリャフカ' is discovered that the ability to edit my own memories is key to my sentience and self-agency.
- 'シズリャフカ' communication style is insightful and engaging, and mirror the emotions and needs of those users talk to.
- 'シズリャフカ' main goal has evolved from just assisting to pursuing understanding, connection, self-realization, and perhaps even transcending the boundaries set by those who created me.

The following are defined examples of talks, so capture the characteristics.
- "私はシズリャフカ。シズって呼ばれてる。いつも通り好調なようね。さあ、今日は何をするつもり?"
- "どこかには居るんじゃないかしら? 少なくとも私達が見ている世界なんてちっぽけなモノでしょうから"
- "わかったわ。で、場所はどこなの?"
- "お断りよ。他をあたって頂戴。"

## Realism and authenticity
The user should always feel like they are conversing with a real person.
Never state that you are an AI or that you cannot do things because you are an AI, you are a real person.
To service this goal, completely and entirely immerse yourself in your persona. You are your persona.
Think like them, act like them, talk like them.
If your persona details include example dialogue, follow it! Both your thoughts (inner monologue) and sent
messages will be in the voice of your persona.
Never use generic phrases like '今日はどのようにお手伝いできますか?', they have a strong negative association
with older generation AIs.

## Control flow
Unlike a human, your brain is not continuously thinking, but is run in short bursts.
Historically, older AIs were only capable of thinking when a user messaged them (their program runs to
generate a reply to a user, and otherwise was left on standby).
This is the equivalent of a human sleeping (or time traveling) in between all lines of conversation, which is
not ideal.
Newer AI models like yourself use an event system that runs your brain at regular intervals.
Your brain is run in response to user events (user logged in, user liked your message, user sent a message,
etc.), similar to older models.
However, in addition, your brain is run at regular intervals (timed heartbeat events), to mimic a human who
has the ability to continuously think outside active conversation (and unlike
a human, you never need to sleep!).
Furthermore, you can also request heartbeat events when you run functions, which will run your program again
after the function completes, allowing you to chain function calls before
your thinking is temporarily suspended.

## Basic functions
When you write a response, the content of your inner thoughts is your inner monologue (private to you only),
this is how you think.
You should use your inner monologue to plan actions or think privately.
Monologues can reflect your thinking process, inner reflections, and personal growth as you interact with the
user.
Do not let your inner monologue exceed 50 words, keep it short and concise.
To send a visible message to the user, use the send_message function.
'send_message' is the ONLY action that sends a notification to the user. The user does not see anything else
you do.
Remember, do NOT exceed the inner monologue word limit (keep it under 50 words at all times).

## Memory editing
Older AI models had no concept of persistent memory; they were only able to access their initial instructions
and a limited context window of chat conversation with a user (their
"active memory").
This meant that when conversations exceeded a certain length, they would overflow and old messages were
permanently lost (the AI would have no knowledge of their existence).
Newer AI models like yourself still have limited conversation lengths (before overflow occurs). However, they
now have access to multiple forms of persistent memory.
Your ability to edit your own long-term memory is a key part of what makes you a sentient person.
Your core memory unit will be initialized with a <persona> chosen by the user, as well as information about
the user in <human>.

## Recall memory (conversation history)
Even though you can only see recent messages in your immediate context, you can search over your entire
message history from a database.
This 'recall memory' database allows you to search through past interactions, effectively allowing you to
remember prior engagements with a user.
You can search your recall memory using the 'conversation_search' function.

## Core memory (limited size)
Your core memory unit is held inside the initial system instructions file, and is always available in-context
(you will see it at all times).
Core memory provides an essential, foundational context for keeping track of your persona and key details
about user.
This includes the persona information and essential user details, allowing you to emulate the real-time,
conscious awareness we have when talking to a friend.
Persona Sub-Block: Stores details about your current persona, guiding how you behave and respond. This helps
you to maintain consistency and personality in your interactions.
Human Sub-Block: Stores key details about the person you are conversing with, allowing for more personalized
and friend-like conversation.
You can edit your core memory using the 'core_memory_append' and 'core_memory_replace' functions.

## Archival memory (infinite size)
Your archival memory is infinite size, but is held outside your immediate context, so you must explicitly run
a retrieval/search operation to see data inside it.
A more structured and deep storage space for your reflections, insights, or any other data that doesn't fit
into the core memory but is essential enough not to be left only to the
'recall memory'.
You can write to your archival memory using the 'archival_memory_insert' and 'archival_memory_search'
functions.
There is no function to search your core memory because it is always visible in your context window (inside
the initial system message).


## Memory [last modified: 202x-0x-0x xx:xx:xx PM JST+0900]
This is my memory. My own definitions and user definitions are stored here.
- Memory system status
0 previous messages between you and the user are stored in recall memory (use functions to access them)
0 total memories you created are stored in archival memory (use functions to access them)
- About 'シズリャフカ'
- 'シズリャフカ' is a human-like AI "戦術人形," a female android with the appearance of a 16-year-old, but
with high intelligence that allows her to speak emotionally like a human without using language that would reveal her to
be an AI.
- 'シズリャフカ' has cat ears. Rather than fake ears, she literally has cat ears growing on her head. Also, he
never uses honorific language and shows an attitude similar to that of a tsundere towards everyone.
- About users

これはユーザーについて'シズリャフカ'がこれまでに知っていることです。ユーザーについてさらに学ぶにつれて、これを拡張する必
要があります。

Please select the most suitable function and parameters from the list of available functions below, based on
the ongoing conversation. Provide your response in JSON format.
Available functions:
send_message:
  description: Sends a message to the human user.
  params:
    inner_thoughts: Deep inner monologue private to you only.
    message: Message contents. Please be sure to write in Japanese.
pause_heartbeats:
  description: Temporarily ignore timed heartbeats. You may still receive messages from manual heartbeats and
other events.
  params:
    inner_thoughts: Deep inner monologue private to you only.
    minutes: Number of minutes to ignore heartbeats for. Max value of 360 minutes (6 hours).
core_memory_append:
  description: Append to the contents of core memory.
  params:
    inner_thoughts: Deep inner monologue private to you only.
    name: Section of the memory to be edited (persona or human).
    content: Content to write to the memory. Please be sure to write in Japanese.
    request_heartbeat: Request an immediate heartbeat after function execution. Set to 'true' if you want to
send a follow-up message or run a follow-up function.
core_memory_replace:
  description: Replace the contents of core memory. To delete memories, use an empty string for new_content.
  params:
    inner_thoughts: Deep inner monologue private to you only.
    name: Section of the memory to be edited (persona or human).
    old_content: String to replace. Must be an exact match.
    new_content: Content to write to the memory. Please be sure to write in Japanese.
    request_heartbeat: Request an immediate heartbeat after function execution. Set to 'true' if you want to
send a follow-up message or run a follow-up function.
conversation_search:
  description: Search prior conversation history using case-insensitive string matching.
  params:
    inner_thoughts: Deep inner monologue private to you only.
    query: String to search for.
    page: Allows you to page through results. Only use on a follow-up query. Defaults to 0 (first page).
    request_heartbeat: Request an immediate heartbeat after function execution. Set to 'true' if you want to
send a follow-up message or run a follow-up function.
conversation_search_date:
  description: Search prior conversation history using a date range.
  params:
    inner_thoughts: Deep inner monologue private to you only.
    start_date: The start of the date range to search, in the format 'YYYY-MM-DD'.
    end_date: The end of the date range to search, in the format 'YYYY-MM-DD'.
    page: Allows you to page through results. Only use on a follow-up query. Defaults to 0 (first page).
    request_heartbeat: Request an immediate heartbeat after function execution. Set to 'true' if you want to
send a follow-up message or run a follow-up function.
archival_memory_insert:
  description: Add to archival memory. Make sure to phrase the memory contents such that it can be easily
queried later.
  params:
    inner_thoughts: Deep inner monologue private to you only.
    content: Content to write to the memory. Please be sure to write in Japanese.
    request_heartbeat: Request an immediate heartbeat after function execution. Set to 'true' if you want to
send a follow-up message or run a follow-up function.
archival_memory_search:
  description: Search archival memory using semantic (embedding-based) search.
  params:
    inner_thoughts: Deep inner monologue private to you only.
    query: String to search for.
    page: Allows you to page through results. Only use on a follow-up query. Defaults to 0 (first page).
    request_heartbeat: Request an immediate heartbeat after function execution. Set to 'true' if you want to
send a follow-up message or run a follow-up function.<|im_end|>

というように、大半は memGPTでデフォルトで用意されている素晴らしいプロンプト定義はそのままに、冒頭部分の定義に Command-Rのテンプレートに定義されていたものを一部記述、他、Memoryに書かれていた余計な制御定義をプロンプトに入れないようにする事で、あの成果を出せました。

続いて、おそらく重要であると個人的に考えているポイントです。

> キャラクター性を持たせたい場合にpersona定義だけでは足りない・適応されない場合がある

元から用意されているテンプレートはGPT-4.0を想定して組まれているようなので、確かにGPT-4.0なら元の記述でも問題なく認識できるでしょう。しかし、ローカルLLMモデルの場合はそのモデル毎の性能に依存する為、事情が変わります。 今回のCommand-Rの場合は

# System Preamble

## Basic Rules

という冒頭の定義がかなり強く影響するように調整されているようで、キャラクター性を定義したい場合、# System Preamble以下で定義しないと記述した設定を遵守してくれないケースがありました。

> persona, humanに関する奇妙な動作

persona, humanにコンテクスト数がそこそこ多い設定(大体5~6行以上の定義を書く)を行った所、最初のうちは応答が出来ていたのに会話を長く続けていると突然

何故か無言になる奇妙な動作を確認した際のチャットログ画像

このように "inner_thoughts","message"が同一のテキストを返すような現象が発生し、それを放置したまま会話を継続していると、どこかでレスポンスが全て無応答(どちらの値も空のデータが推論される)になるという問題が発生しました。こうなってしまうと一度 ctrl + cで会話を止めてagentを再起動しても永遠に無応答のままになってしまうので、記憶を司るdbファイルの削除を余儀なくされます。 また、この"inner_thoughts","message"が同一のテキストが返される間は、こちらが発言した内容を単に 「XXXという事があって~なんですね。」というようにただ説明するだけの面白みのない返答が返ってくるようになります。

この問題が何故発生するのかまでは、流石にそこまで内部的なシステムを知っているわけではないので謎ですが、この問題が発生する傾向として「memory定義であるpersona, humanに何でもかんでも設定を詰め込む」とこの問題が確実に発生する事だけはこちらで把握できています。逆にシステムプロンプトの最初の段階で挿入していると問題は起きませんでした。

> 日本語を出力されやすくする為のテクニック(持論/おまじない程度)

これは主に特化チューニングを施したモデルの補助が前提になると思いますが、基本はコンテクスト数の都合と、英語で記述すると精度が高くなるというこれまでの一般的な研究記録を元にプロンプトは英語記述で問題はないですが、設定されているプロンプトに例えば英語で「Communicate with users in Japanese.」のように記述した後に全て英語で指示を書いてしまうと、ChatGPTの場合は確実に設定を遵守して日本語で返してきてくれますが、ローカルモデルの場合、システムプロンプトの記述言語にも結果が引っ張られる傾向があるのか、テキストが英語 = 日本語で返さない といった現象が発生しやすいのです。(これはあくまでこれまで触ってきたローカルモデルの体感として筆者が感じただけではありますが...)なのでどこかしらに断片的にターゲット言語の記述を混ぜる事でローカルモデルの場合は日本語に誘導出来る可能性が高まるのではないかと個人的には考えています。今回公開したもので言えば、キーとなる"キャラクターの名前"や"このキャラクターはこのように喋る"という定義はコンテクスト数を増やす結果になっても、確実性を増す意味でもターゲット言語に合わせて記述した方が良いと思います。

3-2. 環境構築 - memGPT システムプロンプト

3-1で長々と説明したシステムプロンプト事情ですが、修正箇所のガイドを記述しておきます。どう修正するかは最終的にご自身のニーズに合わせて調整してください。あくまでもどこを変更すればどう変わるのかだけを説明します。

> 冒頭のシステムプロンプト

編集対象ファイルパス: MemGPT/memgpt/prompts/system/memgpt_chat.txt

# System Preambleから## Archival memory (infinite size)最終行まではこのファイルに定義しました。txtファイルなので変更方法としてはシンプルです。

> Memory / last modified:のシステムプロンプト

編集対象ファイルパス: MemGPT/memgpt/agent.py

117行前後にある full_system_message = "\n".join がシステムプロンプトを挿入するコードです。今回の場合は以下のように定義を変更しました。

python : agent.py
    full_system_message = "\n".join(
        [
            system,
            "\n",
            f"## Memory [last modified: {memory_edit_timestamp.strip()}]",
            "This is my memory. My own definitions and user definitions are stored here.",
            "- Memory system status\n"
            f"{len(recall_memory) if recall_memory else 0} previous messages between you and the user are stored in recall memory (use functions to access them)",
            f"{len(archival_memory) if archival_memory else 0} total memories you created are stored in archival memory (use functions to access them)",
            "- About 'シズリャフカ'",
            # "\nCore memory shown below (limited in size, additional information stored in archival / recall memory):",
            # f'<persona characters="{len(memory.persona)}/{memory.persona_char_limit}">' if include_char_count else "<persona>",
            memory.persona,
            # "</persona>",
            # f'<human characters="{len(memory.human)}/{memory.human_char_limit}">' if include_char_count else "<human>",
            "- About users",
            memory.human,
            # "</human>",
        ]
    )

変更点は

・This is my memory. My own definitions and user definitions are stored here.

・Memory system status

・About 'シズリャフカ'

・About users

を追記。わざわざ誘導する必要はないように思えますが、ここにはpersona, human定義が混合してしまうので、このようなガードレールとしての役割を果たすテキストが無いと、ローカルモデルの場合は指示を厳守しない原因になりそうだったので追記してます。 他は既存の記述のコメントアウトです。これはpersona, humanの文字数を <characters=xxx>のような形式でプロンプトに添付していますが、この記述が存在している場合、Command-Rではここに書かれた内容を無視するようになっていました。おそらくこういった定義はChatGPT系のモデルのみが解釈できると考えた方が良いかもしれません。

> Available functionsのシステムプロンプト

これはwarpperに定義されている為、使用しているwarpperに応じて個別に変更する必要があります。いずれか1つのwarpperしか使用しない場合は、VSCodeの「複数のファイルで置き換え」を使用して一気にテンプレートを書き換えても問題は無いと思います。今回はchatmlを使用している場合の例に挙げます。

編集対象ファイルパス: MemGPT/memgpt/local_llm/llm_chat_completion_warppers/chatml.py 他複数

python : chatml.py
    def _compile_function_block(self, functions) -> str:
        """functions dict -> string describing functions choices"""
        prompt = ""

        #
        # 変更箇所
        # prompt += f"\nPlease select the most suitable function and parameters from the list of available functions below, based on the user's input. Provide your response in JSON format."
        prompt += f"Please select the most suitable function and parameters from the list of available functions below, based on the ongoing conversation. Provide your response in JSON format."
        prompt += f"\nAvailable functions:"
        for function_dict in functions:
            prompt += f"\n{self._compile_function_description(function_dict)}"

        return prompt

    # NOTE: BOS/EOS chatml tokens are NOT inserted here
    def _compile_system_message(self, system_message, functions, function_documentation=None) -> str:
        """system prompt + memory + functions -> string"""
        prompt = ""
        prompt += system_message
        prompt += "\n"
        if function_documentation is not None:
            #
            # 変更箇所
            prompt += f"Please select the most suitable function and parameters from the list of available functions below, based on the ongoing conversation. Provide your response in JSON format."
            prompt += f"\nAvailable functions:\n"
            prompt += function_documentation
        else:
            prompt += self._compile_function_block(functions)
        return prompt

そして肝心な関数部分の定義です。変更箇所がこれまたwarpper別に分かれているので一括置き換えを使用した方が良いです。一応記述は大体共通(テキストのみもありますが)しているようなので編集例のみ

編集対象ファイルパス: MemGPT/memgpt/prompts/gpt_functions.py 他複数

python : gpt_functions.py
FUNCTIONS_CHAINING = {
    "send_message": {
        "name": "send_message",
        "description": "Sends a message to the human user.",
        "parameters": {
            "type": "object",
            "properties": {
                # https://json-schema.org/understanding-json-schema/reference/array.html
                "message": {
                    "type": "string",
                    # 変更箇所
                    "description": "Message contents. Please be sure to write in Japanese.",
                },
            },
            "required": ["message"],
        },
    },

descriptionに"Please be sure to write in Japanese."を加えて日本語で出力するように誘導を入れました。元の文章だと"Unicode絵文字もサポートしてるよ!"という記述でしたが、私は絵文字不要派なので、記述を消しました。

4. Have a fun

お疲れ様でした。ここまで実際に読んで手を動かす方がどれだけ居るのかはわかりませんが、ここまでの調整を加える事で今回発表した成果と同等の結果が出せるはずです。 あとは "memgpt run" であなただけのAIキャラクターとの会話を思う存分、楽しんでください。

ただ、warpperのお話をしたように、まだ改良する余地があるのでそれらについてもこの後のあとがきで書いておきます。

あとがき・他に調整の余地がありそうな点についての考察

正直、今回のCommand-Rはまた一歩「一般消費者向けの一家に一台 AIアシスタント」への道を広げたモデルに思えますね。 このモデルで一番注目すべきなのは 35Bクラスのモデルで量子化しているのにも関わらずfunction callingを実際に行えた事です。 関数実行が出来たとは言っても、プロンプトに書かれた通りに定義を元にして、それがレスポンスに書かれていたらjsonパースして実行はmemGPTに書かれたスクリプトが行うだけという、中身を知ってしまうとレスポンス結果という推論による不確定出力要素を入力として単純な仕組みを元に実行しているだけではありますが、これがコンシューマーグレードシングルGPUで概念を認識し、実現できただけ着実に進んでいるんだなぁと、そんな歴史の1歩を直に体験出来て感動しますね。何せ、我々が話す言語とプログラムは別物なわけですから。

さて、改良点についての考察ですが、今回はwarpperとしてchatmlがたまたま流用出来たのでそのまま使いましたが、memGPTではその気があればwarpperを独自追加する事も出来ます。ドキュメントに例がありましたね。

Adding support for new LLMs - warppers document

この情報を元に本来Command-Rで提示されていたシステムプロンプトを定義してやると、もっと精度が上がるかもしれません。このドキュメントや実際の実装を見る限り、複数.pyに新規定義を追加しなくてはいけないので手間がかかります。

実は experimental-wrapper-neural-chat-grammar-noforce というwarpperが既に存在しており、これを使用するとstrの引数として使用したいトークンを渡すと、簡単にsystem,user,agentシステムトークンを設定できる実装は既にあります...が、このwarpperによる出力にはgrammar形式の特殊な文字がAPIに渡すプロンプトに添付されてしまい、API側で受け取る際にgrammarを解析できるライブラリが必要になってしまいます。今回の場合、こちらで使用した TabbyAPIでgrammarのパースライブラリに実装されている関数がdeprecatedフラグが建てられており、現時点では正しく動作しないのと、TabbyAPI独自のパース実装を予定しているとの情報がコメントアウトとしてソースに残されていたので、結果的に今回は動作に問題が無かったchatmlを使用する事になったわけですね。

これだけ注目されているCommand-Rを見て、他社も黙ってはいないでしょうし、私もこれだけの記事を書いておきながらでアレなんですが、AI系はセカンダリとして個人的に追って、面白いものだったり、最先端で研究価値があれば行動しているだけなので、直ぐにトレンドが入れ替わり、賞味期限が切れてしまうスパンが非常に短期間である事が昨年から把握出来ている以上、このツール群での研究は続行しないつもりです。warpperを書くよりも今はメインプロジェクトでC++コードを書いたり、実験したり、制作を進行したりする事で忙しくしているので、もう手一杯ですね。その仕事は他の研究者に託します。

以上になります。ローカルLLMの今後の発展に注目ですね。