Creare un'app Flutter basata su Gemini

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

Screenshot dell'app Colorist che mostra la visualizzazione dei colori e l'interfaccia di chat

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:

  1. Configurazione del progetto: inizierai con una struttura di app Flutter di base e il pacchetto colorist_ui
  2. Integrazione di base di Gemini: collega la tua app a Vertex AI in Firebase e implementa una semplice comunicazione LLM
  3. Prompt efficaci: crea un prompt di sistema che guidi l'LLM a comprendere le descrizioni dei colori
  4. Dichiarazioni di funzione: definisci gli strumenti che l'LLM può utilizzare per impostare i colori nella tua applicazione
  5. Gestione degli strumenti: elabora le chiamate di funzione dall'LLM e collegale allo stato dell'app
  6. Risposte dinamiche: migliora l'esperienza utente con risposte LLM dinamiche in tempo reale
  7. 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 Colorist
  • flutter_riverpod e riverpod_annotation: per la gestione dello stato
  • logging: 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:

  1. 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
  2. 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
  3. 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:

  1. Livello UI: fornito dal pacchetto colorist_ui
  2. Gestione dello stato: utilizza Riverpod per la gestione dello stato reattivo
  3. Livello di servizio: attualmente contiene il servizio echo semplice, che verrà sostituito dal servizio Gemini Chat
  4. 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.

Screenshot dell'app Colorist che mostra il rendering del markdown del servizio Echo

Ora dovresti vedere l'app Colorist con:

  1. Un'area di visualizzazione a colori con un colore predefinito
  2. Un'interfaccia di chat in cui puoi digitare i messaggi
  3. 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 da flutter 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

  1. Vai alla Console Firebase e accedi con il tuo Account Google.
  2. Fai clic su Crea un progetto Firebase o seleziona un progetto esistente.
  3. Segui la configurazione guidata per creare il progetto.
  4. 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

  1. Nella Console Firebase, vai al tuo progetto.
  2. Nella barra laterale a sinistra, seleziona AI.
  3. Nella scheda Vertex AI in Firebase, seleziona Inizia.
  4. 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

  1. Aggiungi i pacchetti Firebase Core e Vertex AI al progetto:
flutter pub add firebase_core firebase_vertexai
  1. 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:

  1. Apri macos/Runner/DebugProfile.entitlements e aggiungi:

macos/Runner/DebugProfile.entitlements

<key>com.apple.security.network.client</key>
<true/>
  1. Apri anche macos/Runner/Release.entitlements e aggiungi la stessa voce.
  2. 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.

  1. firebaseAppProvider: inizializza Firebase con la configurazione del progetto
  2. geminiModelProvider: crea un'istanza del modello generativo Gemini
  3. chatSessionProvider: 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:

  1. Accetta i messaggi degli utenti e li invia all'API Gemini
  2. Aggiorna l'interfaccia della chat con le risposte del modello
  3. Registra tutte le comunicazioni per facilitare la comprensione del flusso LLM reale
  4. 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:

  1. Sostituzione del servizio echo con il servizio di chat basato sull'API Gemini
  2. Aggiunta di schermate di caricamento ed errore utilizzando il pattern AsyncValue di Riverpod con il metodo when
  3. 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.

Screenshot dell&#39;app Colorist che mostra l&#39;LLM di Gemini che risponde a una richiesta di un colore giallo soleggiato

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

  1. Input dell'utente: l'utente inserisce il testo nell'interfaccia della chat
  2. Formattazione della richiesta: l'app formatta il testo come oggetto Content per l'API Gemini
  3. Comunicazione API: il testo viene inviato all'API Gemini tramite Vertex AI in Firebase
  4. Elaborazione LLM: il modello Gemini elabora il testo e genera una risposta
  5. Gestione della risposta: l'app riceve la risposta e aggiorna l'interfaccia utente
  6. 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é:

  1. Garantire la coerenza: aiuta il modello a fornire risposte in un formato coerente
  2. Migliora la pertinenza: concentrati sul tuo dominio specifico (in questo caso, i colori)
  3. Stabilisci dei limiti: definisci cosa deve e non deve fare il modello
  4. Migliora l'esperienza utente: crea un pattern di interazione più naturale e utile
  5. 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:

  1. Definizione del ruolo: stabilisce l'LLM come "assistente esperto di colori"
  2. Spiegazione della task: definisce la task principale come l'interpretazione delle descrizioni dei colori in valori RGB
  3. Formato della risposta: specifica esattamente come devono essere formattati i valori RGB per garantire la coerenza
  4. Esempio di scambio: fornisce un esempio concreto del pattern di interazione previsto
  5. Gestione dei casi limite: spiega come gestire le descrizioni poco chiare
  6. 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

Screenshot dell&#39;app Colorist che mostra l&#39;LLM di Gemini che risponde con una risposta in carattere per un&#39;app di selezione dei colori

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:

  1. Definizione chiara del ruolo: stabilisci lo scopo del modello LLM
  2. Istruzioni esplicite: descrivono esattamente come deve rispondere l'LLM
  3. Esempi concreti: mostra, anziché solo descrivere, come sono le risposte efficaci
  4. Gestione dei casi limite: istruzione dell'LLM su come gestire scenari ambigui
  5. 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 da flutter 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:

  1. Riconoscere quando una richiesta dell'utente potrebbe trarre vantaggio dall'invocazione di una funzione specifica
  2. Genera un oggetto JSON strutturato con i parametri necessari per la funzione
  3. Consenti all'applicazione di eseguire la funzione con questi parametri
  4. 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:

  1. Azione diretta: gli utenti possono descrivere ciò che vogliono in linguaggio naturale e l'app risponde con azioni concrete.
  2. Output strutturato: l'LLM produce dati puliti e strutturati anziché testo che deve essere analizzato
  3. Operazioni complesse: consente all'LLM di accedere a dati esterni, eseguire calcoli o modificare lo stato dell'applicazione
  4. 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:

  1. Nomi delle funzioni: assegna un nome alla funzione set_color per indicarne chiaramente lo scopo
  2. Descrizione della funzione: fornisci una descrizione chiara che aiuti l'LLM a capire quando utilizzarla
  3. 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,0
    • green: il componente verde del colore RGB, specificato come numero compreso tra 0,0 e 1,0
    • blue: il componente blu del colore RGB, specificato come numero compreso tra 0,0 e 1,0
  4. Tipi di schema: utilizza Schema.number() per indicare che si tratta di valori numerici
  5. 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:

  1. Introduzione allo strumento: anziché chiedere valori RGB formattati, ora devi indicare allo strumento LLM lo strumento set_color
  2. Procedura modificata: modifica il passaggio 3 da "formatta i valori nella risposta" in "utilizza lo strumento per impostare i valori"
  3. Esempio aggiornato: mostri come la risposta debba includere una chiamata allo strumento anziché testo formattato
  4. 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

Screenshot dell&#39;app Colorist che mostra l&#39;LLM di Gemini che risponde con una risposta parziale

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:

  1. Selezione della funzione: l'LLM decide se una chiamata di funzione sarebbe utile in base alla richiesta dell'utente.
  2. Generare i parametri: l'LLM genera valori dei parametri che si adattano allo schema della funzione
  3. Formato di chiamata di funzione: l'LLM invia un oggetto di chiamata di funzione strutturato nella risposta
  4. Gestione dell'applicazione: l'app riceverà questa chiamata ed eseguirà la funzione pertinente (implementata nel passaggio successivo).
  5. 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:

  1. Intento dell'utente: indica se la richiesta dell'utente è meglio soddisfatta da una funzione
  2. Rilevanza della funzione: il grado di corrispondenza delle funzioni disponibili all'attività
  3. Disponibilità dei parametri: indica se è possibile determinare con certezza i valori dei parametri
  4. 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

  1. Input utente: l'utente descrive un colore in linguaggio naturale (ad es. "verde bosco")
  2. Elaborazione LLM: Gemini analizza la descrizione e decide di chiamare la funzione set_color
  3. Generare chiamate di funzione: Gemini crea un JSON strutturato con parametri (valori rosso, verde, blu)
  4. Ricezione di chiamate di funzione: la tua app riceve questi dati strutturati da Gemini
  5. Esecuzione della funzione: l'app esegue la funzione con i parametri forniti
  6. Aggiornamento stato: la funzione aggiorna lo stato dell'app (modificando il colore visualizzato).
  7. Generare una risposta: la funzione restituisce i risultati all'LLM
  8. Incorporazione della risposta: l'LLM incorpora questi risultati nella risposta finale
  9. 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:

  1. 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
  2. handleSetColor: il gestore specifico per la funzione set_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
  3. 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:

  1. Controlla se la risposta LLM contiene chiamate di funzione
  2. Per ogni chiamata di funzione, invoca il metodo handleFunctionCall con il nome e gli argomenti della funzione
  3. Raccoglie i risultati di ogni chiamata di funzione
  4. Invia questi risultati all'LLM utilizzando Content.functionResponses
  5. Elabora la risposta dell'LLM ai risultati della funzione
  6. 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

Screenshot dell&#39;app Colorist che mostra l&#39;LLM di Gemini che risponde con una chiamata di funzione

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:

  1. Il messaggio visualizzato nell'interfaccia della chat
  2. La risposta di Gemini visualizzata nella chat
  3. Chiamate di funzione registrate nel riquadro dei log
  4. I risultati della funzione vengono registrati immediatamente dopo
  5. Il rettangolo di colore si aggiorna per mostrare il colore descritto
  6. Aggiornamento dei valori RGB per mostrare i componenti del nuovo colore
  7. 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:

  1. Crea un nuovo oggetto ColorData con i valori forniti
  2. Aggiorna il colore corrente nello stato dell'app
  3. Aggiunge il colore alla cronologia
  4. 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:

  1. Blocco try-catch: racchiude tutte le interazioni con l'LLM per rilevare eventuali eccezioni
  2. Registrazione degli errori: registra gli errori nel riquadro dei log con le tracce dello stack
  3. Feedback degli utenti: fornisce un messaggio di errore facile da capire nella chat
  4. 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:

  1. Interfaccia di linguaggio naturale: gli utenti esprimono l'intent in linguaggio quotidiano
  2. Interpretazione intelligente: l'LLM traduce descrizioni vaghe in valori precisi
  3. Manipolazione diretta: l'interfaccia utente si aggiorna in risposta al linguaggio naturale
  4. Risposte contestuali: l'LLM fornisce il contesto conversazionale delle modifiche
  5. 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:

  1. 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.
  2. Ritmo di conversazione naturale: la comparsa graduale del testo imita il modo in cui le persone comunicano, creando un'esperienza di dialogo più naturale.
  3. 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.
  4. 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.
  5. 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:

  1. Esecuzione anticipata delle funzioni: le chiamate alle funzioni possono essere rilevate ed eseguite non appena vengono visualizzate nello stream, senza attendere la risposta completa.
  2. Aggiornamenti incrementali dell'interfaccia utente: puoi aggiornare l'interfaccia utente progressivamente man mano che arrivano nuove informazioni, creando un'esperienza più dinamica.
  3. 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.
  4. 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:

  1. Monitoraggio dello stato della conversazione:
    • Un conversationStateProvider monitora se l'app sta attualmente elaborando una risposta
    • Lo stato passa da idle a busy durante l'elaborazione e poi torna a idle
    • In questo modo si evitano più richieste concorrenti che potrebbero entrare in conflitto
  2. 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
  3. 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
  4. 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
  5. 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

Screenshot dell&#39;app Colorist che mostra l&#39;LLM di Gemini che risponde in streaming

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:

  1. L'app stabilisce una connessione al servizio Vertex AI
  2. La richiesta dell'utente viene inviata al servizio
  3. Il server inizia a elaborare la richiesta
  4. 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:

  1. Il server invia i blocchi di testo man mano che vengono generati (in genere alcune parole o frasi).
  2. Quando Gemini decide di effettuare una chiamata di funzione, invia le informazioni sulla chiamata di funzione
  3. Potrebbero seguire altri blocchi di testo dopo le chiamate alle funzioni
  4. Lo stream continua fino al completamento della generazione

Elaborazione progressiva

L'app elabora ogni chunk in modo incrementale:

  1. Ogni blocco di testo viene aggiunto alla risposta esistente
  2. Le chiamate di funzione vengono eseguite non appena vengono rilevate
  3. L'interfaccia utente si aggiorna in tempo reale con i risultati di testo e funzione
  4. Lo stato viene monitorato per mostrare che la risposta è ancora in streaming

Completamento dello stream

Al termine della generazione:

  1. Lo stream viene chiuso dal server
  2. Il loop await for esce in modo naturale
  3. Il messaggio è contrassegnato come completato
  4. Lo stato della conversazione viene reimpostato su inattivo
  5. 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:

  1. Indicatori visivi chiari: fornisci sempre indicatori visivi chiari che distinguano i messaggi in streaming da quelli completi.
  2. Blocco input: disattiva l'input dell'utente durante lo streaming per evitare più richieste sovrapposte
  3. Ripristino degli errori: progetta l'interfaccia utente in modo che gestisca il recupero graduale se lo streaming viene interrotto
  4. Transizioni di stato: assicurati transizioni fluide tra gli stati inattivo, in streaming e completato
  5. Visualizzazione dell'avanzamento: valuta la possibilità di utilizzare animazioni o indicatori discreti che mostrino l'elaborazione attiva
  6. Opzioni di annullamento: in un'app completa, offri agli utenti modi per annullare le generazioni in corso
  7. Integrazione dei risultati delle funzioni: progetta l'interfaccia utente per gestire i risultati delle funzioni visualizzati durante lo stream
  8. 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:

  1. Mantiene il contesto: mantiene il modello LLM al corrente di tutte le azioni utente pertinenti
  2. Crea coerenza: produce un'esperienza coerente in cui l'LLM riconosce le interazioni con l'interfaccia utente
  3. Migliora l'intelligenza: consente all'LLM di rispondere in modo appropriato a tutte le azioni dell'utente
  4. Migliora l'esperienza utente: rende l'intera applicazione più integrata e reattiva
  5. 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:

  1. Prende un oggetto ColorData che rappresenta il colore selezionato
  2. Lo codifica in un formato JSON che può essere incluso in un messaggio
  3. Invia un messaggio con un formato speciale all'LLM che indica una selezione dell'utente
  4. 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:

  1. Spiega il concetto di notifiche di selezione della cronologia all'LLM
  2. Fornisce un esempio di come appaiono queste notifiche
  3. Mostra un esempio di risposta appropriata
  4. 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

Screenshot dell&#39;app Colorist che mostra l&#39;LLM di Gemini che risponde a una selezione dalla cronologia dei colori

Il test della sincronizzazione del contesto LLM prevede:

  1. Per prima cosa, genera alcuni colori descrivendoli nella chat.
    • "Fammi vedere un viola acceso"
    • "Vorrei un verde bosco"
    • "Fammi un rosso acceso"
  2. Poi fai clic su una delle miniature dei colori nella barra della cronologia.

Dovresti osservare:

  1. Il colore selezionato viene visualizzato nel display principale
  2. Nella chat viene visualizzato un messaggio dell'utente che indica la selezione del colore
  3. L'LLM risponde confermando la selezione e commentando il colore
  4. 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

  1. Azione utente: l'utente fa clic su un colore nella barra della cronologia
  2. Evento UI: il widget MainScreen rileva questa selezione
  3. Esecuzione del callback: viene attivato il callback notifyColorSelection
  4. Creazione del messaggio: viene creato un messaggio con un formato speciale con i dati di colore
  5. Elaborazione LLM: il messaggio viene inviato a Gemini, che ne riconosce il formato
  6. Risposta contestuale: Gemini risponde in modo appropriato in base al prompt del sistema
  7. 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

  1. Modifiche ai filtri: invia una notifica all'LLM quando gli utenti applicano filtri ai dati
  2. Eventi di navigazione: informano l'LLM quando gli utenti passano da una sezione all'altra
  3. Modifiche alla selezione: aggiorna il modello LLM quando gli utenti selezionano elementi da elenchi o griglie
  4. Aggiornamenti delle preferenze: indica all'LLM quando gli utenti modificano le impostazioni o le preferenze
  5. Manipolazione dei dati: notifica all'LLM quando gli utenti aggiungono, modificano o eliminano dati

In ogni caso, il pattern rimane lo stesso:

  1. Rileva l'evento UI
  2. Serializza i dati pertinenti
  3. Invia una notifica con un formato speciale all'LLM
  4. 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

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.