Come costruire un’app Streamlit multi-pagina interconnessa
Costruzione di un'app Streamlit multi-pagina interconnessa
Dal piano all’esecuzione – come ho costruito GPT lab
Nota: Questo articolo è stato originariamente pubblicato sul blog di Streamlit. Volevo condividerlo qui per farlo vedere alla comunità VoAGI.
Wow! Che incredibili tre mesi da quando ho pubblicato per la prima volta il mio post sul blog sulle lezioni apprese dalla costruzione di GPT Lab! 🚀
Grazie al vostro enorme supporto, GPT Lab ha ricevuto oltre 9.000 visualizzazioni dell’app, oltre 1.150 utenti unici registrati, oltre 900 sessioni con assistenti, oltre 650 test di prompt e oltre 180 assistenti creati. L’app è stata anche presentata nella Galleria delle App di Streamlit insieme ad altre fantastiche app.
http://gptlab.streamlit.app/?embed=true
- Ottimizzare le tue strategie con approcci oltre i test A/B
- 4 Modi Rivoluzionari per Migliorare il Tuo Team di Governance dei Dati
- La Storia dietro le Reti Bayesiane
Molti di voi mi hanno chiesto: “Come hai pianificato e costruito un’applicazione così grande con Streamlit?” Desideroso di rispondere, ho deciso di rendere GPT Lab open source.
In questo post, condividerò approfondimenti sulle strategie e sui processi di pensiero dietro questo progetto ambizioso. Spero che vi ispiri a spingere Streamlit ai suoi limiti e a dare vita alle vostre app ambiziose.
💡 Vuoi saltare avanti? Dai un’occhiata all’app e al codice.
Pianificazione di un’applicazione Streamlit di grandi dimensioni
Costruire app di grandi dimensioni su Streamlit, come GPT Lab, richiede una pianificazione attenta anziché semplicemente unire del codice. Per GPT Lab, mi sono concentrato sulla pianificazione di questi quattro aspetti chiave:
- Funzionalità e UX. Cosa farà l’app? Che tipo di esperienza utente vogliamo offrire?
- Modello di dati. Come verranno persistiti i dati? Cosa dovrebbe essere memorizzato nel database rispetto alle variabili di stato della sessione?
- Struttura del codice. Come dovrebbe essere architettata l’app per garantire modularità, manutenibilità e scalabilità?
- Stati di sessione. Quali variabili di stato di sessione sono necessarie per collegare l’interfaccia utente?
Comprendere questi aspetti offriva una visione più chiara di ciò che stavo cercando di costruire e forniva un framework per affrontare il compito complesso in modo sistematico.
Approfondiamo ogni aspetto in maggior dettaglio.
Funzionalità e UX: Creazione di specifiche iniziali e mockup UX a bassa fedeltà
Per iniziare, ho creato un documento di specifica semplice (o “spec”) che descriveva l’ambito generale e l’approccio. Ho incluso anche una mappa del sito che dettagliava i casi d’uso che volevo supportare. Lo spec mi ha fornito una chiara strada da seguire e un modo per misurare i miei progressi.
Ecco un estratto dalla specifica originale:
Ambito
Crea una piattaforma che permetta agli appassionati di IA generativa (GA) di costruire il proprio chatbot basato su prompt GPT-3 per amici e familiari. L’obiettivo è testare l’ipotesi che abbastanza appassionati di chatbot GA vorrebbero costruire i loro bot in un ambito di nicchia.
Approccio
Un sito Streamlit pubblico che consente agli utenti di interagire con uno dei quattro coach bot pre-addestrati o di creare e interagire con i loro bot.
Come per la maggior parte dei progetti di sviluppo, ho apportato alcune modifiche. Ma la mappa del sito originale è rimasta in gran parte intatta, poiché sono stato in grado di implementare la maggior parte delle funzionalità pianificate.
Ecco la versione finale della mappa del sito:
GPT Lab│├── Home│├── Lounge│├── Assistant│ ├── Cerca assistente│ ├── Dettagli assistente│ ├── Chat attiva│ └── Riepilogo chat│├── Lab│ ├── Passo 1: prompt iniziale + configurazione modello│ ├── Passo 2: chat di prova│ ├── Passo 3: altre configurazioni│ └── Passo 4: conferma│├── FAQ│└── Legale ├── Termini └── Informativa sulla privacy
Non posso sottolineare abbastanza l’importanza della pianificazione delle funzionalità. Fornisce una mappa, un modo per misurare i progressi e un punto di partenza per pensare al modello di dati.
Modello di dati: Determinare lo schema
Dall’inizio, ho riconosciuto che un backend per il salvataggio dei dati era cruciale per persistere i record degli utenti, dell’assistente e delle sessioni. Dopo aver considerato le opzioni, ho deciso di utilizzare Google Firestore grazie alla sua scalabilità, alle capacità in tempo reale e alla generosa offerta gratuita. Per supportare future espansioni, ho progettato strategicamente il modello dei dati. Anche se l’applicazione attuale utilizza solo una parte del suo potenziale, è possibile aggiungere controlli di versione delle prompt a GPT Lab. Questo consentirebbe agli utenti di modificare o ripristinare i loro assistenti.
💡 NOTA: Nel backend dell’app e nel modello dei dati, gli assistenti sono indicati come bot, nonostante la mia precedente insistenza nel non chiamarli così nell’interfaccia utente 😅.
Ora, esploriamo le quattro principali collezioni di Firestore in GPT Lab: utenti, user_hash, bot e sessioni.
Utenti e user_hash
La collezione utenti è dove l’app memorizza le informazioni sui suoi utenti. Per proteggere la privacy degli utenti, l’app non memorizza alcuna informazione identificabile personalmente (PII) sugli utenti. Invece, ogni utente è associato solo al valore di hash unidirezionale della sua chiave API OpenAI. I campi metrici vengono incrementati ogni volta che un utente crea un assistente o avvia/termina una sessione con un assistente. Questo consente la raccolta di analisi di base all’interno dell’app.
Collezione Utenti | | - id: (ID automatico di Firestore) | - user_hash: stringa (valore di hash unidirezionale della chiave API OpenAI) | - data_creazione: data e ora | - data_ultima_modifica: data e ora | - sessioni_avviate: numero | - sessioni_terminate: numero | - bot_creati: numero
Google Firestore non fornisce un modo per garantire l’unicità di un valore di campo di un documento all’interno di una collezione, quindi ho creato una collezione separata chiamata user_hash. Ciò garantisce che ogni chiave API unica abbia un solo record utente associato. Ogni documento utente è associato in modo univoco a un documento user_hash e ogni documento user_hash può essere associato a un documento utente. Il modello dei dati è sufficientemente flessibile per ospitare utenti che cambiano le loro chiavi API in futuro (gli utenti possono effettuare l’accesso con la loro vecchia chiave API e poi sostituirla con una nuova).
Collezione user_hash | | - id = valore di hash unidirezionale della chiave API OpenAI | - tipo_user_hash: stringa (open_ai_key) | - data_creazione: data e ora
Bot
La collezione bot memorizza le configurazioni per gli assistenti di intelligenza artificiale. La parte centrale di ogni assistente di intelligenza artificiale è il suo grande modello di linguaggio (LLM), le configurazioni del modello e le prompt. Per consentire un controllo adeguato delle versioni delle prompt e delle configurazioni del modello in futuro, le configurazioni di modello e le prompt sono modellate come sottocollezioni (parte della visione di GPT Lab è di essere il repository delle tue prompt).
Per ridurre al minimo le letture delle sottocollezioni (in modo da non dover costantemente interrogare le sottocollezioni per il record attivo), gli ID dei documenti delle sottocollezioni attive vengono anche memorizzati a livello di documento. Il campo session_type indica se l’assistente si trova in una sessione di brainstorming o di coaching, il che influisce sulla tecnica di troncamento del messaggio di sessione.
Infine, i campi metrici vengono incrementati quando un utente avvia o termina una sessione con un assistente.
Collezione Bot | | - id: (ID automatico di Firestore) | - nome: stringa | - tag_line: stringa | - descrizione: stringa | - tipo_sessione: numero | - id_utente_creatore: stringa | - data_creazione: data e ora | - data_ultima_modifica: data e ora | - id_prompt_iniziale_attivo: stringa | - id_configurazione_modello_attiva: stringa | - id_prompt_sommario_attivo: stringa | - in_vetrina: booleano | - è_attivo: booleano | v |--> Sottocollezione Configurazioni_modello | | | | - configurazione: mappa | | | - modello: stringa | | | - max_token: numero | | | - temperatura: numero | | | - top_p: numero | | | - penalità_frequenza: numero | | | - penalità_presenza: numero | | - data_creazione: data e ora | | - è_attiva: booleano | v |--> Sottocollezione Prompt | | - tipo_messaggio: stringa | - messaggio: stringa | - data_creazione: data e ora | - è_attivo: booleano | - sessioni_avviate: numero | - sessioni_terminate: numero
Sessioni
La collezione sessioni memorizza i dati delle sessioni. Contiene due tipi di sessioni: sessioni di laboratorio (utilizzate per testare le prompt) e sessioni dell’assistente (utilizzate per chattare con gli assistenti creati). Per ridurre la necessità di recuperare frequentemente il documento del bot, le sue informazioni vengono memorizzate nella cache all’interno del documento della sessione. Questo ha senso concettualmente, poiché il documento del bot potrebbe cambiare se venisse mai implementato un caso d’uso di modifica dell’assistente.
Il campo messages_str
memorizza il payload più recente inviato al LLM di OpenAI. Questa funzionalità consente agli utenti di riprendere le loro sessioni precedenti con l’assistente. La sottocollezione messages
memorizza i messaggi effettivi della chat. Notare che i messaggi della sessione di laboratorio non vengono memorizzati.
Per garantire la riservatezza e la privacy dell’utente, i payload delle richieste e i messaggi della sessione di OpenAI vengono criptati prima di essere salvati nel database. Questo modello dati consente agli utenti di riavviare una sessione precedente e continuare a chattare con l’assistente.
Collezione Sessioni | | - id: (Firestore auto-ID) | - user_id: string | - bot_id: string | - bot_initial_prompt_msg: string | | - bot_model_config: mappa | | - modello: stringa | | - max_tokens: numero | | - temperatura: numero | | - top_p: numero | | - penalità_frequenza: numero | | - penalità_presenza: numero | | - tipo_sessione_bot: numero | - bot_summary_prompt_msg: stringa | - data_creazione: data_ora | - versione_schema_sessione: numero | - stato: numero | - conteggio_messaggi: numero | - messages_str: stringa (criptata) | v |--> Sottocollezione Messaggi | | - data_creazione: data_ora | - messaggio: stringa (criptata) | - ruolo: stringa
Considerando attentamente tutti i potenziali casi d’uso fin dall’inizio, ho creato un modello dati che sia pronto per il futuro e in grado di soddisfare le esigenze e le funzionalità in evoluzione dell’app. Nella sezione seguente, esamineremo la struttura del codice dell’applicazione backend per capire come supporta e implementa questo solido modello dati.
Struttura del codice: Strutturazione per scalabilità e modularità
Ho creato GPT Lab per consentire agli utenti con competenze tecniche basse o nulle di creare le proprie applicazioni AI basate su LLM, senza preoccuparsi dell’infrastruttura sottostante. Il mio obiettivo è offrire in futuro API backend che collegano le app frontend personalizzate degli utenti (con o senza Streamlit) ai loro assistenti AI. Questo mi ha spinto a progettare un’architettura disaccoppiata che separa l’applicazione frontend Streamlit dalla logica di backend.
Il codice di backend è strutturato come segue:
+----------------+ +-------------------+ +-------------------+ +------------+| | | | | | | || App Streamlit |<--->| util_collections |<--->| api_util_firebase |<--->| Firestore || | | (utenti, sessioni, | | | | || | | bot) | | | | |+----------------+ +-------------------+ +-------------------+ +------------+ | | v +-----------------+ +------------+ | | | | | api_util_openai |<--->| OpenAI | | | | | +-----------------+ +------------+
I moduli sono i seguenti:
- api_util_firebase gestisce le operazioni CRUD con il database Firestore.
- api_util_openai interagisce con i modelli di OpenAI, fornisce un modello di chat unificato ai modelli upstream, elimina i messaggi della chat e cerca di rilevare e prevenire gli attacchi di inserimento del prompt.
- api_util_users, api_util_sessions e api_util_bots sono interfacce per le rispettive collezioni di Firestore. Interagiscono con api_util_firebase e api_util_openai e implementano la logica di business specifica di GPT Lab.
Questa progettazione consente lo sviluppo, il test e la scalabilità separati delle diverse parti del codice. Stabilisce inoltre un percorso di migrazione più semplice per convertire i moduli util_collections di backend in Google Cloud Functions, che possono essere esposti tramite API Gateways.
Stati della sessione: Gestione dell’interfaccia utente e del flusso utente
Come spiegato nel primo articolo del blog, ho utilizzato variabili di stato della sessione per controllare e gestire le funzionalità nelle pagine di Streamlit. Di seguito viene illustrato come queste variabili vengono utilizzate in tutta l’app:
home.py
- user controlla se visualizzare il modulo della chiave API di OpenAI
pages/1_lounge.py
- user controlla se visualizzare il modulo della chiave API di OpenAI, abilitare la selezione dell’assistente e mostrare la scheda My Assistants.
- Dopo che gli utenti scelgono di interagire con un assistente, i dettagli dell’assistente vengono memorizzati in bot_info.
pages/2_assistant.py
- user controlla se visualizzare il modulo della chiave API di OpenAI.
- bot_info, session_id e session_ended determinano quale variante dello schermo visualizzare.
- Se bot_info non esiste: controlla se l’assistant_id è presente nel parametro URL. In caso contrario, chiedi agli utenti di cercare un assistente.
- Se bot_info e session_id esistono e session_ended è false: visualizza lo schermo della sessione di chat.
- Se bot_info e session_id esistono e session_ended è true: visualizza lo schermo di riepilogo della sessione di chat.
- Nella sessione di chat, session_msg_list memorizza la conversazione.
pages/3_lab.py
- user controlla se visualizzare il modulo della chiave API di OpenAI e se consentire agli utenti di iniziare a creare assistenti nel laboratorio.
- lab_active_step controlla quale stato di sessione del laboratorio visualizzare:
- Se 1: visualizza l’interfaccia utente del passo 1 per impostare il prompt iniziale dell’assistente e il modello.
- Se 2: visualizza l’interfaccia utente del passo 2 per testare la chat con l’assistente.
- Se 3: visualizza l’interfaccia utente del passo 3 per finalizzare i dettagli dell’assistente. Alla creazione, il record del bot viene creato nel database Firestore e l’ID del documento viene salvato in lab_bot_id.
- Se 4 e lab_bot_id è impostato: visualizza l’interfaccia utente del passo 4 per mostrare la conferma di creazione dell’assistente.
- Nella sessione di chat di test, lab_msg_list memorizza i messaggi di test. Utilizzando lab_bot_id e bot_info separati, posso consentire agli utenti di passare avanti e indietro tra la sala/assistente e il laboratorio senza perdere il progresso in ognuno.
Con la pianificazione iniziale completata, il resto dell’esecuzione è stato molto più gestibile.
Conclusioni
In questo post, ho coperto la pianificazione iniziale necessaria per creare GPT Lab, inclusi le funzionalità, il modello dati, il codice e lo stato della sessione. Spero che questo ti ispiri a costruire le tue ambiziose app Streamlit.
Collegati con me su Twitter o Linkedin. Sarei felice di sentirti.
Happy Streamlit-ing! 🎈