🐷

URL(HTTP),PDF,Word の中身を読み取ってVector Searchに投入する方法

2024/06/03に公開

前回の記事ではローカルに存在しているテキストファイルの中身をベクトル化しVector Searchに投入し検索を行う方法を行いました。
https://zenn.dev/kameping/articles/128ac71b824148
おさらいですが単純な処理フローは

1.指定されたファイルを指定されたパラメータに基づき任意の長さに分割
2.OpenAIを用いてベクトル化
3.Vector Searchにベクトルデータを保存
4.自然言語で検索
4.1 検索文字列をOpenAIのEmbeddedされたモデルでベクトル化
4.2 Vector Search にベクトル検索を実施

となっています。
使い方としては社内向け文書など一般的にウェブでは入手できない情報をベクトル化させVector Searchに登録しておくといった使い方が想定されます。普通にWebから入手できる情報であれば普通にLLMを使った方が精度が良いです。(ただしLLMは学習時点での情報で止まるため、保管としてこのようなRAGを使うことで最新の情報に対して検索を行えるのは意味があるかもしれません。)

社内文書ということを考えると、URL、PDF、Wordなんかが多いのではないかと思います。それぞれ専用のライブラリが準備されているのでやっていきます。

さっそくやってみる URL編

https://pypi.org/project/beautifulsoup4/
beautifulsoupというライブラリを使います。ウェブページのスクレイパーツールです。

pip install requests beautifulsoup4
import getpass
import os
import requests
from bs4 import BeautifulSoup
from langchain.text_splitter import CharacterTextSplitter
from langchain_community.document_loaders import TextLoader
from langchain_community.vectorstores import TiDBVectorStore
from langchain_openai import OpenAIEmbeddings

# Set OpenAI API key
os.environ["OPENAI_API_KEY"] = getpass.getpass("OpenAI API Key:")

# Set TiDB connection string
tidb_connection_string_template = "mysql+pymysql://<userid>:<PASSWORD>@<host>:4000/test?ssl_ca=/etc/ssl/cert.pem&ssl_verify_cert=true&ssl_verify_identity=true"
ssl_ca=/etc/ssl/cert.pem&ssl_verify_cert=true&ssl_verify_identity=true"
tidb_password = getpass.getpass("Input your TiDB password:")
tidb_connection_string = tidb_connection_string_template.replace(
    "<PASSWORD>", tidb_password
)

# Fetch the Wikipedia page content
url = "https://ja.wikipedia.org/wiki/%E6%A9%9F%E5%8B%95%E6%88%A6%E5%A3%AB%E3%82%AC%E3%83%B3%E3%83%80%E3%83%A0"
response = requests.get(url)
soup = BeautifulSoup(response.content, "html.parser")

# Extract the main content text from the page
content = []
for p in soup.find_all('p'):
    content.append(p.get_text())

# Combine all paragraphs into a single text
full_text = '\n'.join(content)

# Save the text to a temporary file for compatibility with TextLoader (if needed)
with open("temp_state_of_the_union.txt", "w", encoding="utf-8") as f:
    f.write(full_text)

# Load and split the text
loader = TextLoader("temp_state_of_the_union.txt",encoding="utf-8")
documents = loader.load()
text_splitter = CharacterTextSplitter(chunk_size=1000, chunk_overlap=0)
docs = text_splitter.split_documents(documents)

# Create embeddings and store in TiDB
embeddings = OpenAIEmbeddings()
TABLE_NAME = "semantic_embeddings"
db = TiDBVectorStore.from_documents(
    documents=docs,
    embedding=embeddings,
    table_name=TABLE_NAME,
    connection_string=tidb_connection_string,
    distance_strategy="cosine",  # default, another option is "l2"
)

# Query the database
query = "アムロはどういう人ですか?"
docs_with_score = db.similarity_search_with_score(query, k=3)

# Print the results
for doc, score in docs_with_score:
    print("-" * 80)
    print("Score: ", score)
    print(doc.page_content)
    print("-" * 80)

変更点はとてもシンプルです。

import requests
from bs4 import BeautifulSoup
<snip>
url = "https://ja.wikipedia.org/wiki/%E6%A9%9F%E5%8B%95%E6%88%A6%E5%A3%AB%E3%82%AC%E3%83%B3%E3%83%80%E3%83%A0"
response = requests.get(url)
soup = BeautifulSoup(response.content, "html.parser")
full_text = '\n'.join(content)

# Save the text to a temporary file for compatibility with TextLoader (if needed)
with open("temp_state_of_the_union.txt", "w", encoding="utf-8") as f:
    f.write(full_text)

最初の必要ライブラリをインポートし、必要なURLを指定すればその中身をtemp_state_of_the_union.txtに吐き出してくれます。あとは同じです。

さっそくやってみる PDF編

次にPDFをやってみます。
https://pymupdf.readthedocs.io/en/latest/
PyMuPDFを使います。

pip install PyMuPDF
pip install langchain[all]
import fitz  # PyMuPDF
import os
import getpass
from langchain.text_splitter import CharacterTextSplitter
from langchain_community.document_loaders import TextLoader
from langchain_community.vectorstores import TiDBVectorStore
from langchain_openai import OpenAIEmbeddings

# Set OpenAI API key
os.environ["OPENAI_API_KEY"] = getpass.getpass("OpenAI API Key:")

# Set TiDB connection string
tidb_connection_string_template = "mysql+pymysql://<userid>:<PASSWORD>@<host>:4000/test?ssl_ca=/etc/ssl/cert.pem&ssl_verify_cert=true&ssl_verify_identity=true"
ssl_ca=/etc/ssl/cert.pem&ssl_verify_cert=true&ssl_verify_identity=true"
tidb_password = getpass.getpass("Input your TiDB password:")
tidb_connection_string = tidb_connection_string_template.replace(
    "<PASSWORD>", tidb_password
)

# Read the PDF file and extract text
pdf_path = "path_to_your_pdf_file.pdf"
document = fitz.open(pdf_path)
pdf_text = ""
for page_num in range(document.page_count):
    page = document.load_page(page_num)
    pdf_text += page.get_text()

# Save the text to a temporary file for compatibility with TextLoader
with open("temp_pdf_text.txt", "w", encoding="utf-8") as f:
    f.write(pdf_text)

# Load and split the text
loader = TextLoader("temp_pdf_text.txt", encoding="utf-8")
documents = loader.load()
text_splitter = CharacterTextSplitter(chunk_size=1000, chunk_overlap=0)
docs = text_splitter.split_documents(documents)

# Create embeddings and store in TiDB
embeddings = OpenAIEmbeddings()
TABLE_NAME = "semantic_embeddings"
db = TiDBVectorStore.from_documents(
    documents=docs,
    embedding=embeddings,
    table_name=TABLE_NAME,
    connection_string=tidb_connection_string,
    distance_strategy="cosine",  # default, another option is "l2"
)

# Query the database
query = "個人情報の正確な定義について教えてください"
docs_with_score = db.similarity_search_with_score(query, k=3)

# Print the results
for doc, score in docs_with_score:
    print("-" * 80)
    print("Score: ", score)
    print(doc.page_content)
    print("-" * 80)

変更点はシンプルです。

import fitz  # PyMuPDF
<snip>
pdf_path = "path_to_your_pdf_file.pdf"
document = fitz.open(pdf_path)
pdf_text = ""
for page_num in range(document.page_count):
    page = document.load_page(page_num)
    pdf_text += page.get_text()

# Save the text to a temporary file for compatibility with TextLoader
with open("temp_pdf_text.txt", "w", encoding="utf-8") as f:
    f.write(pdf_text)

この例ではpath_to_your_pdf_file.pdfを読み取り同様にテキストファイルにダンプしています。
後は同じです。例えば以下なんかを読み込ませておくと個人情報の調べものに便利です。
https://www.ppc.go.jp/files/pdf/20220401_personal_basicpolicy.pdf

さっそくやってみる Word編

上二つと仕組みは同じです。python-docxを使います。
https://python-docx.readthedocs.io/en/latest/

pip install python-docx
import os
import getpass
from docx import Document
from langchain.text_splitter import CharacterTextSplitter
from langchain_community.document_loaders import TextLoader
from langchain_community.vectorstores import TiDBVectorStore
from langchain_openai import OpenAIEmbeddings

# Set OpenAI API key
os.environ["OPENAI_API_KEY"] = getpass.getpass("OpenAI API Key:")

# Set TiDB connection string
tidb_connection_string_template = "mysql+pymysql://<userid>:<PASSWORD>@<host>:4000/test?ssl_ca=/etc/ssl/cert.pem&ssl_verify_cert=true&ssl_verify_identity=true"
ssl_ca=/etc/ssl/cert.pem&ssl_verify_cert=true&ssl_verify_identity=true"
tidb_password = getpass.getpass("Input your TiDB password:")
tidb_connection_string = tidb_connection_string_template.replace(
    "<PASSWORD>", tidb_password
)

# Read the Word file and extract text
word_path = "path_to_your_word_file.docx"
doc = Document(word_path)
word_text = ""
for para in doc.paragraphs:
    word_text += para.text + "\n"

# Save the text to a temporary file for compatibility with TextLoader
with open("temp_word_text.txt", "w", encoding="utf-8") as f:
    f.write(word_text)

# Load and split the text
loader = TextLoader("temp_word_text.txt", encoding="utf-8")
documents = loader.load()
text_splitter = CharacterTextSplitter(chunk_size=1000, chunk_overlap=0)
docs = text_splitter.split_documents(documents)

# Create embeddings and store in TiDB
embeddings = OpenAIEmbeddings()
TABLE_NAME = "semantic_embeddings"
db = TiDBVectorStore.from_documents(
    documents=docs,
    embedding=embeddings,
    table_name=TABLE_NAME,
    connection_string=tidb_connection_string,
    distance_strategy="cosine",  # default, another option is "l2"
)

# Query the database
query = "アムロはどういう人ですか?"
docs_with_score = db.similarity_search_with_score(query, k=3)

# Print the results
for doc, score in docs_with_score:
    print("-" * 80)
    print("Score: ", score)
    print(doc.page_content)
    print("-" * 80)

変更点は以下です。

from docx import Document
<snip>
# Read the Word file and extract text
word_path = "path_to_your_word_file.docx"
doc = Document(word_path)
word_text = ""
for para in doc.paragraphs:
    word_text += para.text + "\n"

# Save the text to a temporary file for compatibility with TextLoader
with open("temp_word_text.txt", "w", encoding="utf-8") as f:
    f.write(word_text)

ローカルに存在しているpath_to_your_word_file.docxの中身をtxtファイルにダンプしてくれています。

Discussion