- タイトルの件についての記事です
- AIで生成したまとめ、その後に本文(まとめの元ネタである自分の文章)という構成になっています
OpenAI(GPT-4o)によるまとめ
経緯
LangChainのバージョン0.2.14を使用し、VectorStoreとしてPostgreSQLとPGVectorを組み合わせた環境を前提としています。
この環境で、ドキュメント(Document)のメタデータを利用して特定のドキュメントを削除する方法が分からず、
最終的にはデータベースのテーブルに直接アクセスして削除する方法を選択しました。
目的
Embeddingの元となったファイルが更新された際に、対応するドキュメントをメタデータ(例:source: ファイル名
)に基づいて削除し、
新しいドキュメントを追加するプロセスを実現することが目的です。
原因
LangChainの公式ドキュメントやGitHubのディスカッションを調べても、メタデータに基づいてドキュメントを削除する方法が明確に記載されていなかったためです。
特に、similarity_search
メソッドではメタデータによるフィルタリングが可能ですが、フィルタリングされたすべてのドキュメントを返してくれるかどうかが不明でした。
構成要素
- LangChain: バージョン0.2.14
- VectorStore: PostgreSQLとPGVector
-
テーブル:
-
langchain_pg_collection
:uuid
,name
などの列があり、コレクション名を指定してuuid
(=collection_id)を取得可能 -
langchain_pg_embedding
:id
,collection_id
,cmetadata
などの列があり、collection_id
とメタデータの情報を指定してid
(=DocumentのID)を取得可能
-
具体的な手順
-
メタデータの設定:
- ドキュメントにメタデータ(例:
source: ファイル名
)を設定する
- ドキュメントにメタデータ(例:
-
ドキュメントの削除と追加:
- Embedding対象のファイルが更新された場合、メタデータとして
source: 対象ファイル名
を持つドキュメントを削除し、新しいドキュメントを追加する
- Embedding対象のファイルが更新された場合、メタデータとして
-
LangChainの制約:
- LangChainの公式ドキュメントでは、ドキュメントIDを指定して削除する方法は記載されていますが、メタデータ内の情報を指定して削除する方法は記載されていません
-
DBの直接操作:
- LangChainの機能でメタデータに基づいたドキュメントの削除方法が不明なため、データベースのテーブルから直接データを削除することにした
-
テーブル構造:
-
langchain_pg_collection
テーブルにはuuid
とname
の列があり、コレクション名を指定してuuid
(=collection_id)を取得可能 -
langchain_pg_embedding
テーブルにはid
,collection_id
,cmetadata
の列があり、collection_id
とメタデータの情報を指定してid
(=DocumentのID)を取得可能
-
-
削除の実行:
- 取得した
id
をLangChainのdelete
メソッドに渡し、メタデータに基づいたドキュメント削除を行う
- 取得した
参考情報
- PGVector | LangChain
- How to get IDs of documents stored in postgres pgvector #17361
- How to get source/Metadata in Pgvector? #17497
本文
目的
LangChainのDocumentオブジェクトにはメタデータを設定できます。例えば、Embeddingの元となったファイルの場所(例:source: ファイル名
)を指定できます。
Embedding対象のファイルが更新された際、対応するメタデータ(例:source: ファイル名
)を持つドキュメントを削除し、
新しいドキュメントを追加する処理を実現したいと考えています。
前提環境
LangChain v0.2.14を使用し、VectorStoreとしてPostgreSQLとPGVectorを組み合わせた環境を前提としています。
課題
PGVector | LangChainを参照すると、
ドキュメントIDを指定してドキュメントを削除する方法は記載されていますが、メタデータに基づいて削除する方法は記載されていません。
similarity_search
メソッドではメタデータによるフィルタリングが可能ですが、フィルタリングされた全てのドキュメントを返してくれるかはよくわかりません。
同様の悩みを抱えている人もいるようです:
- How to get IDs of documents stored in postgres pgvector #17361
- How to get source/Metadata in Pgvector? #17497
目的のドキュメントをすべて削除するにはどうすればよいのでしょうか?
どうやら、LangChainの機能でメタデータに基づいたドキュメントの削除方法は見当たらないみたいなので、
上記のイシューでも述べられているように、データベースのテーブルから直接データを削除することにしました。
対応
LangChainでPGVectorをVectorStoreとして使用する場合、以下のテーブルが作成されます:
-
langchain_pg_collection
:uuid
、name
などの列があり、コレクション名を指定してuuid
(=collection_id)を取得可能です。 -
langchain_pg_embedding
:id
、collection_id
、cmetadata(=Documentオブジェクトのmetadata)
などの列があります
LangChainでベクトル格納時に指定するコレクションを指定してlangchain_pg_collectionからcollection_id
を取得し、
langchain_pg_embeddingからcollection_id
とメタデータの情報を指定してid
(=ドキュメントのID)を取得。
取得したIDをLangChainのdelete
メソッドに渡すことで、メタデータに基づいたドキュメント削除を行うことにしました。
検証
環境の準備
PGVector | LangChainを参考にして、PostgreSQLとPGVectorを準備し、ドキュメントを格納します。
なお、サンプルコードはWindowsおよびPython 3.11の環境で試しています。
必要なパッケージのインストール
まず、必要なパッケージをインストールします。
pip install langchain langchain_postgres sqlalchemy psycopg-binary openai langchain-openai
PostgreSQLコンテナの起動
次に、PostgreSQLコンテナを起動します。
docker run --name pgvector-container -e POSTGRES_USER=langchain -e POSTGRES_PASSWORD=langchain -e POSTGRES_DB=langchain -p 6024:5432 -d pgvector/pgvector:pg16
ドキュメントを追加し、ベクトル検索を行う
サンプルコード
import os
import getpass
from sqlalchemy.orm import Session
from langchain_openai import OpenAIEmbeddings
from langchain_core.documents import Document
from langchain_postgres import PGVector
# 環境変数の設定
os.environ["OPENAI_API_KEY"] = getpass.getpass()
# Embeddingsの初期化
embeddings = OpenAIEmbeddings(model="text-embedding-3-large")
# PostgreSQL接続情報
connection = "postgresql+psycopg://langchain:langchain@localhost:6024/langchain"
collection_name = "my_docs"
# PGVectorの初期化
vector_store = PGVector(
embeddings=embeddings,
collection_name=collection_name,
connection=connection,
use_jsonb=True,
)
# ドキュメントの追加
docs = [
Document(page_content="there are cats in the pond", metadata={"source": "file1.txt"}),
Document(page_content="ducks are also found in the pond", metadata={"source": "file1.txt"}),
Document(page_content="elephants are also found in the pond", metadata={"source": "file2.txt"}),
]
vector_store.add_documents(docs)
# ベクトル検索
results = vector_store.similarity_search("cats", k=10)
print("search results:")
for doc in results:
print(f"* {doc.page_content} [{doc.metadata}]")
実行結果
(venv-langchain-sample1) C:\Users\user\source\repos\venv-langchain-sample1>python sample1.py
Password:
search results:
* there are cats in the pond [{'source': 'file1.txt'}]
* elephants are also found in the pond [{'source': 'file2.txt'}]
* ducks are also found in the pond [{'source': 'file1.txt'}]
メタデータsource=file1.txt
のドキュメントIDを取得する
サンプルコード
import os
from sqlalchemy.orm import Session
from sqlalchemy import create_engine
from sqlalchemy.sql import text
connection = "postgresql+psycopg://langchain:langchain@localhost:6024/langchain"
collection_name = "my_docs"
metadata_key = "source"
metadata_value = "file1.txt"
engine = create_engine(connection)
with Session(engine) as session:
stmt = text("select uuid from langchain_pg_collection where name=:name")
stmt = stmt.bindparams(name=collection_name)
rows = session.execute(stmt).fetchone()
if len(rows) == 0:
exit()
collection_id = rows[0]
print(f'collection_id:{collection_id}')
stmt = text("select id, document, cmetadata from langchain_pg_embedding where collection_id=:collection_id and cmetadata->>:key=:value")
stmt = stmt.bindparams(collection_id=collection_id, key=metadata_key, value=metadata_value)
rows = session.execute(stmt).all()
print('query results:')
for row in rows:
print(row)
実行結果
(venv-langchain-sample1) C:\Users\user\source\repos\venv-langchain-sample1>python sample2.py
collection_id:580c57cb-6bbe-45c8-b525-dfec94862192
query results:
('33c5721f-cb1f-42f0-89e5-3e061babe1f0', 'there are cats in the pond', {'source': 'file1.txt'})
('230211cd-192b-4971-99f9-3484957408ca', 'ducks are also found in the pond', {'source': 'file1.txt'})
ドキュメントIDを取得してから削除する
サンプルコード
import os
import getpass
from sqlalchemy.orm import Session
from sqlalchemy import create_engine
from sqlalchemy.sql import text
from langchain_openai import OpenAIEmbeddings
from langchain_core.documents import Document
from langchain_postgres import PGVector
# 環境変数の設定
os.environ["OPENAI_API_KEY"] = getpass.getpass()
# Embeddingsの初期化
embeddings = OpenAIEmbeddings(model="text-embedding-3-large")
# PostgreSQL接続情報
connection = "postgresql+psycopg://langchain:langchain@localhost:6024/langchain"
collection_name = "my_docs"
# PGVectorの初期化
vector_store = PGVector(
embeddings=embeddings,
collection_name=collection_name,
connection=connection,
use_jsonb=True,
)
# metadataのsource=file1.txtのドキュメントIDの取得
metadata_key = "source"
metadata_value = "file1.txt"
engine = create_engine(connection)
with Session(engine) as session:
stmt = text("select uuid from langchain_pg_collection where name=:name")
stmt = stmt.bindparams(name=collection_name)
rows = session.execute(stmt).fetchone()
if len(rows) == 0:
exit()
collection_id = rows[0]
print(f'collection_id:{collection_id}')
stmt = text("select id, document, cmetadata from langchain_pg_embedding where collection_id=:collection_id and cmetadata->>:key=:value")
stmt = stmt.bindparams(collection_id=collection_id, key=metadata_key, value=metadata_value)
rows = session.execute(stmt).all()
print('query results:')
for row in rows:
print(row)
document_ids = [row[0] for row in rows]
# vector_storeのdeleteメソッドを実行
vector_store.delete(document_ids)
# 削除した結果
with Session(engine) as session:
stmt = text("select uuid from langchain_pg_collection where name=:name")
stmt = stmt.bindparams(name=collection_name)
rows = session.execute(stmt).fetchone()
if len(rows) == 0:
exit()
collection_id = rows[0]
print(f'collection_id:{collection_id}')
stmt = text("select id, document, cmetadata from langchain_pg_embedding where collection_id=:collection_id and cmetadata->>:key=:value")
stmt = stmt.bindparams(collection_id=collection_id, key=metadata_key, value=metadata_value)
rows = session.execute(stmt).all()
print('query results:')
for row in rows:
print(row)
実行結果
(venv-langchain-sample1) C:\Users\user\source\repos\venv-langchain-sample1>python sample3.py
Password:
collection_id:580c57cb-6bbe-45c8-b525-dfec94862192
delete前のlangchain_pg_embeddingの検索結果:
('33c5721f-cb1f-42f0-89e5-3e061babe1f0', 'there are cats in the pond', {'source': 'file1.txt'})
('230211cd-192b-4971-99f9-3484957408ca', 'ducks are also found in the pond', {'source': 'file1.txt'})
collection_id:580c57cb-6bbe-45c8-b525-dfec94862192
delete後のlangchain_pg_embeddingの検索結果:
delete後はsource:file1.txtのデータは0件となった。
まとめ
- langchain+PGVectorで、langchainのAPIでメタデータの情報に基づいたドキュメント取得、削除する方法がわからず、DBのテーブルから直接データを削除する方法で対処しました。
- 目的のDocumentを削除することが出来ましたが、この方法がベストなものかどうかは不明で、ほかにも良いやり方や実はlangcnain公式のやり方などがあるのかもしれません。皆様何かご存じであれば、教えていただけると幸いです