ガチでプログラミング知識ゼロ!生成AIの力だけでLLMアプリケーションを作ってみたシリーズ-Azure OpenAI Service連携アプリ#1-
2022年から生成AIを活用して、プログラミング知識ゼロでアプリケーションを作成しています。これまで作成したものも含めてこれからNoteにあげていこうと思います。
プログラミング知識ゼロ、経験ゼロ!『ド素人の、ド素人による、ド素人のため』の実践体験です。
プログラミングの勉強も一切したことはありませんし、全て生成AIに任せて作成しているので有識者の方ような難しいことは一切分かりかねます。
プログラミング知識ゼロ、経験ゼロでもこんなアプリケーションを自作できたという自己満足の内容になっていますので、悪しからずご容赦ください。
もしかしてプログラミング知識や経験がゼロでも、自分が欲しいアプリケーションくらいは簡単に作れる時代になったんじゃない?
2022年にChatGPTを知り、実際に触れたときにスキルの民主化がされると感じた。
私はIT企業で営業職といて勤務してきたが、プログラミング知識も経験もない。業務上においてもプログラミングの能力は必要とされなかった。
当然プログラミングの勉強なんかしたこともないし、今もやっていない。
でも、「自分でアプリケーションを作れたらいいのにな」という願望は漠然と持ち続けていたし、でも、1からプログラミングの勉強はしたくない…
ネット上にはプログラムのサンプルコードはたくさん公開されているだろうが、プログラミング知識がなければサンプル以上のことは何もできない。
私が欲しいのは、「私の欲しい要件を満たすアプリケーション」だが、それを形にするのは、勉強キライですぐに成果が欲しい私にとっては途方もない道のりであると考えていた。(今もそう思ってる。)
そうして何年も葛藤し続け、全く行動に移せなかった自分にとっては、ChatGPTはまさに自分が行動を起こすに足るだけの革命的な存在として映った。
「これがあれば、プログラミング知識ゼロでもアプリケーション作れるんじゃないか?」という大きな期待と軽い気持ちでやってみることにしたのがきっかけ。
アプリケーションの開発環境??
プログラムコードはChatGPTで生成できるだろうなと思っていたが、よく考えたら、プログラムコードはどこに書いて、どのように実行すればいいのかさえ分からない…あぁ私はプログラムコード云々以前に、アプリケーションの開発に何が必要なのかすらも知らない状況であることに始めて気づいた…
「こんな知識でよくIT企業で務めてきたたものだ…」
これに関してもChatGPTが教えてくれた。どうやらVisual Studio Codeというものをインストールすればよいみたい。また日本語化するだの、Pythonの拡張をインストールするだの、手取り足取り教えてくれた。
環境構築の途中でプログラミング言語はPythonなるものがいいらしいとChatGPTが教えてくれた。(Pythonがなぜ良いのかは知らないが、知識ゼロなので完全にChatGPTに従うことしか私にはできない)
Azure OpenAI Serviceが使えたのでせっかくなら生成AIだけで、生成AIと連携するアプリケーションを作ってみよう
折しも、会社でAzure OpenAI Serviceを使うことができたので、せっかくなら「生成AIと連携するLLMアプリケーションを、生成AIだけでプログラムコードを生成して作ってみたら面白いのでは?」と思った。
環境構築してから簡単なアプリケーションを1つも作ったこともなかったので、簡易なアプリケーション作成から始めてもよかったのだろうが、開発環境の構築が完了し、ChatGPTが横にいることで万能感に浸っていたので、もうやってみたみ衝動に駆られて実践することにした。
アプリケーションの要件
手始めのLLMアプリケーションはシンプルなものにしようと考えた。
WebページやPDFからテキストを抽出
抽出したテキストとユーザーの任意のプロンプトをAzure OpenAIに連携し回答を生成
結果はチャット画面で表示され、テキストファイルへの出力が可能
とりあえずこのくらいの要件にして、後は作成してから欲しい機能を追加することにた。
結果的には作成過程があまりに楽しくて、チャット画面の表示内容を音声出力させる機能も追加してみたりした。
実際に作ったアプリケーション
せっかくNoteを始めてみたので、Noteを登録したときに案内された下記のサイトを使ってデモンストレーション!
ソースコード
こちらが、実際のソースコードです。
※Azure OpenAI Serviceのエンドポイント情報などは消しています。
※このソースコードにより生じた如何なる損害についても、一切の責任は負いかねます。あらかじめご了承ください。
import sys
import requests
import json
import io
import re
import markdown2
from bs4 import BeautifulSoup
from PyQt5.QtWidgets import (QApplication, QWidget, QVBoxLayout, QLineEdit, QPushButton, QTextEdit,
QPlainTextEdit, QHBoxLayout, QLabel, QDialog, QFileDialog, QTabWidget, QMessageBox,
QProgressDialog)
from PyQt5.QtGui import QFont, QPalette, QColor, QTextOption
from PyQt5.QtCore import Qt
import PyPDF2
from PyQt5.QtWidgets import QComboBox
import os
import pyttsx3
CONFIG_FILE = "config.txt"
def extract_text_from_url(url):
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"
}
try:
response = requests.get(url, headers=headers)
response.raise_for_status()
if 'application/pdf' in response.headers['Content-Type']:
with io.BytesIO(response.content) as open_pdf_file:
reader = PyPDF2.PdfReader(open_pdf_file)
return "\n".join(page.extract_text() for page in reader.pages)
else:
soup = BeautifulSoup(response.content, 'html.parser')
for script in soup(["script", "style"]):
script.extract()
return " ".join(soup.stripped_strings)
except requests.RequestException as e:
return "Error extracting text! Check URL or file path.\nテキスト抽出エラー!URLやファイルパスを確認してください"
def extract_text_from_pdf(pdf_path):
try:
with open(pdf_path, 'rb') as file:
reader = PyPDF2.PdfReader(file)
return "\n".join(page.extract_text() for page in reader.pages)
except Exception as e:
return "Error extracting text from PDF! Check file path.\nテキスト抽出エラー!ファイルパスを確認してください"
def send_to_gpt_api(content, user_message, api_key, model, temperature, max_tokens=4096):
headers = {
'Content-type': 'application/json',
'api-key': api_key
}
data = {
"messages": [
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": content},
{"role": "user", "content": user_message}
],
"temperature": temperature,
"max_tokens": max_tokens
}
if model == "GPT-3.5 Turbo":
api_url = "Azure OpenAI Serviceのエンドポイントを入力する"
elif model == "GPT-4o":
api_url = "Azure OpenAI Serviceのエンドポイントを入力する"
else:
api_url = "Azure OpenAI Serviceのエンドポイントを入力する"
response = requests.post(api_url, headers=headers, data=json.dumps(data))
if response.status_code == 200:
return response.json()["choices"][0]["message"]["content"]
else:
return "Error: " + response.text
class ChatGPTApp(QWidget):
def __init__(self, app_name="Doc_Talker"):
super().__init__()
self.app_name = app_name
self.config_file = f"{self.app_name}_config.txt"
self.api_key = self.load_api_key()
self.init_ui()
def load_api_key(self):
if os.path.exists(self.config_file):
with open(self.config_file, 'r') as file:
return file.read().strip()
return None
def init_ui(self):
self.setWindowTitle('DocTalker')
self.setFixedSize(910, 1000)
palette = QPalette()
palette.setColor(QPalette.Window, QColor(30, 30, 30))
self.setPalette(palette)
font_text = QFont()
font_text.setFamily("Yu Mincho")
font_text.setPointSize(11)
font_button = QFont("Yu Mincho", 10)
font_button.setBold(True)
font_combo = QFont("Yu Mincho", 10)
font_combo.setBold(True)
self.url_input = QLineEdit(self)
self.url_input.setPlaceholderText("Enter a Web or PDF URL...")
self.url_input.setFont(font_text)
self.url_input.setStyleSheet("background-color: #202020; color: #E0E0E0; border: 1px solid #505050; height: 40px;")
self.url_input.setFixedWidth(700)
self.send_button_web = QPushButton('Extract', self)
self.send_button_web.setFixedSize(110, 40)
self.send_button_web.setFont(font_button)
self.send_button_web.setStyleSheet("background-color: #303030; color: #E0E0E0; border: 1px solid #505050;")
self.send_button_web.clicked.connect(self.fetch_and_show_extracted_text_from_web)
self.send_button_pdf = QPushButton('📂', self)
self.send_button_pdf.setFixedSize(50, 40)
self.send_button_pdf.setFont(font_button)
self.send_button_pdf.setStyleSheet("background-color: #303030; color: #E0E0E0; border: 1px solid #505050; width: 150px; height: 40px;")
self.send_button_pdf.clicked.connect(self.fetch_and_show_extracted_text_from_pdf)
self.extracted_text_area = QPlainTextEdit(self)
self.extracted_text_area.setMinimumHeight(130)
self.extracted_text_area.setPlaceholderText("Extracted text or directly entered text will be shown here...")
self.extracted_text_area.setReadOnly(False)
self.extracted_text_area.setFont(font_text)
self.extracted_text_area.setStyleSheet("background-color: #202020; color: #E0E0E0; border: 1px solid #505050;")
self.extracted_text_area.textChanged.connect(self.update_char_count)
self.char_count_label = QLabel("0\nchars")
self.char_count_label.setAlignment(Qt.AlignCenter)
self.char_count_label.setFixedWidth(110)
self.char_count_label.setFont(font_button)
self.char_count_label.setStyleSheet("background-color: #303030; color: #E0E0E0; border: 1px solid #505050;")
self.char_count_layout = QHBoxLayout()
self.char_count_layout.addWidget(self.extracted_text_area)
self.char_count_layout.addWidget(self.char_count_label)
self.char_count_layout.setStretch(0, 1)
self.char_count_layout.setStretch(1, 0)
self.chat_area = QPlainTextEdit(self)
self.chat_area.setFixedHeight(400)
self.chat_area.setPlaceholderText("Chat will be displayed here...")
self.chat_area.setReadOnly(True)
self.chat_area.setFont(font_text)
self.chat_area.setStyleSheet("background-color: #202020; color: #E0E0E0; border: 1px solid #505050;")
self.message_input = QPlainTextEdit(self)
self.message_input.setFixedHeight(187)
self.message_input.setPlaceholderText("Type your message here...")
self.message_input.setFont(font_text)
self.message_input.setStyleSheet("background-color: #202020; color: #E0E0E0; border: 1px solid #505050;")
self.message_input.setWordWrapMode(QTextOption.WrapAnywhere)
self.model_selection_combo = QComboBox(self)
self.model_selection_combo.setFixedHeight(40)
self.model_selection_combo.setFont(font_combo)
self.model_selection_combo.addItems(["GPT-3.5 Turbo", "GPT-4o", "GPT-4 Turbo"])
self.model_selection_combo.setCurrentText("GPT-4 Turbo")
self.model_selection_combo.setStyleSheet("background-color: #303030; color: #E0E0E0; border: 1px solid #505050;")
self.temp_setting_combo = QComboBox(self)
self.temp_setting_combo.setFixedHeight(40)
self.temp_setting_combo.setFont(font_combo)
self.temp_setting_combo.addItems([str(i / 10) for i in range(1, 11)])
self.temp_setting_combo.setCurrentText("0.5")
self.temp_setting_combo.setStyleSheet("background-color: #303030; color: #E0E0E0; border: 1px solid #505050;")
self.max_tokens_value = 4096
self.send_msg_button = QPushButton('Send🚀', self)
self.send_msg_button.setFixedHeight(40)
self.send_msg_button.setFont(font_button)
self.send_msg_button.setStyleSheet("background-color: #303030; color: #E0E0E0; border: 1px solid #505050; width: 100px; height: 40px;")
self.send_msg_button.clicked.connect(self.send_message)
self.clear_button = QPushButton('Clear🗑️', self)
self.clear_button.setFont(font_button)
self.clear_button.setStyleSheet("background-color: #303030; color: #E0E0E0; border: 1px solid #505050; height: 40px;")
self.clear_button.clicked.connect(self.clear_all)
self.export_button = QPushButton('Export📤', self)
self.import_button = QPushButton('Import📥', self)
self.import_button.setFont(font_button)
self.import_button.setStyleSheet("background-color: #303030; color: #E0E0E0; border: 1px solid #505050; height: 40px;")
self.import_button.clicked.connect(self.import_from_text_file)
self.export_button.setFont(font_button)
self.export_button.setStyleSheet("background-color: #303030; color: #E0E0E0; border: 1px solid #505050; height: 40px;")
self.export_button.clicked.connect(self.export_to_text_file)
self.speak_button = QPushButton('Speak🔊', self)
self.speak_button.setFont(font_button)
self.speak_button.setStyleSheet("background-color: #303030; color: #E0E0E0; border: 1px solid #505050; height: 40px;")
self.speak_button.clicked.connect(self.text_to_speech)
self.settings_button = QPushButton('API Key🔑', self)
self.settings_button.setFont(font_button)
self.settings_button.setStyleSheet("background-color: #303030; color: #E0E0E0; border: 1px solid #505050; height: 40px;")
self.settings_button.clicked.connect(self.show_settings)
layout = QVBoxLayout()
layout.addSpacing(10)
layout_web = QHBoxLayout()
layout_web.addWidget(self.url_input)
layout_web.addWidget(self.send_button_pdf)
layout_web.addWidget(self.send_button_web)
layout_web.setStretch(0, 1)
layout_web.setStretch(1, 0)
layout_web.setStretch(2, 0)
layout.addLayout(layout_web)
layout.addLayout(self.char_count_layout)
layout.addWidget(self.chat_area)
msg_layout = QVBoxLayout()
msg_layout.addWidget(self.message_input)
button_layout = QHBoxLayout()
button_layout.addWidget(self.model_selection_combo)
button_layout.addWidget(self.temp_setting_combo)
button_layout.addWidget(self.send_msg_button)
bottom_layout = QHBoxLayout()
bottom_layout.addWidget(self.clear_button)
bottom_layout.addWidget(self.speak_button)
bottom_layout.addWidget(self.export_button)
bottom_layout.addWidget(self.import_button)
bottom_layout.addWidget(self.settings_button)
layout.addLayout(msg_layout)
layout.addLayout(button_layout)
layout.addLayout(bottom_layout)
char_count_layout = QHBoxLayout()
char_count_layout.addStretch()
char_count_layout.addWidget(self.char_count_label)
layout.addLayout(char_count_layout)
self.setLayout(layout)
self.move(QApplication.desktop().availableGeometry().topRight() - self.frameGeometry().topRight())
self.show()
def export_to_text_file(self):
self.show_processing_dialog()
chat_content = self.chat_area.toPlainText()
file_name, _ = QFileDialog.getSaveFileName(self, "Export to Text File", "", "Text Files (*.txt);;All Files (*)")
self.progress_dialog.close()
if file_name:
with open(file_name, 'w', encoding='utf-8') as file:
file.write("Extracted Text:\n")
file.write(self.extracted_text_area.toPlainText())
file.write("\n\nChat Log:\n")
file.write(self.chat_area.toPlainText())
def fetch_and_show_extracted_text_from_web(self):
self.show_processing_dialog()
url = self.url_input.text()
extracted_text = extract_text_from_url(url)
self.extracted_text_area.setPlainText(extracted_text)
self.initialize_chat_context(extracted_text)
model = self.model_selection_combo.currentText()
temperature = float(self.temp_setting_combo.currentText())
summary = send_to_gpt_api(extracted_text, "「これは~~について記載された内容です。」という文章にして要約してください。60文字以内。", self.api_key, model, temperature)
self.chat_area.appendHtml(f"<span style='color:#FFA500;'>AI: {summary}何かお手伝いできることはありますか?</span>")
self.progress_dialog.close()
def fetch_and_show_extracted_text_from_pdf(self):
self.show_processing_dialog()
pdf_path, _ = QFileDialog.getOpenFileName(self, "Select PDF", "", "PDF Files (*.pdf);;All Files (*)")
if pdf_path:
extracted_text = extract_text_from_pdf(pdf_path)
self.extracted_text_area.setPlainText(extracted_text)
self.initialize_chat_context(extracted_text)
model = self.model_selection_combo.currentText()
temperature = float(self.temp_setting_combo.currentText())
summary = send_to_gpt_api(extracted_text, "「これは~~について記載された内容です。」という文章にして要約してください。60文字以内。", self.api_key, model, temperature)
self.chat_area.appendHtml(f"<span style='color:#FFA500;'>AI: {summary}何かお手伝いできることはありますか?</span>")
self.progress_dialog.close()
def browse_pdf(self):
pdf_path, _ = QFileDialog.getOpenFileName(self, "Select PDF", "", "PDF Files (*.pdf);;All Files (*)")
self.pdf_input.setText(pdf_path)
def show_processing_dialog(self):
font_text = QFont()
font_text.setFamily("Yu Mincho")
font_text.setPointSize(10)
self.progress_dialog = QProgressDialog(self)
self.progress_dialog.setWindowFlags(Qt.Dialog | Qt.FramelessWindowHint)
self.progress_dialog.setRange(0, 0)
self.progress_dialog.setLabelText("Processing...")
self.progress_dialog.setFont(font_text)
self.progress_dialog.setCancelButton(None)
self.progress_dialog.setWindowModality(Qt.WindowModal)
self.progress_dialog.setAutoFillBackground(True)
self.progress_dialog.setFixedSize(400, 150)
self.progress_dialog.setStyleSheet("""
QProgressDialog {
background-color: #303030;
color: #E0E0E0;
border: 2px solid #505050;
}
QProgressDialog QLabel {
color: #E0E0E0;
font-size: 10pt;
padding-top: 25px;
}
QProgressBar {
max-height: 0px;
}
""")
self.progress_dialog.show()
QApplication.processEvents()
def initialize_chat_context(self, text):
model = self.model_selection_combo.currentText()
temperature = float(self.temp_setting_combo.currentText())
send_to_gpt_api(text, "この内容をインプットし、内容を正確に理解したうえで、以降のユーザーからのリクエストに対応せよ", self.api_key, model, temperature)
def send_message(self):
user_message = self.message_input.toPlainText()
temperature_value = float(self.temp_setting_combo.currentText())
model = self.model_selection_combo.currentText()
self.show_processing_dialog()
QApplication.processEvents()
response = send_to_gpt_api(self.extracted_text_area.toPlainText(), user_message, self.api_key, model, temperature_value)
self.progress_dialog.close()
formatted_response = self.format_response_to_html(response)
self.chat_area.appendHtml(f"<span style='color:#E0E0E0;'>User: {user_message}</span>")
self.chat_area.appendHtml(f"<span style='color:#FFA500;'>AI: {formatted_response}</span>")
self.message_input.clear()
def format_response_to_html(self, text):
formatted_text = text.replace('&', '&').replace('<', '<').replace('>', '>')
formatted_text = formatted_text.replace('\n', '<br>')
formatted_text = re.sub(' {2,}', lambda match: ' ' * len(match.group()), formatted_text)
return formatted_text
def clear_all(self):
self.url_input.clear()
self.extracted_text_area.clear()
self.chat_area.clear()
self.temp_setting_combo.setCurrentText("0.5")
self.model_selection_combo.setCurrentText("GPT-4 Turbo")
def show_settings(self):
dialog = SettingsDialog(self)
dialog.exec_()
def update_char_count(self):
char_count = len(self.extracted_text_area.toPlainText())
self.char_count_label.setText(f"{char_count}\nchars")
def text_to_speech(self):
msgBox = QMessageBox(self)
msgBox.setWindowFlags(Qt.Dialog | Qt.FramelessWindowHint)
palette = QPalette()
palette.setColor(QPalette.Window, QColor(30, 30, 30))
msgBox.setPalette(palette)
font_text = QFont()
font_text.setFamily("Yu Mincho")
font_text.setPointSize(10)
msgBox.setFont(font_text)
msgBox.setText("Play chat as audio? You cannot take actions during audio playback.\nチャットを音声再生しますか?再生中は操作できません")
playButton = msgBox.addButton("Yes", QMessageBox.YesRole)
cancelButton = msgBox.addButton("No", QMessageBox.NoRole)
msgBox.setStyleSheet("""
QMessageBox {
background-color: #303030;
color: #E0E0E0;
border: 2px solid #505050;
}
QLabel {
color: #E0E0E0;
}
QPushButton {
background-color: #303030;
color: #E0E0E0;
border: 1px solid #505050;
height: 40px;
width: 100px;
}
QPushButton:hover {
background-color: #404040;
}
QPushButton:pressed {
background-color: #505050;
}
""")
result = msgBox.exec_()
if result == 0:
engine = pyttsx3.init()
chat_text = self.chat_area.toPlainText()
engine.say(chat_text)
engine.runAndWait()
def import_from_text_file(self):
file_name, _ = QFileDialog.getOpenFileName(self, "Import from Text File", "", "Text Files (*.txt);;All Files (*)")
if not file_name:
return
msgBox = QMessageBox(self)
msgBox.setText("Importing will overwrite current data. Continue?\nインポートすると現在のデータが上書きされます!続行しますか?")
yesButton = msgBox.addButton("Yes", QMessageBox.YesRole)
noButton = msgBox.addButton("No", QMessageBox.NoRole)
result = msgBox.exec_()
if result == 0:
if file_name:
with open(file_name, 'r', encoding='utf-8') as file:
full_content = file.read()
extracted_marker = "Extracted Text:\n"
chat_marker = "\n\nChat Log:\n"
if extracted_marker in full_content and chat_marker in full_content:
extracted_text_start = full_content.index(extracted_marker) + len(extracted_marker)
extracted_text_end = full_content.index(chat_marker)
chat_log_start = extracted_text_end + len(chat_marker)
extracted_text = full_content[extracted_text_start:extracted_text_end].strip()
chat_log = full_content[chat_log_start:].strip()
self.extracted_text_area.setPlainText(extracted_text)
self.chat_area.setPlainText(chat_log)
else:
QMessageBox.warning(self, "Error", "The selected file format is incorrect or the file is corrupted.\n選択したファイル形式が不正またはファイルが壊れています")
class SettingsDialog(QDialog):
def __init__(self, parent=None):
super().__init__(parent)
self.setWindowFlags(Qt.Dialog | Qt.FramelessWindowHint)
palette = QPalette()
palette.setColor(QPalette.Window, QColor(30, 30, 30))
self.setPalette(palette)
font_text = QFont()
font_text.setFamily("Yu Mincho")
font_text.setPointSize(10)
self.setFixedSize(400, 150)
layout = QVBoxLayout()
self.label = QLabel("Enter API Key:")
self.label.setFont(font_text)
self.label.setStyleSheet("color: #E0E0E0;")
self.api_key_input = QLineEdit(self)
self.api_key_input.setText(self.parent().api_key if self.parent().api_key else "")
self.api_key_input.setFont(font_text)
self.api_key_input.setStyleSheet("background-color: #202020; color: #E0E0E0; border: 1px solid #505050; height: 40px;")
self.save_button = QPushButton("Save", self)
self.save_button.setFont(font_text)
self.save_button.setStyleSheet("background-color: #303030; color: #E0E0E0; border: 1px solid #505050; height: 40px;")
self.save_button.clicked.connect(self.save_api_key)
layout.addWidget(self.label)
layout.addWidget(self.api_key_input)
layout.addWidget(self.save_button)
self.setLayout(layout)
self.setStyleSheet("""
QDialog {
background-color: #303030;
color: #E0E0E0;
border: 2px solid #505050;
}
QLabel {
color: #E0E0E0;
}
""")
def save_api_key(self):
self.parent().api_key = self.api_key_input.text()
with open(self.parent().config_file, 'w') as file:
file.write(self.parent().api_key)
self.close()
if __name__ == '__main__':
app = QApplication(sys.argv)
window = ChatGPTApp()
window.show()
sys.exit(app.exec_())
この記事が気に入ったらサポートをしてみませんか?