Créer une application Flutter optimisée par Gemini

Créer une application Flutter optimisée par Gemini

À propos de cet atelier de programmation

subjectDernière mise à jour : mai 19, 2025
account_circleRédigé par Brett Morgan

1. Créer une application Flutter optimisée par Gemini

Ce que vous allez faire

Dans cet atelier de programmation, vous allez créer Colorist, une application Flutter interactive qui exploite la puissance de l'API Gemini directement dans votre application Flutter. Avez-vous déjà voulu permettre aux utilisateurs de contrôler votre application par le biais du langage naturel, mais ne saviez pas par où commencer ? Cet atelier de programmation vous explique comment procéder.

Colorist permet aux utilisateurs de décrire des couleurs dans un langage naturel (par exemple, "l'orange d'un coucher de soleil" ou "bleu océan profond"). L'application:

  • Traite ces descriptions à l'aide de l'API Gemini de Google
  • Interpréte les descriptions en valeurs de couleur RVB précises
  • Affiche la couleur à l'écran en temps réel
  • Fournit des informations techniques sur la couleur et un contexte intéressant à son sujet
  • Conserve un historique des couleurs générées récemment

Capture d'écran de l'application Colorist affichant l'affichage des couleurs et l'interface de chat

L'application présente une interface en écran partagé avec une zone d'affichage en couleur et un système de chat interactif d'un côté, et un panneau de journal détaillé affichant les interactions brutes du LLM de l'autre. Ce journal vous permet de mieux comprendre le fonctionnement d'une intégration de LLM en arrière-plan.

Pourquoi est-ce important pour les développeurs Flutter ?

Les LLM révolutionnent la façon dont les utilisateurs interagissent avec les applications, mais leur intégration efficace dans les applications mobiles et de bureau présente des défis uniques. Cet atelier de programmation vous présente des modèles pratiques qui vont au-delà des appels d'API bruts.

Votre parcours d'apprentissage

Cet atelier de programmation vous explique pas à pas comment créer Colorist:

  1. Configuration du projet : vous commencerez par une structure d'application Flutter de base et le package colorist_ui.
  2. Intégration de base de Gemini : connectez votre application à Vertex AI dans Firebase et implémentez une communication LLM simple.
  3. Requêtes efficaces : créez une requête système qui guide le LLM pour qu'il comprenne les descriptions de couleurs.
  4. Déclarations de fonction : définissez les outils que le LLM peut utiliser pour définir des couleurs dans votre application.
  5. Gestion des outils : permet de traiter les appels de fonction à partir du LLM et de les associer à l'état de votre application.
  6. Réponses en streaming : améliorez l'expérience utilisateur grâce aux réponses LLM en streaming en temps réel.
  7. Synchronisation du contexte LLM : créez une expérience cohérente en informant le LLM des actions des utilisateurs.

Points abordés

  • Configurer Vertex AI dans Firebase pour les applications Flutter
  • Créez des requêtes système efficaces pour guider le comportement du LLM.
  • Implémenter des déclarations de fonction qui relient le langage naturel aux fonctionnalités de l'application
  • Traiter les réponses en streaming pour une expérience utilisateur réactive
  • Synchroniser l'état entre les événements de l'UI et le LLM
  • Gérer l'état de la conversation LLM à l'aide de Riverpod
  • Gérer les erreurs de manière élégante dans les applications basées sur un LLM

Aperçu du code: un aperçu de ce que vous allez implémenter

Voici un aperçu de la déclaration de fonction que vous allez créer pour permettre au LLM de définir des couleurs dans votre application:

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)'),
  },
);

Présentation vidéo de cet atelier de programmation

Regardez Craig Labenz et Andrew Brogdon discuter de cet atelier de programmation dans l'épisode 59 de Flutter Observable:

Prérequis

Pour tirer le meilleur parti de cet atelier de programmation, vous devez disposer des éléments suivants:

  • Expérience en développement Flutter : maîtrise des principes de base de Flutter et de la syntaxe Dart
  • Connaissances en programmation asynchrone : compréhension des futures, de la méthode async/await et des flux
  • Compte Firebase : vous aurez besoin d'un compte Google pour configurer Firebase.
  • Projet Firebase avec facturation activée : Vertex AI in Firebase nécessite un compte de facturation.

Commençons à créer votre première application Flutter optimisée pour le LLM.

2. Configuration du projet et service d'écho

Dans cette première étape, vous allez configurer la structure du projet et implémenter un service d'écho simple qui sera remplacé par l'intégration de l'API Gemini. Cela permet d'établir l'architecture de l'application et de s'assurer que votre UI fonctionne correctement avant d'ajouter la complexité des appels LLM.

Ce que vous allez apprendre dans cette étape

  • Configurer un projet Flutter avec les dépendances requises
  • Utiliser le package colorist_ui pour les composants d'UI
  • Implémenter un service de message d'écho et le connecter à l'UI

Remarque importante concernant les tarifs

Créer un projet Flutter

Commencez par créer un projet Flutter avec la commande suivante:

flutter create -e colorist --platforms=android,ios,macos,web,windows

L'indicateur -e indique que vous souhaitez un projet vide sans l'application counter par défaut. L'application est conçue pour fonctionner sur ordinateur, mobile et Web. Cependant, flutterfire n'est pas compatible avec Linux pour le moment.

Ajouter des dépendances

Accédez au répertoire de votre projet et ajoutez les dépendances requises:

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

Les packages clés suivants seront ajoutés:

  • colorist_ui: package personnalisé qui fournit les composants d'interface utilisateur de l'application Colorist
  • flutter_riverpod et riverpod_annotation: pour la gestion des états
  • logging: pour la journalisation structurée
  • Dépendances de développement pour la génération de code et l'analyse lint

Votre pubspec.yaml se présentera comme suit:

pubspec.yaml

name: colorist
description: "A new Flutter project."
publish_to: 'none'
version: 0.1.0

environment:
  sdk: ^3.8.0

dependencies:
  flutter:
    sdk: flutter
  colorist_ui: ^0.2.3
  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.5
  custom_lint: ^0.7.5

flutter:
  uses-material-design: true

Configurer les options d'analyse

Ajoutez custom_lint à votre fichier analysis_options.yaml à la racine de votre projet:

include: package:flutter_lints/flutter.yaml

analyzer:
  plugins:
    - custom_lint

Cette configuration active les lints spécifiques à Riverpod pour aider à maintenir la qualité du code.

Implémenter le fichier main.dart

Remplacez le contenu du bloc lib/main.dart par le code suivant :

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);
 
}
}

Cela configure une application Flutter qui implémente un service d'écho simple qui imite le comportement d'un LLM en renvoyant simplement le message de l'utilisateur.

Comprendre l'architecture

Prenons quelques instants pour comprendre l'architecture de l'application colorist:

Package colorist_ui

Le package colorist_ui fournit des composants d'interface utilisateur prédéfinis et des outils de gestion de l'état:

  1. MainScreen: composant d'UI principal qui affiche les éléments suivants:
    • Mise en page en écran partagé sur ordinateur (zone d'interaction et panneau de journal)
    • Interface à onglets sur mobile
    • Affichage des couleurs, interface de chat et vignettes de l'historique
  2. Gestion des états: l'application utilise plusieurs notifiers d'état:
    • ChatStateNotifier: gère les messages de chat
    • ColorStateNotifier: gère la couleur actuelle et l'historique
    • LogStateNotifier: gère les entrées de journal pour le débogage
  3. Gestion des messages: l'application utilise un modèle de message avec différents états:
    • Messages utilisateur: saisis par l'utilisateur
    • Messages LLM: générés par le LLM (ou votre service d'écho pour le moment)
    • MessageState: indique si les messages LLM sont terminés ou s'ils sont toujours en streaming

Architecture de l'application

L'application suit l'architecture suivante:

  1. Couche d'interface utilisateur: fournie par le package colorist_ui
  2. Gestion de l'état: utilise Riverpod pour la gestion réactive de l'état.
  3. Couche de service: contient actuellement votre service d'écho simple, qui sera remplacé par le service Gemini Chat
  4. Intégration de LLM: sera ajoutée ultérieurement

Cette séparation vous permet de vous concentrer sur l'implémentation de l'intégration du LLM, tandis que les composants d'interface utilisateur sont déjà pris en charge.

Exécuter l'application

Exécutez l'application avec la commande suivante:

flutter run -d DEVICE

Remplacez DEVICE par votre appareil cible, par exemple macos, windows, chrome ou un ID d'appareil.

Capture d'écran de l'application Colorist montrant le rendu Markdown du service Echo

L'application Colorist doit maintenant s'afficher avec:

  1. Zone d'affichage en couleur avec une couleur par défaut
  2. Une interface de chat dans laquelle vous pouvez saisir des messages
  3. Panneau de journal affichant les interactions de chat

Essayez de saisir un message comme "Je voudrais une couleur bleu foncé", puis appuyez sur Envoyer. Le service d'écho répète simplement votre message. Dans les étapes suivantes, vous allez remplacer cette valeur par une interprétation réelle des couleurs à l'aide de l'API Gemini via Vertex AI dans Firebase.

Étape suivante

À l'étape suivante, vous allez configurer Firebase et implémenter une intégration de base de l'API Gemini pour remplacer votre service d'écho par le service de chat Gemini. Cela permettra à l'application d'interpréter les descriptions de couleur et de fournir des réponses intelligentes.

Dépannage

Problèmes liés aux packages d'interface utilisateur

Si vous rencontrez des problèmes avec le package colorist_ui:

  • Assurez-vous d'utiliser la dernière version
  • Vérifier que vous avez correctement ajouté la dépendance
  • Rechercher des versions de packages en conflit

Erreurs de compilation

Si des erreurs de compilation s'affichent:

  • Vérifiez que vous disposez de la dernière version stable du SDK Flutter
  • Exécuter flutter clean suivi de flutter pub get
  • Rechercher des messages d'erreur spécifiques dans la sortie de la console

Concepts clés acquis

  • Configurer un projet Flutter avec les dépendances nécessaires
  • Comprendre l'architecture de l'application et les responsabilités des composants
  • Implémenter un service simple qui imite le comportement d'un LLM
  • Connecter le service aux composants de l'UI
  • Utiliser Riverpod pour la gestion des états

3. Intégration de base de Gemini Chat

À cette étape, vous allez remplacer le service d'écho de l'étape précédente par l'intégration de l'API Gemini à l'aide de Vertex AI dans Firebase. Vous allez configurer Firebase, configurer les fournisseurs nécessaires et implémenter un service de chat de base qui communique avec l'API Gemini.

Ce que vous allez apprendre dans cette étape

  • Configurer Firebase dans une application Flutter
  • Configurer Vertex AI dans Firebase pour l'accès à Gemini
  • Créer des fournisseurs Riverpod pour les services Firebase et Gemini
  • Implémenter un service de chat de base avec l'API Gemini
  • Gérer les réponses et les états d'erreur des API asynchrones

Configurer Firebase

Vous devez d'abord configurer Firebase pour votre projet Flutter. Pour ce faire, vous devez créer un projet Firebase, y ajouter votre application et configurer les paramètres Vertex AI nécessaires.

Créer un projet Firebase

  1. Accédez à la console Firebase, puis connectez-vous avec votre compte Google.
  2. Cliquez sur Créer un projet Firebase ou sélectionnez un projet existant.
  3. Suivez l'assistant de configuration pour créer votre projet.
  4. Une fois votre projet créé, vous devrez passer au forfait Blaze (paiement à l'utilisation) pour accéder aux services Vertex AI. Cliquez sur le bouton Mettre à jour en bas à gauche de la console Firebase.

Configurer Vertex AI dans votre projet Firebase

  1. Dans la console Firebase, accédez à votre projet.
  2. Dans la barre latérale de gauche, sélectionnez AI.
  3. Dans la fiche Vertex AI in Firebase, sélectionnez Commencer.
  4. Suivez les instructions pour activer les API Vertex AI in Firebase pour votre projet.

Installer la CLI FlutterFire

La CLI FlutterFire simplifie la configuration de Firebase dans les applications Flutter:

dart pub global activate flutterfire_cli

Ajouter Firebase à votre application Flutter

  1. Ajoutez les packages Firebase Core et Vertex AI à votre projet:
flutter pub add firebase_core firebase_vertexai
  1. Exécutez la commande de configuration FlutterFire:
flutterfire configure

Cette commande:

  • vous invite à sélectionner le projet Firebase que vous venez de créer ;
  • Enregistrer vos applications Flutter auprès de Firebase
  • Générer un fichier firebase_options.dart avec la configuration de votre projet

La commande détecte automatiquement les plates-formes sélectionnées (iOS, Android, macOS, Windows et Web) et les configure de manière appropriée.

Configuration spécifique à la plate-forme

Firebase nécessite des versions minimales supérieures à celles par défaut pour Flutter. Il nécessite également un accès réseau pour communiquer avec Vertex AI dans les serveurs Firebase.

Configurer les autorisations macOS

Pour macOS, vous devez activer l'accès au réseau dans les droits d'accès de votre application:

  1. Ouvrez macos/Runner/DebugProfile.entitlements et ajoutez:

macos/Runner/DebugProfile.entitlements

<key>com.apple.security.network.client</key>
<true/>
  1. Ouvrez également macos/Runner/Release.entitlements et ajoutez la même entrée.
  2. Mettez à jour la version minimale de macOS en haut de macos/Podfile:

macos/Podfile

# Firebase requires at least macOS 10.15
platform
:osx, '10.15'

Configurer les autorisations iOS

Pour iOS, mettez à jour la version minimale en haut de ios/Podfile:

ios/Podfile

# Firebase requires at least iOS 13.0
platform
:ios, '13.0'

Configurer les paramètres Android

Pour Android, mettez à jour android/app/build.gradle.kts:

android/app/build.gradle.kts

android {
   
// ...
    ndkVersion
= "27.0.12077973"

    defaultConfig
{
       
// ...
        minSdk
= 23
       
// ...
   
}
}

Créer des fournisseurs de modèles Gemini

Vous allez maintenant créer les fournisseurs Riverpod pour Firebase et Gemini. Créez un fichier 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();
}

Ce fichier définit la base de trois fournisseurs de clés. Ces fournisseurs sont générés lorsque vous exécutez dart run build_runner par les générateurs de code Riverpod.

  1. firebaseAppProvider: initialise Firebase avec la configuration de votre projet
  2. geminiModelProvider: crée une instance de modèle génératif Gemini
  3. chatSessionProvider: crée et gère une session de chat avec le modèle Gemini

L'annotation keepAlive: true sur la session de chat garantit sa persistance tout au long du cycle de vie de l'application, ce qui permet de conserver le contexte de la conversation.

Implémenter le service de chat Gemini

Créez un fichier lib/services/gemini_chat_service.dart pour implémenter le service de 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);

Ce service:

  1. Accepte les messages des utilisateurs et les envoie à l'API Gemini
  2. Met à jour l'interface de chat avec les réponses du modèle
  3. Il consigne toutes les communications pour faciliter la compréhension du flux réel du LLM.
  4. Gère les erreurs avec des commentaires utilisateur appropriés

Remarque:À ce stade, la fenêtre du journal est presque identique à la fenêtre de chat. Le journal devient plus intéressant lorsque vous introduisez des appels de fonction, puis des réponses en streaming.

Générer du code Riverpod

Exécutez la commande du build runner pour générer le code Riverpod nécessaire:

dart run build_runner build --delete-conflicting-outputs

Les fichiers .g.dart dont Riverpod a besoin pour fonctionner sont créés.

Mettre à jour le fichier main.dart

Mettez à jour votre fichier lib/main.dart pour utiliser le nouveau service de chat 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),
     
),
   
);
 
}
}

Voici les principales modifications apportées dans cette mise à jour:

  1. Remplacement du service Echo par le service de chat basé sur l'API Gemini
  2. Ajouter des écrans de chargement et d'erreur à l'aide du modèle AsyncValue de Riverpod avec la méthode when
  3. Connecter l'UI à votre nouveau service de chat via le rappel sendMessage

Exécuter l'application

Exécutez l'application avec la commande suivante:

flutter run -d DEVICE

Remplacez DEVICE par votre appareil cible, par exemple macos, windows, chrome ou un ID d'appareil.

Capture d&#39;écran de l&#39;application Colorist montrant le LLM Gemini répondant à une demande de couleur jaune ensoleillée

Désormais, lorsque vous saisissez un message, il est envoyé à l'API Gemini, et vous recevez une réponse du LLM plutôt qu'un écho. Le panneau des journaux affiche les interactions avec l'API.

Comprendre la communication LLM

Prenons un moment pour comprendre ce qui se passe lorsque vous communiquez avec l'API Gemini:

Flux de communication

  1. Entrée utilisateur: l'utilisateur saisit du texte dans l'interface de chat.
  2. Mise en forme de la requête: l'application met en forme le texte en tant qu'objet Content pour l'API Gemini.
  3. Communication de l'API: le texte est envoyé à l'API Gemini via Vertex AI dans Firebase.
  4. Traitement par LLM: le modèle Gemini traite le texte et génère une réponse.
  5. Gestion des réponses: l'application reçoit la réponse et met à jour l'UI.
  6. Journalisation: toutes les communications sont enregistrées pour plus de transparence.

Sessions de chat et contexte de conversation

La session de chat Gemini conserve le contexte entre les messages, ce qui permet des interactions conversationnelles. Cela signifie que le LLM "se souvient" des échanges précédents de la session en cours, ce qui permet des conversations plus cohérentes.

L'annotation keepAlive: true sur votre fournisseur de session de chat garantit que ce contexte persiste tout au long du cycle de vie de l'application. Ce contexte persistant est essentiel pour maintenir un flux de conversation naturel avec le LLM.

Étape suivante

À ce stade, vous pouvez demander à l'API Gemini n'importe quoi, car aucune restriction n'est appliquée à ce à quoi elle répond. Par exemple, vous pouvez lui demander un résumé des guerres des Roses, ce qui n'a rien à voir avec l'objectif de votre application de couleurs.

À l'étape suivante, vous allez créer une requête système pour guider Gemini dans l'interprétation des descriptions de couleurs plus efficacement. Vous verrez comment personnaliser le comportement d'un grand modèle de langage pour répondre aux besoins spécifiques de l'application et concentrer ses fonctionnalités sur le domaine de votre application.

Dépannage

Problèmes de configuration de Firebase

Si vous rencontrez des erreurs lors de l'initialisation de Firebase:

  • Vérifier que votre fichier firebase_options.dart a été correctement généré
  • Vérifier que vous avez passé au forfait Blaze pour accéder à Vertex AI

Erreurs d'accès aux API

Si vous recevez des erreurs lorsque vous accédez à l'API Gemini:

  • Vérifier que la facturation est correctement configurée sur votre projet Firebase
  • Vérifier que Vertex AI et l'API Cloud AI sont activés dans votre projet Firebase
  • Vérifier la connectivité réseau et les paramètres de pare-feu
  • Vérifier que le nom du modèle (gemini-2.0-flash) est correct et disponible

Problèmes liés au contexte de la conversation

Si vous remarquez que Gemini ne mémorise pas le contexte précédent de la discussion:

  • Vérifier que la fonction chatSession est annotée avec @Riverpod(keepAlive: true)
  • Vérifiez que vous utilisez la même session de chat pour tous les échanges de messages.
  • Vérifier que la session de chat est correctement initialisée avant d'envoyer des messages

Problèmes spécifiques à la plate-forme

Pour les problèmes spécifiques à la plate-forme:

  • iOS/macOS: assurez-vous que les droits d'accès appropriés sont définis et que les versions minimales sont configurées
  • Android: vérifier que la version minimale du SDK est correctement définie
  • Vérifier les messages d'erreur spécifiques à la plate-forme dans la console

Concepts clés acquis

  • Configurer Firebase dans une application Flutter
  • Configurer Vertex AI dans Firebase pour accéder à Gemini
  • Créer des fournisseurs Riverpod pour les services asynchrones
  • Implémenter un service de chat qui communique avec un LLM
  • Gérer les états d'API asynchrones (chargement, erreur, données)
  • Comprendre le flux de communication des LLM et les sessions de chat

4. Requêtes efficaces pour les descriptions de couleurs

Au cours de cette étape, vous allez créer et implémenter une invite système qui guide Gemini dans l'interprétation des descriptions de couleurs. Les requêtes système sont un moyen efficace de personnaliser le comportement du LLM pour des tâches spécifiques sans modifier votre code.

Ce que vous allez apprendre dans cette étape

  • Comprendre les requêtes système et leur importance dans les applications LLM
  • Créer des requêtes efficaces pour des tâches spécifiques à un domaine
  • Charger et utiliser des requêtes système dans une application Flutter
  • Guider un LLM pour qu'il fournisse des réponses mises en forme de manière cohérente
  • Tester l'impact des requêtes système sur le comportement du LLM

Comprendre les requêtes système

Avant de vous lancer dans l'implémentation, voyons ce que sont les requêtes système et pourquoi elles sont importantes:

Que sont les requêtes système ?

Une requête système est un type d'instruction spécial donné à un LLM qui définit le contexte, les consignes de comportement et les attentes concernant ses réponses. Contrairement aux messages utilisateur, les requêtes système:

  • Définir le rôle et le persona du LLM
  • Définir des connaissances ou des compétences spécialisées
  • Fournir des instructions de mise en forme
  • Définir des contraintes sur les réponses
  • Décrire comment gérer différents scénarios

Une requête système donne au LLM sa "description de poste" : elle indique au modèle comment se comporter tout au long de la conversation.

Pourquoi les requêtes système sont-elles importantes ?

Les invites système sont essentielles pour créer des interactions LLM cohérentes et utiles, car elles:

  1. Assurer la cohérence: guidez le modèle pour qu'il fournisse des réponses dans un format cohérent.
  2. Améliorer la pertinence: concentrez le modèle sur votre domaine spécifique (dans votre cas, les couleurs).
  3. Définir des limites: définissez ce que le modèle doit et ne doit pas faire.
  4. Améliorer l'expérience utilisateur: créez un modèle d'interaction plus naturel et plus utile.
  5. Réduire le post-traitement: obtenez des réponses dans des formats plus faciles à analyser ou à afficher.

Pour votre application Colorist, vous avez besoin de la LLM pour interpréter de manière cohérente les descriptions de couleurs et fournir des valeurs RVB dans un format spécifique.

Créer un composant de requête système

Vous allez d'abord créer un fichier d'invite système qui sera chargé au moment de l'exécution. Cette approche vous permet de modifier l'invite sans recompiler votre application.

Créez un fichier assets/system_prompt.md avec le contenu suivant:

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

Comprendre la structure des invites système

Décomposons cette requête:

  1. Définition du rôle: définit le LLM en tant qu'assistant "expert en couleurs"
  2. Explication de la tâche: définit la tâche principale comme étant l'interprétation des descriptions de couleurs en valeurs RVB
  3. Format de réponse: spécifie exactement comment les valeurs RVB doivent être mises en forme pour assurer la cohérence.
  4. Exemple d'échange: fournit un exemple concret du modèle d'interaction attendu.
  5. Gestion des cas spéciaux: explique comment gérer les descriptions peu claires
  6. Contraintes et consignes: définit des limites, par exemple en maintenant les valeurs RVB entre 0,0 et 1,0.

Cette approche structurée garantit que les réponses du LLM seront cohérentes, informatives et formatées de manière à être facilement analysées si vous souhaitez extraire les valeurs RVB de manière programmatique.

Mettre à jour pubspec.yaml

Modifiez maintenant le bas de votre pubspec.yaml pour inclure le répertoire des composants:

pubspec.yaml

flutter:
  uses-material-design: true

  assets:
    - assets/

Exécutez flutter pub get pour actualiser le bundle d'assets.

Créer un fournisseur de requêtes système

Créez un fichier lib/providers/system_prompt.dart pour charger l'invite système:

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');

Ce fournisseur utilise le système de chargement d'éléments de Flutter pour lire le fichier d'invite au moment de l'exécution.

Mettre à jour le fournisseur de modèles Gemini

Modifiez maintenant votre fichier lib/providers/gemini.dart pour inclure l'invite système:

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 modification clé consiste à ajouter systemInstruction: Content.system(systemPrompt) lors de la création du modèle génératif. Cela indique à Gemini d'utiliser vos instructions comme invite système pour toutes les interactions de cette session de chat.

Générer du code Riverpod

Exécutez la commande du build runner pour générer le code Riverpod nécessaire:

dart run build_runner build --delete-conflicting-outputs

Exécuter et tester l'application

À présent, exécutez votre application :

flutter run -d DEVICE

Capture d&#39;écran de l&#39;application Colorist montrant que le LLM Gemini répond avec une réponse en caractère pour une application de sélection de couleur

Essayez de le tester avec différentes descriptions de couleurs:

  • "Je voudrais un bleu ciel"
  • "Pense-moi un vert forêt"
  • "Créer un orange vif rappelant le coucher du soleil"
  • "Je veux la couleur de la lavande fraîche"
  • "Montre-moi quelque chose comme un bleu profond de l'océan"

Vous devriez remarquer que Gemini répond désormais par des explications conversationnelles sur les couleurs, ainsi que par des valeurs RVB formatées de manière cohérente. L'invite du système a efficacement guidé le LLM pour qu'il fournisse le type de réponses dont vous avez besoin.

Essayez également de lui demander du contenu en dehors du contexte des couleurs. Par exemple, les principales causes des guerres des Roses. Vous devriez remarquer une différence par rapport à l'étape précédente.

L'importance de l'ingénierie des requêtes pour les tâches spécialisées

Les invites système sont à la fois un art et une science. Ils constituent une partie essentielle de l'intégration du LLM et peuvent avoir un impact considérable sur l'utilité du modèle pour votre application spécifique. Vous avez ici réalisé une forme d'ingénierie des requêtes : vous avez adapté les instructions pour que le modèle se comporte de manière à répondre aux besoins de votre application.

Une ingénierie des requêtes efficace implique les éléments suivants:

  1. Définition claire du rôle: détermination de l'objectif du LLM
  2. Instructions explicites: elles indiquent précisément comment le LLM doit répondre.
  3. Exemples concrets: montrer plutôt que de simplement dire à quoi ressemblent les bonnes réponses
  4. Gestion des cas particuliers: indique au LLM comment gérer les scénarios ambigus.
  5. Spécifications de mise en forme: s'assurer que les réponses sont structurées de manière cohérente et utilisable

La requête système que vous avez créée transforme les fonctionnalités génériques de Gemini en un assistant d'interprétation des couleurs spécialisé qui fournit des réponses formatées spécifiquement pour les besoins de votre application. Il s'agit d'un modèle puissant que vous pouvez appliquer à de nombreux domaines et tâches différents.

Étape suivante

À l'étape suivante, vous allez développer cette base en ajoutant des déclarations de fonction, qui permettent au LLM de suggérer non seulement des valeurs RVB, mais aussi d'appeler des fonctions dans votre application pour définir directement la couleur. Cela montre comment les LLM peuvent combler l'écart entre le langage naturel et les fonctionnalités concrètes des applications.

Dépannage

Problèmes de chargement des composants

Si vous rencontrez des erreurs lors du chargement de l'invite système:

  • Vérifier que votre pubspec.yaml liste correctement le répertoire des éléments
  • Vérifiez que le chemin d'accès dans rootBundle.loadString() correspond à l'emplacement de votre fichier.
  • Exécutez flutter clean, suivi de flutter pub get pour actualiser le bundle d'assets.

Réponses incohérentes

Si le LLM ne respecte pas systématiquement vos instructions de formatage:

  • Essayez de rendre les exigences de format plus explicites dans l'invite système.
  • Ajouter d'autres exemples pour illustrer le schéma attendu
  • Assurez-vous que le format que vous demandez est adapté au modèle.

Limite de débit des API

Si vous rencontrez des erreurs liées à la limitation du débit:

  • Sachez que le service Vertex AI est soumis à des limites d'utilisation.
  • Envisager d'implémenter une logique de nouvelle tentative avec un intervalle exponentiel entre les tentatives
  • Vérifier dans la console Firebase si des problèmes de quota se sont produits

Concepts clés acquis

  • Comprendre le rôle et l'importance des requêtes système dans les applications LLM
  • Créer des requêtes efficaces avec des instructions, des exemples et des contraintes claires
  • Charger et utiliser des requêtes système dans une application Flutter
  • Guider le comportement du LLM pour les tâches spécifiques au domaine
  • Utiliser l'ingénierie des requêtes pour façonner les réponses des LLM

Cette étape montre comment personnaliser de manière significative le comportement du LLM sans modifier votre code, simplement en fournissant des instructions claires dans l'invite système.

5. Déclarations de fonctions pour les outils LLM

À cette étape, vous allez commencer à permettre à Gemini d'effectuer des actions dans votre application en implémentant des déclarations de fonction. Cette fonctionnalité puissante permet au LLM de suggérer non seulement des valeurs RVB, mais aussi de les définir dans l'interface utilisateur de votre application via des appels d'outils spécialisés. Toutefois, vous devrez passer à l'étape suivante pour voir les requêtes LLM exécutées dans l'application Flutter.

Ce que vous allez apprendre dans cette étape

  • Comprendre l'appel de fonction LLM et ses avantages pour les applications Flutter
  • Définir des déclarations de fonction basées sur un schéma pour Gemini
  • Intégrer des déclarations de fonction à votre modèle Gemini
  • Modifier l'invite système pour utiliser les fonctionnalités de l'outil

Comprendre l'appel de fonction

Avant d'implémenter des déclarations de fonction, voyons en quoi elles consistent et pourquoi elles sont utiles:

Qu'est-ce qu'un appel de fonction ?

L'appel de fonction (parfois appelé "utilisation d'outil") est une fonctionnalité qui permet à un LLM de:

  1. Détecter quand une requête utilisateur bénéficierait de l'appel d'une fonction spécifique
  2. Générer un objet JSON structuré avec les paramètres nécessaires à cette fonction
  3. Laissez votre application exécuter la fonction avec ces paramètres.
  4. Recevoir le résultat de la fonction et l'intégrer dans sa réponse

Au lieu que le LLM se contente de décrire ce qu'il doit faire, l'appel de fonction lui permet de déclencher des actions concrètes dans votre application.

Pourquoi l'appel de fonction est-il important pour les applications Flutter ?

L'appel de fonction crée un pont puissant entre le langage naturel et les fonctionnalités de l'application:

  1. Action directe: les utilisateurs peuvent décrire ce qu'ils veulent en langage naturel, et l'application répond par des actions concrètes.
  2. Sortie structurée: le LLM produit des données structurées et propres plutôt qu'un texte qui doit être analysé.
  3. Opérations complexes: permet au LLM d'accéder à des données externes, d'effectuer des calculs ou de modifier l'état de l'application.
  4. Meilleure expérience utilisateur: intégration parfaite entre la conversation et les fonctionnalités

Dans votre application Colorist, l'appel de fonction permet aux utilisateurs de dire "Je veux un vert forêt" et de voir l'UI se mettre à jour immédiatement avec cette couleur, sans avoir à analyser les valeurs RVB à partir du texte.

Définir des déclarations de fonction

Créez un fichier lib/services/gemini_tools.dart pour définir vos déclarations de fonction:

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);

Comprendre les déclarations de fonction

Voyons ce que fait ce code:

  1. Nom de la fonction: vous appelez votre fonction set_color pour indiquer clairement son objectif.
  2. Description de la fonction: vous fournissez une description claire qui aide le LLM à comprendre quand l'utiliser.
  3. Définitions de paramètres: vous définissez des paramètres structurés avec leurs propres descriptions:
    • red: composante rouge du RVB, spécifiée sous la forme d'un nombre compris entre 0,0 et 1,0
    • green: composante verte du RVB, spécifiée sous la forme d'un nombre compris entre 0,0 et 1,0
    • blue: composant bleu du RVB, spécifié sous la forme d'un nombre compris entre 0,0 et 1,0
  4. Types de schémas: vous utilisez Schema.number() pour indiquer qu'il s'agit de valeurs numériques.
  5. Collection d'outils: vous créez une liste d'outils contenant votre déclaration de fonction.

Cette approche structurée aide le LLM Gemini à comprendre:

  • Quand appeler cette fonction
  • Paramètres qu'il doit fournir
  • Les contraintes qui s'appliquent à ces paramètres (comme la plage de valeurs)

Mettre à jour le fournisseur de modèles Gemini

Modifiez maintenant votre fichier lib/providers/gemini.dart pour inclure les déclarations de fonction lors de l'initialisation du modèle 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 modification clé consiste à ajouter le paramètre tools: geminiTools.tools lors de la création du modèle génératif. Gemini est ainsi informé des fonctions qu'il peut appeler.

Mettre à jour l'invite système

Vous devez maintenant modifier l'invite système pour indiquer au LLM d'utiliser le nouvel outil set_color. Mise à jour de 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

Voici les principales modifications apportées à l'invite système:

  1. Présentation de l'outil: au lieu de demander des valeurs RVB mises en forme, vous indiquez désormais à la LLM l'outil set_color.
  2. Procédure modifiée: à l'étape 3, vous remplacez "Mettre en forme les valeurs dans la réponse" par "Utiliser l'outil pour définir des valeurs".
  3. Exemple mis à jour: vous montrez comment la réponse doit inclure un appel d'outil au lieu d'un texte formaté
  4. Suppression de l'exigence de mise en forme: comme vous utilisez des appels de fonction structurés, vous n'avez plus besoin d'un format de texte spécifique.

Cette invite mise à jour indique au LLM d'utiliser l'appel de fonction plutôt que de simplement fournir des valeurs RVB sous forme de texte.

Générer du code Riverpod

Exécutez la commande du build runner pour générer le code Riverpod nécessaire:

dart run build_runner build --delete-conflicting-outputs

Exécuter l'application

À ce stade, Gemini génère du contenu qui tente d'utiliser l'appel de fonction, mais vous n'avez pas encore implémenté de gestionnaires pour les appels de fonction. Lorsque vous exécutez l'application et décrivez une couleur, Gemini répond comme s'il avait appelé un outil, mais vous ne verrez aucun changement de couleur dans l'interface utilisateur avant l'étape suivante.

Exécutez votre application:

flutter run -d DEVICE

Capture d&#39;écran de l&#39;application Colorist montrant que le LLM Gemini répond avec une réponse partielle

Essayez de décrire une couleur comme "bleu océan profond" ou "vert forêt", et observez les réponses. Le LLM tente d'appeler les fonctions définies ci-dessus, mais votre code ne détecte pas encore les appels de fonction.

Processus d'appel de fonction

Voyons ce qui se passe lorsque Gemini utilise l'appel de fonction:

  1. Sélection de la fonction: le LLM décide si un appel de fonction est utile en fonction de la requête de l'utilisateur.
  2. Génération de paramètres: le LLM génère des valeurs de paramètre qui correspondent au schéma de la fonction.
  3. Format d'appel de fonction: le LLM envoie un objet d'appel de fonction structuré dans sa réponse.
  4. Gestion de l'application: votre application reçoit cet appel et exécute la fonction appropriée (implémentée à l'étape suivante).
  5. Intégration de la réponse: dans les conversations multitours, le LLM s'attend à ce que le résultat de la fonction soit renvoyé.

Dans l'état actuel de votre application, les trois premières étapes sont effectuées, mais vous n'avez pas encore implémenté les étapes 4 ou 5 (gestion des appels de fonction), que vous allez faire à l'étape suivante.

Détails techniques: comment Gemini décide-t-il quand utiliser des fonctions ?

Gemini prend des décisions intelligentes sur le moment où utiliser des fonctions en fonction des éléments suivants:

  1. Intent de l'utilisateur: indique si la requête de l'utilisateur est mieux traitée par une fonction.
  2. Pertinence des fonctions: niveau de correspondance des fonctions disponibles avec la tâche
  3. Disponibilité des paramètres: indique si l'outil peut déterminer avec certitude les valeurs des paramètres.
  4. Instructions système: conseils de votre requête système sur l'utilisation de la fonction

En fournissant des déclarations de fonction et des instructions système claires, vous avez configuré Gemini pour qu'il reconnaisse les requêtes de description de couleur comme des opportunités d'appeler la fonction set_color.

Étape suivante

À l'étape suivante, vous implémenterez des gestionnaires pour les appels de fonction provenant de Gemini. Le cercle est ainsi bouclé, ce qui permet aux descriptions utilisateur de déclencher des changements de couleur réels dans l'UI via les appels de fonction du LLM.

Dépannage

Problèmes liés aux déclarations de fonction

Si vous rencontrez des erreurs avec les déclarations de fonction:

  • Vérifier que les noms et les types de paramètres correspondent à ce qui est attendu
  • Vérifier que le nom de la fonction est clair et descriptif
  • Assurez-vous que la description de la fonction explique précisément son objectif.

Problèmes liés aux requêtes système

Si le LLM n'essaie pas d'utiliser la fonction:

  • Vérifiez que l'invite système indique clairement au LLM d'utiliser l'outil set_color.
  • Vérifier que l'exemple de l'invite système illustre l'utilisation de la fonction
  • Essayez de rendre les instructions d'utilisation de l'outil plus explicites.

Problèmes d'ordre général

Si vous rencontrez d'autres problèmes:

  • Recherchez d'éventuelles erreurs liées aux déclarations de fonction dans la console.
  • Vérifier que les outils sont correctement transmis au modèle
  • Assurez-vous que tout le code généré par Riverpod est à jour

Concepts clés acquis

  • Définir des déclarations de fonction pour étendre les fonctionnalités de LLM dans les applications Flutter
  • Créer des schémas de paramètres pour la collecte de données structurées
  • Intégrer des déclarations de fonction au modèle Gemini
  • Mise à jour des invites système pour encourager l'utilisation de la fonction
  • Comprendre comment les LLM sélectionnent et appellent des fonctions

Cette étape montre comment les LLM peuvent combler l'écart entre l'entrée en langage naturel et les appels de fonction structurés, ce qui permet d'intégrer facilement les fonctionnalités de conversation et d'application.

6. Implémenter la gestion des outils

À cette étape, vous allez implémenter des gestionnaires pour les appels de fonction provenant de Gemini. Le cercle de communication entre les entrées en langage naturel et les fonctionnalités concrètes de l'application est ainsi bouclé, ce qui permet au LLM de manipuler directement votre UI en fonction des descriptions des utilisateurs.

Ce que vous allez apprendre dans cette étape

  • Comprendre le pipeline d'appel de fonction complet dans les applications LLM
  • Traiter les appels de fonction à partir de Gemini dans une application Flutter
  • Implémenter des gestionnaires de fonctions qui modifient l'état de l'application
  • Gérer les réponses des fonctions et renvoyer les résultats au LLM
  • Créer un flux de communication complet entre le LLM et l'UI
  • Journaliser les appels et les réponses de fonction pour plus de transparence

Comprendre le pipeline d'appel de fonction

Avant de passer à l'implémentation, examinons le pipeline d'appel de fonction complet:

Flux de bout en bout

  1. Entrée utilisateur: l'utilisateur décrit une couleur en langage naturel (par exemple, "forest green")
  2. Traitement LLM: Gemini analyse la description et décide d'appeler la fonction set_color.
  3. Génération d'appels de fonction: Gemini crée un JSON structuré avec des paramètres (valeurs rouge, verte et bleue).
  4. Réception d'un appel de fonction: votre application reçoit ces données structurées de Gemini.
  5. Exécution de la fonction: votre application exécute la fonction avec les paramètres fournis.
  6. Mise à jour de l'état: la fonction met à jour l'état de votre application (en modifiant la couleur affichée).
  7. Génération de réponse: votre fonction renvoie les résultats au LLM.
  8. Incorporation de la réponse: le LLM intègre ces résultats dans sa réponse finale.
  9. Mise à jour de l'UI: votre UI réagit au changement d'état et affiche la nouvelle couleur.

Le cycle de communication complet est essentiel pour une intégration correcte du LLM. Lorsqu'un LLM effectue un appel de fonction, il n'envoie pas simplement la requête et ne passe pas à autre chose. Au lieu de cela, il attend que votre application exécute la fonction et renvoie les résultats. Le LLM utilise ensuite ces résultats pour formuler sa réponse finale, créant ainsi un flux de conversation naturel qui confirme les actions effectuées.

Implémenter des gestionnaires de fonctions

Mettons à jour votre fichier lib/services/gemini_tools.dart pour ajouter des gestionnaires aux appels de fonction:

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);

Comprendre les gestionnaires de fonctions

Voyons à quoi servent ces gestionnaires de fonctions:

  1. handleFunctionCall: répartiteur central qui:
    • Enregistre l'appel de fonction pour la transparence dans le panneau des journaux
    • Routage vers le gestionnaire approprié en fonction du nom de la fonction
    • Renvoie une réponse structurée qui sera renvoyée au LLM
  2. handleSetColor: gestionnaire spécifique de votre fonction set_color qui:
    • Extrait les valeurs RVB de la carte des arguments
    • Les convertit en types attendus (doubles)
    • Met à jour l'état de couleur de l'application à l'aide de colorStateNotifier.
    • Crée une réponse structurée avec l'état de réussite et les informations de couleur actuelles
    • Journalise les résultats de la fonction à des fins de débogage
  3. handleUnknownFunction: gestionnaire de remplacement pour les fonctions inconnues qui:
    • Enregistre un avertissement concernant la fonction non prise en charge
    • Renvoie une réponse d'erreur au LLM

La fonction handleSetColor est particulièrement importante, car elle comble le fossé entre la compréhension du langage naturel du LLM et les modifications concrètes de l'interface utilisateur.

Mettre à jour le service de chat Gemini pour traiter les appels de fonction et les réponses

Modifions maintenant le fichier lib/services/gemini_chat_service.dart pour traiter les appels de fonction à partir des réponses du LLM et renvoyer les résultats au 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);

Comprendre le flux de communication

L'ajout clé ici est la gestion complète des appels et des réponses de fonction:

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);
 
}
}

Ce code:

  1. Vérifie si la réponse du LLM contient des appels de fonction
  2. Pour chaque appel de fonction, appelle votre méthode handleFunctionCall avec le nom et les arguments de la fonction
  3. Récupère les résultats de chaque appel de fonction
  4. Renvoie ces résultats au LLM à l'aide de Content.functionResponses.
  5. Traite la réponse du LLM aux résultats de la fonction
  6. Met à jour l'UI avec le texte de la réponse finale

Cela crée un flux aller-retour:

  • Utilisateur → LLM: demande une couleur
  • LLM → Application: appels de fonction avec paramètres
  • Application → Utilisateur: nouvelle couleur affichée
  • Application → LLM: résultats de la fonction
  • LLM → Utilisateur: réponse finale intégrant les résultats de la fonction

Générer du code Riverpod

Exécutez la commande du build runner pour générer le code Riverpod nécessaire:

dart run build_runner build --delete-conflicting-outputs

Exécuter et tester le parcours complet

À présent, exécutez votre application :

flutter run -d DEVICE

Capture d&#39;écran de l&#39;application Colorist montrant que le LLM Gemini répond avec un appel de fonction

Essayez de saisir différentes descriptions de couleurs:

  • "Je voudrais un rouge cramoisi profond"
  • "Montre-moi un bleu ciel apaisant"
  • "Donne-moi la couleur des feuilles de menthe fraîches"
  • "Je veux voir un orange chaud de coucher de soleil"
  • "Choisis un violet royal profond"

Vous devriez maintenant voir:

  1. Votre message s'affiche dans l'interface de chat
  2. Réponse de Gemini dans le chat
  3. Appels de fonction consignés dans le panneau des journaux
  4. Les résultats de la fonction sont consignés immédiatement après
  5. Mise à jour du rectangle de couleur pour afficher la couleur décrite
  6. Mise à jour des valeurs RVB pour afficher les composants de la nouvelle couleur
  7. Affichage de la réponse finale de Gemini, qui commente souvent la couleur définie

Le panneau des journaux fournit des informations sur ce qui se passe en coulisses. Cette page vous indique les informations suivantes :

  • Les appels de fonction exacts effectués par Gemini
  • Les paramètres choisis pour chaque valeur RVB
  • Les résultats renvoyés par votre fonction
  • Les réponses de suivi de Gemini

Notificateur d'état de la couleur

Le colorStateNotifier que vous utilisez pour mettre à jour les couleurs fait partie du package colorist_ui. Il gère les éléments suivants:

  • Couleur actuelle affichée dans l'interface utilisateur
  • Historique des couleurs (10 dernières couleurs)
  • Notification des changements d'état des composants d'interface utilisateur

Lorsque vous appelez updateColor avec de nouvelles valeurs RVB, il:

  1. Crée un objet ColorData avec les valeurs fournies.
  2. Met à jour la couleur actuelle dans l'état de l'application
  3. Ajoute la couleur à l'historique
  4. Déclenche des mises à jour de l'UI via la gestion de l'état de Riverpod.

Les composants d'interface utilisateur du package colorist_ui surveillent cet état et le mettent automatiquement à jour lorsqu'il change, créant ainsi une expérience réactive.

Comprendre le traitement des erreurs

Votre implémentation inclut une gestion des erreurs robuste:

  1. Bloc try-catch: encapsule toutes les interactions LLM pour intercepter les exceptions.
  2. Journalisation des erreurs: enregistre les erreurs dans le panneau des journaux avec des traces de la pile.
  3. Commentaires des utilisateurs: message d'erreur convivial dans le chat
  4. Nettoyage de l'état: finalise l'état du message même en cas d'erreur.

Cela garantit que l'application reste stable et fournit des commentaires appropriés, même en cas de problème avec le service LLM ou l'exécution de la fonction.

L'efficacité des appels de fonction pour l'expérience utilisateur

Ce que vous avez accompli ici montre comment les LLM peuvent créer des interfaces naturelles puissantes:

  1. Interface de langage naturel: les utilisateurs expriment leur intention dans un langage courant.
  2. Interprétation intelligente: le LLM traduit les descriptions vagues en valeurs précises.
  3. Manipulation directe: l'UI se met à jour en réponse au langage naturel
  4. Réponses contextuelles: le LLM fournit le contexte de la conversation concernant les modifications.
  5. Faible charge cognitive: les utilisateurs n'ont pas besoin de comprendre les valeurs RVB ni la théorie des couleurs.

Ce modèle d'utilisation de l'appel de fonction LLM pour relier le langage naturel aux actions de l'interface utilisateur peut être étendu à d'innombrables autres domaines au-delà de la sélection de couleur.

Étape suivante

À l'étape suivante, vous allez améliorer l'expérience utilisateur en implémentant des réponses en streaming. Plutôt que d'attendre la réponse complète, vous traiterez les fragments de texte et les appels de fonction au fur et à mesure de leur réception, ce qui créera une application plus réactive et attrayante.

Dépannage

Problèmes d'appel de fonction

Si Gemini n'appelle pas vos fonctions ou si les paramètres sont incorrects:

  • Vérifier que la déclaration de votre fonction correspond à ce qui est décrit dans l'invite système
  • Vérifier la cohérence des noms et des types de paramètres
  • Assurez-vous que votre invite système indique explicitement au LLM d'utiliser l'outil.
  • Vérifiez que le nom de la fonction dans votre gestionnaire correspond exactement à celui de la déclaration.
  • Examiner le panneau des journaux pour obtenir des informations détaillées sur les appels de fonction

Problèmes de réponse des fonctions

Si les résultats de la fonction ne sont pas correctement renvoyés au LLM:

  • Vérifier que votre fonction renvoie une carte correctement formatée
  • Vérifier que Content.functionResponses est correctement construit
  • Recherchez les erreurs dans le journal liées aux réponses de la fonction.
  • Assurez-vous d'utiliser la même session de chat pour la réponse.

Problèmes d'affichage couleur

Si les couleurs ne s'affichent pas correctement:

  • Assurez-vous que les valeurs RVB sont correctement converties en doubles (LLM peut les envoyer sous forme d'entiers).
  • Vérifiez que les valeurs se situent dans la plage attendue (0,0 à 1,0).
  • Vérifier que le notifier d'état de couleur est appelé correctement
  • Examinez le journal pour identifier les valeurs exactes transmises à la fonction.

Problèmes d'ordre général

Pour les problèmes d'ordre général:

  • Rechercher des erreurs ou des avertissements dans les journaux
  • Vérifier la connectivité Vertex AI in Firebase
  • Rechercher les types qui ne correspondent pas dans les paramètres de fonction
  • Assurez-vous que tout le code généré par Riverpod est à jour

Concepts clés acquis

  • Implémenter un pipeline d'appel de fonction complet dans Flutter
  • Créer une communication complète entre un LLM et votre application
  • Traiter les données structurées à partir des réponses du LLM
  • Renvoyer les résultats de la fonction au LLM pour les intégrer aux réponses
  • Utiliser le panneau des journaux pour obtenir une visibilité sur les interactions LLM-application
  • Associer les entrées en langage naturel à des modifications concrètes de l'UI

Une fois cette étape terminée, votre application illustre l'un des modèles les plus puissants d'intégration d'un LLM: traduire les entrées en langage naturel en actions d'UI concrètes, tout en maintenant une conversation cohérente qui reconnaît ces actions. Cela crée une interface de conversation intuitive qui semble magique pour les utilisateurs.

7. Réponses en streaming pour une meilleure expérience utilisateur

À cette étape, vous allez améliorer l'expérience utilisateur en implémentant des réponses en streaming de Gemini. Au lieu d'attendre que la réponse entière soit générée, vous traiterez les blocs de texte et les appels de fonction au fur et à mesure de leur réception, ce qui créera une application plus réactive et attrayante.

Ce que vous allez apprendre dans cette étape

  • Importance du streaming pour les applications basées sur un LLM
  • Implémenter des réponses LLM en streaming dans une application Flutter
  • Traiter des segments de texte partiels à mesure qu'ils arrivent de l'API
  • Gérer l'état de la conversation pour éviter les conflits de messages
  • Gérer les appels de fonction dans les réponses en streaming
  • Créer des indicateurs visuels pour les réponses en cours

Pourquoi le streaming est-il important pour les applications LLM ?

Avant de les implémenter, voyons pourquoi les réponses en streaming sont essentielles pour créer une excellente expérience utilisateur avec les LLM:

Amélioration de l'expérience utilisateur

Les réponses en streaming présentent plusieurs avantages importants pour l'expérience utilisateur:

  1. Réduction de la latence perçue: les utilisateurs voient le texte commencer à s'afficher immédiatement (généralement entre 100 et 300 ms), au lieu d'attendre plusieurs secondes pour obtenir une réponse complète. Cette perception d'immédiateté améliore considérablement la satisfaction des utilisateurs.
  2. Rythme naturel de la conversation: l'apparition progressive du texte imite la façon dont les humains communiquent, créant une expérience de dialogue plus naturelle.
  3. Traitement progressif des informations: les utilisateurs peuvent commencer à traiter les informations dès leur arrivée, au lieu d'être submergés par un grand bloc de texte en une seule fois.
  4. Possibilité d'interruption anticipée: dans une application complète, les utilisateurs peuvent potentiellement interrompre ou rediriger le LLM s'ils voient qu'il prend une mauvaise direction.
  5. Confirmation visuelle de l'activité: le texte en streaming fournit un retour immédiat indiquant que le système fonctionne, ce qui réduit l'incertitude.

Avantages techniques

Au-delà des améliorations de l'expérience utilisateur, le streaming offre des avantages techniques:

  1. Exécution anticipée des fonctions: les appels de fonction peuvent être détectés et exécutés dès qu'ils apparaissent dans le flux, sans attendre la réponse complète.
  2. Mises à jour incrémentielles de l'UI: vous pouvez mettre à jour votre UI progressivement à mesure que de nouvelles informations arrivent, ce qui crée une expérience plus dynamique.
  3. Gestion de l'état de la conversation: le streaming fournit des signaux clairs sur le moment où les réponses sont terminées ou en cours, ce qui permet une meilleure gestion de l'état.
  4. Réduction des risques de délai avant expiration: avec les réponses sans streaming, les générations de longue durée risquent de provoquer des délais avant expiration de la connexion. Le streaming établit la connexion de manière anticipée et la maintient.

Pour votre application Colorist, l'implémentation du streaming signifie que les utilisateurs verront les réponses textuelles et les changements de couleur s'afficher plus rapidement, ce qui crée une expérience beaucoup plus réactive.

Ajouter la gestion de l'état de la conversation

Commençons par ajouter un fournisseur d'état pour suivre si l'application gère actuellement une réponse de streaming. Mettez à jour votre fichier 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);

Comprendre l'implémentation du streaming

Voyons ce que fait ce code:

  1. Suivi de l'état de la conversation:
    • Un conversationStateProvider permet de savoir si l'application traite actuellement une réponse.
    • L'état passe de idle à busy pendant le traitement, puis à nouveau à idle.
    • Cela évite d'avoir plusieurs requêtes simultanées qui pourraient entrer en conflit.
  2. Initialisation du flux:
    • sendMessageStream() renvoie un flux de blocs de réponse au lieu d'un Future avec la réponse complète.
    • Chaque bloc peut contenir du texte, des appels de fonction ou les deux.
  3. Traitement progressif:
    • await for traite chaque fragment à mesure qu'il arrive en temps réel.
    • Le texte est ajouté immédiatement à l'interface utilisateur, ce qui crée l'effet de streaming.
    • Les appels de fonction sont exécutés dès qu'ils sont détectés.
  4. Gestion des appels de fonction:
    • Lorsqu'un appel de fonction est détecté dans un bloc, il est exécuté immédiatement.
    • Les résultats sont renvoyés au LLM via un autre appel en streaming.
    • La réponse du LLM à ces résultats est également traitée en streaming.
  5. Traitement des erreurs et nettoyage:
    • try/catch offre un traitement des erreurs robuste
    • Le bloc finally garantit que l'état de la conversation est correctement réinitialisé.
    • Le message est toujours finalisé, même en cas d'erreurs.

Cette implémentation crée une expérience de streaming réactive et fiable, tout en maintenant l'état de la conversation approprié.

Modifier l'écran principal pour connecter l'état de la conversation

Modifiez votre fichier lib/main.dart pour transmettre l'état de la conversation à l'écran principal:

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 modification principale consiste à transmettre conversationState au widget MainScreen. MainScreen (fourni par le package colorist_ui) utilisera cet état pour désactiver la saisie de texte pendant le traitement d'une réponse.

Cela crée une expérience utilisateur cohérente, dans laquelle l'UI reflète l'état actuel de la conversation.

Générer du code Riverpod

Exécutez la commande du build runner pour générer le code Riverpod nécessaire:

dart run build_runner build --delete-conflicting-outputs

Exécuter et tester des réponses en streaming

Exécuter votre application :

flutter run -d DEVICE

Capture d&#39;écran de l&#39;application Colorist montrant que le LLM Gemini répond de manière continue

Essayez maintenant de tester le comportement de streaming avec différentes descriptions de couleurs. Essayez les descriptions suivantes:

  • "Montre-moi la couleur bleu turquoise profond de l'océan au crépuscule."
  • "Je voudrais un corail vif qui me rappelle les fleurs tropicales."
  • "Créer un vert olive terne comme les anciens uniformes militaires"

Détail du flux technique de streaming

Voyons exactement ce qui se passe lors du streaming d'une réponse:

Établissement de la connexion

Lorsque vous appelez sendMessageStream(), voici ce qui se passe:

  1. L'application établit une connexion au service Vertex AI.
  2. La requête de l'utilisateur est envoyée au service
  3. Le serveur commence à traiter la requête.
  4. La connexion de flux reste ouverte, prête à transmettre des segments.

Transmission de fragments

À mesure que Gemini génère du contenu, des segments sont envoyés via le flux:

  1. Le serveur envoie des blocs de texte à mesure qu'ils sont générés (généralement quelques mots ou phrases).
  2. Lorsque Gemini décide d'effectuer un appel de fonction, il envoie les informations sur l'appel de fonction.
  3. Des blocs de texte supplémentaires peuvent suivre les appels de fonction.
  4. Le flux continue jusqu'à la fin de la génération.

Traitement progressif

Votre application traite chaque bloc de manière incrémentielle:

  1. Chaque bloc de texte est ajouté à la réponse existante.
  2. Les appels de fonction sont exécutés dès qu'ils sont détectés.
  3. L'UI est mise à jour en temps réel avec les résultats de texte et de fonction.
  4. L'état est suivi pour indiquer que la réponse est toujours en streaming

Fin de la diffusion

Une fois la génération terminée:

  1. Le flux est fermé par le serveur
  2. Votre boucle await for se termine naturellement
  3. Le message est marqué comme terminé
  4. L'état de la conversation est rétabli sur "Inactif".
  5. L'UI est mise à jour pour refléter l'état terminé.

Comparaison entre le streaming et le non-streaming

Pour mieux comprendre les avantages du streaming, comparons les approches avec et sans streaming:

Aspect

Sans streaming

Streaming

Latence perçue

L'utilisateur ne voit rien tant que la réponse complète n'est pas prête

L'utilisateur voit les premiers mots en quelques millisecondes.

Expérience utilisateur

Attente longue suivie de l'apparition soudaine du texte

Apparence naturelle et progressive du texte

Gestion des états

Plus simple (les messages sont en attente ou terminés)

Plus complexe (les messages peuvent être en état de streaming)

Exécution de la fonction

Ne se produit que lorsque la réponse est complète

Se produit lors de la génération de la réponse

Complexité de l'implémentation

Simplicité d'implémentation

Nécessite une gestion d'état supplémentaire

Récupération d'erreurs

Réponse "tout ou rien"

Les réponses partielles peuvent toujours être utiles

Complexité du code

Moins complexe

Plus complexe en raison de la gestion des flux

Pour une application comme Colorist, les avantages de l'UX du streaming l'emportent sur la complexité de l'implémentation, en particulier pour les interprétations de couleurs qui peuvent prendre plusieurs secondes à générer.

Bonnes pratiques pour l'expérience utilisateur de streaming

Lorsque vous implémentez le streaming dans vos propres applications LLM, tenez compte des bonnes pratiques suivantes:

  1. Indicateurs visuels clairs: fournissez toujours des repères visuels clairs qui distinguent les messages en streaming des messages complets.
  2. Blocage de la saisie: désactivez la saisie utilisateur pendant le streaming pour éviter les requêtes multiples qui se chevauchent.
  3. Récupération d'erreurs: concevez votre UI pour qu'elle gère la récupération appropriée si la diffusion en continu est interrompue.
  4. Transitions d'état: assurez des transitions fluides entre les états d'inactivité, de streaming et de finalisation.
  5. Visualisation de la progression: envisagez d'utiliser des animations ou des indicateurs subtils qui indiquent le traitement en cours.
  6. Options d'annulation: dans une application complète, proposez aux utilisateurs des moyens d'annuler les générations en cours.
  7. Intégration des résultats de fonction: concevez votre UI pour gérer les résultats de fonction qui s'affichent en cours de traitement.
  8. Optimisation des performances: minimisez les recompilations de l'UI lors des mises à jour rapides des flux.

Le package colorist_ui implémente de nombreuses bonnes pratiques à votre place, mais elles sont importantes à prendre en compte pour toute implémentation de LLM en streaming.

Étape suivante

À l'étape suivante, vous allez implémenter la synchronisation LLM en informant Gemini lorsque les utilisateurs sélectionnent des couleurs dans l'historique. Cela crée une expérience plus cohérente, dans laquelle le LLM est conscient des modifications apportées à l'état de l'application par l'utilisateur.

Dépannage

Problèmes de traitement par flux

Si vous rencontrez des problèmes avec le traitement de flux:

  • Symptômes: réponses partielles, texte manquant ou arrêt brutal du flux
  • Solution: Vérifiez la connectivité réseau et assurez-vous que les modèles async/await sont correctement utilisés dans votre code.
  • Diagnostic: examinez le panneau des journaux pour rechercher des messages d'erreur ou des avertissements liés au traitement des flux.
  • Correction: Assurez-vous que tout traitement de flux utilise un traitement des erreurs approprié avec des blocs try/catch.

Appels de fonction manquants

Si les appels de fonction ne sont pas détectés dans le flux:

  • Symptômes: le texte s'affiche, mais les couleurs ne sont pas mises à jour, ou le journal n'affiche aucun appel de fonction.
  • Solution: Vérifiez les instructions de l'invite système concernant l'utilisation des appels de fonction.
  • Diagnostic: vérifiez dans le panneau des journaux si des appels de fonction sont reçus.
  • Solution: Ajustez votre invite système pour demander plus explicitement au LLM d'utiliser l'outil set_color.

Gestion des erreurs générales

Pour tout autre problème:

  • Étape 1: Vérifiez si le panneau des journaux contient des messages d'erreur
  • Étape 2: Vérifier la connectivité Vertex AI in Firebase
  • Étape 3: Assurez-vous que tout le code généré par Riverpod est à jour
  • Étape 4: Vérifiez l'implémentation du streaming pour détecter les instructions await manquantes

Concepts clés acquis

  • Implémenter des réponses en streaming avec l'API Gemini pour une expérience utilisateur plus réactive
  • Gérer l'état de la conversation pour gérer correctement les interactions en streaming
  • Traitement des appels texte et de fonction en temps réel à mesure qu'ils arrivent
  • Créer des UI responsives qui s'actualisent de manière incrémentielle pendant le streaming
  • Gérer les flux simultanés avec des modèles asynchrones appropriés
  • Fournir des commentaires visuels appropriés lors des réponses en streaming

En implémentant le streaming, vous avez considérablement amélioré l'expérience utilisateur de votre application Colorist, en créant une interface plus réactive et plus attrayante qui donne l'impression d'être vraiment conversationnelle.

8. Synchronisation du contexte LLM

Dans cette étape bonus, vous allez implémenter la synchronisation de contexte LLM en informant Gemini lorsque les utilisateurs sélectionnent des couleurs dans l'historique. Cela crée une expérience plus cohérente, dans laquelle le LLM est conscient des actions des utilisateurs dans l'interface, et pas seulement de leurs messages explicites.

Ce que vous allez apprendre dans cette étape

  • Créer une synchronisation de contexte LLM entre votre UI et le LLM
  • Sérialiser les événements d'interface utilisateur dans un contexte que le LLM peut comprendre
  • Mettre à jour le contexte de la conversation en fonction des actions des utilisateurs
  • Créer une expérience cohérente entre les différentes méthodes d'interaction
  • Améliorer la prise en compte du contexte par le LLM au-delà des messages de chat explicites

Comprendre la synchronisation du contexte LLM

Les chatbots traditionnels ne répondent qu'aux messages explicites des utilisateurs, ce qui crée une rupture lorsque les utilisateurs interagissent avec l'application par d'autres moyens. La synchronisation de contexte LLM résout cette limitation:

Pourquoi la synchronisation du contexte LLM est-elle importante ?

Lorsque les utilisateurs interagissent avec votre application via des éléments d'interface utilisateur (par exemple, en sélectionnant une couleur dans l'historique), le LLM n'a aucun moyen de savoir ce qui s'est passé, sauf si vous lui en informez explicitement. Synchronisation du contexte LLM:

  1. Maintient le contexte: informe le LLM de toutes les actions utilisateur pertinentes.
  2. Créer de la cohérence: produit une expérience cohérente où le LLM reconnaît les interactions de l'UI
  3. Améliore l'intelligence: permet au LLM de répondre de manière appropriée à toutes les actions des utilisateurs.
  4. Améliore l'expérience utilisateur: l'ensemble de l'application semble plus intégré et plus réactif.
  5. Réduit l'effort utilisateur: les utilisateurs n'ont plus besoin d'expliquer manuellement leurs actions dans l'interface utilisateur.

Dans votre application Colorist, lorsqu'un utilisateur sélectionne une couleur dans l'historique, vous souhaitez que Gemini confirme cette action et commente intelligemment la couleur sélectionnée, tout en donnant l'illusion d'un assistant fluide et averti.

Mise à jour du service de chat Gemini pour les notifications de sélection de couleur

Tout d'abord, vous allez ajouter une méthode à GeminiChatService pour informer le LLM lorsqu'un utilisateur sélectionne une couleur dans l'historique. Mettez à jour votre fichier 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);

La méthode notifyColorSelection est la principale nouveauté. Elle:

  1. Prend un objet ColorData représentant la couleur sélectionnée
  2. L'encode au format JSON, qui peut être inclus dans un message
  3. Envoie un message au format spécial au LLM pour indiquer une sélection d'utilisateur
  4. Réutilise la méthode sendMessage existante pour gérer la notification

Cette approche évite les duplications en utilisant votre infrastructure de gestion des messages existante.

Modifier l'application principale pour connecter les notifications de sélection de couleur

Modifiez maintenant votre fichier lib/main.dart pour transmettre la fonction de notification de sélection de couleur à l'écran principal:

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 modification principale consiste à ajouter le rappel notifyColorSelection, qui connecte l'événement d'interface utilisateur (sélection d'une couleur dans l'historique) au système de notification LLM.

Mettre à jour l'invite système

Vous devez maintenant modifier votre requête système pour indiquer au LLM comment répondre aux notifications de sélection de couleur. Modifiez votre fichier 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

La section "Lorsque les utilisateurs sélectionnent des couleurs historiques" a été ajoutée. Elle permet de:

  1. Explique le concept de notifications de sélection de l'historique envoyées au LLM
  2. Fournit un exemple de ces notifications
  3. Présente un exemple de réponse appropriée
  4. Définit les attentes concernant la confirmation de la sélection et les commentaires sur la couleur

Cela permet au LLM de comprendre comment répondre de manière appropriée à ces messages spéciaux.

Générer du code Riverpod

Exécutez la commande du build runner pour générer le code Riverpod nécessaire:

dart run build_runner build --delete-conflicting-outputs

Exécuter et tester la synchronisation du contexte LLM

Exécuter votre application :

flutter run -d DEVICE

Capture d&#39;écran de l&#39;application Colorist montrant le LLM Gemini répondant à une sélection dans l&#39;historique des couleurs

Pour tester la synchronisation du contexte LLM, procédez comme suit:

  1. Commencez par générer quelques couleurs en les décrivant dans le chat.
    • "Montre-moi un violet vif"
    • "Je voudrais un vert forêt"
    • "Donnez-moi un rouge vif"
  2. Cliquez ensuite sur l'une des miniatures de couleur dans la bande d'historique.

Vous devez observer les éléments suivants:

  1. La couleur sélectionnée s'affiche sur l'écran principal.
  2. Un message de l'utilisateur s'affiche dans le chat pour indiquer la couleur sélectionnée.
  3. Le LLM répond en confirmant la sélection et en commentant la couleur.
  4. L'ensemble de l'interaction semble naturel et cohérent

Cela crée une expérience fluide, dans laquelle le LLM est conscient des messages directs et des interactions avec l'UI, et y répond de manière appropriée.

Fonctionnement de la synchronisation du contexte LLM

Découvrons les détails techniques de cette synchronisation:

Flux de données

  1. Action de l'utilisateur: l'utilisateur clique sur une couleur dans la bande d'historique.
  2. Événement d'interface utilisateur: le widget MainScreen détecte cette sélection.
  3. Exécution du rappel: le rappel notifyColorSelection est déclenché.
  4. Création du message: un message au format spécial est créé avec les données de couleur.
  5. Traitement par le LLM: le message est envoyé à Gemini, qui reconnaît le format.
  6. Réponse contextuelle: Gemini répond de manière appropriée en fonction de l'invite système.
  7. Mise à jour de l'interface utilisateur: la réponse s'affiche dans le chat, ce qui crée une expérience cohérente.

Sérialisation des données

Un aspect clé de cette approche est la façon dont vous sérialisez les données de couleur:

'User selected color from history: ${json.encode(color.toLLMContextMap())}'

La méthode toLLMContextMap() (fournie par le package colorist_ui) convertit un objet ColorData en une carte avec des propriétés clés que le LLM peut comprendre. Cela inclut généralement les éléments suivants:

  • Valeurs RVB (rouge, vert, bleu)
  • Représentation du code hexadécimal
  • Nom ou description associés à la couleur

En formatant ces données de manière cohérente et en les incluant dans le message, vous vous assurez que le LLM dispose de toutes les informations dont il a besoin pour répondre de manière appropriée.

Applications plus larges de la synchronisation de contexte LLM

Ce modèle de notification du LLM des événements d'interface utilisateur a de nombreuses applications au-delà de la sélection de couleur:

Autres cas d'utilisation

  1. Modifications de filtre: informe le LLM lorsque les utilisateurs appliquent des filtres aux données.
  2. Événements de navigation: informent le LLM lorsque les utilisateurs accèdent à différentes sections.
  3. Modification de la sélection: mettez à jour la LLM lorsque les utilisateurs sélectionnent des éléments dans des listes ou des grilles.
  4. Mise à jour des préférences: indiquez au LLM lorsque les utilisateurs modifient des paramètres ou des préférences.
  5. Manipulation des données: informe le LLM lorsque les utilisateurs ajoutent, modifient ou suppriment des données.

Dans chaque cas, le schéma reste le même:

  1. Détecter l'événement de l'UI
  2. Sérialiser les données pertinentes
  3. Envoyer une notification au format spécial au LLM
  4. Guider le LLM pour qu'il réponde de manière appropriée via la requête système

Bonnes pratiques pour la synchronisation du contexte LLM

En fonction de votre implémentation, voici quelques bonnes pratiques pour une synchronisation efficace du contexte LLM:

1. Toujours le même format

Utilisez un format cohérent pour les notifications afin que le LLM puisse les identifier facilement:

"User [action] [object]: [structured data]"

2. Contexte enrichi

Incluez suffisamment de détails dans les notifications pour que le LLM puisse répondre de manière intelligente. Pour les couleurs, cela signifie des valeurs RVB, des codes hexadécimaux et toute autre propriété pertinente.

3. Instructions claires

Fournissez des instructions explicites dans l'invite système sur la façon de gérer les notifications, de préférence avec des exemples.

4. Intégration naturelle

Concevez les notifications de manière à ce qu'elles s'intègrent naturellement à la conversation, et non comme des interruptions techniques.

5. Notification sélective

N'informez le LLM que des actions pertinentes pour la conversation. Il n'est pas nécessaire de communiquer tous les événements d'interface utilisateur.

Dépannage

Problèmes de notification

Si le LLM ne répond pas correctement aux sélections de couleurs:

  • Vérifier que le format du message de notification correspond à celui décrit dans l'invite système
  • Vérifier que les données de couleur sont correctement sérialisées
  • Assurez-vous que l'invite système contient des instructions claires sur la gestion des sélections.
  • Rechercher les erreurs dans le service de chat lors de l'envoi de notifications

Gestion du contexte

Si le LLM semble perdre le contexte:

  • Vérifier que la session de chat est correctement gérée
  • Vérifier que les états de la conversation passent correctement
  • Assurez-vous que les notifications sont envoyées via la même session de chat.

Problèmes d'ordre général

Pour les problèmes d'ordre général:

  • Rechercher des erreurs ou des avertissements dans les journaux
  • Vérifier la connectivité Vertex AI in Firebase
  • Rechercher les types qui ne correspondent pas dans les paramètres de fonction
  • Assurez-vous que tout le code généré par Riverpod est à jour

Concepts clés acquis

  • Créer une synchronisation de contexte LLM entre l'UI et le LLM
  • Sérialiser les événements d'interface utilisateur dans un contexte compatible avec LLM
  • Guider le comportement du LLM pour différents modèles d'interaction
  • Créer une expérience cohérente entre les interactions par message et les autres interactions
  • Amélioration de la connaissance de l'état de l'application par le LLM

En implémentant la synchronisation de contexte LLM, vous avez créé une expérience véritablement intégrée dans laquelle le LLM semble être un assistant conscient et réactif plutôt qu'un simple générateur de texte. Ce modèle peut être appliqué à d'innombrables autres applications pour créer des interfaces plus naturelles et intuitives, optimisées par l'IA.

9. Félicitations !

Vous avez terminé l'atelier de programmation sur le coloriste. 🎉

Ce que vous avez créé

Vous avez créé une application Flutter entièrement fonctionnelle qui intègre l'API Gemini de Google pour interpréter les descriptions de couleurs en langage naturel. Votre application peut désormais:

  • Traiter des descriptions en langage naturel telles que "orange coucher de soleil" ou "bleu profond de l'océan"
  • Utilisez Gemini pour traduire intelligemment ces descriptions en valeurs RVB.
  • Afficher les couleurs interprétées en temps réel avec les réponses en streaming
  • Gérer les interactions des utilisateurs via le chat et les éléments d'interface utilisateur
  • Maintenir la prise de conscience contextuelle entre les différentes méthodes d'interaction

Étapes suivantes

Maintenant que vous maîtrisez les principes de base de l'intégration de Gemini à Flutter, voici quelques façons de poursuivre votre parcours:

Améliorer votre application Colorist

  • Palettes de couleurs: ajoutez une fonctionnalité permettant de générer des jeux de couleurs complémentaires ou assortis.
  • Saisie vocale: intégration de la reconnaissance vocale pour les descriptions verbales des couleurs
  • Gestion de l'historique: ajoutez des options pour nommer, organiser et exporter des ensembles de couleurs.
  • Invite personnalisée: créez une interface permettant aux utilisateurs de personnaliser les invites système.
  • Analyses avancées: suivez les descriptions qui fonctionnent le mieux ou qui posent des difficultés.

Découvrir d'autres fonctionnalités de Gemini

  • Données multimodales: ajoutez des données d'image pour extraire des couleurs à partir de photos.
  • Génération de contenu: utilisez Gemini pour générer des contenus liés aux couleurs, comme des descriptions ou des histoires.
  • Améliorations des appels de fonction: créez des intégrations d'outils plus complexes avec plusieurs fonctions.
  • Paramètres de sécurité: découvrez les différents paramètres de sécurité et leur impact sur les réponses.

Appliquer ces modèles à d'autres domaines

  • Analyse de documents: créez des applications capables de comprendre et d'analyser des documents.
  • Aide à la rédaction: créez des outils d'écriture avec des suggestions optimisées par un LLM.
  • Automatisation des tâches: concevez des applications qui traduisent le langage naturel en tâches automatisées.
  • Applications basées sur les connaissances: créez des systèmes experts dans des domaines spécifiques.

Ressources

Voici quelques ressources utiles pour poursuivre votre apprentissage:

Documentation officielle

Cours et guide sur les requêtes

Communauté

Série Observable Flutter Agentic

Dans l'épisode 59, Craig Labenz et Andrew Brogden explorent cet atelier de programmation, en mettant en avant les parties intéressantes de la création de l'application.

Dans l'épisode 60, rejoignez Craig et Andrew alors qu'ils ajoutent de nouvelles fonctionnalités à l'application de l'atelier de programmation et s'efforcent de faire en sorte que les LLM fassent ce qu'ils leur demandent.

Dans l'épisode 61, Craig est rejoint par Chris Sells pour analyser les titres d'actualités d'une manière nouvelle et générer les images correspondantes.

Commentaires

Nous aimerions connaître votre avis sur cet atelier de programmation. Vous pouvez nous faire part de vos commentaires de différentes manières:

Merci d'avoir suivi cet atelier de programmation. Nous espérons que vous continuerez à explorer les possibilités passionnantes qu'offre l'intersection entre Flutter et l'IA.