chatGPTにCSVデータの情報を元に回答させてみる

Llamaindex を用いてCSVデータからchatGPTちゃんに回答させてみようと思います。
とりあえずやってみた系の記事なので、雑ですがご了承下さい。

## 準備
今回は居酒屋的のメニュー的なものを適当に作りました。
```csv:csv
項目,タイトル,価格,説明
鴨料理,鴨フィレ肉のカシスソース,930円,鴨フィレ肉を浅めにソテーしロゼ色に。甘酸っぱいカシスと赤ワインのソースで。ピンクペッパーがとても合います。赤ワイン。
鴨料理,鴨刺し,860円,"皮面をパリッと焼き、肉にも焼き目をつけた状態をお刺身。"
鴨料理,鴨南蛮うどん,830円,"鴨と長ネギをごま油で炒め、味を出しています。
合わせだしも引いています。汁まで美味しい一品。小さめの為〆に最適です。"
馬料理,ユッケ,930円,"言わずと知れた生肉ユッケ。
...
```

以下のライブラリをインストールします。
```bash:bash
pip install -U llama-index
pip install -U langchain
```
llama-indexはEmbeddingsによる検索を簡単に実装できるPythonライブラリです。

https://gpt-index.readthedocs.io/en/latest/index.html

langchainはLlamaindexなどのツール群をOpenAI APIと連携できるライブラリです。

https://docs.langchain.com/docs/

こちらは色々とできそうですが詳細割愛します。
近々こちらにスコープして書いてみます。

## とりあえずやってみる

分割して記載していきます。
```python:python
import os
import logging
import sys

from llama_index import ServiceContext, GPTSimpleVectorIndex, LLMPredictor
from langchain.chat_models import ChatOpenAI
from llama_index.prompts.prompts import RefinePrompt, QuestionAnswerPrompt
from llama_index import download_loader

os.environ["OPENAI_API_KEY"] = "<OpenAIのAPIキー>"
```
```python:python
# ログレベルの設定
logging.basicConfig(stream=sys.stdout, level=logging.DEBUG, force=True)

# データの読み込み(CSV)
SimpleCSVReader = download_loader("SimpleCSVReader")

# データの読み込み
documents =  SimpleCSVReader().load_data(file="data.csv")
```

今回使用したSimpleCSVReaderについてはこちら

https://llamahub.ai/l/file-simple_csv

```python:python
# tempareture=0をしていて、正確性を最重視
# gpt-3.5-turbo を指定（デフォルトは davinci ）
# トークン数制限を500に
service_context = ServiceContext.from_defaults(
    llm_predictor = LLMPredictor(llm= ChatOpenAI(temperature=0,
                                                 model_name="gpt-3.5-turbo",
                                                 max_tokens=500)))

# 読み込んだcsvデータをもとに Embbeddings API を通信してベクター取得しIndexを生成
index = GPTSimpleVectorIndex.from_documents(documents, service_context=service_context)

# jsonでローカルに保存
index.save_to_disk("index.json")
```
ここまでで、csvに入っていたデータをベクトル化しました。

```python:python
# 質問文
qry ="ラーメンはありますか？"

#質問用のQAプロンプトを生成
QA_PROMPT_TMPL = (
    "私たちは以下の情報をコンテキスト情報として与えます。 \n"
    "---------------------\n"
    "{context_str}"
    "\n---------------------\n"
    "あなたはAIとして、この情報をもとに質問を日本語で答えます。前回と同じ回答の場合は同じ回答を行います。: {query_str}\n"
)
qa_prompt = QuestionAnswerPrompt(QA_PROMPT_TMPL)

#回答要求用のプロンプトを生成
REFINE_PROMPT = ("元の質問は次のとおりです: {query_str} \n"
    "既存の回答を提供しました: {existing_answer} \n"
    "既存の答えを洗練する必要があります \n"
    "(必要な場合のみ)以下にコンテキストを追加します。 \n"
    "------------\n"
    "{context_msg}\n"
    "------------\n"
    "新しいコンテキストを考慮して、元の答えをより良く洗練して質問に答えてください。\n"
    "コンテキストが役に立たない場合は、元の回答と同じものを返します。")

refine_prompt = RefinePrompt(REFINE_PROMPT)
```
ここではプロンプトの定義をしています。
日本語での回答を強制したかったのでこのようにしました。
公式ではこちらに記載されてます。

https://gpt-index.readthedocs.io/en/latest/how_to/customization/custom_prompts.html


```python:python
# 先程生成したベクトルファイルを読み込む
indexFile = GPTSimpleVectorIndex.load_from_disk("index/index.json", service_context=service_context)

# ベクトル検索 + Chat Completion API 実行 + 検索結果TOP3を返す
response = indexFile.query(qry,
                       text_qa_template=qa_prompt,
                       refine_template=refine_prompt,
                       similarity_top_k=3)
#結果確認
print(response)
```
結果
```
申し訳ありませんが、この情報にはラーメンの項目が含まれていません。
代わりに、焼酎割りやワインカクテル、ウィスキーやカクテル、
そして梅酒やソフトドリンクなどがあります。
ラーメンをお探しの場合は、他の店舗を探してみてください。
```

なんでラーメンないんだよっ！！
はい。でもその通りです。csvデータにラーメンは含んでません。

質問文を変えてみました。
```
# 質問文
qry ="さっぱりしたものが食べたいです。"
```
結果
```
当店では、さっぱりとしたメニューを豊富に取り揃えています。
鴨南蛮うどんや春菊のチョレギサラダ、梅水晶などがおすすめです。
また、サワーも白桃＆コラーゲンサワーや北海道ハスカップサワー、
アセロラ＆ヒアルロンサワーなど、様々な種類をご用意しております。
さらに、ハイボールや焼酎割り、本日のグラスワインもお楽しみいただけます。
焼酎は、（芋）大隅、（芋）金黒、（芋）農家の嫁、（麦）村正、（麦）吟麗玄海、（米）もっこす、（米）鳥飼、（泡盛）三年古酒 瑞泉など、多彩なラインナップを揃えています。
また、日本酒やウィスキー、カクテル、梅酒、ソフトドリンクも充実しています。
お好みに合わせてお選びください。
```
お酒のメニューを多くしてたから、やたら飲ませようとしてきます。
これがさっぱりしているお酒なのかは分かりませんが、まあ合ってそう。

## さいごに
とりあえずやってみた感じですが、簡単にそれっぽい回答を得ることはできました。
ただ公式通りやって動かない事もあったりで…進化が早い分すぐにパラメータが変わったりなんだりしそうですね。まだ実運用には怖いかな…

### 参考にさせて頂いた記事等
https://gpt-index.readthedocs.io/en/latest/index.html

https://qiita.com/hedgehog051/items/1872b731791e9b7702ee

https://stackoverflow.com/questions/75924179/llama-index-unexpected-keyword-argument-error-on-chatgpt-model-python