はじめに
GPT-4 Turbo(128K)早く使いたいですよね。
PythonでGPT-4 Turbo(gpt-4-1106-preview)を使用したシンプルな対話型AIチャットボットを作成しました。
応用編として、いくつか機能追加したソースも上げておきますので、皆様のお役に立てれば幸いです。
OpenAI API Reference
ReadMe (OpenAI Python API library)
ソースコードについて
ソース概要
-
basic_chatbot.py
- 物凄くシンプルな(コンソール上での)対話型AIチャットボット
- チャットを終えるには
exit
と入力すれば抜けられます
-
chatbot.py
- basic_chatbot.py を応用して、以下の機能追加したものになります
- カラーテキスト出力
- 通信中のインジケーター表示
- 通信中の応答時間計測
- ファイルのプリローディング機能(文字数計測付き)
- 環境変数とコマンドライン引数による設定管理
- チャット履歴の保存(デバッグ用)
- 役割に応じたクラス分け(それっぽく)
- basic_chatbot.py を応用して、以下の機能追加したものになります
注意点
- 本ツールでは、チャットボットとの対話毎に全ての対話履歴を(OpenAI APIで)送信することで対話を成立させています。
- そのため、対話回数が増えると(一度に送るトークン数が増えるため)、通信料金がそれなりにかかるので注意して下さい。
- 利用する際には、定期的に(exitで)チャットを終了して、対話セッションをリセットすることをお勧めします。(ツールを再起動すれば、対話履歴もリセットされます)
chatbot.py の利用イメージ
ソースコード
- シンプル版
from openai import OpenAI
def main():
client = OpenAI(
# api_key="YOUR_OPENAI_API_KEY", # Replace YOUR_OPENAI_API_KEY
)
messages = []
while True:
user_input = input("\n[Input](or 'exit'): ")
if user_input.lower() == "exit":
break
messages.append({"role": "user", "content": user_input})
completion = client.chat.completions.create(
model="gpt-4-1106-preview",
messages=messages,
)
bot_response = completion.choices[0].message.content
messages.append({"role": "assistant", "content": bot_response})
print(bot_response)
if __name__ == "__main__":
main()
- 応用版
import argparse
import json
import os
import readline
import threading
import time
from openai import OpenAI
class ANSIColor:
RED = "\033[31m"
GREEN = "\033[32m"
BLUE = "\033[34m"
RESET = "\033[0m"
BOLD = '\033[1m'
class Color:
USER = ANSIColor.RED
SYSTEM = ANSIColor.BLUE
BOT = ANSIColor.GREEN
class Role:
USER = "user"
ASSISTANT = "assistant"
SYSTEM = "system"
class OpenAIChatBot:
def __init__(self, model_name="gpt-4-1106-preview"):
self.client = OpenAI()
self.model_name = model_name
self.messages = []
def add_message(self, role, content):
self.messages.append({"role": role, "content": content})
def get_bot_response(self, user_input):
try:
self.add_message(Role.USER, user_input)
completion = self.client.chat.completions.create(
model=self.model_name,
messages=self.messages,
)
bot_response = completion.choices[0].message.content
self.add_message(Role.ASSISTANT, bot_response)
return bot_response
except Exception as e:
print(f"{ANSIColor.RED}[Error]: {e}{ANSIColor.RESET}")
return ""
class PreLoader:
def __init__(self, folder_path, extensions):
self.folder_path = folder_path
self.extensions = extensions
def combine_files(self):
combined_text = ""
for root, dirs, files in os.walk(self.folder_path):
for file in files:
if any(file.endswith(ext) for ext in self.extensions):
with open(os.path.join(root, file), 'r') as infile:
combined_text += f"// Begin of {file}\n{infile.read()}\n// End of {file}\n\n"
return combined_text
class ChatLogger:
def __init__(self, history_file='chatbot_history.json'):
self.history_file = history_file
def save_history(self, messages):
with open(self.history_file, 'w') as file:
json.dump(messages, file, ensure_ascii=False, indent=4)
def print(self, color, message):
print(f"\n{ANSIColor.BOLD}{color}{message}{ANSIColor.RESET}")
class Indicator:
def __init__(self, sleep_time=0.2):
self.sleep_time = sleep_time
self.indicator_thread = threading.Thread(target=self.show_indicator)
self.indicator_active = False
self.indicators = ["-", "\\", "|", "/"]
def start(self):
self.indicator_active = True
if not self.indicator_thread.is_alive():
self.indicator_thread = threading.Thread(target=self.show_indicator)
self.indicator_thread.start()
def stop(self):
self.indicator_active = False
if self.indicator_thread.is_alive():
self.indicator_thread.join()
def show_indicator(self):
elapsed_time = 0.0
indicator_msg = ""
i = 0
while self.indicator_active:
indicator_msg = f"{self.indicators[i % len(self.indicators)]} [Waiting Time]: {elapsed_time:.1f} seconds"
print(f"\r{ANSIColor.BOLD}{Color.SYSTEM}{indicator_msg}{ANSIColor.RESET}", end="")
time.sleep(self.sleep_time)
elapsed_time += self.sleep_time
i += 1
print("\r" + " " * len(indicator_msg) + "\r", end="") # Clear Indicator
class Configuration:
def __init__(self):
self.api_key = self.get_env_variable("OPENAI_API_KEY")
self.args = self.parse_command_line_arguments()
def get_env_variable(self, var_name):
try:
return os.environ[var_name]
except KeyError:
raise SystemExit(f"{ANSIColor.RED}[Error]: Environment variable '{var_name}' is not set.{ANSIColor.RESET}")
def parse_command_line_arguments(self):
parser = argparse.ArgumentParser(description='Terminal interface for OpenAI Chatbot')
parser.add_argument('--use-preload', action='store_true',
help='Please specify if you want to preload a specific file.')
parser.add_argument('--path', type=str, default=".",
help='Path to the folder for preloading. Default is the current directory.[Example]: --path /path/to/directory')
parser.add_argument('--ext', nargs='*', default=[".py"],
help='Specify multiple file extensions for preloading, separated by spaces. Default is ".py". [Example]: --ext .py .md .txt')
return parser.parse_args()
class Chatbot:
def __init__(self):
self.config = Configuration()
self.chatbot = OpenAIChatBot()
self.preloader = PreLoader(folder_path=self.config.args.path, extensions=self.config.args.ext)
self.logger = ChatLogger()
self.indicator = Indicator()
def wake_up(self):
self.preload_if_needed()
while True:
if self.start_chat() == "exit":
break
def preload_if_needed(self):
if self.config.args.use_preload:
preload_text = self.preloader.combine_files()
if preload_text:
self.chatbot.add_message(Role.SYSTEM, preload_text)
self.logger.print(Color.SYSTEM, f"[Preloaded char count]: {len(preload_text):,}")
def start_chat(self):
# Waiting User Input
self.logger.print(Color.USER, "[User Input](or 'exit'): ")
user_input = input().strip()
if user_input.lower() == "exit":
return user_input
elif user_input == "":
return user_input
# Start Log
start_time = time.time()
self.logger.print(Color.SYSTEM, "[Bot Output]: ")
# Get Response
self.indicator.start()
bot_response = self.chatbot.get_bot_response(user_input)
self.indicator.stop()
self.logger.print(Color.BOT, bot_response)
# End Log
response_time = time.time() - start_time
self.logger.print(Color.SYSTEM, f"[Response Time]: {response_time:.2f} seconds")
# Save History
self.logger.save_history(self.chatbot.messages)
def main():
chatbot = Chatbot()
chatbot.wake_up()
if __name__ == "__main__":
main()
クラス概要
chatbot.py:
-
OpenAIChatBot
クラス:OpenAI APIを使用してユーザーの入力を処理し、ボットの応答を生成する。 -
ANSIColor
,Color
,Role
クラス:ターミナルのテキスト出力に色とスタイルを付けるための定義を含んでいる。 -
PreLoader
クラス:特定の拡張子を持つファイルを事前に読み込むための機能を提供する。 -
ChatLogger
クラス:チャット履歴をJSONファイルとして保存し、メッセージをカラフルに出力するメソッドを備えている。 -
Indicator
クラス:処理中のインジケーターをコンソールに表示するためのスレッドベースの機能を提供する。 -
Configuration
クラス:環境変数やコマンドライン引数を解析し、アプリケーション設定を管理する。 -
Chatbot
クラス:上記のすべての機能を組み合わせて実際のインタラクティブなチャットボットインタフェースを形成する。
ソースコードの使い方
事前準備
- OpenAIのAPIキーを取得
- OpenAIのWebサイトからアカウントを作成し、APIアクセスのためのキーを取得
- Pythonのインストール
- Python3以上
- OpenAI Pythonパッケージをインストール
- ターミナルで
pip install openai
を実行 - openaiのバージョンが 1.0 未満の場合は upgrade して下さい。
pip install --upgrade openai
- ターミナルで
- Pythonスクリプト(
basic_chatbot.py
orchatbot.py
)の準備- 必要なファイルをダウンロードまたはコピーして、実行環境に配置
- 必要に応じて事前読み込みしたいファイルの準備 (chatbot.py のオプション)
-
--use-preload
オプションを使用する場合は、プリロードするファイルを準備し、指定するフォルダーに配置
-
使い方
basic_chatbot.py の使い方
- OPENAI_API_KEY の設定
-
basic_chatbot.py
スクリプト内のYOUR_OPENAI_API_KEY
を、有効なOpenAI APIキーに置き換えて、コメントを外してください。(exportで指定済みの場合はこの作業は不要です) api_key = "YOUR_OPENAI_API_KEY"
-
- スクリプトの実行:
- ターミナルで basic_chatbot.py が保存されているディレクトリに移動します。
- ファイルのあるフォルダを右クリックして「フォルダに新規ターミナル」を選択すると簡単です。
-
python basic_chatbot.py
を実行して、チャットボットを起動します。
- チャットの開始:
- スクリプトが起動すると、
[Input](or 'exit'):
と表示されます。 - ユーザーはこのプロンプトにテキストを入力して、チャットボットに送信します。
- スクリプトが起動すると、
- チャットの終了:
-
exit
と入力すると、チャットボットが終了します。
-
ツールを起動するたびに新しいチャットセッションが開始され、前のセッションの履歴は保持されません。
chatbot.py の使い方
- 環境変数の設定:
-
OPENAI_API_KEY
という環境変数にOpenAIのAPIキーを設定します。 - 例:LinuxやMacの場合、ターミナルで
export OPENAI_API_KEY='your_api_key'
を実行します。
-
- スクリプトの実行:
- ターミナルで chatbot.py が保存されているディレクトリに移動します。
-
python chatbot.py
を実行して、チャットボットを起動します。
- プリロードオプションの使用(任意):
- 詳細は「コマンドライン引数のサンプル」参照
- 特定のファイルやディレクトリから情報をプリロードするには、追加のコマンドライン引数を使用します。
-
--use-preload
を指定してプリロード機能を有効にします。 -
--path
を指定して、プリロードするファイルがあるディレクトリを指定します。 -
--ext
を指定して、プリロードするファイルの拡張子を指定します。
- チャットの開始:
- スクリプトが起動すると、
[User Input](or 'exit'):
と表示されます。 - ユーザーはこのプロンプトにテキストを入力して、チャットボットに送信します。
- スクリプトが起動すると、
- チャットの終了:
-
exit
と入力すると、チャットボットが終了します。
-
- チャット履歴の保存:
- 自動的にチャットボットとの会話は、
chatbot_history.json
という名前のJSONファイルに保存されます。
- 自動的にチャットボットとの会話は、
プリロードオプションを利用する場合は、読み込み過ぎないように注意して下さい。(一応プリロードをした文字数カウントは出力しているので、128Kを過ぎないように、自己責任でお願いします)
コマンドライン引数のサンプル
- プリロード機能を使わずにスクリプトを実行する例:
python chatbot.py
- 特定のディレクトリから
.md
ファイルをプリロードする例:python chatbot.py --use-preload --path /path/to/directory --ext .md
- 上記コマンドでは、
--use-preload
でプリロード機能を有効にし、--path
でプリロードするファイルが存在するディレクトリを指定し、--ext
でプリロードするファイル拡張子(ここでは.md
)を指定しています。
- さまざまな種類の拡張子のファイルをプリロードする例:
python chatbot.py --use-preload --path /path/to/directory --ext .py .txt .md
- このコマンドにより、
.py
、.txt
、.md
のファイルがプリロードされます。
- 現在のディレクトリから
.py
をプリロードするだけの例:(デバッグ用)python chatbot.py --use-preload
-
--path
と--ext
を指定しないと、(デフォルト値の)現在のディレクトリから.py
のファイルをプリロードすることになります。
/path/to/directory
は実際のファイルのあるディレクトリパスに置き換えてください。
利用時の注意点
-
OpenAIのAPIキーの扱い: OpenAIのAPIキーは秘密情報です。それをソースコード内で直接記述したり、他人と共有したりするべきではありません。APIキーは環境変数や別の設定ファイルから読み込むようにし、決してリポジトリにコミットしないでください。
-
API利用料金: OpenAIのAPIを利用する際には使用量に応じて料金がかかります。無料枠を超えると課金されるので、使用量を監視し、予算を超えないように計画してください。
-
モデル選択: 利用するモデルはデフォルトで"gpt-4-1106-preview"に設定されています。これはOpenAIが提供するモデルの1つであり、将来的には更新されたり、利用できなくなったりする可能性があります。利用可能なモデルとその特徴を理解し、目的に合ったものを選択してください。
-
レートリミット: OpenAI APIはリクエストのレートリミットが設定されています。連続したリクエストを大量に送ると、一時的に利用制限を受ける可能性があります。
-
セキュリティ: チャットボットを公開する場合は、入力内容のバリデーションやエスケープ処理を確実に行い、悪意のある入力によってシステムが危険にさらされないように対策しましょう。
-
エラーハンドリング: ソースコード内でAPIのレスポンスが失敗した場合の例外処理が行われていますが、エラーの原因に応じた詳細な対処方法やリトライロジックを実装することが推奨されます。
-
プリロードコード:
PreLoader
クラスがファイルを読み込んでプリロードする機能を持っていますが、大量のデータを読み込む際にはメモリ容量に注意し、システムリソースを過度に消費しないようにしてください。 -
非同期動作への対応: インジケーターの表示などの非同期動作が含まれています。クラッシュや不測の動作をすることがあっても許してください。
-
改変可能性: ソースコードは改変を行うことができますが、OpenAIのライセンス条項やガイドラインを遵守し、適切なライセンスの範囲内で改変や再配布を行うようにしてください。
-
別のコマンドライン引数: コマンドライン引数を使用してプリロードの設定やパスの指定などを行います。これらの引数に不正な値を指定すると、期待しない動作やエラーにつながる場合があるので注意が必要です。
参考サイト
上記の素晴らしい記事を参考にさせて頂きました。誠に有難うございます!
おわりに
この記事とソースコードは、本ツールを利用してbot君と対話しながら作成したものになります。
ある程度の手動調整は必要でしたが、大部分はbot君が作成してくれました。
AIの力は凄いですね。皆様も楽しみながらご利用頂けると大変嬉しく思います。