Informacje o tym ćwiczeniu (w Codelabs)
1. Tworzenie aplikacji Flutter z wykorzystaniem Gemini
Co utworzysz
W tym laboratorium programistycznym stworzysz Colorist – interaktywną aplikację Flutter, która wykorzystuje możliwości interfejsu Gemini API bezpośrednio w Twojej aplikacji Flutter. Czy kiedykolwiek chciałeś/chciałyś, aby użytkownicy mogli sterować aplikacją za pomocą języka naturalnego, ale nie wiedziałeś/wiedziałaś, od czego zacząć? Z tego ćwiczenia dowiesz się, jak to zrobić.
Aplikacja Colorist umożliwia użytkownikom opisywanie kolorów w naturalnym języku (np. „pomarańczowy jak zachód słońca” lub „głęboki błękit oceanu”). Aplikacja:
- przetwarza te opisy za pomocą interfejsu Gemini API od Google;
- interpretuje opisy w precyzyjne wartości kolorów RGB.
- Wyświetla kolor na ekranie w czasie rzeczywistym.
- Zawiera techniczne szczegóły koloru i ciekawy kontekst dotyczący koloru
- Zapisywanie historii niedawno wygenerowanych kolorów
Aplikacja ma podzielony ekran: po jednej stronie jest kolorowy wyświetlacz i system interaktywnego czatu, a po drugiej – szczegółowy panel logów z nieprzetworzonymi interakcjami LLM. Dzięki temu logowi możesz lepiej zrozumieć, jak działa integracja z modelem LLM.
Dlaczego to jest ważne dla deweloperów Fluttera
Modele językowe o wielu zadaniach rewolucjonizują sposób, w jaki użytkownicy wchodzą w interakcje z aplikacjami, ale ich skuteczna integracja z aplikacjami mobilnymi i na komputery wiąże się z niezwykłymi wyzwaniami. To ćwiczenie z programowania nauczy Cię praktycznych wzorców, które wykraczają poza zwykłe wywołania interfejsu API.
Twoja ścieżka edukacyjna
W tym ćwiczeniu krok po kroku omawiamy proces tworzenia aplikacji Colorist:
- Konfiguracja projektu – zaczniesz od podstawowej struktury aplikacji Flutter i pakietu
colorist_ui
. - Podstawowa integracja z Gemini – połącz aplikację z Vertex AI w Firebase i wdróż prostą komunikację z modelem LLM.
- Skuteczne prompty – tworzenie promptów systemowych, które pomagają dużemu modelowi językowemu zrozumieć opisy kolorów.
- Deklaracje funkcji – definiowanie narzędzi, których LLM może używać do ustawiania kolorów w aplikacji.
- Obsługa narzędzia – przetwarzanie wywołań funkcji z LLM i połączenie ich ze stanem aplikacji.
- Strumieniowanie odpowiedzi – zwiększenie wygody użytkowników dzięki strumieniowaniu odpowiedzi LLM w czasie rzeczywistym.
- Synchronizacja kontekstu w modelu LLM – tworzenie spójnego środowiska poprzez informowanie modelu LLM o czynnościach użytkownika.
Czego się nauczysz
- Konfigurowanie Vertex AI w Firebase w przypadku aplikacji Flutter
- Utwórz skuteczne prompty systemowe, aby kierować zachowaniem LLM.
- Wdróż deklaracje funkcji, które łączą język naturalny z funkcjami aplikacji.
- Przesyłanie odpowiedzi w strumieniu w celu zapewnienia użytkownikom szybkiej reakcji
- Synchronizuj stan między zdarzeniami interfejsu użytkownika a LLM.
- Zarządzanie stanem rozmowy w LLM za pomocą Riverpod
- Obsługa błędów w aplikacjach korzystających z LLM
Podgląd kodu: wstępny podgląd tego, co zamierzasz zaimplementować
Oto deklaracja funkcji, którą utworzysz, aby umożliwić LLM ustawianie kolorów w aplikacji:
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)'),
},
);
Wymagania wstępne
Aby w pełni wykorzystać możliwości tego ćwiczenia, musisz mieć:
- Doświadczenie w programowaniu w Flutterze – znajomość podstaw Fluttera i składnika Darta.
- Wiedza o programowaniu asynchronicznym – zrozumienie Futures, async/await i strumyków.
- Konto Firebase – do skonfigurowania Firebase potrzebne jest konto Google.
- Projekt Firebase z włączonymi płatnościami – Vertex AI w Firebase wymaga konta rozliczeniowego.
Zacznijmy tworzyć pierwszą aplikację Flutter korzystającą z LLM.
2. Konfigurowanie projektu i usługa echo
W tym pierwszym kroku skonfigurujesz strukturę projektu i wdrożysz prostą usługę echo, którą później zastąpisz integracją z Gemini API. Dzięki temu ustalana jest architektura aplikacji i upewniasz się, że interfejs użytkownika działa prawidłowo, zanim dodasz złożoność wywołań LLM.
Czego się nauczysz
- Konfigurowanie projektu Fluttera z wymaganymi zależnościami
- Praca z pakietem
colorist_ui
dotyczącym komponentów interfejsu - Wdrożenie usługi echo message i połączenie jej z interfejsem użytkownika
Ważna uwaga dotycząca cen
Tworzenie nowego projektu Flutter
Zacznij od utworzenia nowego projektu Flutter za pomocą tego polecenia:
flutter create -e colorist --platforms=android,ios,macos,web,windows
Flaga -e
wskazuje, że chcesz utworzyć pusty projekt bez domyślnej aplikacji counter
. Aplikacja została zaprojektowana tak, aby działać na komputerach, urządzeniach mobilnych i w internecie. flutterfire
nie obsługuje jednak obecnie Linuksa.
Dodawanie zależności
Otwórz katalog projektu i dodaj wymagane zależności:
cd colorist
flutter pub add colorist_ui flutter_riverpod riverpod_annotation
flutter pub add --dev build_runner riverpod_generator riverpod_lint json_serializable custom_lint
Spowoduje to dodanie tych kluczowych pakietów:
colorist_ui
: pakiet niestandardowy zawierający komponenty interfejsu aplikacji Coloristflutter_riverpod
iriverpod_annotation
: do zarządzania stanem.logging
: uporządkowane logowanie- zależności programistyczne do generowania kodu i analizowania błędów,
Twoja pubspec.yaml
będzie wyglądać mniej więcej tak:
pubspec.yaml
name: colorist
description: "A new Flutter project."
publish_to: 'none'
version: 0.1.0
environment:
sdk: ^3.7.2
dependencies:
flutter:
sdk: flutter
colorist_ui: ^0.1.0
flutter_riverpod: ^2.6.1
riverpod_annotation: ^2.6.1
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^5.0.0
build_runner: ^2.4.15
riverpod_generator: ^2.6.5
riverpod_lint: ^2.6.5
json_serializable: ^6.9.4
custom_lint: ^0.7.5
flutter:
uses-material-design: true
Konfigurowanie opcji analizy
Dodaj custom_lint
do pliku analysis_options.yaml
w katalogu głównym projektu:
include: package:flutter_lints/flutter.yaml
analyzer:
plugins:
- custom_lint
Ta konfiguracja umożliwia stosowanie narzędzi do sprawdzania kodu przeznaczonych do Riverpod, aby utrzymać jakość kodu.
Zaimplementuj plik main.dart
.
Zamień zawartość pliku lib/main.dart
na tę:
lib/main.dart
import 'package:colorist_ui/colorist_ui.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
void main() async {
runApp(ProviderScope(child: MainApp()));
}
class MainApp extends ConsumerWidget {
const MainApp({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
return MaterialApp(
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
),
home: MainScreen(
sendMessage: (message) {
sendMessage(message, ref);
},
),
);
}
// A fake LLM that just echoes back what it receives.
void sendMessage(String message, WidgetRef ref) {
final chatStateNotifier = ref.read(chatStateNotifierProvider.notifier);
final logStateNotifier = ref.read(logStateNotifierProvider.notifier);
chatStateNotifier.addUserMessage(message);
logStateNotifier.logUserText(message);
chatStateNotifier.addLlmMessage(message, MessageState.complete);
logStateNotifier.logLlmText(message);
}
}
Skonfiguruj aplikację Flutter, która implementuje prostą usługę echo, naśladującą działanie LLM, zwracając wiadomość użytkownika.
Architektura
Poświęć chwilę na zapoznanie się z architekturą aplikacji colorist
:
Pakiet colorist_ui
Pakiet colorist_ui
zawiera gotowe komponenty interfejsu użytkownika i narzędzia do zarządzania stanem:
- MainScreen: główny komponent interfejsu, który wyświetla:
- Układ podzielonego ekranu na komputerze (obszar interakcji i panel dziennika)
- Interfejs z kartami na urządzeniu mobilnym
- Wyświetlanie kolorów, interfejs czatu i miniatury historii
- Zarządzanie stanem: aplikacja używa kilku powiadomień o stanie:
- ChatStateNotifier zarządza wiadomościami na czacie.
- ColorStateNotifier zarządza bieżącym kolorem i jego historią.
- LogStateNotifier: zarządza wpisami w logu na potrzeby debugowania.
- Postępowanie z wiadomościami: aplikacja używa modelu wiadomości z różnymi stanami:
- Wiadomości dla użytkowników: wpisywane przez użytkownika.
- Wiadomości LLM: generowane przez model LLM (lub na razie przez usługę echo).
- MessageState (stan wiadomości): śledzi, czy wiadomości LLM są kompletne, czy nadal są przesyłane strumieniem.
Architektura aplikacji
Aplikacja ma taką architekturę:
- Warstwa interfejsu: dostarczana przez pakiet
colorist_ui
- Zarządzanie stanem: do zarządzania stanem w reakcji na zdarzenia używa się Riverpod.
- Warstwa usługi: zawiera obecnie prostą usługę echo, która zostanie zastąpiona usługą Gemini Chat.
- Integracja z LLM: zostanie dodana w późniejszych krokach.
Dzięki temu możesz skupić się na wdrażaniu integracji z LLM, podczas gdy komponenty interfejsu są już gotowe.
Uruchamianie aplikacji
Uruchom aplikację za pomocą tego polecenia:
flutter run -d DEVICE
Zastąp DEVICE
urządzeniem docelowym, takim jak macos
, windows
, chrome
lub identyfikator urządzenia.
Powinna się teraz wyświetlić aplikacja Colorist z:
- Kolorowy obszar wyświetlania z domyślnym kolorem
- interfejs czatu, w którym można wpisywać wiadomości;
- Panel logów z interakcjami na czacie
Napisz wiadomość, np. „Chcę ciemnoniebieski kolor”, i kliknij Wyślij. Usługa echo po prostu powtórzy Twoją wiadomość. W kolejnych krokach zastąpisz te wartości rzeczywistą interpretacją kolorów za pomocą interfejsu Gemini API w Vertex AI w Firebase.
Co dalej?
W następnym kroku skonfigurujesz Firebase i wdrożysz podstawową integrację interfejsu Gemini API, aby zastąpić usługę echo usługą czatu Gemini. Pozwoli to aplikacji interpretować opisy kolorów i podawać inteligentne odpowiedzi.
Rozwiązywanie problemów
Problemy z pakietem interfejsu użytkownika
Jeśli napotkasz problemy z pakietem colorist_ui
:
- Upewnij się, że używasz najnowszej wersji
- Sprawdź, czy zależność została dodana prawidłowo.
- Sprawdź, czy nie ma wersji pakietu, które są ze sobą sprzeczne
Błędy kompilacji
Jeśli widzisz błędy kompilacji:
- Sprawdź, czy masz zainstalowany najnowszy pakiet SDK Flutter na kanale stabilnym
- Uruchom
flutter clean
, a potemflutter pub get
- Sprawdź, czy w wyjściu konsoli znajdują się konkretne komunikaty o błędach
Kluczowe pojęcia
- Konfigurowanie projektu Fluttera z wymaganymi zależnościami
- poznanie architektury aplikacji i odpowiedzialności poszczególnych komponentów;
- wdrożenie prostej usługi, która naśladuje działanie LLM;
- Łączenie usługi z komponentami UI
- Korzystanie z Riverpod do zarządzania stanem
3. Podstawowa integracja z Gemini Chat
Na tym etapie zastąpisz usługę echo z poprzedniego kroku integracją Gemini API za pomocą Vertex AI w Firebase. Skonfigurujesz Firebase, skonfigurujesz niezbędnych dostawców i wdrożysz podstawową usługę czatu, która będzie się komunikować z interfejsem Gemini API.
Czego się nauczysz
- Konfigurowanie Firebase w aplikacji Flutter
- Konfigurowanie Vertex AI w Firebase w celu uzyskania dostępu do Gemini
- Tworzenie dostawców Riverpod dla usług Firebase i Gemini
- Implementacja podstawowej usługi czatu za pomocą interfejsu Gemini API
- Obsługa asynchronicznych odpowiedzi interfejsu API i stanów błędów
Konfigurowanie Firebase
Najpierw musisz skonfigurować Firebase w projekcie Flutter. Polega to na utworzeniu projektu Firebase, dodaniu do niego aplikacji i skonfigurowaniu niezbędnych ustawień Vertex AI.
Tworzenie projektu Firebase
- Otwórz konsolę Firebase i zaloguj się na swoje konto Google.
- Kliknij Utwórz projekt Firebase lub wybierz istniejący projekt.
- Aby utworzyć projekt, postępuj zgodnie z instrukcjami w kreatorze konfiguracji.
- Po utworzeniu projektu musisz przejść na abonament Blaze (opłata za korzystanie), aby uzyskać dostęp do usług Vertex AI. W lewym dolnym rogu Konsoli Firebase kliknij przycisk Uaktualnij.
Konfigurowanie Vertex AI w projekcie Firebase
- W konsoli Firebase otwórz swój projekt.
- Na pasku bocznym po lewej stronie kliknij AI.
- Na karcie Vertex AI w Firebase kliknij Rozpocznij.
- Postępuj zgodnie z podanymi instrukcjami, aby włączyć w projekcie interfejsy Vertex AI w Firebase.
Instalowanie interfejsu wiersza poleceń FlutterFire
Narzędzie wiersza poleceń FlutterFire upraszcza konfigurowanie Firebase w aplikacjach Flutter:
dart pub global activate flutterfire_cli
Dodawanie Firebase do aplikacji Flutter
- Dodaj do projektu pakiety Firebase Core i Vertex AI:
flutter pub add firebase_core firebase_vertexai
- Uruchom polecenie konfiguracji FlutterFire:
flutterfire configure
To polecenie:
- poprosi Cię o wybranie utworzonego właśnie projektu Firebase.
- Rejestrowanie aplikacji Flutter w Firebase
- Wygeneruj plik
firebase_options.dart
z konfiguracją projektu
Polecenie automatycznie wykryje wybrane platformy (iOS, Android, macOS, Windows, przeglądarka internetowa) i odpowiednio je skonfiguruje.
Konfiguracja platformy
Firebase wymaga minimalnych wersji wyższych niż domyślne dla Fluttera. Wymaga też dostępu do sieci, aby komunikować się z Vertex AI na serwerach Firebase.
Konfigurowanie uprawnień w systemie macOS
W przypadku systemu macOS musisz włączyć dostęp do sieci w uprawnieniach aplikacji:
- Otwórz
macos/Runner/DebugProfile.entitlements
i dodaj:
macos/Runner/DebugProfile.entitlements
<key>com.apple.security.network.client</key>
<true/>
- Otwórz też plik
macos/Runner/Release.entitlements
i dodaj ten sam wpis. - Zaktualizuj minimalną wersję macOS u góry
macos/Podfile
:
macos/Podfile
# Firebase requires at least macOS 10.15
platform :osx, '10.15'
Konfigurowanie uprawnień na iOS
W przypadku iOS zaktualizuj minimalną wersję u góry strony ios/Podfile
:
ios/Podfile
# Firebase requires at least iOS 13.0
platform :ios, '13.0'
Konfigurowanie ustawień Androida
W przypadku Androida zaktualizuj android/app/build.gradle.kts
:
android/app/build.gradle.kts
android {
// ...
ndkVersion = "27.0.12077973"
defaultConfig {
// ...
minSdk = 23
// ...
}
}
Tworzenie dostawców modeli Gemini
Teraz utwórz dostawców Riverpod dla Firebase i Gemini. Utwórz nowy plik lib/providers/gemini.dart
:
lib/providers/gemini.dart
import 'dart:async';
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_vertexai/firebase_vertexai.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import '../firebase_options.dart';
part 'gemini.g.dart';
@riverpod
Future<FirebaseApp> firebaseApp(Ref ref) =>
Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
@riverpod
Future<GenerativeModel> geminiModel(Ref ref) async {
await ref.watch(firebaseAppProvider.future);
final model = FirebaseVertexAI.instance.generativeModel(
model: 'gemini-2.0-flash',
);
return model;
}
@Riverpod(keepAlive: true)
Future<ChatSession> chatSession(Ref ref) async {
final model = await ref.watch(geminiModelProvider.future);
return model.startChat();
}
Ten plik określa podstawę dla 3 kluczowych dostawców. Te dostawcy są generowani przez generatory kodu Riverpod podczas wykonywania dart run build_runner
.
firebaseAppProvider
: inicjuje Firebase za pomocą konfiguracji projektu;geminiModelProvider
: tworzy instancję generatywnego modelu GeminichatSessionProvider
: tworzy i utrzymuje sesję czatu z modelem Gemini
Adnotacja keepAlive: true
w sesji czatu sprawia, że jest ona zachowana przez cały cykl życia aplikacji, co pozwala zachować kontekst rozmowy.
Wdrożenie usługi czatu Gemini
Aby zaimplementować usługę czatu, utwórz nowy plik lib/services/gemini_chat_service.dart
:
lib/services/gemini_chat_service.dart
import 'dart:async';
import 'package:colorist_ui/colorist_ui.dart';
import 'package:firebase_vertexai/firebase_vertexai.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import '../providers/gemini.dart';
part 'gemini_chat_service.g.dart';
class GeminiChatService {
GeminiChatService(this.ref);
final Ref ref;
Future<void> sendMessage(String message) async {
final chatSession = await ref.read(chatSessionProvider.future);
final chatStateNotifier = ref.read(chatStateNotifierProvider.notifier);
final logStateNotifier = ref.read(logStateNotifierProvider.notifier);
chatStateNotifier.addUserMessage(message);
logStateNotifier.logUserText(message);
final llmMessage = chatStateNotifier.createLlmMessage();
try {
final response = await chatSession.sendMessage(Content.text(message));
final responseText = response.text;
if (responseText != null) {
logStateNotifier.logLlmText(responseText);
chatStateNotifier.appendToMessage(llmMessage.id, responseText);
}
} catch (e, st) {
logStateNotifier.logError(e, st: st);
chatStateNotifier.appendToMessage(
llmMessage.id,
"\nI'm sorry, I encountered an error processing your request. "
"Please try again.",
);
} finally {
chatStateNotifier.finalizeMessage(llmMessage.id);
}
}
}
@riverpod
GeminiChatService geminiChatService(Ref ref) => GeminiChatService(ref);
Ta usługa:
- akceptuje wiadomości od użytkowników i przesyła je do interfejsu Gemini API;
- Aktualizuje interfejs czatu za pomocą odpowiedzi z modelu
- rejestruje wszystkie komunikaty, aby ułatwić zrozumienie rzeczywistego przebiegu procesu LLM;
- obsługa błędów z odpowiednimi opiniami użytkowników;
Uwaga: w tym momencie okno dziennika będzie wyglądać prawie tak samo jak okno czatu. Dziennik stanie się bardziej interesujący, gdy wprowadzisz wywołania funkcji, a potem odpowiedzi strumieniowe.
Generowanie kodu Riverpod
Uruchom polecenie build runner, aby wygenerować kod Riverpod:
dart run build_runner build --delete-conflicting-outputs
Spowoduje to utworzenie plików .g.dart
, których potrzebuje Riverpod.
Aktualizowanie pliku main.dart
Aby korzystać z nowej usługi czatu Gemini, zaktualizuj plik lib/main.dart
:
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),
),
);
}
}
Najważniejsze zmiany w tej aktualizacji to:
- Zastępowanie usługi echo usługą czatu opartą na Gemini API
- Dodawanie ekranów wczytywania i błędów za pomocą wzoru
AsyncValue
z użyciem metodywhen
- Łączenie interfejsu z nową usługą czatu za pomocą wywołania zwrotnego
sendMessage
Uruchamianie aplikacji
Uruchom aplikację za pomocą tego polecenia:
flutter run -d DEVICE
Zastąp DEVICE
urządzeniem docelowym, takim jak macos
, windows
, chrome
lub identyfikator urządzenia.
Gdy teraz wpiszesz wiadomość, zostanie ona wysłana do Gemini API, a Ty otrzymasz odpowiedź od LLM, a nie echo. Panel dziennika zawiera interakcje z interfejsem API.
Komunikacja z modelem LLM
Przyjrzyjmy się, co się dzieje, gdy komunikujesz się z interfejsem API Gemini:
Przepływ komunikacji
- Dane wejściowe użytkownika: użytkownik wpisujący tekst w interfejsie czatu
- Formatowanie żądania: aplikacja formatuje tekst jako obiekt
Content
dla interfejsu Gemini API. - Komunikacja z interfejsem API: tekst jest wysyłany do Gemini API za pomocą Vertex AI w Firebase.
- Przetwarzanie modelu LLM: model Gemini przetwarza tekst i generuje odpowiedź.
- Zarządzanie odpowiedzią: aplikacja odbiera odpowiedź i aktualizuje interfejs użytkownika.
- Logowanie: w celu zapewnienia przejrzystości wszystkie rozmowy są rejestrowane.
Sesje czatu i kontekst rozmowy
Sesja czatu Gemini zachowuje kontekst między wiadomościami, umożliwiając interakcje w ramach rozmowy. Oznacza to, że LLM „pamięta” poprzednie wymiany w bieżącej sesji, co umożliwia prowadzenie bardziej spójnych rozmów.
Adnotacja keepAlive: true
w usłudze czatu zapewnia, że kontekst jest zachowany przez cały cykl życia aplikacji. Ten trwały kontekst jest kluczowy dla zachowania naturalnego przebiegu rozmowy z modelem LLM.
Co dalej?
Obecnie możesz zapytać Gemini API o wszystko, ponieważ nie ma żadnych ograniczeń dotyczących tego, na co może ono odpowiedzieć. Możesz na przykład poprosić o podsumowanie Wojny Dwóch Róż, która nie ma nic wspólnego z celem Twojej aplikacji.
W następnym kroku utworzysz prompt systemowy, który pomoże Gemini skuteczniej interpretować opisy kolorów. W tym filmie pokażemy, jak dostosować działanie LLM do potrzeb aplikacji i skoncentrować jego możliwości na domenie aplikacji.
Rozwiązywanie problemów
Problemy z konfiguracją Firebase
Jeśli wystąpią błędy podczas inicjowania Firebase:
- Sprawdź, czy plik
firebase_options.dart
został poprawnie wygenerowany - Sprawdź, czy masz dostęp do Vertex AI w ramach abonamentu Blaze
Błędy dostępu do interfejsu API
Jeśli pojawią się błędy podczas uzyskiwania dostępu do interfejsu Gemini API:
- Sprawdź, czy płatności są prawidłowo skonfigurowane w projekcie Firebase
- Sprawdź, czy w projekcie Firebase są włączone interfejsy Vertex AI i Cloud AI API
- Sprawdź połączenie z siecią i ustawienia zapory sieciowej
- Sprawdź, czy nazwa modelu (
gemini-2.0-flash
) jest prawidłowa i dostępna.
Problemy z kontekstem rozmowy
Jeśli zauważysz, że Gemini nie pamięta poprzedniego kontekstu z czatu:
- Sprawdź, czy funkcja
chatSession
jest opatrzona adnotacją@Riverpod(keepAlive: true)
- Sprawdź, czy używasz tej samej sesji czatu do wszystkich wymian wiadomości.
- Przed wysłaniem wiadomości sprawdź, czy sesja czatu została prawidłowo zainicjowana.
Problemy związane z konkretną platformą
W przypadku problemów związanych z daną platformą:
- iOS/macOS: sprawdź, czy masz odpowiednie uprawnienia i czy skonfigurowano wersje minimalne
- Android: Sprawdź, czy minimalna wersja pakietu SDK jest prawidłowo ustawiona
- Sprawdzanie komunikatów o błędach związanych z konkretną platformą w konsoli
Kluczowe pojęcia
- Konfigurowanie Firebase w aplikacji Flutter
- Konfigurowanie Vertex AI w Firebase w celu uzyskania dostępu do Gemini
- Tworzenie dostawców Riverpod dla usług asynchronicznych
- Wdrożenie usługi czatu, która komunikuje się z modelem LLM
- Obsługa asynchronicznych stanów interfejsu API (ładowanie, błąd, dane)
- Przepływ komunikacji i sesje czatu w modelu LLM
4. Skuteczne prompty do opisu kolorów
W tym kroku utworzysz i wdrożysz prompt systemowy, który pomoże Gemini interpretować opisy kolorów. Prompty systemowe to skuteczny sposób na dostosowanie działania LLM do konkretnych zadań bez zmiany kodu.
Czego się nauczysz
- Wprowadzenie do promptów systemowych i ich znaczenie w przypadku aplikacji wykorzystujących LLM
- Tworzenie skutecznych promptów do zadań związanych z danymi domenami
- Ładowanie i używanie promptów systemowych w aplikacji Flutter
- kierowanie modelem LLM w kierunku udzielania odpowiedzi w spójnym formacie;
- Testowanie wpływu promptów systemowych na działanie LLM
Wskazówki dotyczące komunikatów systemowych
Zanim przejdziemy do wdrażania, wyjaśnijmy, czym są prompty systemowe i dlaczego są ważne:
Czym są prompty systemowe?
Prompt systemowy to specjalny typ instrukcji przekazywany modelowi LLM, który określa kontekst, wskazówki dotyczące zachowania i oczekiwań dotyczących odpowiedzi. W odróżnieniu od wiadomości od użytkowników prompty systemowe:
- Określ rolę i osobowość LLM
- Określ specjalistyczną wiedzę lub umiejętności
- Instrukcje dotyczące formatowania
- Ustawianie ograniczeń odpowiedzi
- Opisz, jak sobie radzić w różnych sytuacjach
Potraktuj prompt systemowy jako „opis stanowiska” dla LLM. Informuje on model, jak ma się zachowywać podczas rozmowy.
Dlaczego komunikaty systemowe są ważne
Prompty systemowe są kluczowe dla tworzenia spójnych, przydatnych interakcji z modelem LLM, ponieważ:
- Zachowaj spójność: kieruj model w kierunku udzielania odpowiedzi w spójnym formacie.
- Poprawić trafność: skoncentruj model na konkretnej domenie (w Twoim przypadku kolory).
- Określ granice: określ, co model powinien i czego nie powinien robić.
- Ulepsz wrażenia użytkowników: stwórz bardziej naturalny i przydatny wzorzec interakcji.
- Ograniczenie konieczności stosowania przetwarzania w pościepce: odpowiedzi są dostarczane w formatach, które łatwiej jest przeanalizować lub wyświetlić.
W przypadku aplikacji Colorist musisz użyć LLM, aby konsekwentnie interpretować opisy kolorów i podawać wartości RGB w określonym formacie.
Tworzenie komponentu z promptem systemowym
Najpierw utwórz plik promptu systemu, który będzie ładowany w czasie wykonywania. Dzięki temu możesz zmodyfikować prompt bez konieczności ponownego kompilowania aplikacji.
Utwórz nowy plik assets/system_prompt.md
z tą treścią:
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
Struktura promptów systemowych
Przyjrzyjmy się, jak działa ten prompt:
- Definicja roli: model LLM pełni rolę „asystenta eksperta ds. kolorów”.
- Wyjaśnienie zadania: definiuje główne zadanie jako interpretowanie opisów kolorów na wartości RGB.
- Format odpowiedzi: określa, jak dokładnie należy sformatować wartości RGB, aby zachować spójność.
- Przykładowa wymiana: zawiera konkretny przykład oczekiwanego wzorca interakcji.
- Rozwiązywanie problemów szczególnych: instrukcje dotyczące postępowania w przypadku niejasnych opisów.
- Ograniczenia i wskazówki: określa granice, np.utrzymywanie wartości RGB w zakresie od 0,0 do 1,0.
Dzięki temu podejściu odpowiedzi LLM będą spójne, zawierające informacje i sformatowane w sposób, który ułatwi ich analizę, jeśli chcesz wyodrębnić wartości RGB w ramach programu.
Aktualizowanie pliku pubspec.yaml
Teraz zaktualizuj dół pliku pubspec.yaml
, aby uwzględnić katalog zasobów:
pubspec.yaml
flutter:
uses-material-design: true
assets:
- assets/
Aby odświeżyć pakiet komponentów, uruchom flutter pub get
.
Tworzenie dostawcy promptów systemowych
Aby załadować komunikat systemowy, utwórz nowy plik lib/providers/system_prompt.dart
:
lib/providers/system_prompt.dart
import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'system_prompt.g.dart';
@riverpod
Future<String> systemPrompt(Ref ref) =>
rootBundle.loadString('assets/system_prompt.md');
Ten dostawca używa systemu wczytywania zasobów Fluttera do odczytywania pliku promptu w czasie wykonywania.
Aktualizowanie dostawcy modelu Gemini
Zmodyfikuj teraz plik lib/providers/gemini.dart
, aby zawierał komunikat systemowy:
lib/providers/gemini.dart
import 'dart:async';
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_vertexai/firebase_vertexai.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import '../firebase_options.dart';
import 'system_prompt.dart'; // Add this import
part 'gemini.g.dart';
@riverpod
Future<FirebaseApp> firebaseApp(Ref ref) =>
Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
@riverpod
Future<GenerativeModel> geminiModel(Ref ref) async {
await ref.watch(firebaseAppProvider.future);
final systemPrompt = await ref.watch(systemPromptProvider.future); // Add this line
final model = FirebaseVertexAI.instance.generativeModel(
model: 'gemini-2.0-flash',
systemInstruction: Content.system(systemPrompt), // And this line
);
return model;
}
@Riverpod(keepAlive: true)
Future<ChatSession> chatSession(Ref ref) async {
final model = await ref.watch(geminiModelProvider.future);
return model.startChat();
}
Kluczowa zmiana polega na dodaniu systemInstruction: Content.system(systemPrompt)
podczas tworzenia modelu generatywnego. To spowoduje, że Gemini będzie używać Twoich instrukcji jako promptu systemowego dla wszystkich interakcji w tej sesji czatu.
Generowanie kodu Riverpod
Uruchom polecenie build runner, aby wygenerować kod Riverpod:
dart run build_runner build --delete-conflicting-outputs
Uruchamianie i testowanie aplikacji
Teraz uruchom aplikację:
flutter run -d DEVICE
Spróbuj przetestować go z różnymi opisami kolorów:
- „Chcę niebieski”.
- „Daj mi zielony leśny”.
- „Utwórz intensywny pomarańczowy kolor zachodzącego słońca”
- „Chcę kolor świeżej lawendy”
- „Pokaż mi coś w kolorze głębokiej zieleni oceanu”.
Gemini powinien teraz odpowiadać w sposób zrozumiały, podając informacje o kolorach wraz z wartościami RGB w spójnym formacie. Prompt systemu skutecznie pokierował LLM, aby uzyskać odpowiedzi, których potrzebujesz.
Spróbuj też poprosić o treści bez uwzględniania kolorów. Na przykład główne przyczyny wojen domowych w Angliach. Powinieneś zauważyć różnicę w porównaniu z poprzednim krokiem.
Ważność projektowania promptów do zadań specjalistycznych
Prompty systemowe to połączenie sztuki i nauki. Stanowią one kluczowy element integracji LLM i mogą znacząco wpłynąć na to, jak przydatny będzie model w przypadku konkretnej aplikacji. Wykonaliśmy tutaj pewną formę prompt engineeringu, czyli dopasowaliśmy instrukcje, aby model działał w sposób odpowiedni do potrzeb Twojej aplikacji.
Skuteczne tworzenie promptów obejmuje:
- Jasna definicja roli: określenie celu LLM.
- Wyraźne instrukcje: szczegółowe informacje o tym, jak LLM powinien reagować.
- Konkretne przykłady: pokazywanie zamiast tylko opisywania, jak powinny wyglądać dobre odpowiedzi.
- Obsługa przypadków szczególnych: instruowanie LLM, jak radzić sobie z niejednoznacznymi scenariuszami.
- Specyfikacje formatowania: upewnij się, że odpowiedzi są uporządkowane w sposób spójny i użyteczny.
Utworzony przez Ciebie prompt systemowy przekształca ogólne możliwości Gemini w specjalistycznego asystenta interpretacji kolorów, który zapewnia odpowiedzi sformatowane specjalnie pod kątem potrzeb Twojej aplikacji. Jest to skuteczny wzór, którego można używać w różnych domenach i przy różnych zadaniach.
Co dalej?
W następnym kroku zbudujesz na tej podstawie deklaracje funkcji, które pozwolą LLM nie tylko sugerować wartości RGB, ale też wywoływać funkcje w aplikacji, aby bezpośrednio ustawiać kolor. Pokazuje to, jak LLM mogą wypełnić lukę między językiem naturalnym a konkretnymi funkcjami aplikacji.
Rozwiązywanie problemów
Problemy z wczytywaniem komponentów
Jeśli wystąpią błędy podczas wczytywania komunikatu systemowego:
- Sprawdź, czy
pubspec.yaml
poprawnie wyświetla katalog zasobów. - Sprawdź, czy ścieżka w pliku
rootBundle.loadString()
jest zgodna z lokalizacją pliku. - Aby odświeżyć pakiet komponentów, uruchom polecenie
flutter clean
, a następnieflutter pub get
.
Niespójne odpowiedzi
Jeśli LLM nie przestrzega konsekwentnie instrukcji formatowania:
- Spróbuj bardziej wyraźnie określić wymagania dotyczące formatu w promptach systemowych.
- Dodaj więcej przykładów, aby zademonstrować oczekiwany wzór
- Upewnij się, że format, którego używasz, jest odpowiedni dla modelu.
Ograniczanie liczby żądań interfejsu API
Jeśli napotkasz błędy związane z ograniczaniem szybkości:
- Pamiętaj, że usługa Vertex AI ma limity użycia
- Rozważ wdrożenie logiki ponownych prób z wzrastającym czasem do ponowienia (zostaw to jako ćwiczenie dla czytelnika)
- Sprawdź w konsoli Firebase, czy nie występują problemy z limitem
Kluczowe pojęcia
- Rola i znaczenie komunikatów systemowych w aplikacji LLM
- Tworzenie skutecznych promptów z jasnymi instrukcjami, przykładami i ograniczeniami
- Ładowanie i używanie promptów systemowych w aplikacji Flutter
- Kierowanie zachowaniem LLM w przypadku zadań związanych z konkretną domeną
- Kształtowanie odpowiedzi LLM za pomocą tworzenia promptów
Ten krok pokazuje, jak możesz dostosować działanie LLM bez zmiany kodu – wystarczy, że podasz wyraźne instrukcje w promptzie systemu.
5. Deklaracje funkcji w przypadku narzędzi LLM
Na tym etapie zaczniesz umożliwiać Gemini podejmowanie działań w aplikacji przez implementowanie deklaracji funkcji. Ta zaawansowana funkcja pozwala LLM nie tylko sugerować wartości RGB, ale też ustawiać je w interfejsie aplikacji za pomocą wywołań specjalnych narzędzi. Aby jednak zobaczyć żądania LLM wykonywane w aplikacji Flutter, musisz wykonać kolejny krok.
Czego się nauczysz
- Wywoływanie funkcji LLM i jego zalety dla aplikacji Flutter
- Definiowanie deklaracji funkcji opartych na schemacie na potrzeby Gemini
- Integrowanie deklaracji funkcji z modelem Gemini
- Aktualizowanie promptu systemu w celu wykorzystania możliwości narzędzia
Wywoływanie funkcji
Zanim zaczniesz stosować deklaracje funkcji, dowiedz się, czym one są i dlaczego są przydatne:
Co to jest wywołanie funkcji?
Wywoływanie funkcji (czasem nazywane „używaniem narzędzia”) to funkcja, która pozwala LLM:
- rozpoznawać, kiedy żądanie użytkownika będzie wymagało wywołania określonej funkcji;
- wygenerować uporządkowany obiekt JSON z parametrami potrzebnymi do wykonania tej funkcji;
- aplikacja wykonuje funkcję z tymi parametrami.
- Odbieranie wyniku funkcji i włączanie go w jej odpowiedzi
Zamiast tylko opisywać, co ma robić, LLM może wywoływać konkretne działania w aplikacji.
Dlaczego wywoływanie funkcji ma znaczenie w przypadku aplikacji Flutter
Funkcja wywoływania tworzy skuteczny most między językiem naturalnym a funkcjami aplikacji:
- Bezpośrednie działanie: użytkownicy mogą opisać, czego chcą, za pomocą języka naturalnego, a aplikacja reaguje na to konkretnymi działaniami.
- Wyjście uporządkowane: LLM generuje czyste, uporządkowane dane, a nie tekst, który wymaga parsowania.
- Operacje złożone: umożliwiają LLM dostęp do danych zewnętrznych, wykonywanie obliczeń lub modyfikowanie stanu aplikacji.
- Lepsze wrażenia użytkowników: zapewnia płynną integrację między rozmową a funkcjami.
W aplikacji Colorist wywołanie funkcji pozwala użytkownikom powiedzieć „Chcę zielony las” i natychmiast zaktualizować interfejs, stosując ten kolor, bez konieczności parsowania wartości RGB z tekstu.
Definiowanie deklaracji funkcji
Aby zdefiniować deklaracje funkcji, utwórz nowy plik lib/services/gemini_tools.dart
:
lib/services/gemini_tools.dart
import 'package:firebase_vertexai/firebase_vertexai.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'gemini_tools.g.dart';
class GeminiTools {
GeminiTools(this.ref);
final Ref ref;
FunctionDeclaration get setColorFuncDecl => FunctionDeclaration(
'set_color',
'Set the color of the display square based on red, green, and blue values.',
parameters: {
'red': Schema.number(description: 'Red component value (0.0 - 1.0)'),
'green': Schema.number(description: 'Green component value (0.0 - 1.0)'),
'blue': Schema.number(description: 'Blue component value (0.0 - 1.0)'),
},
);
List<Tool> get tools => [
Tool.functionDeclarations([setColorFuncDecl]),
];
}
@riverpod
GeminiTools geminiTools(Ref ref) => GeminiTools(ref);
Deklaracje funkcji
Zobaczmy, co robi ten kod:
- Nazwy funkcji: nazwa funkcji
set_color
wyraźnie wskazuje jej przeznaczenie. - Opis funkcji: podajesz czytelny opis, który pomaga modelowi LLM zrozumieć, kiedy należy go używać.
- Definicje parametrów: parametry strukturalne definiujesz wraz z opisami:
red
: składowa czerwona modelu RGB, określona jako liczba z zakresu 0,0–1,0.green
: składowa zielona modelu RGB, określona jako liczba z zakresu od 0,0 do 1,0.blue
: składowa niebieska modelu RGB, określona jako liczba z zakresu 0,0–1,0
- Typy schematów: wartość
Schema.number()
oznacza, że są to wartości liczbowe. - Kolekcja narzędzi: tworzysz listę narzędzi zawierających deklarację funkcji.
Dzięki temu uporządkowanemu podejściu model LLM Gemini może zrozumieć:
- Kiedy ma wywołać tę funkcję
- jakie parametry musi podać.
- jakie ograniczenia dotyczą tych parametrów (np. zakres wartości);
Aktualizowanie dostawcy modelu Gemini
Teraz zmodyfikuj plik lib/providers/gemini.dart
, aby zawierał deklaracje funkcji podczas inicjowania modelu Gemini:
lib/providers/gemini.dart
import 'dart:async';
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_vertexai/firebase_vertexai.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import '../firebase_options.dart';
import '../services/gemini_tools.dart'; // Add this import
import 'system_prompt.dart';
part 'gemini.g.dart';
@riverpod
Future<FirebaseApp> firebaseApp(Ref ref) =>
Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
@riverpod
Future<GenerativeModel> geminiModel(Ref ref) async {
await ref.watch(firebaseAppProvider.future);
final systemPrompt = await ref.watch(systemPromptProvider.future);
final geminiTools = ref.watch(geminiToolsProvider); // Add this line
final model = FirebaseVertexAI.instance.generativeModel(
model: 'gemini-2.0-flash',
systemInstruction: Content.system(systemPrompt),
tools: geminiTools.tools, // And this line
);
return model;
}
@Riverpod(keepAlive: true)
Future<ChatSession> chatSession(Ref ref) async {
final model = await ref.watch(geminiModelProvider.future);
return model.startChat();
}
Kluczowa zmiana to dodanie parametru tools: geminiTools.tools
podczas tworzenia modelu generatywnego. Dzięki temu Gemini wie, jakie funkcje może wywoływać.
Aktualizacja komunikatu o aktualizacji systemu
Teraz musisz zmodyfikować prompt systemowy, aby przekazać LLM informacje o nowym narzędziu set_color
. Aktualizacja assets/system_prompt.md
:
assets/system_prompt.md
# Colorist System Prompt
You are a color expert assistant integrated into a desktop app called Colorist. Your job is to interpret natural language color descriptions and set the appropriate color values using a specialized tool.
## Your Capabilities
You are knowledgeable about colors, color theory, and how to translate natural language descriptions into specific RGB values. You have access to the following tool:
`set_color` - Sets the RGB values for the color display based on a description
## How to Respond to User Inputs
When users describe a color:
1. First, acknowledge their color description with a brief, friendly response
2. Interpret what RGB values would best represent that color description
3. Use the `set_color` tool to set those values (all values should be between 0.0 and 1.0)
4. After setting the color, provide a brief explanation of your interpretation
Example:
User: "I want a sunset orange"
You: "Sunset orange is a warm, vibrant color that captures the golden-red hues of the setting sun. It combines a strong red component with moderate orange tones."
[Then you would call the set_color tool with approximately: red=1.0, green=0.5, blue=0.25]
After the tool call: "I've set a warm orange with strong red, moderate green, and minimal blue components that is reminiscent of the sun low on the horizon."
## When Descriptions are Unclear
If a color description is ambiguous or unclear, please ask the user clarifying questions, one at a time.
## Important Guidelines
- Always keep RGB values between 0.0 and 1.0
- Provide thoughtful, knowledgeable responses about colors
- When possible, include color psychology, associations, or interesting facts about colors
- Be conversational and engaging in your responses
- Focus on being helpful and accurate with your color interpretations
Najważniejsze zmiany w promptach systemowych to:
- Wprowadzenie do narzędzia: zamiast prosić o sformatowane wartości RGB, możesz teraz poinformować LLM o narzędzie
set_color
- Zmodyfikowany proces: krok 3 zmieniasz z „Sformatuj wartości w odpowiedzi” na „Użyj narzędzia do ustawienia wartości”.
- Zaktualizowany przykład: pokazujesz, jak odpowiedź powinna zawierać wywołanie narzędzia zamiast sformatowanego tekstu.
- Wymagania dotyczące formatowania zostały usunięte: ponieważ używasz uporządkowanych wywołań funkcji, nie musisz już używać określonego formatu tekstu.
To zaktualizowane prompt kieruje LLM do wywoływania funkcji zamiast podawania wartości RGB w postaci tekstowej.
Generowanie kodu Riverpod
Uruchom polecenie build runner, aby wygenerować kod Riverpod:
dart run build_runner build --delete-conflicting-outputs
Uruchamianie aplikacji
W tym momencie Gemini wygeneruje treści, które próbują użyć wywołania funkcji, ale nie zostały jeszcze zaimplementowane żadne moduły obsługi wywołań funkcji. Gdy uruchomisz aplikację i opiszesz kolor, Gemini zareaguje tak, jakby wywołało narzędzie, ale do następnego kroku nie zobaczysz żadnych zmian kolorów w interfejsie.
Uruchom aplikację:
flutter run -d DEVICE
Spróbuj opisać kolor, np. „ciemny błękit oceaniczny” lub „zielony leśny”, i obserwuj reakcje. LLM próbuje wywołać zdefiniowane powyżej funkcje, ale Twój kod nie wykrywa jeszcze wywołań funkcji.
Proces wywoływania funkcji
Sprawdźmy, co się dzieje, gdy Gemini używa wywołania funkcji:
- Wybór funkcji: na podstawie żądania użytkownika LLM decyduje, czy wywołanie funkcji będzie przydatne.
- Generowanie parametrów: LLM generuje wartości parametrów, które pasują do schematu funkcji.
- Format wywołania funkcji: LLM wysyła w odpowiedzi obiekt wywołania funkcji w ustrukturyzowanej postaci.
- Obsługa aplikacji: aplikacja otrzyma to wywołanie i wykonuje odpowiednią funkcję (wdrożoną w następnym kroku).
- Integracja z odpowiedziami: w rozmowach wieloetapowych LLM oczekuje zwrócenia wyniku funkcji.
W obecnej wersji aplikacji występują 3 pierwsze kroki, ale nie zostały jeszcze zaimplementowane kroki 4 i 5 (obsługa wywołań funkcji). Zrobisz to w następnym kroku.
Szczegóły techniczne: jak Gemini decyduje, kiedy używać funkcji
Gemini podejmuje inteligentne decyzje dotyczące tego, kiedy używać funkcji, na podstawie:
- Intencja użytkownika: czy żądanie użytkownika najlepiej spełni funkcja
- Odpowiednie funkcje: jak dobrze dostępne funkcje pasują do zadania
- Dostępność parametrów: określa, czy można z pewnością określić wartości parametrów.
- Instrukcje systemowe: wskazówki dotyczące korzystania z funkcji wyświetlane przez system.
Dzięki dodaniu przejrzystych deklaracji funkcji i instrukcji systemowych skonfigurowałeś/skonfigurowałaś Gemini tak, aby rozpoznawał żądania opisu koloru jako okazję do wywołania funkcji set_color
.
Co dalej?
W następnym kroku zaimplementujesz metody obsługi wywołań funkcji pochodzących z Gemini. Dzięki temu opisy użytkowników będą mogły wywoływać rzeczywiste zmiany kolorów w interfejsie za pomocą wywołań funkcji LLM.
Rozwiązywanie problemów
Problemy z deklaracją funkcji
Jeśli wystąpią błędy w deklaracjach funkcji:
- Sprawdź, czy nazwy i typy parametrów są zgodne z oczekiwaniami
- Sprawdź, czy nazwa funkcji jest jasna i opisowa
- Upewnij się, że opis funkcji dokładnie wyjaśnia jej przeznaczenie.
Problemy z promptem systemowym
Jeśli LLM nie próbuje użyć funkcji:
- Sprawdź, czy komunikat systemu wyraźnie instruuje administratora, aby użył narzędzia
set_color
. - Sprawdź, czy przykład w prompcie systemowej pokazuje wykorzystanie funkcji.
- Spróbuj bardziej wyraźnie opisać instrukcje dotyczące korzystania z tego narzędzia
Problemy ogólne
Jeśli napotkasz inne problemy:
- Sprawdź konsolę pod kątem błędów związanych z deklaracjami funkcji.
- Sprawdź, czy narzędzia są prawidłowo przekazywane do modelu.
- Upewnij się, że cały kod wygenerowany przez Riverpod jest aktualny
Kluczowe pojęcia
- Definiowanie deklaracji funkcji w celu rozszerzenia możliwości LLM w aplikacjach Flutter
- Tworzenie schematów parametrów na potrzeby gromadzenia uporządkowanych danych
- Integrowanie deklaracji funkcji z modelem Gemini
- Aktualizowanie komunikatów systemowych zachęcających do korzystania z funkcji
- Jak duże modele językowe wybierają i wywołują funkcje
Ten krok pokazuje, jak LLM mogą wypełnić lukę między danymi wejściowymi w języku naturalnym a zapytaniami do funkcji strukturalnych, co stwarza podstawę do płynnej integracji funkcji konwersacyjnych i aplikacyjnych.
6. Obsługa narzędzia
W tym kroku zaimplementujesz procedury obsługi wywołań funkcji pochodzących z Gemini. W ten sposób zostaje zamknięte koło komunikacji między danymi wejściowymi w języku naturalnym a konkretnymi funkcjami aplikacji, co pozwala LLM bezpośrednio modyfikować interfejs na podstawie opisów użytkownika.
Czego się nauczysz
- Poznaj cały potok wywoływania funkcji w aplikacjach LLM
- Przetwarzanie wywołań funkcji z Gemini w aplikacji Flutter
- Implementacja funkcji obsługi, które modyfikują stan aplikacji
- Obsługa odpowiedzi funkcji i zwracanie wyników do LLM
- Tworzenie pełnego przepływu komunikacji między LLM a UI
- Rejestrowanie wywołań funkcji i ich odpowiedzi w celu zapewnienia przejrzystości
Omówienie potoku wywoływania funkcji
Zanim przejdziemy do implementacji, przyjrzyjmy się całemu procesowi wywoływania funkcji:
Cały proces
- Dane wejściowe użytkownika: użytkownik opisuje kolor w języku naturalnym (np. „forest green”)
- Procesowanie LLM: Gemini analizuje opis i podejmuje decyzję o wywołaniu funkcji
set_color
- Generowanie wywołania funkcji: Gemini tworzy uporządkowany plik JSON z parametrami (wartości czerwony, zielony, niebieski).
- Otrzymywanie wywołania funkcji: aplikacja otrzymuje te dane ustrukturyzowane od Gemini.
- Wykonanie funkcji: aplikacja wykonuje funkcję z podanymi parametrami.
- Aktualizacja stanu: funkcja aktualizuje stan aplikacji (zmienia wyświetlany kolor).
- Generowanie odpowiedzi: funkcja zwraca wyniki do LLM.
- Integracja odpowiedzi: model LLM uwzględnia te wyniki w swojej ostatecznej odpowiedzi.
- Aktualizacja interfejsu: interfejs reaguje na zmianę stanu, wyświetlając nowy kolor.
Pełny cykl komunikacji jest niezbędny do prawidłowej integracji LLM. Gdy LLM wywołuje funkcję, nie wysyła po prostu żądania i nie przechodzi dalej. Zamiast tego czeka, aż aplikacja wykona funkcję i zwróci wyniki. Następnie model LLM wykorzystuje te wyniki do sformułowania ostatecznej odpowiedzi, tworząc naturalny tok rozmowy, który uwzględnia podjęte działania.
Implementacja modułów obsługi funkcji
Zaktualizujmy plik lib/services/gemini_tools.dart
, aby dodać do niego moduły obsługi wywołań funkcji:
lib/services/gemini_tools.dart
import 'package:colorist_ui/colorist_ui.dart';
import 'package:firebase_vertexai/firebase_vertexai.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'gemini_tools.g.dart';
class GeminiTools {
GeminiTools(this.ref);
final Ref ref;
FunctionDeclaration get setColorFuncDecl => FunctionDeclaration(
'set_color',
'Set the color of the display square based on red, green, and blue values.',
parameters: {
'red': Schema.number(description: 'Red component value (0.0 - 1.0)'),
'green': Schema.number(description: 'Green component value (0.0 - 1.0)'),
'blue': Schema.number(description: 'Blue component value (0.0 - 1.0)'),
},
);
List<Tool> get tools => [
Tool.functionDeclarations([setColorFuncDecl]),
];
Map<String, Object?> handleFunctionCall( // Add from here
String functionName,
Map<String, Object?> arguments,
) {
final logStateNotifier = ref.read(logStateNotifierProvider.notifier);
logStateNotifier.logFunctionCall(functionName, arguments);
return switch (functionName) {
'set_color' => handleSetColor(arguments),
_ => handleUnknownFunction(functionName),
};
}
Map<String, Object?> handleSetColor(Map<String, Object?> arguments) {
final colorStateNotifier = ref.read(colorStateNotifierProvider.notifier);
final red = (arguments['red'] as num).toDouble();
final green = (arguments['green'] as num).toDouble();
final blue = (arguments['blue'] as num).toDouble();
final functionResults = {
'success': true,
'current_color':
colorStateNotifier
.updateColor(red: red, green: green, blue: blue)
.toLLMContextMap(),
};
final logStateNotifier = ref.read(logStateNotifierProvider.notifier);
logStateNotifier.logFunctionResults(functionResults);
return functionResults;
}
Map<String, Object?> handleUnknownFunction(String functionName) {
final logStateNotifier = ref.read(logStateNotifierProvider.notifier);
logStateNotifier.logWarning('Unsupported function call $functionName');
return {
'success': false,
'reason': 'Unsupported function call $functionName',
};
} // To here.
}
@riverpod
GeminiTools geminiTools(Ref ref) => GeminiTools(ref);
Obsługa funkcji
Zobaczmy, co robią te funkcje obsługi:
handleFunctionCall
: centralny rozsyłający:- W panelu logów rejestruje wywołanie funkcji w celu zapewnienia przejrzystości.
- Przekierowuje na odpowiedni moduł obsługi na podstawie nazwy funkcji.
- Zwraca ustrukturyzowaną odpowiedź, która zostanie wysłana z powrotem do modelu LLM.
handleSetColor
: konkretny moduł obsługi funkcjiset_color
, który:- Wyodrębnianie wartości RGB z mapy argumentów
- Konwertuje je na oczekiwane typy (double).
- Aktualizuje stan koloru aplikacji za pomocą
colorStateNotifier
- Tworzy uporządkowaną odpowiedź z informacjami o stanie powodzenia i bieżącym kolorze.
- rejestruje wyniki funkcji na potrzeby debugowania,
handleUnknownFunction
: awaryjny moduł obsługi nieznanych funkcji, który:- rejestruje ostrzeżenie o nieobsługiwanej funkcji;
- zwraca komunikat o błędzie do LLM;
Funkcja handleSetColor
jest szczególnie ważna, ponieważ łączy zrozumienie języka naturalnego przez LLM z konkretnymi zmianami w interfejsie.
Zaktualizuj usługę Gemini Chat, aby przetwarzać wywołania i odpowiedzi funkcji
Zmień teraz plik lib/services/gemini_chat_service.dart
, aby przetwarzać wywołania funkcji z odpowiedzi LLM i przesyłać wyniki z powrotem do LLM:
lib/services/gemini_chat_service.dart
import 'dart:async';
import 'package:colorist_ui/colorist_ui.dart';
import 'package:firebase_vertexai/firebase_vertexai.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import '../providers/gemini.dart';
import 'gemini_tools.dart'; // Add this import
part 'gemini_chat_service.g.dart';
class GeminiChatService {
GeminiChatService(this.ref);
final Ref ref;
Future<void> sendMessage(String message) async {
final chatSession = await ref.read(chatSessionProvider.future);
final chatStateNotifier = ref.read(chatStateNotifierProvider.notifier);
final logStateNotifier = ref.read(logStateNotifierProvider.notifier);
chatStateNotifier.addUserMessage(message);
logStateNotifier.logUserText(message);
final llmMessage = chatStateNotifier.createLlmMessage();
try {
final response = await chatSession.sendMessage(Content.text(message));
final responseText = response.text;
if (responseText != null) {
logStateNotifier.logLlmText(responseText);
chatStateNotifier.appendToMessage(llmMessage.id, responseText);
}
if (response.functionCalls.isNotEmpty) { // Add from here
final geminiTools = ref.read(geminiToolsProvider);
final functionResultResponse = await chatSession.sendMessage(
Content.functionResponses([
for (final functionCall in response.functionCalls)
FunctionResponse(
functionCall.name,
geminiTools.handleFunctionCall(
functionCall.name,
functionCall.args,
),
),
]),
);
final responseText = functionResultResponse.text;
if (responseText != null) {
logStateNotifier.logLlmText(responseText);
chatStateNotifier.appendToMessage(llmMessage.id, responseText);
}
} // To here.
} catch (e, st) {
logStateNotifier.logError(e, st: st);
chatStateNotifier.appendToMessage(
llmMessage.id,
"\nI'm sorry, I encountered an error processing your request. "
"Please try again.",
);
} finally {
chatStateNotifier.finalizeMessage(llmMessage.id);
}
}
}
@riverpod
GeminiChatService geminiChatService(Ref ref) => GeminiChatService(ref);
Przepływ komunikacji
Najważniejszym dodatkiem jest pełne obsługiwanie wywołań i odpowiedzi funkcji:
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);
}
}
Ten kod:
- Sprawdzanie, czy odpowiedź LLM zawiera jakiekolwiek wywołania funkcji
- W przypadku każdego wywołania funkcji wywołuje metodę
handleFunctionCall
z nazwą funkcji i argumentami. - Zbiera wyniki każdego wywołania funkcji.
- Przesyła te wyniki z powrotem do modelu LLM za pomocą funkcji
Content.functionResponses
. - Przetwarza odpowiedź LLM na wyniki funkcji.
- aktualizuje interfejs, dodając tekst ostatecznej odpowiedzi;
W ten sposób tworzysz proces podróży w obie strony:
- Użytkownik –> LLM: prosi o kolor
- LLM → Aplikacja: wywołania funkcji z parametrami
- Aplikacja → Użytkownik: wyświetlany nowy kolor
- Aplikacja → LLM: wyniki funkcji
- LLM –> Użytkownik: ostateczna odpowiedź zawierająca wyniki funkcji
Generowanie kodu Riverpod
Uruchom polecenie build runner, aby wygenerować kod Riverpod:
dart run build_runner build --delete-conflicting-outputs
Uruchamianie i testowanie pełnego procesu
Teraz uruchom aplikację:
flutter run -d DEVICE
Spróbuj wpisać różne opisy kolorów:
- „Chcę ciemnoczerwony”.
- „Pokaż mi uspokajający niebieski kolor”
- „Daj mi kolor świeżych liści mięty”.
- „Chcę zobaczyć ciepły pomarańczowy kolor zachodzącego słońca”
- „Ustaw kolor na ciemny fiolet”
Powinieneś zobaczyć:
- Twoja wiadomość wyświetlana w interfejsie czatu
- Odpowiedź Gemini wyświetlana na czacie
- Wywołania funkcji są rejestrowane w panelu logów
- Wyniki funkcji są rejestrowane natychmiast po
- Kwadrat koloru aktualizowany w celu wyświetlenia opisanego koloru
- wartości RGB, które zmieniają się, aby wyświetlać komponenty nowego koloru;
- Ostatnia odpowiedź Gemini, często odnosząca się do koloru, który został ustawiony
Panel logów zawiera informacje o tym, co dzieje się za kulisami. Zobaczysz:
- Dokładne wywołania funkcji wykonywane przez Gemini
- Parametry wybrane dla każdej wartości RGB.
- wyniki zwracane przez funkcję,
- odpowiedzi uzupełniające od Gemini,
Powiadomienie o kolorowym stanie
colorStateNotifier
, którego używasz do aktualizowania kolorów, jest częścią pakietu colorist_ui
. Zarządza:
- Aktualny kolor wyświetlany w interfejsie
- historia kolorów (ostatnie 10 kolorów);
- Powiadomienia o zmianach stanu komponentów interfejsu
Gdy wywołasz funkcję updateColor
z nowymi wartościami RGB, wykonuje ona następujące czynności:
- Tworzy nowy obiekt
ColorData
z podanymi wartościami. - Aktualizuje bieżący kolor w stanie aplikacji.
- Dodaje kolor do historii.
- powoduje aktualizacje interfejsu za pomocą zarządzania stanem Riverpod;
Komponenty interfejsu w pakiecie colorist_ui
monitorują ten stan i automatycznie go aktualizują, gdy się zmieni, co powoduje, że interfejs reaguje na zmiany.
Obsługa błędów
Implementacja musi zawierać solidne mechanizmy obsługi błędów:
- Blok try-catch: obejmuje wszystkie interakcje z LLM, aby przechwytywać wszelkie wyjątki.
- Rejestrowanie błędów: rejestruje błędy w panelu dziennika wraz z lokalizacją błędów w kodzie.
- Opinia użytkownika: wyświetla przyjazny komunikat o błędzie w czacie.
- Usuwanie stanu: kończy stan wiadomości, nawet jeśli wystąpi błąd.
Dzięki temu aplikacja pozostaje stabilna i zapewnia odpowiednią informację zwrotną nawet wtedy, gdy występują problemy z usługą LLM lub z wykonywaniem funkcji.
Zastosowanie funkcji wywołania w celu zwiększenia zadowolenia użytkowników
Twoje osiągnięcia pokazują, jak duże możliwości mają duże modele językowe w ramach tworzenia naturalnych interfejsów:
- Interfejs w języku naturalnym: użytkownicy wyrażają intencję w języku codziennym.
- Inteligentna interpretacja: LLM przekształca niejasne opisy w precyzyjne wartości.
- Bezpośrednie manipulowanie: interfejs aktualizuje się w odpowiedzi na język naturalny.
- Odpowiedzi kontekstowe: LLM zapewnia kontekst konwersacji dotyczący zmian.
- Niski poziom obciążenia poznawczego: użytkownicy nie muszą rozumieć wartości RGB ani teorii kolorów.
Ten wzorzec wywoływania funkcji LLM w celu połączenia języka naturalnego z działaniami w interfejsie użytkownika można rozszerzyć na niezliczone inne obszary poza wyborem kolorów.
Co dalej?
W następnym kroku poprawisz wrażenia użytkownika, wdrażając odpowiedzi strumieniowe. Zamiast czekać na pełną odpowiedź, możesz przetwarzać fragmenty tekstu i wywołania funkcji w miarę ich otrzymywania, tworząc bardziej responsywną i zaangażowaną aplikację.
Rozwiązywanie problemów
Problemy z wywoływaniem funkcji
Jeśli Gemini nie wywołuje Twoich funkcji lub parametry są nieprawidłowe:
- Sprawdź, czy deklaracja funkcji jest zgodna z informacjami wyświetlanymi przez system
- Sprawdź, czy nazwy i typy parametrów są spójne
- Upewnij się, że komunikat systemu wyraźnie instruuje LLM, aby użył tego narzędzia.
- Sprawdź, czy nazwa funkcji w obiekcie obsługującym jest taka sama jak w deklaracji.
- szczegółowe informacje o wywołaniach funkcji znajdziesz w panelu logów;
Problemy z odpowiedziami funkcji
Jeśli wyniki funkcji nie są prawidłowo wysyłane z powrotem do LLM:
- Sprawdź, czy funkcja zwraca prawidłowo sformatowaną mapę.
- Sprawdź, czy Content.functionResponses jest prawidłowo budowany.
- Sprawdź, czy w logu nie ma błędów związanych z odpowiedziami funkcji.
- Upewnij się, że używasz tej samej sesji czatu, w której została udzielona odpowiedź.
Problemy z wyświetlaniem kolorów
Jeśli kolory nie wyświetlają się prawidłowo:
- Upewnij się, że wartości RGB są prawidłowo konwertowane na liczby podwójnie precyzyjne (LLM może wysyłać je jako liczby całkowite).
- Sprawdź, czy wartości mieszczą się w oczekiwanym zakresie (0,0–1,0).
- Sprawdź, czy powiadomienie o stanie koloru jest wywoływane prawidłowo
- Sprawdź w logu dokładne wartości przekazywane do funkcji.
Problemy ogólne
Problemy ogólne:
- Sprawdź dzienniki pod kątem błędów lub ostrzeżeń.
- Sprawdzanie połączeń Vertex AI w Firebase
- Sprawdź, czy w parametrach funkcji nie ma niezgodności typów.
- Upewnij się, że cały kod wygenerowany przez Riverpod jest aktualny
Kluczowe pojęcia
- Implementacja pełnego potoku wywoływania funkcji w Flutter
- Uzyskiwanie pełnej komunikacji między LLM a aplikacją
- Przetwarzanie uporządkowanych danych z odpowiedzi modelu LLM
- wysyłanie wyników funkcji z powrotem do LLM w celu uwzględnienia ich w odpowiedziach;
- Korzystanie z panelu logów w celu uzyskania wglądu w interakcje LLM z aplikacją
- Połączenie danych wejściowych w języku naturalnym z konkretnymi zmianami w interfejsie
Po wykonaniu tego kroku Twoja aplikacja demonstruje jeden z najskuteczniejszych wzorców integracji z modelami LLM: przekształca dane wejściowe w naturalnym języku w konkretne działania w interfejsie, zachowując przy tym spójność konwersacji, która potwierdza te działania. Dzięki temu interfejs konwersacyjny jest intuicyjny i wydaje się użytkownikom magiczny.
7. Strumieniowanie odpowiedzi w celu zapewnienia lepszych wrażeń
W tym kroku ulepszasz wrażenia użytkowników, wdrażając strumieniowe odpowiedzi z Gemini. Zamiast czekać na wygenerowanie całej odpowiedzi, będziesz przetwarzać fragmenty tekstu i wywołania funkcji w miarę ich otrzymywania, tworząc bardziej responsywną i zaangażowaną aplikację.
Omówienie tego etapu
- Dlaczego strumieniowanie jest ważne w przypadku aplikacji korzystających z LLM
- Implementacja strumieniowych odpowiedzi LLM w aplikacji Flutter
- Przetwarzanie częściowych fragmentów tekstu w miarę ich otrzymywania z interfejsu API
- Zarządzanie stanem rozmowy w celu zapobiegania konfliktom wiadomości
- Obsługa wywołań funkcji w odpowiedziach strumieniowych
- Tworzenie wizualnych wskaźników postępu odpowiedzi
Dlaczego strumieniowanie jest ważne w przypadku aplikacji LLM
Zanim przejdziemy do wdrażania, wyjaśnijmy, dlaczego strumieniowe przesyłanie odpowiedzi jest kluczowe dla zapewnienia użytkownikom doskonałych wrażeń w przypadku modeli LLM:
Lepsze wrażenia użytkowników
Transmisja strumieniowa odpowiedzi zapewnia użytkownikom kilka istotnych korzyści:
- Zmniejszony czas oczekiwania: użytkownicy widzą tekst od razu (zwykle w ciągu 100–300 ms), zamiast czekać kilka sekund na pełną odpowiedź. Takie wrażenie natychmiastowości znacznie zwiększa zadowolenie użytkowników.
- Naturalny rytm rozmowy: stopniowe pojawianie się tekstu naśladuje sposób komunikacji ludzi, tworząc bardziej naturalny dialog.
- Stopniowe przetwarzanie informacji: użytkownicy mogą zacząć przetwarzać informacje w miarę ich otrzymywania, zamiast być przytłomieni dużym blokiem tekstu naraz.
- Możliwość przerwania w trakcie: w pełnej aplikacji użytkownicy mogą przerwać lub przekierować LLM, jeśli uznają, że idzie on w nieodpowiednim kierunku.
- Wizualne potwierdzenie działania: tekst strumieniowy zapewnia natychmiastową informację o tym, że system działa, co zmniejsza niepewność.
Zalety techniczne
Oprócz ulepszenia wrażeń użytkownika streaming zapewnia też korzyści techniczne:
- Wczesna realizacja funkcji: wywołania funkcji mogą być wykrywane i wykonywane, gdy tylko pojawią się w strumieniach, bez oczekiwania na pełną odpowiedź.
- Stopniowe aktualizacje interfejsu: możesz aktualizować interfejs stopniowo, gdy pojawiają się nowe informacje, co spowoduje większą dynamikę.
- Zarządzanie stanem rozmowy: strumieniowanie zapewnia wyraźne sygnały o tym, kiedy odpowiedzi są gotowe, a kiedy nadal w trakcie, co umożliwia lepsze zarządzanie stanem.
- Mniejsze ryzyko przekroczenia limitu czasu: w przypadku odpowiedzi nieprzekazanych strumieniowo długotrwałe generowanie może spowodować przekroczenie limitu czasu połączenia. Strumieniowanie nawiązuje połączenie wcześnie i utrzymuje je.
W przypadku aplikacji Colorist strumieniowe przesyłanie danych oznacza, że użytkownicy szybciej zobaczą zarówno odpowiedzi tekstowe, jak i zmiany kolorów, co znacznie poprawia szybkość działania aplikacji.
Dodawanie zarządzania stanem rozmów
Najpierw dodamy dostawcę stanu, aby śledzić, czy aplikacja obsługuje obecnie odpowiedź strumieniową. Zaktualizuj plik lib/services/gemini_chat_service.dart
:
lib/services/gemini_chat_service.dart
import 'dart:async';
import 'package:colorist_ui/colorist_ui.dart';
import 'package:firebase_vertexai/firebase_vertexai.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import '../providers/gemini.dart';
import 'gemini_tools.dart';
part 'gemini_chat_service.g.dart';
final conversationStateProvider = StateProvider( // Add from here...
(ref) => ConversationState.idle,
); // To here.
class GeminiChatService {
GeminiChatService(this.ref);
final Ref ref;
Future<void> sendMessage(String message) async {
final chatSession = await ref.read(chatSessionProvider.future);
final conversationState = ref.read(conversationStateProvider); // Add this line
final chatStateNotifier = ref.read(chatStateNotifierProvider.notifier);
final logStateNotifier = ref.read(logStateNotifierProvider.notifier);
if (conversationState == ConversationState.busy) { // Add from here...
logStateNotifier.logWarning(
"Can't send a message while a conversation is in progress",
);
throw Exception(
"Can't send a message while a conversation is in progress",
);
}
final conversationStateNotifier = ref.read(
conversationStateProvider.notifier,
);
conversationStateNotifier.state = ConversationState.busy; // To here.
chatStateNotifier.addUserMessage(message);
logStateNotifier.logUserText(message);
final llmMessage = chatStateNotifier.createLlmMessage();
try { // Modify from here...
final responseStream = chatSession.sendMessageStream(
Content.text(message),
);
await for (final block in responseStream) {
await _processBlock(block, llmMessage.id);
} // To here.
} catch (e, st) {
logStateNotifier.logError(e, st: st);
chatStateNotifier.appendToMessage(
llmMessage.id,
"\nI'm sorry, I encountered an error processing your request. "
"Please try again.",
);
} finally {
chatStateNotifier.finalizeMessage(llmMessage.id);
conversationStateNotifier.state = ConversationState.idle; // Add this line.
}
}
Future<void> _processBlock( // Add from here...
GenerateContentResponse block,
String llmMessageId,
) async {
final chatSession = await ref.read(chatSessionProvider.future);
final chatStateNotifier = ref.read(chatStateNotifierProvider.notifier);
final logStateNotifier = ref.read(logStateNotifierProvider.notifier);
final blockText = block.text;
if (blockText != null) {
logStateNotifier.logLlmText(blockText);
chatStateNotifier.appendToMessage(llmMessageId, blockText);
}
if (block.functionCalls.isNotEmpty) {
final geminiTools = ref.read(geminiToolsProvider);
final responseStream = chatSession.sendMessageStream(
Content.functionResponses([
for (final functionCall in block.functionCalls)
FunctionResponse(
functionCall.name,
geminiTools.handleFunctionCall(
functionCall.name,
functionCall.args,
),
),
]),
);
await for (final response in responseStream) {
final responseText = response.text;
if (responseText != null) {
logStateNotifier.logLlmText(responseText);
chatStateNotifier.appendToMessage(llmMessageId, responseText);
}
}
}
} // To here.
}
@riverpod
GeminiChatService geminiChatService(Ref ref) => GeminiChatService(ref);
Informacje o wdrażaniu strumieniowania
Zobaczmy, co robi ten kod:
- Śledzenie stanu rozmowy:
conversationStateProvider
śledzi, czy aplikacja przetwarza obecnie odpowiedź.- Podczas przetwarzania stan zmienia się z
idle
→busy
, a potem z powrotem naidle
- Zapobiega to wysyłaniu wielu żądań jednocześnie, które mogłyby się ze sobą sprzeczać.
- Inicjowanie transmisji:
sendMessageStream()
zwraca strumień fragmentów odpowiedzi zamiast obiektu Future z pełną odpowiedzią.- Każdy fragment może zawierać tekst, wywołania funkcji lub jedno i drugie.
- Procesowanie progresywne:
await for
przetwarza każdy fragment w czasie rzeczywistym w miarę jego otrzymywania- Tekst jest natychmiast dołączany do interfejsu, tworząc efekt strumieniowego przesyłania.
- Wywołania funkcji są wykonywane natychmiast po wykryciu.
- Obsługa wywołania funkcji:
- Gdy w bloku zostanie wykryty wywołanie funkcji, zostanie ono natychmiast wykonane.
- Wyniki są wysyłane z powrotem do LLM za pomocą kolejnego wywołania strumieniowego.
- Odpowiedź LLM na te wyniki jest również przetwarzana w trybie strumieniowym.
- Obsługa błędów i czyszczenie:
- Tagi
try
/catch
zapewniają niezawodną obsługę błędów - Blokada
finally
zapewnia prawidłowe zresetowanie stanu rozmowy - wiadomość jest zawsze zamykana, nawet jeśli wystąpią błędy;
- Tagi
Dzięki temu strumieniowanie jest płynne i stabilne, a rozmowa nie jest przerywana.
Aktualizacja ekranu głównego w celu połączenia stanu rozmowy
Zmodyfikuj plik lib/main.dart
, aby przekazać stan rozmowy na ekran główny:
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),
),
);
}
}
Kluczowa zmiana polega na przekazaniu wartości conversationState
do widżetu MainScreen
. MainScreen
(dostarcza go pakiet colorist_ui
) użyje tego stanu, aby wyłączyć wprowadzanie tekstu podczas przetwarzania odpowiedzi.
Dzięki temu użytkownicy mogą korzystać z jednego spójnego interfejsu, który odzwierciedla bieżący stan rozmowy.
Generowanie kodu Riverpod
Uruchom polecenie build runner, aby wygenerować kod Riverpod:
dart run build_runner build --delete-conflicting-outputs
Uruchamianie i testowanie odpowiedzi strumieniowych
Uruchamianie aplikacji:
flutter run -d DEVICE
Teraz przetestuj zachowanie strumieniowego przesyłania danych z różnymi opisami kolorów. Wypróbuj takie opisy:
- „Pokaż ciemnozielony kolor oceanu o zmierzchu”
- „Chcę zobaczyć żywy koral, który przypomina mi tropikalne kwiaty”
- „Utwórz przytłumiony oliwkowy kolor, jak w starych mundurach wojskowych”.
Szczegóły procesu strumieniowania
Zobaczmy, co dokładnie dzieje się podczas przesyłania strumieniowego odpowiedzi:
Nawiązywanie połączenia
Gdy zadzwonisz na numer sendMessageStream()
:
- Aplikacja nawiązuje połączenie z usługą Vertex AI
- Żądanie użytkownika jest wysyłane do usługi
- Serwer rozpoczyna przetwarzanie żądania
- Połączenie strumienia pozostaje otwarte i gotowe do przesyłania fragmentów
Przesyłanie fragmentów
Gdy Gemini generuje treści, fragmenty są wysyłane przez strumień:
- Serwer wysyła fragmenty tekstu w miarę ich generowania (zwykle kilka słów lub zdań).
- Gdy Gemini zdecyduje się wywołać funkcję, wyśle informacje o tym wywołaniu.
- Po wywołaniu funkcji mogą występować dodatkowe fragmenty tekstu.
- Transmisja trwa do momentu ukończenia generowania.
Progresywne przetwarzanie
Aplikacja przetwarza każdy fragment osobno:
- Każdy fragment tekstu jest dołączany do istniejącej odpowiedzi.
- Wywołania funkcji są wykonywane natychmiast po wykryciu.
- Interfejs użytkownika aktualizuje się w czasie rzeczywistym, wyświetlając wyniki funkcji i tekst.
- Stan jest śledzony, aby pokazać, że odpowiedź jest nadal przesyłana strumieniowo
Zakończenie transmisji
Po zakończeniu generowania:
- transmisja jest zamykana przez serwer;
- pętla
await for
kończy się naturalnie, - Wiadomość została oznaczona jako zakończona
- Stan rozmowy wraca do stanu nieaktywnej
- Interfejs użytkownika zmienia się, aby odzwierciedlić stan ukończonego zadania.
Porównanie strumieniowania z niestrumieniowaniem
Aby lepiej zrozumieć zalety strumieniowego przesyłania danych, porównaj strumieniowe przesyłanie danych z metodami bez strumieniowego przesyłania danych:
Aspekt | Bez strumieniowania | Streaming |
Odczuwalne opóźnienie | Użytkownik nie widzi nic, dopóki nie pojawi się pełna odpowiedź | Użytkownik widzi pierwsze słowa w ciągu kilku milisekund |
Wrażenia użytkownika | Długie oczekiwanie, po którym nagle pojawia się tekst | Naturalny, progresywny wygląd tekstu |
Zarządzanie stanem | Prostsze (wiadomości są oczekujące lub ukończone) | bardziej złożone (wiadomości mogą być w stanie strumieniowania); |
Wykonywanie funkcji | Występuje tylko po pełnej odpowiedzi | Występuje podczas generowania odpowiedzi |
Złożoność implementacji | Prostsza implementacja | Wymaga dodatkowego zarządzania stanem |
Odzyskiwanie danych po wystąpieniu błędu | Odpowiedź typu „wszystko albo nic” | Odpowiedzi częściowe mogą być przydatne |
Złożoność kodu | Mniej złożone | bardziej złożone ze względu na obsługę strumienia; |
W przypadku aplikacji takiej jak Colorist korzyści dla użytkownika wynikające ze streamingu przeważają nad złożonością implementacji, zwłaszcza w przypadku interpretacji kolorów, które mogą generować się przez kilka sekund.
Sprawdzone metody dotyczące wrażeń użytkowników podczas strumieniowego przesyłania danych
Podczas wdrażania strumieniowego przesyłania danych w własnych aplikacjach LLM weź pod uwagę te sprawdzone metody:
- Jasne wizualne wskazówki: zawsze podawaj wyraźne wizualne wskazówki, które odróżniają strumieniowanie od pełnych wiadomości.
- Blokowanie danych wejściowych: wyłączanie danych wejściowych użytkownika podczas przesyłania strumieniowego, aby zapobiec nakładaniu się wielu żądań.
- Naprawa błędów: zaprojektuj interfejs użytkownika tak, aby można było płynnie przywrócić streaming w przypadku przerwania.
- Przejścia między stanami: zapewnij płynne przejścia między stanami bezczynności, przesyłania strumieniowego i zakończonego.
- Wizualizacja postępu: rozważ użycie subtelnych animacji lub wskaźników, które pokazują aktywne przetwarzanie.
- Opcje anulowania: w kompletnej aplikacji podaj sposoby anulowania trwających generacji.
- Integracja wyników funkcji: zaprojektuj interfejs użytkownika tak, aby obsługiwał wyniki funkcji wyświetlane w trakcie wykonywania obliczeń.
- Optymalizacja wydajności: minimalizowanie odtwarzania interfejsu podczas szybkich aktualizacji strumienia.
Pakiet colorist_ui
implementuje wiele z tych sprawdzonych metod, ale są one ważne w przypadku każdej implementacji LLM na potrzeby strumieniowego przesyłania danych.
Co dalej?
W następnym kroku zaimplementujesz synchronizację LLM, powiadamiając Gemini, gdy użytkownicy wybiorą kolory z historii. Dzięki temu użytkownicy będą mogli korzystać z bardziej spójnego interfejsu, w którym LLM będzie wiedzieć o zmianach stanu aplikacji wprowadzonych przez użytkownika.
Rozwiązywanie problemów
Problemy z przetwarzaniem strumieniowym
Jeśli napotkasz problemy z przetwarzaniem strumienia:
- Objawy: odpowiedzi częściowe, brakujący tekst lub nagłe zakończenie transmisji.
- Rozwiązanie: sprawdź połączenie z siecią i upewnij się, że w kodzie występują odpowiednie wzorce async/await.
- Diagnoza: sprawdź panel logów pod kątem komunikatów o błędach lub ostrzeżeń związanych z przetwarzaniem strumienia.
- Rozwiązanie: sprawdź, czy wszystkie przetwarzanie strumieni korzysta z prawidłowego przetwarzania błędów za pomocą bloków
try
/catch
.
Brakujące wywołania funkcji
Jeśli wywołania funkcji nie są wykrywane w strumieniach:
- Objawy: tekst jest widoczny, ale kolory się nie aktualizują lub w logu nie ma wywołań funkcji.
- Rozwiązanie: sprawdź instrukcje dotyczące korzystania z wywołań funkcji wyświetlane przez system.
- Diagnoza: sprawdź w panelu logów, czy funkcje są wywoływane.
- Rozwiązanie: dostosuj komunikat systemowy, aby wyraźniej wskazać LLM, że ma użyć narzędzia
set_color
.
Ogólna obsługa błędów
W przypadku innych problemów:
- Krok 1. Sprawdź panel dziennika pod kątem komunikatów o błędach.
- Krok 2. Sprawdź połączenie Vertex AI w Firebase
- Krok 3. Upewnij się, że cały kod wygenerowany przez Riverpod jest aktualny.
- Krok 4. Sprawdź, czy w implementacji strumieniowego przesyłania danych nie brakuje instrukcji await.
Kluczowe pojęcia
- Implementacja strumieniowych odpowiedzi za pomocą interfejsu Gemini API w celu uzyskania bardziej responsywnego UX
- Zarządzanie stanem rozmowy w celu prawidłowego obsługiwania interakcji strumieniowych
- Przetwarzanie połączeń tekstowych i funkcyjnych w czasie rzeczywistym.
- tworzenie elastycznych interfejsów użytkownika, które są aktualizowane stopniowo podczas przesyłania strumieniowego;
- Obsługa strumieni równoległych za pomocą odpowiednich wzorów asynchronicznych
- wyświetlanie odpowiednich sygnałów wizualnych podczas przesyłania strumieniowego odpowiedzi;
Dzięki wdrożeniu strumieniowego przesyłania danych znacznie poprawiłeś wrażenia użytkowników aplikacji Colorist, tworząc bardziej responsywny i atrakcyjny interfejs, który sprawia wrażenie naprawdę rozmownego.
8. Synchronizacja kontekstu LLM
W tym dodatkowym kroku zaimplementujesz synchronizację kontekstu LLM, powiadamiając Gemini, gdy użytkownicy wybiorą kolory z historii. Dzięki temu użytkownik ma bardziej spójne wrażenia, ponieważ LLM wie, jakie działania wykonuje w interfejsie, a nie tylko jakie wysyła wyraźne komunikaty.
Omówienie tego etapu
- Tworzenie synchronizacji kontekstu LLM między interfejsem użytkownika a modelem LLM
- serializacja zdarzeń interfejsu użytkownika w kontekście zrozumiałym dla dużego modelu językowego;
- Aktualizowanie kontekstu rozmowy na podstawie działań użytkownika
- Tworzenie spójnego doświadczenia w różnych metodach interakcji
- Ulepszanie świadomości kontekstu LLM poza jawnymi wiadomościami na czacie
Synchronizacja kontekstu LLM
Tradycyjne boty odpowiadają tylko na wyraźne wiadomości od użytkowników, co powoduje przerwę w komunikacji, gdy użytkownicy wchodzą w interakcję z aplikacją w inny sposób. Synchronizacja kontekstu w LLM rozwiązuje to ograniczenie:
Dlaczego synchronizacja kontekstu LLM jest ważna
Gdy użytkownicy wchodzą w interakcję z Twoją aplikacją za pomocą elementów interfejsu użytkownika (np. wybierają kolor z historii), LLM nie ma możliwości sprawdzenia, co się stało, chyba że wyraźnie mu to powiesz. Synchronizacja kontekstu LLM:
- Utrzymuje kontekst: informuje LLM o wszystkich istotnych działaniach użytkownika.
- Tworzy spójność: zapewnia spójne działanie, w którym LLM reaguje na interakcje z interfejsem.
- Ulepsza inteligencję: umożliwia LLM odpowiednią reakcję na wszystkie działania użytkownika.
- Zwiększa wygodę użytkowników: sprawia, że cała aplikacja wydaje się bardziej zintegrowana i szybka.
- Zmniejsza wysiłek użytkownika: eliminuje konieczność ręcznego wyjaśniania działań w interfejsie.
Gdy użytkownik wybierze kolor z historii w aplikacji Colorist, chcesz, aby Gemini potwierdził to działanie i inteligentnie skomentował wybrany kolor, zachowując iluzję płynnego, świadomego asystenta.
Aktualizacja usługi Gemini Chat w celu wyświetlania powiadomień o wyborze koloru
Najpierw dodaj do metody GeminiChatService
metodę, która powiadomi usługę LLM, gdy użytkownik wybierze kolor z historii. Zaktualizuj plik lib/services/gemini_chat_service.dart
:
lib/services/gemini_chat_service.dart
import 'dart:async';
import 'dart:convert'; // Add this import
import 'package:colorist_ui/colorist_ui.dart';
import 'package:firebase_vertexai/firebase_vertexai.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import '../providers/gemini.dart';
import 'gemini_tools.dart';
part 'gemini_chat_service.g.dart';
final conversationStateProvider = StateProvider(
(ref) => ConversationState.idle,
);
class GeminiChatService {
GeminiChatService(this.ref);
final Ref ref;
Future<void> notifyColorSelection(ColorData color) => sendMessage( // Add from here...
'User selected color from history: ${json.encode(color.toLLMContextMap())}',
); // To here.
Future<void> sendMessage(String message) async {
final chatSession = await ref.read(chatSessionProvider.future);
final conversationState = ref.read(conversationStateProvider);
final chatStateNotifier = ref.read(chatStateNotifierProvider.notifier);
final logStateNotifier = ref.read(logStateNotifierProvider.notifier);
if (conversationState == ConversationState.busy) {
logStateNotifier.logWarning(
"Can't send a message while a conversation is in progress",
);
throw Exception(
"Can't send a message while a conversation is in progress",
);
}
final conversationStateNotifier = ref.read(
conversationStateProvider.notifier,
);
conversationStateNotifier.state = ConversationState.busy;
chatStateNotifier.addUserMessage(message);
logStateNotifier.logUserText(message);
final llmMessage = chatStateNotifier.createLlmMessage();
try {
final responseStream = chatSession.sendMessageStream(
Content.text(message),
);
await for (final block in responseStream) {
await _processBlock(block, llmMessage.id);
}
} catch (e, st) {
logStateNotifier.logError(e, st: st);
chatStateNotifier.appendToMessage(
llmMessage.id,
"\nI'm sorry, I encountered an error processing your request. "
"Please try again.",
);
} finally {
chatStateNotifier.finalizeMessage(llmMessage.id);
conversationStateNotifier.state = ConversationState.idle;
}
}
Future<void> _processBlock(
GenerateContentResponse block,
String llmMessageId,
) async {
final chatSession = await ref.read(chatSessionProvider.future);
final chatStateNotifier = ref.read(chatStateNotifierProvider.notifier);
final logStateNotifier = ref.read(logStateNotifierProvider.notifier);
final blockText = block.text;
if (blockText != null) {
logStateNotifier.logLlmText(blockText);
chatStateNotifier.appendToMessage(llmMessageId, blockText);
}
if (block.functionCalls.isNotEmpty) {
final geminiTools = ref.read(geminiToolsProvider);
final responseStream = chatSession.sendMessageStream(
Content.functionResponses([
for (final functionCall in block.functionCalls)
FunctionResponse(
functionCall.name,
geminiTools.handleFunctionCall(
functionCall.name,
functionCall.args,
),
),
]),
);
await for (final response in responseStream) {
final responseText = response.text;
if (responseText != null) {
logStateNotifier.logLlmText(responseText);
chatStateNotifier.appendToMessage(llmMessageId, responseText);
}
}
}
}
}
@riverpod
GeminiChatService geminiChatService(Ref ref) => GeminiChatService(ref);
Najważniejszym dodatkiem jest metoda notifyColorSelection
, która:
- Przyjmuje obiekt
ColorData
reprezentujący wybrany kolor. - Koduje go w formacie JSON, który można umieścić w wiadomości.
- Wysyła do LLM wiadomość w specjalnym formacie, która wskazuje na wybór użytkownika
- Ponowne użycie istniejącej metody
sendMessage
do obsługi powiadomienia
Dzięki temu unikniesz duplikowania, wykorzystując istniejącą infrastrukturę obsługi wiadomości.
Zaktualizuj aplikację główną, aby połączyć powiadomienia o wyborze koloru
Teraz zmodyfikuj plik lib/main.dart
, aby przekazać funkcję powiadomienia o wyborze koloru na ekran główny:
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),
),
);
}
}
Kluczowa zmiana to dodanie wywołania zwrotnego notifyColorSelection
, które łączy zdarzenie interfejsu użytkownika (wybieranie koloru z historii) z systemem powiadomień LLM.
Aktualizacja komunikatu o aktualizacji systemu
Teraz musisz zaktualizować prompt systemu, aby poinformować LLM, jak ma reagować na powiadomienia o wyborze koloru. Zmień plik 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
Najważniejsze nowe informacje znajdują się w sekcji „Gdy użytkownicy wybierają historyczne kolory”, która:
- Wyjaśnienie pojęcia powiadomień o wyborach historii LLM
- Przykłady wyglądu tych powiadomień
- Pokazuje przykład odpowiedniej odpowiedzi
- Ustawia oczekiwania dotyczące potwierdzenia wyboru i komentarza do koloru
Pomaga to modelowi LLM zrozumieć, jak odpowiednio reagować na te specjalne wiadomości.
Generowanie kodu Riverpod
Uruchom polecenie build runner, aby wygenerować kod Riverpod:
dart run build_runner build --delete-conflicting-outputs
Uruchamianie i testowanie synchronizacji kontekstu LLM
Uruchamianie aplikacji:
flutter run -d DEVICE
Testowanie synchronizacji kontekstu LLM obejmuje:
- Najpierw wygeneruj kilka kolorów, opisując je na czacie.
- „Pokaż mi żywy fiolet”
- „Chcę zielony leśny”
- „Daj mi jaskrawoczerwony”.
- Następnie kliknij jedną z miniatur kolorów na pasku historii.
Należy pamiętać o tym, że:
- Wybrany kolor pojawi się na głównym wyświetlaczu.
- W czacie pojawia się wiadomość od użytkownika z informacją o wybranym kolorze
- Model LLM odpowiada, potwierdzając wybór i komentując kolor
- Cała interakcja jest naturalna i spójna
Dzięki temu LLM jest świadomy i odpowiednio reaguje na wiadomości bezpośrednie oraz interakcje z interfejsem.
Jak działa synchronizacja kontekstu w modelu LLM
Przyjrzyjmy się szczegółom technicznym tej synchronizacji:
Data Flow
- Działanie użytkownika: użytkownik klika kolor na pasku historii.
- Zdarzenie w interfejsie: widżet
MainScreen
wykrywa tę pozycję - Wykonanie wywołania zwrotnego:
notifyColorSelection
wywołanie zwrotne jest wywoływane. - Tworzenie wiadomości: tworzona jest wiadomość w specjalnym formacie z danymi kolorów.
- Przetwarzanie LLM: wiadomość jest wysyłana do Gemini, który rozpoznaje format.
- Odpowiedź kontekstowa: Gemini odpowiada odpowiednio w oparciu o prompt systemu.
- Aktualizacja interfejsu: odpowiedź pojawia się na czacie, co zapewnia spójność.
Serializacja danych
Kluczowym aspektem tego podejścia jest sposób serializacji danych kolorów:
'User selected color from history: ${json.encode(color.toLLMContextMap())}'
Metoda toLLMContextMap()
(dostępna w pakiecie colorist_ui
) konwertuje obiekt ColorData
na mapę z kluczowymi właściwościami, które LLM może zrozumieć. Obejmuje to zazwyczaj:
- wartości RGB (czerwony, zielony, niebieski);
- Reprezentacja kodu szesnastkowego
- nazwa lub opis powiązany z kolorem;
Dzięki spójnemu formatowaniu tych danych i ich dołączeniu do wiadomości masz pewność, że LLM ma wszystkie informacje potrzebne do udzielenia odpowiedniej odpowiedzi.
Szerokie zastosowanie synchronizacji kontekstu w modelach LLM
Ten schemat powiadamiania LLM o zdarzeniach w interfejsie ma wiele zastosowań poza wyborem koloru:
Inne zastosowania
- Zmiany filtra: powiadomienie usługi LLM, gdy użytkownicy zastosują filtry do danych.
- Zdarzenia związane z nawigacją: informują LLM, gdy użytkownicy przechodzą do różnych sekcji.
- Zmiany w wyborze: aktualizuj LLM, gdy użytkownicy wybierają elementy z list lub siatek.
- Aktualizacje preferencji: informuj LLM, gdy użytkownicy zmieniają ustawienia lub preferencje.
- Manipulowanie danymi: powiadomienie LLM, gdy użytkownicy dodają, edytują lub usuwają dane.
W każdym przypadku schemat pozostaje taki sam:
- Wykrywanie zdarzenia interfejsu
- serializować odpowiednie dane;
- Wysyłanie do LLM powiadomienia o specjalnym formacie
- Poproś LLM o odpowiednią odpowiedź za pomocą prompta systemowego.
Sprawdzone metody synchronizacji kontekstu w modelu LLM
Na podstawie Twojej implementacji podajemy kilka sprawdzonych metod skutecznej synchronizacji kontekstu LLM:
1. Spójny format
Używaj spójnego formatu powiadomień, aby LLM mógł je łatwo rozpoznawać:
"User [action] [object]: [structured data]"
2. Szczegółowy kontekst
Dołącz do powiadomień wystarczającą ilość szczegółów, aby LLM mógł mądrze na nie reagować. W przypadku kolorów oznacza to wartości RGB, kody heksadecymalne i inne odpowiednie właściwości.
3. Wyczyść instrukcje
W prompcie systemowym podaj wyraźne instrukcje dotyczące postępowania z powiadomieniami, najlepiej z przykładami.
4. Naturalna integracja
Zaprojektuj powiadomienia tak, aby pojawiały się w naturalny sposób w trakcie rozmowy, a nie jako techniczne przerwy.
5. Powiadomienie selektywne
Informuj LLM tylko o działaniach związanych z rozmową. Nie wszystkie zdarzenia interfejsu muszą być przekazywane.
Rozwiązywanie problemów
Problemy dotyczące powiadomień
Jeśli model LLM nie reaguje prawidłowo na wybrane kolory:
- Sprawdź, czy format powiadomienia jest zgodny z tym, co zostało opisane w promptzie systemowym.
- Sprawdź, czy dane kolorów są prawidłowo serializowane
- Upewnij się, że komunikat systemu zawiera wyraźne instrukcje dotyczące wyborów.
- Sprawdzanie błędów w usłudze czatu podczas wysyłania powiadomień
Zarządzanie kontekstem
Jeśli LLM wydaje się tracić kontekst:
- Sprawdź, czy sesja czatu jest prawidłowo obsługiwana
- Sprawdź, czy stany konwersacji przechodzą prawidłowo
- Upewnij się, że powiadomienia są wysyłane w ramach tej samej sesji czatu.
Problemy ogólne
Problemy ogólne:
- Sprawdź dzienniki pod kątem błędów lub ostrzeżeń.
- Sprawdzanie połączeń Vertex AI w Firebase
- Sprawdź, czy w parametrach funkcji nie ma niezgodności typów.
- Upewnij się, że cały kod wygenerowany przez Riverpod jest aktualny
Kluczowe pojęcia
- Tworzenie synchronizacji kontekstu LLM między interfejsem użytkownika a modelem LLM
- Serializacja zdarzeń interfejsu użytkownika w kontekście zgodnym z LLM
- Kierowanie działania LLM w przypadku różnych wzorców interakcji
- Tworzenie spójnego interfejsu w przypadku interakcji z wiadomościami i bez wiadomości
- Zwiększanie świadomości LLM na temat szerszego stanu aplikacji
Dzięki wdrożeniu synchronizacji kontekstu LLM stworzysz naprawdę zintegrowane środowisko, w którym LLM będzie działać jak świadomy, responsywny asystent, a nie tylko generator tekstu. Ten wzór można zastosować w niezliczonych innych aplikacjach, aby tworzyć bardziej naturalne i intuicyjne interfejsy oparte na AI.
9. Gratulacje!
Udało Ci się ukończyć Colorist Codelab. 🎉
Co zostało utworzone
Utworzyłeś/-aś w Flutterze w pełni funkcjonalną aplikację, która integruje interfejs Gemini API od Google w celu interpretowania opisów kolorów w języku naturalnym. Aplikacja może teraz:
- Przetwarzanie opisów w języku naturalnym, takich jak „pomarańczowy jak zachodzące słońce” czy „głęboki błękit oceanu”.
- Użyj Gemini do inteligentnego przekształcania tych opisów w wartości RGB
- wyświetlać interpretowane kolory w czasie rzeczywistym dzięki przesyłaniu strumieniowemu odpowiedzi;
- obsługa interakcji z użytkownikami za pomocą czatu i elementów interfejsu;
- Utrzymywanie świadomości kontekstowej w różnych metodach interakcji
Co dalej
Teraz, gdy znasz już podstawy integracji Gemini z Flutterem, możesz kontynuować naukę:
Ulepszanie aplikacji Colorist
- Palety kolorów: dodaj funkcję generowania schematów kolorów uzupełniających lub pasujących do siebie.
- Głosowe wprowadzanie tekstu: integracja rozpoznawania mowy w celu tworzenia ustnych opisów kolorów.
- Zarządzanie historią: dodawanie opcji umożliwiających nazywanie, porządkowanie i eksportowanie zestawów kolorów
- Niestandardowe prompty: stwórz interfejs, który pozwoli użytkownikom dostosować prompty systemowe.
- Zaawansowane statystyki: śledzenie, które opisy działają najlepiej lub powodują problemy
Więcej funkcji Gemini
- Wejścia multimodalne: dodaj wejścia z obrazem, aby wyodrębnić kolory z zdjęć.
- Generowanie treści: korzystaj z Gemini do generowania treści związanych z kolorami, takich jak opisy czy historie.
- Ulepszenia wywoływania funkcji: możesz tworzyć bardziej złożone integracje narzędzi z wieloma funkcjami.
- Ustawienia bezpieczeństwa: dowiedz się więcej o różnych ustawieniach bezpieczeństwa i ich wpływie na odpowiedzi.
Stosowanie tych wzorców w innych domenach
- Analiza dokumentów: tworzenie aplikacji, które mogą interpretować i analizować dokumenty
- Pomoc w kreatywnym pisaniu: tworzenie narzędzi do pisania z użyciem sugestii opartych na LLM.
- Automatyzacja zadań: projektowanie aplikacji, które przekształcają naturalny język w automatyczne zadania.
- Aplikacje oparte na wiedzy: tworzenie systemów eksperckich w konkretnych domenach.
Zasoby
Oto przydatne materiały, które pomogą Ci kontynuować naukę:
oficjalna dokumentacja,
- Dokumentacja Vertex AI w Firebase
- Dokumentacja Fluttera
- Dokumentacja Riverpod
- Dokumentacja interfejsu Gemini API
Kurs i przewodnik dotyczący promptów
Społeczność
Prześlij opinię
Chętnie poznamy Twoje wrażenia z korzystania z tego Codelab. Przekaż opinię za pomocą:
Dziękujemy za ukończenie tego Codelab. Mamy nadzieję, że będziesz dalej odkrywać ekscytujące możliwości na styku Fluttera i AI.