🤖

pgvector入門 ~アプリ編 (RAG)~

に公開

はじめに

こんにちは。ミスミグループ本社 Gateway推進本部の飯塚です。レコメンドや検索、DB構築などを幅広く担当しています。

本記事は【pgvector 入門】と題して、PostgreSQL の環境構築からベクトルデータの格納、アプリ実装までを三部構成で連載していきます。
最終回の今回は ~アプリ編~ になります。Amazon Bedrock と Streamlit を使って簡易 RAG アプリを実装していきます。

振り返り

第一回では Amazon Aurora PostgreSQL Compatible 環境を構築し、Amazon SageMaker Code Editor を使って PostgreSQL へアクセスしました。
第二回ではベクトル化した PDF ファイルを PostgreSQL に格納し、幾つかのケースを例にベクトル検索の実用方法を紹介しました。
今回はこれら環境をそのまま利用し、RAG アプリの実装を行っていきます。

前提知識

アプリ実装の前に幾つか整理しておきましょう。

RAG とは

Retrieval-Augmented Generation(検索拡張生成)の略で、大規模言語モデル(LLM)によるテキスト生成に外部情報の検索を組み合わせる技術のことです。メリットは幾つかありますが、主に以下のようなものがあります。

  • LLM 単体では困難だった最新情報の反映が可能
  • 特定ドメインに特化した専門知識や機密性の高い社内データなど、通常の LLM では扱っていない情報を元に LLM に回答させることが出来る
  • LLM が情報の出典元を提示できるようになり、ユーザーがソースドキュメントを自分で参照することが可能になる

これを実現するため RAG は以下のような仕組みになっています。

  1. ユーザーが RAG アプリケーションに質問をする
  2. アプリケーションが質問内容を外部データベースに問い合わせる
  3. データベースからの返却結果とユーザーの質問をプロンプトに加工して LLM に送信する
  4. LLM がプロンプトを元に回答を生成し、ユーザーに返却する

このように、LLM が外部から得た知識を元に回答を生成していることが分かります。
ここで対比的に覚えておきたいことがファインチューニングです。これも LLM に新しいデータやドメイン特化した情報を返却させるようにする技術ですが、大きな違いは LLM に直接データを学習させる点です。
上記の通り、RAG は LLM と外部データベースを繋ぎこむことで実現していますが、ファインチューニングは事前に LLM(汎用モデル) に追加データを覚え込ませ、特化モデルの LLM として単体で回答させる仕組みになります。
ケースにも寄りますが、一般的にコスト面や精度面どちらをとっても RAG の方が優れていることが多いようです。また実装難易度も RAG の方が圧倒的に低いため、今回は外部データベース(pgvector)と LLM を繋げて RAG アプリケーションを作っていきます。

Amazon Bedrock とは

基盤モデルを使用して生成 AI アプリケーションを構築およびスケーリングする最も簡単な方法。
出典: https://aws.amazon.com/jp/bedrock/

Amazon Bedrock とは、AWS が提供する生成 AI の基盤サービスで、Anthropic、Cohere、Luma、Meta、Amazon など様々な企業の基盤モデルを取り扱っています。
このサービスはサーバレスで提供されているため、自身で大規模言語モデル (LLM) をダウンロード/デプロイする必要が無く、すぐに API 経由で結果を取得することが出来ます。
今回は Amazon Bedrock で取り扱っているモデルを使って RAG アプリを作っていきましょう。

Streamlit とは

A faster way to build and share data apps.
Turn your data scripts into shareable web apps in minutes. All in pure Python. No front‑end experience required.
出典: https://streamlit.io/

Streamlit とは、たった数行のコードだけで簡易Webアプリケーションが作れるフレームワークです。
フロントエンドの知識はいらず、Python さえ書ければ簡単に綺麗な UI を作成できるため、データサイエンティストやエンジニアが分析結果等を素早く公開するために使われることが多いです。
Playground のページでは、よく使われるケースに合わせてサンプルコードが公開されています。それらコードを Playground 上で編集することも可能なので、動作確認に利用してみてください。

本題

では、RAG アプリを実装していきましょう。
大きく以下の流れで進めていきます。

  1. 環境準備
  2. Streamlit の動作確認
  3. Amazon Bedrock の準備
  4. RAG アプリ実装

環境準備

今まで使って来た環境は AWS の VPC 上に構成されています。つまりこのままでは Web アプリケーションを起動してもブラウザから直接接続することが出来ないので少し設定をいじります。
設定内容は本記事の趣旨とずれるため割愛しますが、以下記事がとても参考になりました。記事に沿ってカスタムイメージの作成と Amazon SageMaker Code Editor の起動まで行ってください。

https://qiita.com/moritalous/items/859c9977dd6b923472f1

Streamlit の動作確認

Code Editor の起動が完了したら Streamlit の動作確認をしていきます。

ターミナル
pip install streamlit

Streamlit のチュートリアルの Web アプリを起動します。

ターミナル
streamlit hello --server.port 8080 --server.baseUrlPath /codeeditor/default/absolute/8080

ブラウザで以下の URL にアクセスし Streamlit が起動していることを確認します。
xxxxxx.studio.ap-northeast-1.sagemaker.aws/codeeditor/default/absolute/8080
(xxxxxx の部分は現在立ち上げている Code Editor の URL と同じ値です)


ブラウザでこのように画面が表示されたら Streamlit の初期設定完了です。
今後も同じポートを使ってWebアプリを立ち上げるので、Ctrl + c でチュートリアルアプリは落としておいて下さい。

では続いて空の Web アプリを作っていきます。

streamlit.py
import streamlit as st

st.title("pgvector入門 ~アプリ編~")
input_text = st.text_input("pgvector に格納されたデータを検索し LLM が回答します")
send_button = st.button("送信")

Web アプリを立ち上げます。

ターミナル
streamlit run streamlit.py --server.port 8080 --server.baseUrlPath /codeeditor/default/absolute/8080

以下のように表示されたら完成です。

コードの解説が要らないくらい簡単ですね。
今はボタン押下時のアクションを定義していないので何も動きません。後ほど検索窓に入力された文字列をベクトルDB に問い合わせし、LLM によって回答するように修正します。

Amazon Bedrock の準備

空アプリが出来たら LLM の準備に入ります。
Amazon Bedrock の画面に遷移して使いたいモデルを選択します。今回は Claude 3.5 Sonnet を使ってみましょう。

以下スクショのように「Open in playground」がクリック出来ない場合は、「リクエスト」を送る必要があります。

「モデルアクセス」画面に遷移し利用したいモデルのリクエストを送信します。アクセス権は数分で付与されます。

アクセス権が付与されたら先ほどの画面はこのような表示に変わっています。

では Playground で少し触ってみましょう。
「Open in playground」ボタンを押したらチャット画面が出てくるので LLM に質問しましょう。

まさに本連載のメリットとデメリットを挙げてくれています。
なおモデルの使用にはコストが掛かります。使用するモデルや使用方法によって料金体系は異なりますので、詳しくは Amazon Bedrock の料金ページを参照してください。

最後に Web アプリからこの LLM を呼び出す際に必要な Model ID を控えます。

ここまででようやく準備完了です。次項で pgvector、LLM、Web アプリを繋げていきます。

RAG アプリ実装

まずは Code Editor インスタンス上で必要なライブラリをインストールします。

ターミナル
sudo apt update
sudo apt install postgresql postgresql-contrib
pip install boto3
pip install pgvector
pip install psycopg2-binary
pip install langchain
pip install langchain_huggingface
pip install langchain_postgres

次に空アプリで作成したファイルを以下のように修正します。

streamlit.py
import boto3
from langchain_postgres.vectorstores import PGVector
from langchain_aws.chat_models import ChatBedrock
from langchain.chains import RetrievalQA
from langchain_core.prompts import PromptTemplate
from langchain_huggingface.embeddings import HuggingFaceEmbeddings
import streamlit as st

# 接続文字列の定義
_host = "<データベースエンドポイント>"  # TODO: 実行環境に合わせて修正
_user = "postgres"
_pass = "<設定したパスワード>"  # TODO: 実行環境に合わせて修正
_port = "5432"
CONNECTION_STRING = f"postgresql+psycopg://{_user}:{_pass}@{_host}:{_port}"

# boto3の定義
bedrock_runtime = boto3.client(
    service_name="bedrock-runtime",
    region_name="ap-northeast-1",
)

# Embeddingsの定義
model_name = "sentence-transformers/all-mpnet-base-v2"
model_kwargs = {"device": "cpu"}
encode_kwargs = {"normalize_embeddings": False}
embeddings = HuggingFaceEmbeddings(
    model_name=model_name, model_kwargs=model_kwargs, encode_kwargs=encode_kwargs
)

# PostgreSQLの定義
db = PGVector.from_existing_index(connection=CONNECTION_STRING, embedding=embeddings)

# LLMの定義
llm = ChatBedrock(
    client=bedrock_runtime,
    model_id="anthropic.claude-3-5-sonnet-20240620-v1:0",  # 前項で控えた Model ID
    model_kwargs={"max_tokens": 8000},
)

# promptの定義
prompt_template = """
  以下の参考文書を元に、質問に対して詳しく説明してください。
  もし質問の内容が参考文書に無かった場合は「文書にありません」と答えてください。
  
  参考文書:
  {context}
  
  質問:
  {question}
  
  回答(日本語):"""
prompt = PromptTemplate(
    template=prompt_template, input_variables=["context", "question"]
)
chain_type_kwargs = {"prompt": prompt}

# Chainの定義
qa = RetrievalQA.from_chain_type(
    llm,
    chain_type="stuff",
    retriever=db.as_retriever(search_kwargs={"k": 15}),  # 検索結果の上位15件を使用
    chain_type_kwargs=chain_type_kwargs,
)

# streamlitの設定
st.title("pgvector入門 ~アプリ編~")
input_text = st.text_input("pgvector に格納されたデータを検索し LLM が回答します")
send_button = st.button("送信")

if send_button:
    st.write(qa.invoke(input_text))

今までに実装したコードでほとんど構成されているためコードの説明は割愛しますが、プロンプト部分は工夫の余地ありです。所謂プロンプトエンジニアリングの要領で、こちらの求める回答を LLM に指示することが出来ます。

では早速 Web アプリを立ち上げましょう。

ターミナル
streamlit run streamlit.py --server.port 8080 --server.baseUrlPath /codeeditor/default/absolute/8080

立ち上げに成功すると空アプリと同じ画面が表示されます。

では試しに Amazon Sagemaker について聞いてみましょう。

LLM によって検索結果が文章としてまとめられていることが分かりますね。
おめでとうございます!これで RAG アプリの完成です。あとはデータやモデル、プロンプトなどを変えて自分の独自 RAG アプリを作ってみてください。

補足

LLM に引き渡す前の pgvector の結果は、ベクトル検索と LLM の invoke を2つに分けることで確認できます。LLM が回答のベースにしているドキュメントの出典を提示する際などに使用してみてください。

# ベクトルDB検索
retrieved_docs = db.as_retriever(search_kwargs={"k": 15}).invoke(input_text)

st.write("検索結果:")
context = "\n".join([doc.page_content for doc in retrieved_docs])
st.write(context)

# 検索結果をプロンプトに詰め込みLLMに送信
response = qa.invoke({"context": context, "query": input_text})
st.write("LLM回答:\n")
st.write(response)

後片付け

意図していない課金を防ぐため、作成したリソースは停止または削除しておきましょう。
特に Aurora は、停止しても一週間後に自動的に起動されてしまうので削除をお薦めします。

  • Amazon Aurora Serverless v2 (PostgreSQL Compatible)
  • Amazon SageMaker Code Editor
  • Amazon Bedrock

おわりに

pgvector 入門いかがでしたでしょうか?
第一回で AWS 環境上にベクトルDBを構築し、第二回でベクトル化した PDF ドキュメントを DB に格納しました。そして最終回の今回では LLM とベクトルDBを繋ぎ合わせて Web アプリの構築まで行いました。
最近ではデータを格納するだけで RAG を利用できるサービスも出てきています。(今回使った Amazon Bedrock にも有ります)ただし一つ一つのサービスを理解し自分で実装した経験は、RAG アプリの精度改善や別のアプリケーション構築時に必ず活きてきます。AI 関連のサービスは今後も爆発的にリリースされていきます。その時に "まず自分で作ってみる" 精神で挑戦してみてください。

ミスミ DataTech ブログ

Discussion