背景や目的
(正確に言うと、複数の情報源があった場合にどうやってRAGのベクトルデータベースを作るか、というテーマのお話です。)
RAG(Retrieval-Augmented Generation)を活用した社内チャットボットの構築にあたり、私たちは2つの異なる情報源を扱う必要がありました。ひとつは日々蓄積されるSlack内の会話データ、もうひとつは社内のルール情報です。
今回は、Slackデータとルール情報を同じベクトルDBに統合し、検索結果でルール情報を優先したくて、少し工夫をしました。
本記事では、その内容を紹介します。
実現したい事
複数の情報源をベクトルDBに登録し、Slackの古いデータよりルール情報を優先して活用できるRAGを実現したいです。

手順サマリー
手順のサマリーとしては以下の通りです。
1.対象データの準備
2.ベクトルDB作成
3.ベクトル検索処理コード作成
順番に紹介してゆきます。
1.対象データの準備
ベクトルDBに登録する元データを用意します。
・元データは、Slackの会話データとルール情報(CSV形式)の2種類で、それぞれ用意しました。
・ちなみに、実際の元データは以下のような内容です。
- ルール情報(CSV形式)3件

- Slack会話 3件
・今回は上記データを使用しますが、目的に応じてご自身でご用意ください。
次に、このデータをベクトルDB(Index)に登録するため、まずはIndexを作成します。
2.ベクトルDB作成:Index作成
いずれにせよ、データを入れる「箱」=「ベクトルDBのIndex」が必要なので、それを作ります。今回はPineconeでIndexを作成します。
※すでにDBのIndexを作成済みで、レコードの登録も完了している場合は、「3.ベクトル検索処理コード作成」セクションへ進んでください。
・Pineconeのコンソールにログインし、以下の通りIndexを作成します。
(※アカウント作成やクレジットカード登録などの手順はここでは割愛します)
・新しいプロジェクトを作成します。画面右上の「Create Project」を押します。

・プロジェクト名を入力し、「Create Project」を押下したらプロンプト作成は完了。

・作成直後に表示されるPineconeのAPIキーはコピペします。(注意:後から確認不可です)

・プロジェクト画面に遷移したら、Indexを作成します。「Create Index」を押します。

・Index作成画面で名前を入力し、ConfigurationではEmbeddingモデルを指定します。

・今回はOSSモデル(GluCoSE)を使うため「Custom settings」にチェックします。既存のモデルを使う場合は、リストから選択するだけでOKです。
・カスタム設定セクションで下表の値を入力します。

| 設定値 | 値 |
|---|---|
| Vector type | 「Dense」を選択します。※一般的なベクトルデータ |
| Dimensions (次元数) | 「768」を入力します。※利用モデルの次元数に合わせます。 |
| Metric | 「cosine」を選択します。※コサイン類似度で比較されます。 |
・続いて、「Capacity mode」は「Serverless」、「Cloud provider」は「AWS」、任意の「Region」を選択し、「Create Index」をクリックすれば作成完了です。


・最後にIndexを一覧で確認し、「Host」URLを控えておきます。
(Indexへレコードのupsert時に使用します)

これでベクトルDB Indexの作成は完成!
2.ベクトルDB作成:Indexにレコード登録
Index作成が完了したので、元データをベクトル化してUpsertします。
(※Upsertは「Update」と「Insert」を合わせた操作で、データを更新又は新規登録する事です)
・基本的な流れは以下の通りです:
- 元データを整形・リスト化し、Embeddingモデルでベクトル化。
- メタデータ(IDや分類情報など)とともにIndexへUpsertします。
・テキストのベクトル化には、OSS版のEmbeddingモデル「GluCoSE」を私たちは使いました。各種モデルが公開されているHugging Faceからロードします。
・環境はGoogle Colaboを利用します。
・まずは、Pineconeをインストールします。
!pip install -U pinecone
・続いて、Upsert処理に関連するコードは以下の通りです。
import pandas as pd
import itertools
from sentence_transformers import SentenceTransformer
from pinecone import Pinecone
# --- データ前処理 ---
# CSVファイルからルール情報読み込み
df = pd.read_csv("abcd.csv")
filtered_df = df[["カテゴリー", "Q", "A"]]
rule_data_list = []
for _, row in filtered_df.iterrows():
category = row["カテゴリー"]
question = str(row["Q"]).strip()
answer = str(row["A"]).strip()
combined = f"以下は、「{category}」に関するルール情報です。{question} {answer}"
rule_data_list.append(combined)
# Slackデータの整形
#Slackチャンネルから取得した情報例
slack_messages = [
"○○ソフトウェアをインストールしたいです。",
]
channel_id = "C12342391"
slack_data_list = [
f"以下は、Slackチャンネル「{channel_id}」の情報です。{msg.strip()}"
for msg in slack_messages
]
# --- ベクトルDB登録処理 ---
# Pinecone設定
#プロジェクトのAPIキー
pc = Pinecone(api_key='******') #先ほど作成したAPIキーの情報を設定
index = pc.Index(host="https://****.pinecone.io")#先ほど作成したindexのHOST URLを設定
# HuggingFaceからモデル読み込み(GluCoSE)
model = SentenceTransformer("pkshatech/GLuCoSE-base-ja-v2")
# ベクトル生成
def create_embeddings(sentence):
return model.encode(f"passage: {sentence}").tolist()
# レコード生成
def generate_records(data_list, prefix, start_id):
is_f_flag = prefix == "F"
return [
{
"id": f"{prefix}{str(start_id + i)}",
"values": create_embeddings(data),
"metadata": {"doc": data, "is_f": is_f_flag}
}
for i, data in enumerate(data_list)
]
# チャンク分割
def chunks(iterable, batch_size):
it = iter(iterable)
chunk = tuple(itertools.islice(it, batch_size))
while chunk:
yield chunk
chunk = tuple(itertools.islice(it, batch_size))
# Upsert実行
def do_upsert(record_list, batch_size):
for chunk in chunks(record_list, batch_size=batch_size):
index.upsert(vectors=chunk)
print("UPSERTED:", len(record_list))
# 実行
rule_records = generate_records(rule_data_list, prefix="F", start_id=1)
slack_records = generate_records(slack_data_list, prefix="S", start_id=1)
do_upsert(rule_records, batch_size=100)
do_upsert(slack_records, batch_size=100)・上記のコードでは主な処理は以下の通りです:
1) ルール情報・Slackデータの前処理(行:9~29)
CSVからルール情報を抽出・整形してrule_data_listを作成。Slackから取得した会話データも、同様に整形し、slack_data_listを作成。
2) generate_records() (行:46~55)
rule_data_listとslack_data_list内レコード毎にID(FまたはS + 番号)を付与し、「is_f」メタデータ付きのレコードを作成します。
各テキストはGluCoSEを使ってベクトル化され、valuesに格納されます。
3) create_embeddings()(行:42~43)
”passage:” を付けたテキストをGluCoSEに送信し、ベクトル値を取得します。
4) chunks() (行:58~63)
レコードリストを指定バッチサイズごとに分割します(Upsert時に使用)。
5) do_upsert() (行:66~69)
分割済みのレコードを index.upsert()を使ってPineconeに登録します。処理後確認のために件数を出力します。
・Upsert時にPineconeに以下の情報を登録します。
| 設定値 | 値 |
|---|---|
| id | 識別のため、Slackは「S」、ルール情報は「F」をID先頭に付けました。(例:S1, F1) |
| values | 整形済情報(それぞれ元情報)のベクトル値 |
| metadata | 1. doc = ベクトル値の元情報 2. is_f = ルール情報かどうかを判別するため、「true」や「false」の値 |
このように、データUpsert処理を作成しました。
2.ベクトルDB作成:登録状態の確認
Upsertされたデータを、Pineconeのコンソール上で以下の通り確認しておきます。
・ まず、Pineconeにログインします。先ほど作成したプロンプトに遷移します。
・ index一覧に今回作成したindex名が表示されていますので、それを押します。

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

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


値の確認が出来たら、ベクトルDB作成は完成です!
3.ベクトル検索処理コード作成
ベクトルDBの作成が完了したので、次はそれを用いた検索(query)処理に進みます。
RAGでは、ユーザーの質問をEmbeddingモデルでベクトル化し、そのベクトルを使ってベクトルDBをコサイン類似度で検索します。
ただし今回は、Slackの会話データとルール情報という性質の異なる2つの情報源を同じベクトルDBに統合しているため、単純な類似度検索だけでは期待通りの結果が得られないことがありました。
実際、ルール情報が存在していても、古いSlackデータの類似度スコアが高く出てしまい、そちらが優先されてしまうケースもありました。それを解決したいです。
・対策として、以下の工夫を加えました:
-is_fメタデータとtop_k=2を使い、Slackとルール情報を別々で検索(query)する。
※top_kは取得件数を指定するパラメータです。
- それぞれ2件ずつを抽出し、ID順(降順)にソートして新しい順に並べる。
- ルール情報 → Slack情報 の順で結合し、回答生成モデルに渡すコンテクストを作成。
・実際の検索処理コードは以下の通りです。Google Colabo上で動作確認をします。
from sentence_transformers import SentenceTransformer
import os
from pinecone import Pinecone
import logging
logger = logging.getLogger()
logger.setLevel(logging.INFO)
#プロジェクトのAPIキー
pc = Pinecone(api_key='******') #先ほど作成したAPIキーの情報を設定
index = pc.Index(host="https://****.pinecone.io")#先ほど作成したindexのHOST URLを設定
# --- Load GLuCoSE model ---
model = SentenceTransformer("pkshatech/GLuCoSE-base-ja-v2")
def exec_rag(question, similarity): # similarity: -1 ~ 1
try:
question_text = f"query: {question}"
q_embeddings = model.encode(question_text).tolist()
print("Embeddings created.")
print(q_embeddings)
print("Querying Pinecone for similarity search...")
# For File IDs (is_f=True)
search_results_f = index.query(vector=q_embeddings, top_k=2,filter={"is_f": True}, include_values=False, include_metadata=True)
# For Slack IDs (is_f not True OR missing)
search_results_s = index.query(
vector=q_embeddings,
top_k=2,
filter={
"$or": [
{"is_f": {"$ne": True}}, # is_f exists but is not True
{"is_f": {"$exists": False}} # is_f not set (old Slack vectors)
]
},
include_values=False,
include_metadata=True
)
# Filter search results based on similarity
context_candidates = []
work_list = []
filtered_f = [
[val['id'], val['score'], val['metadata']['doc']]
for val in search_results_f['matches']
if val['score'] >= similarity
]
filtered_s = [
[val['id'], val['score'], val['metadata']['doc']]
for val in search_results_s['matches']
if val['score'] >= similarity
]
# Sort only by ID (descending)
filtered_f.sort(key=lambda x: int(x[0][1:]), reverse=True)
filtered_s.sort(key=lambda x: int(x[0][1:]), reverse=True)
# Create Context 検索結果 with F first, then S
context_candidates = filtered_f + filtered_s
print(context_candidates)
except Exception as e:
print(f"Error in exec_rag: {str(e)}")
raise
# 質問と検索結果
question = "サクラエディタをインストールしたいです。"
similarity = 0.65
exec_rag(question, similarity)・APIキーなどを設定し、「question」に質問文、「similarity」に類似度のしきい値を入力して実行します。出力されるのは、生成モデルに渡すコンテクスト用の情報です(リスト形式)。
次は、この出力(コンテクスト)と質問文をあわせて、回答生成モデル(GPT-4o)に渡すことでRAGは完成しますが、本記事ではここまでとさせていただきます(すみません!)。
必要な場合は、回答生成処理部は以前の記事(→RAG型チャットボット画面に評価機能を追加。)で「サーバー側Lambda関数の作成」を参照してください。
試用してみましょう!
コードが完成したので、検索出力リストが意図通り(ルール情報が先、Slack情報が後)になっているか、質問を入力してテストしてみましょう。
・上記のコード内必要な部分を入力し、実行してみます。

・出力リストの順:[‘F2’, ‘F1’, ‘S3’, ‘S2’] でした。
期待通り、ルール情報のIDが先に来るようになっています。完成!
まとめ
今回は、異なる情報源を統合したベクトルDBを作成し、RAGで活用する方法を紹介しました。
古いSlack情報がルール情報より先に選ばれてしまう課題に対し、Upsert時に情報源を識別できるメタデータ「is_f」を付加し、検索時に別々に検索することで解決しました。
今後は、時系列を考慮した検索や古いデータの削除も試してみたいと考えています(Pineconeは現在時系列検索等に非対応のため)。
DevAIsಗೆ ಭೇಟಿ ನೀಡುವವರ ಸಂಖ್ಯೆ ಹೆಚ್ಚಿರುವುದು ನಮಗೆ ತುಂಬಾ ಸಂತೋಷ ತಂದಿದೆ.
(DevAIsへのアクセス数が増加しました。大変嬉しく思っております。)


