LoginSignup
0
3

More than 1 year has passed since last update.

ChatGPT APIを使った問答をチャットUIを使って実現する方法

Last updated at Posted at 2023-05-30

はじめに

前回はRedmineに埋もれたナレッジをGPTを活用して再利用する実験を行った
今回はそのやり取りをChat形式で行えるUIを作成してみる

注意点:

  • 下記のQiitaの記事の内容を前提とする
  • 動作することを確認するためのサンプルプログラムなので複数人が同時に問い合わせることは考慮しない

結論

スクリーンショットのようなチャットのUIを使ってknowledgebaseの内容についてGPTに問い合わせできることを確認した。

スクリーンショット 2023-05-29 9.44.19.png

課題

GPTが回答したテキストデータをそのまま出力しているため回答が読み辛い
GPTの回答を待っている時間が長いため辛い

用意するもの

本記事ではチャットのUIをローカルのPCで動作させたい。Google Colabを利用せず、M1 Macbook Pro 上の python の環境で動作確認を行った

$ /opt/homebrew/bin/python3 --version                                             
Python 3.11.3

OpenAI API キー : https://openai.com/blog/openai-api

実験に利用したサンプルプログラム

GitHubにサンプルプログラムを保存しておく

VectorStore に格納したデータをファイルに保存

csv形式のファイルに保存しておいたredmineknowledgebaseのデータをembeddingしてvectorstoreに格納し、再利用しやすいようにファイルに保存する

vectorstore_persist_csv.py
import os
import sys
import json

import openai
import logging

from langchain.embeddings.openai import OpenAIEmbeddings
from langchain.vectorstores import Chroma
from langchain.chat_models import ChatOpenAI
from langchain.document_loaders.csv_loader import CSVLoader
from langchain.text_splitter import CharacterTextSplitter

# debug config
logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s:%(name)s - %(message)s")

# load config
args = sys.argv
for i in args:
    logging.debug(i)
    
config_file = os.path.dirname(__file__) + "/config.json" if len(args) <=1 else args[1]

logging.debug(config_file)
with open(config_file, 'r') as file:
    config = json.load(file)

for key, value in config.items():
    logging.debug(key +":" + value)

# config
openai_api_key = config['openai_api_key']
file_path = os.path.dirname(__file__) + '/'+ config['file']
persist_dir =os.path.dirname(__file__) + '/'+ config['persist_directory']

# load 
loader = CSVLoader(file_path=file_path,  source_column=config.get('source_column'))
data = loader.load()

text_splitter = CharacterTextSplitter(
    separator = "\n",
    chunk_size = 1000,
    chunk_overlap  = 100,
)

docs = text_splitter.split_documents(data)
logging.debug(docs[0])

# Preprocessing for using Open AI
os.environ["OPENAI_API_KEY"] = openai_api_key
openai.api_key = os.getenv("OPENAI_API_KEY")
llm = ChatOpenAI(temperature=0, model_name="gpt-3.5-turbo")
embeddings = OpenAIEmbeddings()

# Stores information about the split text in a vector store
vectorstore = Chroma.from_documents(docs, embedding=embeddings, persist_directory=persist_dir)
vectorstore.persist()
config.json のサンプル (自分の環境に合わせて修正)
{
    "openai_api_key": "sk-...",
    "file": "...csv",
    "persist_directory": "db",
    "source_column": "source"
}

OpenAIへの問い合わせ処理

チャットUIから渡されたテキストをベースにGPTに問い合わせを行いGPTからの回答をチャットUIに返す

  • ファイルに保存しておいたknowledgebaseの内容をロードする
  • ロードした内容についてGPTで問い合わせを行う
openai_chat.py
import os
import sys
import logging
import json

import openai

from langchain.embeddings.openai import OpenAIEmbeddings
from langchain.vectorstores import Chroma
from langchain.chat_models import ChatOpenAI
from langchain.chains import ConversationalRetrievalChain

# load config
def load_config():
    args = sys.argv
    config_file = os.path.dirname(__file__) + "/config.json" if len(args) <=1 else args[1]
    
    logging.info(config_file)
    with open(config_file, 'r') as file:
        config = json.load(file)

    return {
            "openai_api_key": config['openai_api_key'],
            "persist_dir": os.path.dirname(__file__) + '/'+ config['persist_directory']
        }

# Preprocessing for using OpenAI
def get_chain(config):
    os.environ["OPENAI_API_KEY"] = config["openai_api_key"]
    openai.api_key = os.getenv("OPENAI_API_KEY")
    llm = ChatOpenAI(temperature=0, model_name="gpt-3.5-turbo")
    embeddings = OpenAIEmbeddings()
    # Loading the persisted database from disk
    vectordb = Chroma(persist_directory=config["persist_dir"], embedding_function=embeddings)
    # IF for asking OpenAI
    return ConversationalRetrievalChain.from_llm(llm, vectordb.as_retriever(), return_source_documents=True)

def openai_qa(query, history=[]):
    csv_qa = get_chain(load_config())
    chat_history = history
    return csv_qa({"question": query, "chat_history": chat_history})

def main():
    # Question
    query = 'ラズパイでスクリーンショットを撮りたい'
    result = openai_qa(query)
    print(result["answer"])
    # Review the original set of texts related to the above responses.
    for doc in result['source_documents']:
        print(doc.page_content.split('\n', 1)[0])
        print(doc.metadata)


if __name__ == '__main__':
    logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s:%(name)s - %(message)s")    
    main()

チャットUIを用意

利用ライブラリ

チャットUI: chatux
Webサーバ: flask

WebサーバとチャットUI

ディレクトリ 説明
/ チャットUIをロード
/chat チャットUIとGPTを取り持つ
/icon/botPhoto チャットボット側のアイコン (フリー素材を利用)
app.py
from flask import Flask
from flask import render_template
from flask import send_file
from flask import request

import os
import io
import json
import logging
from gpt.openai_chat import openai_qa

app = Flask(__name__)
chat_history=[]

# loading chat client
@app.route('/')
def chat_client():
    return render_template('chat_client.html')

# icon botPhoto
@app.route('/icon/botPhoto')
def icon():
    icon = os.path.dirname(__file__) + '/'+ 'templates/botPhoto.png'
    logging.debug("icon:" + icon)
    return send_file(icon, mimetype='image/png')

@app.route('/chat')
def chat():
    # preprocess
    query = request.args.get('text')
    callback = request.args.get('callback')
    # query to openai
    chat_history = []
    result = openai_qa(query, chat_history)
    answer = result["answer"]
    # debug
    logging.debug("answer: " + answer)
    for doc in result['source_documents']:
        logging.debug(doc.page_content.split('\n', 1)[0])
        logging.debug(doc.metadata)
    # answer to client
    res = callback + '(' + json.dumps({
                "output":[  
                    {  
                        "type":"text",
                        "value":answer
                    }
                    ]
            }) + ')'
    logging.debug(res)
    return res

def main():
    logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s:%(name)s - %(message)s")   
    app.debug = True  
    app.run()

if __name__ == '__main__':
    main()
chat_client.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
    <title>エコーボット</title>
</head>
<body>
<script src="https://riversun.github.io/chatux/chatux.min.js"></script>
<script>
    // original source code
    // license: MIT
    // https://github.com/riversun/chatux-examples-ja
    const chatux = new ChatUx();

    //ChatUXの初期化パラメータ
    const initParam =
        {
            renderMode: 'auto',
            api: {
                //echo chat server
                endpoint: 'http://127.0.0.1:5000/chat',
                method: 'GET',
                dataType: 'jsonp',
                escapeUserInput: false
            },
            bot: {
                // original https://www.flaticon.com/free-icon/grinning_2171967?term=shiba+inu&page=1&position=8&origin=search&related_id=2171967
                // license : Free for personal and commercial use with attribution. More info
                botPhoto: 'http://127.0.0.1:5000/icon/botPhoto',
                humanPhoto: null,
                widget: {
                    sendLabel: '送信',
                    placeHolder: '何か話しかけてみてください'
                }
            },
            window: {
                title: 'エコーボット',
                infoUrl: 'https://github.com/riversun/chatux'
            }
        };
    chatux.init(initParam);
    chatux.start(true);

</script>
</body>
</html>

実験

knowledgebaseのデータをembeddingしてvectorstoreに格納したデータをファイルに保存

$ python3 ./gpt/vectorstore_persist_csv.py
$ ls gpt/db
chroma-collections.parquet	chroma-embeddings.parquet	index

ファイルに保存したvectorstoreのデータをロードしてGPTに問い合わせるUIを実行
Webブラウザで http://127.0.0.1:5000 にアクセスする

$ python3 app.py  
* Serving Flask app 'app'
* Debug mode: on
INFO:werkzeug - WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
* Running on http://127.0.0.1:5000
0
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
3