(最終更新月:2023年4月)
当記事では、Pythonをベースとしたfletで作成したChatGPTアプリの概要やその作り方を解説しています。
コードまですべて解説しているので、ぜひ最後までご覧ください。
fletで作るChatGPTボットデスクトップアプリの概要
当記事でご紹介するアプリの仕組みはこちらのとおり。
fletアプリケーションを作り、ユーザー向けのフォームや表示テキストはもちろん、ChatGPTAPIとの連携をおこないます。
当記事の構成
当記事では、ChatGPTアプリの書き方を以下の2とおりで説明します。
- Pythonパッケージ「openai」を使用する方法
- JSONデータでAPIエンドポイントへリクエストする方法
どちらもご説明いたしますが、以下のアプリケーションは同じ仕様です。
次章よりそれぞれの方法を解説します。
フォーム入力画面
Pythonのfletでは、簡単に画面を作れます。
ChatGPTに投げるリクエストに含めたい値をフォームに入力して送信します。
回答を画面に表示する
レスポンスから必要な情報を抜き出して、画面上に表示させます。
Pythonパッケージ「openai」を使用する方法
openaiを使用する方法では、モデル「gpt-4 Trubo」を使用します。
以下の順で解説していきます。
- 必要なライブラリのインストール
- GPTとのやり取りを担うコード「gpts.py」
- fletでUI担当のコード「main.py」
必要なライブラリのインストール
仮想環境を立ち上げたら、以下のrequirements.txtを使って、パッケージをインストールしよう。
altgraph==0.17.4
annotated-types==0.6.0
anyio==3.7.1
arrow==1.3.0
binaryornot==0.4.4
certifi==2023.11.17
chardet==5.2.0
charset-normalizer==3.3.2
click==8.1.7
cookiecutter==2.5.0
distro==1.8.0
exceptiongroup==1.2.0
flet==0.12.2
flet-core==0.12.2
flet-runtime==0.12.2
h11==0.14.0
httpcore==0.17.3
httpx==0.24.1
idna==3.4
Jinja2==3.1.2
markdown-it-py==3.0.0
MarkupSafe==2.1.3
mdurl==0.1.2
oauthlib==3.2.2
openai==1.3.4
packaging==23.2
pydantic==2.5.1
pydantic_core==2.14.3
Pygments==2.17.2
pyinstaller==6.2.0
pyinstaller-hooks-contrib==2023.10
pyperclip==1.8.2
pypng==0.20220715.0
python-dateutil==2.8.2
python-dotenv==1.0.0
python-slugify==8.0.1
PyYAML==6.0.1
qrcode==7.4.2
repath==0.9.0
requests==2.31.0
rich==13.7.0
six==1.16.0
sniffio==1.3.0
text-unidecode==1.3
tqdm==4.66.1
types-python-dateutil==2.8.19.14
typing_extensions==4.8.0
urllib3==2.1.0
watchdog==3.0.0
websocket-client==1.6.4
websockets==11.0.3
インストールのコマンドはこちらです。
pip install -r requirements.txt
バージョンなどが異なると、思うように動作しない場合もあります。
こちらの記事に沿って進める場合はバージョンも揃えてください。
GPTとのやり取りを担うコード「gpts.py」
openaiを使用する方法は、APIエンドポイントのものより短くすみます。
今回は、以下の情報をnamedtupleで取り出す方法です。
- GPTからの返信テキスト「reply_text」
- 質問と返答にかかったトークン数「total_tokens」
- 返答のJSONデータ「json」
import os
from openai import OpenAI
from collections import namedtuple
def ask_gpt(role: str, msg: str):
client = OpenAI(
api_key=<ここにAPIキーを入れる>,
)
completion = client.chat.completions.create(
model="gpt-4-1106-preview",
messages=[
{"role": "system", "content": role},
{"role": "user", "content": msg}
]
)
REPLY = namedtuple('REPLY', [
'reply_text',
'total_tokens',
'json'
])
reply = REPLY(
completion.choices[0].message.content,
completion.usage.total_tokens,
completion.json()
)
return reply
APIキーについては適切な方法で、指定してください。
fletでUI担当のコード「main.py」
長いですが、以下のUIを担当するコードをすべて公開します。
import flet as ft
from gpts import ask_gpt
import pyperclip
GPT_MODEL = "gpt-4-1106-preview"
def main(page: ft.Page):
page.title = "GPTee"
page.scroll = ft.ScrollMode.AUTO
page.update()
role_input = ft.TextField(label="ロールを入力", prefix_text="あなたは、",
multiline=True, min_lines=2)
your_role = ft.Text("")
question_input = ft.TextField(
label="お願いすることを入力",
multiline=True,
min_lines=3,
max_lines=10)
your_question = ft.Text("")
output = ft.Text("", no_wrap=False, max_lines=10, selectable=True)
all_json = ft.Text("")
outputContainer = ft.Container(
content=output,
)
def output_copy_btn_clicked(e):
pyperclip.copy(output.value)
output_copy_btn = ft.ElevatedButton(
"回答をコピー",
icon=ft.icons.CONTENT_COPY,
disabled=True,
on_click=output_copy_btn_clicked)
def json_copy_btn_clicked(e):
pyperclip.copy(all_json.value)
json_copy_btn = ft.ElevatedButton(
"JSONデータをコピー",
icon=ft.icons.FILE_COPY,
disabled=True,
on_click=json_copy_btn_clicked)
def all_copy_btn_clicked(e):
all = role_input.value + "\n" + question_input.value + \
"\n" + output.value + "\n" + all_json.value
pyperclip.copy(all)
all_copy_btn = ft.ElevatedButton(
"全てコピー",
icon=ft.icons.FOLDER_COPY_SHARP,
disabled=True,
on_click=all_copy_btn_clicked)
def request_btn(e):
output.value = ""
output.update()
your_role.value = "あなたは" + role_input.value
your_role.update()
your_question.value = question_input.value
your_question.update()
reply = ask_gpt(
role=your_role.value,
msg=your_question.value
)
output.value = reply.reply_text
output.update()
all_json.value = reply.json
all_json.update()
output_copy_btn.disabled = False
output_copy_btn.update()
json_copy_btn.disabled = False
json_copy_btn.update()
all_copy_btn.disabled = False
all_copy_btn.update()
def switched(e):
if switch.value:
role_input.value = "優秀なライターで、与えられた見出しに対して、わかりやすい文章を100から200字程度で執筆します。"
your_role.value = role_input.value
role_input.read_only = True
role_input.update()
your_role.update()
else:
role_input.value = ""
your_role.value = ""
role_input.read_only = False
role_input.update()
your_role.update()
switch = ft.Switch(label="ライーターモード", value=False, on_change=switched)
page.add(
ft.Row(
[
role_input,
switch
],
width=page.window_width,
),
ft.Row(
[
question_input
],
width=page.window_width,
),
ft.Row(
[
ft.FilledButton(
"リクエスト",
on_click=request_btn
),
output_copy_btn,
json_copy_btn,
all_copy_btn
],
width=page.window_width,
),
ft.Row(
[
ft.Text("【ロール】"),
your_role
],
width=page.window_width,
),
ft.Row(
[
ft.Text("【依頼内容】"),
your_question
],
width=page.window_width,
),
outputContainer,
ft.Row(
[
ft.Text("【レスポンスデータ】"),
],
width=page.window_width,
),
all_json,
)
ft.app(target=main)
以下のコマンドでアプリが立ち上がります。
python main.py
アプリ化するには、requirements.txtにもある「pyinstaller」を使って、以下のコマンドを打ちましょう。
flet pack main.py
「main.py」は、当記事でご紹介しているファイル名。
皆さんの環境に合わせて、ファイル名を調整してください。
JSONデータでAPIエンドポイントへリクエストする方法
JSONデータを作成し、APIエンドポイントへリクエストを飛ばす方法を見ていきます。
モデルは「gpt-3.5-turbo」と古いものですが、参考にはなるはずです。
- リクエスト・レスポンスデータの概要
- 当方法の動画解説
- GPTとのやり取りを担うコード「gpts.py」
- fletでUI担当のコード「main.py」
リクエスト・レスポンスデータの概要
リクエストとレスポンスデータを見ておきましょう。
取り出したいデータがどこにあるかを把握できます。
- リクエストデータ
- レスポンスデータ
リクエストデータ
リクエストとして、以下の形式でJSONデータを飛ばします。
req_data = {
"model": "gpt-3.5-turbo",
"messages":[
{
"role": "system","content": <ロールはここにいれる>
},
{"role": "user", "content": <テキストはここに入れる>},
] ,
"max_tokens": 4000,
}
max_tokensは、最大値だけ気にしながらお好きな値を入れましょう。
指定しなくてもリクエストとしては問題ありません。
レスポンスデータ
レスポンスは、ネストがかかったJSONデータです。
以下のようになるので、必要なものを抜き出す必要があります。
{
"id": "chatcmpl-70RAZc0sTE6rrLZ2clciXJmWCdyiI",
"object": "chat.completion",
"created": 1680338795,
"model": "gpt-3.5-turbo-0301",
"usage": {
"prompt_tokens": 51,
"completion_tokens": 52,
"total_tokens": 103
},
"choices": [
{
"message": {
"role": "assistant",
"content": "import hmac\nimport hashlib\n\nmessage = \"\u3053\u308c\u306f\u30c6\u30b9\u30c8\u6587\u5b57\u5217\"\nkey = \"mysecretkey\".encode('utf-8')\ndigest = hmac.new(key, message.encode('utf-8'), hashlib.sha256).hexdigest()\n\nprint(digest)"
},
"finish_reason": "stop",
"index": 0
}
]
}
当方法の動画解説
YouTube動画でも作り方を一から解説。
トータル20分で一から作成可能です。
ChatGPTAPIの登録方法などが知りたい方はこちらをご覧ください。
GPTとのやり取りを担うコード「gpts.py」
動画上でもコードを公開していますが、アプリ内のコードはこちらです。
import requests, json,os
from dotenv import load_dotenv
#ここでAPIキーを保存している
env_path = "assets/.env"
load_dotenv(env_path)
GPT_TOKEN = os.environ.get("GPT_AUTH")
class GetCompletion():
def __init__(self, role, text):
self.EP = "https://api.openai.com/v1/chat/completions"
self.headers = {
"Content-Type": "application/json",
'Authorization': 'Bearer ' + GPT_TOKEN
}
self.role = role
self.text = text
def make_request_data(self):
req_data = {
"model": "gpt-3.5-turbo",
"messages":[
{
"role": "system","content": self.role
},
{"role": "user", "content": self.text},
] ,
"max_tokens": 4000,
}
json_data = json.dumps(req_data)
res = requests.post(self.EP, data=json_data, headers=self.headers)
status_code = res.status_code
data = json.loads(res.text.encode('utf-8'))
return data, status_code
def get_reply(self):
data, status_code = self.make_request_data()
indented_data = json.dumps(data, indent=4)
choices = data.get("choices")[0]
message = choices.get("message")
content = message.get("content")
msg = content.strip()
return indented_data, msg, status_code
fletでUI担当のコード「main.py」
import flet as ft
from gpts import GetCompletion
import pyperclip
from scraper import get_scraped_text
#pyperclip.copy(text)
def main(page: ft.Page):
page.title = "GPTee"
page.scroll = ft.ScrollMode.AUTO
page.update()
input_role = ft.TextField(label="ロールを入力", prefix_text="あなたは、", multiline=True, min_lines=2)
your_role = ft.Text("")
input_question = ft.TextField(label="お願いすることを入力", multiline=True, min_lines=3, max_lines=10)
your_question = ft.Text("")
output = ft.Text("", no_wrap=False, max_lines=10, selectable=True)
all_json = ft.Text("")
outputContainer=ft.Container(
content=output,
)
def output_copy_btn_clicked(e):
pyperclip.copy(output.value)
output_copy_btn = ft.ElevatedButton(
"回答をコピー",
icon=ft.icons.CONTENT_COPY,
disabled=True,
on_click=output_copy_btn_clicked)
def json_copy_btn_clicked(e):
pyperclip.copy(all_json.value)
json_copy_btn = ft.ElevatedButton(
"JSONデータをコピー",
icon=ft.icons.FILE_COPY,
disabled=True,
on_click=json_copy_btn_clicked)
def all_copy_btn_clicked(e):
all = input.value + "\n" + favor.value + "\n" + output.value + "\n" + all_json.value
pyperclip.copy(all)
all_copy_btn = ft.ElevatedButton(
"全てコピー",
icon=ft.icons.FOLDER_COPY_SHARP,
disabled=True,
on_click=all_copy_btn_clicked)
def request_btn(e):
output.value = ""
output.update()
your_role.value = "あなたは" + role_input.value
your_role.update()
your_question.value = question_input.value
your_question.update()
instance = GetCompletion(
role=your_role.value
text=your_question.value)
data, msg, status_code = instance.get_reply()
output.value = msg
output.update()
all_json.value = data
all_json.update()
output_copy_btn.disabled=False
output_copy_btn.update()
json_copy_btn.disabled=False
json_copy_btn.update()
all_copy_btn.disabled=False
all_copy_btn.update()
def switched(e):
if switch.value:
input.value = "優秀なライターで、与えられた見出しに対して、わかりやすい文章を100から200字程度で執筆します。"
inputed.value = input.value
input.read_only = True
input.update()
inputed.update()
else:
input.value = ""
inputed.value = ""
input.read_only = False
input.update()
inputed.update()
switch = ft.Switch(label="ライーターモード", value=False,on_change=switched)
page.add(
ft.Row(
[
input,
switch
],
width=page.window_width,
),
ft.Row(
[
favor
],
width=page.window_width,
),
ft.Row(
[
ft.FilledButton(
"リクエスト",
on_click=request_btn
),
output_copy_btn,
json_copy_btn,
all_copy_btn
],
width=page.window_width,
),
ft.Row(
[
ft.Text("【ロール】"),
inputed
],
width=page.window_width,
),
ft.Row(
[
ft.Text("【依頼内容】"),
favored
],
width=page.window_width,
),
outputContainer,
ft.Row(
[
ft.Text("【レスポンスデータ】"),
],
width=page.window_width,
),
all_json,
)
ft.app(target=main)
まとめ
fletはPythonをベースとしたデスクトップアプリなどが簡単に作れるフレームワークです。
Pythonに慣れている方ならかなり簡単に作れるはず。
ChatGPTだけでなく、いろいろと作れるのでぜひ試してみてください。