Crea un’applicazione web simile a ChatGPT in Python puro usando Reflex

Crea un'applicazione web simile a ChatGPT utilizzando Python puro con Reflex

Usa l’API di OpenAI per creare un’app web di chat in Python puro con distribuzione in una sola riga

Chat app GIF di Autore

In questi ultimi mesi mi sono divertito a giocare con tutti i nuovi chatbot LLM incredibili, inclusi Llama 2, GPT-4, Falcon 40B e Claude 2. Una domanda che mi tormenta costantemente è come posso costruire la mia interfaccia utente del chatbot che richiami tutte queste ottime LLM come API?

Ci sono innumerevoli opzioni disponibili per costruire belle interfacce utente, ma essendo un ingegnere di intelligenza artificiale (IA), non ho esperienza con JavaScript o qualsiasi linguaggio di front-end. Stavo cercando un modo per costruire la mia app web utilizzando solo il linguaggio che conosco attualmente, Python!

Ho deciso di utilizzare un framework open-source abbastanza recente chiamato Reflex, che mi ha permesso di costruire sia il back-end che il front-end completamente in Python.

Avviso legale: Lavoro come Ingegnere Fondatore presso Reflex, dove contribuisco al framework open-source.

In questo tutorial ti mostrerò come costruire un’app di chat IA completa da zero in Python puro: puoi trovare tutto il codice anche in questo repository Github.

Imparerai come:

  1. Installare reflex e configurare l’ambiente di sviluppo.
  2. Creare componenti per definire e stilizzare la tua interfaccia utente.
  3. Utilizzare lo stato per aggiungere interattività alla tua app.
  4. Distribuire la tua app con un solo comando per condividerla con gli altri.

Configurazione del tuo progetto

Inizieremo creando un nuovo progetto e impostando il nostro ambiente di sviluppo. Innanzitutto, crea una nuova directory per il tuo progetto e naviga al suo interno.

~ $ mkdir chatapp~ $ cd chatapp

Successivamente, creeremo un ambiente virtuale per il nostro progetto. In questo esempio, utilizzeremo venv per creare il nostro ambiente virtuale.

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

Ora installeremo Reflex e creeremo un nuovo progetto. Questo creerà una nuova struttura di directory nella nostra directory di progetto.

chatapp $ pip install reflexchatapp $ reflex init────────────────────────────────── Inizializzazione chatapp ───────────────────────────────────Successo: Chatapp inizializzatochatapp $ lsassets          chatapp         rxconfig.py     .venv

Puoi eseguire l’app template per assicurarti che tutto funzioni correttamente.

chatapp $ reflex run─────────────────────────────────── Avvio app Reflex ───────────────────────────────────Compilazione:  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 100% 1/1 0:00:00─────────────────────────────────────── App in esecuzione ──────────────────────────────────────App in esecuzione su: http://localhost:3000

Dovresti vedere la tua app in esecuzione su http://localhost:3000 .

Reflex avvia anche il server back-end che gestisce tutta la gestione dello stato e la comunicazione con il front-end. Puoi verificare che il server backend sia in esecuzione navigando su http://localhost:8000/ping .

Ora che abbiamo impostato il nostro progetto, costruiamo la nostra app!

Frontend di base

Iniziamo definendo il front-end della nostra app di chat. In Reflex, il front-end può essere suddiviso in componenti indipendenti e riutilizzabili. Consulta la documentazione dei componenti per ulteriori informazioni.

Mostrare una domanda e una risposta

Modificheremo la funzione index nel file chatapp/chatapp.py per restituire un componente che mostra una singola domanda e risposta.

Immagine dell'autore (codice di seguito)
# chatapp.pyimport reflex as rxdef index() -> rx.Component:    return rx.container(        rx.box(            "Cos'è Reflex?",            # La domanda dell'utente è a destra.            text_align="right",        ),        rx.box(            "Un modo per creare app web in Python puro!",            # La risposta è a sinistra.            text_align="left",        ),    )# Aggiungi stato e pagina all'app.app = rx.App()app.add_page(index)app.compile()

I componenti possono essere nidificati l’uno dentro l’altro per creare layout complessi. Qui creiamo un contenitore principale che contiene due box per la domanda e la risposta.

Aggiungiamo anche alcuni stili di base ai componenti. I componenti accettano argomenti di parola chiave, chiamati props, che modificano l’aspetto e la funzionalità del componente. Utilizziamo la prop text_align per allineare il testo a sinistra e a destra.

Riutilizzo dei componenti

Ora che abbiamo un componente che mostra una singola domanda e risposta, possiamo riutilizzarlo per mostrare domande e risposte multiple. Spostiamo il componente in una funzione separata domanda_risposta e lo chiamiamo dalla funzione index.

Immagine dell'autore (codice di seguito)
def qa(domanda: str, risposta: str) -> rx.Component:    return rx.box(        rx.box(domanda, text_align="right"),        rx.box(risposta, text_align="left"),        margin_y="1em",    )def chat() -> rx.Component:    coppie_qa = [        (            "Cos'è Reflex?",            "Un modo per creare app web in Python puro!",        ),        (            "Cosa posso fare con esso?",            "Qualsiasi cosa, da un semplice sito web a un'app web complessa!",        ),    ]    return rx.box(        *[            qa(domanda, risposta)            for domanda, risposta in coppie_qa        ]    )def index() -> rx.Component:    return rx.container(chat())

Input della chat

Ora vogliamo un modo per permettere all’utente di inserire una domanda. Per fare ciò, useremo il componente input per far inserire all’utente del testo e il componente button per inviare la domanda.

Immagine dell'autore (codice di seguito)
def action_bar() -> rx.Component:    return rx.hstack(        rx.input(placeholder="Fai una domanda"),        rx.button("Chiedi"),    )def index() -> rx.Component:    return rx.container(        chat(),        action_bar(),    )

Stile

Aggiungiamo un po’ di stile all’app. Ulteriori informazioni sullo stile si trovano nella documentazione sugli stili. Per mantenere il nostro codice pulito, sposteremo lo stile in un file separato chatapp/style.py.

# style.py# Stili comuni per domande e risposte.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",)# Imposta stili specifici per domande e risposte.question_style = message_style | dict(    bg="#F5EFFE", margin_left=chat_margin)answer_style = message_style | dict(    bg="#DEEAFD", margin_right=chat_margin)# Stili per la barra delle azioni.input_style = dict(    border_width="1px", padding="1em", box_shadow=shadow)button_style = dict(bg="#CEFFEE", box_shadow=shadow)

Importeremo gli stili in chatapp.py e li utilizzeremo nei componenti. A questo punto, l’applicazione dovrebbe apparire così:

Immagine di Autore
# chatapp.pyimport reflex come rxfrom chatapp import styledef qa(domanda: str, risposta: str) -> rx.Component:    return rx.box(        rx.box(            rx.text(domanda, style=style.question_style),            text_align="right",        ),        rx.box(            rx.text(risposta, style=style.answer_style),            text_align="left",        ),        margin_y="1em",    )def chat() -> rx.Component:    coppie_qa = [        (            "Cos'è Reflex?",            "Un modo per creare app web in puro Python!",        ),        (            "Cosa posso realizzare con esso?",            "Qualsiasi cosa, da un sito web semplice a un'app web complessa!",        ),    ]    return rx.box(        *[            qa(domanda, risposta)            for domanda, risposta in coppie_qa        ]    )def action_bar() -> rx.Component:    return rx.hstack(        rx.input(            placeholder="Fai una domanda",            style=style.input_style,        ),        rx.button("Chiedi", style=style.button_style),    )def index() -> rx.Component:    return rx.container(        chat(),        action_bar(),    )app = rx.App()app.add_page(index)app.compile()

L’applicazione sembra buona, ma non è ancora molto utile! Ora aggiungiamo un po’ di funzionalità.

Stato

Adesso rendiamo l’applicazione di chat interattiva aggiungendo uno stato. Lo stato è dove definiamo tutte le variabili che possono cambiare nell’applicazione e tutte le funzioni che possono modificarle. Puoi saperne di più sullo stato nella documentazione sullo stato.

Definizione dello Stato

Creeremo un nuovo file chiamato state.py nella directory chatapp. Il nostro stato terrà traccia della domanda corrente e della cronologia della chat. Definiremo anche un gestore eventi risposta che elaborerà la domanda corrente e aggiungerà la risposta alla cronologia della chat.

# state.pyimport reflex come rxclass State(rx.State):    # La domanda corrente.    domanda: str    # Tieni traccia della cronologia della chat come una lista di tuple (domanda, risposta).    cronologia_chat: list[tuple[str, str]]    def risposta(self):        # Il nostro chatbot non è molto intelligente al momento...        risposta = "Non lo so!"        self.cronologia_chat.append((self.domanda, risposta))

Collegamento dello Stato ai Componenti

Ora possiamo importare lo stato in chatapp.py e fare riferimento ad esso nei nostri componenti frontend. Modificheremo il componente chat per utilizzare lo stato al posto delle domande e risposte fisse correnti.

Immagine di Autore
# chatapp.pyfrom chatapp.state import State...def chat() -> rx.Component:    return rx.box(        rx.foreach(            State.cronologia_chat,            lambda messaggi: qa(messaggi[0], messaggi[1]),        )    )...def action_bar() -> rx.Component:    return rx.hstack(        rx.input(            placeholder="Fai una domanda",            on_change=State.set_question,            style=style.input_style,        ),        rx.button(            "Chiedi",            on_click=State.risposta,            style=style.button_style,        ),    )

I normali cicli for in Python non funzionano per iterare sulle variabili di stato perché questi valori possono cambiare e non sono noti al momento della compilazione. Invece, utilizziamo il componente foreach per iterare sulla cronologia della chat.

Inoltre, collegiamo l’evento on_change dell’input all’handler dell’evento set_question, che aggiornerà la variabile di stato question mentre l’utente digita nell’input. Colleghiamo l’evento on_click del pulsante all’handler dell’evento answer, che elaborerà la domanda e aggiungerà la risposta alla cronologia della chat. L’handler dell’evento set_question è un handler di evento definito implicitamente. Ogni variabile di base ne ha uno. Per saperne di più, consulta la documentazione sugli eventi nella sezione Setters.

Cancellare l’Input

Attualmente l’input non si cancella dopo che l’utente fa clic sul pulsante. Possiamo risolvere il problema collegando il valore dell’input a question, con value=State.question, e cancellandolo quando eseguiamo l’handler dell’evento answer, con self.question = ''.

# chatapp.pydef action_bar() -> rx.Component:    return rx.hstack(        rx.input(            value=State.question,            placeholder="Fai una domanda",            on_change=State.set_question,            style=style.input_style,        ),        rx.button(            "Chiedi",            on_click=State.answer,            style=style.button_style,        ),    )

# state.pydef answer(self):    # Il nostro chatbot non è molto intelligente...    answer = "Non lo so!"    self.chat_history.append((self.question, answer))    self.question = ""

Streaming del Testo

Normalmente gli aggiornamenti dello stato vengono inviati al frontend quando un handler dell’evento restituisce. Tuttavia, vogliamo inviare in streaming il testo del chatbot man mano che viene generato. Possiamo farlo usando il comando yield nell’handler dell’evento. Consulta la documentazione sugli eventi yield per ulteriori informazioni.

# state.pyimport asyncio...async def answer(self):    # Il nostro chatbot non è molto intelligente...    answer = "Non lo so!"    self.chat_history.append((self.question, ""))    # Cancellare l'input della domanda.    self.question = ""    # Usare yield qui per cancellare l'input del frontend prima di continuare.    yield    for i in range(len(answer)):        # Pausa per mostrare l'effetto di streaming.        await asyncio.sleep(0.1)        # Aggiungi una lettera alla volta all'output.        self.chat_history[-1] = (            self.chat_history[-1][0],            answer[: i + 1],        )        yield

Utilizzando l’API

Utilizzeremo l’API di OpenAI per dare al nostro chatbot un po’ di intelligenza. Dobbiamo modificare l’handler dell’evento per inviare una richiesta all’API.

# state.pyimport osimport openaiopenai.api_key = os.environ["OPENAI_API_KEY"]...def answer(self):    # Il nostro chatbot ora ha un po' di intelligenza!    session = openai.ChatCompletion.create(        model="gpt-3.5-turbo",        messages=[            {"role": "user", "content": self.question}        ],        stop=None,        temperature=0.7,        stream=True,    )    # Aggiungi alla risposta mentre il chatbot risponde.    answer = ""    self.chat_history.append((self.question, answer))    # Cancella l'input della domanda.    self.question = ""    # Usare yield qui per cancellare l'input del frontend prima di continuare.    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

Finalmente, abbiamo il nostro chatbot AI!

Conclusioni

Seguendo questo tutorial, abbiamo creato con successo la nostra App di Chat utilizzando la chiave dell’API di OpenAI, interamente in Python.

Per eseguire questa app, possiamo eseguire il semplice comando:

$ reflex run

Per distribuirla, in modo da poterla condividere con altri utenti, possiamo eseguire il comando:

$ reflex deploy

Spero che questo tutorial ti ispiri a creare le tue app basate su LLM. Sono ansioso di vedere cosa andrete a creare, quindi per favore contattatemi sui social media o nei commenti.

Se hai domande, commentale qui sotto o mandami un messaggio su Twitter a @tgotsman12 oppure su LinkedIn. Condividi le tue creazioni di app sui social media e taggami, sarò felice di fornire feedback o aiutare con un retweet!