1. Mit Gemini eine Flutter-App erstellen
Aufgaben
In diesem Codelab entwickeln Sie Colorist, eine interaktive Flutter-Anwendung, die die Leistungsfähigkeit der Gemini API direkt in Ihre Flutter-App bringt. Wollten Sie schon immer, dass Nutzer Ihre App über natürliche Sprache steuern können, wussten aber nicht, wo Sie anfangen sollen? In diesem Codelab erfahren Sie, wie das geht.
Mit Colorist können Nutzer Farben in natürlicher Sprache beschreiben, z. B. „das Orange eines Sonnenuntergangs“ oder „tiefes Ozeanblau“. Die App:
- Verarbeitet diese Beschreibungen mit der Gemini API von Google
- Interpretiert die Beschreibungen in präzise RGB-Farbwerte
- Zeigt die Farbe in Echtzeit auf dem Bildschirm an
- Liefert technische Farbdetails und interessanten Kontext zur Farbe
- Verlauf der zuletzt generierten Farben
Die App bietet eine Split-Screen-Oberfläche mit einem Farbbereich und einem interaktiven Chatsystem auf der einen Seite und einem detaillierten Protokollbereich mit den Rohdaten der LLM-Interaktionen auf der anderen Seite. Anhand dieses Logs können Sie besser nachvollziehen, wie eine LLM-Integration wirklich funktioniert.
Warum das für Flutter-Entwickler wichtig ist
LLMs revolutionieren die Interaktion von Nutzern mit Anwendungen. Die effektive Integration in mobile Apps und Desktop-Apps stellt jedoch besondere Herausforderungen dar. In diesem Codelab lernen Sie praktische Muster kennen, die über die reinen API-Aufrufe hinausgehen.
Ihre Lernreise
In diesem Codelab wird Schritt für Schritt beschrieben, wie Sie Colorist erstellen:
- Projekteinrichtung: Sie beginnen mit einer einfachen Flutter-App-Struktur und dem
colorist_ui
-Paket. - Einfache Gemini-Integration: Verbinden Sie Ihre App mit Firebase AI Logic und implementieren Sie die LLM-Kommunikation.
- Effektive Prompts: Erstellen Sie einen System-Prompt, der das LLM anleitet, Farbbeschreibungen zu verstehen.
- Funktionsdeklarationen: Definieren Sie Tools, mit denen das LLM Farben in Ihrer Anwendung festlegen kann.
- Tool-Verarbeitung: Funktionsaufrufe des LLM verarbeiten und mit dem Status Ihrer App verbinden
- Streaming-Antworten: Die Nutzerfreundlichkeit durch LLM-Antworten in Echtzeit verbessern
- LLM-Kontextsynchronisierung: Für eine einheitliche Nutzererfahrung wird das LLM über Nutzeraktionen informiert.
Lerninhalte
- Firebase AI Logic für Flutter-Anwendungen konfigurieren
- Effektive System-Prompts erstellen, um das Verhalten von LLMs zu steuern
- Funktionsdeklarationen implementieren, die natürliche Sprache und App-Funktionen verbinden
- Streamingantworten verarbeiten, um eine reaktionsschnelle Nutzererfahrung zu ermöglichen
- Status zwischen UI-Ereignissen und dem LLM synchronisieren
- LLM-Konversationsstatus mit Riverpod verwalten
- Fehler in LLM-gestützten Anwendungen ordnungsgemäß behandeln
Codevorschau: Ein Vorgeschmack auf die Implementierung
Hier sehen Sie einen Ausschnitt der Funktionsdeklaration, die Sie erstellen, damit das LLM Farben in Ihrer App festlegen kann:
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)'),
},
);
Videoübersicht zu diesem Codelab
Craig Labenz und Andrew Brogdon sprechen in der Observable Flutter-Folge 59 über dieses Codelab:
Vorbereitung
Für dieses Codelab benötigen Sie Folgendes:
- Flutter-Entwicklungserfahrung: Vertrautheit mit den Grundlagen von Flutter und der Dart-Syntax
- Kenntnisse im asynchronen Programmieren: Verständnis von Futures, async/await und Streams
- Firebase-Konto: Sie benötigen ein Google-Konto, um Firebase einzurichten.
Legen wir los und erstellen Sie Ihre erste LLM-gestützte Flutter-App.
2. Projekteinrichtung und Echo-Dienst
In diesem ersten Schritt richten Sie die Projektstruktur ein und implementieren einen Echo-Dienst, der später durch die Gemini API-Integration ersetzt wird. So wird die Anwendungsarchitektur festgelegt und dafür gesorgt, dass die Benutzeroberfläche richtig funktioniert, bevor die Komplexität von LLM-Aufrufen hinzukommt.
Lerninhalte in diesem Schritt
- Ein Flutter-Projekt mit den erforderlichen Abhängigkeiten einrichten
- Mit dem
colorist_ui
-Paket für UI-Komponenten arbeiten - Einen Echo-Nachrichtendienst implementieren und mit der Benutzeroberfläche verbinden
Neues Flutter-Projekt erstellen
Erstellen Sie zuerst ein neues Flutter-Projekt mit dem folgenden Befehl:
flutter create -e colorist --platforms=android,ios,macos,web,windows
Das Flag -e
gibt an, dass Sie ein leeres Projekt ohne die Standard-App counter
erstellen möchten. Die App ist für die Verwendung auf Computern, Mobilgeräten und im Web konzipiert. Linux wird auf flutterfire
derzeit jedoch nicht unterstützt.
Abhängigkeiten hinzufügen
Gehen Sie zu Ihrem Projektverzeichnis und fügen Sie die erforderlichen Abhängigkeiten hinzu:
cd colorist
flutter pub add colorist_ui flutter_riverpod riverpod_annotation
flutter pub add --dev build_runner riverpod_generator riverpod_lint json_serializable
Dadurch werden die folgenden Schlüsselpakete hinzugefügt:
colorist_ui
: Ein benutzerdefiniertes Paket, das die UI-Komponenten für die Colorist-App bereitstelltflutter_riverpod
undriverpod_annotation
: Für die Statusverwaltunglogging
: Für strukturiertes Logging- Entwicklungsabhängigkeiten für Codegenerierung und Linting
Ihre pubspec.yaml
sollte in etwa so aussehen:
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
main.dart
-Datei implementieren
Ersetzen Sie den Inhalt von lib/main.dart
durch Folgendes:
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);
}
}
Dadurch wird eine Flutter-App eingerichtet, die einen Echodienst implementiert, der das Verhalten eines LLM imitiert, indem er die Nachricht des Nutzers zurückgibt.
Übersicht der Architektur
Sehen wir uns kurz die Architektur der colorist
App an:
Das Paket colorist_ui
Das colorist_ui
-Paket bietet vorgefertigte UI-Komponenten und Tools zur Statusverwaltung:
- MainScreen: Die Haupt-UI-Komponente, in der Folgendes angezeigt wird:
- Ein Splitscreen-Layout auf dem Computer (Interaktionsbereich und Protokollbereich)
- Eine tabellarische Oberfläche auf einem Mobilgerät
- Farbdarstellung, Chatoberfläche und Verlaufsminiaturen
- Statusverwaltung: Die App verwendet mehrere Status-Notifier:
- ChatStateNotifier: Verwaltet die Chatnachrichten.
- ColorStateNotifier: Verwaltet die aktuelle Farbe und den Verlauf
- LogStateNotifier: Verwaltet die Logeinträge für das Debugging.
- Nachrichtenverarbeitung: Die App verwendet ein Nachrichtenmodell mit verschiedenen Status:
- Nutzermitteilungen: vom Nutzer eingegeben
- LLM-Nachrichten: Vom LLM (oder vorerst von Ihrem Echo-Dienst) generiert
- MessageState: Gibt an, ob LLM-Nachrichten vollständig sind oder noch gestreamt werden.
Anwendungsarchitektur
Die App folgt der folgenden Architektur:
- UI-Ebene: Wird vom
colorist_ui
-Paket bereitgestellt. - Zustandsverwaltung: Verwendet Riverpod für die reaktive Zustandsverwaltung
- Dienstebene: Enthält derzeit Ihren einfachen Echo-Dienst. Dieser wird durch den Gemini Chat-Dienst ersetzt.
- LLM-Integration: Wird in späteren Schritten hinzugefügt
Durch diese Trennung können Sie sich auf die Implementierung der LLM-Integration konzentrieren, während sich die UI-Komponenten bereits erledigt haben.
Anwendung ausführen
Führen Sie die App mit dem folgenden Befehl aus:
flutter run -d DEVICE
Ersetzen Sie DEVICE
durch Ihr Zielgerät, z. B. macos
, windows
, chrome
oder eine Geräte-ID.
Sie sollten jetzt die Colorist-App mit Folgendem sehen:
- Ein Farbbereich mit einer Standardfarbe
- Eine Chatoberfläche, auf der Sie Nachrichten eingeben können
- Ein Protokollbereich mit den Chatinteraktionen
Geben Sie eine Nachricht wie „Ich möchte eine dunkelblaue Farbe“ ein und drücken Sie auf „Senden“. Der Echo-Dienst wiederholt Ihre Nachricht einfach. In späteren Schritten ersetzen Sie dies durch die tatsächliche Farbanalyse mit Firebase AI Logic.
Nächste Schritte
Im nächsten Schritt konfigurieren Sie Firebase und implementieren die grundlegende Gemini API-Integration, um Ihren Echo-Dienst durch den Gemini-Chatdienst zu ersetzen. So kann die App Farbbeschreibungen interpretieren und intelligente Antworten geben.
Fehlerbehebung
Probleme mit UI-Paketen
Wenn Probleme mit dem colorist_ui
-Paket auftreten:
- Achten Sie darauf, dass Sie die aktuelle Version verwenden.
- Prüfen, ob die Abhängigkeit richtig hinzugefügt wurde
- Auf in Konflikt stehende Paketversionen prüfen
Build-Fehler
Wenn Build-Fehler angezeigt werden:
- Prüfen Sie, ob Sie das neueste stabile Flutter SDK installiert haben.
- Führen Sie
flutter clean
und dannflutter pub get
aus. - Konsolenausgabe auf bestimmte Fehlermeldungen prüfen
Wichtige gelernte Konzepte
- Ein Flutter-Projekt mit den erforderlichen Abhängigkeiten einrichten
- Architektur der Anwendung und Verantwortlichkeiten der Komponenten
- Einen einfachen Dienst implementieren, der das Verhalten eines LLM nachahmt
- Dienst mit den UI-Komponenten verbinden
- Riverpod für die Statusverwaltung verwenden
3. Einfache Gemini Chat-Integration
In diesem Schritt ersetzen Sie den Echo-Dienst aus dem vorherigen Schritt durch die Gemini API-Integration mit Firebase AI Logic. Sie konfigurieren Firebase, richten die erforderlichen Anbieter ein und implementieren einen einfachen Chatdienst, der mit der Gemini API kommuniziert.
Lerninhalte in diesem Schritt
- Firebase in einer Flutter-Anwendung einrichten
- Firebase AI Logic für den Gemini-Zugriff konfigurieren
- Riverpod-Provider für Firebase- und Gemini-Dienste erstellen
- Einen einfachen Chatdienst mit der Gemini API implementieren
- Asynchrone API-Antworten und Fehlerstatus behandeln
Firebase einrichten
Zuerst müssen Sie Firebase für Ihr Flutter-Projekt einrichten. Dazu müssen Sie ein Firebase-Projekt erstellen, Ihre App hinzufügen und die erforderlichen Firebase AI Logic-Einstellungen konfigurieren.
Firebase-Projekt erstellen
- Rufen Sie die Firebase Console auf und melden Sie sich mit Ihrem Google-Konto an.
- Klicken Sie auf Firebase-Projekt erstellen oder wählen Sie ein vorhandenes Projekt aus.
- Folgen Sie dem Einrichtungsassistenten, um Ihr Projekt zu erstellen.
Firebase AI Logic in Ihrem Firebase-Projekt einrichten
- Rufen Sie in der Firebase Console Ihr Projekt auf.
- Wählen Sie in der linken Seitenleiste KI aus.
- Wählen Sie im Drop-down-Menü für KI die Option KI-Logik aus.
- Wählen Sie auf der Karte „Firebase AI Logic“ die Option Jetzt starten aus.
- Folgen Sie der Anleitung, um die Gemini Developer API für Ihr Projekt zu aktivieren.
FlutterFire CLI installieren
Die FlutterFire CLI vereinfacht die Firebase-Einrichtung in Flutter-Apps:
dart pub global activate flutterfire_cli
Firebase zu Ihrer Flutter-App hinzufügen
- Fügen Sie Ihrem Projekt die Firebase Core- und Firebase AI Logic-Pakete hinzu:
flutter pub add firebase_core firebase_ai
- Führen Sie den FlutterFire-Konfigurationsbefehl aus:
flutterfire configure
Mit diesem Befehl wird Folgendes ausgeführt:
- Sie werden aufgefordert, das gerade erstellte Firebase-Projekt auszuwählen.
- Flutter-App(s) bei Firebase registrieren
firebase_options.dart
-Datei mit Ihrer Projektkonfiguration generieren
Der Befehl erkennt automatisch die ausgewählten Plattformen (iOS, Android, macOS, Windows, Web) und konfiguriert sie entsprechend.
Plattformspezifische Konfiguration
Für Firebase sind Mindestversionen erforderlich, die höher sind als die Standardversionen für Flutter. Außerdem ist Netzwerkzugriff erforderlich, um mit den Firebase AI Logic-Servern zu kommunizieren.
macOS-Berechtigungen konfigurieren
Unter macOS müssen Sie den Netzwerkzugriff in den Berechtigungen Ihrer App aktivieren:
- Öffnen Sie
macos/Runner/DebugProfile.entitlements
und fügen Sie Folgendes hinzu:
macos/Runner/DebugProfile.entitlements
<key>com.apple.security.network.client</key>
<true/>
- Öffnen Sie auch
macos/Runner/Release.entitlements
und fügen Sie denselben Eintrag hinzu.
iOS-Einstellungen konfigurieren
Aktualisieren Sie für iOS die Mindestversion oben in ios/Podfile
:
ios/Podfile
# Firebase requires at least iOS 15.0
platform :ios, '15.0'
Gemini-Modellanbieter erstellen
Jetzt erstellen Sie die Riverpod-Provider für Firebase und Gemini. Erstellen Sie eine neue Datei 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();
}
Diese Datei definiert die Grundlage für drei wichtige Anbieter. Diese Anbieter werden generiert, wenn Sie dart run build_runner
mit den Riverpod-Codegeneratoren ausführen.
firebaseAppProvider
: Initialisiert Firebase mit Ihrer Projektkonfiguration.geminiModelProvider
: Erstellt eine Gemini-Instanz für generative Modelle.chatSessionProvider
: Erstellt und verwaltet eine Chatsitzung mit dem Gemini-Modell.
Die Anmerkung keepAlive: true
in der Chatsitzung sorgt dafür, dass sie während des gesamten Lebenszyklus der App erhalten bleibt und der Kontext der Unterhaltung beibehalten wird.
Gemini Chat-Dienst implementieren
Erstellen Sie eine neue Datei lib/services/gemini_chat_service.dart
, um den Chatdienst zu implementieren:
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);
Dieser Dienst:
- Nimmt Nutzernachrichten entgegen und sendet sie an die Gemini API
- Aktualisiert die Chatoberfläche mit Antworten des Modells
- Protokolliert die gesamte Kommunikation, um den tatsächlichen LLM-Ablauf besser nachvollziehen zu können
- Fehler mit angemessenem Nutzerfeedback behandeln
Hinweis:Das Logfenster sieht an dieser Stelle fast genauso aus wie das Chatfenster. Das Protokoll wird interessanter, wenn Sie Funktionsaufrufe und dann Streaming-Antworten einführen.
Riverpod-Code generieren
Führen Sie den Build-Runner-Befehl aus, um den erforderlichen Riverpod-Code zu generieren:
dart run build_runner build --delete-conflicting-outputs
Dadurch werden die .g.dart
-Dateien erstellt, die Riverpod für die Funktion benötigt.
Datei „main.dart“ aktualisieren
Aktualisieren Sie die Datei lib/main.dart
, um den neuen Gemini Chat-Dienst zu verwenden:
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),
),
);
}
}
Die wichtigsten Änderungen in diesem Update sind:
- Ersetzen des Echo-Dienstes durch den auf der Gemini API basierenden Chatdienst
- Lade- und Fehlerbildschirme mit dem
AsyncValue
-Muster von Riverpod und derwhen
-Methode hinzufügen - Benutzeroberfläche über den
sendMessage
-Callback mit dem neuen Chatdienst verbinden
Anwendung ausführen
Führen Sie die App mit dem folgenden Befehl aus:
flutter run -d DEVICE
Ersetzen Sie DEVICE
durch Ihr Zielgerät, z. B. macos
, windows
, chrome
oder eine Geräte-ID.
Wenn Sie jetzt eine Nachricht eingeben, wird sie an die Gemini API gesendet und Sie erhalten eine Antwort vom LLM anstelle eines Echos. Im Log-Bereich werden die Interaktionen mit der API angezeigt.
LLM-Kommunikation
Sehen wir uns an, was passiert, wenn Sie mit der Gemini API kommunizieren:
Kommunikationsablauf
- Nutzereingabe: Der Nutzer gibt Text in die Chatoberfläche ein.
- Anfrageformatierung: Die App formatiert den Text als
Content
-Objekt für die Gemini API. - API-Kommunikation: Der Text wird über Firebase AI Logic an die Gemini API gesendet.
- LLM-Verarbeitung: Das Gemini-Modell verarbeitet den Text und generiert eine Antwort.
- Antwortverarbeitung: Die App empfängt die Antwort und aktualisiert die Benutzeroberfläche.
- Protokollierung: Alle Kommunikationsvorgänge werden zur Transparenz protokolliert.
Chatsitzungen und Unterhaltungskontext
In der Gemini Chat-Sitzung wird der Kontext zwischen den Nachrichten beibehalten, sodass Unterhaltungen möglich sind. Das bedeutet, dass sich das LLM an frühere Interaktionen in der aktuellen Sitzung „erinnert“, was zu kohärenteren Unterhaltungen führt.
Die Annotation keepAlive: true
für den Anbieter Ihrer Chatsitzung sorgt dafür, dass dieser Kontext während des gesamten Lebenszyklus der App erhalten bleibt. Dieser dauerhafte Kontext ist entscheidend, um einen natürlichen Gesprächsfluss mit dem LLM aufrechtzuerhalten.
Nächste Schritte
An diesem Punkt können Sie Gemini API alles fragen, da es keine Einschränkungen gibt, worauf sie antwortet. Sie könnten beispielsweise nach einer Zusammenfassung der Rosenkriege fragen, die nichts mit dem Zweck Ihrer Farbanwendung zu tun haben.
Im nächsten Schritt erstellen Sie einen System-Prompt, um Gemini bei der Interpretation von Farbbeschreibungen zu unterstützen. Hier wird gezeigt, wie Sie das Verhalten eines LLM an anwendungsspezifische Anforderungen anpassen und seine Funktionen auf die Domain Ihrer App konzentrieren.
Fehlerbehebung
Probleme bei der Firebase-Konfiguration
Wenn bei der Firebase-Initialisierung Fehler auftreten, können Sie Folgendes versuchen:
- Prüfen Sie, ob die Datei
firebase_options.dart
korrekt generiert wurde. - Prüfen Sie, ob Sie ein Upgrade auf den Blaze-Tarif für den Zugriff auf Firebase AI Logic durchgeführt haben.
Fehler beim API-Zugriff
Wenn Sie beim Zugriff auf die Gemini API Fehler erhalten:
- Prüfen, ob die Abrechnung für Ihr Firebase-Projekt richtig eingerichtet ist
- Prüfen, ob Firebase AI Logic und die Cloud AI API in Ihrem Firebase-Projekt aktiviert sind
- Netzwerkverbindung und Firewalleinstellungen prüfen
- Prüfen Sie, ob der Modellname (
gemini-2.0-flash
) korrekt ist und verfügbar ist.
Probleme mit dem Kontext von Unterhaltungen
Wenn Sie feststellen, dass Gemini sich den vorherigen Kontext aus dem Chat nicht merkt, gehen Sie so vor:
- Prüfen Sie, ob die Funktion
chatSession
mit@Riverpod(keepAlive: true)
annotiert ist. - Prüfen Sie, ob Sie für alle Nachrichten denselben Chat verwenden.
- Prüfen Sie, ob die Chatsitzung richtig initialisiert wurde, bevor Sie Nachrichten senden.
Plattformspezifische Probleme
Bei plattformspezifischen Problemen:
- iOS/macOS: Prüfen, ob die richtigen Berechtigungen festgelegt und Mindestversionen konfiguriert sind
- Android: Prüfen, ob die mindestens erforderliche SDK-Version richtig festgelegt ist
- Plattformspezifische Fehlermeldungen in der Konsole prüfen
Wichtige gelernte Konzepte
- Firebase in einer Flutter-Anwendung einrichten
- Firebase AI Logic für den Zugriff auf Gemini konfigurieren
- Riverpod-Anbieter für asynchrone Dienste erstellen
- Chatdienst implementieren, der mit einem LLM kommuniziert
- Asynchrone API-Status (Laden, Fehler, Daten) verarbeiten
- Kommunikationsablauf und Chatsitzungen von LLMs
4. Effektive Prompts für Farbbeschreibungen
In diesem Schritt erstellen und implementieren Sie einen System-Prompt, der Gemini bei der Interpretation von Farbbeschreibungen unterstützt. Systemprompts sind eine leistungsstarke Möglichkeit, das Verhalten von LLMs für bestimmte Aufgaben anzupassen, ohne den Code ändern zu müssen.
Lerninhalte in diesem Schritt
- Systemprompts und ihre Bedeutung in LLM-Anwendungen
- Effektive Prompts für domainspezifische Aufgaben erstellen
- Systemprompts in einer Flutter-App laden und verwenden
- LLM anleiten, einheitlich formatierte Antworten zu geben
- Testen, wie sich Systemprompts auf das Verhalten von LLMs auswirken
Systemaufforderungen
Bevor wir uns mit der Implementierung befassen, sollten wir uns ansehen, was Systemprompts sind und warum sie wichtig sind:
Was sind Systemprompts?
Ein Systemprompt ist eine spezielle Art von Anweisung, die einem LLM gegeben wird und den Kontext, die Verhaltensrichtlinien und die Erwartungen für seine Antworten festlegt. Im Gegensatz zu Nutzernachrichten gilt für Systemprompts Folgendes:
- Rolle und Persona des LLM festlegen
- Spezialwissen oder ‑fähigkeiten definieren
- Formatierungsanweisungen bereitstellen
- Einschränkungen für Antworten festlegen
- Beschreiben, wie in verschiedenen Szenarien vorgegangen werden soll
Ein Systemprompt ist wie eine „Stellenbeschreibung“ für das LLM. Er gibt dem Modell vor, wie es sich während der Unterhaltung verhalten soll.
Warum Systemprompts wichtig sind
Systemprompts sind entscheidend für konsistente, nützliche LLM-Interaktionen, weil sie:
- Für Konsistenz sorgen: Das Modell anweisen, Antworten in einem einheitlichen Format zu liefern
- Relevanz verbessern: Richten Sie das Modell auf Ihren spezifischen Bereich aus (in Ihrem Fall Farben).
- Grenzen festlegen: Definieren Sie, was das Modell tun darf und was nicht.
- Nutzerfreundlichkeit verbessern: Ein natürlicheres, hilfreiches Interaktionsmuster schaffen
- Nachbearbeitung reduzieren: Antworten in Formaten erhalten, die leichter zu parsen oder darzustellen sind
Für Ihre Colorist-App muss das LLM Farbbeschreibungen einheitlich interpretieren und RGB-Werte in einem bestimmten Format bereitstellen.
Systemprompt-Asset erstellen
Zuerst erstellen Sie eine Systemprompt-Datei, die zur Laufzeit geladen wird. So können Sie den Prompt ändern, ohne Ihre App neu zu kompilieren.
Erstellen Sie eine neue Datei vom Typ assets/system_prompt.md
mit folgendem Inhalt:
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
Struktur des System-Prompts
Sehen wir uns an, was dieser Prompt bewirkt:
- Definition der Rolle: Das LLM wird als „Farbexperten-Assistent“ festgelegt.
- Aufgabenerklärung: Definiert die primäre Aufgabe als Interpretation von Farbbeschreibungen in RGB-Werte.
- Antwortformat: Gibt genau an, wie RGB-Werte für Konsistenz formatiert werden sollen.
- Beispiel für Austausch: Bietet ein konkretes Beispiel für das erwartete Interaktionsmuster.
- Umgang mit Grenzfall: Hier wird beschrieben, wie mit unklaren Beschreibungen umzugehen ist.
- Einschränkungen und Richtlinien: Legt Grenzen fest, z. B. dass RGB-Werte zwischen 0,0 und 1,0 liegen müssen.
Dieser strukturierte Ansatz sorgt dafür, dass die Antworten des LLM konsistent und informativ sind und so formatiert werden, dass sie sich leicht parsen lassen, wenn Sie die RGB-Werte programmatisch extrahieren möchten.
pubspec.yaml aktualisieren
Aktualisieren Sie nun das Ende der Datei pubspec.yaml
, um das Assets-Verzeichnis einzuschließen:
pubspec.yaml
flutter:
uses-material-design: true
assets:
- assets/
Führen Sie flutter pub get
aus, um das Asset-Bundle zu aktualisieren.
Systemprompt-Anbieter erstellen
Erstellen Sie eine neue Datei lib/providers/system_prompt.dart
, um den Systemprompt zu laden:
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');
Dieser Anbieter verwendet das Asset-Ladesystem von Flutter, um die Prompt-Datei zur Laufzeit zu lesen.
Gemini-Modellanbieter aktualisieren
Ändern Sie nun die Datei lib/providers/gemini.dart
, um den Systemprompt einzufügen:
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();
}
Die Änderung besteht darin, dass beim Erstellen des generativen Modells systemInstruction: Content.system(systemPrompt)
hinzugefügt wird. Dadurch wird Gemini angewiesen, Ihre Anweisungen als System-Prompt für alle Interaktionen in dieser Chatsitzung zu verwenden.
Riverpod-Code generieren
Führen Sie den Build-Runner-Befehl aus, um den erforderlichen Riverpod-Code zu generieren:
dart run build_runner build --delete-conflicting-outputs
Anwendung ausführen und testen
Führen Sie nun Ihre Anwendung aus:
flutter run -d DEVICE
Testen Sie die Funktion mit verschiedenen Farbbeschreibungen:
- „I'd like a sky blue“ (Ich möchte einen himmelblauen)
- „Gib mir ein Waldgrün“
- „Erstelle ein leuchtendes Sonnenuntergangsorange.“
- „Ich möchte die Farbe von frischem Lavendel“
- „Show me something like a deep ocean blue“ (Zeig mir etwas wie ein tiefes Ozeanblau)
Gemini antwortet jetzt mit Erklärungen zu den Farben in einem natürlichen Gesprächston und gibt einheitlich formatierte RGB-Werte an. Der System-Prompt hat das LLM effektiv dazu angeleitet, die Art von Antworten zu liefern, die Sie benötigen.
Sie können auch nach Inhalten fragen, die nichts mit Farben zu tun haben. Nenne die Hauptursachen der Rosenkriege. Sie sollten einen Unterschied zum vorherigen Schritt feststellen.
Die Bedeutung von Prompt Engineering für spezielle Aufgaben
Systemprompts sind sowohl Kunst als auch Wissenschaft. Sie sind ein wichtiger Bestandteil der LLM-Integration und können die Nützlichkeit des Modells für Ihre spezielle Anwendung erheblich beeinflussen. Sie haben hier eine Form des Prompt-Engineerings angewendet, indem Sie Anweisungen so angepasst haben, dass das Modell sich so verhält, wie es für Ihre Anwendung erforderlich ist.
Effektives Prompt Engineering umfasst:
- Klare Rollendefinition: Festlegen des Zwecks des LLM
- Explizite Anweisungen: Detaillierte Angaben dazu, wie das LLM reagieren soll
- Konkrete Beispiele: Zeigen statt nur sagen, wie gute Antworten aussehen
- Umgang mit Sonderfällen: Das LLM anweisen, wie mit mehrdeutigen Szenarien umzugehen ist
- Spezifikationen zur Formatierung: Antworten müssen einheitlich und nutzerfreundlich strukturiert sein.
Der von Ihnen erstellte Systemprompt wandelt die allgemeinen Funktionen von Gemini in einen spezialisierten Assistenten für die Farbanalyse um, der Antworten liefert, die speziell auf die Anforderungen Ihrer Anwendung zugeschnitten sind. Dies ist ein leistungsstarkes Muster, das Sie auf viele verschiedene Bereiche und Aufgaben anwenden können.
Nächste Schritte
Im nächsten Schritt bauen Sie auf dieser Grundlage auf, indem Sie Funktionsdeklarationen hinzufügen. So kann das LLM nicht nur RGB-Werte vorschlagen, sondern auch Funktionen in Ihrer App aufrufen, um die Farbe direkt festzulegen. Hier wird gezeigt, wie LLMs die Lücke zwischen natürlicher Sprache und konkreten Anwendungsfunktionen schließen können.
Fehlerbehebung
Probleme beim Laden von Assets
Wenn beim Laden des Systemprompts Fehler auftreten:
- Prüfen Sie, ob in
pubspec.yaml
das Asset-Verzeichnis richtig aufgeführt ist. - Prüfen Sie, ob der Pfad in
rootBundle.loadString()
mit dem Speicherort der Datei übereinstimmt. - Führen Sie
flutter clean
und dannflutter pub get
aus, um das Asset-Bundle zu aktualisieren.
Inkonsistente Antworten
Wenn das LLM Ihre Formatierungsanweisungen nicht konsistent befolgt:
- Formatanforderungen im System-Prompt expliziter formulieren
- Fügen Sie weitere Beispiele hinzu, um das erwartete Muster zu veranschaulichen.
- Das angeforderte Format muss für das Modell angemessen sein.
API-Ratenbegrenzung
Wenn Fehler im Zusammenhang mit der Ratenbegrenzung auftreten, gehen Sie so vor:
- Der Firebase AI Logic-Dienst hat Nutzungslimits.
- Wiederholungslogik mit exponentiellem Backoff implementieren
- In der Firebase Console nach Kontingentproblemen suchen
Wichtige gelernte Konzepte
- Rolle und Bedeutung von Systemprompts in LLM-Anwendungen
- Effektive Prompts mit klaren Anweisungen, Beispielen und Einschränkungen erstellen
- Systemprompts in einer Flutter-Anwendung laden und verwenden
- LLM-Verhalten für fachbereichsspezifische Aufgaben steuern
- Mit Prompt-Engineering LLM-Antworten optimieren
In diesem Schritt wird gezeigt, wie Sie das Verhalten von LLMs erheblich anpassen können, ohne Ihren Code zu ändern. Dazu müssen Sie lediglich klare Anweisungen im Systemprompt angeben.
5. Funktionsdeklarationen für LLM-Tools
In diesem Schritt beginnen Sie mit der Implementierung von Funktionsdeklarationen, um Gemini zu ermöglichen, Aktionen in Ihrer App auszuführen. Mit dieser leistungsstarken Funktion kann das LLM nicht nur RGB-Werte vorschlagen, sondern sie auch über spezielle Tool-Aufrufe in der Benutzeroberfläche Ihrer App festlegen. Dazu ist jedoch der nächste Schritt erforderlich, um die in der Flutter-App ausgeführten LLM-Anfragen zu sehen.
Lerninhalte in diesem Schritt
- Funktionsaufrufe mit LLMs und ihre Vorteile für Flutter-Anwendungen
- Schemabasierte Funktionsdeklarationen für Gemini definieren
- Funktionsdeklarationen in Ihr Gemini-Modell einbinden
- Systemaufforderung aktualisieren, um Toolfunktionen zu nutzen
Funktionsaufrufe
Bevor wir Funktionsdeklarationen implementieren, sollten wir uns ansehen, was sie sind und warum sie nützlich sind:
Was sind Funktionsaufrufe?
Funktionsaufrufe (manchmal auch als „Tool-Nutzung“ bezeichnet) sind eine Funktion, mit der ein LLM Folgendes tun kann:
- Erkennen, wann die Ausführung einer bestimmten Funktion für eine Nutzeranfrage sinnvoll wäre
- Generieren Sie ein strukturiertes JSON-Objekt mit den für diese Funktion erforderlichen Parametern.
- Anwendung die Funktion mit diesen Parametern ausführen lassen
- Das Ergebnis der Funktion empfangen und in die Antwort einfügen
Beim Funktionsaufruf beschreibt das LLM nicht nur, was zu tun ist, sondern kann auch konkrete Aktionen in Ihrer Anwendung auslösen.
Warum Funktionsaufrufe für Flutter-Apps wichtig sind
Funktionsaufrufe schaffen eine leistungsstarke Verbindung zwischen natürlicher Sprache und Anwendungsfunktionen:
- Direkte Aktion: Nutzer können in natürlicher Sprache beschreiben, was sie möchten, und die App reagiert mit konkreten Aktionen.
- Strukturierte Ausgabe: Das LLM gibt saubere, strukturierte Daten aus, anstatt Text, der geparst werden muss.
- Komplexe Vorgänge: Ermöglicht dem LLM, auf externe Daten zuzugreifen, Berechnungen durchzuführen oder den Anwendungsstatus zu ändern.
- Bessere Nutzerfreundlichkeit: Ermöglicht eine nahtlose Integration zwischen Konversation und Funktion
In Ihrer Colorist-App können Nutzer durch die Funktion „Funktionsaufruf“ beispielsweise „Ich möchte ein Waldgrün“ sagen und die Benutzeroberfläche wird sofort mit dieser Farbe aktualisiert, ohne dass RGB-Werte aus Text geparst werden müssen.
Funktionsdeklarationen definieren
Erstellen Sie eine neue Datei lib/services/gemini_tools.dart
, um Ihre Funktionsdeklarationen zu definieren:
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);
Funktionsdeklarationen
Sehen wir uns an, was dieser Code bewirkt:
- Funktionsbenennung: Sie nennen Ihre Funktion
set_color
, um ihren Zweck klar anzugeben. - Funktionsbeschreibung: Sie geben eine klare Beschreibung an, damit das LLM weiß, wann die Funktion verwendet werden soll.
- Parameterdefinitionen: Sie definieren strukturierte Parameter mit eigenen Beschreibungen:
red
: Die rote Komponente von RGB, angegeben als Zahl zwischen 0,0 und 1,0.green
: Die grüne Komponente von RGB, angegeben als Zahl zwischen 0,0 und 1,0.blue
: Die blaue Komponente von RGB, angegeben als Zahl zwischen 0,0 und 1,0.
- Schematypen: Mit
Schema.number()
geben Sie an, dass es sich um numerische Werte handelt. - Tools-Sammlung: Sie erstellen eine Liste von Tools, die Ihre Funktionsdeklaration enthält.
Dieser strukturierte Ansatz hilft dem Gemini LLM, Folgendes zu verstehen:
- Wann diese Funktion aufgerufen werden soll
- Welche Parameter müssen angegeben werden?
- Welche Einschränkungen gelten für diese Parameter (z. B. der Wertebereich)?
Gemini-Modellanbieter aktualisieren
Ändern Sie nun Ihre lib/providers/gemini.dart
-Datei so, dass die Funktionsdeklarationen beim Initialisieren des Gemini-Modells enthalten sind:
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();
}
Die wichtigste Änderung ist das Hinzufügen des Parameters tools: geminiTools.tools
beim Erstellen des generativen Modells. So weiß Gemini, welche Funktionen aufgerufen werden können.
Systemaufforderung aktualisieren
Jetzt müssen Sie den Systemprompt so anpassen, dass das LLM angewiesen wird, das neue set_color
-Tool zu verwenden. assets/system_prompt.md
aktualisieren:
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
Die wichtigsten Änderungen am Systemprompt sind:
- Tool-Einführung: Anstatt nach formatierten RGB-Werten zu fragen, informieren Sie das LLM jetzt über das Tool
set_color
. - Geänderter Prozess: Sie ändern Schritt 3 von „Werte in der Antwort formatieren“ zu „Tool zum Festlegen von Werten verwenden“.
- Aktualisiertes Beispiel: Sie zeigen, wie die Antwort einen Tool-Aufruf anstelle von formatiertem Text enthalten sollte.
- Anforderung an Formatierung entfernt: Da Sie strukturierte Funktionsaufrufe verwenden, ist kein bestimmtes Textformat mehr erforderlich.
In diesem aktualisierten Prompt wird das LLM angewiesen, Funktionsaufrufe zu verwenden, anstatt nur RGB-Werte in Textform anzugeben.
Riverpod-Code generieren
Führen Sie den Build-Runner-Befehl aus, um den erforderlichen Riverpod-Code zu generieren:
dart run build_runner build --delete-conflicting-outputs
Anwendung ausführen
An diesem Punkt generiert Gemini Inhalte, in denen versucht wird, Funktionsaufrufe zu verwenden. Sie haben jedoch noch keine Handler für die Funktionsaufrufe implementiert. Wenn Sie die App ausführen und eine Farbe beschreiben, antwortet Gemini so, als ob ein Tool aufgerufen wurde. Sie sehen jedoch erst im nächsten Schritt Farbänderungen in der Benutzeroberfläche.
Führen Sie Ihre App aus:
flutter run -d DEVICE
Beschreiben Sie eine Farbe wie „tiefes Ozeanblau“ oder „Waldgrün“ und sehen Sie sich die Antworten an. Das LLM versucht, die oben definierten Funktionen aufzurufen, aber Ihr Code erkennt noch keine Funktionsaufrufe.
Der Prozess für Funktionsaufrufe
So funktioniert die Funktionsaufruffunktion von Gemini:
- Funktionsauswahl: Das LLM entscheidet anhand der Nutzeranfrage, ob ein Funktionsaufruf hilfreich wäre.
- Parametergenerierung: Das LLM generiert Parameterwerte, die zum Schema der Funktion passen.
- Format für Funktionsaufrufe: Das LLM sendet in seiner Antwort ein strukturiertes Funktionsaufrufobjekt.
- Anwendungsbearbeitung: Ihre App würde diesen Aufruf empfangen und die entsprechende Funktion ausführen (die im nächsten Schritt implementiert wird).
- Antwortintegration: Bei Unterhaltungen in mehreren Runden erwartet das LLM, dass das Ergebnis der Funktion zurückgegeben wird.
Im aktuellen Zustand Ihrer App werden die ersten drei Schritte ausgeführt, aber Sie haben Schritt 4 oder 5 (Verarbeiten der Funktionsaufrufe) noch nicht implementiert. Das werden Sie im nächsten Schritt tun.
Technische Details: So entscheidet Gemini, wann Funktionen verwendet werden
Gemini trifft intelligente Entscheidungen darüber, wann Funktionen verwendet werden sollen. Dabei werden folgende Faktoren berücksichtigt:
- Nutzerabsicht: Gibt an, ob die Anfrage des Nutzers am besten durch eine Funktion erfüllt werden kann.
- Funktionsrelevanz: Wie gut die verfügbaren Funktionen zur Aufgabe passen
- Verfügbarkeit von Parametern: Gibt an, ob Parameterwerte zuverlässig ermittelt werden können.
- Systemanweisungen: Anweisungen aus Ihrem System-Prompt zur Verwendung von Funktionen
Durch die Bereitstellung klarer Funktionsdeklarationen und Systemanweisungen haben Sie Gemini so eingerichtet, dass Farbbeschreibungsanfragen als Möglichkeiten zum Aufrufen der set_color
-Funktion erkannt werden.
Nächste Schritte
Im nächsten Schritt implementieren Sie Handler für die Funktionsaufrufe, die von Gemini kommen. So schließt sich der Kreis und Nutzerbeschreibungen können über die Funktionsaufrufe des LLM tatsächliche Farbänderungen in der Benutzeroberfläche auslösen.
Fehlerbehebung
Probleme mit Funktionsdeklarationen
Wenn Fehler bei Funktionsdeklarationen auftreten:
- Prüfen, ob die Parameternamen und -typen den Erwartungen entsprechen
- Prüfen, ob der Funktionsname klar und aussagekräftig ist
- Die Funktionsbeschreibung muss den Zweck der Funktion genau erläutern.
Probleme mit System-Prompts
Wenn das LLM nicht versucht, die Funktion zu verwenden:
- Prüfen Sie, ob der Systemprompt das LLM eindeutig anweist, das Tool
set_color
zu verwenden. - Prüfen Sie, ob im Beispiel im Systemprompt die Verwendung von Funktionen demonstriert wird.
- Versuchen Sie, die Anweisung zur Verwendung des Tools expliziter zu formulieren.
Allgemeine Probleme
Wenn andere Probleme auftreten:
- Konsole auf Fehler im Zusammenhang mit Funktionsdeklarationen prüfen
- Prüfen, ob die Tools richtig an das Modell übergeben werden
- Achten Sie darauf, dass der gesamte von Riverpod generierte Code auf dem neuesten Stand ist.
Wichtige gelernte Konzepte
- Funktionsdeklarationen definieren, um die LLM-Funktionen in Flutter-Apps zu erweitern
- Parameterschemas für die Erhebung strukturierter Daten erstellen
- Funktionsdeklarationen in das Gemini-Modell einbinden
- Systemaufforderungen aktualisieren, um die Funktionsnutzung zu fördern
- Auswahl und Aufruf von Funktionen durch LLMs
In diesem Schritt wird gezeigt, wie LLMs die Lücke zwischen Eingaben in natürlicher Sprache und strukturierten Funktionsaufrufen schließen können. Das ist die Grundlage für eine nahtlose Integration zwischen Konversations- und Anwendungsfunktionen.
6. Umgang mit dem Tool implementieren
In diesem Schritt implementieren Sie Handler für die Funktionsaufrufe, die von Gemini kommen. Damit wird der Kommunikationszyklus zwischen Eingaben in natürlicher Sprache und konkreten Anwendungsfunktionen geschlossen. Das LLM kann Ihre Benutzeroberfläche basierend auf Nutzerbeschreibungen direkt bearbeiten.
Lerninhalte in diesem Schritt
- Die vollständige Pipeline für Funktionsaufrufe in LLM-Anwendungen
- Funktionsaufrufe von Gemini in einer Flutter-Anwendung verarbeiten
- Funktionshandler implementieren, die den Anwendungsstatus ändern
- Funktionsantworten verarbeiten und Ergebnisse an das LLM zurückgeben
- Vollständigen Kommunikationsfluss zwischen LLM und Benutzeroberfläche erstellen
- Funktionsaufrufe und Antworten zur Transparenz protokollieren
Informationen zur Pipeline für Funktionsaufrufe
Bevor wir uns mit der Implementierung befassen, sehen wir uns die gesamte Pipeline für Funktionsaufrufe an:
End-to-End-Ablauf
- Nutzereingabe: Der Nutzer beschreibt eine Farbe in natürlicher Sprache (z.B. „waldgrün“)
- LLM-Verarbeitung: Gemini analysiert die Beschreibung und entscheidet, die Funktion
set_color
aufzurufen. - Generierung von Funktionsaufrufen: Gemini erstellt ein strukturiertes JSON mit Parametern (Rot-, Grün- und Blauwerte).
- Empfang von Funktionsaufrufen: Ihre App empfängt diese strukturierten Daten von Gemini.
- Funktionsausführung: Ihre App führt die Funktion mit den angegebenen Parametern aus.
- Statusaktualisierung: Die Funktion aktualisiert den Status Ihrer App (die angezeigte Farbe wird geändert).
- Antwortgenerierung: Ihre Funktion gibt Ergebnisse an das LLM zurück.
- Einbeziehung der Antwort: Das LLM bezieht diese Ergebnisse in seine endgültige Antwort ein.
- UI-Aktualisierung: Die Benutzeroberfläche reagiert auf die Statusänderung und zeigt die neue Farbe an.
Der vollständige Kommunikationszyklus ist für die richtige LLM-Integration unerlässlich. Wenn ein LLM einen Funktionsaufruf ausführt, wird die Anfrage nicht einfach gesendet und dann fortgefahren. Stattdessen wird gewartet, bis Ihre Anwendung die Funktion ausführt und Ergebnisse zurückgibt. Das LLM verwendet diese Ergebnisse dann, um die endgültige Antwort zu formulieren. So entsteht ein natürlicher Gesprächsfluss, in dem die ergriffenen Maßnahmen berücksichtigt werden.
Funktions-Handler implementieren
Aktualisieren wir die Datei lib/services/gemini_tools.dart
, um Handler für Funktionsaufrufe hinzuzufügen:
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);
Funktionshandler
Sehen wir uns an, was diese Funktionshandler tun:
handleFunctionCall
: Ein zentraler Dispatcher, der Folgendes bietet:- Protokolliert den Funktionsaufruf zur Transparenz im Logbereich
- Leitet Anfragen basierend auf dem Funktionsnamen an den entsprechenden Handler weiter
- Gibt eine strukturierte Antwort zurück, die an das LLM gesendet wird.
handleSetColor
: Der spezifische Handler für Ihreset_color
-Funktion, der Folgendes tut:- Extrahiert RGB-Werte aus der Argumentzuordnung.
- Konvertiert sie in die erwarteten Typen (Doubles).
- Aktualisiert den Farbstatus der Anwendung mit
colorStateNotifier
- Erstellt eine strukturierte Antwort mit dem Erfolgsstatus und den aktuellen Farbinformationen.
- Protokolliert die Funktionsergebnisse für das Debugging
handleUnknownFunction
: Ein Fallback-Handler für unbekannte Funktionen, der Folgendes ausführt:- Protokolliert eine Warnung zur nicht unterstützten Funktion
- Gibt eine Fehlerantwort an das LLM zurück
Die Funktion handleSetColor
ist besonders wichtig, da sie die Lücke zwischen dem Natural Language Understanding des LLM und konkreten Änderungen an der Benutzeroberfläche schließt.
Gemini Chat-Dienst aktualisieren, um Funktionsaufrufe und Antworten zu verarbeiten
Aktualisieren Sie nun die Datei lib/services/gemini_chat_service.dart
, um Funktionsaufrufe aus den LLM-Antworten zu verarbeiten und die Ergebnisse an das LLM zurückzusenden:
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);
Kommunikationsablauf
Die wichtigste Neuerung ist die vollständige Verarbeitung von Funktionsaufrufen und ‑antworten:
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);
}
}
Dieser Code:
- Prüft, ob die LLM-Antwort Funktionsaufrufe enthält
- Bei jedem Funktionsaufruf wird die Methode
handleFunctionCall
mit dem Funktionsnamen und den Argumenten aufgerufen. - Sammelt die Ergebnisse jedes Funktionsaufrufs
- Diese Ergebnisse werden mit
Content.functionResponses
an das LLM zurückgesendet. - Verarbeitet die Antwort des LLM auf die Funktionsergebnisse
- Aktualisiert die Benutzeroberfläche mit dem endgültigen Antworttext
Dadurch wird ein Roundtrip-Ablauf erstellt:
- Nutzer → LLM: Fordert eine Farbe an
- LLM → App: Funktionsaufrufe mit Parametern
- App → Nutzer: Neue Farbe wird angezeigt
- App → LLM: Funktionsergebnisse
- LLM → Nutzer: Endgültige Antwort mit Funktionsergebnissen
Riverpod-Code generieren
Führen Sie den Build-Runner-Befehl aus, um den erforderlichen Riverpod-Code zu generieren:
dart run build_runner build --delete-conflicting-outputs
Vollständigen Ablauf ausführen und testen
Führen Sie nun Ihre Anwendung aus:
flutter run -d DEVICE
Geben Sie verschiedene Farbbeschreibungen ein:
- „Ich möchte ein tiefes Karmesinrot.“
- „Zeig mir ein beruhigendes Himmelblau.“
- „Gib mir die Farbe frischer Minzblätter.“
- „Ich möchte einen warmen Sonnenuntergang in Orange sehen.“
- „Make it a rich royal purple“ (Mach es zu einem satten, königlichen Lila)
Jetzt sollten Sie Folgendes sehen:
- Ihre Nachricht wird in der Chatoberfläche angezeigt
- Die Antwort von Gemini wird im Chat angezeigt
- Funktionsaufrufe werden im Logfeld protokolliert
- Funktionsergebnisse werden unmittelbar danach protokolliert.
- Das Farbrechteck wird aktualisiert und zeigt die beschriebene Farbe an.
- Die RGB-Werte werden aktualisiert und zeigen die Komponenten der neuen Farbe an.
- Die endgültige Antwort von Gemini wird angezeigt, oft mit einem Kommentar zur festgelegten Farbe.
Das Log-Steuerfeld bietet Einblicke in die Vorgänge im Hintergrund. Sie sehen hier Folgendes:
- Die genauen Funktionsaufrufe, die Gemini ausführt
- Die Parameter, die für jeden RGB-Wert ausgewählt werden
- Die Ergebnisse, die Ihre Funktion zurückgibt
- Die Folgeantworten von Gemini
Benachrichtigung zum Farbstatus
Die colorStateNotifier
, die Sie zum Aktualisieren von Farben verwenden, ist Teil des colorist_ui
-Pakets. Folgendes wird verwaltet:
- Die aktuelle Farbe, die in der Benutzeroberfläche angezeigt wird
- Der Farbverlauf (letzte 10 Farben)
- Benachrichtigung über Statusänderungen von UI-Komponenten
Wenn Sie updateColor
mit neuen RGB-Werten aufrufen, passiert Folgendes:
- Erstellt ein neues
ColorData
-Objekt mit den angegebenen Werten. - Aktualisiert die aktuelle Farbe im App-Status
- Fügt die Farbe dem Verlauf hinzu
- Löst UI-Updates über die Statusverwaltung von Riverpod aus
Die UI-Komponenten im colorist_ui
-Paket beobachten diesen Status und werden automatisch aktualisiert, wenn er sich ändert. So entsteht eine reaktive Benutzeroberfläche.
Fehlerbehandlung
Ihre Implementierung umfasst eine robuste Fehlerbehandlung:
- Try-Catch-Block: Umschließt alle LLM-Interaktionen, um Ausnahmen abzufangen.
- Fehlerprotokollierung: Fehler werden mit Stacktraces im Log-Bereich aufgezeichnet.
- Nutzerfeedback: Gibt eine freundliche Fehlermeldung im Chat aus.
- Statusbereinigung: Der Nachrichtenstatus wird auch bei einem Fehler abgeschlossen.
So bleibt die App stabil und gibt auch dann angemessenes Feedback, wenn Probleme mit dem LLM-Dienst oder der Funktionsausführung auftreten.
Funktionsaufrufe für eine bessere Nutzererfahrung
Sie haben hier gesehen, wie leistungsstarke natürliche Schnittstellen mit LLMs erstellt werden können:
- Schnittstelle für natürliche Sprache: Nutzer drücken ihre Absicht in Alltagssprache aus.
- Intelligente Interpretation: Das LLM übersetzt vage Beschreibungen in genaue Werte.
- Direkte Bearbeitung: Die Benutzeroberfläche wird als Reaktion auf natürliche Sprache aktualisiert.
- Kontextbezogene Antworten: Das LLM liefert Kontextinformationen zu den Änderungen.
- Geringe kognitive Belastung: Nutzer müssen keine RGB-Werte oder Farbtheorie verstehen.
Dieses Muster, bei dem LLM-Funktionsaufrufe verwendet werden, um natürliche Sprache und UI-Aktionen zu verbinden, kann auf unzählige andere Bereiche als die Farbauswahl ausgeweitet werden.
Nächste Schritte
Im nächsten Schritt verbessern Sie die Nutzerfreundlichkeit, indem Sie Streaming-Antworten implementieren. Anstatt auf die vollständige Antwort zu warten, verarbeiten Sie Textblöcke und Funktionsaufrufe, sobald sie eingehen. So entsteht eine reaktionsschnellere und ansprechendere Anwendung.
Fehlerbehebung
Probleme mit Funktionsaufrufen
Wenn Gemini Ihre Funktionen nicht aufruft oder Parameter falsch sind, gehen Sie so vor:
- Prüfen Sie, ob Ihre Funktionsdeklaration der Beschreibung im Systemprompt entspricht.
- Prüfen, ob Parameternamen und -typen übereinstimmen
- Achten Sie darauf, dass der Systemprompt das LLM explizit anweist, das Tool zu verwenden.
- Prüfen Sie, ob der Funktionsname in Ihrem Handler genau mit dem Namen in der Deklaration übereinstimmt.
- Logbereich auf detaillierte Informationen zu Funktionsaufrufen prüfen
Probleme mit Funktionsantworten
Wenn Funktionsergebnisse nicht richtig an das LLM zurückgesendet werden:
- Prüfen Sie, ob Ihre Funktion eine korrekt formatierte Map zurückgibt.
- Prüfen, ob „Content.functionResponses“ korrekt erstellt wird
- Suchen Sie im Log nach Fehlern im Zusammenhang mit Funktionsantworten.
- Achten Sie darauf, dass Sie für die Antwort dieselbe Chatsitzung verwenden.
Probleme mit der Farbdarstellung
Wenn Farben nicht richtig angezeigt werden:
- Achten Sie darauf, dass RGB-Werte richtig in Gleitkommazahlen konvertiert werden (das LLM sendet sie möglicherweise als Ganzzahlen).
- Prüfen Sie, ob die Werte im erwarteten Bereich (0,0 bis 1,0) liegen.
- Prüfen, ob die Benachrichtigung über den Farbstatus korrekt aufgerufen wird
- Prüfen Sie das Log auf die genauen Werte, die an die Funktion übergeben werden.
Allgemeine Probleme
Bei allgemeinen Problemen:
- Protokolle auf Fehler oder Warnungen prüfen
- Firebase AI Logic-Verbindung prüfen
- Prüfen Sie, ob es Typkonflikte bei Funktionsparametern gibt.
- Achten Sie darauf, dass der gesamte von Riverpod generierte Code auf dem neuesten Stand ist.
Wichtige gelernte Konzepte
- Vollständige Pipeline für Funktionsaufrufe in Flutter implementieren
- Vollständige Kommunikation zwischen einem LLM und Ihrer Anwendung herstellen
- Verarbeiten strukturierter Daten aus LLM-Antworten
- Funktionsergebnisse an das LLM zurücksenden, damit sie in Antworten einbezogen werden können
- Logbereich verwenden, um Einblick in die Interaktionen zwischen LLM und Anwendung zu erhalten
- Verknüpfen von Eingaben in natürlicher Sprache mit konkreten Änderungen an der Benutzeroberfläche
Nachdem Sie diesen Schritt abgeschlossen haben, demonstriert Ihre App eines der leistungsstärksten Muster für die LLM-Integration: die Übersetzung von Eingaben in natürlicher Sprache in konkrete UI-Aktionen bei gleichzeitiger Aufrechterhaltung einer kohärenten Konversation, in der diese Aktionen berücksichtigt werden. So entsteht eine intuitive, dialogorientierte Oberfläche, die für Nutzer wie Magie wirkt.
7. Streamingantworten für eine bessere Nutzerfreundlichkeit
In diesem Schritt verbessern Sie die Nutzerfreundlichkeit, indem Sie Streaming-Antworten von Gemini implementieren. Anstatt darauf zu warten, dass die gesamte Antwort generiert wird, verarbeiten Sie Textblöcke und Funktionsaufrufe, sobald sie empfangen werden. So entsteht eine reaktionsschnellere und ansprechendere Anwendung.
Inhalte dieses Schritts
- Die Bedeutung von Streaming für LLM-basierte Anwendungen
- Streaming von LLM-Antworten in einer Flutter-Anwendung implementieren
- Verarbeiten von Textausschnitten, sobald sie von der API empfangen werden
- Unterhaltungsstatus verwalten, um Nachrichtenkonflikte zu vermeiden
- Funktionsaufrufe in Streaming-Antworten verarbeiten
- Visuelle Hinweise für laufende Antworten erstellen
Warum Streaming für LLM-Anwendungen wichtig ist
Bevor wir mit der Implementierung beginnen, wollen wir uns ansehen, warum das Streamen von Antworten so wichtig ist, um mit LLMs eine hervorragende User Experience zu schaffen:
Verbesserte Nutzerfreundlichkeit
Das Streamen von Antworten bietet mehrere wichtige Vorteile für die Nutzerfreundlichkeit:
- Geringere wahrgenommene Latenz: Nutzer sehen Text sofort (in der Regel innerhalb von 100 bis 300 ms), anstatt mehrere Sekunden auf eine vollständige Antwort zu warten. Diese Wahrnehmung der Unmittelbarkeit verbessert die Nutzerzufriedenheit erheblich.
- Natürlicher Gesprächsrhythmus: Der Text wird nach und nach eingeblendet, was der menschlichen Kommunikation nachempfunden ist und für einen natürlicheren Dialog sorgt.
- Progressive Informationsverarbeitung: Nutzer können mit der Verarbeitung von Informationen beginnen, sobald sie eintreffen, anstatt von einem großen Textblock auf einmal überfordert zu werden.
- Möglichkeit für frühe Unterbrechung: In einer vollständigen Anwendung können Nutzer die LLM möglicherweise unterbrechen oder umleiten, wenn sie feststellen, dass sie in eine nicht hilfreiche Richtung geht.
- Visuelle Bestätigung der Aktivität: Der Streaming-Text gibt sofortiges Feedback, dass das System funktioniert, wodurch die Unsicherheit verringert wird.
Technische Vorteile
Neben UX-Verbesserungen bietet Streaming auch technische Vorteile:
- Frühe Funktionsausführung: Funktionsaufrufe können erkannt und ausgeführt werden, sobald sie im Stream erscheinen, ohne auf die vollständige Antwort zu warten.
- Inkrementelle UI-Updates: Sie können die Benutzeroberfläche nach und nach aktualisieren, wenn neue Informationen eingehen, um eine dynamischere Nutzererfahrung zu schaffen.
- Verwaltung des Unterhaltungsstatus: Durch das Streaming werden klare Signale dafür gesendet, wann Antworten abgeschlossen sind und wann sie noch laufen. So lässt sich der Status besser verwalten.
- Geringeres Risiko von Zeitüberschreitungen: Bei Nicht-Streaming-Antworten besteht bei lang andauernden Generierungen das Risiko von Verbindungszeitüberschreitungen. Beim Streaming wird die Verbindung frühzeitig hergestellt und aufrechterhalten.
Wenn Sie Streaming in Ihre Colorist-App implementieren, sehen Nutzer sowohl Textantworten als auch Farbänderungen schneller, was die Reaktionsfähigkeit deutlich verbessert.
Verwaltung des Unterhaltungsstatus hinzufügen
Fügen wir zuerst einen Statusanbieter hinzu, um zu verfolgen, ob die App derzeit eine Streaming-Antwort verarbeitet. Aktualisieren Sie Ihre lib/services/gemini_chat_service.dart
-Datei:
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);
Streaming-Implementierung
Sehen wir uns an, was dieser Code bewirkt:
- Tracking des Konversationsstatus:
- Eine
conversationStateProvider
gibt an, ob die App derzeit eine Antwort verarbeitet. - Der Status wechselt während der Verarbeitung von
idle
→busy
und dann zurück zuidle
. - So wird verhindert, dass mehrere gleichzeitige Anfragen zu Konflikten führen.
- Eine
- Stream-Initialisierung:
sendMessageStream()
gibt einen Stream von Antwort-Chunks anstelle einesFuture
mit der vollständigen Antwort zurück.- Jeder Chunk kann Text, Funktionsaufrufe oder beides enthalten.
- Progressive Verarbeitung:
await for
verarbeitet jeden Chunk in Echtzeit, sobald er eintrifft.- Text wird sofort an die Benutzeroberfläche angehängt, wodurch der Streaming-Effekt entsteht.
- Funktionsaufrufe werden ausgeführt, sobald sie erkannt werden.
- Verarbeitung von Funktionsaufrufen:
- Wenn in einem Chunk ein Funktionsaufruf erkannt wird, wird er sofort ausgeführt.
- Die Ergebnisse werden über einen weiteren Streaming-Aufruf an das LLM zurückgesendet.
- Die Reaktion des LLM auf diese Ergebnisse wird ebenfalls im Streaming-Modus verarbeitet.
- Fehlerbehandlung und Bereinigung:
try
/catch
bietet eine robuste Fehlerbehandlung- Der
finally
-Block sorgt dafür, dass der Unterhaltungsstatus richtig zurückgesetzt wird. - Die Nachricht wird immer fertiggestellt, auch wenn Fehler auftreten.
Diese Implementierung sorgt für ein reaktionsschnelles und zuverlässiges Streaming und erhält gleichzeitig den richtigen Konversationsstatus bei.
Hauptbildschirm aktualisieren, um den Unterhaltungsstatus zu verbinden
Ändern Sie die Datei lib/main.dart
, um den Unterhaltungsstatus an den Hauptbildschirm zu übergeben:
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),
),
);
}
}
Die wichtigste Änderung besteht darin, dass conversationState
an das MainScreen
-Widget übergeben wird. Der MainScreen
(bereitgestellt vom Paket colorist_ui
) verwendet diesen Status, um die Texteingabe zu deaktivieren, während eine Antwort verarbeitet wird.
So wird eine einheitliche Nutzererfahrung geschaffen, bei der die Benutzeroberfläche den aktuellen Status der Unterhaltung widerspiegelt.
Riverpod-Code generieren
Führen Sie den Build-Runner-Befehl aus, um den erforderlichen Riverpod-Code zu generieren:
dart run build_runner build --delete-conflicting-outputs
Streaming-Antworten ausführen und testen
Führen Sie Ihre Anwendung aus:
flutter run -d DEVICE
Testen Sie nun das Streamingverhalten mit verschiedenen Farbbeschreibungen. Versuchen Sie es mit Beschreibungen wie:
- „Show me the deep teal color of the ocean at twilight“ (Zeig mir die tiefblaue Farbe des Ozeans in der Dämmerung)
- „Ich möchte ein leuchtendes Korallenriff sehen, das mich an tropische Blumen erinnert.“
- „Erstelle ein gedämpftes Olivgrün wie bei alten Armeeuniformen.“
Der technische Ablauf des Streamings im Detail
Sehen wir uns genau an, was beim Streamen einer Antwort passiert:
Verbindung herstellen
Wenn Sie sendMessageStream()
aufrufen, geschieht Folgendes:
- Die App stellt eine Verbindung zum Firebase AI Logic-Dienst her.
- Die Nutzeranfrage wird an den Dienst gesendet.
- Der Server beginnt mit der Verarbeitung der Anfrage.
- Die Streamverbindung bleibt offen und ist bereit, Chunks zu übertragen.
Chunk-Übertragung
Während Gemini Inhalte generiert, werden Chunks über den Stream gesendet:
- Der Server sendet Textblöcke, sobald sie generiert werden (in der Regel einige Wörter oder Sätze).
- Wenn Gemini sich für einen Funktionsaufruf entscheidet, werden die Informationen zum Funktionsaufruf gesendet.
- Auf Funktionsaufrufe können weitere Textblöcke folgen
- Der Stream wird fortgesetzt, bis die Generierung abgeschlossen ist.
Progressive Verarbeitung
Ihre App verarbeitet jeden Chunk inkrementell:
- Jeder Textblock wird an die vorhandene Antwort angehängt.
- Funktionsaufrufe werden ausgeführt, sobald sie erkannt werden.
- Die Benutzeroberfläche wird in Echtzeit mit Text- und Funktionsergebnissen aktualisiert.
- Der Status wird verfolgt, um anzuzeigen, dass die Antwort noch gestreamt wird.
Stream abgeschlossen
Wenn die Generierung abgeschlossen ist:
- Der Stream wird vom Server geschlossen.
- Die
await for
-Schleife wird auf natürliche Weise beendet - Die Nachricht ist als erledigt markiert
- Der Unterhaltungsstatus wird auf „Inaktiv“ zurückgesetzt.
- Die Benutzeroberfläche wird aktualisiert und zeigt den Status „Abgeschlossen“ an.
Vergleich von Streaming- und Nicht-Streaming-Antworten
Um die Vorteile des Streamings besser zu verstehen, vergleichen wir Streaming- und Nicht-Streaming-Ansätze:
Aspekt | Nicht-Streaming | Streaming |
Wahrgenommene Latenz | Der Nutzer sieht nichts, bis die vollständige Antwort bereit ist | Nutzer sehen die ersten Wörter innerhalb von Millisekunden |
Auswirkungen für Nutzer | Lange Wartezeit, gefolgt von plötzlichem Erscheinen des Texts | Natürliches, progressives Einblenden von Text |
Statusverwaltung | Einfacher (Nachrichten sind entweder ausstehend oder abgeschlossen) | Komplexer (Nachrichten können im Streaming-Status sein) |
Funktionsausführung | Tritt erst nach einer vollständigen Antwort auf | Tritt während der Antwortgenerierung auf |
Komplexität der Implementierung | Einfachere Implementierung | Erfordert zusätzliche Statusverwaltung |
Wiederherstellung nach Fehlern | Alles-oder-Nichts-Reaktion | Teilweise Antworten können trotzdem nützlich sein |
Komplexität des Codes | Weniger komplex | Komplexer aufgrund der Streamverarbeitung |
Bei einer Anwendung wie Colorist überwiegen die UX-Vorteile des Streamings die Implementierungskomplexität, insbesondere bei Farbinformationen, deren Generierung mehrere Sekunden dauern kann.
Best Practices für die Streaming-Benutzerfreundlichkeit
Beachten Sie beim Implementieren von Streaming in Ihren eigenen LLM-Anwendungen die folgenden Best Practices:
- Deutliche visuelle Hinweise: Sorgen Sie immer für deutliche visuelle Hinweise, die zwischen Streaming- und vollständigen Nachrichten unterscheiden.
- Eingabe blockieren: Deaktivieren Sie die Nutzereingabe während des Streamings, um mehrere sich überschneidende Anfragen zu verhindern.
- Fehlerbehebung: Gestalte deine Benutzeroberfläche so, dass sie eine reibungslose Wiederherstellung ermöglicht, wenn das Streaming unterbrochen wird.
- Statusübergänge: Sorge für reibungslose Übergänge zwischen den Status „Leerlauf“, „Streaming“ und „Abgeschlossen“.
- Visualisierung des Fortschritts: Verwenden Sie subtile Animationen oder Anzeigen, um zu zeigen, dass die Verarbeitung aktiv ist.
- Optionen zum Abbrechen: In einer vollständigen App sollten Nutzer laufende Generierungen abbrechen können.
- Integration von Funktionsergebnissen: Gestalten Sie die Benutzeroberfläche so, dass Funktionsergebnisse während der Ausführung angezeigt werden können.
- Leistungsoptimierung: Minimieren Sie das Neuerstellen der Benutzeroberfläche bei schnellen Stream-Updates.
Das colorist_ui
-Paket implementiert viele dieser Best Practices für Sie. Sie sind jedoch wichtige Überlegungen für jede Streaming-LLM-Implementierung.
Nächste Schritte
Im nächsten Schritt implementieren Sie die LLM-Synchronisierung, indem Sie Gemini benachrichtigen, wenn Nutzer Farben aus dem Verlauf auswählen. So wird eine kohärentere Nutzererfahrung geschaffen, bei der das LLM über vom Nutzer initiierte Änderungen am Anwendungsstatus informiert ist.
Fehlerbehebung
Probleme bei der Streamverarbeitung
Wenn Probleme bei der Streamverarbeitung auftreten:
- Symptome: Teilantworten, fehlender Text oder abruptes Beenden des Streams
- Lösung: Netzwerkverbindung prüfen und für korrekte async/await-Muster im Code sorgen
- Diagnose: Prüfen Sie das Log-Feld auf Fehlermeldungen oder Warnungen im Zusammenhang mit der Streamverarbeitung.
- Beheben: Achten Sie darauf, dass bei der gesamten Streamverarbeitung eine ordnungsgemäße Fehlerbehandlung mit
try
-/catch
-Blöcken verwendet wird.
Fehlende Funktionsaufrufe
Wenn Funktionsaufrufe im Stream nicht erkannt werden:
- Symptome: Text wird angezeigt, aber die Farben werden nicht aktualisiert, oder im Log sind keine Funktionsaufrufe zu sehen.
- Lösung: Überprüfen Sie die Anweisungen im System-Prompt zur Verwendung von Funktionsaufrufen.
- Diagnose: Prüfen Sie im Logbereich, ob Funktionsaufrufe empfangen werden.
- Lösung: Passen Sie den System-Prompt an, um das LLM expliziter anzuweisen, das Tool
set_color
zu verwenden.
Allgemeine Fehlerbehandlung
Bei allen anderen Problemen:
- Schritt 1: Im Logbereich nach Fehlermeldungen suchen
- Schritt 2: Verbindung zu Firebase AI Logic überprüfen
- Schritt 3: Sicherstellen, dass der gesamte von Riverpod generierte Code auf dem neuesten Stand ist
- Schritt 4: Streaming-Implementierung auf fehlende „await“-Anweisungen prüfen
Wichtige gelernte Konzepte
- Streaming-Antworten mit der Gemini API für eine reaktionsschnellere Benutzeroberfläche implementieren
- Unterhaltungsstatus verwalten, um Streaming-Interaktionen richtig zu verarbeiten
- Echtzeittext und Funktionsaufrufe werden verarbeitet, sobald sie eingehen.
- Reaktionsfähige UIs erstellen, die während des Streamings inkrementell aktualisiert werden
- Gleichzeitige Streams mit geeigneten asynchronen Mustern verarbeiten
- Angemessenes visuelles Feedback während des Streamings von Antworten geben
Durch die Implementierung von Streaming haben Sie die Nutzerfreundlichkeit Ihrer Colorist-App erheblich verbessert und eine reaktionsschnellere, ansprechendere Oberfläche geschaffen, die sich wirklich dialogorientiert anfühlt.
8. LLM-Kontextsynchronisierung
In diesem Bonusschritt implementieren Sie die LLM-Kontextsynchronisierung, indem Sie Gemini benachrichtigen, wenn Nutzer Farben aus dem Verlauf auswählen. So wird eine kohärentere Erfahrung geschaffen, bei der das LLM nicht nur die expliziten Nachrichten, sondern auch die Nutzeraktionen in der Benutzeroberfläche kennt.
Inhalte dieses Schritts
- LLM-Kontextsynchronisierung zwischen Ihrer Benutzeroberfläche und dem LLM erstellen
- UI-Ereignisse in einen Kontext serialisieren, den das LLM verstehen kann
- Konversationskontext auf Grundlage von Nutzeraktionen aktualisieren
- Einheitliche Nutzererfahrung bei verschiedenen Interaktionsmethoden
- LLM-Kontextbewusstsein über explizite Chatnachrichten hinaus verbessern
LLM-Kontextsynchronisierung
Herkömmliche Chatbots reagieren nur auf explizite Nutzernachrichten. Das kann zu Problemen führen, wenn Nutzer auf andere Weise mit der App interagieren. Durch die LLM-Kontextsynchronisierung wird diese Einschränkung behoben:
Warum die Synchronisierung des LLM-Kontexts wichtig ist
Wenn Nutzer über UI-Elemente mit Ihrer App interagieren, z. B. eine Farbe aus dem Verlauf auswählen, kann das LLM nicht wissen, was passiert ist, es sei denn, Sie teilen es ihm explizit mit. LLM-Kontextsynchronisierung:
- Kontext beibehalten: Das LLM wird über alle relevanten Nutzeraktionen auf dem Laufenden gehalten.
- Schafft Kohärenz: Das LLM reagiert auf UI-Interaktionen und sorgt so für ein stimmiges Nutzererlebnis.
- Verbessert die Intelligenz: Das LLM kann angemessen auf alle Nutzeraktionen reagieren.
- Verbesserte Nutzerfreundlichkeit: Die gesamte Anwendung wirkt integrierter und reaktionsschneller.
- Weniger Aufwand für Nutzer: Nutzer müssen ihre UI-Aktionen nicht manuell erläutern.
Wenn ein Nutzer in Ihrer Colorist-App eine Farbe aus dem Verlauf auswählt, soll Gemini diese Aktion bestätigen und intelligent zur ausgewählten Farbe kommentieren, um den Eindruck eines nahtlosen, aufmerksamen Assistenten zu erwecken.
Gemini Chat-Dienst für Benachrichtigungen zur Farbauswahl aktualisieren
Zuerst fügen Sie der GeminiChatService
eine Methode hinzu, um das LLM zu benachrichtigen, wenn ein Nutzer eine Farbe aus dem Verlauf auswählt. Aktualisieren Sie Ihre lib/services/gemini_chat_service.dart
-Datei:
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);
Die wichtigste Ergänzung ist die Methode notifyColorSelection
, die folgende Funktionen bietet:
- Akzeptiert ein
ColorData
-Objekt, das die ausgewählte Farbe darstellt. - Codiert sie in ein JSON-Format, das in eine Nachricht eingefügt werden kann.
- Sendet eine speziell formatierte Nachricht an das LLM, die eine Nutzerauswahl angibt.
- Die vorhandene Methode
sendMessage
wird zur Verarbeitung der Benachrichtigung wiederverwendet.
So wird eine doppelte Verarbeitung vermieden, da Ihre vorhandene Infrastruktur zur Nachrichtenverarbeitung genutzt wird.
Haupt-App aktualisieren, um Benachrichtigungen zur Farbauswahl zu erhalten
Ändern Sie nun die Datei lib/main.dart
, um die Benachrichtigungsfunktion für die Farbauswahl an den Hauptbildschirm zu übergeben:
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),
),
);
}
}
Die wichtigste Änderung ist das Hinzufügen des notifyColorSelection
-Callbacks, der das UI-Ereignis (Auswählen einer Farbe aus dem Verlauf) mit dem LLM-Benachrichtigungssystem verbindet.
Systemaufforderung aktualisieren
Jetzt müssen Sie den System-Prompt aktualisieren, um das LLM anzuweisen, wie es auf Benachrichtigungen zur Farbauswahl reagieren soll. Ändern Sie die Datei 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
Die wichtigste Ergänzung ist der Abschnitt „Wenn Nutzer bisherige Farben auswählen“, in dem Folgendes beschrieben wird:
- Das Konzept der Benachrichtigungen zur Auswahl des Verlaufs für das LLM erläutern
- Beispiel für diese Benachrichtigungen
- Beispiel für eine angemessene Antwort
- Erwartungen an die Bestätigung der Auswahl und die Kommentierung der Farbe
So kann das LLM angemessen auf diese speziellen Nachrichten reagieren.
Riverpod-Code generieren
Führen Sie den Build-Runner-Befehl aus, um den erforderlichen Riverpod-Code zu generieren:
dart run build_runner build --delete-conflicting-outputs
LLM-Kontextsynchronisierung ausführen und testen
Führen Sie Ihre Anwendung aus:
flutter run -d DEVICE
Das Testen der LLM-Kontextsynchronisierung umfasst Folgendes:
- Generieren Sie zuerst einige Farben, indem Sie sie im Chat beschreiben.
- „Show me a vibrant purple“ (Zeig mir ein kräftiges Lila)
- „Ich möchte ein Waldgrün.“
- „Gib mir ein helles Rot“
- Klicken Sie dann auf eines der Farbmineaturen im Verlaufsstreifen.
Beachten Sie Folgendes:
- Die ausgewählte Farbe wird auf dem Hauptdisplay angezeigt.
- Im Chat wird eine Nutzernachricht mit der Farbauswahl angezeigt.
- Das LLM reagiert, indem es die Auswahl bestätigt und die Farbe kommentiert.
- Die gesamte Interaktion fühlt sich natürlich und zusammenhängend an.
So entsteht eine nahtlose Erfahrung, bei der das LLM sowohl auf Direktnachrichten als auch auf UI-Interaktionen reagiert.
So funktioniert die Synchronisierung des LLM-Kontexts
Sehen wir uns die technischen Details der Synchronisierung an:
Datenfluss
- Nutzeraktion: Der Nutzer klickt im Verlaufsstreifen auf eine Farbe.
- UI-Ereignis: Das
MainScreen
-Widget erkennt diese Auswahl. - Callback-Ausführung: Der
notifyColorSelection
-Callback wird ausgelöst. - Nachrichtenerstellung: Eine speziell formatierte Nachricht wird mit den Farbdaten erstellt.
- LLM-Verarbeitung: Die Nachricht wird an Gemini gesendet, das das Format erkennt.
- Kontextbezogene Antwort: Gemini antwortet angemessen basierend auf dem System-Prompt.
- Aktualisierung der Benutzeroberfläche: Die Antwort wird im Chat angezeigt, was für eine einheitliche Nutzererfahrung sorgt.
Datenserialisierung
Ein wichtiger Aspekt dieses Ansatzes ist, wie Sie die Farbdaten serialisieren:
'User selected color from history: ${json.encode(color.toLLMContextMap())}'
Die Methode toLLMContextMap()
(bereitgestellt vom Paket colorist_ui
) konvertiert ein ColorData
-Objekt in eine Map mit Schlüsseleigenschaften, die das LLM verstehen kann. Dazu gehören in der Regel:
- RGB-Werte (Rot, Grün, Blau)
- Hexadezimale Darstellung
- Name oder Beschreibung, die mit der Farbe verknüpft ist
Wenn Sie diese Daten einheitlich formatieren und in die Nachricht einfügen, stellen Sie sicher, dass das LLM alle Informationen hat, die es für eine angemessene Antwort benötigt.
Breitere Anwendung der LLM-Kontextsynchronisierung
Dieses Muster, das LLM über UI-Ereignisse zu benachrichtigen, hat zahlreiche Anwendungen, die über die Farbauswahl hinausgehen:
Andere Anwendungsfälle
- Änderungen filtern: Die LLM benachrichtigen, wenn Nutzer Filter auf Daten anwenden
- Navigationsereignisse: Informieren das LLM, wenn Nutzer zu verschiedenen Bereichen navigieren.
- Auswahländerungen: Aktualisieren Sie das LLM, wenn Nutzer Elemente aus Listen oder Rastern auswählen.
- Aktualisierungen der Einstellungen: LLM informieren, wenn Nutzer Einstellungen ändern
- Datenbearbeitung: Das LLM benachrichtigen, wenn Nutzer Daten hinzufügen, bearbeiten oder löschen
In jedem Fall bleibt das Muster gleich:
- UI-Ereignis erkennen
- Relevante Daten serialisieren
- Eine speziell formatierte Benachrichtigung an das LLM senden
- LLM durch den Systemprompt zu einer angemessenen Reaktion anleiten
Best Practices für die LLM-Kontextsynchronisierung
Hier sind einige Best Practices für eine effektive LLM-Kontextsynchronisierung, die auf Ihrer Implementierung basieren:
1. Einheitliches Format
Verwenden Sie ein einheitliches Format für Benachrichtigungen, damit das LLM sie leicht erkennen kann:
"User [action] [object]: [structured data]"
2. Umfangreicher Kontext
Benachrichtigungen müssen genügend Details enthalten, damit das LLM intelligent reagieren kann. Bei Farben sind das RGB-Werte, Hexadezimalcodes und alle anderen relevanten Eigenschaften.
3. Klare Anweisungen
Geben Sie im System-Prompt explizite Anweisungen dazu, wie Benachrichtigungen behandelt werden sollen, idealerweise mit Beispielen.
4. Natürliche Integration
Benachrichtigungen sollten sich natürlich in die Unterhaltung einfügen und nicht als technische Unterbrechungen wahrgenommen werden.
5. Selektive Benachrichtigung
Benachrichtigen Sie das LLM nur über Aktionen, die für die Unterhaltung relevant sind. Nicht jedes UI-Ereignis muss kommuniziert werden.
Fehlerbehebung
Probleme mit Benachrichtigungen
Wenn das LLM nicht richtig auf Farbauswahlen reagiert:
- Prüfen, ob das Format der Benachrichtigungsmeldung dem entspricht, was im Systemprompt beschrieben wird
- Prüfen, ob die Farbdaten richtig serialisiert werden
- Sorgen Sie dafür, dass die Systemanweisung klare Anweisungen für die Verarbeitung von Auswahlen enthält.
- Suchen Sie im Chatdienst nach Fehlern beim Senden von Benachrichtigungen.
Kontextverwaltung
Wenn das LLM den Kontext zu verlieren scheint:
- Prüfen, ob die Chatsitzung ordnungsgemäß aufrechterhalten wird
- Prüfen, ob die Konversationsstatus korrekt übergehen
- Benachrichtigungen müssen über dieselbe Chatsitzung gesendet werden.
Allgemeine Probleme
Bei allgemeinen Problemen:
- Protokolle auf Fehler oder Warnungen prüfen
- Firebase AI Logic-Verbindung prüfen
- Prüfen Sie, ob es Typkonflikte bei Funktionsparametern gibt.
- Achten Sie darauf, dass der gesamte von Riverpod generierte Code auf dem neuesten Stand ist.
Wichtige gelernte Konzepte
- LLM-Kontextsynchronisierung zwischen UI und LLM erstellen
- UI-Ereignisse in LLM-freundlichen Kontext serialisieren
- LLM-Verhalten für verschiedene Interaktionsmuster steuern
- Einheitliche Nutzererfahrung bei Interaktionen mit und ohne Nachrichten
- LLM-Bewusstsein für den allgemeinen Anwendungsstatus verbessern
Durch die Implementierung der LLM-Kontextsynchronisierung haben Sie eine wirklich integrierte Lösung geschaffen, bei der sich das LLM wie ein aufmerksamer, reaktionsschneller Assistent und nicht nur wie ein Textgenerator anfühlt. Dieses Muster kann auf unzählige andere Anwendungen angewendet werden, um natürlichere, intuitivere KI-basierte Benutzeroberflächen zu schaffen.
9. Glückwunsch!
Sie haben das Colorist-Codelab erfolgreich abgeschlossen. 🎉
Was Sie erstellt haben
Sie haben eine voll funktionsfähige Flutter-Anwendung erstellt, in die die Gemini API von Google integriert ist, um Farbbeschreibungen in natürlicher Sprache zu interpretieren. Ihre App kann jetzt:
- Verarbeiten von Beschreibungen in natürlicher Sprache wie „Sonnenuntergangsorange“ oder „Tiefseeblau“
- Gemini verwenden, um diese Beschreibungen intelligent in RGB-Werte zu übersetzen
- Interpretierte Farben in Echtzeit mit Streaming-Antworten anzeigen
- Nutzerinteraktionen über Chat und UI-Elemente verarbeiten
- Kontextbezug bei verschiedenen Interaktionsmethoden beibehalten
So geht es weiter
Nachdem Sie nun die Grundlagen der Integration von Gemini in Flutter beherrschen, haben Sie folgende Möglichkeiten, um weiterzumachen:
Colorist-App optimieren
- Farbpaletten: Funktion zum Generieren von komplementären oder passenden Farbschemas hinzufügen
- Spracheingabe: Spracherkennung für verbale Farbbeschreibungen einbinden
- Verlauf verwalten: Optionen zum Benennen, Organisieren und Exportieren von Farbsets hinzugefügt
- Benutzerdefinierte Prompts: Erstellen Sie eine Schnittstelle, über die Nutzer System-Prompts anpassen können.
- Erweiterte Analysen: Sie können nachvollziehen, welche Beschreibungen am besten funktionieren oder Schwierigkeiten verursachen.
Weitere Gemini-Funktionen
- Multimodale Eingaben: Mit Bildeingaben können Sie Farben aus Fotos extrahieren.
- Inhaltsgenerierung: Mit Gemini farbbezogene Inhalte wie Beschreibungen oder Geschichten generieren
- Verbesserungen bei Funktionsaufrufen: Komplexere Tool-Integrationen mit mehreren Funktionen erstellen
- Sicherheitseinstellungen: Hier können Sie verschiedene Sicherheitseinstellungen und ihre Auswirkungen auf Antworten ausprobieren.
Diese Muster auf andere Domains anwenden
- Dokumentanalyse: Apps erstellen, die Dokumente verstehen und analysieren können
- Unterstützung beim kreativen Schreiben: Schreibtools mit LLM-gestützten Vorschlägen erstellen
- Aufgabenautomatisierung: Entwickeln Sie Apps, die natürliche Sprache in automatisierte Aufgaben umwandeln.
- Wissensbasierte Anwendungen: Expertensysteme in bestimmten Bereichen erstellen
Ressourcen
Hier sind einige hilfreiche Ressourcen, die Ihnen den Einstieg erleichtern:
Offizielle Dokumentation
Kurs und Anleitung zu Prompts
Community
Observable Flutter Agentic-Serie
In Folge 59 sehen Sie, wie Craig Labenz und Andrew Brogden dieses Codelab durchgehen und interessante Aspekte der App-Entwicklung hervorheben.
In Folge 60 sind Craig und Andrew wieder dabei. Sie erweitern die Codelab-App um neue Funktionen und versuchen, LLMs dazu zu bringen, das zu tun, was sie sollen.
In Folge 61 analysiert Craig gemeinsam mit Chris Sells Nachrichtenüberschriften und generiert entsprechende Bilder.
Feedback
Wir freuen uns über Ihr Feedback zu diesem Codelab. Sie können Feedback auf folgende Weise geben:
Vielen Dank, dass Sie dieses Codelab durchgearbeitet haben. Wir hoffen, dass Sie die spannenden Möglichkeiten an der Schnittstelle von Flutter und KI weiter erkunden werden.