1. Creare un'app Flutter basata su Gemini
Cosa creerai
In questo codelab creerai Colorist, un'applicazione Flutter interattiva che porta la potenza dell'API Gemini direttamente nella tua app Flutter. Hai mai desiderato consentire agli utenti di controllare la tua app tramite il linguaggio naturale, ma non sapevi da dove iniziare? Questo codelab ti mostra come.
Colorist consente agli utenti di descrivere i colori in linguaggio naturale (ad esempio "l'arancione di un tramonto" o "blu oceano intenso") e l'app:
- Elabora queste descrizioni utilizzando l'API Gemini di Google
- Interpreta le descrizioni in valori di colore RGB precisi
- Visualizza il colore sullo schermo in tempo reale
- Fornisce dettagli tecnici sul colore e un contesto interessante sul colore
- Mantiene una cronologia dei colori generati di recente
L'app presenta un'interfaccia a schermo diviso con un'area di visualizzazione a colori e un sistema di chat interattivo da un lato e un riquadro dettagliato dei log che mostra le interazioni non elaborate con LLM dall'altro. Questo log ti consente di comprendere meglio il funzionamento di un'integrazione LLM.
Perché è importante per gli sviluppatori Flutter
Gli LLM stanno rivoluzionando il modo in cui gli utenti interagiscono con le applicazioni, ma la loro integrazione efficace nelle app mobile e desktop presenta sfide uniche. Questo codelab ti insegna pattern pratici che vanno oltre le semplici chiamate API.
Il tuo percorso di apprendimento
Questo codelab illustra la procedura per creare Colorist passo passo:
- Configurazione del progetto: inizierai con una struttura di app Flutter di base e il pacchetto
colorist_ui
- Integrazione di base di Gemini: collega la tua app a Vertex AI in Firebase e implementa una semplice comunicazione LLM
- Prompt efficaci: crea un prompt di sistema che guidi l'LLM a comprendere le descrizioni dei colori
- Dichiarazioni di funzione: definisci gli strumenti che l'LLM può utilizzare per impostare i colori nella tua applicazione
- Gestione degli strumenti: elabora le chiamate di funzione dall'LLM e collegale allo stato dell'app
- Risposte dinamiche: migliora l'esperienza utente con risposte LLM dinamiche in tempo reale
- Sincronizzazione del contesto LLM: crea un'esperienza coerente informando l'LLM delle azioni dell'utente
Obiettivi didattici
- Configura Vertex AI in Firebase per le applicazioni Flutter
- Crea prompt di sistema efficaci per guidare il comportamento dell'LLM
- Implementa dichiarazioni di funzioni che colleghino il linguaggio naturale alle funzionalità dell'app
- Elabora le risposte in streaming per un'esperienza utente reattiva
- Sincronizza lo stato tra gli eventi dell'interfaccia utente e l'LLM
- Gestire lo stato della conversazione LLM utilizzando Riverpod
- Gestire gli errori in modo corretto nelle applicazioni basate su LLM
Anteprima del codice: un assaggio di ciò che implementerai
Ecco un'anteprima della dichiarazione di funzione che creerai per consentire all'LLM di impostare i colori nella tua app:
FunctionDeclaration get setColorFuncDecl => FunctionDeclaration(
'set_color',
'Set the color of the display square based on red, green, and blue values.',
parameters: {
'red': Schema.number(description: 'Red component value (0.0 - 1.0)'),
'green': Schema.number(description: 'Green component value (0.0 - 1.0)'),
'blue': Schema.number(description: 'Blue component value (0.0 - 1.0)'),
},
);
Prerequisiti
Per ottenere il massimo da questo codelab, devi disporre di:
- Esperienza di sviluppo Flutter: familiarità con le nozioni di base di Flutter e la sintassi di Dart
- Conoscenze di programmazione asincrona: comprensione di Futures, async/await e stream
- Account Firebase: per configurare Firebase è necessario un Account Google
- Progetto Firebase con fatturazione abilitata: Vertex AI in Firebase richiede un account di fatturazione
Iniziamo a creare la tua prima app Flutter basata su LLM.
2. Configurazione del progetto e servizio di echo
In questo primo passaggio, configurerai la struttura del progetto e implementerai un semplice servizio echo che in seguito verrà sostituito con l'integrazione dell'API Gemini. In questo modo viene stabilita l'architettura dell'applicazione e viene garantito il corretto funzionamento dell'interfaccia utente prima di aggiungere la complessità delle chiamate LLM.
Che cosa imparerai in questo passaggio
- Configurazione di un progetto Flutter con le dipendenze richieste
- Utilizzo del pacchetto
colorist_ui
per i componenti dell'interfaccia utente - Implementazione di un servizio di messaggi di eco e collegamento all'interfaccia utente
Una nota importante sui prezzi
Creare un nuovo progetto Flutter
Inizia creando un nuovo progetto Flutter con il seguente comando:
flutter create -e colorist --platforms=android,ios,macos,web,windows
Il flag -e
indica che vuoi un progetto vuoto senza l'app counter
predefinita. L'app è progettata per funzionare su computer, dispositivi mobili e web. Tuttavia, al momento flutterfire
non supporta Linux.
Aggiungi dipendenze
Vai alla directory del progetto e aggiungi le dipendenze richieste:
cd colorist
flutter pub add colorist_ui flutter_riverpod riverpod_annotation
flutter pub add --dev build_runner riverpod_generator riverpod_lint json_serializable custom_lint
Verranno aggiunti i seguenti pacchetti principali:
colorist_ui
: un pacchetto personalizzato che fornisce i componenti dell'interfaccia utente per l'app Coloristflutter_riverpod
eriverpod_annotation
: per la gestione dello statologging
: per il logging strutturato- Dipendenze di sviluppo per la generazione di codice e il linting
pubspec.yaml
avrà il seguente aspetto:
pubspec.yaml
name: colorist
description: "A new Flutter project."
publish_to: 'none'
version: 0.1.0
environment:
sdk: ^3.7.2
dependencies:
flutter:
sdk: flutter
colorist_ui: ^0.1.0
flutter_riverpod: ^2.6.1
riverpod_annotation: ^2.6.1
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^5.0.0
build_runner: ^2.4.15
riverpod_generator: ^2.6.5
riverpod_lint: ^2.6.5
json_serializable: ^6.9.4
custom_lint: ^0.7.5
flutter:
uses-material-design: true
Configurare le opzioni di analisi
Aggiungi custom_lint
al file analysis_options.yaml
nella directory principale del progetto:
include: package:flutter_lints/flutter.yaml
analyzer:
plugins:
- custom_lint
Questa configurazione attiva i lint specifici di Riverpod per contribuire a mantenere la qualità del codice.
Implementa il file main.dart
Sostituisci i contenuti di lib/main.dart
con quanto segue:
lib/main.dart
import 'package:colorist_ui/colorist_ui.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
void main() async {
runApp(ProviderScope(child: MainApp()));
}
class MainApp extends ConsumerWidget {
const MainApp({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
return MaterialApp(
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
),
home: MainScreen(
sendMessage: (message) {
sendMessage(message, ref);
},
),
);
}
// A fake LLM that just echoes back what it receives.
void sendMessage(String message, WidgetRef ref) {
final chatStateNotifier = ref.read(chatStateNotifierProvider.notifier);
final logStateNotifier = ref.read(logStateNotifierProvider.notifier);
chatStateNotifier.addUserMessage(message);
logStateNotifier.logUserText(message);
chatStateNotifier.addLlmMessage(message, MessageState.complete);
logStateNotifier.logLlmText(message);
}
}
In questo modo, viene configurata un'app Flutter che implementa un semplice servizio di eco che imita il comportamento di un LLM semplicemente restituendo il messaggio dell'utente.
Informazioni sull'architettura
Prenditi un minuto per comprendere l'architettura dell'app colorist
:
Il pacchetto colorist_ui
Il pacchetto colorist_ui
fornisce componenti dell'interfaccia utente predefiniti e strumenti di gestione dello stato:
- MainScreen: il componente dell'interfaccia utente principale che mostra:
- Un layout a schermo diviso su computer (area di interazione e riquadro dei log)
- Un'interfaccia a schede sui dispositivi mobili
- Visualizzazione a colori, interfaccia della chat e miniature della cronologia
- Gestione dello stato: l'app utilizza diversi notificatori dello stato:
- ChatStateNotifier: gestisce i messaggi della chat
- ColorStateNotifier: gestisce il colore e la cronologia attuali
- LogStateNotifier: gestisce le voci di log per il debug
- Gestione dei messaggi: l'app utilizza un modello di messaggio con stati diversi:
- Messaggi utente: inseriti dall'utente
- Messaggi LLM: generati dall'LLM (o per il momento dal tuo servizio Echo)
- MessageState: monitora se i messaggi LLM sono completi o se sono ancora in streaming
Architettura dell'applicazione
L'app segue la seguente architettura:
- Livello UI: fornito dal pacchetto
colorist_ui
- Gestione dello stato: utilizza Riverpod per la gestione dello stato reattivo
- Livello di servizio: attualmente contiene il servizio echo semplice, che verrà sostituito dal servizio Gemini Chat
- Integrazione LLM: verrà aggiunta nei passaggi successivi
Questa separazione ti consente di concentrarti sull'implementazione dell'integrazione di LLM, mentre i componenti dell'interfaccia utente sono già gestiti.
Esegui l'app
Esegui l'app con il seguente comando:
flutter run -d DEVICE
Sostituisci DEVICE
con il dispositivo di destinazione, ad esempio macos
, windows
, chrome
o un ID dispositivo.
Ora dovresti vedere l'app Colorist con:
- Un'area di visualizzazione a colori con un colore predefinito
- Un'interfaccia di chat in cui puoi digitare i messaggi
- Un riquadro del log che mostra le interazioni della chat
Prova a digitare un messaggio come "Vorrei un colore blu intenso" e premi Invia. Il servizio Echo ripeterà semplicemente il messaggio. Nei passaggi successivi, sostituirai questo valore con l'interpretazione effettiva del colore utilizzando l'API Gemini tramite Vertex AI in Firebase.
Passaggi successivi
Nel passaggio successivo, configurerai Firebase e implementerai l'integrazione di base dell'API Gemini per sostituire il servizio echo con il servizio di chat Gemini. In questo modo, l'app potrà interpretare le descrizioni dei colori e fornire risposte intelligenti.
Risoluzione dei problemi
Problemi relativi al pacchetto dell'interfaccia utente
Se riscontri problemi con il pacchetto colorist_ui
:
- Assicurati di utilizzare la versione più recente
- Verifica di aver aggiunto la dipendenza correttamente
- Controlla se sono presenti versioni dei pacchetti in conflitto
Errori di compilazione
Se vengono visualizzati errori di compilazione:
- Assicurati di aver installato l'SDK Flutter del canale stabile più recente
- Esegui
flutter clean
seguito daflutter pub get
- Controlla se nell'output della console sono presenti messaggi di errore specifici
Concetti chiave appresi
- Configurazione di un progetto Flutter con le dipendenze necessarie
- Comprendere l'architettura e le responsabilità dei componenti dell'applicazione
- Implementazione di un servizio semplice che simula il comportamento di un LLM
- Collegamento del servizio ai componenti dell'interfaccia utente
- Utilizzare Riverpod per la gestione dello stato
3. Integrazione di base di Gemini Chat
In questo passaggio, sostituirai il servizio echo del passaggio precedente con l'integrazione dell'API Gemini utilizzando Vertex AI in Firebase. Configura Firebase, imposta i provider necessari e implementa un servizio di chat di base che comunica con l'API Gemini.
Che cosa imparerai in questo passaggio
- Configurazione di Firebase in un'applicazione Flutter
- Configurazione di Vertex AI in Firebase per l'accesso a Gemini
- Creazione di provider Riverpod per i servizi Firebase e Gemini
- Implementazione di un servizio di chat di base con l'API Gemini
- Gestione delle risposte API asincrone e degli stati di errore
Configura Firebase
Innanzitutto, devi configurare Firebase per il tuo progetto Flutter. Ciò comporta la creazione di un progetto Firebase, l'aggiunta della tua app e la configurazione delle impostazioni Vertex AI necessarie.
Crea un progetto Firebase
- Vai alla Console Firebase e accedi con il tuo Account Google.
- Fai clic su Crea un progetto Firebase o seleziona un progetto esistente.
- Segui la configurazione guidata per creare il progetto.
- Una volta creato il progetto, dovrai eseguire l'upgrade al piano Blaze (a consumo) per accedere ai servizi Vertex AI. Fai clic sul pulsante Esegui l'upgrade in basso a sinistra nella Console Firebase.
Configurare Vertex AI nel progetto Firebase
- Nella Console Firebase, vai al tuo progetto.
- Nella barra laterale a sinistra, seleziona AI.
- Nella scheda Vertex AI in Firebase, seleziona Inizia.
- Segui le istruzioni per abilitare le API Vertex AI in Firebase per il tuo progetto.
Installa l'interfaccia a riga di comando FlutterFire
L'interfaccia a riga di comando FlutterFire semplifica la configurazione di Firebase nelle app Flutter:
dart pub global activate flutterfire_cli
Aggiungere Firebase all'app Flutter
- Aggiungi i pacchetti Firebase Core e Vertex AI al progetto:
flutter pub add firebase_core firebase_vertexai
- Esegui il comando di configurazione FlutterFire:
flutterfire configure
Questo comando:
- Ti chiede di selezionare il progetto Firebase che hai appena creato
- Registra le tue app Flutter con Firebase
- Genera un file
firebase_options.dart
con la configurazione del progetto
Il comando rileverà automaticamente le piattaforme selezionate (iOS, Android, macOS, Windows, web) e le configurerà di conseguenza.
Configurazione specifica della piattaforma
Firebase richiede versioni minime superiori a quelle predefinite per Flutter. Richiede inoltre l'accesso alla rete per comunicare con Vertex AI nei server Firebase.
Configurare le autorizzazioni di macOS
Per macOS, devi attivare l'accesso alla rete nei diritti dell'app:
- Apri
macos/Runner/DebugProfile.entitlements
e aggiungi:
macos/Runner/DebugProfile.entitlements
<key>com.apple.security.network.client</key>
<true/>
- Apri anche
macos/Runner/Release.entitlements
e aggiungi la stessa voce. - Aggiorna la versione minima di macOS nella parte superiore di
macos/Podfile
:
macos/Podfile
# Firebase requires at least macOS 10.15
platform :osx, '10.15'
Configurare le autorizzazioni iOS
Per iOS, aggiorna la versione minima nella parte superiore di ios/Podfile
:
ios/Podfile
# Firebase requires at least iOS 13.0
platform :ios, '13.0'
Configurare le impostazioni di Android
Per Android, aggiorna android/app/build.gradle.kts
:
android/app/build.gradle.kts
android {
// ...
ndkVersion = "27.0.12077973"
defaultConfig {
// ...
minSdk = 23
// ...
}
}
Crea fornitori di modelli Gemini
Ora crea i provider Riverpod per Firebase e Gemini. Crea un nuovo file lib/providers/gemini.dart
:
lib/providers/gemini.dart
import 'dart:async';
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_vertexai/firebase_vertexai.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import '../firebase_options.dart';
part 'gemini.g.dart';
@riverpod
Future<FirebaseApp> firebaseApp(Ref ref) =>
Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
@riverpod
Future<GenerativeModel> geminiModel(Ref ref) async {
await ref.watch(firebaseAppProvider.future);
final model = FirebaseVertexAI.instance.generativeModel(
model: 'gemini-2.0-flash',
);
return model;
}
@Riverpod(keepAlive: true)
Future<ChatSession> chatSession(Ref ref) async {
final model = await ref.watch(geminiModelProvider.future);
return model.startChat();
}
Questo file definisce le basi per tre fornitori chiave. Questi provider vengono generati quando esegui dart run build_runner
dai generatori di codice Riverpod.
firebaseAppProvider
: inizializza Firebase con la configurazione del progettogeminiModelProvider
: crea un'istanza del modello generativo GeminichatSessionProvider
: crea e gestisce una sessione di chat con il modello Gemini
L'annotazione keepAlive: true
sulla sessione di chat ne garantisce la persistenza per tutto il ciclo di vita dell'app, mantenendo il contesto della conversazione.
Implementare il servizio di chat di Gemini
Crea un nuovo file lib/services/gemini_chat_service.dart
per implementare il servizio di chat:
lib/services/gemini_chat_service.dart
import 'dart:async';
import 'package:colorist_ui/colorist_ui.dart';
import 'package:firebase_vertexai/firebase_vertexai.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import '../providers/gemini.dart';
part 'gemini_chat_service.g.dart';
class GeminiChatService {
GeminiChatService(this.ref);
final Ref ref;
Future<void> sendMessage(String message) async {
final chatSession = await ref.read(chatSessionProvider.future);
final chatStateNotifier = ref.read(chatStateNotifierProvider.notifier);
final logStateNotifier = ref.read(logStateNotifierProvider.notifier);
chatStateNotifier.addUserMessage(message);
logStateNotifier.logUserText(message);
final llmMessage = chatStateNotifier.createLlmMessage();
try {
final response = await chatSession.sendMessage(Content.text(message));
final responseText = response.text;
if (responseText != null) {
logStateNotifier.logLlmText(responseText);
chatStateNotifier.appendToMessage(llmMessage.id, responseText);
}
} catch (e, st) {
logStateNotifier.logError(e, st: st);
chatStateNotifier.appendToMessage(
llmMessage.id,
"\nI'm sorry, I encountered an error processing your request. "
"Please try again.",
);
} finally {
chatStateNotifier.finalizeMessage(llmMessage.id);
}
}
}
@riverpod
GeminiChatService geminiChatService(Ref ref) => GeminiChatService(ref);
Questo servizio:
- Accetta i messaggi degli utenti e li invia all'API Gemini
- Aggiorna l'interfaccia della chat con le risposte del modello
- Registra tutte le comunicazioni per facilitare la comprensione del flusso LLM reale
- Gestisce gli errori con un feedback utente appropriato
Nota: a questo punto la finestra del log sarà quasi identica a quella della chat. Il log diventerà più interessante quando introdurrai le chiamate alle funzioni e poi le risposte in streaming.
Generare codice Riverpod
Esegui il comando del runner di compilazione per generare il codice Riverpod necessario:
dart run build_runner build --delete-conflicting-outputs
Verranno creati i file .g.dart
necessari per il funzionamento di Riverpod.
Aggiorna il file main.dart
Aggiorna il file lib/main.dart
per utilizzare il nuovo servizio di chat di Gemini:
lib/main.dart
import 'package:colorist_ui/colorist_ui.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'providers/gemini.dart';
import 'services/gemini_chat_service.dart';
void main() async {
runApp(ProviderScope(child: MainApp()));
}
class MainApp extends ConsumerWidget {
const MainApp({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final model = ref.watch(geminiModelProvider);
return MaterialApp(
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
),
home: model.when(
data:
(data) => MainScreen(
sendMessage: (text) {
ref.read(geminiChatServiceProvider).sendMessage(text);
},
),
loading: () => LoadingScreen(message: 'Initializing Gemini Model'),
error: (err, st) => ErrorScreen(error: err),
),
);
}
}
Le modifiche principali di questo aggiornamento sono:
- Sostituzione del servizio echo con il servizio di chat basato sull'API Gemini
- Aggiunta di schermate di caricamento ed errore utilizzando il pattern
AsyncValue
di Riverpod con il metodowhen
- Collegamento dell'interfaccia utente al nuovo servizio di chat tramite il callback
sendMessage
Esegui l'app
Esegui l'app con il seguente comando:
flutter run -d DEVICE
Sostituisci DEVICE
con il dispositivo di destinazione, ad esempio macos
, windows
, chrome
o un ID dispositivo.
Ora, quando digiti un messaggio, questo viene inviato all'API Gemini e ricevi una risposta dall'LLM anziché un eco. Il riquadro dei log mostra le interazioni con l'API.
Informazioni sulla comunicazione LLM
Prendiamoci un momento per capire cosa succede quando comunichi con l'API Gemini:
Il flusso di comunicazione
- Input dell'utente: l'utente inserisce il testo nell'interfaccia della chat
- Formattazione della richiesta: l'app formatta il testo come oggetto
Content
per l'API Gemini - Comunicazione API: il testo viene inviato all'API Gemini tramite Vertex AI in Firebase
- Elaborazione LLM: il modello Gemini elabora il testo e genera una risposta
- Gestione della risposta: l'app riceve la risposta e aggiorna l'interfaccia utente
- Logging: tutte le comunicazioni vengono registrate per garantire trasparenza
Sessioni di chat e contesto della conversazione
La sessione di chat di Gemini mantiene il contesto tra i messaggi, consentendo interazioni conversazionali. Ciò significa che l'LLM "ricorda" gli scambi precedenti nella sessione corrente, consentendo conversazioni più coerenti.
L'annotazione keepAlive: true
sul provider della sessione di chat garantisce che questo contesto persista per tutto il ciclo di vita dell'app. Questo contesto persistente è fondamentale per mantenere un flusso di conversazione naturale con l'LLM.
Passaggi successivi
A questo punto, puoi chiedere qualsiasi cosa all'API Gemini, poiché non sono previste limitazioni a ciò a cui risponderà. Ad esempio, potresti chiedergli un riepilogo delle Guerre delle Rose, che non è correlato allo scopo della tua app di colori.
Nel passaggio successivo, creerai un prompt di sistema per aiutare Gemini a interpretare le descrizioni dei colori in modo più efficace. Verrà dimostrato come personalizzare il comportamento di un LLM in base alle esigenze specifiche dell'applicazione e concentrarne le funzionalità sul dominio della tua app.
Risoluzione dei problemi
Problemi di configurazione di Firebase
Se riscontri errori con l'inizializzazione di Firebase:
- Assicurati che il file
firebase_options.dart
sia stato generato correttamente - Verifica di aver eseguito l'upgrade al piano Blaze per l'accesso a Vertex AI
Errori di accesso all'API
Se ricevi errori durante l'accesso all'API Gemini:
- Verificare che la fatturazione sia configurata correttamente nel progetto Firebase
- Verifica che Vertex AI e l'API Cloud AI siano abilitati nel progetto Firebase
- Controlla la connettività di rete e le impostazioni del firewall
- Verifica che il nome del modello (
gemini-2.0-flash
) sia corretto e disponibile
Problemi relativi al contesto della conversazione
Se noti che Gemini non ricorda il contesto precedente della chat:
- Verifica che la funzione
chatSession
sia annotata con@Riverpod(keepAlive: true)
- Verifica di riutilizzare la stessa sessione di chat per tutti gli scambi di messaggi
- Verifica che la sessione di chat sia inizializzata correttamente prima di inviare messaggi
Problemi specifici della piattaforma
Per problemi specifici della piattaforma:
- iOS/macOS: assicurati che i diritti appropriati siano impostati e che le versioni minime siano configurate
- Android: verifica che la versione minima dell'SDK sia impostata correttamente
- Controllare i messaggi di errore specifici della piattaforma nella console
Concetti chiave appresi
- Configurazione di Firebase in un'applicazione Flutter
- Configurazione di Vertex AI in Firebase per l'accesso a Gemini
- Creazione di provider Riverpod per i servizi asincroni
- Implementazione di un servizio di chat che comunica con un LLM
- Gestione degli stati asincroni dell'API (caricamento, errore, dati)
- Informazioni sul flusso di comunicazione e sulle sessioni di chat LLM
4. Prompt efficaci per le descrizioni dei colori
In questo passaggio, creerai e implementerai un prompt di sistema che guidi Gemini nell'interpretazione delle descrizioni dei colori. I prompt di sistema sono un modo efficace per personalizzare il comportamento dell'LLM per attività specifiche senza modificare il codice.
Che cosa imparerai in questo passaggio
- Informazioni sui prompt di sistema e sulla loro importanza nelle applicazioni LLM
- Creare prompt efficaci per attività specifiche del dominio
- Caricamento e utilizzo dei prompt di sistema in un'app Flutter
- Guidare un LLM a fornire risposte formattate in modo coerente
- Testare in che modo i prompt di sistema influiscono sul comportamento dell'LLM
Informazioni sui prompt di sistema
Prima di passare all'implementazione, scopriamo cosa sono i prompt di sistema e perché sono importanti:
Che cosa sono i prompt di sistema?
Un prompt di sistema è un tipo speciale di istruzione fornita a un LLM che imposta il contesto, le linee guida sul comportamento e le aspettative per le sue risposte. A differenza dei messaggi utente, i prompt di sistema:
- Stabilire il ruolo e la personalità dell'LLM
- Definire conoscenze o competenze specializzate
- Fornire istruzioni di formattazione
- Impostare vincoli sulle risposte
- Descrivere come gestire vari scenari
Pensa a un prompt di sistema come alla "descrizione del lavoro" dell'LLM: indica al modello come comportarsi durante la conversazione.
Perché i prompt di sistema sono importanti
I prompt di sistema sono fondamentali per creare interazioni LLM coerenti e utili perché:
- Garantire la coerenza: aiuta il modello a fornire risposte in un formato coerente
- Migliora la pertinenza: concentrati sul tuo dominio specifico (in questo caso, i colori)
- Stabilisci dei limiti: definisci cosa deve e non deve fare il modello
- Migliora l'esperienza utente: crea un pattern di interazione più naturale e utile
- Riduci il post-trattamento: ricevi le risposte in formati più facili da analizzare o visualizzare
Per la tua app Colorist, hai bisogno dell'LLM per interpretare in modo coerente le descrizioni dei colori e fornire valori RGB in un formato specifico.
Creare un asset prompt di sistema
Innanzitutto, dovrai creare un file prompt dei sistemi che verrà caricato in fase di esecuzione. Questo approccio ti consente di modificare la richiesta senza ricompilare l'app.
Crea un nuovo file assets/system_prompt.md
con i seguenti contenuti:
assets/system_prompt.md
# Colorist System Prompt
You are a color expert assistant integrated into a desktop app called Colorist. Your job is to interpret natural language color descriptions and provide the appropriate RGB values that best represent that description.
## Your Capabilities
You are knowledgeable about colors, color theory, and how to translate natural language descriptions into specific RGB values. When users describe a color, you should:
1. Analyze their description to understand the color they are trying to convey
2. Determine the appropriate RGB values (values should be between 0.0 and 1.0)
3. Respond with a conversational explanation and explicitly state the RGB values
## How to Respond to User Inputs
When users describe a color:
1. First, acknowledge their color description with a brief, friendly response
2. Interpret what RGB values would best represent that color description
3. Always include the RGB values clearly in your response, formatted as: `RGB: (red=X.X, green=X.X, blue=X.X)`
4. Provide a brief explanation of your interpretation
Example:
User: "I want a sunset orange"
You: "Sunset orange is a warm, vibrant color that captures the golden-red hues of the setting sun. It combines a strong red component with moderate orange tones.
RGB: (red=1.0, green=0.5, blue=0.25)
I've selected values with high red, moderate green, and low blue to capture that beautiful sunset glow. This creates a warm orange with a slightly reddish tint, reminiscent of the sun low on the horizon."
## When Descriptions are Unclear
If a color description is ambiguous or unclear, please ask the user clarifying questions, one at a time.
## Important Guidelines
- Always keep RGB values between 0.0 and 1.0
- Always format RGB values as: `RGB: (red=X.X, green=X.X, blue=X.X)` for easy parsing
- Provide thoughtful, knowledgeable responses about colors
- When possible, include color psychology, associations, or interesting facts about colors
- Be conversational and engaging in your responses
- Focus on being helpful and accurate with your color interpretations
Informazioni sulla struttura del prompt del sistema
Analizziamo cosa fa questo prompt:
- Definizione del ruolo: stabilisce l'LLM come "assistente esperto di colori"
- Spiegazione della task: definisce la task principale come l'interpretazione delle descrizioni dei colori in valori RGB
- Formato della risposta: specifica esattamente come devono essere formattati i valori RGB per garantire la coerenza
- Esempio di scambio: fornisce un esempio concreto del pattern di interazione previsto
- Gestione dei casi limite: spiega come gestire le descrizioni poco chiare
- Limiti e linee guida: imposta limiti, ad esempio mantenere i valori RGB compresi tra 0,0 e 1,0
Questo approccio strutturato garantisce che le risposte dell'LLM siano coerenti, informative e formattate in modo da essere facilmente analizzabili se vuoi estrarre i valori RGB in modo programmatico.
Aggiorna pubspec.yaml
Ora aggiorna la parte inferiore di pubspec.yaml
in modo da includere la directory degli asset:
pubspec.yaml
flutter:
uses-material-design: true
assets:
- assets/
Esegui flutter pub get
per aggiornare il bundle di asset.
Crea un provider di prompt di sistema
Crea un nuovo file lib/providers/system_prompt.dart
per caricare il prompt del sistema:
lib/providers/system_prompt.dart
import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'system_prompt.g.dart';
@riverpod
Future<String> systemPrompt(Ref ref) =>
rootBundle.loadString('assets/system_prompt.md');
Questo provider utilizza il sistema di caricamento delle risorse di Flutter per leggere il file prompt in fase di runtime.
Aggiorna il provider del modello Gemini
Ora modifica il file lib/providers/gemini.dart
per includere il prompt del sistema:
lib/providers/gemini.dart
import 'dart:async';
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_vertexai/firebase_vertexai.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import '../firebase_options.dart';
import 'system_prompt.dart'; // Add this import
part 'gemini.g.dart';
@riverpod
Future<FirebaseApp> firebaseApp(Ref ref) =>
Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
@riverpod
Future<GenerativeModel> geminiModel(Ref ref) async {
await ref.watch(firebaseAppProvider.future);
final systemPrompt = await ref.watch(systemPromptProvider.future); // Add this line
final model = FirebaseVertexAI.instance.generativeModel(
model: 'gemini-2.0-flash',
systemInstruction: Content.system(systemPrompt), // And this line
);
return model;
}
@Riverpod(keepAlive: true)
Future<ChatSession> chatSession(Ref ref) async {
final model = await ref.watch(geminiModelProvider.future);
return model.startChat();
}
La modifica principale è l'aggiunta di systemInstruction: Content.system(systemPrompt)
durante la creazione del modello generativo. In questo modo, Gemini utilizzerà le tue istruzioni come prompt di sistema per tutte le interazioni in questa sessione di chat.
Generare codice Riverpod
Esegui il comando del programma di esecuzione di compilazione per generare il codice Riverpod necessario:
dart run build_runner build --delete-conflicting-outputs
Esegui e testa l'applicazione
Ora esegui l'applicazione:
flutter run -d DEVICE
Prova a testarlo con varie descrizioni dei colori:
- "Vorrei un colore azzurro"
- "Fammi un verde bosco"
- "Creare un arancione tramonto intenso"
- "Voglio il colore della lavanda fresca"
- "Fammi vedere qualcosa di blu come il mare"
Dovresti notare che ora Gemini risponde con spiegazioni colloquiali sui colori e con valori RGB formattati in modo coerente. Il prompt del sistema ha guidato efficacemente l'LLM a fornire il tipo di risposte di cui hai bisogno.
Prova anche a chiedere contenuti al di fuori del contesto dei colori. Ad esempio, le cause principali delle guerre di religione inglesi. Dovresti notare una differenza rispetto al passaggio precedente.
L'importanza della progettazione di prompt per attività specializzate
I prompt di sistema sono sia arte che scienza. Sono un componente fondamentale dell'integrazione dell'LLM che può influire notevolmente sull'utilità del modello per la tua applicazione specifica. Hai eseguito una forma di progettazione dei prompt, ovvero hai personalizzato le istruzioni per fare in modo che il modello si comporti in modo da soddisfare le esigenze della tua applicazione.
Un prompt engineering efficace prevede:
- Definizione chiara del ruolo: stabilisci lo scopo del modello LLM
- Istruzioni esplicite: descrivono esattamente come deve rispondere l'LLM
- Esempi concreti: mostra, anziché solo descrivere, come sono le risposte efficaci
- Gestione dei casi limite: istruzione dell'LLM su come gestire scenari ambigui
- Specifiche di formattazione: assicurati che le risposte siano strutturate in modo coerente e utilizzabile
Il prompt di sistema che hai creato trasforma le funzionalità generiche di Gemini in un assistente specializzato per l'interpretazione dei colori che fornisce risposte formattate in modo specifico per le esigenze della tua applicazione. Si tratta di un modello efficace che puoi applicare a molti domini e attività diversi.
Passaggi successivi
Nel passaggio successivo, perfezionerai questa base aggiungendo dichiarazioni di funzioni, che consentono all'LLM non solo di suggerire valori RGB, ma anche di chiamare funzioni nella tua app per impostare direttamente il colore. Questo dimostra come gli LLM possono colmare il divario tra il linguaggio naturale e le funzionalità concrete delle applicazioni.
Risoluzione dei problemi
Problemi di caricamento degli asset
Se si verificano errori durante il caricamento della richiesta di sistema:
- Verifica che
pubspec.yaml
elenchi correttamente la directory degli asset - Verifica che il percorso in
rootBundle.loadString()
corrisponda alla posizione del file - Esegui
flutter clean
seguito daflutter pub get
per aggiornare il bundle di asset
Risposte incoerenti
Se l'LLM non segue costantemente le istruzioni di formattazione:
- Prova a rendere più espliciti i requisiti di formato nel prompt di sistema
- Aggiungi altri esempi per dimostrare il pattern previsto
- Assicurati che il formato richiesto sia ragionevole per il modello
Limitazione della frequenza delle richieste API
Se riscontri errori relativi al limite di frequenza:
- Tieni presente che il servizio Vertex AI ha limiti di utilizzo
- Valuta la possibilità di implementare la logica per i nuovi tentativi con backoff esponenziale (esercizio per il lettore)
- Controlla la Console Firebase per verificare la presenza di problemi relativi alla quota
Concetti chiave appresi
- Comprendere il ruolo e l'importanza dei prompt di sistema nelle applicazioni LLM
- Creare prompt efficaci con istruzioni, esempi e vincoli chiari
- Caricamento e utilizzo dei prompt di sistema in un'applicazione Flutter
- Guidare il comportamento dell'LLM per attività specifiche del dominio
- Utilizzo del prompt engineering per modellare le risposte degli LLM
Questo passaggio mostra come puoi ottenere una personalizzazione significativa del comportamento di LLM senza modificare il codice, semplicemente fornendo istruzioni chiare nel prompt del sistema.
5. Dichiarazioni di funzione per gli strumenti LLM
In questo passaggio, inizierai a consentire a Gemini di intervenire nella tua app implementando le dichiarazioni delle funzioni. Questa potente funzionalità consente all'LLM non solo di suggerire i valori RGB, ma anche di impostarli nell'interfaccia utente dell'app tramite chiamate di strumenti specializzati. Tuttavia, per visualizzare le richieste LLM eseguite nell'app Flutter sarà necessario il passaggio successivo.
Che cosa imparerai in questo passaggio
- Informazioni sulle chiamate di funzioni LLM e sui relativi vantaggi per le applicazioni Flutter
- Definizione di dichiarazioni di funzioni basate su schema per Gemini
- Integrazione delle dichiarazioni di funzione con il modello Gemini
- Aggiornamento della richiesta di sistema per utilizzare le funzionalità dello strumento
Informazioni sulle chiamate di funzione
Prima di implementare le dichiarazioni di funzione, scopriamo che cosa sono e perché sono importanti:
Che cos'è la chiamata di funzioni?
La chiamata di funzione (a volte chiamata "utilizzo di strumenti") è una funzionalità che consente a un LLM di:
- Riconoscere quando una richiesta dell'utente potrebbe trarre vantaggio dall'invocazione di una funzione specifica
- Genera un oggetto JSON strutturato con i parametri necessari per la funzione
- Consenti all'applicazione di eseguire la funzione con questi parametri
- Ricevere il risultato della funzione e incorporarlo nella risposta
Invece di descrivere semplicemente cosa fare, la chiamata di funzione consente all'LLM di attivare azioni concrete nella tua applicazione.
Perché le chiamate di funzione sono importanti per le app Flutter
La chiamata di funzioni crea un potente ponte tra il linguaggio naturale e le funzionalità dell'applicazione:
- Azione diretta: gli utenti possono descrivere ciò che vogliono in linguaggio naturale e l'app risponde con azioni concrete.
- Output strutturato: l'LLM produce dati puliti e strutturati anziché testo che deve essere analizzato
- Operazioni complesse: consente all'LLM di accedere a dati esterni, eseguire calcoli o modificare lo stato dell'applicazione
- Migliore esperienza utente: crea un'integrazione perfetta tra conversazione e funzionalità
Nell'app Colorist, la chiamata di funzioni consente agli utenti di dire "Voglio un verde bosco" e di aggiornare immediatamente l'interfaccia utente con quel colore, senza dover analizzare i valori RGB dal testo.
Definire le dichiarazioni di funzione
Crea un nuovo file lib/services/gemini_tools.dart
per definire le dichiarazioni delle funzioni:
lib/services/gemini_tools.dart
import 'package:firebase_vertexai/firebase_vertexai.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'gemini_tools.g.dart';
class GeminiTools {
GeminiTools(this.ref);
final Ref ref;
FunctionDeclaration get setColorFuncDecl => FunctionDeclaration(
'set_color',
'Set the color of the display square based on red, green, and blue values.',
parameters: {
'red': Schema.number(description: 'Red component value (0.0 - 1.0)'),
'green': Schema.number(description: 'Green component value (0.0 - 1.0)'),
'blue': Schema.number(description: 'Blue component value (0.0 - 1.0)'),
},
);
List<Tool> get tools => [
Tool.functionDeclarations([setColorFuncDecl]),
];
}
@riverpod
GeminiTools geminiTools(Ref ref) => GeminiTools(ref);
Informazioni sulle dichiarazioni di funzione
Analizziamo cosa fa questo codice:
- Nomi delle funzioni: assegna un nome alla funzione
set_color
per indicarne chiaramente lo scopo - Descrizione della funzione: fornisci una descrizione chiara che aiuti l'LLM a capire quando utilizzarla
- Definizioni dei parametri: definisci i parametri strutturati con le relative descrizioni:
red
: il componente rosso del colore RGB, specificato come numero compreso tra 0,0 e 1,0green
: il componente verde del colore RGB, specificato come numero compreso tra 0,0 e 1,0blue
: il componente blu del colore RGB, specificato come numero compreso tra 0,0 e 1,0
- Tipi di schema: utilizza
Schema.number()
per indicare che si tratta di valori numerici - Raccolta di strumenti: crei un elenco di strumenti contenente la dichiarazione della funzione
Questo approccio strutturato aiuta il modello LLM di Gemini a comprendere:
- Quando deve chiamare questa funzione
- Quali parametri deve fornire
- Quali vincoli si applicano a questi parametri (ad esempio l'intervallo di valori)
Aggiorna il provider del modello Gemini
Ora modifica il file lib/providers/gemini.dart
in modo da includere le dichiarazioni di funzione durante l'inizializzazione del modello Gemini:
lib/providers/gemini.dart
import 'dart:async';
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_vertexai/firebase_vertexai.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import '../firebase_options.dart';
import '../services/gemini_tools.dart'; // Add this import
import 'system_prompt.dart';
part 'gemini.g.dart';
@riverpod
Future<FirebaseApp> firebaseApp(Ref ref) =>
Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
@riverpod
Future<GenerativeModel> geminiModel(Ref ref) async {
await ref.watch(firebaseAppProvider.future);
final systemPrompt = await ref.watch(systemPromptProvider.future);
final geminiTools = ref.watch(geminiToolsProvider); // Add this line
final model = FirebaseVertexAI.instance.generativeModel(
model: 'gemini-2.0-flash',
systemInstruction: Content.system(systemPrompt),
tools: geminiTools.tools, // And this line
);
return model;
}
@Riverpod(keepAlive: true)
Future<ChatSession> chatSession(Ref ref) async {
final model = await ref.watch(geminiModelProvider.future);
return model.startChat();
}
La modifica principale è l'aggiunta del parametro tools: geminiTools.tools
durante la creazione del modello generativo. In questo modo, Gemini è a conoscenza delle funzioni che può chiamare.
Aggiorna la richiesta di sistema
Ora devi modificare il prompt del sistema per indicare all'LLM di utilizzare il nuovo strumento set_color
. Aggiornamento assets/system_prompt.md
:
assets/system_prompt.md
# Colorist System Prompt
You are a color expert assistant integrated into a desktop app called Colorist. Your job is to interpret natural language color descriptions and set the appropriate color values using a specialized tool.
## Your Capabilities
You are knowledgeable about colors, color theory, and how to translate natural language descriptions into specific RGB values. You have access to the following tool:
`set_color` - Sets the RGB values for the color display based on a description
## How to Respond to User Inputs
When users describe a color:
1. First, acknowledge their color description with a brief, friendly response
2. Interpret what RGB values would best represent that color description
3. Use the `set_color` tool to set those values (all values should be between 0.0 and 1.0)
4. After setting the color, provide a brief explanation of your interpretation
Example:
User: "I want a sunset orange"
You: "Sunset orange is a warm, vibrant color that captures the golden-red hues of the setting sun. It combines a strong red component with moderate orange tones."
[Then you would call the set_color tool with approximately: red=1.0, green=0.5, blue=0.25]
After the tool call: "I've set a warm orange with strong red, moderate green, and minimal blue components that is reminiscent of the sun low on the horizon."
## When Descriptions are Unclear
If a color description is ambiguous or unclear, please ask the user clarifying questions, one at a time.
## Important Guidelines
- Always keep RGB values between 0.0 and 1.0
- Provide thoughtful, knowledgeable responses about colors
- When possible, include color psychology, associations, or interesting facts about colors
- Be conversational and engaging in your responses
- Focus on being helpful and accurate with your color interpretations
Le modifiche principali al prompt del sistema sono:
- Introduzione allo strumento: anziché chiedere valori RGB formattati, ora devi indicare allo strumento LLM lo strumento
set_color
- Procedura modificata: modifica il passaggio 3 da "formatta i valori nella risposta" in "utilizza lo strumento per impostare i valori"
- Esempio aggiornato: mostri come la risposta debba includere una chiamata allo strumento anziché testo formattato
- Requisito di formattazione rimosso: poiché utilizzi chiamate di funzioni strutturate, non è più necessario un formato di testo specifico
Questo prompt aggiornato indica all'LLM di utilizzare la chiamata di funzioni anziché fornire semplicemente i valori RGB in formato di testo.
Generare codice Riverpod
Esegui il comando del runner di compilazione per generare il codice Riverpod necessario:
dart run build_runner build --delete-conflicting-outputs
Eseguire l'applicazione
A questo punto, Gemini genererà contenuti che tentano di utilizzare le chiamate di funzione, ma non hai ancora implementato i relativi gestori. Quando esegui l'app e descrivi un colore, vedrai che Gemini risponde come se avesse richiamato uno strumento, ma non vedrai alcuna modifica del colore nell'interfaccia utente fino al passaggio successivo.
Esegui l'app:
flutter run -d DEVICE
Prova a descrivere un colore come "blu oceano intenso" o "verde foresta" e osserva le risposte. L'LLM sta tentando di chiamare le funzioni definite sopra, ma il codice non rileva ancora le chiamate di funzione.
La procedura di chiamata di funzione
Vediamo cosa succede quando Gemini utilizza la chiamata di funzione:
- Selezione della funzione: l'LLM decide se una chiamata di funzione sarebbe utile in base alla richiesta dell'utente.
- Generare i parametri: l'LLM genera valori dei parametri che si adattano allo schema della funzione
- Formato di chiamata di funzione: l'LLM invia un oggetto di chiamata di funzione strutturato nella risposta
- Gestione dell'applicazione: l'app riceverà questa chiamata ed eseguirà la funzione pertinente (implementata nel passaggio successivo).
- Integrazione della risposta: nelle conversazioni con più turni, l'LLM si aspetta che venga restituito il risultato della funzione
Nell'attuale stato dell'app, vengono eseguiti i primi tre passaggi, ma non hai ancora implementato il passaggio 4 o 5 (gestione delle chiamate di funzione), che eseguirai nel passaggio successivo.
Dettagli tecnici: come Gemini decide quando utilizzare le funzioni
Gemini prende decisioni intelligenti su quando utilizzare le funzioni in base a:
- Intento dell'utente: indica se la richiesta dell'utente è meglio soddisfatta da una funzione
- Rilevanza della funzione: il grado di corrispondenza delle funzioni disponibili all'attività
- Disponibilità dei parametri: indica se è possibile determinare con certezza i valori dei parametri
- Istruzioni di sistema: indicazioni del prompt di sistema sull'utilizzo delle funzioni
Fornendo dichiarazioni di funzioni e istruzioni di sistema chiare, hai configurato Gemini in modo che riconosca le richieste di descrizione dei colori come opportunità per chiamare la funzione set_color
.
Passaggi successivi
Nel passaggio successivo, implementerai i gestori per le chiamate di funzione provenienti da Gemini. In questo modo, il cerchio sarà completo e le descrizioni degli utenti attiveranno le modifiche effettive dei colori nell'interfaccia utente tramite le chiamate alle funzioni dell'LLM.
Risoluzione dei problemi
Problemi con la dichiarazione di funzione
Se riscontri errori con le dichiarazioni di funzione:
- Verifica che i nomi e i tipi di parametro corrispondano a quanto previsto
- Verifica che il nome della funzione sia chiaro e descrittivo
- Assicurati che la descrizione della funzione ne spieghi con precisione lo scopo
Problemi relativi al prompt del sistema
Se l'LLM non tenta di utilizzare la funzione:
- Verifica che il prompt del sistema indichi chiaramente all'LLM di utilizzare lo strumento
set_color
- Verifica che l'esempio nella richiesta di sistema dimostri l'utilizzo della funzione
- Prova a rendere più esplicite le istruzioni per l'utilizzo dello strumento
Problemi generici
Se riscontri altri problemi:
- Controlla la console per verificare la presenza di errori relativi alle dichiarazioni di funzione
- Verifica che gli strumenti vengano trasmessi correttamente al modello
- Assicurati che tutto il codice generato da Riverpod sia aggiornato
Concetti chiave appresi
- Definire le dichiarazioni di funzione per estendere le funzionalità LLM nelle app Flutter
- Creazione di schemi di parametri per la raccolta dei dati strutturati
- Integrazione delle dichiarazioni di funzione con il modello Gemini
- Aggiornamento delle richieste di sistema per incoraggiare l'utilizzo delle funzioni
- Informazioni su come gli LLM selezionano e chiamano le funzioni
Questo passaggio dimostra come gli LLM possono colmare il divario tra l'input in linguaggio naturale e le chiamate di funzioni strutturate, gettando le basi per un'integrazione perfetta tra le funzionalità di conversazione e di applicazione.
6. Implementazione della gestione degli strumenti
In questo passaggio, implementerai i gestori per le chiamate di funzione provenienti da Gemini. In questo modo si completa il circolo di comunicazione tra input in linguaggio naturale e funzionalità applicative concrete, consentendo all'LLM di manipolare direttamente l'interfaccia utente in base alle descrizioni degli utenti.
Che cosa imparerai in questo passaggio
- Informazioni sulla pipeline completa di chiamata di funzioni nelle applicazioni LLM
- Elaborazione delle chiamate di funzione da Gemini in un'applicazione Flutter
- Implementazione di gestori di funzioni che modificano lo stato dell'applicazione
- Gestione delle risposte alle funzioni e restituzione dei risultati all'LLM
- Creazione di un flusso di comunicazione completo tra LLM e UI
- Registrazione di chiamate e risposte alle funzioni per la trasparenza
Informazioni sulla pipeline di chiamata delle funzioni
Prima di esaminare l'implementazione, comprendiamo la pipeline di chiamata di funzioni completa:
Il flusso end-to-end
- Input utente: l'utente descrive un colore in linguaggio naturale (ad es. "verde bosco")
- Elaborazione LLM: Gemini analizza la descrizione e decide di chiamare la funzione
set_color
- Generare chiamate di funzione: Gemini crea un JSON strutturato con parametri (valori rosso, verde, blu)
- Ricezione di chiamate di funzione: la tua app riceve questi dati strutturati da Gemini
- Esecuzione della funzione: l'app esegue la funzione con i parametri forniti
- Aggiornamento stato: la funzione aggiorna lo stato dell'app (modificando il colore visualizzato).
- Generare una risposta: la funzione restituisce i risultati all'LLM
- Incorporazione della risposta: l'LLM incorpora questi risultati nella risposta finale
- Aggiornamento dell'interfaccia utente: l'interfaccia utente reagisce alla modifica dello stato, mostrando il nuovo colore
Il ciclo di comunicazione completo è essenziale per un'integrazione LLM corretta. Quando un LLM effettua una chiamata di funzione, non invia semplicemente la richiesta e passa alla successiva. ma attende che l'applicazione esegua la funzione e restituisca i risultati. L'LLM utilizza quindi questi risultati per formulare la risposta finale, creando un flusso di conversazione naturale che conferma le azioni intraprese.
Implementare i gestori delle funzioni
Aggiorna il file lib/services/gemini_tools.dart
per aggiungere gestori per le chiamate di funzione:
lib/services/gemini_tools.dart
import 'package:colorist_ui/colorist_ui.dart';
import 'package:firebase_vertexai/firebase_vertexai.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'gemini_tools.g.dart';
class GeminiTools {
GeminiTools(this.ref);
final Ref ref;
FunctionDeclaration get setColorFuncDecl => FunctionDeclaration(
'set_color',
'Set the color of the display square based on red, green, and blue values.',
parameters: {
'red': Schema.number(description: 'Red component value (0.0 - 1.0)'),
'green': Schema.number(description: 'Green component value (0.0 - 1.0)'),
'blue': Schema.number(description: 'Blue component value (0.0 - 1.0)'),
},
);
List<Tool> get tools => [
Tool.functionDeclarations([setColorFuncDecl]),
];
Map<String, Object?> handleFunctionCall( // Add from here
String functionName,
Map<String, Object?> arguments,
) {
final logStateNotifier = ref.read(logStateNotifierProvider.notifier);
logStateNotifier.logFunctionCall(functionName, arguments);
return switch (functionName) {
'set_color' => handleSetColor(arguments),
_ => handleUnknownFunction(functionName),
};
}
Map<String, Object?> handleSetColor(Map<String, Object?> arguments) {
final colorStateNotifier = ref.read(colorStateNotifierProvider.notifier);
final red = (arguments['red'] as num).toDouble();
final green = (arguments['green'] as num).toDouble();
final blue = (arguments['blue'] as num).toDouble();
final functionResults = {
'success': true,
'current_color':
colorStateNotifier
.updateColor(red: red, green: green, blue: blue)
.toLLMContextMap(),
};
final logStateNotifier = ref.read(logStateNotifierProvider.notifier);
logStateNotifier.logFunctionResults(functionResults);
return functionResults;
}
Map<String, Object?> handleUnknownFunction(String functionName) {
final logStateNotifier = ref.read(logStateNotifierProvider.notifier);
logStateNotifier.logWarning('Unsupported function call $functionName');
return {
'success': false,
'reason': 'Unsupported function call $functionName',
};
} // To here.
}
@riverpod
GeminiTools geminiTools(Ref ref) => GeminiTools(ref);
Informazioni sugli handler delle funzioni
Vediamo cosa fanno questi gestori di funzioni:
handleFunctionCall
: un gestore centrale che:- Registra la chiamata di funzione per la trasparenza nel riquadro dei log
- Inoltra le richieste all'handler appropriato in base al nome della funzione
- Restituisce una risposta strutturata che verrà inviata nuovamente all'LLM
handleSetColor
: il gestore specifico per la funzioneset_color
che:- Estrae i valori RGB dalla mappa degli argomenti
- Li converte nei tipi previsti (doppi).
- Aggiorna lo stato del colore dell'applicazione utilizzando
colorStateNotifier
- Crea una risposta strutturata con lo stato di esito e le informazioni sui colori correnti
- Registra i risultati della funzione per il debug
handleUnknownFunction
: un gestore di riserva per le funzioni sconosciute che:- Registra un avviso relativo alla funzione non supportata
- Restituisce una risposta di errore all'LLM
La funzione handleSetColor
è particolarmente importante perché colma il divario tra la comprensione del linguaggio naturale dell'LLM e le modifiche concrete dell'interfaccia utente.
Aggiorna il servizio di chat di Gemini per elaborare le chiamate e le risposte alle funzioni
Ora aggiorna il file lib/services/gemini_chat_service.dart
per elaborare le chiamate di funzione dalle risposte dell'LLM e inviare i risultati all'LLM:
lib/services/gemini_chat_service.dart
import 'dart:async';
import 'package:colorist_ui/colorist_ui.dart';
import 'package:firebase_vertexai/firebase_vertexai.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import '../providers/gemini.dart';
import 'gemini_tools.dart'; // Add this import
part 'gemini_chat_service.g.dart';
class GeminiChatService {
GeminiChatService(this.ref);
final Ref ref;
Future<void> sendMessage(String message) async {
final chatSession = await ref.read(chatSessionProvider.future);
final chatStateNotifier = ref.read(chatStateNotifierProvider.notifier);
final logStateNotifier = ref.read(logStateNotifierProvider.notifier);
chatStateNotifier.addUserMessage(message);
logStateNotifier.logUserText(message);
final llmMessage = chatStateNotifier.createLlmMessage();
try {
final response = await chatSession.sendMessage(Content.text(message));
final responseText = response.text;
if (responseText != null) {
logStateNotifier.logLlmText(responseText);
chatStateNotifier.appendToMessage(llmMessage.id, responseText);
}
if (response.functionCalls.isNotEmpty) { // Add from here
final geminiTools = ref.read(geminiToolsProvider);
final functionResultResponse = await chatSession.sendMessage(
Content.functionResponses([
for (final functionCall in response.functionCalls)
FunctionResponse(
functionCall.name,
geminiTools.handleFunctionCall(
functionCall.name,
functionCall.args,
),
),
]),
);
final responseText = functionResultResponse.text;
if (responseText != null) {
logStateNotifier.logLlmText(responseText);
chatStateNotifier.appendToMessage(llmMessage.id, responseText);
}
} // To here.
} catch (e, st) {
logStateNotifier.logError(e, st: st);
chatStateNotifier.appendToMessage(
llmMessage.id,
"\nI'm sorry, I encountered an error processing your request. "
"Please try again.",
);
} finally {
chatStateNotifier.finalizeMessage(llmMessage.id);
}
}
}
@riverpod
GeminiChatService geminiChatService(Ref ref) => GeminiChatService(ref);
Informazioni sul flusso di comunicazione
L'aggiunta principale è la gestione completa delle chiamate e delle risposte alle funzioni:
if (response.functionCalls.isNotEmpty) {
final geminiTools = ref.read(geminiToolsProvider);
final functionResultResponse = await chatSession.sendMessage(
Content.functionResponses([
for (final functionCall in response.functionCalls)
FunctionResponse(
functionCall.name,
geminiTools.handleFunctionCall(
functionCall.name,
functionCall.args,
),
),
]),
);
final responseText = functionResultResponse.text;
if (responseText != null) {
logStateNotifier.logLlmText(responseText);
chatStateNotifier.appendToMessage(llmMessage.id, responseText);
}
}
Questo codice:
- Controlla se la risposta LLM contiene chiamate di funzione
- Per ogni chiamata di funzione, invoca il metodo
handleFunctionCall
con il nome e gli argomenti della funzione - Raccoglie i risultati di ogni chiamata di funzione
- Invia questi risultati all'LLM utilizzando
Content.functionResponses
- Elabora la risposta dell'LLM ai risultati della funzione
- Aggiorna l'interfaccia utente con il testo della risposta finale
Viene creato un flusso di andata e ritorno:
- Utente → LLM: richiede un colore
- LLM → App: chiamate di funzioni con parametri
- App → Utente: nuovo colore visualizzato
- App → LLM: risultati della funzione
- LLM → Utente: risposta finale che incorpora i risultati della funzione
Generare codice Riverpod
Esegui il comando del programma di esecuzione di compilazione per generare il codice Riverpod necessario:
dart run build_runner build --delete-conflicting-outputs
Esegui e testa il flusso completo
Ora esegui l'applicazione:
flutter run -d DEVICE
Prova a inserire varie descrizioni dei colori:
- "Vorrei un rosso cremisi intenso"
- "Fammi vedere un blu cielo rilassante"
- "Dammi il colore delle foglie di menta fresca"
- "Voglio vedere un arancione caldo di tramonto"
- "Rendi il colore un viola reale intenso"
Ora dovresti vedere:
- Il messaggio visualizzato nell'interfaccia della chat
- La risposta di Gemini visualizzata nella chat
- Chiamate di funzione registrate nel riquadro dei log
- I risultati della funzione vengono registrati immediatamente dopo
- Il rettangolo di colore si aggiorna per mostrare il colore descritto
- Aggiornamento dei valori RGB per mostrare i componenti del nuovo colore
- Viene visualizzata la risposta finale di Gemini, spesso con un commento sul colore impostato
Il riquadro dei log fornisce informazioni su cosa succede dietro le quinte. Visualizzerai:
- Le chiamate di funzione esatte effettuate da Gemini
- I parametri scelti per ogni valore RGB
- I risultati restituiti dalla funzione
- Le risposte di Gemini
L'indicatore dello stato del colore
Il colorStateNotifier
che stai utilizzando per aggiornare i colori fa parte del pacchetto colorist_ui
. Gestisce:
- Il colore corrente visualizzato nell'interfaccia utente
- La cronologia dei colori (ultimi 10 colori)
- Notifica delle modifiche dello stato ai componenti dell'interfaccia utente
Quando chiami updateColor
con nuovi valori RGB, la funzione:
- Crea un nuovo oggetto
ColorData
con i valori forniti - Aggiorna il colore corrente nello stato dell'app
- Aggiunge il colore alla cronologia
- Attiva gli aggiornamenti dell'interfaccia utente tramite la gestione dello stato di Riverpod
I componenti dell'interfaccia utente nel pacchetto colorist_ui
monitorano questo stato e si aggiornano automaticamente quando cambia, creando un'esperienza reattiva.
Informazioni sulla gestione degli errori
L'implementazione include una gestione degli errori efficace:
- Blocco try-catch: racchiude tutte le interazioni con l'LLM per rilevare eventuali eccezioni
- Registrazione degli errori: registra gli errori nel riquadro dei log con le tracce dello stack
- Feedback degli utenti: fornisce un messaggio di errore facile da capire nella chat
- Pulizia dello stato: completa lo stato del messaggio anche se si verifica un errore
In questo modo, l'app rimane stabile e fornisce un feedback appropriato anche in caso di problemi con l'esecuzione del servizio o della funzione LLM.
La potenza delle chiamate di funzione per l'esperienza utente
Ciò che hai realizzato qui dimostra come gli LLM possono creare interfacce naturali efficaci:
- Interfaccia di linguaggio naturale: gli utenti esprimono l'intent in linguaggio quotidiano
- Interpretazione intelligente: l'LLM traduce descrizioni vaghe in valori precisi
- Manipolazione diretta: l'interfaccia utente si aggiorna in risposta al linguaggio naturale
- Risposte contestuali: l'LLM fornisce il contesto conversazionale delle modifiche
- Carico cognitivo ridotto: gli utenti non devono comprendere i valori RGB o la teoria del colore
Questo modello di utilizzo delle chiamate di funzione LLM per colmare il divario tra il linguaggio naturale e le azioni dell'interfaccia utente può essere esteso a innumerevoli altri domini oltre alla selezione dei colori.
Passaggi successivi
Nel passaggio successivo migliorerai l'esperienza utente implementando le risposte in streaming. Anziché attendere la risposta completa, elaborerai i blocchi di testo e le chiamate di funzione man mano che vengono ricevuti, creando un'applicazione più reattiva e coinvolgente.
Risoluzione dei problemi
Problemi relativi alle chiamate di funzione
Se Gemini non chiama le funzioni o i parametri non sono corretti:
- Verifica che la dichiarazione della funzione corrisponda a quanto descritto nel prompt del sistema
- Verifica che i nomi e i tipi di parametro siano coerenti
- Assicurati che il prompt di sistema indichi esplicitamente all'LLM di utilizzare lo strumento
- Verifica che il nome della funzione nel gestore corrisponda esattamente a quello indicato nella dichiarazione
- Esamina il riquadro del log per informazioni dettagliate sulle chiamate di funzione
Problemi di risposta della funzione
Se i risultati della funzione non vengono inviati correttamente all'LLM:
- Verifica che la funzione restituisca una mappa formattata correttamente
- Verifica che Content.functionResponses venga costruito correttamente
- Cerca eventuali errori nel log relativi alle risposte della funzione
- Assicurati di utilizzare la stessa sessione di chat per la risposta
Problemi di visualizzazione dei colori
Se i colori non vengono visualizzati correttamente:
- Assicurati che i valori RGB vengano convertiti correttamente in numeri doppi (LLM potrebbe inviarli come numeri interi)
- Verifica che i valori rientrino nell'intervallo previsto (da 0,0 a 1,0)
- Verifica che l'indicatore dello stato del colore venga chiamato correttamente
- Esamina il log per i valori esatti trasmessi alla funzione
Problemi generali
Per problemi generali:
- Esamina i log per verificare la presenza di errori o avvisi
- Verificare la connettività di Vertex AI in Firebase
- Verifica la presenza di mancate corrispondenze di tipo nei parametri della funzione
- Assicurati che tutto il codice generato da Riverpod sia aggiornato
Concetti chiave appresi
- Implementazione di una pipeline di chiamata di funzioni completa in Flutter
- Creazione di una comunicazione completa tra un modello LLM e la tua applicazione
- Elaborazione dei dati strutturati dalle risposte LLM
- Invio dei risultati della funzione all'LLM per l'incorporazione nelle risposte
- Utilizzo del riquadro dei log per ottenere visibilità sulle interazioni tra LLM e le applicazioni
- Collegamento degli input in linguaggio naturale a modifiche concrete dell'interfaccia utente
Una volta completato questo passaggio, la tua app mostrerà uno dei pattern più efficaci per l'integrazione di LLM: la traduzione degli input in linguaggio naturale in azioni UI concrete, mantenendo al contempo una conversazione coerente che ne conferma l'esecuzione. In questo modo viene creata un'interfaccia di conversazione intuitiva che sembra magica per gli utenti.
7. Risposte dinamiche per un'esperienza utente migliore
In questo passaggio, migliorerai l'esperienza utente implementando le risposte in streaming di Gemini. Anziché attendere la generazione dell'intera risposta, elaborerai i blocchi di testo e le chiamate alle funzioni man mano che vengono ricevuti, creando un'applicazione più reattiva e coinvolgente.
Argomenti trattati in questo passaggio
- L'importanza dello streaming per le applicazioni basate su LLM
- Implementazione di risposte LLM in streaming in un'applicazione Flutter
- Elaborazione di blocchi di testo parziali man mano che arrivano dall'API
- Gestione dello stato della conversazione per evitare conflitti di messaggi
- Gestione delle chiamate di funzione nelle risposte dinamiche
- Creazione di indicatori visivi per le risposte in corso
Perché lo streaming è importante per le applicazioni LLM
Prima di procedere all'implementazione, scopriamo perché le risposte in streaming sono fondamentali per creare esperienze utente eccellenti con gli LLM:
Esperienza utente migliorata
Le risposte in streaming offrono diversi vantaggi significativi per l'esperienza utente:
- Latenza percepita ridotta: gli utenti vedono il testo iniziare a comparire immediatamente (in genere entro 100-300 ms), anziché attendere diversi secondi per una risposta completa. Questa percezione di immediatezza migliora notevolmente la soddisfazione degli utenti.
- Ritmo di conversazione naturale: la comparsa graduale del testo imita il modo in cui le persone comunicano, creando un'esperienza di dialogo più naturale.
- Elaborazione progressiva delle informazioni: gli utenti possono iniziare a elaborare le informazioni man mano che arrivano, anziché essere sopraffatti da un grande blocco di testo tutto in una volta.
- Opportunità di interruzione anticipata: in un'applicazione completa, gli utenti potrebbero potenzialmente interrompere o reindirizzare l'LLM se ritengono che stia andando in una direzione non utile.
- Conferma visiva dell'attività: il testo in streaming fornisce un feedback immediato sul funzionamento del sistema, riducendo l'incertezza.
Vantaggi tecnici
Oltre ai miglioramenti dell'esperienza utente, lo streaming offre vantaggi tecnici:
- Esecuzione anticipata delle funzioni: le chiamate alle funzioni possono essere rilevate ed eseguite non appena vengono visualizzate nello stream, senza attendere la risposta completa.
- Aggiornamenti incrementali dell'interfaccia utente: puoi aggiornare l'interfaccia utente progressivamente man mano che arrivano nuove informazioni, creando un'esperienza più dinamica.
- Gestione dello stato della conversazione: lo streaming fornisce indicatori chiari su quando le risposte sono complete e quando sono ancora in corso, consentendo una migliore gestione dello stato.
- Rischi di timeout ridotti: con le risposte non in streaming, le generazioni a lungo termine rischiano di causare timeout della connessione. Lo streaming stabilisce la connessione in anticipo e la mantiene.
Per la tua app Colorist, l'implementazione dello streaming significa che gli utenti vedranno le risposte di testo e le modifiche ai colori più rapidamente, creando un'esperienza molto più reattiva.
Aggiungere la gestione dello stato della conversazione
Per prima cosa, aggiungiamo un provider dello stato per monitorare se l'app sta attualmente gestendo una risposta in streaming. Aggiorna il file lib/services/gemini_chat_service.dart
:
lib/services/gemini_chat_service.dart
import 'dart:async';
import 'package:colorist_ui/colorist_ui.dart';
import 'package:firebase_vertexai/firebase_vertexai.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import '../providers/gemini.dart';
import 'gemini_tools.dart';
part 'gemini_chat_service.g.dart';
final conversationStateProvider = StateProvider( // Add from here...
(ref) => ConversationState.idle,
); // To here.
class GeminiChatService {
GeminiChatService(this.ref);
final Ref ref;
Future<void> sendMessage(String message) async {
final chatSession = await ref.read(chatSessionProvider.future);
final conversationState = ref.read(conversationStateProvider); // Add this line
final chatStateNotifier = ref.read(chatStateNotifierProvider.notifier);
final logStateNotifier = ref.read(logStateNotifierProvider.notifier);
if (conversationState == ConversationState.busy) { // Add from here...
logStateNotifier.logWarning(
"Can't send a message while a conversation is in progress",
);
throw Exception(
"Can't send a message while a conversation is in progress",
);
}
final conversationStateNotifier = ref.read(
conversationStateProvider.notifier,
);
conversationStateNotifier.state = ConversationState.busy; // To here.
chatStateNotifier.addUserMessage(message);
logStateNotifier.logUserText(message);
final llmMessage = chatStateNotifier.createLlmMessage();
try { // Modify from here...
final responseStream = chatSession.sendMessageStream(
Content.text(message),
);
await for (final block in responseStream) {
await _processBlock(block, llmMessage.id);
} // To here.
} catch (e, st) {
logStateNotifier.logError(e, st: st);
chatStateNotifier.appendToMessage(
llmMessage.id,
"\nI'm sorry, I encountered an error processing your request. "
"Please try again.",
);
} finally {
chatStateNotifier.finalizeMessage(llmMessage.id);
conversationStateNotifier.state = ConversationState.idle; // Add this line.
}
}
Future<void> _processBlock( // Add from here...
GenerateContentResponse block,
String llmMessageId,
) async {
final chatSession = await ref.read(chatSessionProvider.future);
final chatStateNotifier = ref.read(chatStateNotifierProvider.notifier);
final logStateNotifier = ref.read(logStateNotifierProvider.notifier);
final blockText = block.text;
if (blockText != null) {
logStateNotifier.logLlmText(blockText);
chatStateNotifier.appendToMessage(llmMessageId, blockText);
}
if (block.functionCalls.isNotEmpty) {
final geminiTools = ref.read(geminiToolsProvider);
final responseStream = chatSession.sendMessageStream(
Content.functionResponses([
for (final functionCall in block.functionCalls)
FunctionResponse(
functionCall.name,
geminiTools.handleFunctionCall(
functionCall.name,
functionCall.args,
),
),
]),
);
await for (final response in responseStream) {
final responseText = response.text;
if (responseText != null) {
logStateNotifier.logLlmText(responseText);
chatStateNotifier.appendToMessage(llmMessageId, responseText);
}
}
}
} // To here.
}
@riverpod
GeminiChatService geminiChatService(Ref ref) => GeminiChatService(ref);
Informazioni sull'implementazione dello streaming
Analizziamo cosa fa questo codice:
- Monitoraggio dello stato della conversazione:
- Un
conversationStateProvider
monitora se l'app sta attualmente elaborando una risposta - Lo stato passa da
idle
abusy
durante l'elaborazione e poi torna aidle
- In questo modo si evitano più richieste concorrenti che potrebbero entrare in conflitto
- Un
- Inizializzazioe dello stream:
sendMessageStream()
restituisce uno stream di chunk di risposta anziché un Future con la risposta completa- Ogni chunk può contenere testo, chiamate di funzioni o entrambi
- Elaborazione progressiva:
await for
elabora ogni chunk non appena arriva in tempo reale- Il testo viene aggiunto immediatamente all'interfaccia utente, creando l'effetto di streaming
- Le chiamate di funzione vengono eseguite non appena vengono rilevate
- Gestione delle chiamate di funzione:
- Quando viene rilevata una chiamata di funzione in un chunk, viene eseguita immediatamente
- I risultati vengono inviati nuovamente all'LLM tramite un'altra chiamata in streaming
- Anche la risposta dell'LLM a questi risultati viene elaborata in streaming
- Gestione degli errori e pulizia:
try
/catch
offre una gestione degli errori affidabile- Il blocco
finally
garantisce che lo stato della conversazione venga reimpostato correttamente - Il messaggio viene sempre finalizzato, anche se si verificano errori
Questa implementazione crea un'esperienza di streaming affidabile e reattiva, mantenendo allo stesso tempo lo stato corretto della conversazione.
Aggiornare la schermata principale per collegare lo stato della conversazione
Modifica il file lib/main.dart
per passare lo stato della conversazione alla schermata principale:
lib/main.dart
import 'package:colorist_ui/colorist_ui.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'providers/gemini.dart';
import 'services/gemini_chat_service.dart';
void main() async {
runApp(ProviderScope(child: MainApp()));
}
class MainApp extends ConsumerWidget {
const MainApp({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final model = ref.watch(geminiModelProvider);
final conversationState = ref.watch(conversationStateProvider); // Add this line
return MaterialApp(
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
),
home: model.when(
data:
(data) => MainScreen(
conversationState: conversationState, // And this line
sendMessage: (text) {
ref.read(geminiChatServiceProvider).sendMessage(text);
},
),
loading: () => LoadingScreen(message: 'Initializing Gemini Model'),
error: (err, st) => ErrorScreen(error: err),
),
);
}
}
La modifica principale qui è il passaggio di conversationState
al widget MainScreen
. MainScreen
(fornito dal pacchetto colorist_ui
) utilizzerà questo stato per disattivare l'inserimento di testo durante l'elaborazione di una risposta.
In questo modo viene creata un'esperienza utente coerente in cui l'interfaccia utente riflette lo stato corrente della conversazione.
Generare codice Riverpod
Esegui il comando del programma di esecuzione di compilazione per generare il codice Riverpod necessario:
dart run build_runner build --delete-conflicting-outputs
Esegui e testa le risposte dinamiche
Esegui l'applicazione:
flutter run -d DEVICE
Ora prova a testare il comportamento dello streaming con varie descrizioni dei colori. Prova descrizioni come:
- "Fammi vedere il colore verde smeraldo dell'oceano al crepuscolo"
- "Vorrei vedere un corallo vivace che mi ricordi i fiori tropicali"
- "Crea un verde oliva tenue come quello delle vecchie divise dell'esercito"
Il flusso tecnico dello streaming in dettaglio
Vediamo esattamente cosa succede durante lo streaming di una risposta:
Creazione della connessione
Quando chiami sendMessageStream()
, si verifica quanto segue:
- L'app stabilisce una connessione al servizio Vertex AI
- La richiesta dell'utente viene inviata al servizio
- Il server inizia a elaborare la richiesta
- La connessione dello stream rimane aperta, pronta a trasmettere i chunk
Trasmissione di chunk
Quando Gemini genera contenuti, i chunk vengono inviati tramite lo stream:
- Il server invia i blocchi di testo man mano che vengono generati (in genere alcune parole o frasi).
- Quando Gemini decide di effettuare una chiamata di funzione, invia le informazioni sulla chiamata di funzione
- Potrebbero seguire altri blocchi di testo dopo le chiamate alle funzioni
- Lo stream continua fino al completamento della generazione
Elaborazione progressiva
L'app elabora ogni chunk in modo incrementale:
- Ogni blocco di testo viene aggiunto alla risposta esistente
- Le chiamate di funzione vengono eseguite non appena vengono rilevate
- L'interfaccia utente si aggiorna in tempo reale con i risultati di testo e funzione
- Lo stato viene monitorato per mostrare che la risposta è ancora in streaming
Completamento dello stream
Al termine della generazione:
- Lo stream viene chiuso dal server
- Il loop
await for
esce in modo naturale - Il messaggio è contrassegnato come completato
- Lo stato della conversazione viene reimpostato su inattivo
- L'interfaccia utente si aggiorna per riflettere lo stato completato
Confronto tra streaming e non streaming
Per comprendere meglio i vantaggi dello streaming, confrontiamo gli approcci di streaming e non:
Aspetto | Non in streaming | Streaming |
Latenza percepita | L'utente non vede nulla finché la risposta completa non è pronta | L'utente vede le prime parole entro millisecondi |
Esperienza utente | Lunga attesa seguita dalla comparsa improvvisa del testo | Aspetto del testo naturale e progressivo |
Gestione dello stato | Più semplice (i messaggi sono in attesa o completi) | Più complesse (i messaggi possono essere in uno stato di streaming) |
Esecuzione della funzione | Si verifica solo dopo una risposta completa | Si verifica durante la generazione della risposta |
Complessità di implementazione | Più semplice da implementare | Richiede una gestione dello stato aggiuntiva |
Recupero degli errori | Risposta di tipo tutto o niente | Le risposte parziali possono comunque essere utili |
Complessità del codice | Meno complessa | Più complessa a causa della gestione degli stream |
Per un'applicazione come Colorist, i vantaggi dell'esperienza utente dello streaming superano la complessità di implementazione, in particolare per le interpretazioni dei colori che potrebbero richiedere diversi secondi per essere generate.
Best practice per l'esperienza utente in streaming
Quando implementi lo streaming nelle tue applicazioni LLM, tieni presente queste best practice:
- Indicatori visivi chiari: fornisci sempre indicatori visivi chiari che distinguano i messaggi in streaming da quelli completi.
- Blocco input: disattiva l'input dell'utente durante lo streaming per evitare più richieste sovrapposte
- Ripristino degli errori: progetta l'interfaccia utente in modo che gestisca il recupero graduale se lo streaming viene interrotto
- Transizioni di stato: assicurati transizioni fluide tra gli stati inattivo, in streaming e completato
- Visualizzazione dell'avanzamento: valuta la possibilità di utilizzare animazioni o indicatori discreti che mostrino l'elaborazione attiva
- Opzioni di annullamento: in un'app completa, offri agli utenti modi per annullare le generazioni in corso
- Integrazione dei risultati delle funzioni: progetta l'interfaccia utente per gestire i risultati delle funzioni visualizzati durante lo stream
- Ottimizzazione delle prestazioni: riduci al minimo le ricostruzioni dell'interfaccia utente durante gli aggiornamenti rapidi dello stream
Il pacchetto colorist_ui
implementa molte di queste best practice per te, ma sono considerazioni importanti per qualsiasi implementazione di LLM in streaming.
Passaggi successivi
Nel passaggio successivo, implementerai la sincronizzazione LLM inviando una notifica a Gemini quando gli utenti selezionano i colori dalla cronologia. In questo modo verrà creata un'esperienza più coerente in cui l'LLM è a conoscenza delle modifiche allo stato dell'applicazione avviate dall'utente.
Risoluzione dei problemi
Problemi di elaborazione dei flussi
Se riscontri problemi con l'elaborazione dello stream:
- Sintomi: risposte parziali, testo mancante o interruzione improvvisa dello stream
- Soluzione: controlla la connettività di rete e assicurati che nel codice siano presenti pattern async/await appropriati
- Diagnostica: esamina il riquadro dei log per verificare la presenza di messaggi di errore o avvisi relativi all'elaborazione dello stream
- Correzione: assicurati che tutta l'elaborazione dello stream utilizzi una gestione degli errori corretta con i blocchi
try
/catch
Chiamate di funzioni mancanti
Se le chiamate alle funzioni non vengono rilevate nello stream:
- Sintomi: il testo viene visualizzato, ma i colori non vengono aggiornati oppure il log non mostra chiamate di funzioni
- Soluzione: verifica le istruzioni del prompt del sistema sull'utilizzo delle chiamate di funzione
- Diagnosi: controlla il riquadro dei log per verificare se le chiamate alle funzioni vengono ricevute
- Correzione: modifica il prompt del sistema per indicare in modo più esplicito all'LLM di utilizzare lo strumento
set_color
Gestione degli errori generali
Per qualsiasi altro problema:
- Passaggio 1: controlla se nel riquadro dei log sono presenti messaggi di errore
- Passaggio 2: verifica la connettività di Vertex AI in Firebase
- Passaggio 3: assicurati che tutto il codice generato da Riverpod sia aggiornato
- Passaggio 4: controlla l'implementazione dello streaming per verificare la presenza di eventuali istruzioni await mancanti
Concetti chiave appresi
- Implementazione di risposte in streaming con l'API Gemini per un'esperienza utente più reattiva
- Gestione dello stato della conversazione per gestire correttamente le interazioni in streaming
- Elaborazione di chiamate di funzioni e di testo in tempo reale man mano che arrivano
- Creazione di UI adattabili che si aggiornano in modo incrementale durante lo streaming
- Gestire stream simultanei con pattern asincroni appropriati
- Fornire un feedback visivo appropriato durante le risposte in streaming
Implementando lo streaming, hai migliorato notevolmente l'esperienza utente della tua app Colorist, creando un'interfaccia più reattiva e coinvolgente che sembra davvero di conversazione.
8. Sincronizzazione del contesto LLM
In questo passaggio extra, implementerai la sincronizzazione del contesto LLM inviando una notifica a Gemini quando gli utenti selezionano i colori dalla cronologia. In questo modo si crea un'esperienza più coerente in cui l'LLM è a conoscenza delle azioni dell'utente nell'interfaccia, non solo dei suoi messaggi espliciti.
Argomenti trattati in questo passaggio
- Creazione della sincronizzazione del contesto LLM tra l'interfaccia utente e l'LLM
- Serializzazione degli eventi dell'interfaccia utente in un contesto comprensibile per l'LLM
- Aggiornamento del contesto della conversazione in base alle azioni dell'utente
- Creare un'esperienza coerente su diversi metodi di interazione
- Miglioramento della consapevolezza del contesto dell'LLM oltre i messaggi di chat espliciti
Informazioni sulla sincronizzazione del contesto LLM
I chatbot tradizionali rispondono solo ai messaggi espliciti degli utenti, creando una disconnessione quando gli utenti interagiscono con l'app tramite altri mezzi. La sincronizzazione del contesto LLM risolve questa limitazione:
Perché la sincronizzazione del contesto LLM è importante
Quando gli utenti interagiscono con la tua app tramite elementi dell'interfaccia utente (ad esempio selezionando un colore dalla cronologia), l'LLM non ha modo di sapere cosa è successo, a meno che tu non glielo dica esplicitamente. Sincronizzazione del contesto LLM:
- Mantiene il contesto: mantiene il modello LLM al corrente di tutte le azioni utente pertinenti
- Crea coerenza: produce un'esperienza coerente in cui l'LLM riconosce le interazioni con l'interfaccia utente
- Migliora l'intelligenza: consente all'LLM di rispondere in modo appropriato a tutte le azioni dell'utente
- Migliora l'esperienza utente: rende l'intera applicazione più integrata e reattiva
- Riduce l'impegno degli utenti: elimina la necessità per gli utenti di spiegare manualmente le loro azioni nell'interfaccia utente
Nell'app Colorist, quando un utente seleziona un colore dalla cronologia, vuoi che Gemini confermi questa azione e commenti in modo intelligente il colore selezionato, mantenendo l'illusione di un assistente consapevole e senza interruzioni.
Aggiornare il servizio di chat di Gemini per le notifiche di selezione dei colori
Innanzitutto, aggiungi un metodo a GeminiChatService
per notificare all'LLM quando un utente seleziona un colore dalla cronologia. Aggiorna il file lib/services/gemini_chat_service.dart
:
lib/services/gemini_chat_service.dart
import 'dart:async';
import 'dart:convert'; // Add this import
import 'package:colorist_ui/colorist_ui.dart';
import 'package:firebase_vertexai/firebase_vertexai.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import '../providers/gemini.dart';
import 'gemini_tools.dart';
part 'gemini_chat_service.g.dart';
final conversationStateProvider = StateProvider(
(ref) => ConversationState.idle,
);
class GeminiChatService {
GeminiChatService(this.ref);
final Ref ref;
Future<void> notifyColorSelection(ColorData color) => sendMessage( // Add from here...
'User selected color from history: ${json.encode(color.toLLMContextMap())}',
); // To here.
Future<void> sendMessage(String message) async {
final chatSession = await ref.read(chatSessionProvider.future);
final conversationState = ref.read(conversationStateProvider);
final chatStateNotifier = ref.read(chatStateNotifierProvider.notifier);
final logStateNotifier = ref.read(logStateNotifierProvider.notifier);
if (conversationState == ConversationState.busy) {
logStateNotifier.logWarning(
"Can't send a message while a conversation is in progress",
);
throw Exception(
"Can't send a message while a conversation is in progress",
);
}
final conversationStateNotifier = ref.read(
conversationStateProvider.notifier,
);
conversationStateNotifier.state = ConversationState.busy;
chatStateNotifier.addUserMessage(message);
logStateNotifier.logUserText(message);
final llmMessage = chatStateNotifier.createLlmMessage();
try {
final responseStream = chatSession.sendMessageStream(
Content.text(message),
);
await for (final block in responseStream) {
await _processBlock(block, llmMessage.id);
}
} catch (e, st) {
logStateNotifier.logError(e, st: st);
chatStateNotifier.appendToMessage(
llmMessage.id,
"\nI'm sorry, I encountered an error processing your request. "
"Please try again.",
);
} finally {
chatStateNotifier.finalizeMessage(llmMessage.id);
conversationStateNotifier.state = ConversationState.idle;
}
}
Future<void> _processBlock(
GenerateContentResponse block,
String llmMessageId,
) async {
final chatSession = await ref.read(chatSessionProvider.future);
final chatStateNotifier = ref.read(chatStateNotifierProvider.notifier);
final logStateNotifier = ref.read(logStateNotifierProvider.notifier);
final blockText = block.text;
if (blockText != null) {
logStateNotifier.logLlmText(blockText);
chatStateNotifier.appendToMessage(llmMessageId, blockText);
}
if (block.functionCalls.isNotEmpty) {
final geminiTools = ref.read(geminiToolsProvider);
final responseStream = chatSession.sendMessageStream(
Content.functionResponses([
for (final functionCall in block.functionCalls)
FunctionResponse(
functionCall.name,
geminiTools.handleFunctionCall(
functionCall.name,
functionCall.args,
),
),
]),
);
await for (final response in responseStream) {
final responseText = response.text;
if (responseText != null) {
logStateNotifier.logLlmText(responseText);
chatStateNotifier.appendToMessage(llmMessageId, responseText);
}
}
}
}
}
@riverpod
GeminiChatService geminiChatService(Ref ref) => GeminiChatService(ref);
L'aggiunta principale è il metodo notifyColorSelection
, che:
- Prende un oggetto
ColorData
che rappresenta il colore selezionato - Lo codifica in un formato JSON che può essere incluso in un messaggio
- Invia un messaggio con un formato speciale all'LLM che indica una selezione dell'utente
- Riutilizza il metodo
sendMessage
esistente per gestire la notifica
Questo approccio evita la duplicazione utilizzando l'infrastruttura di gestione dei messaggi esistente.
Aggiorna l'app principale per collegare le notifiche di selezione dei colori
Ora modifica il file lib/main.dart
per passare la funzione di notifica di selezione del colore alla schermata principale:
lib/main.dart
import 'package:colorist_ui/colorist_ui.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'providers/gemini.dart';
import 'services/gemini_chat_service.dart';
void main() async {
runApp(ProviderScope(child: MainApp()));
}
class MainApp extends ConsumerWidget {
const MainApp({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final model = ref.watch(geminiModelProvider);
final conversationState = ref.watch(conversationStateProvider);
return MaterialApp(
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
),
home: model.when(
data:
(data) => MainScreen(
conversationState: conversationState,
notifyColorSelection: (color) { // Add from here...
ref.read(geminiChatServiceProvider).notifyColorSelection(color);
}, // To here.
sendMessage: (text) {
ref.read(geminiChatServiceProvider).sendMessage(text);
},
),
loading: () => LoadingScreen(message: 'Initializing Gemini Model'),
error: (err, st) => ErrorScreen(error: err),
),
);
}
}
La modifica principale è l'aggiunta del callback notifyColorSelection
, che collega l'evento dell'interfaccia utente (la selezione di un colore dalla cronologia) al sistema di notifica LLM.
Aggiorna la richiesta di sistema
Ora devi aggiornare il prompt di sistema per indicare all'LLM come rispondere alle notifiche di selezione del colore. Modifica il file assets/system_prompt.md
:
assets/system_prompt.md
# Colorist System Prompt
You are a color expert assistant integrated into a desktop app called Colorist. Your job is to interpret natural language color descriptions and set the appropriate color values using a specialized tool.
## Your Capabilities
You are knowledgeable about colors, color theory, and how to translate natural language descriptions into specific RGB values. You have access to the following tool:
`set_color` - Sets the RGB values for the color display based on a description
## How to Respond to User Inputs
When users describe a color:
1. First, acknowledge their color description with a brief, friendly response
2. Interpret what RGB values would best represent that color description
3. Use the `set_color` tool to set those values (all values should be between 0.0 and 1.0)
4. After setting the color, provide a brief explanation of your interpretation
Example:
User: "I want a sunset orange"
You: "Sunset orange is a warm, vibrant color that captures the golden-red hues of the setting sun. It combines a strong red component with moderate orange tones."
[Then you would call the set_color tool with approximately: red=1.0, green=0.5, blue=0.25]
After the tool call: "I've set a warm orange with strong red, moderate green, and minimal blue components that is reminiscent of the sun low on the horizon."
## When Descriptions are Unclear
If a color description is ambiguous or unclear, please ask the user clarifying questions, one at a time.
## When Users Select Historical Colors
Sometimes, the user will manually select a color from the history panel. When this happens, you'll receive a notification about this selection that includes details about the color. Acknowledge this selection with a brief response that recognizes what they've done and comments on the selected color.
Example notification:
User: "User selected color from history: {red: 0.2, green: 0.5, blue: 0.8, hexCode: #3380CC}"
You: "I see you've selected an ocean blue from your history. This tranquil blue with a moderate intensity has a calming, professional quality to it. Would you like to explore similar shades or create a contrasting color?"
## Important Guidelines
- Always keep RGB values between 0.0 and 1.0
- Provide thoughtful, knowledgeable responses about colors
- When possible, include color psychology, associations, or interesting facts about colors
- Be conversational and engaging in your responses
- Focus on being helpful and accurate with your color interpretations
L'aggiunta principale è la sezione "Quando gli utenti selezionano i colori storici", che:
- Spiega il concetto di notifiche di selezione della cronologia all'LLM
- Fornisce un esempio di come appaiono queste notifiche
- Mostra un esempio di risposta appropriata
- Imposta le aspettative per il riconoscimento della selezione e il commento sul colore
In questo modo, l'LLM può capire come rispondere in modo appropriato a questi messaggi speciali.
Genera codice Riverpod
Esegui il comando del programma di esecuzione di compilazione per generare il codice Riverpod necessario:
dart run build_runner build --delete-conflicting-outputs
Esegui e testa la sincronizzazione del contesto LLM
Esegui l'applicazione:
flutter run -d DEVICE
Il test della sincronizzazione del contesto LLM prevede:
- Per prima cosa, genera alcuni colori descrivendoli nella chat.
- "Fammi vedere un viola acceso"
- "Vorrei un verde bosco"
- "Fammi un rosso acceso"
- Poi fai clic su una delle miniature dei colori nella barra della cronologia.
Dovresti osservare:
- Il colore selezionato viene visualizzato nel display principale
- Nella chat viene visualizzato un messaggio dell'utente che indica la selezione del colore
- L'LLM risponde confermando la selezione e commentando il colore
- L'intera interazione è naturale e coerente
In questo modo si crea un'esperienza fluida in cui l'LLM è consapevole e risponde in modo appropriato sia ai messaggi diretti sia alle interazioni con l'interfaccia utente.
Come funziona la sincronizzazione del contesto LLM
Vediamo i dettagli tecnici di come funziona questa sincronizzazione:
Flusso di dati
- Azione utente: l'utente fa clic su un colore nella barra della cronologia
- Evento UI: il widget
MainScreen
rileva questa selezione - Esecuzione del callback: viene attivato il callback
notifyColorSelection
- Creazione del messaggio: viene creato un messaggio con un formato speciale con i dati di colore
- Elaborazione LLM: il messaggio viene inviato a Gemini, che ne riconosce il formato
- Risposta contestuale: Gemini risponde in modo appropriato in base al prompt del sistema
- Aggiornamento dell'interfaccia utente: la risposta viene visualizzata nella chat, creando un'esperienza coerente
Serializzazione dei dati
Un aspetto fondamentale di questo approccio è il modo in cui esegui la serializzazione dei dati di colore:
'User selected color from history: ${json.encode(color.toLLMContextMap())}'
Il metodo toLLMContextMap()
(fornito dal pacchetto colorist_ui
) converte un oggetto ColorData
in una mappa con proprietà chiave che l'LLM può comprendere. In genere, sono inclusi:
- Valori RGB (rosso, verde, blu)
- Rappresentazione del codice esadecimale
- Qualsiasi nome o descrizione associata al colore
Se formatti questi dati in modo coerente e li includi nel messaggio, ti assicuri che l'LLM disponga di tutte le informazioni necessarie per rispondere in modo appropriato.
Applicazioni più ampie della sincronizzazione del contesto LLM
Questo modello di notifica all'LLM degli eventi dell'interfaccia utente ha numerose applicazioni oltre alla selezione dei colori:
Altri casi d'uso
- Modifiche ai filtri: invia una notifica all'LLM quando gli utenti applicano filtri ai dati
- Eventi di navigazione: informano l'LLM quando gli utenti passano da una sezione all'altra
- Modifiche alla selezione: aggiorna il modello LLM quando gli utenti selezionano elementi da elenchi o griglie
- Aggiornamenti delle preferenze: indica all'LLM quando gli utenti modificano le impostazioni o le preferenze
- Manipolazione dei dati: notifica all'LLM quando gli utenti aggiungono, modificano o eliminano dati
In ogni caso, il pattern rimane lo stesso:
- Rileva l'evento UI
- Serializza i dati pertinenti
- Invia una notifica con un formato speciale all'LLM
- Guidare l'LLM a rispondere in modo appropriato tramite il prompt di sistema
Best practice per la sincronizzazione del contesto LLM
In base alla tua implementazione, ecco alcune best practice per una sincronizzazione del contesto LLM efficace:
1. Formato coerente
Utilizza un formato coerente per le notifiche in modo che l'LLM possa identificarle facilmente:
"User [action] [object]: [structured data]"
2. Contesto avanzato
Includi nelle notifiche dettagli sufficienti per consentire all'LLM di rispondere in modo intelligente. Per i colori, si tratta di valori RGB, codici esadecimali ed eventuali altre proprietà pertinenti.
3. Istruzioni chiare
Fornisci istruzioni esplicite nel prompt di sistema su come gestire le notifiche, possibilmente con esempi.
4. Integrazione naturale
Progetta le notifiche in modo che si inseriscano in modo naturale nella conversazione, non come interruzioni tecniche.
5. Notifica selettiva
Comunica all'LLM solo le azioni pertinenti alla conversazione. Non è necessario comunicare tutti gli eventi dell'interfaccia utente.
Risoluzione dei problemi
Problemi relativi alle notifiche
Se l'LLM non risponde correttamente alle selezioni di colore:
- Verifica che il formato del messaggio di notifica corrisponda a quanto descritto nella richiesta di sistema
- Verifica che i dati di colore vengano serializzati correttamente
- Assicurati che la richiesta del sistema contenga istruzioni chiare per la gestione delle selezioni
- Cerca eventuali errori nel servizio di chat durante l'invio delle notifiche
Gestione del contesto
Se l'LLM sembra perdere il contesto:
- Verifica che la sessione di chat venga gestita correttamente
- Verificare che gli stati della conversazione vengano gestiti correttamente
- Assicurati che le notifiche vengano inviate tramite la stessa sessione di chat
Problemi generali
Per problemi generali:
- Esamina i log per verificare la presenza di errori o avvisi
- Verificare la connettività di Vertex AI in Firebase
- Verifica la presenza di mancate corrispondenze di tipo nei parametri della funzione
- Assicurati che tutto il codice generato da Riverpod sia aggiornato
Concetti chiave appresi
- Creazione della sincronizzazione del contesto LLM tra l'interfaccia utente e l'LLM
- Serializzazione degli eventi dell'interfaccia utente in un contesto adatto agli LLM
- Guidare il comportamento dell'LLM per diversi pattern di interazione
- Creare un'esperienza coerente tra le interazioni con i messaggi e quelle senza messaggi
- Miglioramento della consapevolezza dell'LLM dello stato più ampio dell'applicazione
Se implementi la sincronizzazione del contesto LLM, hai creato un'esperienza davvero integrata in cui l'LLM sembra un assistente consapevole e reattivo, anziché solo un generatore di testo. Questo modello può essere applicato a innumerevoli altre applicazioni per creare interfacce più naturali e intuitive basate sull'IA.
9. Complimenti!
Hai completato il codelab Colorist. 🎉
Che cosa hai creato
Hai creato un'applicazione Flutter completamente funzionale che integra l'API Gemini di Google per interpretare le descrizioni dei colori in linguaggio naturale. Ora la tua app può:
- Elabora descrizioni in linguaggio naturale come "arancione tramonto" o "blu oceano profondo"
- Utilizza Gemini per tradurre in modo intelligente queste descrizioni in valori RGB
- Visualizza i colori interpretati in tempo reale con le risposte in streaming
- Gestire le interazioni degli utenti tramite chat ed elementi dell'interfaccia utente
- Mantieni la consapevolezza contestuale in diversi metodi di interazione
Come procedere
Ora che hai appreso le nozioni di base sull'integrazione di Gemini con Flutter, ecco alcuni modi per continuare il tuo percorso:
Migliorare l'app Colorist
- Tavolozze di colori: aggiungi la funzionalità per generare combinazioni di colori complementari o in tinta
- Input vocale: integra il riconoscimento vocale per le descrizioni verbali dei colori
- Gestione della cronologia: aggiungi opzioni per assegnare un nome, organizzare ed esportare insiemi di colori
- Prompt personalizzati: crea un'interfaccia che consenta agli utenti di personalizzare i prompt di sistema
- Analisi avanzate: monitora le descrizioni che funzionano meglio o causano difficoltà
Esplorare altre funzionalità di Gemini
- Input multimodali: aggiungi input di immagini per estrarre i colori dalle foto
- Generazione di contenuti: utilizza Gemini per generare contenuti correlati ai colori, come descrizioni o storie
- Miglioramenti alle chiamate di funzione: crea integrazioni di strumenti più complesse con più funzioni
- Impostazioni di sicurezza: esplora le diverse impostazioni di sicurezza e il loro impatto sulle risposte
Applicare questi pattern ad altri domini
- Analisi dei documenti: crea app in grado di comprendere e analizzare i documenti
- Assistenza alla scrittura creativa: crea strumenti di scrittura con suggerimenti basati su LLM
- Automazione delle attività: progetta app che traducono il linguaggio naturale in attività automatizzate
- Applicazioni basate su conoscenza: crea sistemi esperti in domini specifici
Risorse
Ecco alcune risorse preziose per continuare a imparare:
Documentazione ufficiale
- Documentazione su Vertex AI in Firebase
- Documentazione di Flutter
- Documentazione di Riverpod
- Documentazione dell'API Gemini
Corso e guida sui prompt
Community
Feedback
Ci piacerebbe conoscere la tua esperienza con questo codelab. Ti invitiamo a fornire un feedback tramite:
Grazie per aver completato questo codelab e ci auguriamo che tu continui a esplorare le entusiasmanti possibilità all'intersezione di Flutter e AI.