PineconeにQAデータ格納してボット回答試してみた。

Pineconeのアカウントが開設できたので、試してみました！
無料のアカウント開設に3日間くらい待機リストに入りましたが、割りとすぐに使えるようになりました🙌

合わせてOpenaAIの埋め込みとLLMを使ってボットで回答をさせてみます。本記事はその際の備忘録的な感じです。
Pineconeって何？については割愛します。

https://www.pinecone.io/

https://docs.pinecone.io/docs/quickstart

## Pineconeにインデックスを作成する
元データにはこちらのデータセットを使ってみようと思います。

https://linecorp.com/ja/csr/newslist/ja/2020/260

必要なモジュールをインストール
```bash:bash
pip install --upgrade pip

pip install pandas
pip install numpy
pip install python-dotenv
pip install pinecone-client
pip install openai
pip install openai[embeddings]
```
```python:python
import os
from dotenv import load_dotenv
import numpy as np
import openai
from openai.embeddings_utils import get_embedding
import pandas as pd
import pinecone

load_dotenv()
```

データ読み込み
```python:python
df = pd.read_csv("data/data.csv")
```

中身はこんな感じで雑に加工。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2821082/e9bb1def-7ccf-4e65-bd11-172f4dac2272.png)
欠損を確認しとく。
```python:python
df.isna().sum()
```
```
id           0
qes          0
ans          0
um1          0
um2         31
um3        225
service    225
dtype: int64
```
qesとans合わせたCombined列を作成
```python:python
df["Combined"] = (
    "qes: " + df.qes.str.strip() + "; ans: " + df.ans.str.strip()
)
df["Combined"].isna().sum()
```

埋め込み前の準備諸々します。
```python:python
openai.api_key = os.environ.get("OPENAIKEY")
pineconeKey = os.environ.get("PINECONEKEY")
MODEL = "text-embedding-ada-002"
embedding_dimension = 1536 
```

ちなみにPineconeのAPIキーはここで見れます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2821082/e07c8819-20e8-efa0-be82-5337447b545f.png)

埋め込みの実行
```python:python
df["Embedding"] = df["Combined"].apply(lambda x: get_embedding(x, engine=MODEL))
```

https://github.com/openai/openai-python

```python:python
#dfを一旦確認
df
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2821082/3ee8cdee-b22c-cb04-203a-15b92a0240e4.png)
大丈夫そう！
そしたらPineconeのインデックスを作成する手順に。
```python:python
pinecone.init(api_key=pineconeKey, environment="us-west1-gcp-free")
pinecone.create_index("example-index", dimension=embedding_dimension, metric="cosine")
pinecone_index = pinecone.Index("example-index")
```
```python:python
#作成したインデックスにデータを流す
for i in df.index:
    pinecone_index.upsert(
        vectors=[
            (
                str(df["id"][i]),
                df["Embedding"][i],
                {"text": df["ans"][i], "source": df["Combined"][i]}
            )
        ],
    )
```
因みに、インデックス作成の際のパラメータに関しては公式参照

https://docs.pinecone.io/docs/indexes

ポータルで確認。うん。できてる。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2821082/1facf39f-6a0d-6d65-9b45-eba906d021a4.png)

## セマンティック検索＆ボットで回答
質問をベクトル化
```python:python
q = "子供が生まれたらまずは何をすればいいですか？"
embedding = get_embedding(q, engine=MODEL)
```
作成したindexに対してクエリ。類似結果TOP3。
```python:python
res = pinecone_index.query(
        [embedding], 
        top_k=3, 
        #キーワードでフィルターする事もできる。
        # filter={
        #     "source": {"$in": ["妊娠・出産"]}
        # },
        include_metadata=True
    )
```
キーワードでフィルターする事もできるのね。
おお、便利～

https://docs.pinecone.io/docs/metadata-filtering

結果を確認してみる
```python:python
ansList = []

for i in res["matches"]:
    score = i["score"]
    id = i["id"]
    text = i["metadata"]["text"]
    source = i["metadata"]["source"]

    ansList.append(text)
    print(f"id: {id}")
    print(f"score: {score}")
    print(f"text: {text}")
    print(f"source: {source}")
    print("------")
```
```
id: 14
score: 0.874312103
text: 出産後に必要な手続きは出生届・出生通知票の提出、児童手当、子ども医療費助成の申請等があります。

▼詳しくはこちら
（自治体HP内関連ページのURL）
source: qes: 子どもが生まれたら、どんな手続きが必要ですか。; ans: 出産後に必要な手続きは出生届・出生通知票の提出、児童手当、子ども医療費助成の申請等があります。

▼詳しくはこちら
（自治体HP内関連ページのURL）
------
id: 425
score: 0.858622193
text: 子どもの手当・助成としては、児童手当と子ども医療費助成制度があります。

▼詳しくはこちら
（自治体HP内関連ページのURL）

◆お問い合わせ
（自治体の担当課等の名称）
（電話番号）／（開庁時間）
source: qes: 子どもが生まれたら、どのような手当がありますか。; ans: 子どもの手当・助成としては、児童手当と子ども医療費助成制度があります。

▼詳しくはこちら
（自治体HP内関連ページのURL）
...

AAA保健相談所：（電話番号）（○時○分から○時○分）
BBB保健相談所：（電話番号）（○時○分から○時○分）
------
```
割りと思った通りの検索ができた。
次にボットにこの検索結果を乗っけて回答を生成させてみる。

https://www.pinecone.io/learn/chatbots-with-pinecone/

やり方で紹介されていたのは下記の２つ。
1. 取得した結果のテキストを元のクエリにコンテキストとして添付し、両方をプロンプトとして生成 AI モデルに送信して、根拠のある関連する応答を求める方法。
1. OpenAIのchatGPT検索プラグインでやる方法。

今回は1の手順をやってみる事に。

https://docs.pinecone.io/docs/gen-qa-openai

```python:python
response = openai.Completion.create(
    engine="text-davinci-003",
    prompt=f"Please answer the question in Japanese, taking into account the following context. \
        Context: \
            Question: {q} \
            Answer: {ansList[0]},{ansList[1]},{ansList[2]}", #先程取得した回答データ(TOP3)
    temperature=0,
    max_tokens=500
)
print(response.choices[0]["text"])
```
```
まずは、子供が生まれたら出生届・出生通知票の提出、児童手当、子ども医療費助成の申請などの必要な手続きを行います。
詳しくは、自治体のHP内関連ページをご覧ください。
また、子どもの成長や性格、生活環境や事情によって解決策が異なる場合もありますので、保健師にご相談ください。AAA保健相談所、BBB保健相談所などで受けられます。
```
なるほど～。最初にしてはいい感じ。

最後に`LangChain`でPineconeに作成したインデックスに対して検索＆ボット回答

https://note.com/npaka/n/nf2849b26a524

https://blog.langchain.dev/retrieval/


```
pip install langchain
```

```python:python
# 必要なモジュールを追加
from langchain import VectorDBQA, OpenAI, VectorDBQAWithSourcesChain
from langchain.embeddings.openai import OpenAIEmbeddings
from langchain.vectorstores import Pinecone
```
```python:python
OPENAI_API_KEY = os.environ.get("OPENAIKEY")
embeddings = OpenAIEmbeddings(openai_api_key=OPENAI_API_KEY)
```
```python:python
#すでにあるindexをベクトルストアに設定
docsearch = Pinecone.from_existing_index("example-index", embeddings)
```

https://python.langchain.com/en/latest/modules/indexes/vectorstores/examples/pinecone.html

先程と同じように検索をかけてみる。
```python:python
query = "子供が生まれたら何をすればいいですか？"
docs = docsearch.similarity_search(query,k=3)
```
結果
```
[Document(page_content='出産後に必要な手続きは出生届・出生通知票の提出、児童手当、子ども医療費助成の申請等があります。\n\n▼詳しくはこちら\n（自治体HP内関連ページのURL）', metadata={'source': 'qes: 子どもが生まれたら、どんな手続きが必要ですか。; ans: 出産後に必要な手続きは出生届・出生通知票の提出、児童手当、子ども医療費助成の申請等があります。\n\n▼詳しくはこちら\n（自治体HP内関連ページのURL）'}), Document(page_content='子どもの手当・助成としては、児童手当と子ども医療費助成制度があります。\n\n▼詳しくはこちら\n（自治体HP内関連ページのURL）\n\n◆お問い合わせ\n（自治体の担当課等の名称）\n（電話番号）／（開庁時間）', metadata={'source': 'qes: 子どもが生まれたら、どのような手当がありますか。; ans: 子どもの手当・助成としては、児童手当と子ども医療費助成制度があります。\n\n▼詳しくはこちら\n（自治体HP内関連ページのURL）\n\n◆お問い合わせ\n（自治体の担当課等の名称）\n（電話番号）／（開庁時間）'}), Document(page_content='ご心配ですね。お子さんの成長や性格、生活環境や事情によって解決策が違います。まずは保健師にご相談ください。\nAAA保健相談所：（電話番号）（○時○分から○時○分）\nBBB保健相談所：（電話番号）（○時○分から○時○分）', metadata={'source': 'qes: 子どもが癇癪を起こします。; ans: ご心配ですね。お子さんの成長や性格、生活環境や事情によって解決策が違います。まずは保健師にご相談ください。\nAAA保健相談所：（電話番号）（○時○分から○時○分）\nBBB保健相談所：（電話番号）（○時○分から○時○分）'})]
```
おお、同じ様に検索できますね。
次は検索と回答の生成をやってみます。
先程は、`text-davinci-003`を使用したので`gpt-3.5-turbo`を設定してみます。
```python:python
# OpenAIのLLM設定
llm = OpenAI(
    openai_api_key=OPENAI_API_KEY,
    model_name='gpt-3.5-turbo',
    temperature=0.0
)

# VectorDBQAChainのセットアップ
chain = VectorDBQA.from_chain_type(
    llm=llm,
    chain_type="stuff",
    vectorstore=docsearch,
)

# 検索＆回答生成
chain.run("子供が生まれたら何をすればいいですか？")
```
```
出生届・出生通知票の提出、児童手当、子ども医療費助成の申請等が必要です。
また、子どもの手当・助成としては、児童手当と子ども医療費助成制度があります。
保健師に相談することもおすすめです。自治体の担当課等に問い合わせることもできます。
```
因みに回答のソースを確認する場合は。
```python:python
sources = VectorDBQAWithSourcesChain.from_chain_type(
    llm=llm,
    chain_type="stuff",
    vectorstore=docsearch
)

sources("子供が生まれたら何をすればいいですか？")
```
```
{'question': '子供が生まれたら何をすればいいですか？',
 'answer': "If you need to know what procedures are necessary after childbirth, you need to submit a birth certificate/notification, apply for child allowance, and apply for child medical expense subsidies. For more information, please visit the relevant page on the local government's website. As for child allowances and subsidies, there are child allowances and child medical expense subsidy systems. For more information, please visit the relevant page on the local government's website. If your child throws a tantrum or doesn't listen to you, you should consult a public health nurse first. AAA Public Health Consultation Center: (phone number) (from ○ o'clock to ○ o'clock) BBB Public Health Consultation Center: (phone number) (from ○ o'clock to ○ o'clock).\n",
 'sources': 'qes: 子どもが生まれたら、どんな手続きが必要ですか。; ans: 出産後に必要な手続きは出生届・出生通知票の提出、児童手当、子ども医療費助成の申請等があります。▼詳しくはこちら（自治体HP内関連ページのURL）;
...
```

## さいごに
Pineconeをさくっと使ってみましたが、非常に心地よく扱える印象を持ちました。
`ハイブリット検索`なる独自機能も搭載しているので、独自のデータを用意してやってみようと思います🙂

https://docs.pinecone.io/docs/ecommerce-search