Pineconeを使いVectorStoreを構成してみた

背景や目的


1.RAGで使うVectorStoreとしてちらほらと紹介されるPineconeを試しました。

2.せっかくなので、実際の開発に役立つように、実データに近いDBを使用してUpsert処理をしてみました。

3.同じ理由で、検索処理の書き方も詳しく確認します。  

利用したデータ


1.本番システムで利用中のデータの一部を利用します。

2.具体的には、客先提供中システムの保守対応記録のデータです。データには、問合せ内容が含まれています。

3.実際のデータ

 ◆保持形式: RDB(PostgreSQL)

 ◆必要データの抽出方法:対象システムの機能でCSV出力

 ◆項目:問合せID,日付,時間(分),問合せ内容,対応,等118列あります

 ◆件数:661件

 ◆データイメージ:

システム構成


上記のような全体構成を考慮し、この記事では右上の青色囲み部を紹介します。

元データの前処理


元データに前処理を施します。

元データの前処理:不要情報のカット


最終的に費用や性能に関わるので、コンテクストにすべき情報のみ残します。

・ 元ファイルは、 qa20240220.csv のように出力日毎のcsvとして用意します。

・ そのファイルをエクセルで開き、不要な列を削除します。

・ 残したのは“問合せID”,“問合せ種類システム”、“問合せ種類”、“問合せ内容”、  “対応”、“メモ”の6列。

元データの前処理:個人情報のマスキング


個人情報に配慮した措置です。

・エクセル上で目視確認し、以下のように匿名化しました。

   - 電話番号: 03-1234-5678 -> 03-***-***

   - メアド:aeiou@abcd.co.jp -> ***@***

   - 社員、顧客個人:***さん、 ***様、に置換

   - 顧客会社、取引会社、部門名: ***社に置換

元データの前処理:検索時優先ワード定義ファイルの準備


検索後のコンテクストに、前提的な情報が欠落しないようにする意図です。具体的には、対象システム名を定義しました。

・元ファイルの列“問合せ種類システム”に含まれる名称を重複排除し抽出します。

・それを、ファイル名priority_word.csvに収め、保存します。

元データの前処理:元データをS3へアップ


元データと優先ワード定義ファイルは、後で参照できるようS3にアップしておきます。

・AWSコンソールにログイン後、検索窓に”S3″を入力し「S3」を押下します。

・Amazon S3の画面に遷移し、メニュー「バケット」で「バケットを作成」を押します。

・画面「バケットを作成」では以下を登録する。

設定値
バケット名proto2-data
オブジェクト所有者ACL 無効 (推奨)
ブロックパブリックアクセス設定パブリックアクセスをすべて ブロック
バケットのバージョニング無効
暗号化タイプAmazon S3マネージドキーを・・・暗号化 (SSE-S3)
バケットキー有効
アップロードするファイル1.qa20240220.csv
2.qa20240330.csv(※2回目出力ファイルを別途作成)
3.priority_word.csv

VectorStoreの作成


ベクター検索用のデータを作成し、それを検索処理で利用できるようにします。今回はPineconeを使いました。

VectorStoreの作成処理環境を用意


投入処理は、AWS Lambdaで実行させます。まず関数を作成します。

・AWSコンソールでLambdaメニューに遷移し、Lambdaトップ画面で「関数の作成」を押下します。

・画面「関数の作成」では、以下の設定値で、作成します。(「VPCを有効化」~「セキュリティグループ」の値は、お使いの環境に合わせて設定して下さい。)

設定値
関数名proto2_data_sync
ランタイムpython3.9
アーキテクチャx86_64(デフォルト)
実行ロールatd_role_for_lambda ※お手元の環境で任意に設定
関数 URL を有効化チェック
認証タイプAWS_IAM
VPCを有効化チェック
VPCvpc-*************(172.31.0.0/16)
サブネットprivate-lambda(172.31.64.0/20)
セキュリティグループsg-06*************** (default)

・OpenAIとPineconeを使うために、レイヤーを作成します。

 - まず、CloudShellでpinecone.zipを作成。

AWS Cloudshell
[cloudshell-user@ip-*** ~]$ pip install -t ./python/pinecone-client
[cloudshell-user@ip-*** ~]$ zip -r pinecone.zip ./python
[cloudshell-user@ip-*** ~]$ rm -r ./python/

 - 同様に、 openai.zipも作成。

 - Lambda画面の「レイヤー」メニューを開き、「レイヤーの作成」を押す。

 - その画面内で以下を入力し、作成を完了させる。

設定値
名前pinecone
.zip ファイルをアップロードチェック
アップロードpinecone.zipを指定
互換性のあるアーキテクチャx86_64
互換性のあるランタイムPython 3.9

 - 同様に、 OpenAIのレイヤーも作成。

設定値
名前openai
.zip ファイルをアップロードチェック
アップロードopenai.zipを指定
互換性のあるアーキテクチャx86_64
互換性のあるランタイムPython 3.9

・ Lambda関数に作成したレイヤーを適用します。

 - 関数proto2_data_syncの画面内下部「レイヤーの追加」を押す。

 - 画面では以下を指定し、追加を完了させます。

設定値1(Pinecone)
レイヤーソースカスタムレイヤー
カスタムレイヤーpinecone
設定値2(OpenAI)
レイヤーソースカスタムレイヤー
カスタムレイヤーopenai

・ 3秒では処理が終了しないので、タイムアウト値を3分にします。

 - 関数proto2_data_syncの「設定」、「一般設定」、「編集」を押す。

 - その画面のタイムアウト欄を3分に変更し、保存を押します。

・ Lambda関数がS3を参照出来るようにIAMポリシーを追加します。

 - AWSコンソールでIAM画面に遷移し、メニュー「ロール」を開きます。

 - その画面で、Lambda関数に割り当てたロール名を押し、 「許可を追加」を押し、以下ポリシーをアタッチします。

設定値
許可ポリシー名AmazonS3FullAccess

・ 差分発見用にpandasを使うので、そのレイヤーも作成しておきます。

 - 関数proto2_data_syncの画面内下部の「レイヤーの追加」を押す。

 - その画面で以下を入力し、追加を完了させます。

設定値
レイヤーソースAWSレイヤー
AWS レイヤーAWSSDKPandas-Python39
バージョン16

VectorStore作成:処理1)処理対象ファイルの決定


投入処理が終わっていない今回処理するファイルを見つけます。以下、処理概要。

・ S3の処理結果ファイル(result.txt)を参照し、前回処理済ファイルがどれかを知る。

・ S3内ファイル群の最終更新日を見て、前回処理済の次のファイルを得る(=今回対象)。

・ 後続処理用に、前回と今回のファイルを、pandas形式に変換しておく。

・ 以下、対象のコード。

Lambda_Handler
def lambda_handler(event, context):
    #処理情報のチェック
    executed_dt_str = check_exec_info('result.txt','utf-8') #前回実行ファイルのLastModifiedを得る
    #前回実行ファイルの情報から今回実行すべきタスクを把握。
    last_time_set,this_time_set,this_time_set_jst =  get_todo(executed_dt_str)
    for_set_data = []
    if last_time_set != '' and this_time_set != '' : #前回ファイル有り、今回ファイル有りの時
        df_this_time,df_last_time = get_2files_data(last_time_set,this_time_set) #2ファイルのpandasDFを作成
    elif last_time_set == '' and this_time_set != '' : #前回ファイル無し、今回ファイル有りの時
        df_this_time,df_last_time = get_2files_data(None,this_time_set)
    if this_time_set == '' :
        exec_num,ret = 0,''
    else:
        exec_num = set_vector_store(df_this_time,df_last_time) #vectorstoreへ登録
    if exec_num > 0 :
        ret = rec_exec_info('result.txt',this_time_set_jst) #今回処理したファイル(最終更新日)を記録しておく。    

    body = {'last_time':executed_dt_str,'this_time':ret,'exec_num':exec_num }
    return json.dumps(body)
Lambda3
def check_exec_info(object_key,file_code): #処理情報チェック関数 object_key:S3のkey_name,file_code:文字コード
    try:
        response = s3_client.get_object(Bucket=BUCKET_NAME, Key=object_key)
        body = response["Body"].read()
        return body.decode(file_code)
    except s3_client.exceptions.ClientError as ex:
        #無い場合は、初期ファイルを作成
        if ex.response['Error']['Code'] == 'NoSuchKey': # File not found
            set_body = '2024/01/01 11:11:11'
            response = s3_client.put_object(Body=set_body,Bucket=BUCKET_NAME,Key=object_key)
            return set_body
        else:
            return '2012/12/12 12:12:12'
Lambda4
def get_todo(executed_dt_str): #実行すべきタスクの情報を得る関数 executed_dt_str:前回実行日付
    last_time_set,this_time_set,this_time_set_jst = '','',''
    executed_dt_str+='+09:00' #Timezone=JSTの情報を付加
    executed_dt = datetime.strptime(executed_dt_str,'%Y/%m/%d %H:%M:%S%z') #Timezone=JSTを付加しdatetome型に変換
    obj,obj_sorted = [],[]
    objs = s3_client.list_objects_v2(Bucket=BUCKET_NAME)
    obj.extend(objs["Contents"])
    obj_sorted = sorted(obj, key=lambda x: x['LastModified']) #最終更新日昇順でソート
    files = []
    for files in obj_sorted:
        if files['Key'][0:2] == 'qa':
            jst = timezone(timedelta(hours=9), 'JST') #日本時間に直す(timezone定義)
            check_time_jst = files['LastModified'].astimezone(jst) #日本時間に直す(timezone=日本を適用)
            if check_time_jst < executed_dt : #前回処理済ファイルより前のファイルだ
                continue #何もしない
            if check_time_jst == executed_dt : #前回処理済ファイルだ
                last_time_set = files['Key']
                continue
            if check_time_jst > executed_dt : #今回新登場のファイルの1個目だ
                this_time_set = files['Key']
                this_time_set_jst = check_time_jst
                break #新登場2個目以降は本システムでは考慮しない。定期処理を再実行すれば処理される前提。
    return last_time_set,this_time_set,this_time_set_jst
Lambda5
def get_2files_data(last_time_set=None,this_time_set=None): #対象2ファイルの情報をpandasDFで返す関数 last_time_set:前回処理済ファイルのKey,this_time_set:今回処理ファイルのKey
    df_this_time,df_last_time = None,None
    if this_time_set is not None: #今回ファイルがあるならそれをopen
        try:
            response = s3_client.get_object(Bucket=BUCKET_NAME, Key=this_time_set)
            body = response["Body"].read().decode('utf-8')
            df_this_time = pd.read_csv(StringIO(body), index_col=0) # pandas dataflame として出力。問合せIDをindexに。
        except:
            return df_this_time,df_last_time
    if last_time_set is not None: #前回ファイルもあるならopen
        try:
            response = s3_client.get_object(Bucket=BUCKET_NAME, Key=last_time_set)
            body = response["Body"].read().decode('utf-8')
            df_last_time = pd.read_csv(StringIO(body), index_col=0)
        except:
            return df_this_time,df_last_time

    return df_this_time,df_last_time

VectorStore作成:処理2)登録情報の整形


・ “以下は問い合わせ対応をした時の内容だ”、というリード文を追加。

・ そのリード文には、質問には無い、前提的な情報を含めた(対象システム名等) 。

・ 見出しも付加し、全体としては以下のような構造です。

以下の文章は+[問合せ種類システム]+の+[問合せ種類]+についてサポート対応をした時の記録です。(改行)+c
<問合せ内容>(改行)+
[問合せ内容] +(改行)+
<対応内容>(改行)+
[対応] +(改行)+
<メモ>(改行)+
[メモ] +(改行)

・ 以下、対象のコード。

Lambda6
def shape_text(df_row): #外部情報格納前に整形する関数。df_row:pandasframeの1行
    #備考)row[0]: 問合せID/row[1]:問合せ種類システム/row[2]:問合せ種類/row[3]:問合せ内容/row[4]:対応/row[5]:メモ
    text1 = '以下の文章は、'
    if pd.notnull(df_row[1]):
        text1 = text1 + df_row[1] + 'の'
    if pd.notnull(df_row[2]):
        text1 = text1 + df_row[2] + 'について'
    text1 = text1 + 'サポート対応をした時の記録です。\n'
    if pd.notnull(df_row[3]):
        text1 = text1 + '<問合せ内容>\n' + df_row[3] + '\n'
    if pd.notnull(df_row[4]):
        text1 = text1 + '<対応内容>\n' + df_row[4] + '\n'
    if pd.notnull(df_row[5]):
        text1 = text1 + '<メモ>\n' + df_row[5] + '\n'
    return text1

VectorStore作成:処理3)登録処理


・ OpenAIの”text-embedding-ada-002”を使い、整形済情報のベクター値を得る。

・ PineconeのUpsert仕様に従い、以下の値を登録。

設定値
id問合せID  #あとで元データを確認する時にし易いこれにしました。
values整形済情報(問い合わせ情報)のベクター値
metadata整形済情報(問い合わせ情報) #検索結果としてこれが必要

・ 初回か2回目以降か判断し、2回目以降は差分をUpsertさせています。

・ 初回の場合に、index自体が未作成なら、作成しています。

・ 以下、対象のコード(全件処理部)。

Lambda8a
def set_vector_store(df_this_time,df_last_time):  #vectorstore登録関数 df_this_time:今回データpandas形式,df_last_time:前回データ
    exec_num = 0
    if (df_this_time is None) and (df_last_time is None): #対象ファイル無しの時は登録せず終了。
        return exec_num
    elif df_last_time is None: #前回データが無い=全件セット(初回) のケース
        exec_num = len(df_this_time)
        res_code = init_vector_store(1) #index確認と作成。mode=1無ければ作成、2削除&作成
        if res_code >= 1 and res_code <= 3:
            i = 0
            record_list = []
            for row in df_this_time.itertuples():
                i += 1
                text1 = shape_text(row) #テキスト部整形
                response = openai_client.embeddings.create(input=text1,model="text-embedding-ada-002") #ベクター値取得
                text1_vector = response.data[0].embedding
                record_dict_tmp = {"id":str(row[0]),"values":text1_vector,"metadata": {"doc":text1,} }
                record_list.append(record_dict_tmp)
                #備考)row[0]: 問合せID/row[1]:問合せ種類システム/row[2]:問合せ種類/row[3]:問合せ内容/row[4]:対応/row[5]:メモ
            do_upsert(record_list,batch_size=100) #Upsert実行 batch_size毎に実行
        else:
            return 0 #異常終了(init_vector_storeの何か)
        return i # 全件セットの登録数を返却
Lambda2
def init_vector_store(mode): #プロト2用indexの存在確認(無ければ作成)。mode:1通常 2削除してから実行
    if mode == 1:
        if INDEX_NAME in pc.list_indexes().names():
            return 1 #存在
        else:
            pc.create_index(name=INDEX_NAME,dimension=1536,metric="cosine",spec=PodSpec(environment="gcp-starter"))
            return 2 #作成した
    elif (mode == 2) and (INDEX_NAME in pc.list_indexes().names()):
        pc.delete_index(INDEX_NAME)
        pc.create_index(name=INDEX_NAME,dimension=1536,metric="cosine",spec=PodSpec(environment="gcp-starter"))
        return 3 #削除&作成した

・ 以下、対象のコード(更新処理部)。

Lambda8b
def set_vector_store(df_this_time,df_last_time):  #vectorstore登録関数 df_this_time:今回データpandas形式,df_last_time:前回データ
    ・・・全件部参照・・・
    else: #前回も今回もデータが存在=差分セット のケース
        #新規追加分を検出し、新規追加用dataflameにセット。
        df_set_insert = pd.merge(df_this_time,df_last_time, how ="left",left_index=True,right_index=True, indicator=True).query(f'_merge == "left_only"').copy()
        df_set_update = df_this_time.copy() #後で処理する更新用DFも作成しておく。
        for idx, sr_row in df_set_insert.iterrows():
            check_row_val = idx #後で処理する更新DF用情報
            df_set_update.drop(df_set_update[df_set_update.index == check_row_val].index, inplace=True) #UPDATE情報DF完成
        #差分評価用DFを作成。
        df_set_update_sort = df_set_update.sort_index().copy() #両DFをindexでソートしておく
        df_last_time_sort = df_last_time.sort_index().copy()
        df_err_idx = pd.merge(df_set_update_sort,df_last_time_sort, how ="outer",left_index=True,right_index=True, indicator=True).query(f'_merge != "both"').copy()
        if len(df_err_idx) != 0:
            return 0 #強制終了
        df_comp = df_set_update_sort.compare(df_last_time_sort).unstack().dropna().unstack(1).reset_index().set_axis(['COLUMN','qa_id','self','other',],axis=1,)
        df_tmp = df_comp.drop_duplicates(subset=['qa_id']).copy()
        df_set_update_final = df_this_time[df_this_time.index.isin(df_tmp['qa_id'])].copy()
        #set_vector 新規分と差分の分、両方を書き込む。
        i = 0
        record_list = []
        for row in df_set_insert.itertuples(): # 1)df_set_insert (新規分の登録)
            i += 1
            text1 = shape_text(row) #テキスト部整形
            response = openai_client.embeddings.create(input=text1,model="text-embedding-ada-002")
            text1_vector = response.data[0].embedding #ベクター値取得
            record_dict_tmp = {"id":str(row[0]),"values":text1_vector,"metadata": {"doc":text1,} }
            record_list.append(record_dict_tmp)
        for row in df_set_update_final.itertuples(): # 2)df_set_update_final (差分の分を登録)
            i += 1
            text1 = shape_text(row) #テキスト部整形
            response = openai_client.embeddings.create(input=text1,model="text-embedding-ada-002")
            text1_vector = response.data[0].embedding #ベクター値取得
            record_dict_tmp = {"id":str(row[0]),"values":text1_vector,"metadata": {"doc":text1,} }
            record_list.append(record_dict_tmp)
        do_upsert(record_list,batch_size=100) #Upsert実行 batch_size毎に実行
        return i
Lambda9
def rec_exec_info(object_key,add_txt): #処理情報記録関数 object_key:S3のkey_name,add_txt:追記する最終更新日の文字列
    set_body = str(add_txt).replace('+09:00', '').replace('-', '/')
    try:
        response = s3_client.put_object(Body=set_body,Bucket=BUCKET_NAME,Key=object_key)
        return set_body
    except s3_client.exceptions.ClientError as ex:
        #無い場合は、初期ファイルを作成
        if ex.response['Error']['Code'] == 'NoSuchKey': # File not found
            response = s3_client.put_object(Body=set_body,Bucket=BUCKET_NAME,Key=object_key)
            return set_body
        else:
            return ''

・ 以下、対象のコード(Upsert処理部)。

Lambda7
def chunks(iterable, batch_size=100): #chunk関数(1回のUpsertを100件以下にする(pinecone推奨)
    """A helper function to break an iterable into chunks of size batch_size."""
    it = iter(iterable)
    chunk = tuple(itertools.islice(it, batch_size))
    while chunk:
        yield chunk
        chunk = tuple(itertools.islice(it, batch_size))

def do_upsert(record_list,batch_size=100): #VectorStoreへUpsertする関数。record_list: Upsert用データlist(id,values,metadata/doc) 
    index = pc.Index(INDEX_NAME)
    for ids_vectors_chunk in chunks(record_list, batch_size=batch_size): #Normal方式
        index.upsert(vectors=ids_vectors_chunk)
    return

VectorStore作成:Lambda関数で動作させる


実際に動作させるために、以下のimportライブラリ等が必要です。

Lambda1
import json
import boto3
from datetime import datetime, timedelta, timezone
from io import StringIO
import pandas as pd
from pinecone import Pinecone, PodSpec
from openai import OpenAI
import itertools

openai_client = OpenAI(api_key = "sk-*****") #お手元の情報をセットして下さい
s3_client = boto3.client('s3')
BUCKET_NAME = 'proto2-data' #バケット名
pc = Pinecone(api_key='*****') #お手元の情報をセットして下さい
INDEX_NAME = 'starter-index-test1' #今回使うindexの名前

・openai_clientはOpenAIから取得したAPIキーをセットします。OpenAIキーについてはこちら(株式会社ユリーカ様の記事を参照させて頂きました)。そして、上の方で紹介したS3 バケット名、PineconeのAPI キーとインデックス名を指定します。

・Lambda関数にセットする手順を整理しておきます。Lambda関数の「コード」タブで、1ファイル(lambda_function.py)で書いてます。
1.本記事のコードセル Lambda1~7を上から順番にセットします。
2.その下に Lambda8a をコピーし、それに続けて Lambda8b3行目以降をコピーします。
3.最後に、Lambda9Lambda_Handlerをセットして完了です。Lambda関数を保存します。

VectorStore作成:定期実行の設定


元データ側システムが新ファイルをアップしていればUpsertされるように、本処理をデイリー(等)で実行するように設定しておきます。

・ AWSコンソールでLambda関数の画面に遷移し、画面内の「トリガーを追加」を押します。

・ その画面で、以下を入力し、追加を完了させます。

設定値
ソースを選択EventBridge(CloudWatch Events)
RuleCreate a new rule
Rule nameatd_proto2_rule
Rule descriptiondaily sync #何でも良い
Rule typeSchedule expression
Schedule expressioncron(0 2 * * ? *) #AM2時の場合

VectorStore作成:登録状態の確認


Upsertされたデータを、Pineconeのコンソール上で確認しておきます。

・ まず、Pineconeにログインします。
 (アカウント未登録の場合、別途ご登録下さい。→参照URL

・ 左メニューに今回作成したindex名が表示されていますので、それを押します。

・ VECTOR COUNTの欄の数字が登録件数です。それが合っていればまずOKです。

 ・ 下部データリスト中各データ欄のペンマークを押すと、値も確認できます。

ベクター検索の試験:仕様


作成したVectorStoreを検索してみます。以下の3つケースを試します。

1.登録した質問文をそのまま入力し、検索上位に出てくるかどうか?
2.質問文を少し変え、検索上位に出てくるかどうか?
3.登録していない内容の質問をして、どんな結果になるか?

ちなみに、この検索はコサイン類似度による比較となります。参照:hi-ku様の解説

ベクター検索の試験:準備


今回は、予備実験用に、Google Colaboで試します。
(Google colaboの利用方法はここでは割愛します。)

・Colab上コードセルを開き、まず「Pinecone」と「OpenAI」をインストールします。

Google Colabo
### pinecone install
!pip install pinecone-client
Google Colabo
### open ai install
!pip install --upgrade openai

・以下はベクター検索(コサイン類似度比較)を試すコードです。「query」は質問文です。

Google Colabo
###コサイン類似度検索を試す。
from pinecone import Pinecone, PodSpec
import json
from openai import OpenAI

pc = Pinecone(api_key='*****')#お手元の情報をセットして下さい

client = OpenAI(api_key = "sk-*****")#お手元の情報をセットして下さい

index = pc.Index("starter-index-test1")#今回使うindexの名前

##質問のベクター値を作成
#query = '金額入力画面の内容をEXCEL出力したい'
#query = '金額入力画面の内容をエクセルで出力したい'
query = '株価編集画面の内容をJSONで出力したい'
response = client.embeddings.create(input=query,model="text-embedding-ada-002")
query_emb = response.data[0].embedding

##ベクター検索
index.query(vector=query_emb,top_k=3,include_values=False,include_metadata=True)

 PineconeとOpenAIのAPIキーは、ご自身の情報をセットして下さい。
 Index名は、VectorStore作成部の方と合わせてあればOKです。

・上のコードセルをGoogleColabo上Shift+Enterすれば検索実行されます。

ベクター検索の試験:結果


試験仕様で書いた3つを試した結果です。

試験1は、登録済QA(ID:1606)と同じQを質問文にして試しています。
試験2は、その質問文の表現を少しだけ変えています。
試験3は、登録済QAに無い質問です。
 ※ Scoreは、 1 に近いほど似ている事を示します。

試験意図質問文Score結果欄結果
想定QAである「問合せID:1606」が1位になる金額入力画面の内容をEXCEL出力したい0.8925133351位:’id’: ‘1606
‘score’:0.892513335
2位: ‘id’: ‘1052’,
‘score’:0.879674554
3位: ‘id’: ‘1051’,
‘score’:0.867886662
期待値通り1位でヒット
同様に意中のID:1606が1位(高順位)も、Scoreは少し下がるはず金額入力画面の内容をエクセルで出力したい0.8886248471位: ‘id’: ‘1606
‘score’:0.888624847
2位:’id’: ‘1052’,
‘score’:0.875948846
3位: ‘id’: ‘1051’,
‘score’:0.864667594
想定通りScoreが少し減少(▲0.0039)
DBに無いのでScoreが試験1で得たScoreよりかなり低いはず。株価編集画面の内容をJSONで出力したい0.821543634
(関係無いが1位になったQA(ID:1037)とのScore)
1位:’id’: ‘1037’,
‘score’:0.821543634
2位:’id’: ‘1379’,
‘score’:0.817604125
3位: ‘id’: ‘1428’,
‘score’:0.817518055
想像通りの低いScore(質問1と比べ▲0.0710)

参考)ID:1606の内容:
以下の文章は、*****システムのEXCEL出力についてサポート対応をした時の記録です。
<問合せ内容>
金額入力画面の内容をEXCEL出力したい
<対応内容>
帳票出力>EXCEL出力>金額・作業単価計算コード使用状況
から出力できることをお伝えしました。
上記手順では記事欄の出力が無いため、記事欄の出力は出来ないのか?と聞かれ
EXCEL出力では記事欄の出力はできないと回答しました。
しかし、後からEXCEL出力(TMS)から出力できることに気づいたため
留守電に出力をできる旨のメッセージを残しました。

参考)ID:1037の内容:
以下の文章は、*****システムの帳票についてについてサポート対応をした時の記録です。
<問合せ内容>
見積価格査定書の「記事」欄には何を出力しているのか。
<対応内容>
確認の上回答する。

結果として、ケース 1 も2も、問合せ ID:1606 が期待どおり 1 位にランクされました。
ケース 2 の想像通り下がったScoreは、大きいのか小さいのか、正直ピンときません。

ケース 3 も、無関係な項目が1位になり、Scoreが低い、という想像通りの結果です。

全体的には Pinecone のベクター検索は概ね想像通りで、RAGで使えそうな事がわかりました。

まとめ


・ベクター検索をする環境としてPineconeを試し、期待通りに機能し、RAGで使えそうな事がわかりました。

・検索すると、VectorStoreに存在するものは上位で抽出され、存在しないものは上位に無い、という結果で、このあとLLMでテキスト生成する事を考慮し、リーズナブルだと思いました。

・細部ですが、”Score値の一般的な適切な範囲”というのは無さそうなので、コンテクストに貼り付ける検索結果の量を、コスト等の理由で制限するために、閾値の設定が重要になりそうです。(これはPineconeに限らない話です。)

・本番運用する想定でデータを構成してみましたが、ローカル必須等の要件がなければ、RAGのVectorStoreはこれで良い気がしました。より良い方法等あれば、ご指摘頂けますと嬉しいです。(フィードバックをお願いします)。

ご連絡フォーム


フィードバックを是非お願いします。
本記事の方法での問題点や、よりよい方法のアイデアを頂けると大変助かります。

この記事に関して

その他のご連絡

DevAIsをもっと見る

今すぐ購読し、続きを読んで、すべてのアーカイブにアクセスしましょう。

続きを読む