Zenn
Closed4

コマンドラインでLLMが使える「llm」を試す2: Embedding

kun432kun432

前回同様、Dockerでお試し。

docker run --rm -it python:3.12-slim /bin/bash

以降はDockerコンテナ内で。

パッケージインストール

pip install llm
llm --version
出力
llm, version 0.17.1

OpenAIのAPIキーをセット

llm keys set openai

APIキーを入力

出力
Enter key: 

OpenAIで使用可能なモデルを確認

llm embed-models
出力
ada-002 (aliases: ada)
3-small
3-large
3-small-512
3-large-256
3-large-1024

デフォルトのモデルを確認

llm embed-models default
出力
<No default embedding model set>

初期状態では設定されていないみたい。

text-embedding-3-smallをデフォルトにセット

llm embed-models default 3-small

再度デフォルトを確認

llm embed-models default
出力
3-small

ではEmbeddingsを生成してみる。-ccontents)で文字列を渡す

llm embed -c 'こんにちは!'
出力
[0.040503513, -0.013761906, -0.029625986, (snip) , 0.009661444, 0.025641633, 0.0067220675]

モデルを明示的に指定する場合は-m

llm embed -c 'こんにちは!' -m 3-large
出力
[0.0035559454, 0.00020729085, -0.014258971, (snip) , -0.0055388995, 0.0020199036, -0.0008766699]
kun432kun432

生成したEmbeddingsをSQLiteに保存することができる。

まずSQLiteのデータベースがどこに作成されるかを確認

llm collections path
出力
/root/.config/io.datasette.llm/embeddings.db

上記はDockerコンテナの場合だが、通常はユーザの$HOME/.config/io.datasette.llm/embeddings.dbになると思う。ただし、この時点ではDBファイル自体はまだ作成されていない。

ls -lt /root/.config/io.datasette.llm/embeddings.db
出力
ls: cannot access '/root/.config/io.datasette.llm/embeddings.db': No such file or directory

ではデータベースに登録する。データベースは

  • コレクション: 同じモデルを使って生成したEmbeddingsのグループ。
  • ID: 個々のEmbeddingsを識別するための一意なキーとなるID。

で管理することになる。コレクションが1つのベクトル空間になるのだろうと思う。

コレクションはIDはllm embed コレクション名 キー名 -c "文字列" --storeで指定する。以下の場合だと、コレクション名: quotes、キー名: keiba-1となる。

llm embed quotes keiba-1 -c "皐月賞は最も速い馬が勝つ" --store

--storeをつけない場合、EmbeddingsとIDだけが保存される。--storeをつけると指定した文字列がcontentとして併せて保存される。一般的には、Embeddingsに紐づいている文字列なしで使うことは少ないかなと思うので、--storeは必須と考えて良さそう。

コレクションの一覧を確認

llm collections list
出力
quotes: 3-small
  1 embedding

Embeddingsが一つ登録されているのがわかる。

データベースファイルも確認。

ls -lt /root/.config/io.datasette.llm/embeddings.db
出力
-rw-r--r-- 1 root root 40960 Nov  6 11:41 /root/.config/io.datasette.llm/embeddings.db

データベースファイルが作成されているのがわかる。

では、続けて複数登録してみる。

llm embed quotes keiba-2 -c "ダービーは最も運のいい馬が勝つ" --store
llm embed quotes keiba-3 -c "菊花賞は最も強い馬が勝つ" --store

ではベクトル検索してみる。検索はllm similarを使う。

llm similar quotes -c '最も強い馬が勝つのは?'
出力
{"id": "keiba-3", "score": 0.7369883088595545, "content": "\u83ca\u82b1\u8cde\u306f\u6700\u3082\u5f37\u3044\u99ac\u304c\u52dd\u3064", "metadata": null}
{"id": "keiba-2", "score": 0.6486974513369872, "content": "\u30c0\u30fc\u30d3\u30fc\u306f\u6700\u3082\u904b\u306e\u3044\u3044\u99ac\u304c\u52dd\u3064", "metadata": null}
{"id": "keiba-1", "score": 0.5854581385413556, "content": "\u7690\u6708\u8cde\u306f\u6700\u3082\u901f\u3044\u99ac\u304c\u52dd\u3064", "metadata": null}

Unicodeエンコードされているので、jqでデコードする。Dockerコンテナなのでjqがない。インストール。

apt update && apt install -y jq
llm similar quotes -c '最も強い馬が勝つのは?' | jq -c .
出力
{"id":"keiba-3","score":0.736949844261556,"content":"菊花賞は最も強い馬が勝つ","metadata":null}
{"id":"keiba-2","score":0.6487469154391556,"content":"ダービーは最も運のいい馬が勝つ","metadata":null}
{"id":"keiba-1","score":0.585456258576198,"content":"皐月賞は最も速い馬が勝つ","metadata":null}

想定通り。

ではRAGをやってみる。

question="最も強い馬が勝つのはなんというレースですか?"
llm similar quotes -c $question | jq -c . | llm $question
出力
最も強い馬が勝つと言われるレースは「菊花賞」です。

シェルスクリプトとかにしておくといいかもね

以下は余談。

SQLiteの中身をのぞいてみた。

SQLiteのデータベースの中身を少しのぞいてみる。sqlite3をインストール。

apt install -y sqlite3

データベースに接続

sqlite3 /root/.config/io.datasette.llm/embeddings.db
出力
SQLite version 3.40.1 2022-12-28 14:03:47
Enter ".help" for usage hints.
sqlite>
SELECT name FROM sqlite_master WHERE type='table';
出力
_sqlite_migrations
collections
embeddings
.schema collections
出力
CREATE TABLE [collections] (
   [id] INTEGER PRIMARY KEY,
   [name] TEXT,
   [model] TEXT
);
CREATE UNIQUE INDEX [idx_collections_name]
    ON [collections] ([name]);
SELECT * FROM collections;
出力
1|quotes|3-small
.schema embeddings
出力
CREATE TABLE IF NOT EXISTS "embeddings" (
   [collection_id] INTEGER REFERENCES [collections]([id]),
   [id] TEXT,
   [embedding] BLOB,
   [content] TEXT,
   [content_blob] BLOB,
   [content_hash] BLOB,
   [metadata] TEXT,
   [updated] INTEGER,
   PRIMARY KEY ([collection_id], [id])
);

表示できるものだけ。

sqlite> SELECT collection_id, id, content, metadata, updated FROM embeddings;
出力
1|keiba-1|皐月賞は最も速い馬が勝つ||1730893298
1|keiba-2|ダービーは最も運のいい馬が勝つ||1730893646
1|keiba-3|菊花賞は最も強い馬が勝つ||1730893650

sqlite-vssとか使ってるのかなと思いきや、そうではなさそう。

https://github.com/simonw/llm/tree/main/docs/embeddings/storage.md

以下には注意

これは現在、大きなコレクションに対してうまくスケールしない、遅いブルートフォースアプローチを使用しています。プラグインによって提供されるベクトルインデックスによって、よりスケーラブルなアプローチを追加する計画については issue 216 を参照してください。

kun432kun432

まとめ

Embeddingsも扱えてSQLiteがビルトインされているので、CLIでRAGもお手軽にできた。ファイルが多い場合は検索速度がネックになるかもしれないが、上に記載している通り、ベクトルDBもプラグイン化する計画はある様子なので、そのうちできるようになるかも?(ただIssueの最終更新は去年なのですぐにはできなさそう・・・)

https://github.com/simonw/llm/issues/216

その他ドキュメントには

  • 画像のEmbeddings(llm-clipを使用)
  • sentence-transformersを使用したEmbeddings(llm-sentence-transformersを使用)
  • メタデータを追加
  • コンテンツをファイルに記載して、まとめてバッチでEmbedding生成・登録
  • ディレクトリ内のファイルを、まとめてバッチでEmbedding生成・登録
  • データベースファイルを明示的に指定

などが紹介されているので参考に。

GitHubレポジトリをクローンして、レポジトリ内のファイルをまとめてベクトル化してDBに登録、そのままDBファイルもレポジトリにコミットしておけば、誰でも手元でRAGできる、みたいなユースケースは面白そう。

このスクラップは5ヶ月前にクローズされました
ログインするとコメントできます