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
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.
- Ricercatori dell’Università di Surrey hanno sviluppato un nuovo modello di intelligenza artificiale (IA) che potrebbe aiutare la rete di telecomunicazioni a risparmiare fino al 76% nella rete.
- OpenAI API – Introduzione e 11 esempi pratici di implementazione dei modelli dietro ChatGPT
- Algoritmo evolutivo – Spiegazione delle mutazioni
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:
- Installare
reflex
e configurare l’ambiente di sviluppo. - Creare componenti per definire e stilizzare la tua interfaccia utente.
- Utilizzare lo stato per aggiungere interattività alla tua app.
- 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.
# 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
.
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.
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ì:
# 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.
# 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!