フェーズ2:ベクトル検索によるRAGの導入
- 商品説明文から OpenAI Embedding を生成
- Pinecone / Weaviate / Supabase vector などでベクトル検索セットアップ
- ユーザーの質問 → 関連商品抽出 →
messages
に注入 → ChatGPT呼び出し - 効果・価格・注意点などを含む「事実ベースのセールストーク」生成
Embeddingの生成
Embeddingとは?
Embeddingとは、文章や単語の「意味」をAIが理解できるように数値(ベクトル)で表したものです。たとえば「ニキビを治すクリーム」という文を入力すると、AIはその意味を数百〜数千次元の数値列に変換します。この数列は言葉そのものではなく、「スキンケア」「薬」「肌」「改善」といった概念的な特徴を含んでいます。
Embeddingの大きな特徴は、似た意味の文章は近い位置に配置されることです。たとえば「ニキビを治すクリーム」と「肌荒れを防ぐ薬」は、単語が違っても意味が似ているため、ベクトル空間では近くに並びます。逆に「ヒゲ剃りローション」は遠くに位置します。つまりAIは、単語の一致ではなく「意味の近さ」で判断できるようになります。
EmbeddingはChatGPTのように文章を生成する仕組みとは異なり、一方向の変換(不可逆)です。この数列から元の文章を再構成することはできません。その代わり、意味検索やレコメンド、RAG(検索+生成)といった仕組みで大きな力を発揮します。
長い文章をそのままEmbeddingにすると、複数の話題が混ざって意味が平均化されてしまうため、
通常は200〜500文字程度の短い文や段落に分けて(チャンク化して)ベクトル化します。こうすることで、AIはより正確に文脈を捉え、ユーザーの質問に近い情報を探し出せるようになります。
一言でいえば、Embeddingは「言葉を意味の地図に変える技術」です。AIはこの地図を使って、関連する情報や商品を見つけ出し、より人間的に理解された回答を返します。
1単語でも100単語でも、text-embedding-3-smallでは1,536次元、text-embedding-3-largeでは3,072次元の1ベクトルになる。
Python ライブラリの準備
pip install openai numpy pandas
Embedding生成
from openai import OpenAI
import json
import numpy as np
import pandas as pd
openai_api_key = "xxx"
client = OpenAI(api_key=openai_api_key)
with open("simplified_products.json", "r", encoding="utf-8") as f:
products = json.load(f)
# 各商品に対してembeddingを生成
embeddings_data = []
for p in products:
# 商品タイトル+説明を結合(descriptionがない場合はtitleのみ)
text = f"{p['title']} - {p.get('description', '')}".strip()
# 高速・低コスト版(精度を上げたいなら text-embedding-3-large)
response = client.embeddings.create(
model="text-embedding-3-small",
input=text
)
vector = response.data[0].embedding
embeddings_data.append({
"id": p.get("id", None),
"title": p["title"],
"tags": p.get("tags", ""),
"embedding": vector
})
# 結果をDataFrameにして確認
df = pd.DataFrame(embeddings_data)
print(df)
# 必要ならCSV保存(Pineconeなどにアップロード前段階)
df.to_csv("product_embeddings.csv", index=False)
Embeddingのイメージ
id title tags embedding
0 Aftershave - Bump Control 65ml Solution 2 packs Skincare [0.013, -0.024, 0.009, ...]
1 Benzoyl Peroxide - Pernex 20mg Cream Skincare [0.001, 0.027, -0.015, ...]
ベクトル検索のセットアップ
ベクトルデータベース
ChatGPT単体では「外部のデータ」を覚えていません。Embeddingで商品を数値化したあと、それを保存・検索できるベクトルデータベースが必要になります。例えば、以下のようなサービスが存在します。
- 🟦 Pinecone(クラウド特化、高速・安定)
- 🟪 Weaviate(オープンソース、自己ホスト可能)
- 🟩 Supabase Vector(PostgreSQL拡張)
特徴 | Pinecone | Weaviate | Supabase Vector |
---|---|---|---|
運用形態 | クラウドSaaS | OSS or Cloud | PostgreSQL拡張 |
セットアップ | 最も簡単 | 中級(Docker等) | 開発者フレンドリー |
スケーラビリティ | ◎ 高速スケーラブル | ◎ 構造化・関係型対応 | ○ 小規模〜中規模 |
検索性能 | 高精度(Annoy/ScaNNなど) | 高精度(HNSW) | 良好(pgvector) |
コスト | 有料(無料枠あり) | 無料 or 有料Cloud | 安価 or 自社DB内完結 |
向き | 本番運用向け | 自前構築・制御したい人 | 中小規模PoC |
ベクトル検索の流れ
① 商品説明文をEmbedding化
↓
② ベクトルDB(Pineconeなど)に保存
↓
③ ユーザー質問をEmbedding化
↓
④ 類似ベクトル検索(=意味が近い商品を抽出)
↓
⑤ 抽出結果をChatGPTに渡して回答生成
pineconeのインストール
pip install pinecone
Indexの作成とベクトルの追加
index.upsertの実行
from openai import OpenAI
from pinecone import Pinecone, ServerlessSpec
import pandas as pd
import ast
import math
openai_api_key = "xxx"
pinecorn_key = "yyy"
client = OpenAI(api_key=openai_api_key)
pc = Pinecone(api_key=pinecorn_key)
index_name = "products"
if index_name not in [i["name"] for i in pc.list_indexes()]:
# Index(保存先)を作成
pc.create_index(
name=index_name,
dimension=1536, # text-embedding-3-small の次元数
metric="cosine",
spec=ServerlessSpec(cloud="aws", region="us-east-1")
)
index = pc.Index(index_name)
df = pd.read_csv("product_embeddings.csv")
vectors = []
for _, row in df.iterrows():
try:
emb = ast.literal_eval(row["embedding"])
tag = row.get("tags", "")
if isinstance(tag, float) and math.isnan(tag):
tag = "" # NaN → 空文字に変換
vectors.append({
"id": str(row["id"]),
"values": emb,
"metadata": {
"title": row["title"],
"tags": tag
}
})
except Exception as e:
print(f"Skipped row {row['id']}: {e}")
index.upsert(vectors=vectors)
print(f"Uploaded {len(vectors)} vectors to Pinecone index '{index_name}'.")
Indexの状態確認
index.describe_index_statsの実行
from pinecone import Pinecone, ServerlessSpec
pinecorn_key = "yyy"
pc = Pinecone(api_key=pinecorn_key)
index_name = "products"
index = pc.Index(index_name)
stats = index.describe_index_stats()
print(stats)
{'dimension': 1536,
'index_fullness': 0.0,
'metric': 'cosine',
'namespaces': {'': {'vector_count': 41}},
'total_vector_count': 41,
'vector_type': 'dense'}
Indexの検索
index.queryの実行
client.embeddings.createの実行
rom openai import OpenAI
from pinecone import Pinecone, ServerlessSpec
openai_api_key = "xxx"
pinecorn_key = "yyy"
client = OpenAI(api_key=openai_api_key)
pc = Pinecone(api_key=pinecorn_key)
index_name = "products"
index = pc.Index(index_name)
query = "ニキビに効果のある薬"
query_emb = client.embeddings.create(
model="text-embedding-3-small",
input=query
).data[0].embedding
results = index.query(vector=query_emb, top_k=3, include_metadata=True)
for match in results["matches"]:
print(f"{match['score']:.2f} - {match['metadata']['title']}")
0.30 - Betamethasone dipropionate cream - Xtraderm 20mg Cream
0.29 - Benzoyl Peroxide - Pernex 20mg Cream
0.26 - Minoxidil - Noxidil 5% Topical Solution 60ml
RAG最小構成完成版
① ユーザー質問を受け取る
② OpenAIで質問をEmbedding化
③ Pineconeで類似商品を検索
④ 類似商品をまとめてChatGPTに渡す
⑤ ChatGPTが自然な説明・提案を生成
from openai import OpenAI
from pinecone import Pinecone
import os
OPENAI_API_KEY = "xxx"
PINECONE_API_KEY = "yyy"
INDEX_NAME = "products"
client = OpenAI(api_key=OPENAI_API_KEY)
pc = Pinecone(api_key=PINECONE_API_KEY)
# === Indexの取得 ===
index = pc.Index(INDEX_NAME)
# === 質問のEmbedding生成 ===
user_query = input("質問を入力してください: ")
embedding_response = client.embeddings.create(
model="text-embedding-3-small",
input=user_query
)
query_vec = embedding_response.data[0].embedding
# === Pinecone検索 ===
results = index.query(vector=query_vec, top_k=3, include_metadata=True)
# スコアが低いものを除外(例:0.5未満)
matches = [m for m in results["matches"] if m["score"] >= 0.3]
if not matches:
print("関連商品が見つかりませんでした。")
exit()
# === 検索結果をまとめてChatGPTに渡す ===
context = "\n".join([
f"- {m['metadata']['title']} (タグ: {m['metadata'].get('tags', 'N/A')})"
for m in matches
])
# === ChatGPTで自然な回答生成 ===
system_prompt = """
あなたはオンライン薬局の営業アシスタントです。
次のルールを守ってください:
- 回答は提供された商品情報のみに基づく。
- 人気・おすすめなどの主観的表現は禁止。
- 効果効能は誇張せず、客観的に説明する。
- 不明点は「情報がありません」と答える。
"""
response = client.chat.completions.create(
model="gpt-4o",
temperature=0.7,
messages=[
{"role": "system", "content": system_prompt},
{"role": "user", "content": f"以下の商品データを参考にしてください:\n{context}\n\n質問: {user_query}"}
]
)
print("\n AIの回答:")
print(response.choices[0].message.content)
質問を入力してください: AGAの薬はある?
AIの回答:
はい、AGA(男性型脱毛症)の薬として「Dutasteride - Avodart 0.5mg Tablets 30 tabs」があります。
効果・価格・注意点などを含む「事実ベースのセールストーク」生成
- ベクトル検索で関連商品を
top_k
取得 - 取得した商品の 事実(metadata)だけを整形して文脈に注入
- ChatGPTに 厳格ルール+JSON形式 で生成させる
- JSONをパースしてUIや次処理へ
from openai import OpenAI
from pinecone import Pinecone
import os, json
OPENAI_API_KEY = "xxx"
PINECONE_API_KEY = "yyy"
INDEX_NAME = "products"
SIM_THRESHOLD = 0.30 # 類似度のしきい値(0.5未満は捨てる)
TOP_K = 5 # 候補数
client = OpenAI(api_key=OPENAI_API_KEY)
pc = Pinecone(api_key=PINECONE_API_KEY)
index = pc.Index(INDEX_NAME)
def embed(text: str):
return client.embeddings.create(
model="text-embedding-3-small", input=text
).data[0].embedding
def search_products(query: str):
qvec = embed(query)
res = index.query(vector=qvec, top_k=TOP_K, include_metadata=True)
matches = [m for m in res["matches"] if m["score"] >= SIM_THRESHOLD]
return matches
def to_context(matches):
# ChatGPTに渡す“事実セット”。モデルが読みやすい行構造にする
lines = []
for m in matches:
md = m["metadata"] or {}
line = {
"id": m["id"],
"title": md.get("title") or "",
"price": md.get("price") or "",
"description": md.get("description") or "",
"tags": md.get("tags") or "",
"image": md.get("image") or "",
"similarity": round(m["score"], 3)
}
lines.append(line)
return lines
SYSTEM_PROMPT = """あなたは薬局ECの営業アシスタントです。以下を厳守してください。
- 出力は必ずJSONのみ。自然文は書かない。
- 与えられた商品データ以外の知識を使わない(一般知識での補完や想像禁止)。
- 「人気」「おすすめ」「効果的」「ベスト」等の主観表現は禁止。
- 効果・用途・注意点はデータに存在する内容だけで、事実ベースで簡潔に。
- 不明項目は空文字にせず「情報がありません」と記す。
- 価格は与えられた値をそのまま表示(通貨や税抜/税込の推測をしない)。
- 出力スキーマ:
{
"query": string,
"pitches": [
{
"product_id": string,
"product_name": string,
"price": string,
"what_it_does": string,
"suitable_for": string,
"cautions": string,
"concise_pitch": string,
"evidence": string // 「提供データのみ」と記す
}
]
}
"""
def generate_pitch(user_query: str):
matches = search_products(user_query)
if not matches:
return {
"query": user_query,
"pitches": []
}
context_items = to_context(matches)
print(context_items)
# userメッセージに“事実”をJSONで添付
user_content = (
"ユーザー質問:\n"
f"{user_query}\n\n"
"関連商品データ(JSON):\n"
+ json.dumps(context_items, ensure_ascii=False)
+ "\n\n上記のみを根拠に、スキーマに沿って応答を生成してください。"
)
resp = client.chat.completions.create(
model="gpt-4o",
temperature=0.3, # 事実寄せで低め
response_format={"type": "json_object"},
messages=[
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": user_content}
],
)
content = resp.choices[0].message.content
result = json.loads(content)
return result
# ===== 実行例 =====
if __name__ == "__main__":
q = "ニキビに使える外用薬が知りたい。刺激が強すぎないものが良い。"
result = generate_pitch(q)
print(json.dumps(result, ensure_ascii=False, indent=2))
{
"query": "ニキビに使える外用薬が知りたい。刺激が強すぎないものが良い。",
"pitches": [
{
"product_id": "8580179165338",
"product_name": "Aftershave - Bump Control 65ml Solution 2 packs",
"price": "950.0",
"what_it_does": "It soothes and hydrates your skin, helping to reduce redness and leave it feeling calm and smooth after every shave.",
"suitable_for": "情報がありません",
"cautions": "情報がありません",
"concise_pitch": "BUMP PATROLは、シェービング後の肌を落ち着かせ、潤いを与えるアフターシェーブトリートメントです。",
"evidence": "提供データのみ"
}
]
}
生成のポイント(設計意図)
- 厳格ガード:
「提供データ以外は使わない」「主観語NG」「不明は“情報がありません”」を system で固定。 - JSON固定:
response_format={"type":"json_object"}
(新SDK)で、パース安定。 - 低温度:
temperature=0.3
で事実寄せ&再現性UP。 - スコア閾値:
0.5 未満は除外(“無理やりの紹介”を防止)。必要に応じて調整。 - concise_pitch:
事実の再言だけで30〜60文字程度の一文にさせるとUIに載せやすい。
※ metadataに「description/cautions/suitable」等が薄いと、“情報がありません”が入ります。精度を上げるには 商品説明を少しだけリッチに(用途・注意・容量など)するのが効きます。
まとめ
フェーズ2では、商品データをベクトル化し、ユーザーの質問内容に応じて関連商品を検索・要約するRAGの仕組みを構築しました。これにより、事実ベースでセールストークを生成できるAI営業アシスタントの土台が完成しました。
一方で、以下の課題が残っています。
- 応答が完了してからまとめて返るため、ユーザー体験がやや遅い
- 会話から在庫確認やCRM登録などのアクションにつなげられていない
- 英語・日本語が混在する環境での対応が限定的
- 出力結果をCRMや分析に直接活用できる形に整備できていない
これらを踏まえ、次のフェーズ3では以下に取り組みます。
stream: true
によるリアルタイム応答でUXを改善- Function Callingを用いた在庫・CRM連携の自動化
- 日本語・英語のハイブリッド対応による多言語化
- 購買確度スコアなどのJSON出力を通じたデータ活用強化
これにより、AIを「情報を答える存在」から
「実際に行動し、業務を支援する営業アシスタント」へと進化させます。