著者:遠藤幸太郎
AIと自然言語処理技術の発展により、「ChatGPT」に代表される、人間と自然な形で対話できる会話型AIが注目を集めています。今回は、PythonでAIチャットボットアプリを開発していきます。米Google社の「Gemini API」を利用し、対話だけでなく、音声や映像ファイルをアップロードして分析できるようにします。
シェルスクリプトマガジン Vol.95は以下のリンク先でご購入できます。![]()
![]()
図3 「win.py」ファイルに記述するコード
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 基本的なチャットボットアプリのコード
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の③で示す箇所に挿入するコード
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の④で示す箇所に挿入するコード
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の⑥で示す箇所に挿入するコード
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の⑪で示す箇所に挿入するコード
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