Vector Storesを使ってLLMに独自コンテンツを参照して回答させる方法

Vector Stores GPT
Vector Stores

LLMを使ったシステムを開発していると「自社の資料を参照して回答させたい」とか「ブログ記事を参照して回答させたい」のような要望が出てくることがありますよね?

そんなときに使えるのがLangChainのVector Storesです。

Page Not Found | 🦜️🔗 Langchain

この記事では、LangChainのVector Storesを使ってLLMに独自コンテンツを参照して回答させる方法を紹介します。

Vector Storesとは?

Vector Storesはテキストデータをベクトル化して保存・検索するための仕組みです。

事前に独自コンテンツのテキストをベクトル化して保存しておきます。

検索時には検索キーワードをベクトル化して、類似するテキストを抽出します。

これを実現するためには、ベクトル化したデータを保存するデータベースが必要になりますが、LangChainのVector Storesは、さまざまなベクトルデータベースに対応しています。

この記事では、LangChainの公式ドキュメントで紹介されているChromaを使います。

基本的な使い方

はじめにVector Storesの基本的な使い方を説明しましょう。

サンプルテキストとして夏目漱石の『坊っちゃん』の文を使います。

親譲りの無鉄砲で小供の時から損ばかりしている。
小学校に居る時分学校の二階から飛び降りて一週間ほど腰を抜かした事がある。
なぜそんな無闇をしたと聞く人があるかも知れぬ。
別段深い理由でもない。
新築の二階から首を出していたら、同級生の一人が冗談に、いくら威張っても、そこから飛び降りる事は出来まい。
弱虫やーい。
と囃したからである。
小使に負ぶさって帰って来た時、おやじが大きな眼をして二階ぐらいから飛び降りて腰を抜かす奴があるかと云ったから、
この次は抜かさずに飛んで見せますと答えた。

このテキストをベクトル化してChromaに保存しますが、事前にLangChainのTextSplitterを使ってチャンクに分割します。

それから、Chromaにベクトル化したデータを保存します。

最後に、キーワード「おやじ」を含むテキストを検索して抽出します。

import os
from langchain.document_loaders import TextLoader
from langchain.embeddings.openai import OpenAIEmbeddings
from langchain.text_splitter import CharacterTextSplitter
from langchain.vectorstores import Chroma

os.environ['OPENAI_API_KEY'] = OPENAI_API_KEY

# ドキュメントを読み込み
raw_documents = TextLoader('./サンプル.txt').load()

# 改行でチャンクに分割
text_splitter = CharacterTextSplitter(
    chunk_size=100,
    chunk_overlap=0,
    separator = "\\n",
)
documents = text_splitter.split_documents(raw_documents)

# 分割したテキストをベクターストア (Chroma)に保存する
db = Chroma.from_documents(documents, OpenAIEmbeddings())

# キーワードで類似検索する
query = "おやじ"
docs = db.similarity_search(query)
print(docs[0].page_content)

とてもシンプルでわかりやすい仕組みです。

これがVector Storesを使ってデータをベクトル化・検索する基本的な流れになります。

後述しますが、これを応用することによって、LLMに独自コンテンツを参照させた回答をさせられるようになります。

もっと大きなデータを検索する

次に、WEBサイトなどの大きなデータの検索を試してみましょう。

以下はChatGPTに生成させた3ページ分のHTMLです。

それぞれのページのテキストをベクトル化してChromaに保存し、ユーザーがキーワードを入力すると関連するテキストを抽出できるようにします。

<!DOCTYPE html>
<html>
<head>
    <title>人工知能について</title>
</head>
<body>
    <header>
        <h1>人工知能についての概要</h1>
    </header>
    <nav>
        <ul>
            <li><a href="#what-is-ai">AIとは</a></li>
            <li><a href="#history">歴史</a></li>
            <li><a href="#applications">応用分野</a></li>
        </ul>
    </nav>
    <section id="what-is-ai">
        <h2>AIとは</h2>
        <p>人工知能(Artificial Intelligence、AI)は、コンピュータシステムが人間の知的なタスクを模倣し、学習や問題解決ができる能力を指します。</p>
    </section>
    <section id="history">
        <h2>歴史</h2>
        <p>AIの研究は1950年代から始まりました。初期の研究はシンボリックAIに焦点を当てていましたが、後に機械学習や深層学習などの新たなアプローチが登場しました。</p>
    </section>
    <section id="applications">
        <h2>応用分野</h2>
        <p>AIは医療診断、自動運転、自然言語処理、ゲームプレイ、金融分析など、さまざまな分野で活用されています。</p>
    </section>
    <footer>
        <p>© 2023 AI情報サイト</p>
    </footer>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
    <title>機械学習アルゴリズム</title>
</head>
<body>
    <header>
        <h1>機械学習アルゴリズムの紹介</h1>
    </header>
    <nav>
        <ul>
            <li><a href="#intro">概要</a></li>
            <li><a href="#types">アルゴリズムの種類</a></li>
            <li><a href="#examples">具体例</a></li>
        </ul>
    </nav>
    <section id="intro">
        <h2>概要</h2>
        <p>機械学習アルゴリズムは、データからパターンや関係性を学習し、予測や分類を行うための手法です。教師あり学習、教師なし学習、強化学習などのカテゴリがあります。</p>
    </section>
    <section id="types">
        <h2>アルゴリズムの種類</h2>
        <p>代表的な機械学習アルゴリズムには、線形回帰、決定木、サポートベクターマシン、ニューラルネットワークなどがあります。</p>
    </section>
    <section id="examples">
        <h2>具体例</h2>
        <p>例えば、決定木アルゴリズムは分類問題に適しており、顧客の購買履歴から購買行動を予測する際に活用されます。</p>
    </section>
    <footer>
        <p>© 2023 機械学習アルゴリズム情報サイト</p>
    </footer>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
    <title>AI倫理と社会への影響</title>
</head>
<body>
    <header>
        <h1>AI倫理と社会への影響</h1>
    </header>
    <nav>
        <ul>
            <li><a href="#ethics">AI倫理</a></li>
            <li><a href="#impact">社会への影響</a></li>
            <li><a href="#regulation">規制とガバナンス</a></li>
        </ul>
    </nav>
    <section id="ethics">
        <h2>AI倫理</h2>
        <p>AIの進化に伴い、倫理的な問題も浮上してきました。プライバシー、バイアス、自律的な意思決定などが議論の的となっています。</p>
    </section>
    <section id="impact">
        <h2>社会への影響</h2>
        <p>AIの普及は効率化や便益をもたらす一方、雇用の変化、偏った意思決定、個人情報の流出などの懸念も指摘されています。</p>
    </section>
    <section id="regulation">
        <h2>規制とガバナンス</h2>
        <p>AIの適切な使用を保障するため、政府や国際機関による規制やガバナンスの必要性が議論されています。</p>
    </section>
    <footer>
        <p>© 2023 AI倫理と社会影響情報サイト</p>
    </footer>
</body>
</html>

前処理としてBeautifulSoupを使ってHTMLを解析し、テキストを抽出します。

抽出したテキストは1つの文字列にまとめますが、あとで分割しやすいように、それぞれのページの間に<page>というセパレータを入れています。

なお、ここではローカルにHTMLファイルがある前提でコードを記載しますが、実際には任意のサイトにリクエストを投げてHTMLを取得する必要があるかもしれません。

抽出したテキストは「contents.txt」として保存します。

from bs4 import BeautifulSoup

page_count = 3
file_names = ["1.html", "2.html", "3.html"]
page_content = ""

for i in range(page_count):

    # ローカルのhtmlファイルを開いて読み込む
    with open(file_names[i], "r", encoding="utf-8") as html_file:
        html_content = html_file.read()
    
    # BeautifulSoupで解析
    soup = BeautifulSoup(html_content, "html.parser")

    # テキストのみを取り出す
    page_text = soup.get_text()

    page_content += page_text

    # ページごとの区切り文字を追加
    page_content += "<page>"

print(page_content)

# txtファイルとして保存
with open('./contents.txt', 'w') as f:
    f.write(page_content)

次に、抽出したテキストをChromaに保存して検索します。

ここでは「アルゴリズム」というキーワードを検索して「2.html」のテキストを取得できるが検証します。

Chromaへの保存や類似検索のやり方は先ほどとほぼ同じです。

ただし、データの大きさに合わせて「CharacterTextSplitter」の「chunk_size」を変更しています。

また、「CharacterTextSplitter」のセパレータとして先ほど定義した<page>タグを指定しています。

import os
os.environ['OPENAI_API_KEY'] = OPENAI_API_KEY

from langchain.document_loaders import TextLoader
from langchain.embeddings.openai import OpenAIEmbeddings
from langchain.text_splitter import CharacterTextSplitter
from langchain.vectorstores import Chroma

# 保存したテキストデータを取得
documents = TextLoader('./contents.txt').load()

# <page>タグでチャンクに分割
text_splitter = CharacterTextSplitter(
    chunk_size=500,
    chunk_overlap=0,
    separator = "<page>",
)

# チャンクごとにテキストを読み込んでベクターストア (Chroma)にロードする
documents = text_splitter.split_documents(documents)
db = Chroma.from_documents(documents, OpenAIEmbeddings())

# クエリテキストで類似検索する
query = "アルゴリズム"
docs = db.similarity_search(query)
print(docs[0])

これを実行すると以下のように「アルゴリズム」に関するテキストを含む「2.html」のテキストを抽出できます。

機械学習アルゴリズム



機械学習アルゴリズムの紹介



概要
アルゴリズムの種類
具体例



概要
機械学習アルゴリズムは、データからパターンや関係性を学習し、予測や分類を行うための手法です。教師あり学習、教師なし学習、強化学習などのカテゴリがあります。


アルゴリズムの種類
代表的な機械学習アルゴリズムには、線形回帰、決定木、サポートベクターマシン、ニューラルネットワークなどがあります。


具体例
例えば、決定木アルゴリズムは分類問題に適しており、顧客の購買履歴から購買行動を予測する際に活用されます。


© 2023 機械学習アルゴリズム情報サイト

このようにVector Storesを使ってHTMLに含まれるテキストデータを検索し、関連するテキストを抽出できます。

LLMに独自コンテンツを参照して回答させるには?

それでは、いよいよ本題の「LLMに独自コンテンツを参照して回答させる方法」を説明します。

全体の流れは、

  1. 事前に用意したコンテンツからテキストを抽出してChromaに保存する
  2. ユーザーの質問からキーワードを抽出してChromaを検索し関連コンテンツのテキストを得る
  3. 検索結果とユーザーの質問を合体してLLMに渡す
  4. LLMの回答をユーザーに返す

のようになります。

①Chromaを検索して得られたテキスト
②「このテキストを参照して以下の質問に回答してください」というプロンプト
③ユーザーの質問

の3つの文字列を結合してLLMに渡します。

こうすることによって、LLMが独自コンテンツを参照して回答できるようになります。

さっそくやってみましょう。

例として、以下の架空の就業規則のテキストを「labor_regulation.txt」として用意しました。

これはChatGPTを使って生成した架空の就業規則です。

**就業規則**

**第1条 目的**
この就業規則は、〇〇株式会社(以下「会社」という)の従業員の行動規範および労働条件を定めるものであり、円滑な業務遂行と職場の健全な環境を確保することを目的とする。

**第2条 労働契約**
1. 従業員は、入社時に労働契約を締結する。契約内容は、労働条件、給与、勤務地などを含む。契約に基づいて労働を行うものとし、契約違反には適切な措置を講じる。

**第3条 勤務時間**
1. 基本労働時間は1日8時間、週40時間とするが、業務の特性に応じて柔軟な勤務時間を採用することができる。
2. 労働時間外の勤務には、法定の時間外手当を支給する。

**第4条 休暇**
1. 年次有給休暇は、勤務開始から6ヶ月後に付与される。付与率は労働時間に応じて決定する。
2. 特別休暇や育児休暇など、必要に応じてさまざまな休暇を取得できる。ただし、事前に申請が必要である。

**第5条 給与**
1. 給与は、職務内容や経験、業績などに応じて決定する。給与改定は年次に一度行われる。
2. 給与支払いは月末に行われるが、銀行振込や電子マネーによる支払いも選択できる。

**第6条 行動規範**
1. 従業員は、職場でのマナーや他の従業員への尊重を遵守する。ハラスメントや差別は厳禁とする。
2. 業務中および業務外での会社の機密情報の漏洩や不正利用は禁止する。

**第7条 福利厚生**
1. 従業員には、健康保険・厚生年金・雇用保険などの社会保険が適用される。さらに、福利厚生プログラムも提供する。

**第8条 制裁措置**
1. 就業規則に違反した場合、適切な処分を行う。軽微な場合は注意喚起から始まり、重大な場合は懲戒処分を含む措置を取ることがある。

**第9条 規則の変更**
1. 就業規則の変更は、従業員に適切な告知と説明を行った上で行われる。変更に対する意見や提案を受け付ける。

**第10条 最終規定**
1. 本規則に定められていない事項や細則は、会社の指針に従うものとする。

この就業規則は、〇〇株式会社の従業員としての義務と権利を明確にし、職場での健全な環境を維持するための基本的な枠組みとなるものとする。

これをChromaに保存します。

import os
os.environ['OPENAI_API_KEY'] = OPENAI_API_KEY

from langchain.document_loaders import TextLoader
from langchain.embeddings.openai import OpenAIEmbeddings
from langchain.text_splitter import CharacterTextSplitter
from langchain.vectorstores import Chroma

documents = TextLoader('./labor_regulation.txt').load()

text_splitter = CharacterTextSplitter(
    chunk_size=100,
    chunk_overlap=0
)

# チャンクごとにテキストを読み込んでベクターストア (Chroma)にロードする
documents = text_splitter.split_documents(documents)
db = Chroma.from_documents(documents, OpenAIEmbeddings())

次に、ユーザーの質問からキーワードを抽出し、Chromaを検索します。

ここではユーザーの質問は「給与の支払いはいつですか?」であり、キーワードとして「給与」が抽出できたものとします。

キーワード「給与」を使ってChromaを検索します。

# キーワードでChromaを検索する
question = "給与の支払いはいつですか?"
keyword = "給与" # 実際には形態素解析などによりキーワードを抽出する
docs = db.similarity_search(keyword)

docs = db.similarity_search(keyword)

# 関連するコンテンツを取得
related_content = docs[0].page_content


print(related_content)

# **第5条 給与**
# 1. 給与は、職務内容や経験、業績などに応じて決定する。給与改定は年次に一度行われる。
# 2. 給与支払いは月末に行われるが、銀行振込や電子マネーによる支払いも選択できる。

「給与」に関連するテキストを取得できています。

ここで取得したテキストとユーザーの質問をひとつのプロンプトにまとめてLLMに送信します。

ここではモデルにOpenAIの「gpt-3.5-turbo」を使用します。

import openai

# gpt-3.5-turboに回答させる
response = openai.ChatCompletion.create(
    model="gpt-3.5-turbo",
    messages=[
        {"role": "system", "content": f"あなたは就業規則に詳しいアシスタントです"},
        {"role": "system", "content": f"以下のテキストをもとにユーザーの質問に回答してください: {related_content}"},
        {"role": "user", "content": question},
    ],
) 

# GPTの回答
print(response['choices'][0]['message']['content'])

# 給与支払いは、第5条の規定に基づき、月末に行われます。
# ただし、銀行振込や電子マネーによる支払いも選択できる場合がありますので、
# それによって支払い日が異なることもあります。具体的な支払い日については、
# 会社の方針や就業規則に記載されている内容をご確認ください。

就業規則の該当部分のテキストを参照して回答できています。

「それによって支払い日が異なる」という部分はもとのテキストには含まれておらず余分なので、厳密さを求めるならもう少し調整が必要かもしれません。

ただし、参照元のテキストはわかっているので、それをユーザーにも返してもよさそうです。

このように、Vector Storesを使うことで、LLMに独自コンテンツを参照して回答させることができます。

完成版のPythonコードは以下です。

import os
os.environ['OPENAI_API_KEY'] = OPENAI_API_KEY

from langchain.document_loaders import TextLoader
from langchain.embeddings.openai import OpenAIEmbeddings
from langchain.text_splitter import CharacterTextSplitter
from langchain.vectorstores import Chroma
import openai

documents = TextLoader('./labor_regulation.txt').load()

text_splitter = CharacterTextSplitter(
    chunk_size=100,
    chunk_overlap=0
)

# チャンクごとにテキストを読み込んでベクターストア (Chroma)にロードする
documents = text_splitter.split_documents(documents)
db = Chroma.from_documents(documents, OpenAIEmbeddings())

# キーワードでChromaを検索する
question = "給与の支払いはいつですか?"

keyword = "給与" # 実際には形態素解析などによりキーワードを抽出する
docs = db.similarity_search(keyword)

# 関連するコンテンツを取得
related_content = docs[0].page_content

# gpt-3.5-turboに回答させる
response = openai.ChatCompletion.create(
    model="gpt-3.5-turbo",
    messages=[
        {"role": "system", "content": f"あなたは就業規則に詳しいアシスタントです"},
        {"role": "system", "content": f"以下のテキストをもとにユーザーの質問に回答してください: {related_content}"},
        {"role": "user", "content": question},
    ],
) 

# GPTの回答
print(response['choices'][0]['message']['content'])

まとめ

この記事では、LangChainのVector StoresとChromaを使って独自コンテンツのテキストをもとにLLMに回答させる方法を紹介しました。

TextSplitterのchunk_sizeの設定や独自コンテンツの前処理部分がやや面倒そうですが、社内特有のコンテンツをベースに回答するQ&Aシステムなどではかなり使えそうです。

ぜひ使ってみてください。

記事の内容にわからないことがある方は以下のアカウントに気軽にDMしてください!

@sti320a

また、最新の技術の活用方法など役に立つツイートを心がけているので、ぜひフォローしてもらえると嬉しいです!

タイトルとURLをコピーしました