リフレックスを使って、純粋なPythonでChatGPTに似たWebアプリを作成する

リフレクションを活用し、純粋なPythonでChatGPTに似たWebアプリを作成する方法

OpenAIのAPIを使用してPythonで純粋なChat Webアプリを1行でデプロイする

Chat app GIF by Author

ここ数ヶ月、Llama 2、GPT-4、Falcon 40B、Claude 2などの素晴らしい新しいLLMチャットボットを含むすべての信じられないほどのLLMチャットボットを試してきました。常に私を悩ませる一つの質問は、これらの素晴らしいLLMをAPIとして呼び出す自分自身のチャットボットUIをどのように構築できるかということです。

美しいユーザーインターフェースを構築するための無数のオプションがありますが、私はMLエンジニアとして、JavaScriptやその他のフロントエンド言語にまったくの経験がありません。今私が知っている現在の言語であるPythonだけを使用してWebアプリを構築する方法を探していました!

私はReflexという比較的新しいオープンソースのフレームワークを使用することにしました。ReflexはバックエンドとフロントエンドをPythonだけで構築できるようにしてくれます。

免責事項:私はReflexの創設エンジニアとして働いており、オープンソースのフレームワークに貢献しています。

このチュートリアルでは、Pythonだけを使用してゼロからAIチャットアプリを構築する方法について説明します。コードはGithubリポジトリで見つけることもできます。

以下を学びます:

  1. reflexをインストールし、開発環境を設定する方法。
  2. UIを定義してスタイル付けするためのコンポーネントを作成する方法。
  3. アプリに対話性を追加するための状態を使用する方法。
  4. 1行でアプリをデプロイして他の人と共有する方法。

プロジェクトのセットアップ

新しいプロジェクトを作成し、開発環境を設定することから始めましょう。まず、プロジェクトのための新しいディレクトリを作成し、そのディレクトリに移動します。

~ $ mkdir chatapp~ $ cd chatapp

次に、プロジェクトのための仮想環境を作成します。この例では、venvを使用して仮想環境を作成します。

chatapp $ python3 -m venv .venv$ source .venv/bin/activate

次に、Reflexをインストールし、新しいプロジェクトを作成します。これにより、プロジェクトディレクトリに新しいディレクトリ構造が作成されます。

chatapp $ pip install reflexchatapp $ reflex init────────────────────────────────── chatappの初期化 ──────────────────────────────────成功:chatappが初期化されましたchatapp $ lsassets          chatapp         rxconfig.py     .venv

テンプレートアプリケーションを実行して、すべてが動作していることを確認できます。

chatapp $ reflex run─────────────────────────────────── Reflexアプリを起動中 ──────────────────────────────────コンパイル中:  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 100% 1/1 0:00:00─────────────────────────────────────── アプリが実行中 ───────────────────────────────────────App running at: http://localhost:3000

http://localhost:3000でアプリが実行されていることが表示されます。

Reflexはバックエンドサーバーも起動しますが、これはすべての状態管理とフロントエンドとの通信を処理します。バックエンドサーバーが正常に実行されているかどうかを確認するには、http://localhost:8000/pingに移動してテストできます。

これでプロジェクトのセットアップが完了したので、アプリを構築しましょう!

基本フロントエンド

まず、チャットアプリのフロントエンドを定義してみましょう。Reflexでは、フロントエンドを独立して再利用可能なコンポーネントに分割することができます。さらなる詳細については、コンポーネントのドキュメントをご覧ください。

質問と回答を表示する

chatapp/chatapp.pyファイル内のindex関数を変更して、単一の質問と回答を表示するコンポーネントを返すようにします。

Image by Author (code below)
# chatapp.pyimport reflex as rxdef index() -> rx.Component:    return rx.container(        rx.box(            "Reflexとは何ですか?",            # ユーザーの質問は右に表示されます。            text_align="right",        ),        rx.box(            "純粋なPythonでウェブアプリを構築する方法です!",            # 回答は左に表示されます。            text_align="left",        ),    )# コンポーネントをネストして複雑なレイアウトを作成できます。ここでは、質問と回答のためのボックスを2つ含む親コンテナを作成しています。

また、コンポーネントに基本的なスタイリングを追加します。コンポーネントは、外観や機能を変更するキーワード引数(props)を受け取ります。テキストの位置を左右に揃えるためにtext_alignプロップを使用します。

コンポーネントの再利用

1つの質問と回答を表示するコンポーネントができたので、同じものを複数表示するために再利用できます。コンポーネントを別の関数question_answerに移動し、index関数から呼び出します。

Image by Author (code below)
def qa(question: str, answer: str) -> rx.Component:    return rx.box(        rx.box(question, text_align="right"),        rx.box(answer, text_align="left"),        margin_y="1em",    )def chat() -> rx.Component:    qa_pairs = [        (            "Reflexとは何ですか?",            "純粋なPythonでウェブアプリを構築する方法です!",        ),        (            "それを使って何を作れますか?",            "シンプルなウェブサイトから複雑なウェブアプリまで、何でも作れます!",        ),    ]    return rx.box(        *[            qa(question, answer)            for question, answer in qa_pairs        ]    )def index() -> rx.Component:    return rx.container(chat())

チャットの入力

ユーザーが質問を入力できるようにする必要があります。そのために、inputコンポーネントを使用してテキストを追加し、ボタンコンポーネントを使用して質問を送信します。

Image by Author (code below)
def action_bar() -> rx.Component:    return rx.hstack(        rx.input(placeholder="質問を入力してください"),        rx.button("送信"),    )def index() -> rx.Component:    return rx.container(        chat(),        action_bar(),    )

スタイリング

アプリにスタイリングを追加しましょう。スタイリングの詳細についてはスタイリングドキュメントをご覧ください。コードをきれいに保つために、スタイリングを別ファイルchatapp/style.pyに移動します。

# style.py# 質問と回答のための共通スタイル.shadow = "rgba(0, 0, 0, 0.15) 0px 2px 8px"chat_margin = "20%"message_style = dict(    padding="1em",    border_radius="5px",    margin_y="0.5em",    box_shadow=shadow,    max_width="30em",    display="inline-block",)# 質問と回答の特定のスタイルを設定します。question_style = message_style | dict(    bg="#F5EFFE", margin_left=chat_margin)answer_style = message_style | dict(    bg="#DEEAFD", margin_right=chat_margin)# アクションバーのスタイル.input_style = dict(    border_width="1px", padding="1em", box_shadow=shadow)button_style = dict(bg="#CEFFEE", box_shadow=shadow)

chatapp.py内のスタイルをインポートしてコンポーネントで使用します。この時点では、アプリはこのように見えるはずです:

Image by Author
# chatapp.pyimport reflex as rxfrom chatapp import styledef qa(question: str, answer: str) -> rx.Component:    return rx.box(        rx.box(            rx.text(question, style=style.question_style),            text_align="right",        ),        rx.box(            rx.text(answer, style=style.answer_style),            text_align="left",        ),        margin_y="1em",    )def chat() -> rx.Component:    qa_pairs = [        (            "リフレックスとは何ですか?",            "純粋なPythonでウェブアプリをビルドする方法です!",        ),        (            "それを使って何を作成できますか?",            "シンプルなウェブサイトから複雑なウェブアプリまで、何でも作成できます!",        ),    ]    return rx.box(        *[            qa(question, answer)            for question, answer in qa_pairs        ]    )def action_bar() -> rx.Component:    return rx.hstack(        rx.input(            placeholder="質問をする",            style=style.input_style,        ),        rx.button("質問する", style=style.button_style),    )def index() -> rx.Component:    return rx.container(        chat(),        action_bar(),    )app = rx.App()app.add_page(index)app.compile()

アプリは見た目はいいですが、まだあまり使い物になりません!さて、いくつかの機能を追加しましょう。

状態

では、状態を追加してチャットアプリをインタラクティブにしましょう。状態では、アプリ内で変更できるすべての変数と、それらを変更できるすべての関数を定義します。状態については状態ドキュメントで詳しく学ぶことができます。

状態の定義

chatappディレクトリ内にstate.pyという新しいファイルを作成します。状態は、現在の質問とチャットの履歴を管理します。また、現在の質問を処理し、回答をチャットの履歴に追加するanswerイベントハンドラを定義します。

# state.pyimport reflex as rxclass State(rx.State):    # 現在の質問です。    question: str    # (質問, 回答)のタプルのリストとしてチャット履歴を保持します。    chat_history: list[tuple[str, str]]    def answer(self):        # 今のところ、私たちのチャットボットはあまり賢くありません...        answer = "分かりません!"        self.chat_history.append((self.question, answer))

コンポーネントに状態をバインドする

これでchatapp.pyで状態をインポートし、フロントエンドコンポーネントで参照することができます。現在の固定された質問と回答の代わりに状態を使用するようにchatコンポーネントを変更します。

Image by Author
# chatapp.pyfrom chatapp.state import State...def chat() -> rx.Component:    return rx.box(        rx.foreach(            State.chat_history,            lambda messages: qa(messages[0], messages[1]),        )    )...def action_bar() -> rx.Component:    return rx.hstack(        rx.input(            placeholder="質問をする",            on_change=State.set_question,            style=style.input_style,        ),        rx.button(            "質問する",            on_click=State.answer,            style=style.button_style,        ),    )

通常のPythonのforループは、状態変数を繰り返し処理するために使用できません。これらの値は変更される可能性があり、コンパイル時にはわかりません。代わりに、foreachコンポーネントを使用してチャット履歴を繰り返し処理します。

また、入力のon_changeイベントをset_questionイベントハンドラにバインドし、ユーザーが入力中にquestion状態変数を更新します。ボタンのon_clickイベントをanswerイベントハンドラにバインドし、質問を処理し、回答をチャット履歴に追加します。set_questionイベントハンドラは組み込みの暗黙的に定義されたイベントハンドラです。すべてのベース変数には自動的に一つずつ設定されます。詳細はイベントドキュメントのSettersセクションをご覧ください。

入力のクリア

現在、ユーザーがボタンをクリックした後も、入力がクリアされません。これを修正するために、入力の値をquestionにバインドし、value=State.questionで、answerのイベントハンドラが実行された時にそれをクリアするようにします。self.question = ''

# chatapp.pydef action_bar() -> rx.Component:    return rx.hstack(        rx.input(            value=State.question,            placeholder="質問をする",            on_change=State.set_question,            style=style.input_style,        ),        rx.button(            "質問する",            on_click=State.answer,            style=style.button_style,        ),    )

# state.pydef answer(self):    # 当社のチャットボットは現在はあまり賢くありません...    answer = "わかりません!"    self.chat_history.append((self.question, answer))    self.question = ""

テキストのストリーミング

通常、状態の更新は、イベントハンドラが戻るときにフロントエンドに送信されます。ただし、チャットボットが生成されるときにテキストをストリームする必要があります。これを実現するために、イベントハンドラからyieldすることができます。詳細はイベントyieldドキュメントを参照してください。

# state.pyimport asyncio...async def answer(self):    # 当社のチャットボットは現在はあまり賢くありません...    answer = "わかりません!"    self.chat_history.append((self.question, ""))    # 質問入力をクリアします。    self.question = ""    # 継続する前にフロントエンドの入力をクリアするためにここでyieldします。    yield    for i in range(len(answer)):        # ストリーミング効果を表示するために一時停止します。        await asyncio.sleep(0.1)        # 出力に一つの文字ずつ追加します。        self.chat_history[-1] = (            self.chat_history[-1][0],            answer[: i + 1],        )        yield

APIの利用

チャットボットに知性を与えるために、OpenAIのAPIを使用します。イベントハンドラを修正してAPIにリクエストを送信する必要があります。

# state.pyimport osimport openaiopenai.api_key = os.environ["OPENAI_API_KEY"]...def answer(self):    # 当社のチャットボットにはいくらかの知性があります!    session = openai.ChatCompletion.create(        model="gpt-3.5-turbo",        messages=[            {"role": "user", "content": self.question}        ],        stop=None,        temperature=0.7,        stream=True,    )    # チャットボットの応答に追加します。    answer = ""    self.chat_history.append((self.question, answer))    # 質問入力をクリアします。    self.question = ""    # 継続する前にフロントエンドの入力をクリアするためにここでyieldします。    yield    for item in session:        if hasattr(item.choices[0].delta, "content"):            answer += item.choices[0].delta.content            self.chat_history[-1] = (                self.chat_history[-1][0],                answer,            )            yield

最後に、AIチャットボットが完成しました!

結論

このチュートリアルに従って、OpenAIのAPIキーを使用して、Pythonだけでチャットアプリを作成しました。

このアプリを実行するには、単純なコマンドを実行します:

$ reflex run

他のユーザーと共有するためにデプロイするには、次のコマンドを実行します:

$ reflex deploy

このチュートリアルが、皆さん自身のLLMベースのアプリを作成することにインスピレーションを与えることを願っています。ソーシャルメディアやコメントで連絡してください。

質問がある場合は、以下にコメントしてください。または、Twitterの@tgotsman12またはLinkedInでメッセージを送ってください。アプリの制作物をソーシャルメディアで共有してタグ付けしてください。フィードバックやリツイートのサポートを提供することができます。

We will continue to update VoAGI; if you have any questions or suggestions, please contact us!

Share:

Was this article helpful?

93 out of 132 found this helpful

Discover more