LangChain ベクターストアへのデータの保存
RAGの為のデータを取得、分割、ベクトル化、保存の一連の流れを試しています
記事作成日:2025-11-30, 著者: Hi6
Overview
LLM へ情報を与えるRAGのためのベクターストアへのデータの保存手順をまとめます。
- Load : LLMに情報を与えたい内容のドキュメントをプログラムに読み込みます。
- ドキュメントはWebやPDF、マークダウン形式とあらゆる形式から読み込める。
- Split : 大きなドキュメントを小さく分割してデータを保存します。
- これによりindex化が効能可能になり検索性能が向上します。大きなデータの検索は時間がかかる。
- Embedding : LLMが類似度で検索できるように細切れにしたドキュメントをベクトル化します。
- Store : ベクトル化したドキュメントをデータベースに保存します。
1. Loading documents
PDF から読み込む pdf からデータを読み込む場合、pypdfが有名でLangChainの公式でも例として使用されているのですが、日本語を含むpdfでは文字化けして利用できません。英文のみの場合は利用可能です。 そこで日本語が読み込めるツールを探した結果、PyMuPDFがよさそうでLangChainも利用可能です。ここではPyMuPDFを利用したドキュメントの読み込みを試します。
import pymupdf
print(pymupdf.__doc__) # バージョン情報の表示
# PDFを読み込む
filename = 'target.pdf'
doc = pymupdf.open(filename)
# 情報取得
print(doc.page_count) # pdfファイルの総ページ数
print(doc.metadata) # メタデータの取得
print(doc.get_toc()) # 目次の取得 [階層レベル, title, ページ数]
print(doc.load_page(26).get_text()) # 指定ページのテキストを取得(0‑origin)
Webページから読み込む 単一Webページの指定部分を Beautiful Soup で読み込みます。
import bs4
from langchain_community.document_loaders import WebBaseLoader
# ターゲットサイトの読み込みたい部分を class や id を指定して取得します
bs4_strainer = bs4.SoupStrainer(\
class_=("post-title", "post-header", "post-content")) # クラス指定例
# pythonの予約語と衝突しないように `class_`が採用されています
bs4_strainer = bs4.SoupStrainer(id="content-area") # id 指定例
# loader の作成
loader = WebBaseLoader(
# ターゲットサイトアドレス
web_paths=["https://docs.langchain.com/oss/python/langchain/rag"],
# ターゲットクラスやID
bs_kwargs={"parse_only": bs4_strainer}
)
docs = loader.load() # loaderを利用してドキュメントを読み込みます
複数のWebページから読み込む ローカルに保存したsitemap.xmlを利用してsitemapに記載されたアドレスのコンテンツを取得します。
import bs4
from langchain_community.document_loaders import SitemapLoader
bs4_strainer = bs4.SoupStrainer(class_="document") # ターゲットクラスの指定
xml_path = "./sitemap.xml" # ローカルに保存したサイトマップ
loader = SitemapLoader(
xml_path,
bs_kwargs={"parse_only": bs4_strainer},
is_local = True, # ローカルのサイトマップの使用を指定
)
docs = loader.load()
複数のMarkDownファイルの読み込み ローカルに保存したマークダウンファイルをディレクトリを指定して、サブディレクトリも含め全ファイルを読み込みます。
import os
from langchain_core.documents import Document
all_files = [] # 対象ファイルパス
docs = [] # 対象のドキュメント化
pattern = os.path.join(DIR_TEXT_DATA, "**", "*.md")
all_files.extend(glob.glob(pattern, recursive=True))
for file_path in all_files:
with open(file_path, 'r', encoding='utf-8') as f:
content = f.read()
doc = Document(
page_content=content,
metadata={
"source": file_path,
"filename": os.path.basename(file_path)
}
)
docs.append(doc)
2. Splitting documents
from langchain_text_splitters import RecursiveCharacterTextSplitter
jp_separators=[
"\n\n",
"\n",
" ",
".",
",",
"\u200b", # 文書中で文字幅をゼロにしたスペース。
"\uff0c", # 日本語・中国語で使われる「全角コンマ」,
"\u3001", # 日本語・中国語で使われる「句読点」、
"\uff0e", # 全角ドット .
"\u3002", # 句点 。
"",
]
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=1000,
chunk_overlap=200,
separators=jp_separators,
is_separator_regex=True,
add_start_index=True
)
all_splits = text_splitter.split_documents(docs) # ドキュメントの分割
- chunk_size : チャンクの最大サイズ。サイズは length_function によって決まります。
- chunk_overlap : チャンク間のターゲットの重複。チャンクを重複させることで、チャンク間でコンテキストが分割された際に生じる情報の損失を軽減できます。
- length_function : チャンク サイズを決定する関数。デフォルトはpythonの組み込み関数の len
- is_separator_regex : 区切りリスト (デフォルトは
["\n\n", "\n", " ", ""]) を正規表現として解釈するかどうか。 - add_start_index : 分割後の
Document.metadataに、元テキスト全体に対する 開始文字位置(0‑origin) をstart_indexとして付加
3. Embedding and 4. Storing documents
ローカルに立ち上げたollamaを利用してベクトル化をし、ベクターストアChromaにデータを保存します。ollamaもChromaもLangChainによりサポートされているので簡単に実装できます。
from langchain_community.vectorstores import Chroma # 少し古い書き方
from langchain_chroma import Chroma # 新しい書き方
from langchain_ollama import OllamaEmbeddings
# 事前に ollama pull granite-embedding:278m で取得しておきます
# 日本語を対象にする場合は、embeddingに使用するモデルも
# 日本語を理解できるモデルである必要があります。granite-embedding:278mは 多言語 OK
# ベクトル化に使用するembeddingを作成
embeddings = OllamaEmbeddings(model="granite-embedding:278m")
# ベクターストアの作成
vector_store = Chroma(
collection_name="web_collection",
persist_directory="./chroma_langchain_db", #データベースを保存するディレクトリ
embedding_function=embeddings #ベクトル化に使用するembeddings
)
# ドキュメントの登録
_ = vector_store.add_documents(documents=all_splits)
# add_documents することで分割されたドキュメントはベクトル化されデータベースに登録されます
# 追加されたテキストの ID のリストが返りますがここでは必要ないので _ で受け取らない
# 作成と同紙に登録する場合はfrom_documentsを使えます
vector_store = Chroma.from_documents(
collection_name = "pdf_documents",
persist_directory="./chroma_langchain_db",
embedding=embeddings,
documents=all_splits, # ドキュメント指定
)
登録された内容をベクトル検索で取得してみる
# ベクター検索
results = vector_store.similarity_search(
query="ここに検索に引っ掛けたい内容のプロンプトを記入する",
k=2 # 検索結果をいくつ取得するか デフォルトは 4
)
# リトライバーを使用すれば複数のプロンプトによる検索も可能
retriever = vector_store.as_retriever(
search_type="similarity",
search_kwargs={"k": 2},
)
results = retriever.batch(
[
"ここに検索に引っ掛けたい内容のプロンプトを記入する",
"ここに検索に引っ掛けたい内容のプロンプトを記入する",
],
)
print(results)
[!NOTE] 参照サイト