Input vocale e linguaggio naturale per la tua app mobile utilizzando LLM

Input vocale e linguaggio naturale per app mobile con LLM

Come sfruttare le funzioni di OpenAI GPT-4 per navigare nella tua interfaccia grafica

Foto di Kelly Sikkema su Unsplash

Introduzione

Un Large Language Model (LLM) è un sistema di apprendimento automatico in grado di elaborare efficacemente il linguaggio naturale. Al momento, il LLM più avanzato disponibile è GPT-4, che alimenta la versione a pagamento di ChatGPT. In questo articolo imparerai come dare al tuo’app un’interpretazione del linguaggio parlato altamente flessibile utilizzando la chiamata di funzioni di GPT-4, in piena sinergia con l’interfaccia grafica utente (GUI) della tua app. È rivolto a proprietari di prodotti, progettisti UX e sviluppatori mobile.

Sfondo

Gli assistenti digitali sui telefoni cellulari (Android e iOS) non sono riusciti a imporsi per diversi motivi; tra questi vi sono difetti, limitazioni e spesso difficoltà d’uso. I LLM e, in particolare, OpenAI GPT-4, hanno il potenziale per fare la differenza qui, con la loro capacità di comprendere più approfonditamente l’intenzione dell’utente invece di cercare di abbinare grossolanamente un’espressione parlata.

Android ha le “app actions” di Google Assistant e iOS ha gli “intents” di SiriKit. Questi forniscono semplici modelli che puoi utilizzare per registrare richieste vocali che la tua app può gestire. Google Assistant e Siri si sono già migliorati parecchio negli ultimi anni – anche più di quanto probabilmente tu possa immaginare. La loro copertura è determinata in gran parte da quali app implementano il supporto per loro. Tuttavia, puoi, ad esempio, riprodurre la tua canzone preferita su Spotify utilizzando il linguaggio parlato. L’interpretazione del linguaggio naturale di questi servizi forniti dal sistema operativo, tuttavia, precede i grandi progressi in questo campo che i LLM hanno portato – quindi è ora di fare il passo successivo: sfruttare la potenza dei LLM per rendere l’input vocale più affidabile e flessibile.

Anche se possiamo aspettarci che i servizi di sistema operativo (come Siri e Google Assistant) adatteranno presto le loro strategie per sfruttare i LLM, possiamo già abilitare le nostre app a utilizzare la voce senza limitazioni da parte di questi servizi. Una volta adottati i concetti di questo articolo, la tua app sarà pronta anche per sfruttare nuovi assistenti, una volta disponibili.

La scelta del tuo LLM (GPT, PaLM, LLama2, MPT, Falcon, ecc.) ha un impatto sulla affidabilità, ma i principi fondamentali che imparerai qui possono essere applicati a ognuno di essi. Permetteremo all’utente di accedere all’intera funzionalità della tua app dicendo ciò che desidera in un’unica espressione. Il LLM mappa un’espressione in linguaggio naturale in una chiamata di funzione sulla struttura di navigazione e la funzionalità della nostra app. E non è necessario che sia una frase pronunciata come un robot. Le capacità interpretative del LLM consentono agli utenti di parlare come esseri umani, utilizzando le loro parole o il loro linguaggio; esitare, commettere errori e correggere errori. Dove gli utenti hanno rifiutato gli assistenti vocali perché spesso non riescono a capire cosa intendono, la flessibilità di un LLM può rendere l’interazione molto più naturale e affidabile, portando a una maggiore adozione da parte degli utenti.

Perché l’input vocale nella tua app, e perché ora?

Vantaggi:

  • Navigare verso una schermata e fornire tutti i parametri in un’unica espressione vocale
  • Curva di apprendimento ridotta: non è necessario che l’utente trovi dove si trovano i dati nella tua app o come utilizzare la GUI
  • Mani libere
  • Complementare e non slegato (come in un’interfaccia utente vocale o VUI): la voce e la GUI lavorano in armonia
  • Accessibilità per persone con disabilità visive
  • Ora: perché l’interpretazione del linguaggio naturale è salita a un nuovo livello attraverso i LLM, quindi le risposte sono molto più affidabili

Svantaggi:

  • Privacy durante la conversazione
  • accuratezza/interpretazioni errate
  • ancora relativamente lento
  • Conoscenza nella testa rispetto al mondo (Cosa posso dire?): l’utente non sa quali espressioni vocali il sistema comprende e ha risposte a

Esempi di app che possono beneficiare dell’input vocale includono quelle utilizzate per l’assistenza alla guida in auto o in bicicletta. In generale, gli utenti potrebbero non voler utilizzare il tocco per navigare in un’app quando non possono facilmente utilizzare le mani, ad esempio quando sono in movimento, indossano guanti o sono occupati a lavorare con le mani.

Le app di shopping possono trarre vantaggio da questa funzionalità, in quanto gli utenti possono verbalizzare i loro desideri con parole proprie, anziché navigare attraverso le schermate di acquisto e impostare filtri.

Quando si applica questo approccio per aumentare l’accessibilità delle persone con disabilità visive, potrebbe essere opportuno considerare l’aggiunta di output in linguaggio naturale e funzionalità di text-to-speech.

La tua app

La figura seguente mostra la struttura di navigazione di un’app tipica, esemplificata da un pianificatore di viaggi in treno con cui potresti essere familiare. In alto si vede la struttura di navigazione predefinita per la navigazione tramite tocco. Questa struttura è governata dal Componente di Navigazione. Tutti i clic di navigazione vengono delegati al Componente di Navigazione, che esegue quindi l’azione di navigazione. In basso viene mostrato come possiamo sfruttare questa struttura utilizzando l’input vocale.

abilitazione dell'input vocale per la tua app utilizzando la chiamata di funzione LLM

Gli utenti dicono ciò che desiderano, quindi un riconoscitore vocale trasforma il discorso in testo. Il sistema costruisce un prompt contenente questo testo e lo invia all’LLM. L’LLM risponde all’app con i dati, indicandogli quale schermata attivare con quali parametri. Questo oggetto dati viene trasformato in un deep link e fornito al componente di navigazione. Il componente di navigazione attiva la schermata corretta con i parametri corretti: in questo esempio la schermata ‘Uscite’ con ‘Amsterdam’ come parametro. Si noti che questa è una semplificazione. Approfondiremo i dettagli di seguito.

Molte app moderne hanno un componente di navigazione centralizzato sotto il cofano. Android ha la Navigazione di Jetpack, Flutter ha il Router, iOS ha la NavigationStack. I componenti di navigazione centralizzati consentono il deep linking, una tecnica che consente agli utenti di navigare direttamente verso una schermata specifica all’interno di un’applicazione mobile, anziché passare per la schermata principale o il menu dell’app. Per far funzionare i concetti descritti in questo articolo, non è necessario un componente di navigazione e deep linking centralizzato, ma rende l’implementazione dei concetti più semplice.

Il deep linking consiste nel creare un percorso unico (URI) che punta a un contenuto specifico o a una sezione specifica all’interno di un’app. Inoltre, questo percorso può contenere parametri che controllano gli stati degli elementi dell’interfaccia utente sullo schermo a cui il deep link punta.

Chiamata di funzione per la tua app

Diciamo all’LLM di mappare un’espressione in linguaggio naturale a una chiamata di funzione di navigazione attraverso tecniche di ingegneria dei prompt. In sostanza, il prompt legge qualcosa del tipo: ‘dato i seguenti modelli di funzioni con parametri, mappa la seguente domanda in linguaggio naturale su uno di questi modelli di funzioni e restituiscilo’.

La maggior parte degli LLM è in grado di farlo. LangChain lo ha sfruttato in modo efficace attraverso gli agenti Zero Shot ReAct e le funzioni da chiamare sono chiamate Tools. OpenAI ha addestrato in modo specifico i loro modelli GPT-3.5 e GPT-4 con versioni speciali (attualmente gpt-3.5-turbo-0613 e gpt-4–0613) che sono molto bravi in questo e hanno creato voci di API specifiche per questo scopo. In questo articolo useremo la notazione di OpenAI, ma i concetti possono essere applicati a qualsiasi LLM, ad esempio utilizzando il meccanismo ReAct menzionato. Inoltre, LangChain ha un tipo di agente specifico (AgentType.OPENAI_FUNCTIONS) che traduce Tools in modelli di funzioni di OpenAI sotto il cofano. Per LLama2 sarai in grado di utilizzare llama-api con la stessa sintassi di OpenAI.

La chiamata di funzione per gli LLM funziona come segue:

  1. Inserisci uno schema JSON di modelli di funzioni nel tuo prompt insieme all’espressione in linguaggio naturale dell’utente come messaggio dell’utente.
  2. L’LLM cerca di mappare l’espressione in linguaggio naturale dell’utente su uno di questi modelli
  3. L’LLM restituisce l’oggetto JSON risultante in modo che il tuo codice possa effettuare una chiamata di funzione

In questo articolo le definizioni delle funzioni sono corrispondenze dirette dell’interfaccia utente grafica (GUI) di un’app (mobile), in cui ogni funzione corrisponde a una schermata e ogni parametro a un elemento della GUI su quella schermata. Un’espressione in linguaggio naturale inviata all’LLM restituisce un oggetto JSON contenente un nome di funzione e i relativi parametri che puoi utilizzare per navigare verso la schermata corretta e attivare la funzione corretta nel tuo modello di visualizzazione, in modo che i dati corretti vengano recuperati e il valore degli elementi della GUI rilevanti su quella schermata venga impostato in base ai parametri.

Questo è illustrato nella seguente figura:

mappatura delle funzioni LLM sulla GUI della tua app mobile

Mostra una versione ridotta dei modelli di funzioni aggiunti al prompt per il LLM. Per vedere il prompt completo per il messaggio dell’utente: ‘Cosa posso fare ad Amsterdam?’, clicca qui (Github Gist). Contiene una richiesta curl completa che puoi utilizzare dalla riga di comando o importare in postman. Devi inserire la tua chiave OpenAI nel segnaposto per eseguirlo.

Schermate senza parametri

Alcune schermate della tua app non hanno parametri, o almeno non quelli di cui il LLM ha bisogno di essere consapevole. Per ridurre l’uso dei token e l’ingombro, possiamo combinare alcuni di questi trigger di schermate in una singola funzione con un parametro: lo schermo da aprire

{    "name": "show_screen",    "description": "Determina quale schermo l'utente vuole vedere",    "parameters": {        "type": "object",        "properties": {            "screen_to_show": {                "description": "tipo di schermo da mostrare. Può essere                     'account': 'tutti i dati personali dell'utente',                     'impostazioni': 'se l'utente vuole cambiare le impostazioni                                 dell'app'",                "enum": [                    "account",                    "impostazioni"                ],                "type": "string"            }        },        "required": [            "screen_to_show"        ]    }},

Il criterio per determinare se una funzione di attivazione ha bisogno di parametri è se l’utente ha una scelta: c’è una forma di ricerca o navigazione sullo schermo, cioè ci sono campi di ricerca o schede da scegliere.

Se non ci sono, allora il LLM non ha bisogno di saperlo e l’attivazione dello schermo può essere aggiunta alla funzione di attivazione generica dello schermo della tua app. È principalmente una questione di sperimentazione con le descrizioni dello scopo dello schermo. Se hai bisogno di una descrizione più lunga, potresti considerare di assegnarle la sua definizione di funzione separata, per mettere maggiore enfasi separata sulla sua descrizione rispetto all’enum del parametro generico.

Istruzioni di guida e riparazione del prompt:

Nel messaggio di sistema del tuo prompt fornisci informazioni di guida generiche. Nel nostro esempio può essere importante per il LLM sapere la data e l’ora attuali, ad esempio se vuoi pianificare un viaggio per domani. Un’altra cosa importante è orientarlo sulla sua presunzione. Spesso preferiremmo che il LLM fosse eccessivamente fiducioso piuttosto che infastidire l’utente con la sua incertezza. Un buon messaggio di sistema per la nostra app di esempio è:

"messages": [        {            "role": "system",            "content": "La data e l'ora attuali sono 2023-07-13T08:21:16+02:00.                       Sii molto presuntuoso nel fare supposizioni sui valori dei                        parametri di funzione."        },

Le descrizioni dei parametri di funzione possono richiedere un’accurata messa a punto. Un esempio è la trip_date_time quando si pianifica un viaggio in treno. Una descrizione ragionevole del parametro è:

"trip_date_time": {      "description": "DataOra richiesta per la partenza o l'arrivo del viaggio                       nel formato 'YYYY-MM-DDTHH:MM:SS+02:00'.                      L'utente utilizzerà un'ora in un sistema a 12 ore, fare un                      'intelligente supposizione su cosa l'utente intende più                       probabilmente in termini di un sistema a 24 ore, ad esempio                       non pianificare per il passato.",                      "type": "string"                  },

Quindi, se sono le 15:00 e gli utenti dicono che vogliono partire alle 8, intendono le 20:00 a meno che non menzionino specificamente l’ora del giorno. L’istruzione sopra funziona ragionevolmente bene per GPT-4. Ma in alcuni casi limite può ancora fallire. Possiamo quindi ad esempio aggiungere parametri extra al modello di funzione che possiamo utilizzare per effettuare ulteriori riparazioni nel nostro codice. Ad esempio, possiamo aggiungere:

"explicit_day_part_reference": {          "description": "Preferisci sempre None! None se la richiesta si riferisce                         al giorno corrente, altrimenti la parte del giorno a cui                         si riferisce la richiesta."          "enum": ["nessuno", "mattina", "pomeriggio", "sera", "notte"],                            }

Nella tua app probabilmente troverai parametri che richiedono un post-processing per migliorare il loro tasso di successo.

Richieste di sistema per chiarimenti

A volte la richiesta dell’utente manca di informazioni per procedere. Potrebbe non esserci una funzione adatta per gestire la richiesta dell’utente. In tal caso, LLM risponderà in linguaggio naturale che puoi mostrare all’utente, ad esempio tramite un Toast.

Potrebbe anche accadere che LLM riconosca una funzione potenziale da chiamare, ma manchino informazioni per riempire tutti i parametri richiesti dalla funzione. In questo caso, considera di rendere facoltativi i parametri, se possibile. Ma se ciò non è possibile, LLM può inviare una richiesta, in linguaggio naturale, per i parametri mancanti, nella lingua dell’utente. Dovresti mostrare questo testo agli utenti, ad esempio tramite un Toast o text-to-speech, in modo che possano fornire le informazioni mancanti (a voce). Ad esempio, quando l’utente dice “Voglio andare ad Amsterdam” (e la tua app non ha fornito una posizione predefinita o corrente tramite il messaggio di sistema), LLM potrebbe rispondere con “Capisco che vuoi fare un viaggio in treno, da dove vuoi partire?”.

Questo solleva la questione della cronologia delle conversazioni. Ti consiglio di includere sempre gli ultimi 4 messaggi dell’utente nel prompt, in modo che una richiesta di informazioni possa avvenire in più passaggi. Per semplificare le cose, ometti semplicemente le risposte del sistema dalla cronologia, perché in questo caso d’uso tendono a fare più male che bene.

Riconoscimento del parlato

Il riconoscimento del parlato è una parte cruciale nella trasformazione dal parlato a un’azione di navigazione parametrizzata nell’app. Quando la qualità dell’interpretazione è alta, un cattivo riconoscimento del parlato potrebbe essere il punto debole. I telefoni cellulari hanno un riconoscimento del parlato integrato, con una qualità ragionevole, ma il riconoscimento del parlato basato su LLM come Whisper, Google Chirp/USM, Meta MMS o DeepGram tende a ottenere risultati migliori.

Architettura

Probabilmente è meglio memorizzare le definizioni delle funzioni sul server, ma possono anche essere gestite dall’app e inviate con ogni richiesta. Entrambi hanno vantaggi e svantaggi. L’invio con ogni richiesta è più flessibile e l’allineamento delle funzioni e delle schermate potrebbe essere più facile da mantenere. Tuttavia, i modelli di funzioni contengono non solo il nome delle funzioni e i parametri, ma anche le loro descrizioni che potremmo voler aggiornare più rapidamente rispetto al flusso di aggiornamento nei negozi delle app. Queste descrizioni dipendono più o meno da LLM e sono realizzate in base a ciò che funziona. Non è improbabile che tu voglia sostituire LLM con uno migliore o più economico, o addirittura sostituirlo dinamicamente in un certo punto. Avere i modelli di funzioni sul server può anche avere il vantaggio di mantenerli in un unico punto se la tua app è nativa su iOS e Android. Se utilizzi i servizi di OpenAI sia per il riconoscimento del parlato che per l’elaborazione del linguaggio naturale, la visione generale tecnica del flusso appare come segue:

architettura per abilitare il parlato nella tua app mobile utilizzando Whisper e la chiamata di funzioni di OpenAI

Gli utenti esprimono la loro richiesta, viene registrata in un buffer/file m4a (o mp3 se preferisci), che viene inviato al tuo server, che lo trasmette a Whisper. Whisper risponde con la trascrizione, e il tuo server la combina con il messaggio di sistema e i modelli di funzioni in un prompt per LLM. Il tuo server riceve il JSON della chiamata di funzione grezza, che elabora in un oggetto JSON di chiamata di funzione per la tua app.

Per illustrare come una chiamata di funzione si traduce in un collegamento profondo, prendiamo la risposta della chiamata di funzione dall’esempio iniziale:

"function_call": {                    "name": "outings",                    "arguments": "{\n  \"area\": \"Amsterdam\"\n}"                }

Questa situazione viene gestita in modi diversi su piattaforme diverse e nel tempo sono stati utilizzati molti meccanismi di navigazione diversi, che spesso sono ancora in uso. È al di là dell’ambito di questo articolo entrare nei dettagli di implementazione, ma in linea di massima le piattaforme nella loro versione più recente possono utilizzare il collegamento profondo come segue:

Su Android:

navController.navigate("outings/?area=Amsterdam")

Su Flutter:

Navigator.pushNamed(      context,      '/outings',      arguments: ScreenArguments(        area: 'Amsterdam',      ),    );

Su iOS le cose sono un po’ meno standardizzate, ma utilizzando NavigationStack:

NavigationStack(path: $router.path) {            ...}

E quindi emettendo:

router.path.append("outing?area=Amsterdam")

Più informazioni sul deep linking possono essere trovate qui: per Android, per Flutter, per iOS

Campo di testo libero per le app

Ci sono due modalità di input di testo libero: voce e digitazione. Abbiamo principalmente parlato di input vocale, ma un campo di testo per l’input tramite digitazione è anche un’opzione. Il linguaggio naturale di solito è piuttosto lungo, quindi potrebbe essere difficile competere con l’interazione GUI. Tuttavia, GPT-4 tende ad essere abbastanza bravo nel indovinare i parametri dalle abbreviazioni, quindi anche una digitazione abbreviata molto breve può spesso essere interpretata correttamente.

L’uso di funzioni con parametri nella richiesta di solito restringe notevolmente il contesto di interpretazione per un LLM. Pertanto, ha bisogno di molto poco, e ancora meno se gli si ordina di essere presuntuoso. Questo è un nuovo fenomeno che promette per l’interazione mobile. Nel caso della pianificazione da stazione a stazione del treno, l’LLM ha effettuato le seguenti interpretazioni quando utilizzato con la struttura di richiesta esemplificata in questo articolo. Puoi provarlo tu stesso utilizzando il prompt gist menzionato sopra.

Esempi:

‘ams utr’: mostrami un elenco di itinerari in treno da Amsterdam Centraal a Utrecht Centraal che partono da ora

‘utr ams arr 9’: (Dato che è attualmente 13:00). Mostrami un elenco di itinerari in treno da Utrecht Centraal ad Amsterdam Centraal che arrivano prima delle 21:00

Interazione di follow-up

Come in ChatGPT, puoi perfezionare la tua query se invii un breve pezzo della cronologia di interazione:

Utilizzando la funzione di cronologia, anche il seguente funziona molto bene (supponendo che sia attualmente le 9:00 del mattino):

Digita: ‘ams utr’ e ottieni la risposta come sopra. Quindi digita ‘arr 7’ nel turno successivo. E sì, può effettivamente tradurlo in un viaggio pianificato da Amsterdam Centraal a Utrecht Centraal che arriva prima delle 19:00. Ho creato un’app web di esempio su questo, che puoi trovare un video qui. Il link all’app effettiva è nella descrizione.

Futuro

Puoi aspettarti che questa struttura di deep link per gestire le funzioni all’interno della tua app diventi una parte integrante del sistema operativo del tuo telefono (Android o iOS). Un assistente globale sul telefono gestirà le richieste vocali e le app possono esporre le loro funzioni al sistema operativo, in modo che possano essere attivate in modalità deep linking. Questo è simile a come i plug-in sono resi disponibili per ChatGPT. Ovviamente, già ora una forma grossolana di questo è disponibile tramite gli intenti in AndroidManifest e le App Actions su Android e tramite SiriKit intents su iOS. La quantità di controllo che hai su questi è limitata e l’utente deve parlare come un robot per attivarli in modo affidabile. Indubbiamente questo migliorerà nel tempo, quando gli assistenti basati su LLM prenderanno il sopravvento.

La VR e l’AR (XR) offrono grandi opportunità per il riconoscimento vocale, perché le mani degli utenti sono spesso impegnate in altre attività.

Probabilmente non passerà molto tempo prima che chiunque possa eseguire il proprio LLM di alta qualità. I costi diminuiranno e la velocità aumenterà rapidamente nel prossimo anno. Presto i LLM LoRA saranno disponibili sugli smartphone, quindi l’inferenza può avvenire sul tuo telefono, riducendo costi e velocità. Inoltre, sempre più concorrenza arriverà, sia open source come Llama2, sia closed source come PaLM.

Infine, la sinergia delle modalità può essere spinta oltre che fornire un accesso casuale all’intera GUI della tua app. È il potere dei LLM di combinare più fonti, che promette l’emergere di un’assistenza migliore. Alcuni articoli interessanti: dialogo multimodale, blog di Google su GUI e LLM, interpretazione dell’interazione GUI come linguaggio.

Conclusioni

In questo articolo hai imparato come applicare la chiamata di funzione per abilitare la tua app all’uso della voce. Utilizzando il Gist fornito come punto di partenza, puoi sperimentare in postman o dalla riga di comando per farti un’idea di quanto potente sia la chiamata di funzione. Se vuoi eseguire una POC per abilitare la tua app all’uso della voce, ti consiglierei di inserire direttamente nella tua app la parte del server, dalla sezione dell’architettura. Tutto si riduce a 2 chiamate http, un po’ di costruzione del prompt e all’implementazione della registrazione del microfono. A seconda delle tue competenze e della tua base di codice, avrai la tua POC pronta e funzionante in pochi giorni.

Happy coding!

Seguimi su LinkedIn

Tutte le immagini in questo articolo, salvo diversa indicazione, sono dell’autore