「AIを使ってなんかしろ」という上層部からのお達しに応えるべく、LlamaIndexを使ったチャットボットを作成してみました。
LlamaIndexにたどり着くまでにはLangchainやPineconeを調べていましたが、私の理解力では理想のチャットボットが作れず、LlamaIndexにたどり着きました。
本記事では、へーしゃのITスキルレベルに合わせるべく、UIの少ないチャットボットの作成を目的にしています。
参考
意外と簡単!GPTを使ってPDFとQ&Aできるアプリを作ってみた – YouTube
GitHub – nyanta012/pdf_chatbot
Llama Hub (llama-hub-ui.vercel.app)
Streaming – LlamaIndex 🦙 0.6.30 (gpt-index.readthedocs.io)
実装したい内容
・ドキュメントフォルダに入れたファイルに対して、回答するチャットボットを作りたい。
・グループウエア/ポータルサイトにiframeで載せるので、UIは極力少なくする。
・ChatGPT使ってる感は出したいのでStreamingはやりたい。
・従来のチャットボットのように特定の単語に反応する機能もつけておきたい。
作ったもの
画面はシンプルにUIを少なくなるようにしています。
ポータルサイトに載せる部分
<iframe src="ここにURL" style="width:100%;height:500px;margin-top:-0px;border:none;display:block;"></iframe>
以下のようなイメージでポータルサイトに表示します。
準備
.envファイルにOPENAI_API_KEYを記入します。
ORGANAZTION_ID=org-
OPENAI_API_KEY=sk-
pip listから関係ありそうなところを抜き出しておきます。
langchain 0.0.206
streamlit 1.23.1
llama-index 0.6.28
openai 0.27.8
ソースコード
import os
import shutil
from pathlib import Path
import streamlit as st
# 環境変数の読み込み
from dotenv import load_dotenv
load_dotenv()
import openai
openai.organization = os.environ["ORGANAZTION_ID"]
openai.api_key = os.environ["OPENAI_API_KEY"]
from llama_index import (
download_loader,
LLMPredictor,
GPTVectorStoreIndex,
ServiceContext,
QuestionAnswerPrompt,
StorageContext,
load_index_from_storage,
SimpleDirectoryReader
)
from langchain import OpenAI
from langchain.chat_models import ChatOpenAI
INPUT_DATA_DIR = "./input/"
INDEX_DATA_DIR = "./index/"
# ハンバーガーメニュー非表示
css = """
<style>
#MainMenu {visibility: hidden;}
</style>
"""
st.markdown(css, unsafe_allow_html=True)
os.makedirs(INPUT_DATA_DIR, exist_ok=True)
os.makedirs(INDEX_DATA_DIR, exist_ok=True)
class QAResponseGenerator:
def __init__(self, selected_model):
self.llm_predictor = LLMPredictor(llm=ChatOpenAI(streaming=True, temperature=0, model_name=selected_model))
self.QA_PROMPT_TMPL = (
"下記の情報が与えられています。 \n"
"---------------------\n"
"{context_str}"
"\n---------------------\n"
"この情報を参照して次の質問に答えてください: {query_str}\n"
)
self.service_context = ServiceContext.from_defaults(llm_predictor=self.llm_predictor)
def generate(self, question, result_area):
try:
# インデックスから
storage_context = StorageContext.from_defaults(persist_dir=f"{INDEX_DATA_DIR}{INPUT_DATA_DIR}")
index = load_index_from_storage(storage_context, service_context=self.service_context)
print("load existing file..")
except Exception as e:
print(e)
# ファイルから
documents = SimpleDirectoryReader(INPUT_DATA_DIR, recursive=True, exclude_hidden=True).load_data()
index = GPTVectorStoreIndex.from_documents(documents,service_context=self.service_context)
# インデックスの保存
index.storage_context.persist(persist_dir=f"{INDEX_DATA_DIR}{INPUT_DATA_DIR}")
engine = index.as_query_engine(streaming=True,
similarity_top_k=1,
text_qa_template=QuestionAnswerPrompt(self.QA_PROMPT_TMPL)
)
result = engine.query(question)
text = ''
for next_text in result.response_gen:
# "。"の後ろに改行を付与
if "。" in next_text:
next_text = next_text.replace("。","。 \n ")
text += next_text
result_area.write(text)
return text, result.get_formatted_sources(1000)
def main():
selected_model = "gpt-3.5-turbo"
choice = st.radio("質問相手を選択:", ["情シスBot", "情シスBot for Debug", "ChatGPT API"])
question = st.text_input("Your question", max_chars = 1024)
# メインの画面に質問送信ボタンを設定
submit_question = st.button("質問")
response_generator = QAResponseGenerator(selected_model)
# ボタンがクリックされた場合の処理
if submit_question:
result_area = st.empty()
info_area = st.empty()
if question: # 質問が入力されている場合
if choice == "ChatGPT API":
completion = openai.ChatCompletion.create(
model='gpt-3.5-turbo',
messages=[{"role": "user", "content": question}],
stream=True,
)
text = ''
for chunk in completion:
next = chunk['choices'][0]['delta'].get('content', '')
text += next
result_area.write(text)
else:
if "テスト" in question:
result_area.write("テストです")
else:
response, source = response_generator.generate(question,result_area)
if choice == "情シスBot for Debug":
info = f"\n\n参照した情報は次の通りです:\n{source}"
info_area.write(info)
if __name__ == "__main__":
main()