著者:遠藤幸太郎
AIと自然言語処理技術の発展により、「ChatGPT」に代表される、人間と自然な形で対話できる会話型AIが注目を集めています。今回は、PythonでAIチャットボットアプリを開発していきます。米Google社の「Gemini API」を利用し、対話だけでなく、音声や映像ファイルをアップロードして分析できるようにします。
シェルスクリプトマガジン Vol.95は以下のリンク先でご購入できます。
図3 「win.py」ファイルに記述するコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
from PySide6.QtWidgets import ( QApplication, QMainWindow, QLabel ) from PySide6.QtCore import Qt import sys class MainWindow(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("ウィンドウタイトル") self.setGeometry(100, 100, 400, 300) label = QLabel("テキスト", self) label.setGeometry(0, 0, 400, 300) label.setAlignment(Qt.AlignCenter) if __name__ == "__main__": app = QApplication(sys.argv) window = MainWindow() window.show() sys.exit(app.exec()) |
図5 基本的なチャットボットアプリのコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 |
from PySide6.QtWidgets import ( QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QPushButton, QTextEdit, QLineEdit, ① ) from PySide6.QtCore import Qt, QThread, Signal import os, sys, re ② import google.generativeai as genai from dotenv import load_dotenv from markdown2 import markdown MAX_HISTORY_LENGTH = 10 class ChatWindow(QMainWindow): def __init__(self): super().__init__() self.history = [] self.setWindowTitle("ChatBot") self.setGeometry(100, 100, 600, 400) self.central_widget = QWidget() self.setCentralWidget(self.central_widget) self.main_layout = QVBoxLayout() self.chat_layout = QVBoxLayout() self.input_layout = QHBoxLayout() self.file_layout = QHBoxLayout() self.chat_display = QTextEdit() self.chat_display.setReadOnly(True) self.main_layout.addWidget(self.chat_display) self.input_box = QLineEdit() self.input_box.setPlaceholderText("ここにメッセージを入力") self.input_layout.addWidget(self.input_box) self.send_button = QPushButton("送信") self.send_button.clicked.connect(self.handle_send) self.input_layout.addWidget(self.send_button) self.main_layout.addLayout(self.input_layout) ③ self.central_widget.setLayout(self.main_layout) def handle_send(self): user_message = self.input_box.text().strip() if not user_message: return self.send_button.setEnabled(False) self.chat_display.append(f"<b>あなた:</b> {user_message}") ④ self.input_box.clear() self.response_thread = BotResponseThread( user_message, self.history ⑤ ) self.response_thread.response_ready.connect( self.display_bot_response ) self.response_thread.start() def safe_markdown(self, text): lines = text.splitlines() formatted_lines = [] for i, line in enumerate(lines): if re.match(r"^\s*(\d+\.\s+|[-*+]\s+)", line) and ( i + 1 < len(lines) and lines[i + 1].strip() ): formatted_lines.append(line) formatted_lines.append("") else: formatted_lines.append(line) formatted_text = "\n".join(formatted_lines) return markdown(formatted_text) def display_bot_response(self, response_text): html_content = self.safe_markdown(response_text) self.chat_display.append(f"<b>Bot:</b> {html_content}") self.send_button.setEnabled(True) ⑥ class BotResponseThread(QThread): response_ready = Signal(str) def __init__(self, user_message, history): ⑦ super().__init__() self.user_message = user_message ⑧ self.history = history def run(self): response_text = self.get_bot_response(self.user_message) ⑨ self.response_ready.emit(response_text) def get_bot_response(self, message): ⑩ input_text = message self.history.append({"role": "user", "parts": [input_text]}) ⑪ response = model.generate_content(self.history) self.history.append(response.candidates[0].content) if len(self.history) > MAX_HISTORY_LENGTH: self.history.pop(0) return response.text def initialize_genai(): load_dotenv() api_key = os.getenv("GOOGLE_API_KEY") genai.configure(api_key=api_key) return genai.GenerativeModel("gemini-1.5-flash") if __name__ == "__main__": model = initialize_genai() app = QApplication(sys.argv) window = ChatWindow() window.show() sys.exit(app.exec()) |
図9 図5の③で示す箇所に挿入するコード
1 2 3 4 5 6 7 8 |
self.file_label = QLabel("ファイルが選択されていません") self.file_label.setAlignment(Qt.AlignLeft) self.file_layout.addWidget(self.file_label) self.upload_button = QPushButton("ファイルを選択") self.upload_button.clicked.connect(self.handle_file_selection) self.file_layout.addWidget(self.upload_button) self.main_layout.addLayout(self.file_layout) self.selected_file_path = None |
図10 図5の④で示す箇所に挿入するコード
1 2 3 4 5 6 7 8 9 |
if self.selected_file_path: self.chat_display.append( f"<b>添付ファイル:</b> {self.selected_file_path}" ) file_path = self.selected_file_path self.selected_file_path = None self.file_label.setText("ファイルが選択されていません") else: file_path = None |
図11 図5の⑥で示す箇所に挿入するコード
1 2 3 4 5 6 7 8 9 10 |
def handle_file_selection(self): file_dialog = QFileDialog() file_path, _ = file_dialog.getOpenFileName( self, "ファイルを選択" ) if file_path: self.selected_file_path = file_path self.file_label.setText( f"選択したファイル: {file_path.split('/')[-1]}" ) |
図12 図5の⑪で示す箇所に挿入するコード
1 2 3 4 5 6 7 8 9 10 |
if file_path: attached_file = genai.upload_file(path=file_path) while attached_file.state.name == "PROCESSING": time.sleep(10) attached_file = genai.get_file(attached_file.name) self.history.append( {"role": "user", "parts": [attached_file]} ) else: pass |