Créer une application Flutter optimisée par Gemini

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 intègre la puissance de l'API Gemini directement dans votre application Flutter. Vous avez toujours voulu permettre aux utilisateurs de contrôler votre application en langage naturel, mais vous ne saviez pas par où commencer ? Cet atelier de programmation vous explique comment faire.

Colorist permet aux utilisateurs de décrire des couleurs en 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
  • Conserve un historique des couleurs générées récemment

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

L'application dispose d'une interface à écran partagé avec une zone d'affichage couleur et un système de chat interactif d'un côté, et un panneau de journaux détaillé montrant les interactions brutes du LLM de l'autre côté. Ce journal vous permet de mieux comprendre comment fonctionne réellement une intégration 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 apprend des modèles pratiques qui vont au-delà des simples appels d'API.

Votre parcours d'apprentissage

Cet atelier de programmation vous guide pas à pas dans la création de Colorist :

  1. Configuration du projet : vous allez commencer par une structure d'application Flutter de base et le package colorist_ui.
  2. Intégration de base de Gemini : connectez votre application à Firebase AI Logic et implémentez la communication LLM.
  3. Requêtes efficaces : créez une requête système qui aide le LLM à comprendre les descriptions de couleurs.
  4. Déclarations de fonctions : définissez les outils que le LLM peut utiliser pour définir les couleurs dans votre application.
  5. Gestion des outils : traitez les appels de fonction du LLM et connectez-les à l'état de votre application.
  6. Streaming des réponses : améliorez l'expérience utilisateur grâce au streaming en temps réel des réponses LLM.
  7. Synchronisation du contexte LLM : créez une expérience cohérente en informant le LLM des actions de l'utilisateur.

Points abordés

  • Configurer Firebase AI Logic pour les applications Flutter
  • Rédiger des instructions système efficaces pour guider le comportement des LLM
  • Implémenter des déclarations de fonctions qui font le lien entre le langage naturel et les 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 d'UI et le LLM
  • Gérer l'état des conversations LLM avec Riverpod
  • Gérer les erreurs de façon optimale dans les applications basées sur des LLM

Aperçu du code : un avant-goût 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 les 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)'),
  },
);

Vidéo de présentation de cet atelier de programmation

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

Prérequis

Pour tirer pleinement parti de cet atelier de programmation, vous devez remplir les prérequis suivants :

  • Expérience en développement Flutter : connaissances de base de Flutter et de la syntaxe Dart
  • Connaissances en programmation asynchrone : compréhension des futurs, d'async/await et des flux
  • Compte Firebase : vous aurez besoin d'un compte Google pour configurer Firebase.

Commençons à créer votre première application Flutter basée sur un 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 qui sera remplacé ultérieurement 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

Créer un projet Flutter

Commencez par créer un projet Flutter à l'aide de 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. Toutefois, 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

Les packages de clés suivants seront ajoutés :

  • colorist_ui : package personnalisé qui fournit les composants d'UI pour 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 et l'analyse du code

Votre pubspec.yaml doit ressembler à ceci :

pubspec.yaml

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

environment:
  sdk: ^3.9.2

dependencies:
  flutter:
    sdk: flutter
  colorist_ui: ^0.3.0
  flutter_riverpod: ^3.0.0
  riverpod_annotation: ^3.0.0

dev_dependencies:
  flutter_test:
    sdk: flutter
  flutter_lints: ^6.0.0
  build_runner: ^2.7.1
  riverpod_generator: ^3.0.0
  riverpod_lint: ^3.0.0
  json_serializable: ^6.11.1

flutter:
  uses-material-design: true

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(chatStateProvider.notifier);
    final logStateNotifier = ref.read(logStateProvider.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 qui imite le comportement d'un LLM en renvoyant 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'UI prédéfinis et des outils de gestion de l'état :

  1. MainScreen : le composant d'UI principal qui affiche :
    • Mise en page en écran partagé sur ordinateur (zone d'interaction et panneau de journaux)
    • Interface à onglets sur mobile
    • Affichage des couleurs, interface de chat et vignettes de l'historique
  2. Gestion de l'état : l'application utilise plusieurs notificateurs 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 complets ou toujours en cours de diffusion.

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 simple service d'écho, qui sera remplacé par le service Gemini Chat.
  4. Intégration du LLM : sera ajoutée lors d'étapes ultérieures

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

Exécuter l'application

Exécutez l'application à l'aide de la commande suivante :

flutter run -d DEVICE

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

Capture d'écran de l'application Colorist montrant le service d'écho rendant le code Markdown

L'application Colorist devrait maintenant s'afficher avec :

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

Saisissez un message comme "J'aimerais une couleur bleu foncé", puis appuyez sur Envoyer. Le service d'écho répète simplement votre message. Dans les étapes suivantes, vous remplacerez cette interprétation par une interprétation réelle des couleurs à l'aide de Firebase AI Logic.

Étape suivante

Dans l'étape suivante, vous allez configurer Firebase et implémenter l'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 couleurs et de fournir des réponses intelligentes.

Dépannage

Problèmes liés aux packages UI

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

  • Assurez-vous d'utiliser la dernière version.
  • Vérifiez que vous avez correctement ajouté la dépendance.
  • Vérifier s'il existe des versions de package en conflit

Erreurs de compilation

Si vous rencontrez des erreurs de compilation :

  • Assurez-vous d'avoir installé la dernière version stable du SDK Flutter.
  • Exécutez flutter clean, puis flutter pub get.
  • Rechercher les messages d'erreur spécifiques dans la sortie de la console

Concepts clés appris

  • 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

Dans 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 Firebase AI Logic. 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 Firebase AI Logic pour accéder à 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

Tout d'abord, vous devez 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 Firebase AI Logic 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.

Configurer Firebase AI Logic dans votre projet Firebase

  1. Dans la console Firebase, accédez à votre projet.
  2. Dans la barre latérale de gauche, sélectionnez IA.
  3. Dans le menu déroulant "IA", sélectionnez Logique d'IA.
  4. Dans la fiche Firebase AI Logic, sélectionnez Premiers pas.
  5. Suivez les instructions pour activer l'API Gemini Developer 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 Firebase AI Logic à votre projet :
flutter pub add firebase_core firebase_ai
  1. Exécutez la commande de configuration FlutterFire :
flutterfire configure

Cette commande :

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

La commande détecte automatiquement les plates-formes que vous avez sélectionnées (iOS, Android, macOS, Windows, 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 au réseau pour communiquer avec les serveurs Firebase AI Logic.

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.

Configurer les paramètres iOS

Pour iOS, modifiez la version minimale en haut de ios/Podfile :

ios/Podfile

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

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_ai/firebase_ai.dart';
import 'package:firebase_core/firebase_core.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 = FirebaseAI.googleAI().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, en conservant 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_ai/firebase_ai.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(chatStateProvider.notifier);
    final logStateNotifier = ref.read(logStateProvider.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. Enregistre toutes les communications pour faciliter la compréhension du flux LLM réel
  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 deviendra plus intéressant une fois que vous aurez introduit les appels de fonction, puis les réponses en streaming.

Générer du code Riverpod

Exécutez la commande du générateur de compilation pour générer le code Riverpod nécessaire :

dart run build_runner build --delete-conflicting-outputs

Cela créera les fichiers .g.dart dont Riverpod a besoin pour fonctionner.

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 :

  1. Remplacer le service d'écho 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 à l'aide de la commande suivante :

flutter run -d DEVICE

Remplacez DEVICE par votre appareil cible, tel que 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 soleil

Désormais, lorsque vous saisissez un message, il est envoyé à l'API Gemini et vous recevez une réponse du LLM au lieu d'un écho. Le panneau de journaux affiche les interactions avec l'API.

Comprendre la communication des LLM

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

Flux de communication

  1. Saisie utilisateur : l'utilisateur saisit du texte dans l'interface de chat.
  2. Mise en forme des requêtes : l'application met en forme le texte en tant qu'objet Content pour l'API Gemini.
  3. Communication avec l'API : le texte est envoyé à l'API Gemini via Firebase AI Logic.
  4. Traitement du 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 consignées pour plus de transparence.

Sessions de chat et contexte de conversation

La session de discussion 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 d'avoir 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 poser n'importe quelle question à l'API Gemini, car aucune restriction n'est en place concernant les réponses qu'elle fournit. Par exemple, vous pouvez lui demander un résumé de la guerre des Deux-Roses, qui n'est pas lié à l'objectif de votre application de couleurs.

Dans l'étape suivante, vous allez créer une requête système pour aider Gemini à interpréter plus efficacement les descriptions de couleurs. Vous apprendrez ainsi à personnaliser le comportement d'un LLM pour répondre aux besoins spécifiques d'une application et à concentrer ses capacité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 :

  • Assurez-vous que votre fichier firebase_options.dart a été correctement généré.
  • Vérifiez que vous êtes passé à la formule Blaze pour accéder à Firebase AI Logic.

Erreurs d'accès à l'API

Si vous recevez des erreurs lors de l'accès à l'API Gemini :

  • Vérifiez que la facturation est correctement configurée dans votre projet Firebase.
  • Vérifiez que Firebase AI Logic et l'API Cloud AI sont activés dans votre projet Firebase.
  • Vérifier la connectivité réseau et les paramètres du pare-feu
  • Vérifiez 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 se souvient pas du contexte précédent de la discussion :

  • Vérifiez que la fonction chatSession est annotée avec @Riverpod(keepAlive: true).
  • Vérifiez que vous réutilisez la même session de chat pour tous les échanges de messages.
  • Vérifiez 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érifiez 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 appris

  • Configurer Firebase dans une application Flutter
  • Configurer Firebase AI Logic 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 et les sessions de chat des LLM

4. Requêtes efficaces pour les descriptions de couleurs

Dans 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 invites système sont un moyen efficace de personnaliser le comportement des LLM pour des tâches spécifiques sans modifier votre code.

Ce que vous allez apprendre dans cette étape

  • Comprendre les invites système et leur importance dans les applications LLM
  • Rédiger des requêtes efficaces pour des tâches spécifiques à un domaine
  • Charger et utiliser des invites 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 invites système sur le comportement des LLM

Comprendre les invites système

Avant de passer à l'implémentation, voyons ce que sont les invites système et pourquoi elles sont importantes :

Que sont les invites système ?

Une invite système est un type spécial d'instruction donnée à un LLM qui définit le contexte, les consignes de comportement et les attentes pour ses réponses. Contrairement aux messages des utilisateurs, les invites système :

  • Définir le rôle et le persona du LLM
  • Définir des connaissances ou des capacités 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

Considérez une invite système comme une "description de poste" pour le LLM. Elle indique au modèle comment se comporter tout au long de la conversation.

Pourquoi les invites 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éfinissez des limites : définissez ce que le modèle doit faire et ne pas faire.
  4. Améliorer l'expérience utilisateur : créer un modèle d'interaction plus naturel et 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 que le LLM interprète de manière cohérente les descriptions de couleurs et fournisse 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 de l'invite système

Voici ce que fait cette requête :

  1. Définition du rôle : définit le LLM comme un "assistant expert en couleurs"
  2. Explication de la tâche : définit la tâche principale comme l'interprétation des descriptions de couleurs en valeurs RVB.
  3. Format de réponse : indique précisément 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 particuliers : indique comment gérer les descriptions peu claires
  6. Contraintes et consignes : définit des limites, par exemple en conservant 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 mises en forme de manière à être facilement analysées si vous souhaitez extraire les valeurs RVB de manière programmatique.

Mettre à jour pubspec.yaml

Maintenant, mettez à jour 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'éléments.

Créer un fournisseur d'invite 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: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 des ressources de Flutter pour lire le fichier d'invite au moment de l'exécution.

Mettre à jour le fournisseur du modèle 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_ai/firebase_ai.dart';
import 'package:firebase_core/firebase_core.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 = FirebaseAI.googleAI().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 principale modification 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 requête système pour toutes les interactions de cette session de chat.

Générer du code Riverpod

Exécutez la commande du générateur de compilation 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 le LLM Gemini répondant avec une réponse en caractère pour une application de sélection de couleurs

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

  • "Je voudrais un bleu ciel"
  • "Donne-moi un vert forêt"
  • "Crée un orange vif de coucher de soleil"
  • "Je veux la couleur de la lavande fraîche"
  • "Montre-moi quelque chose comme un bleu océan profond"

Vous devriez remarquer que Gemini répond désormais en fournissant des explications conversationnelles sur les couleurs, ainsi que des valeurs RVB au format cohérent. L'invite 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 constater une différence par rapport à l'étape précédente.

L'importance du prompt engineering pour les tâches spécialisées

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

Pour appliquer efficacement le prompt engineering, vous devez :

  1. Définition claire du rôle : définir l'objectif du LLM
  2. Instructions explicites : elles indiquent exactement comment le LLM doit répondre.
  3. Exemples concrets : montrer plutôt que simplement dire à quoi ressemblent les bonnes réponses
  4. Gestion des cas extrêmes : donner au LLM des instructions sur la façon de 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 capacités génériques de Gemini en un assistant spécialisé dans l'interprétation des couleurs, qui fournit des réponses mises en forme 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 vous appuierez sur cette base en ajoutant des déclarations de fonctions. Cela permettra au LLM non seulement de suggérer des valeurs RVB, mais aussi d'appeler des fonctions dans votre application pour définir directement la couleur. Vous verrez ainsi comment les LLM peuvent combler le fossé 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érifiez que votre pubspec.yaml liste correctement le répertoire des composants
  • 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'éléments.

Réponses incohérentes

Si le LLM ne respecte pas systématiquement vos instructions de mise en forme :

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

Limitation du débit des API

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

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

Concepts clés appris

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

Cette étape montre comment personnaliser considérablement 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

Dans cette étape, vous allez commencer à permettre à Gemini d'effectuer des actions dans votre application en implémentant des déclarations de fonctions. Cette fonctionnalité puissante permet au LLM non seulement de suggérer des valeurs RVB, mais aussi de les définir dans l'UI de votre application grâce à 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 fonctions basées sur un schéma pour Gemini
  • Intégrer des déclarations de fonction à votre modèle Gemini
  • Mettre à jour l'invite système pour utiliser les fonctionnalités de l'outil

Comprendre l'appel de fonction

Avant d'implémenter les déclarations de fonctions, voyons ce qu'elles sont et pourquoi elles sont utiles :

Qu'est-ce que l'appel de fonction ?

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

  1. Reconnaître 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 pour 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 à sa réponse

Au lieu de simplement décrire ce qu'il faut faire, l'appel de fonction permet au LLM 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 des applications :

  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 propres et structurées plutôt que du texte à analyser.
  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 : crée une intégration fluide entre la conversation et la fonctionnalité

Dans votre application Colorist, l'appel de fonction permet aux utilisateurs de dire "Je veux un vert forêt" et de voir l'interface utilisateur 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_ai/firebase_ai.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 fonctions

Voici ce que fait ce code :

  1. Nom de la fonction : vous nommez 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 des paramètres : vous définissez des paramètres structurés avec leurs propres descriptions :
    • red : composant rouge du code RVB, spécifié sous la forme d'un nombre compris entre 0.0 et 1.0
    • green : composant vert du RVB, spécifié 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éma : 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 cette fonction doit-elle être appelée ?
  • les paramètres qu'il doit fournir.
  • les contraintes qui s'appliquent à ces paramètres (comme la plage de valeurs) ;

Mettre à jour le fournisseur du modèle Gemini

Modifiez maintenant votre fichier lib/providers/gemini.dart pour inclure les déclarations de fonctions lors de l'initialisation du modèle Gemini :

lib/providers/gemini.dart

import 'dart:async';

import 'package:firebase_ai/firebase_ai.dart';
import 'package:firebase_core/firebase_core.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 = FirebaseAI.googleAI().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();
}

Le changement clé consiste à ajouter le paramètre tools: geminiTools.tools lors de la création du modèle génératif. Gemini peut ainsi connaître les fonctions qu'il peut appeler.

Mettre à jour l'invite système

Vous devez maintenant modifier l'invite système pour indiquer au LLM comment 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 présentez maintenant l'outil set_color au LLM.
  2. Processus modifié : vous remplacez l'étape 3 "Mettre en forme les valeurs dans la réponse" par "Utiliser l'outil pour définir les valeurs".
  3. Exemple mis à jour : vous montrez comment la réponse doit inclure un appel d'outil au lieu d'un texte mis en forme.
  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 requête mise à jour demande 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 générateur de compilation 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, vous voyez Gemini répondre comme s'il avait appelé un outil, mais vous ne voyez aucun changement de couleur dans l'UI avant la prochaine étape.

Exécutez votre application :

flutter run -d DEVICE

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

Essayez de décrire une couleur comme "bleu océan profond" ou "vert forêt", puis 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

Voici 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 serait utile en fonction de la demande de l'utilisateur.
  2. Génération de paramètres : le LLM génère des valeurs de paramètres 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 des applications : votre application recevra cet appel et exécutera la fonction correspondante (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 se produisent, mais vous n'avez pas encore implémenté les étapes 4 et 5 (gestion des appels de fonction), que vous effectuerez à l'étape suivante.

Détails techniques : comment Gemini décide quand utiliser des fonctions

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

  1. Intention de l'utilisateur : indique si la requête de l'utilisateur serait mieux traitée par une fonction.
  2. Pertinence de la fonction : mesure dans quelle mesure les fonctions disponibles correspondent à la tâche.
  3. Disponibilité des paramètres : indique si l'IA peut déterminer les valeurs des paramètres de manière fiable.
  4. Instructions système : conseils de votre invite système sur l'utilisation des fonctions

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

Étape suivante

Dans l'étape suivante, vous implémenterez des gestionnaires pour les appels de fonction provenant de Gemini. La boucle sera ainsi bouclée, ce qui permettra aux descriptions des utilisateurs de déclencher de véritables changements de couleur dans l'UI grâce aux appels de fonction du LLM.

Dépannage

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

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

  • Vérifiez que les noms et les types de paramètres correspondent à ce qui est attendu.
  • Vérifiez 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 invites système

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

  • Vérifiez que votre invite système indique clairement au LLM d'utiliser l'outil set_color.
  • Vérifiez que l'exemple dans l'invite système montre comment utiliser les fonctions.
  • Essayez de rendre l'instruction d'utilisation de l'outil plus explicite.

Problèmes d'ordre général

Si vous rencontrez d'autres problèmes :

  • Recherchez dans la console les éventuelles erreurs liées aux déclarations de fonctions.
  • 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 appris

  • Définir des déclarations de fonctions pour étendre les capacités du 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 des fonctions
  • Comprendre comment les LLM sélectionnent et appellent des fonctions

Cette étape montre comment les LLM peuvent combler le fossé entre les entrées en langage naturel et les appels de fonction structurés, ce qui ouvre la voie à une intégration fluide entre les fonctionnalités de conversation et d'application.

6. Implémenter la gestion des outils

Dans cette étape, vous allez implémenter des gestionnaires pour les appels de fonction provenant de Gemini. Cela complète le cycle de communication entre les entrées en langage naturel et les fonctionnalités concrètes de l'application, 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 l'intégralité du pipeline d'appel de fonction dans les applications LLM
  • Traiter les appels de fonction 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
  • Journalisation des appels et des réponses de fonctions pour la transparence

Comprendre le pipeline d'appel de fonction

Avant de passer à l'implémentation, examinons l'intégralité du pipeline d'appel de fonction :

Flux de bout en bout

  1. Entrée utilisateur : l'utilisateur décrit une couleur en langage naturel (par exemple, "vert forêt")
  2. Traitement du 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 fichier JSON structuré avec des paramètres (valeurs rouge, vert et bleu).
  4. Réception de l'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 la réponse : votre fonction renvoie les résultats au LLM.
  8. Intégration 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 des LLM. Lorsqu'un LLM effectue un appel de fonction, il ne se contente pas d'envoyer la requête et de passer à 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 reconnaît les actions entreprises.

Implémenter des gestionnaires de fonctions

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

lib/services/gemini_tools.dart

import 'package:colorist_ui/colorist_ui.dart';
import 'package:firebase_ai/firebase_ai.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(logStateProvider.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(colorStateProvider.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(logStateProvider.notifier);
    logStateNotifier.logFunctionResults(functionResults);
    return functionResults;
  }

  Map<String, Object?> handleUnknownFunction(String functionName) {
    final logStateNotifier = ref.read(logStateProvider.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

Voici ce que font ces gestionnaires de fonctions :

  1. handleFunctionCall : répartiteur central qui :
    • Enregistre l'appel de fonction pour la transparence dans le panneau de journaux
    • Achemine 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 dans les types attendus (doubles)
    • Met à jour l'état de la couleur de l'application à l'aide de colorStateNotifier.
    • Crée une réponse structurée avec l'état de réussite et les informations sur la couleur actuelle
    • Enregistre les résultats de la fonction pour le débogage
  3. handleUnknownFunction : gestionnaire de secours pour les fonctions inconnues qui :
    • Consigne 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'UI.

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

Maintenant, mettons à jour 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_ai/firebase_ai.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(chatStateProvider.notifier);
    final logStateNotifier = ref.read(logStateProvider.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

La principale nouveauté ici est la gestion complète des appels et des réponses de fonctions :

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. Collecte 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. Mettre à 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
  • Appli → 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 générateur de compilation pour générer le code Riverpod nécessaire :

dart run build_runner build --delete-conflicting-outputs

Exécuter et tester le flux complet

À présent, exécutez votre application :

flutter run -d DEVICE

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

Essayez de saisir différentes descriptions de couleurs :

  • "Je voudrais un rouge carmin foncé."
  • "Montre-moi un bleu ciel apaisant"
  • "Donne-moi la couleur des feuilles de menthe fraîche"
  • "Je veux voir un orange coucher de soleil chaud"
  • "Fais-le en violet royal intense"

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 de journaux
  4. Résultats de la fonction enregistrés immédiatement après
  5. Rectangle de couleur se mettant à jour pour afficher la couleur décrite
  6. Valeurs RVB mises à jour pour afficher les composants de la nouvelle couleur
  7. La réponse finale de Gemini s'affiche, souvent en commentant la couleur définie.

Le panneau de journaux fournit des informations sur ce qui se passe en arrière-plan. Cette page vous indique les informations suivantes :

  • Les appels de fonction exacts effectués par Gemini
  • Les paramètres qu'il choisit pour chaque valeur RVB
  • Les résultats renvoyés par votre fonction
  • Les réponses complémentaires de Gemini

Indicateur d'état de couleur

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

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

Lorsque vous appelez updateColor avec de nouvelles valeurs RVB, l'opération :

  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 les mises à jour de l'UI via la gestion de l'état de Riverpod

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

Comprendre la gestion 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 du journal avec les traces de la pile
  3. Commentaires des utilisateurs : un message d'erreur convivial s'affiche 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é de l'appel 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 en 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 un contexte conversationnel sur 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 faire le lien entre le langage naturel et les actions d'interface utilisateur peut être étendu à d'innombrables autres domaines au-delà de la sélection de couleurs.

Étape suivante

À l'étape suivante, vous allez améliorer l'expérience utilisateur en implémentant des réponses de streaming. Au lieu d'attendre la réponse complète, vous traiterez les blocs de texte et les appels de fonction au fur et à mesure de leur réception, ce qui rendra votre application plus réactive et attrayante.

Dépannage

Problèmes liés aux appels de fonctions

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

  • Vérifiez que la déclaration de votre fonction correspond à ce qui est décrit dans l'invite système.
  • Vérifiez que les noms et les types de paramètres sont cohérents.
  • 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 de journaux pour obtenir des informations détaillées sur les appels de fonction

Problèmes liés aux réponses des fonctions

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

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

Problèmes d'affichage des couleurs

Si les couleurs ne s'affichent pas correctement :

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

Problèmes d'ordre général

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

  • Examinez les journaux pour détecter les erreurs ou les avertissements.
  • Vérifier la connectivité de Firebase AI Logic
  • Vérifiez si les types de paramètres de fonction ne correspondent pas.
  • Assurez-vous que tout le code généré par Riverpod est à jour.

Concepts clés appris

  • 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 issues des réponses des LLM
  • Renvoi des résultats de la fonction au LLM pour qu'il les intègre dans les réponses
  • Utiliser le panneau de journaux pour obtenir de la visibilité sur les interactions entre les applications et les LLM
  • Associer des entrées en langage naturel à des modifications concrètes de l'UI

Cette étape étant terminée, votre application démontre désormais l'un des modèles les plus puissants pour l'intégration de LLM : la traduction des 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 intuitive et conversationnelle qui semble magique pour les utilisateurs.

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

Dans 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 rendra votre application plus réactive et attrayante.

Ce que vous allez faire dans cette étape

  • Importance du streaming pour les applications basées sur des LLM
  • Implémenter des réponses LLM en streaming dans une application Flutter
  • Traitement des blocs 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 flux continu
  • Créer des indicateurs visuels pour les réponses en cours

Pourquoi le streaming est important pour les applications LLM

Avant de commencer, voyons pourquoi les réponses en streaming sont essentielles pour créer d'excellentes expériences utilisateur avec les LLM :

Amélioration de l'expérience utilisateur

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

  1. Latence perçue réduite : 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 de conversation naturel : le texte apparaît progressivement, comme dans une conversation entre humains, ce qui crée une expérience de dialogue plus naturelle.
  3. Traitement progressif des informations : les utilisateurs peuvent commencer à traiter les informations à mesure qu'elles arrivent, plutôt que d'être submergés par un grand bloc de texte d'un seul coup.
  4. Possibilité d'interruption précoce : dans une application complète, les utilisateurs peuvent potentiellement interrompre ou rediriger le LLM s'ils constatent qu'il prend une direction non pertinente.
  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'UX, le streaming offre des avantages techniques :

  1. Exécution anticipée des fonctions : les appels de fonctions 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 de manière progressive à 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 indiquant quand les réponses sont terminées ou toujours 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 d'entraîner des délais avant expiration de la connexion. Le streaming établit la connexion de manière précoce 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 apparaître plus rapidement, ce qui créera 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 savoir 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_ai/firebase_ai.dart';
import 'package:flutter_riverpod/legacy.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(chatStateProvider.notifier);
    final logStateNotifier = ref.read(logStateProvider.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(chatStateProvider.notifier);
    final logStateNotifier = ref.read(logStateProvider.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

Voici ce que fait ce code :

  1. Suivi de l'état de la conversation :
    • Un conversationStateProvider indique si l'application est en train de traiter une réponse.
    • L'état passe de idle à busy pendant le traitement, puis revient à idle.
    • Cela évite les conflits entre plusieurs requêtes simultanées.
  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'UI, 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 par le biais d'un autre appel de streaming.
    • La réponse du LLM à ces résultats est également traitée en streaming.
  5. Gestion des erreurs et nettoyage :
    • try/catch offre une gestion robuste des erreurs.
    • Le bloc finally garantit que l'état de la conversation est correctement réinitialisé.
    • Le message est toujours finalisé, même en cas d'erreur.

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

Mettre à jour 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 principale modification ici consiste à transmettre le conversationState au widget MainScreen. Le 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 où l'UI reflète l'état actuel de la conversation.

Générer du code Riverpod

Exécutez la commande du générateur de compilation pour générer le code Riverpod nécessaire :

dart run build_runner build --delete-conflicting-outputs

Exécuter et tester les réponses en streaming

Exécuter votre application :

flutter run -d DEVICE

Capture d&#39;écran de l&#39;application Colorist montrant le LLM Gemini qui répond en streaming

Testez maintenant le comportement de streaming avec différentes descriptions de couleurs. Voici quelques exemples de descriptions :

  • "Montre-moi la couleur bleu sarcelle foncé de l'océan au crépuscule."
  • "J'aimerais voir un corail aux couleurs vives qui me rappelle les fleurs tropicales."
  • "Crée un vert olive discret comme un vieil uniforme militaire"

Procédure technique de streaming détaillée

Examinons exactement ce qui se passe lorsque vous diffusez une réponse :

Établissement de la connexion

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

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

Transmission de fragments

Lorsque Gemini génère du contenu, des blocs sont envoyés via le flux :

  1. Le serveur envoie des blocs de texte au fur et à mesure de leur génération (généralement quelques mots ou phrases).
  2. Lorsque Gemini décide d'effectuer un appel de fonction, il envoie les informations correspondantes.
  3. Des blocs de texte supplémentaires peuvent suivre les appels de fonction
  4. Le flux se poursuit 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 se met à jour en temps réel avec les résultats du texte et de la fonction.
  4. L'état est suivi pour indiquer que la réponse est toujours en cours de diffusion.

Diffusion terminée

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 de nouveau défini sur "inactif".
  5. L'UI est actualisée pour refléter l'état "Terminé".

Comparaison entre les réponses affichées progressivement et celles qui ne le sont pas

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

Longue attente suivie de l'apparition soudaine du texte

Apparence naturelle et progressive du texte

Gestion de l'état

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

Plus complexe (les messages peuvent être dans un état de flux)

Exécution de la fonction

Se produit uniquement après une réponse complète

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

Complexité de l'implémentation

Plus simple à implémenter

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

Récupération des erreurs

Réponse "tout ou rien"

Les réponses partielles peuvent quand même être utiles

Complexité du code

Moins complexe

Plus complexe en raison de la gestion des flux

Pour une application comme Colorist, les avantages 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 concernant l'expérience utilisateur pour le 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 des saisies : désactivez les saisies utilisateur pendant le streaming pour éviter les demandes multiples qui se chevauchent.
  3. Récupération des erreurs : concevez votre UI pour qu'elle gère la récupération en douceur si le streaming est interrompu.
  4. Transitions d'état : assurez-vous que les transitions entre les états inactif, en streaming et terminé sont fluides.
  5. Visualisation de la progression : envisagez d'utiliser des animations ou des indicateurs subtils pour montrer que le traitement est en cours.
  6. Options d'annulation : dans une application complète, fournissez 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 flux.
  8. Optimisation des performances : minimisez les reconstructions de l'UI lors des mises à jour rapides du flux.

Le package colorist_ui implémente de nombreuses bonnes pratiques pour vous, mais il s'agit de considérations importantes pour toute implémentation de LLM de streaming.

Étape suivante

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

Dépannage

Problèmes de traitement des flux

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

  • Problèmes constatés : 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 corrects dans votre code.
  • Diagnostic : examinez le panneau de journaux pour identifier les messages d'erreur ou les avertissements liés au traitement des flux.
  • Corriger : assurez-vous que le traitement des flux utilise une gestion des erreurs appropriée avec les blocs try/catch.

Appels de fonctions manquants

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

  • Problèmes constatés : 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 les appels de fonction sont reçus.
  • Solution : Ajustez votre prompt système pour indiquer plus explicitement au LLM d'utiliser l'outil set_color.

Gestion générale des erreurs

Pour tout autre problème :

  • Étape 1 : Vérifiez si le panneau de journaux comporte des messages d'erreur
  • Étape 2 : Vérifier la connectivité de Firebase AI Logic
  • É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 éventuelles instructions "await" manquantes

Concepts clés appris

  • Implémenter des réponses en streaming avec l'API Gemini pour une UX plus réactive
  • Gérer l'état de la conversation pour gérer correctement les interactions de streaming
  • Traiter le texte en temps réel et les appels de fonction à leur arrivée
  • Créer des UI responsives qui se mettent à jour 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 engageante qui donne l'impression d'être réellement conversationnelle.

8. Synchronisation du contexte LLM

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

Ce que vous allez faire dans cette étape

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

Comprendre la synchronisation du contexte des LLM

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

Pourquoi la synchronisation du contexte du LLM est-elle importante ?

Lorsque les utilisateurs interagissent avec votre application via des éléments d'UI (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 le lui indiquez explicitement. Synchronisation du contexte LLM :

  1. Maintien du contexte : le LLM est informé de toutes les actions pertinentes de l'utilisateur.
  2. Cohérence : l'expérience est cohérente, car le LLM reconnaît les interactions avec l'UI.
  3. Améliore l'intelligence : permet au LLM de répondre de manière appropriée à toutes les actions de l'utilisateur.
  4. Amélioration de l'expérience utilisateur : l'application semble plus intégrée et réactive
  5. Réduit l'effort de l'utilisateur : élimine la nécessité pour les utilisateurs d'expliquer manuellement leurs actions d'interface utilisateur

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

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

Vous allez d'abord 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_ai/firebase_ai.dart';
import 'package:flutter_riverpod/legacy.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(chatStateProvider.notifier);
    final logStateNotifier = ref.read(logStateProvider.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(chatStateProvider.notifier);
    final logStateNotifier = ref.read(logStateProvider.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 principale nouveauté est la méthode notifyColorSelection, qui :

  1. Accepte un objet ColorData représentant la couleur sélectionnée.
  2. L'encode au format JSON pour qu'il puisse être inclus dans un message
  3. Envoie un message spécialement formaté au LLM indiquant une sélection de l'utilisateur
  4. Réutilise la méthode sendMessage existante pour gérer la notification.

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

Mettre à jour 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),
      ),
    );
  }
}

Le changement clé consiste à ajouter le rappel notifyColorSelection, qui relie l'événement d'UI (sélection d'une couleur dans l'historique) au système de notification du LLM.

Mettre à jour l'invite système

Vous devez maintenant mettre à jour l'invite 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 principale nouveauté est la section "Lorsque les utilisateurs sélectionnent des couleurs historiques", qui :

  1. Explique au LLM le concept de notifications de sélection de l'historique
  2. Elle présente un exemple de ce à quoi ressemblent ces notifications.
  3. Exemple de réponse appropriée
  4. Définir les attentes concernant la confirmation de la sélection et les commentaires sur la couleur

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

Générer du code Riverpod

Exécutez la commande du générateur de compilation 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 de l&#39;historique des couleurs

Pour tester la synchronisation du contexte du LLM :

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

Vous devriez observer les éléments suivants :

  1. La couleur sélectionnée s'affiche dans l'écran principal.
  2. Un message utilisateur s'affiche dans le chat pour indiquer la sélection de couleur.
  3. Le LLM répond en accusant réception de 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 où 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

Examinons 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'UI : 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 du 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 du prompt système.
  7. Mise à jour de l'UI : 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 carte avec des propriétés clés que le LLM peut comprendre. Cela inclut généralement les personnes suivantes :

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

En mettant en forme 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 du contexte LLM

Ce modèle de notification au LLM concernant les événements d'UI a de nombreuses applications au-delà de la sélection de couleurs :

Autres cas d'utilisation

  1. Filtrer les modifications : informer le LLM lorsque les utilisateurs appliquent des filtres aux données
  2. Événements de navigation : ils informent le LLM lorsque les utilisateurs accèdent à différentes sections.
  3. Modifications de la sélection : mettez à jour le LLM lorsque les utilisateurs sélectionnent des éléments dans des listes ou des grilles.
  4. Mises à jour des préférences : indiquez au LLM quand les utilisateurs modifient des paramètres ou des préférences.
  5. Manipulation des données : informer 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 d'UI
  2. Sérialiser les données pertinentes
  3. Envoyer une notification spécialement formatée au LLM
  4. Guider le LLM pour qu'il réponde de manière appropriée grâce au prompt système

Bonnes pratiques pour la synchronisation du contexte des LLM

En fonction de votre implémentation, voici quelques bonnes pratiques pour une synchronisation efficace du contexte des 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 les valeurs RVB, les 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, idéalement 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. Tous les événements d'UI n'ont pas besoin d'être communiqués.

Dépannage

Problèmes de notification

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

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

Gestion du contexte

Si le LLM semble perdre le contexte :

  • Vérifiez que la session de chat est correctement maintenue.
  • Vérifier que les états de conversation changent correctement
  • Assurez-vous que les notifications sont envoyées dans la même session de chat.

Problèmes d'ordre général

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

  • Examinez les journaux pour détecter les erreurs ou les avertissements.
  • Vérifier la connectivité de Firebase AI Logic
  • Vérifiez si les types de paramètres de fonction ne correspondent pas.
  • Assurez-vous que tout le code généré par Riverpod est à jour.

Concepts clés appris

  • Créer une synchronisation du contexte LLM entre l'UI et le LLM
  • Sérialiser les événements d'UI dans un contexte adapté aux LLM
  • Guider le comportement des LLM pour différents schémas d'interaction
  • Créer une expérience cohérente entre les interactions avec et sans message
  • Améliorer la connaissance du LLM de l'état global de l'application

En implémentant la synchronisation du contexte LLM, vous avez créé une expérience véritablement intégrée où le LLM ressemble à 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 Colorist. 🎉

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 les descriptions en langage naturel telles que "orange coucher de soleil" ou "bleu océan profond"
  • 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 conscience du contexte pour différentes méthodes d'interaction

Étapes suivantes

Maintenant que vous maîtrisez les bases de l'intégration de Gemini à Flutter, voici quelques pistes pour continuer votre parcours :

Améliorer votre application Colorist

  • Palettes de couleurs : ajoutez une fonctionnalité permettant de générer des schémas de couleurs complémentaires ou assortis.
  • Saisie vocale : intégrez 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.
  • Requêtes personnalisées : créez une interface permettant aux utilisateurs de personnaliser les requêtes système.
  • Données analytiques avancées : suivez les descriptions qui fonctionnent le mieux ou qui posent des difficultés.

Découvrir d'autres fonctionnalités de Gemini

  • Entrées multimodales : ajoutez des images pour extraire les couleurs des photos.
  • Génération de contenu : utilisez Gemini pour générer du contenu lié aux couleurs, comme des descriptions ou des histoires.
  • Améliorations de l'appel 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éative : créez des outils de rédaction avec des suggestions optimisées par 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é

Observable Flutter Agentic series

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

Dans l'épisode 60, rejoignez à nouveau Craig et Andrew pour étendre l'application de l'atelier de programmation avec de nouvelles fonctionnalités et lutter pour que les LLM fassent ce qu'on leur dit.

Dans l'épisode 61, Craig est rejoint par Chris Sells pour analyser les titres de l'actualité et générer les images correspondantes.

Commentaires

Nous aimerions connaître votre avis sur cet atelier de programmation. Veuillez nous faire part de vos commentaires par l'un des moyens suivants :

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