Mem-build aplikasi Flutter yang didukung Gemini

Mem-build aplikasi Flutter yang didukung Gemini

Tentang codelab ini

subjectTerakhir diperbarui Mei 19, 2025
account_circleDitulis oleh Brett Morgan

1. Mem-build aplikasi Flutter yang didukung Gemini

Yang akan Anda build

Dalam codelab ini, Anda akan mem-build Colorist - aplikasi Flutter interaktif yang menghadirkan kecanggihan Gemini API langsung ke aplikasi Flutter Anda. Pernah ingin mengizinkan pengguna mengontrol aplikasi Anda melalui bahasa alami, tetapi tidak tahu harus memulai dari mana? Codelab ini menunjukkan caranya.

Colorist memungkinkan pengguna mendeskripsikan warna dalam bahasa alami (seperti "oranye matahari terbenam" atau "biru laut dalam"), dan aplikasi:

  • Memproses deskripsi ini menggunakan Gemini API Google
  • Menafsirkan deskripsi menjadi nilai warna RGB yang akurat
  • Menampilkan warna di layar secara real time
  • Memberikan detail warna teknis dan konteks menarik tentang warna
  • Mempertahankan histori warna yang baru dibuat

Screenshot Aplikasi Colorist yang menampilkan tampilan warna dan antarmuka chat

Aplikasi ini memiliki antarmuka layar terpisah dengan area tampilan warna dan sistem chat interaktif di satu sisi, serta panel log mendetail yang menampilkan interaksi LLM mentah di sisi lain. Log ini memungkinkan Anda lebih memahami cara kerja integrasi LLM di balik layar.

Mengapa hal ini penting bagi developer Flutter

LLM merevolusi cara pengguna berinteraksi dengan aplikasi, tetapi mengintegrasikannya secara efektif ke dalam aplikasi seluler dan desktop menghadirkan tantangan unik. Codelab ini mengajarkan pola praktis yang lebih dari sekadar panggilan API mentah.

Perjalanan Anda dalam belajar

Codelab ini akan memandu Anda melalui proses pembuatan Colorist langkah demi langkah:

  1. Penyiapan project - Anda akan memulai dengan struktur aplikasi Flutter dasar dan paket colorist_ui
  2. Integrasi Gemini dasar - Hubungkan aplikasi Anda ke Vertex AI di Firebase dan terapkan komunikasi LLM sederhana
  3. Perintah yang efektif - Buat perintah sistem yang memandu LLM untuk memahami deskripsi warna
  4. Deklarasi fungsi - Menentukan alat yang dapat digunakan LLM untuk menetapkan warna di aplikasi Anda
  5. Penanganan alat - Memproses panggilan fungsi dari LLM dan menghubungkannya ke status aplikasi Anda
  6. Respons streaming - Meningkatkan pengalaman pengguna dengan respons LLM streaming real-time
  7. Sinkronisasi Konteks LLM - Membuat pengalaman yang kohesif dengan memberi tahu LLM tentang tindakan pengguna

Yang akan Anda pelajari

  • Mengonfigurasi Vertex AI in Firebase untuk aplikasi Flutter
  • Membuat perintah sistem yang efektif untuk memandu perilaku LLM
  • Mengimplementasikan deklarasi fungsi yang menjembatani bahasa alami dan fitur aplikasi
  • Memproses respons streaming untuk pengalaman pengguna yang responsif
  • Menyinkronkan status antara peristiwa UI dan LLM
  • Mengelola status percakapan LLM menggunakan Riverpod
  • Menangani error dengan baik di aplikasi yang didukung LLM

Pratinjau kode: Contoh yang akan Anda terapkan

Berikut adalah sekilas deklarasi fungsi yang akan Anda buat untuk memungkinkan LLM menetapkan warna di aplikasi Anda:

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

Ringkasan video codelab ini

Tonton Craig Labenz dan Andrew Brogdon membahas codelab ini di episode Observable Flutter #59:

Prasyarat

Untuk mendapatkan hasil maksimal dari codelab ini, Anda harus memiliki:

  • Pengalaman pengembangan Flutter - Pemahaman tentang dasar-dasar Flutter dan sintaksis Dart
  • Pengetahuan pemrograman asinkron - Pemahaman tentang Future, async/await, dan streaming
  • Akun Firebase - Anda memerlukan Akun Google untuk menyiapkan Firebase
  • Project Firebase dengan penagihan diaktifkan - Vertex AI in Firebase memerlukan akun penagihan

Mari kita mulai mem-build aplikasi Flutter pertama Anda yang didukung LLM.

2. Penyiapan project & layanan echo

Pada langkah pertama ini, Anda akan menyiapkan struktur project dan menerapkan layanan echo sederhana yang nantinya akan diganti dengan integrasi Gemini API. Tindakan ini akan menetapkan arsitektur aplikasi dan memastikan UI Anda berfungsi dengan benar sebelum menambahkan kompleksitas panggilan LLM.

Yang akan Anda pelajari di langkah ini

  • Menyiapkan project Flutter dengan dependensi yang diperlukan
  • Menggunakan paket colorist_ui untuk komponen UI
  • Menerapkan layanan pesan echo dan menghubungkannya ke UI

Catatan penting tentang harga

Membuat project Flutter baru

Mulailah dengan membuat project Flutter baru dengan perintah berikut:

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

Flag -e menunjukkan bahwa Anda menginginkan project kosong tanpa aplikasi counter default. Aplikasi ini dirancang agar berfungsi di desktop, perangkat seluler, dan web. Namun, flutterfire saat ini tidak mendukung Linux.

Menambahkan dependensi

Buka direktori project Anda dan tambahkan dependensi yang diperlukan:

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

Tindakan ini akan menambahkan paket kunci berikut:

  • colorist_ui: Paket kustom yang menyediakan komponen UI untuk aplikasi Colorist
  • flutter_riverpod dan riverpod_annotation: Untuk pengelolaan status
  • logging: Untuk logging terstruktur
  • Dependensi pengembangan untuk pembuatan kode dan linting

pubspec.yaml Anda akan terlihat seperti ini:

pubspec.yaml

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

environment:
  sdk: ^3.8.0

dependencies:
  flutter:
    sdk: flutter
  colorist_ui: ^0.2.3
  flutter_riverpod: ^2.6.1
  riverpod_annotation: ^2.6.1

dev_dependencies:
  flutter_test:
    sdk: flutter
  flutter_lints: ^5.0.0
  build_runner: ^2.4.15
  riverpod_generator: ^2.6.5
  riverpod_lint: ^2.6.5
  json_serializable: ^6.9.5
  custom_lint: ^0.7.5

flutter:
  uses-material-design: true

Mengonfigurasi opsi analisis

Tambahkan custom_lint ke file analysis_options.yaml di root project Anda:

include: package:flutter_lints/flutter.yaml

analyzer:
  plugins:
    - custom_lint

Konfigurasi ini memungkinkan lint khusus Riverpod untuk membantu mempertahankan kualitas kode.

Menerapkan file main.dart

Ganti konten lib/main.dart dengan konten berikut:

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

Tindakan ini akan menyiapkan aplikasi Flutter yang mengimplementasikan layanan echo sederhana yang meniru perilaku LLM dengan hanya menampilkan pesan pengguna.

Memahami arsitektur

Mari luangkan waktu untuk memahami arsitektur aplikasi colorist:

Paket colorist_ui

Paket colorist_ui menyediakan komponen UI bawaan dan alat pengelolaan status:

  1. MainScreen: Komponen UI utama yang menampilkan:
    • Tata letak layar terpisah di desktop (area interaksi dan panel log)
    • Antarmuka dengan tab di perangkat seluler
    • Tampilan warna, antarmuka chat, dan thumbnail histori
  2. Pengelolaan Status: Aplikasi menggunakan beberapa notifikasi status:
    • ChatStateNotifier: Mengelola pesan chat
    • ColorStateNotifier: Mengelola warna dan histori saat ini
    • LogStateNotifier: Mengelola entri log untuk proses debug
  3. Penanganan Pesan: Aplikasi menggunakan model pesan dengan status yang berbeda:
    • Pesan pengguna: Dimasukkan oleh pengguna
    • Pesan LLM: Dibuat oleh LLM (atau layanan echo Anda untuk saat ini)
    • MessageState: Melacak apakah pesan LLM sudah selesai atau masih di-streaming

Arsitektur aplikasi

Aplikasi mengikuti arsitektur berikut:

  1. Lapisan UI: Disediakan oleh paket colorist_ui
  2. Pengelolaan Status: Menggunakan Riverpod untuk pengelolaan status reaktif
  3. Lapisan Layanan: Saat ini berisi layanan echo sederhana Anda, yang akan diganti dengan Layanan Gemini Chat
  4. Integrasi LLM: Akan ditambahkan di langkah berikutnya

Pemisahan ini memungkinkan Anda berfokus pada penerapan integrasi LLM saat komponen UI sudah ditangani.

Menjalankan aplikasi

Jalankan aplikasi dengan perintah berikut:

flutter run -d DEVICE

Ganti DEVICE dengan perangkat target Anda, seperti macos, windows, chrome, atau ID perangkat.

Screenshot Aplikasi Colorist yang menampilkan markdown rendering layanan echo

Sekarang Anda akan melihat aplikasi Colorist dengan:

  1. Area tampilan warna dengan warna default
  2. Antarmuka chat tempat Anda dapat mengetik pesan
  3. Panel log yang menampilkan interaksi chat

Coba ketik pesan seperti "Saya ingin warna biru tua", lalu tekan Kirim. Layanan echo hanya akan mengulangi pesan Anda. Pada langkah berikutnya, Anda akan menggantinya dengan interpretasi warna yang sebenarnya menggunakan Gemini API melalui Vertex AI di Firebase.

Apa langkah selanjutnya?

Pada langkah berikutnya, Anda akan mengonfigurasi Firebase dan menerapkan integrasi Gemini API dasar untuk mengganti layanan echo dengan layanan chat Gemini. Hal ini akan memungkinkan aplikasi menafsirkan deskripsi warna dan memberikan respons yang cerdas.

Pemecahan masalah

Masalah paket UI

Jika Anda mengalami masalah dengan paket colorist_ui:

  • Pastikan Anda menggunakan versi terbaru
  • Pastikan Anda telah menambahkan dependensi dengan benar
  • Memeriksa apakah ada versi paket yang bentrok

Error build

Jika Anda melihat error build:

  • Pastikan Anda telah menginstal Flutter SDK saluran stabil terbaru
  • Jalankan flutter clean diikuti dengan flutter pub get
  • Periksa output konsol untuk melihat pesan error tertentu

Konsep utama yang dipelajari

  • Menyiapkan project Flutter dengan dependensi yang diperlukan
  • Memahami arsitektur dan tanggung jawab komponen aplikasi
  • Menerapkan layanan sederhana yang meniru perilaku LLM
  • Menghubungkan layanan ke komponen UI
  • Menggunakan Riverpod untuk pengelolaan status

3. Integrasi chat Gemini dasar

Pada langkah ini, Anda akan mengganti layanan echo dari langkah sebelumnya dengan integrasi Gemini API menggunakan Vertex AI di Firebase. Anda akan mengonfigurasi Firebase, menyiapkan penyedia yang diperlukan, dan menerapkan layanan chat dasar yang berkomunikasi dengan Gemini API.

Yang akan Anda pelajari di langkah ini

  • Menyiapkan Firebase di aplikasi Flutter
  • Mengonfigurasi Vertex AI in Firebase untuk akses Gemini
  • Membuat penyedia Riverpod untuk layanan Firebase dan Gemini
  • Mengimplementasikan layanan chat dasar dengan Gemini API
  • Menangani respons API asinkron dan status error

Menyiapkan Firebase

Pertama, Anda harus menyiapkan Firebase untuk project Flutter. Hal ini mencakup pembuatan project Firebase, menambahkan aplikasi Anda ke project tersebut, dan mengonfigurasi setelan Vertex AI yang diperlukan.

Membuat project Firebase

  1. Buka Firebase Console dan login dengan Akun Google Anda.
  2. Klik Create a Firebase project atau pilih project yang ada.
  3. Ikuti wizard penyiapan untuk membuat project Anda.
  4. Setelah project dibuat, Anda harus mengupgrade ke paket Blaze (bayar sesuai penggunaan) untuk mengakses layanan Vertex AI. Klik tombol Upgrade di kiri bawah Firebase console.

Menyiapkan Vertex AI di project Firebase

  1. Di Firebase console, buka project Anda.
  2. Di sidebar kiri, pilih AI.
  3. Di kartu Vertex AI in Firebase, pilih Get Started.
  4. Ikuti petunjuk untuk mengaktifkan Vertex AI in Firebase API untuk project Anda.

Menginstal FlutterFire CLI

FlutterFire CLI menyederhanakan penyiapan Firebase di aplikasi Flutter:

dart pub global activate flutterfire_cli

Menambahkan Firebase ke aplikasi Flutter

  1. Tambahkan paket Firebase core dan Vertex AI ke project Anda:
flutter pub add firebase_core firebase_vertexai
  1. Jalankan perintah konfigurasi FlutterFire:
flutterfire configure

Perintah ini akan:

  • Meminta Anda untuk memilih project Firebase yang baru saja dibuat
  • Mendaftarkan aplikasi Flutter Anda ke Firebase
  • Membuat file firebase_options.dart dengan konfigurasi project Anda

Perintah ini akan otomatis mendeteksi platform yang Anda pilih (iOS, Android, macOS, Windows, web) dan mengonfigurasinya dengan tepat.

Konfigurasi khusus platform

Firebase memerlukan versi minimum yang lebih tinggi dari versi default untuk Flutter. Server ini juga memerlukan akses jaringan untuk berkomunikasi dengan Vertex AI di server Firebase.

Mengonfigurasi izin macOS

Untuk macOS, Anda perlu mengaktifkan akses jaringan di hak aplikasi:

  1. Buka macos/Runner/DebugProfile.entitlements dan tambahkan:

macos/Runner/DebugProfile.entitlements

<key>com.apple.security.network.client</key>
<true/>
  1. Buka juga macos/Runner/Release.entitlements dan tambahkan entri yang sama.
  2. Perbarui versi macOS minimum di bagian atas macos/Podfile:

macos/Podfile

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

Mengonfigurasi izin iOS

Untuk iOS, perbarui versi minimum di bagian atas ios/Podfile:

ios/Podfile

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

Mengonfigurasi setelan Android

Untuk Android, update android/app/build.gradle.kts:

android/app/build.gradle.kts

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

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

Membuat penyedia model Gemini

Sekarang Anda akan membuat penyedia Riverpod untuk Firebase dan Gemini. Buat file baru 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();
}

File ini menentukan dasar untuk tiga penyedia utama. Penyedia ini dibuat saat Anda menjalankan dart run build_runner oleh generator kode Riverpod.

  1. firebaseAppProvider: Melakukan inisialisasi Firebase dengan konfigurasi project Anda
  2. geminiModelProvider: Membuat instance model generatif Gemini
  3. chatSessionProvider: Membuat dan mempertahankan sesi chat dengan model Gemini

Anotasi keepAlive: true pada sesi chat memastikannya tetap ada di seluruh siklus proses aplikasi, sehingga mempertahankan konteks percakapan.

Mengimplementasikan layanan chat Gemini

Buat file baru lib/services/gemini_chat_service.dart untuk menerapkan layanan chat:

lib/services/gemini_chat_service.dart

import 'dart:async';

import 'package:colorist_ui/colorist_ui.dart';
import 'package:firebase_vertexai/firebase_vertexai.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';

import '../providers/gemini.dart';

part 'gemini_chat_service.g.dart';

class GeminiChatService {
 
GeminiChatService(this.ref);
 
final Ref ref;

 
Future<void> sendMessage(String message) async {
   
final chatSession = await ref.read(chatSessionProvider.future);
   
final chatStateNotifier = ref.read(chatStateNotifierProvider.notifier);
   
final logStateNotifier = ref.read(logStateNotifierProvider.notifier);

   
chatStateNotifier.addUserMessage(message);
   
logStateNotifier.logUserText(message);
   
final llmMessage = chatStateNotifier.createLlmMessage();
   
try {
     
final response = await chatSession.sendMessage(Content.text(message));

     
final responseText = response.text;
     
if (responseText != null) {
       
logStateNotifier.logLlmText(responseText);
       
chatStateNotifier.appendToMessage(llmMessage.id, responseText);
     
}
   
} catch (e, st) {
     
logStateNotifier.logError(e, st: st);
     
chatStateNotifier.appendToMessage(
       
llmMessage.id,
       
"\nI'm sorry, I encountered an error processing your request. "
       
"Please try again.",
     
);
   
} finally {
     
chatStateNotifier.finalizeMessage(llmMessage.id);
   
}
 
}
}

@riverpod
GeminiChatService geminiChatService(Ref ref) => GeminiChatService(ref);

Layanan ini:

  1. Menerima pesan pengguna dan mengirimkannya ke Gemini API
  2. Memperbarui antarmuka chat dengan respons dari model
  3. Mencatat semua komunikasi untuk memudahkan pemahaman alur LLM yang sebenarnya
  4. Menangani error dengan masukan pengguna yang sesuai

Catatan: Pada tahap ini, jendela Log akan terlihat hampir sama dengan jendela chat. Log akan menjadi lebih menarik setelah Anda memperkenalkan panggilan fungsi, lalu melakukan streaming respons.

Membuat kode Riverpod

Jalankan perintah runner build untuk membuat kode Riverpod yang diperlukan:

dart run build_runner build --delete-conflicting-outputs

Tindakan ini akan membuat file .g.dart yang diperlukan Riverpod agar dapat berfungsi.

Memperbarui file main.dart

Perbarui file lib/main.dart untuk menggunakan layanan chat Gemini yang baru:

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

Perubahan utama dalam update ini adalah:

  1. Mengganti layanan echo dengan layanan chat berbasis Gemini API
  2. Menambahkan layar pemuatan dan error menggunakan pola AsyncValue Riverpod dengan metode when
  3. Menghubungkan UI ke layanan chat baru Anda melalui callback sendMessage

Menjalankan aplikasi

Jalankan aplikasi dengan perintah berikut:

flutter run -d DEVICE

Ganti DEVICE dengan perangkat target Anda, seperti macos, windows, chrome, atau ID perangkat.

Screenshot Aplikasi Colorist yang menampilkan LLM Gemini yang merespons permintaan warna kuning cerah

Sekarang, saat Anda mengetik pesan, pesan tersebut akan dikirim ke Gemini API, dan Anda akan menerima respons dari LLM, bukan echo. Panel log akan menampilkan interaksi dengan API.

Memahami komunikasi LLM

Mari luangkan waktu sejenak untuk memahami apa yang terjadi saat Anda berkomunikasi dengan Gemini API:

Alur komunikasi

  1. Input pengguna: Pengguna memasukkan teks di antarmuka chat
  2. Pemformatan Permintaan: Aplikasi memformat teks sebagai objek Content untuk Gemini API
  3. Komunikasi API: Teks dikirim ke Gemini API melalui Vertex AI in Firebase
  4. Pemrosesan LLM: Model Gemini memproses teks dan menghasilkan respons
  5. Penanganan Respons: Aplikasi menerima respons dan memperbarui UI
  6. Logging: Semua komunikasi dicatat untuk transparansi

Sesi chat dan konteks percakapan

Sesi chat Gemini mempertahankan konteks di antara pesan, sehingga memungkinkan interaksi percakapan. Artinya, LLM "mengingat" pertukaran sebelumnya dalam sesi saat ini, sehingga memungkinkan percakapan yang lebih koheren.

Anotasi keepAlive: true di penyedia sesi chat Anda memastikan konteks ini tetap ada selama siklus proses aplikasi. Konteks persisten ini sangat penting untuk mempertahankan alur percakapan yang alami dengan LLM.

Apa langkah selanjutnya?

Pada tahap ini, Anda dapat menanyakan apa pun kepada Gemini API, karena tidak ada batasan terkait hal yang akan diresponsnya. Misalnya, Anda dapat meminta ringkasan Perang Mawar, yang tidak terkait dengan tujuan aplikasi warna Anda.

Pada langkah berikutnya, Anda akan membuat perintah sistem untuk memandu Gemini dalam menafsirkan deskripsi warna secara lebih efektif. Ini akan menunjukkan cara menyesuaikan perilaku LLM untuk kebutuhan khusus aplikasi dan memfokuskan kemampuannya pada domain aplikasi Anda.

Pemecahan masalah

Masalah konfigurasi Firebase

Jika Anda mengalami error dengan inisialisasi Firebase:

  • Pastikan file firebase_options.dart Anda dibuat dengan benar
  • Pastikan Anda telah mengupgrade ke paket Blaze untuk akses Vertex AI

Error akses API

Jika Anda menerima error saat mengakses Gemini API:

  • Pastikan penagihan disiapkan dengan benar di project Firebase Anda
  • Pastikan Vertex AI dan Cloud AI API diaktifkan di project Firebase Anda
  • Periksa konektivitas jaringan dan setelan firewall Anda
  • Pastikan nama model (gemini-2.0-flash) sudah benar dan tersedia

Masalah konteks percakapan

Jika Anda melihat bahwa Gemini tidak mengingat konteks sebelumnya dari chat:

  • Pastikan fungsi chatSession dianotasi dengan @Riverpod(keepAlive: true)
  • Pastikan Anda menggunakan kembali sesi chat yang sama untuk semua pertukaran pesan
  • Memastikan sesi chat diinisialisasi dengan benar sebelum mengirim pesan

Masalah khusus platform

Untuk masalah khusus platform:

  • iOS/macOS: Memastikan hak yang tepat ditetapkan dan versi minimum dikonfigurasi
  • Android: Memverifikasi bahwa versi SDK minimum ditetapkan dengan benar
  • Memeriksa pesan error khusus platform di konsol

Konsep utama yang dipelajari

  • Menyiapkan Firebase di aplikasi Flutter
  • Mengonfigurasi Vertex AI in Firebase untuk akses ke Gemini
  • Membuat penyedia Riverpod untuk layanan asinkron
  • Mengimplementasikan layanan chat yang berkomunikasi dengan LLM
  • Menangani status API asinkron (pemuatan, error, data)
  • Memahami alur komunikasi LLM dan sesi chat

4. Perintah yang efektif untuk deskripsi warna

Pada langkah ini, Anda akan membuat dan menerapkan perintah sistem yang memandu Gemini dalam menafsirkan deskripsi warna. Perintah sistem adalah cara yang efektif untuk menyesuaikan perilaku LLM untuk tugas tertentu tanpa mengubah kode Anda.

Yang akan Anda pelajari di langkah ini

  • Memahami perintah sistem dan pentingnya dalam aplikasi LLM
  • Membuat perintah yang efektif untuk tugas khusus domain
  • Memuat dan menggunakan perintah sistem di aplikasi Flutter
  • Memandu LLM untuk memberikan respons yang diformat secara konsisten
  • Menguji pengaruh perintah sistem terhadap perilaku LLM

Memahami perintah sistem

Sebelum membahas implementasi, mari kita pahami apa yang dimaksud dengan perintah sistem dan mengapa perintah sistem itu penting:

Apa yang dimaksud dengan perintah sistem?

Perintah sistem adalah jenis petunjuk khusus yang diberikan ke LLM yang menetapkan konteks, panduan perilaku, dan ekspektasi untuk responsnya. Tidak seperti pesan pengguna, perintah sistem:

  • Menetapkan peran dan persona LLM
  • Menentukan pengetahuan atau kemampuan khusus
  • Memberikan petunjuk pemformatan
  • Menetapkan batasan pada respons
  • Menjelaskan cara menangani berbagai skenario

Anggap perintah sistem sebagai memberikan "deskripsi pekerjaan" kepada LLM - perintah ini memberi tahu model bagaimana harus berperilaku selama percakapan.

Pentingnya perintah sistem

Perintah sistem sangat penting untuk menciptakan interaksi LLM yang konsisten dan berguna karena:

  1. Memastikan konsistensi: Membimbing model untuk memberikan respons dalam format yang konsisten
  2. Meningkatkan relevansi: Memfokuskan model pada domain tertentu (dalam kasus Anda, warna)
  3. Menetapkan batasan: Menentukan hal yang boleh dan tidak boleh dilakukan model
  4. Meningkatkan pengalaman pengguna: Membuat pola interaksi yang lebih alami dan bermanfaat
  5. Mengurangi pascapemrosesan: Mendapatkan respons dalam format yang lebih mudah diuraikan atau ditampilkan

Untuk aplikasi Colorist, Anda memerlukan LLM untuk menafsirkan deskripsi warna secara konsisten dan memberikan nilai RGB dalam format tertentu.

Membuat aset perintah sistem

Pertama, Anda akan membuat file perintah sistem yang akan dimuat saat runtime. Pendekatan ini memungkinkan Anda mengubah perintah tanpa mengompilasi ulang aplikasi.

Buat file baru assets/system_prompt.md dengan konten berikut:

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

Memahami struktur perintah sistem

Mari kita uraikan fungsi perintah ini:

  1. Definisi peran: Menetapkan LLM sebagai "asisten pakar warna"
  2. Penjelasan tugas: Menentukan tugas utama sebagai menafsirkan deskripsi warna menjadi nilai RGB
  3. Format respons: Menentukan dengan tepat cara nilai RGB harus diformat untuk konsistensi
  4. Contoh pertukaran: Memberikan contoh konkret tentang pola interaksi yang diharapkan
  5. Penanganan kasus ekstrem: Memberi petunjuk cara menangani deskripsi yang tidak jelas
  6. Batasan dan panduan: Menetapkan batas seperti mempertahankan nilai RGB antara 0,0 dan 1,0

Pendekatan terstruktur ini memastikan respons LLM akan konsisten, informatif, dan diformat sedemikian rupa sehingga mudah diuraikan jika Anda ingin mengekstrak nilai RGB secara terprogram.

Memperbarui pubspec.yaml

Sekarang, perbarui bagian bawah pubspec.yaml untuk menyertakan direktori aset:

pubspec.yaml

flutter:
  uses-material-design: true

  assets:
    - assets/

Jalankan flutter pub get untuk memuat ulang paket aset.

Membuat penyedia perintah sistem

Buat file baru lib/providers/system_prompt.dart untuk memuat perintah sistem:

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

Penyedia ini menggunakan sistem pemuatan aset Flutter untuk membaca file perintah saat runtime.

Mengupdate penyedia model Gemini

Sekarang ubah file lib/providers/gemini.dart Anda untuk menyertakan perintah sistem:

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

Perubahan utama adalah menambahkan systemInstruction: Content.system(systemPrompt) saat membuat model generatif. Tindakan ini akan memberi tahu Gemini untuk menggunakan petunjuk Anda sebagai perintah sistem untuk semua interaksi dalam sesi chat ini.

Membuat kode Riverpod

Jalankan perintah runner build untuk membuat kode Riverpod yang diperlukan:

dart run build_runner build --delete-conflicting-outputs

Menjalankan dan menguji aplikasi

Sekarang jalankan aplikasi Anda:

flutter run -d DEVICE

Screenshot Aplikasi Colorist yang menampilkan LLM Gemini yang merespons dengan respons dalam karakter untuk aplikasi pemilihan warna

Coba uji dengan berbagai deskripsi warna:

  • "Saya ingin warna biru langit"
  • "Berikan warna hijau hutan"
  • "Buat oranye cerah seperti matahari terbenam"
  • "Saya ingin warna lavender segar"
  • "Tampilkan sesuatu yang seperti biru laut yang dalam"

Anda akan melihat bahwa Gemini kini merespons dengan penjelasan percakapan tentang warna beserta nilai RGB yang diformat secara konsisten. Perintah sistem telah memandu LLM secara efektif untuk memberikan jenis respons yang Anda butuhkan.

Coba juga minta konten di luar konteks warna. Misalnya, penyebab utama Perang Mawar. Anda akan melihat perbedaan dari langkah sebelumnya.

Pentingnya rekayasa perintah untuk tugas khusus

Perintah sistem adalah seni dan ilmu pengetahuan. Ini adalah bagian penting dari integrasi LLM yang dapat secara drastis memengaruhi seberapa berguna model tersebut untuk aplikasi spesifik Anda. Yang telah Anda lakukan di sini adalah bentuk rekayasa perintah - menyesuaikan petunjuk agar model berperilaku dengan cara yang sesuai dengan kebutuhan aplikasi Anda.

Rekayasa perintah yang efektif melibatkan:

  1. Definisi peran yang jelas: Menetapkan tujuan LLM
  2. Petunjuk eksplisit: Menjelaskan secara mendetail bagaimana LLM harus merespons
  3. Contoh konkret: Menunjukkan, bukan hanya memberi tahu, seperti apa respons yang baik
  4. Penanganan kasus ekstrem: Memberi petunjuk kepada LLM tentang cara menangani skenario yang ambigu
  5. Spesifikasi pemformatan: Memastikan respons disusun dengan cara yang konsisten dan dapat digunakan

Perintah sistem yang telah Anda buat mengubah kemampuan umum Gemini menjadi asisten penafsiran warna khusus yang memberikan respons yang diformat khusus untuk kebutuhan aplikasi Anda. Ini adalah pola yang efektif yang dapat Anda terapkan ke berbagai domain dan tugas.

Apa langkah selanjutnya?

Pada langkah berikutnya, Anda akan mem-build fondasi ini dengan menambahkan deklarasi fungsi, yang memungkinkan LLM tidak hanya menyarankan nilai RGB, tetapi benar-benar memanggil fungsi di aplikasi Anda untuk menetapkan warna secara langsung. Hal ini menunjukkan bagaimana LLM dapat menjembatani kesenjangan antara bahasa alami dan fitur aplikasi konkret.

Pemecahan masalah

Masalah pemuatan aset

Jika Anda mengalami error saat memuat perintah sistem:

  • Pastikan pubspec.yaml mencantumkan direktori aset dengan benar
  • Pastikan jalur di rootBundle.loadString() cocok dengan lokasi file Anda
  • Jalankan flutter clean diikuti dengan flutter pub get untuk memuat ulang paket aset

Respons tidak konsisten

Jika LLM tidak konsisten mengikuti petunjuk format Anda:

  • Coba buat persyaratan format lebih eksplisit di perintah sistem
  • Tambahkan contoh lainnya untuk menunjukkan pola yang diharapkan
  • Pastikan format yang Anda minta wajar untuk model

Pembatasan kapasitas API

Jika Anda mengalami error terkait pembatasan kapasitas:

  • Perhatikan bahwa layanan Vertex AI memiliki batas penggunaan
  • Pertimbangkan untuk menerapkan logika percobaan ulang dengan backoff eksponensial
  • Periksa Firebase console Anda untuk mengetahui masalah kuota

Konsep utama yang dipelajari

  • Memahami peran dan pentingnya perintah sistem dalam aplikasi LLM
  • Membuat perintah yang efektif dengan petunjuk, contoh, dan batasan yang jelas
  • Memuat dan menggunakan perintah sistem di aplikasi Flutter
  • Membimbing perilaku LLM untuk tugas khusus domain
  • Menggunakan rekayasa perintah untuk membentuk respons LLM

Langkah ini menunjukkan cara Anda dapat mencapai penyesuaian perilaku LLM yang signifikan tanpa mengubah kode - cukup dengan memberikan petunjuk yang jelas di perintah sistem.

5. Deklarasi fungsi untuk alat LLM

Pada langkah ini, Anda akan memulai pekerjaan untuk mengaktifkan Gemini agar dapat mengambil tindakan di aplikasi Anda dengan menerapkan deklarasi fungsi. Fitur canggih ini memungkinkan LLM tidak hanya menyarankan nilai RGB, tetapi benar-benar menetapkannya di UI aplikasi Anda melalui panggilan alat khusus. Namun, Anda memerlukan langkah berikutnya untuk melihat permintaan LLM yang dijalankan di aplikasi Flutter.

Yang akan Anda pelajari di langkah ini

  • Memahami panggilan fungsi LLM dan manfaatnya untuk aplikasi Flutter
  • Menentukan deklarasi fungsi berbasis skema untuk Gemini
  • Mengintegrasikan deklarasi fungsi dengan model Gemini
  • Memperbarui perintah sistem untuk menggunakan kemampuan alat

Memahami panggilan fungsi

Sebelum menerapkan deklarasi fungsi, mari kita pahami definisinya dan mengapa deklarasi fungsi itu penting:

Apa yang dimaksud dengan pemanggilan fungsi?

Panggilan fungsi (terkadang disebut "penggunaan alat") adalah kemampuan yang memungkinkan LLM untuk:

  1. Mengenali kapan permintaan pengguna akan mendapatkan manfaat dari pemanggilan fungsi tertentu
  2. Buat objek JSON terstruktur dengan parameter yang diperlukan untuk fungsi tersebut
  3. Izinkan aplikasi Anda menjalankan fungsi dengan parameter tersebut
  4. Menerima hasil fungsi dan menggabungkannya ke dalam responsnya

Panggilan fungsi memungkinkan LLM memicu tindakan konkret dalam aplikasi Anda, bukan hanya menjelaskan apa yang harus dilakukan.

Alasan pentingnya panggilan fungsi untuk aplikasi Flutter

Panggilan fungsi menciptakan jembatan yang kuat antara bahasa alami dan fitur aplikasi:

  1. Tindakan langsung: Pengguna dapat mendeskripsikan apa yang mereka inginkan dalam bahasa alami, dan aplikasi merespons dengan tindakan konkret
  2. Output terstruktur: LLM menghasilkan data terstruktur yang bersih, bukan teks yang perlu diuraikan
  3. Operasi kompleks: Memungkinkan LLM mengakses data eksternal, melakukan penghitungan, atau mengubah status aplikasi
  4. Pengalaman pengguna yang lebih baik: Menciptakan integrasi yang lancar antara percakapan dan fungsi

Di aplikasi Colorist, panggilan fungsi memungkinkan pengguna mengucapkan "Saya ingin warna hijau hutan" dan membuat UI segera diperbarui dengan warna tersebut, tanpa harus mengurai nilai RGB dari teks.

Menentukan deklarasi fungsi

Buat file baru lib/services/gemini_tools.dart untuk menentukan deklarasi fungsi Anda:

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

Memahami deklarasi fungsi

Mari kita uraikan fungsi kode ini:

  1. Penamaan fungsi: Anda memberi nama fungsi set_color untuk menunjukkan tujuannya dengan jelas
  2. Deskripsi fungsi: Anda memberikan deskripsi yang jelas yang membantu LLM memahami kapan harus menggunakannya
  3. Definisi parameter: Anda menentukan parameter terstruktur dengan deskripsinya sendiri:
    • red: Komponen merah RGB, yang ditentukan sebagai angka antara 0,0 dan 1,0
    • green: Komponen hijau RGB, yang ditentukan sebagai angka antara 0,0 dan 1,0
    • blue: Komponen biru RGB, yang ditentukan sebagai angka antara 0,0 dan 1,0
  4. Jenis skema: Anda menggunakan Schema.number() untuk menunjukkan bahwa ini adalah nilai numerik
  5. Kumpulan alat: Anda membuat daftar alat yang berisi deklarasi fungsi

Pendekatan terstruktur ini membantu LLM Gemini memahami:

  • Kapan harus memanggil fungsi ini
  • Parameter yang perlu disediakan
  • Batasan yang berlaku untuk parameter tersebut (seperti rentang nilai)

Mengupdate penyedia model Gemini

Sekarang, ubah file lib/providers/gemini.dart untuk menyertakan deklarasi fungsi saat melakukan inisialisasi model 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();
}

Perubahan utamanya adalah menambahkan parameter tools: geminiTools.tools saat membuat model generatif. Hal ini membuat Gemini mengetahui fungsi yang tersedia untuk dipanggil.

Memperbarui perintah sistem

Sekarang Anda perlu mengubah perintah sistem untuk memberi tahu LLM tentang penggunaan alat set_color baru. Update 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

Perubahan utama pada perintah sistem adalah:

  1. Pengantar alat: Daripada meminta nilai RGB yang diformat, Anda kini memberi tahu LLM tentang alat set_color
  2. Proses yang diubah: Anda mengubah langkah 3 dari "memformat nilai dalam respons" menjadi "menggunakan alat untuk menetapkan nilai"
  3. Contoh yang diperbarui: Anda menunjukkan bagaimana respons harus menyertakan panggilan alat, bukan teks berformat
  4. Menghapus persyaratan pemformatan: Karena menggunakan panggilan fungsi terstruktur, Anda tidak perlu lagi format teks tertentu

Perintah yang diperbarui ini mengarahkan LLM untuk menggunakan panggilan fungsi, bukan hanya memberikan nilai RGB dalam bentuk teks.

Membuat kode Riverpod

Jalankan perintah runner build untuk membuat kode Riverpod yang diperlukan:

dart run build_runner build --delete-conflicting-outputs

Menjalankan aplikasi

Pada tahap ini, Gemini akan membuat konten yang mencoba menggunakan panggilan fungsi, tetapi Anda belum menerapkan pengendali untuk panggilan fungsi. Saat menjalankan aplikasi dan mendeskripsikan warna, Anda akan melihat Gemini merespons seolah-olah telah memanggil alat, tetapi Anda tidak akan melihat perubahan warna apa pun di UI hingga langkah berikutnya.

Jalankan aplikasi Anda:

flutter run -d DEVICE

Screenshot Aplikasi Colorist yang menampilkan LLM Gemini yang merespons dengan respons sebagian

Coba deskripsikan warna seperti "deep ocean blue" atau "forest green" dan amati responsnya. LLM mencoba memanggil fungsi yang ditentukan di atas, tetapi kode Anda belum mendeteksi panggilan fungsi.

Proses pemanggilan fungsi

Mari kita pahami apa yang terjadi saat Gemini menggunakan panggilan fungsi:

  1. Pemilihan fungsi: LLM memutuskan apakah panggilan fungsi akan berguna berdasarkan permintaan pengguna
  2. Pembuatan parameter: LLM menghasilkan parameter value yang sesuai dengan skema fungsi
  3. Format panggilan fungsi: LLM mengirimkan objek panggilan fungsi terstruktur dalam responsnya
  4. Penanganan aplikasi: Aplikasi Anda akan menerima panggilan ini dan menjalankan fungsi yang relevan (diterapkan pada langkah berikutnya)
  5. Integrasi respons: Dalam percakapan multi-giliran, LLM mengharapkan hasil fungsi ditampilkan

Dalam status aplikasi saat ini, tiga langkah pertama terjadi, tetapi Anda belum menerapkan langkah 4 atau 5 (menangani panggilan fungsi), yang akan Anda lakukan di langkah berikutnya.

Detail teknis: Cara Gemini memutuskan kapan harus menggunakan fungsi

Gemini membuat keputusan cerdas tentang kapan harus menggunakan fungsi berdasarkan:

  1. Intent pengguna: Apakah permintaan pengguna akan ditayangkan dengan sebaik mungkin oleh fungsi
  2. Relevansi fungsi: Seberapa baik fungsi yang tersedia cocok dengan tugas
  3. Ketersediaan parameter: Apakah parameter dapat menentukan nilai parameter dengan yakin
  4. Petunjuk sistem: Panduan dari perintah sistem tentang penggunaan fungsi

Dengan memberikan deklarasi fungsi dan petunjuk sistem yang jelas, Anda telah menyiapkan Gemini untuk mengenali permintaan deskripsi warna sebagai peluang untuk memanggil fungsi set_color.

Apa langkah selanjutnya?

Pada langkah berikutnya, Anda akan menerapkan pengendali untuk panggilan fungsi yang berasal dari Gemini. Tindakan ini akan melengkapi lingkaran, sehingga deskripsi pengguna dapat memicu perubahan warna yang sebenarnya di UI melalui panggilan fungsi LLM.

Pemecahan masalah

Masalah deklarasi fungsi

Jika Anda mengalami error dengan deklarasi fungsi:

  • Pastikan nama dan jenis parameter cocok dengan yang diharapkan
  • Pastikan nama fungsi jelas dan deskriptif
  • Pastikan deskripsi fungsi menjelaskan tujuannya secara akurat

Masalah perintah sistem

Jika LLM tidak mencoba menggunakan fungsi:

  • Pastikan perintah sistem Anda dengan jelas menginstruksikan LLM untuk menggunakan alat set_color
  • Pastikan contoh di perintah sistem menunjukkan penggunaan fungsi
  • Coba buat petunjuk untuk menggunakan alat ini lebih eksplisit

Masalah umum

Jika Anda mengalami masalah lain:

  • Periksa konsol untuk menemukan error yang terkait dengan deklarasi fungsi
  • Memverifikasi bahwa alat diteruskan dengan benar ke model
  • Memastikan semua kode yang dihasilkan Riverpod sudah yang terbaru

Konsep utama yang dipelajari

  • Menentukan deklarasi fungsi untuk memperluas kemampuan LLM di aplikasi Flutter
  • Membuat skema parameter untuk pengumpulan data terstruktur
  • Mengintegrasikan deklarasi fungsi dengan model Gemini
  • Memperbarui perintah sistem untuk mendorong penggunaan fungsi
  • Memahami cara LLM memilih dan memanggil fungsi

Langkah ini menunjukkan bagaimana LLM dapat menjembatani kesenjangan antara input bahasa alami dan panggilan fungsi terstruktur, yang meletakkan dasar untuk integrasi yang lancar antara fitur percakapan dan aplikasi.

6. Menerapkan penanganan alat

Pada langkah ini, Anda akan menerapkan pengendali untuk panggilan fungsi yang berasal dari Gemini. Hal ini melengkapi lingkaran komunikasi antara input bahasa alami dan fitur aplikasi konkret, sehingga LLM dapat langsung memanipulasi UI Anda berdasarkan deskripsi pengguna.

Yang akan Anda pelajari di langkah ini

  • Memahami pipeline panggilan fungsi lengkap di aplikasi LLM
  • Memproses panggilan fungsi dari Gemini di aplikasi Flutter
  • Mengimplementasikan pengendali fungsi yang mengubah status aplikasi
  • Menangani respons fungsi dan menampilkan hasil ke LLM
  • Membuat alur komunikasi lengkap antara LLM dan UI
  • Mencatat panggilan dan respons fungsi untuk transparansi

Memahami pipeline panggilan fungsi

Sebelum mempelajari implementasi, mari kita pahami pipeline panggilan fungsi lengkap:

Alur menyeluruh

  1. Input pengguna: Pengguna mendeskripsikan warna dalam bahasa alami (misalnya, "forest green")
  2. Pemrosesan LLM: Gemini menganalisis deskripsi dan memutuskan untuk memanggil fungsi set_color
  3. Pembuatan panggilan fungsi: Gemini membuat JSON terstruktur dengan parameter (nilai merah, hijau, biru)
  4. Penerimaan panggilan fungsi: Aplikasi Anda menerima data terstruktur ini dari Gemini
  5. Eksekusi fungsi: Aplikasi Anda mengeksekusi fungsi dengan parameter yang disediakan
  6. Pembaruan status: Fungsi ini memperbarui status aplikasi Anda (mengubah warna yang ditampilkan)
  7. Pembuatan respons: Fungsi Anda menampilkan hasil kembali ke LLM
  8. Penggabungan respons: LLM menggabungkan hasil ini ke dalam respons akhirnya
  9. Pembaruan UI: UI Anda bereaksi terhadap perubahan status, menampilkan warna baru

Siklus komunikasi yang lengkap sangat penting untuk integrasi LLM yang tepat. Saat membuat panggilan fungsi, LLM tidak hanya mengirimkan permintaan dan melanjutkan. Sebagai gantinya, fungsi ini menunggu aplikasi Anda mengeksekusi fungsi dan menampilkan hasil. LLM kemudian menggunakan hasil ini untuk merumuskan respons akhirnya, sehingga menciptakan alur percakapan yang alami yang mengonfirmasi tindakan yang diambil.

Menerapkan pengendali fungsi

Mari kita perbarui file lib/services/gemini_tools.dart untuk menambahkan pengendali panggilan fungsi:

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

Memahami pengendali fungsi

Mari kita uraikan apa yang dilakukan pengendali fungsi ini:

  1. handleFunctionCall: Pengirim pusat yang:
    • Mencatat panggilan fungsi untuk transparansi di panel log
    • Merutekan ke pengendali yang sesuai berdasarkan nama fungsi
    • Menampilkan respons terstruktur yang akan dikirim kembali ke LLM
  2. handleSetColor: Pengendali spesifik untuk fungsi set_color Anda yang:
    • Mengekstrak nilai RGB dari peta argumen
    • Mengonversinya ke jenis yang diharapkan (ganda)
    • Memperbarui status warna aplikasi menggunakan colorStateNotifier
    • Membuat respons terstruktur dengan status berhasil dan informasi warna saat ini
    • Mencatat hasil fungsi ke dalam log untuk proses debug
  3. handleUnknownFunction: Pengendali penggantian untuk fungsi yang tidak diketahui yang:
    • Mencatat peringatan tentang fungsi yang tidak didukung
    • Menampilkan respons error ke LLM

Fungsi handleSetColor sangat penting karena menjembatani kesenjangan antara pemahaman bahasa alami LLM dan perubahan UI konkret.

Memperbarui layanan chat Gemini untuk memproses panggilan dan respons fungsi

Sekarang, mari kita perbarui file lib/services/gemini_chat_service.dart untuk memproses panggilan fungsi dari respons LLM dan mengirim hasilnya kembali ke 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);

Memahami alur komunikasi

Penambahan utama di sini adalah penanganan lengkap panggilan dan respons fungsi:

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

Kode ini:

  1. Memeriksa apakah respons LLM berisi panggilan fungsi
  2. Untuk setiap panggilan fungsi, panggil metode handleFunctionCall Anda dengan nama dan argumen fungsi
  3. Mengumpulkan hasil setiap panggilan fungsi
  4. Mengirimkan hasil ini kembali ke LLM menggunakan Content.functionResponses
  5. Memproses respons LLM terhadap hasil fungsi
  6. Memperbarui UI dengan teks respons akhir

Tindakan ini akan membuat alur bolak-balik:

  • Pengguna → LLM: Meminta warna
  • LLM → Aplikasi: Panggilan fungsi dengan parameter
  • Aplikasi → Pengguna: Warna baru ditampilkan
  • Aplikasi → LLM: Hasil fungsi
  • LLM → Pengguna: Respons akhir yang menggabungkan hasil fungsi

Membuat kode Riverpod

Jalankan perintah runner build untuk membuat kode Riverpod yang diperlukan:

dart run build_runner build --delete-conflicting-outputs

Menjalankan dan menguji alur lengkap

Sekarang jalankan aplikasi Anda:

flutter run -d DEVICE

Screenshot Aplikasi Colorist yang menampilkan LLM Gemini yang merespons dengan panggilan fungsi

Coba masukkan berbagai deskripsi warna:

  • "Saya ingin warna merah tua"
  • "Tampilkan warna biru langit yang menenangkan"
  • "Beri tahu saya warna daun mint segar"
  • "Saya ingin melihat warna oranye hangat saat matahari terbenam"
  • "Buat warnanya menjadi ungu kerajaan yang kaya"

Sekarang Anda akan melihat:

  1. Pesan Anda yang muncul di antarmuka chat
  2. Respons Gemini yang muncul di chat
  3. Panggilan fungsi yang dicatat di panel log
  4. Hasil fungsi dicatat dalam log segera setelah
  5. Pembaruan persegi panjang warna untuk menampilkan warna yang dijelaskan
  6. Nilai RGB diperbarui untuk menampilkan komponen warna baru
  7. Respons akhir Gemini muncul, sering kali mengomentari warna yang ditetapkan

Panel log memberikan insight tentang apa yang terjadi di balik layar. Anda akan melihat:

  • Panggilan fungsi yang tepat yang dilakukan Gemini
  • Parameter yang dipilih untuk setiap nilai RGB
  • Hasil yang ditampilkan fungsi Anda
  • Respons lanjutan dari Gemini

Notifikasi status warna

colorStateNotifier yang Anda gunakan untuk memperbarui warna adalah bagian dari paket colorist_ui. API ini mengelola:

  • Warna saat ini yang ditampilkan di UI
  • Histori warna (10 warna terakhir)
  • Notifikasi perubahan status ke komponen UI

Saat Anda memanggil updateColor dengan nilai RGB baru, tindakan ini akan:

  1. Membuat objek ColorData baru dengan nilai yang diberikan
  2. Memperbarui warna saat ini dalam status aplikasi
  3. Menambahkan warna ke histori
  4. Memicu update UI melalui pengelolaan status Riverpod

Komponen UI dalam paket colorist_ui memantau status ini dan otomatis diperbarui saat berubah, sehingga menciptakan pengalaman reaktif.

Memahami penanganan error

Penerapan Anda mencakup penanganan error yang andal:

  1. Blok try-catch: Menggabungkan semua interaksi LLM untuk menangkap pengecualian
  2. Pembuatan log error: Mencatat error di panel log dengan pelacakan tumpukan
  3. Masukan pengguna: Memberikan pesan error yang mudah dipahami di chat
  4. Pembersihan status: Menyelesaikan status pesan meskipun terjadi error

Hal ini memastikan aplikasi tetap stabil dan memberikan masukan yang sesuai meskipun terjadi masalah dengan layanan atau eksekusi fungsi LLM.

Manfaat panggilan fungsi untuk pengalaman pengguna

Yang telah Anda capai di sini menunjukkan bagaimana LLM dapat membuat antarmuka alami yang efektif:

  1. Antarmuka natural language: Pengguna mengekspresikan intent dalam bahasa sehari-hari
  2. Penafsiran cerdas: LLM menerjemahkan deskripsi yang tidak jelas menjadi nilai yang tepat
  3. Manipulasi langsung: UI diperbarui sebagai respons terhadap bahasa alami
  4. Respons kontekstual: LLM memberikan konteks percakapan tentang perubahan
  5. Beban kognitif rendah: Pengguna tidak perlu memahami nilai RGB atau teori warna

Pola penggunaan panggilan fungsi LLM ini untuk menjembatani natural language dan tindakan UI dapat diperluas ke banyak domain lain di luar pemilihan warna.

Apa langkah selanjutnya?

Pada langkah berikutnya, Anda akan meningkatkan pengalaman pengguna dengan menerapkan respons streaming. Daripada menunggu respons lengkap, Anda akan memproses potongan teks dan panggilan fungsi saat diterima, sehingga membuat aplikasi yang lebih responsif dan menarik.

Pemecahan masalah

Masalah panggilan fungsi

Jika Gemini tidak memanggil fungsi atau parameter Anda salah:

  • Memastikan deklarasi fungsi Anda cocok dengan yang dijelaskan di perintah sistem
  • Pastikan nama dan jenis parameter konsisten
  • Pastikan perintah sistem Anda secara eksplisit menginstruksikan LLM untuk menggunakan alat tersebut
  • Pastikan nama fungsi di pengendali Anda sama persis dengan yang ada dalam deklarasi
  • Periksa panel log untuk mengetahui informasi mendetail tentang panggilan fungsi

Masalah respons fungsi

Jika hasil fungsi tidak dikirim kembali dengan benar ke LLM:

  • Pastikan fungsi Anda menampilkan Peta yang diformat dengan benar
  • Pastikan Content.functionResponses dibuat dengan benar
  • Cari error apa pun dalam log yang terkait dengan respons fungsi
  • Pastikan Anda menggunakan sesi chat yang sama untuk respons

Masalah tampilan warna

Jika warna tidak ditampilkan dengan benar:

  • Memastikan nilai RGB dikonversi dengan benar ke bilangan ganda (LLM mungkin mengirimkannya sebagai bilangan bulat)
  • Pastikan nilai berada dalam rentang yang diharapkan (0,0 hingga 1,0)
  • Pastikan notifikasi status warna dipanggil dengan benar
  • Periksa log untuk mengetahui nilai persis yang diteruskan ke fungsi

Masalah umum

Untuk masalah umum:

  • Periksa log untuk menemukan error atau peringatan
  • Memverifikasi konektivitas Vertex AI in Firebase
  • Memeriksa ketidakcocokan jenis dalam parameter fungsi
  • Memastikan semua kode yang dihasilkan Riverpod sudah yang terbaru

Konsep utama yang dipelajari

  • Menerapkan pipeline panggilan fungsi lengkap di Flutter
  • Membuat komunikasi penuh antara LLM dan aplikasi Anda
  • Memproses data terstruktur dari respons LLM
  • Mengirim hasil fungsi kembali ke LLM untuk digabungkan ke dalam respons
  • Menggunakan panel log untuk mendapatkan visibilitas ke interaksi aplikasi LLM
  • Menghubungkan input bahasa alami ke perubahan UI yang konkret

Setelah langkah ini selesai, aplikasi Anda kini menunjukkan salah satu pola paling canggih untuk integrasi LLM: menerjemahkan input bahasa alami menjadi tindakan UI yang konkret, sekaligus mempertahankan percakapan yang koheren yang mengonfirmasi tindakan ini. Hal ini menciptakan antarmuka percakapan yang intuitif dan terasa ajaib bagi pengguna.

7. Respons streaming untuk UX yang lebih baik

Pada langkah ini, Anda akan meningkatkan pengalaman pengguna dengan menerapkan respons streaming dari Gemini. Daripada menunggu seluruh respons dibuat, Anda akan memproses potongan teks dan panggilan fungsi saat diterima, sehingga membuat aplikasi yang lebih responsif dan menarik.

Yang akan Anda bahas dalam langkah ini

  • Pentingnya streaming untuk aplikasi yang didukung LLM
  • Mengimplementasikan respons LLM streaming di aplikasi Flutter
  • Memproses potongan teks sebagian saat tiba dari API
  • Mengelola status percakapan untuk mencegah konflik pesan
  • Menangani panggilan fungsi dalam respons streaming
  • Membuat indikator visual untuk respons yang sedang berlangsung

Alasan streaming penting untuk aplikasi LLM

Sebelum menerapkan, mari kita pahami mengapa respons streaming sangat penting untuk menciptakan pengalaman pengguna yang luar biasa dengan LLM:

Pengalaman pengguna yang ditingkatkan

Respons streaming memberikan beberapa manfaat pengalaman pengguna yang signifikan:

  1. Mengurangi latensi yang dirasakan: Pengguna melihat teks mulai langsung muncul (biasanya dalam waktu 100-300 md), bukan menunggu beberapa detik untuk mendapatkan respons lengkap. Persepsi tentang kedekatan ini secara dramatis meningkatkan kepuasan pengguna.
  2. Ritme percakapan yang alami: Tampilan teks yang bertahap meniru cara manusia berkomunikasi, sehingga menciptakan pengalaman dialog yang lebih alami.
  3. Pemrosesan informasi progresif: Pengguna dapat mulai memproses informasi saat informasi tersebut diterima, bukan dikejutkan dengan blok teks yang besar sekaligus.
  4. Peluang untuk gangguan awal: Dalam aplikasi lengkap, pengguna berpotensi mengganggu atau mengalihkan LLM jika mereka melihatnya mengarah ke arah yang tidak membantu.
  5. Konfirmasi visual aktivitas: Teks streaming memberikan masukan langsung bahwa sistem berfungsi, sehingga mengurangi ketidakpastian.

Keunggulan teknis

Selain peningkatan UX, streaming menawarkan manfaat teknis:

  1. Eksekusi fungsi awal: Panggilan fungsi dapat dideteksi dan dieksekusi segera setelah muncul di aliran data, tanpa menunggu respons lengkap.
  2. Pembaruan UI inkremental: Anda dapat memperbarui UI secara bertahap saat informasi baru tiba, sehingga menciptakan pengalaman yang lebih dinamis.
  3. Pengelolaan status percakapan: Streaming memberikan sinyal yang jelas tentang kapan respons selesai vs. masih dalam proses, sehingga memungkinkan pengelolaan status yang lebih baik.
  4. Risiko waktu tunggu yang berkurang: Dengan respons non-streaming, pembuatan yang berjalan lama berisiko mengalami waktu tunggu koneksi. Streaming membuat koneksi lebih awal dan mempertahankannya.

Untuk aplikasi Colorist, menerapkan streaming berarti pengguna akan melihat respons teks dan perubahan warna muncul lebih cepat, sehingga menciptakan pengalaman yang jauh lebih responsif.

Menambahkan pengelolaan status percakapan

Pertama, mari kita tambahkan penyedia status untuk melacak apakah aplikasi saat ini menangani respons streaming. Update file lib/services/gemini_chat_service.dart Anda:

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

Memahami penerapan streaming

Mari kita uraikan fungsi kode ini:

  1. Pelacakan status percakapan:
    • conversationStateProvider melacak apakah aplikasi saat ini sedang memproses respons
    • Status bertransisi dari idlebusy saat memproses, lalu kembali ke idle
    • Hal ini mencegah beberapa permintaan serentak yang dapat menimbulkan konflik
  2. Inisialisasi streaming:
    • sendMessageStream() menampilkan Stream potongan respons, bukan Future dengan respons lengkap
    • Setiap bagian dapat berisi teks, panggilan fungsi, atau keduanya
  3. Pemrosesan progresif:
    • await for memproses setiap bagian saat tiba secara real time
    • Teks langsung ditambahkan ke UI, sehingga menciptakan efek streaming
    • Panggilan fungsi dieksekusi segera setelah terdeteksi
  4. Penanganan panggilan fungsi:
    • Saat panggilan fungsi terdeteksi dalam bagian, panggilan tersebut akan langsung dieksekusi
    • Hasil dikirim kembali ke LLM melalui panggilan streaming lain
    • Respons LLM terhadap hasil ini juga diproses dengan cara streaming
  5. Penanganan dan pembersihan error:
    • try/catch memberikan penanganan error yang andal
    • Blok finally memastikan status percakapan direset dengan benar
    • Pesan selalu diselesaikan, meskipun terjadi error

Implementasi ini menciptakan pengalaman streaming yang responsif dan andal sekaligus mempertahankan status percakapan yang tepat.

Memperbarui layar utama untuk menghubungkan status percakapan

Ubah file lib/main.dart untuk meneruskan status percakapan ke layar utama:

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

Perubahan utama di sini adalah meneruskan conversationState ke widget MainScreen. MainScreen (disediakan oleh paket colorist_ui) akan menggunakan status ini untuk menonaktifkan input teks saat respons sedang diproses.

Hal ini menciptakan pengalaman pengguna yang kohesif dengan UI yang mencerminkan status percakapan saat ini.

Membuat kode Riverpod

Jalankan perintah runner build untuk membuat kode Riverpod yang diperlukan:

dart run build_runner build --delete-conflicting-outputs

Menjalankan dan menguji respons streaming

Jalankan aplikasi Anda:

flutter run -d DEVICE

Screenshot Aplikasi Colorist yang menampilkan LLM Gemini yang merespons secara streaming

Sekarang, coba uji perilaku streaming dengan berbagai deskripsi warna. Coba deskripsi seperti:

  • "Tampilkan warna biru kehijauan tua lautan saat senja"
  • "Saya ingin melihat karang yang cerah dan mengingatkan saya pada bunga tropis"
  • "Buat warna hijau zaitun yang redup seperti seragam tentara lama"

Alur teknis streaming secara mendetail

Mari kita pelajari apa yang sebenarnya terjadi saat melakukan streaming respons:

Pembentukan koneksi

Saat Anda memanggil sendMessageStream(), hal berikut akan terjadi:

  1. Aplikasi membuat koneksi ke layanan Vertex AI
  2. Permintaan pengguna dikirim ke layanan
  3. Server mulai memproses permintaan
  4. Koneksi streaming tetap terbuka, siap untuk mengirimkan potongan

Transmisi bagian

Saat Gemini membuat konten, potongan dikirim melalui streaming:

  1. Server mengirimkan potongan teks saat dibuat (biasanya beberapa kata atau kalimat)
  2. Saat memutuskan untuk melakukan panggilan fungsi, Gemini akan mengirimkan informasi panggilan fungsi
  3. Potongan teks tambahan dapat mengikuti panggilan fungsi
  4. Streaming berlanjut hingga pembuatan selesai

Pemrosesan progresif

Aplikasi Anda memproses setiap bagian secara bertahap:

  1. Setiap bagian teks ditambahkan ke respons yang ada
  2. Panggilan fungsi dieksekusi segera setelah terdeteksi
  3. UI diperbarui secara real-time dengan hasil teks dan fungsi
  4. Status dilacak untuk menunjukkan bahwa respons masih di-streaming

Penyelesaian streaming

Setelah pembuatan selesai:

  1. Streaming ditutup oleh server
  2. Loop await for Anda keluar secara alami
  3. Pesan ditandai sebagai selesai
  4. Status percakapan ditetapkan kembali ke tidak ada aktivitas
  5. UI diperbarui untuk mencerminkan status selesai

Perbandingan streaming vs. non-streaming

Untuk lebih memahami manfaat streaming, mari kita bandingkan pendekatan streaming vs. non-streaming:

Aspek

Non-Streaming

Streaming

Latensi yang dirasakan

Pengguna tidak melihat apa pun hingga respons lengkap siap

Pengguna melihat kata pertama dalam milidetik

Pengalaman pengguna

Waktu tunggu yang lama diikuti dengan kemunculan teks secara tiba-tiba

Tampilan teks progresif yang alami

Pengelolaan status

Lebih sederhana (pesan tertunda atau selesai)

Lebih kompleks (pesan dapat berada dalam status streaming)

Eksekusi fungsi

Hanya terjadi setelah respons lengkap

Terjadi selama pembuatan respons

Kompleksitas penerapan

Lebih mudah diterapkan

Memerlukan pengelolaan status tambahan

Pemulihan dari error

Respons semua atau tidak sama sekali

Respons sebagian mungkin masih berguna

Kompleksitas kode

Lebih sederhana

Lebih kompleks karena penanganan streaming

Untuk aplikasi seperti Colorist, manfaat UX dari streaming lebih besar daripada kompleksitas penerapan, terutama untuk interpretasi warna yang mungkin memerlukan waktu beberapa detik untuk dibuat.

Praktik terbaik untuk UX streaming

Saat menerapkan streaming di aplikasi LLM Anda sendiri, pertimbangkan praktik terbaik berikut:

  1. Indikator visual yang jelas: Selalu berikan isyarat visual yang jelas yang membedakan pesan streaming vs. pesan lengkap
  2. Pemblokiran input: Menonaktifkan input pengguna selama streaming untuk mencegah beberapa permintaan yang tumpang-tindih
  3. Pemulihan error: Desain UI Anda untuk menangani pemulihan yang halus jika streaming terganggu
  4. Transisi status: Memastikan transisi yang lancar antara status tidak ada aktivitas, streaming, dan selesai
  5. Visualisasi progres: Pertimbangkan animasi atau indikator halus yang menunjukkan pemrosesan aktif
  6. Opsi pembatalan: Dalam aplikasi lengkap, berikan cara bagi pengguna untuk membatalkan pembuatan yang sedang berlangsung
  7. Integrasi hasil fungsi: Mendesain UI untuk menangani hasil fungsi yang muncul di tengah streaming
  8. Pengoptimalan performa: Meminimalkan pembuatan ulang UI selama pembaruan streaming yang cepat

Paket colorist_ui menerapkan banyak praktik terbaik ini untuk Anda, tetapi praktik ini merupakan pertimbangan penting untuk setiap penerapan LLM streaming.

Apa langkah selanjutnya?

Pada langkah berikutnya, Anda akan menerapkan sinkronisasi LLM dengan memberi tahu Gemini saat pengguna memilih warna dari histori. Hal ini akan menciptakan pengalaman yang lebih kohesif dengan LLM yang mengetahui perubahan yang dimulai pengguna pada status aplikasi.

Pemecahan masalah

Masalah stream processing

Jika Anda mengalami masalah dengan pemrosesan streaming:

  • Gejala: Respons sebagian, teks tidak ada, atau penghentian streaming secara tiba-tiba
  • Solusi: Periksa konektivitas jaringan dan pastikan pola asinkron/menunggu yang tepat dalam kode Anda
  • Diagnosis: Periksa panel log untuk menemukan pesan error atau peringatan yang terkait dengan pemrosesan streaming
  • Perbaikan: Memastikan semua pemrosesan streaming menggunakan penanganan error yang tepat dengan blok try/catch

Panggilan fungsi tidak ada

Jika panggilan fungsi tidak terdeteksi di aliran data:

  • Gejala: Teks muncul, tetapi warna tidak diperbarui, atau log tidak menampilkan panggilan fungsi
  • Solusi: Verifikasi petunjuk perintah sistem tentang penggunaan panggilan fungsi
  • Diagnosis: Periksa panel log untuk melihat apakah panggilan fungsi diterima
  • Perbaikan: Sesuaikan perintah sistem Anda untuk secara lebih eksplisit menginstruksikan LLM agar menggunakan alat set_color

Penanganan error umum

Untuk masalah lainnya:

  • Langkah 1: Periksa panel log untuk menemukan pesan error
  • Langkah 2: Verifikasi Vertex AI dalam konektivitas Firebase
  • Langkah 3: Pastikan semua kode yang dihasilkan Riverpod sudah yang terbaru
  • Langkah 4: Tinjau penerapan streaming untuk menemukan pernyataan await yang hilang

Konsep utama yang dipelajari

  • Mengimplementasikan respons streaming dengan Gemini API untuk UX yang lebih responsif
  • Mengelola status percakapan untuk menangani interaksi streaming dengan benar
  • Memproses panggilan fungsi dan teks real-time saat diterima
  • Membuat UI responsif yang diperbarui secara bertahap selama streaming
  • Menangani streaming serentak dengan pola asinkron yang tepat
  • Memberikan respons visual yang sesuai selama respons streaming

Dengan menerapkan streaming, Anda telah meningkatkan pengalaman pengguna aplikasi Colorist secara signifikan, sehingga menciptakan antarmuka yang lebih responsif dan menarik yang benar-benar terasa seperti percakapan.

8. Sinkronisasi Konteks LLM

Pada langkah bonus ini, Anda akan menerapkan Sinkronisasi Konteks LLM dengan memberi tahu Gemini saat pengguna memilih warna dari histori. Hal ini menciptakan pengalaman yang lebih kohesif, yaitu LLM mengetahui tindakan pengguna di antarmuka, bukan hanya pesan eksplisit mereka.

Yang akan Anda bahas dalam langkah ini

  • Membuat Sinkronisasi Konteks LLM antara UI dan LLM
  • Membuat serialisasi peristiwa UI ke dalam konteks yang dapat dipahami LLM
  • Memperbarui konteks percakapan berdasarkan tindakan pengguna
  • Membuat pengalaman yang koheren di berbagai metode interaksi
  • Meningkatkan kesadaran konteks LLM di luar pesan chat vulgar

Memahami Sinkronisasi Konteks LLM

Chatbot tradisional hanya merespons pesan pengguna yang eksplisit, sehingga menyebabkan pemutusan saat pengguna berinteraksi dengan aplikasi melalui cara lain. Sinkronisasi Konteks LLM mengatasi batasan ini:

Pentingnya Sinkronisasi Konteks LLM

Saat pengguna berinteraksi dengan aplikasi Anda melalui elemen UI (seperti memilih warna dari histori), LLM tidak dapat mengetahui apa yang terjadi kecuali jika Anda memberi tahu secara eksplisit. Sinkronisasi Konteks LLM:

  1. Mempertahankan konteks: Memastikan LLM selalu mendapatkan informasi tentang semua tindakan pengguna yang relevan
  2. Menciptakan koherensi: Menghasilkan pengalaman yang kohesif saat LLM mengonfirmasi interaksi UI
  3. Meningkatkan kecerdasan: Memungkinkan LLM merespons semua tindakan pengguna dengan tepat
  4. Meningkatkan pengalaman pengguna: Membuat seluruh aplikasi terasa lebih terintegrasi dan responsif
  5. Mengurangi upaya pengguna: Menghilangkan kebutuhan pengguna untuk menjelaskan tindakan UI mereka secara manual

Di aplikasi Colorist, saat pengguna memilih warna dari histori, Anda ingin Gemini mengonfirmasi tindakan ini dan memberikan komentar yang cerdas tentang warna yang dipilih, sehingga mempertahankan ilusi asisten yang lancar dan cerdas.

Memperbarui layanan chat Gemini untuk notifikasi pemilihan warna

Pertama, Anda akan menambahkan metode ke GeminiChatService untuk memberi tahu LLM saat pengguna memilih warna dari histori. Update file lib/services/gemini_chat_service.dart Anda:

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

Penambahan utama adalah metode notifyColorSelection, yang:

  1. Mengambil objek ColorData yang mewakili warna yang dipilih
  2. Mengenkodenya ke format JSON yang dapat disertakan dalam pesan
  3. Mengirim pesan berformat khusus ke LLM yang menunjukkan pemilihan pengguna
  4. Menggunakan kembali metode sendMessage yang ada untuk menangani notifikasi

Pendekatan ini menghindari duplikasi dengan memanfaatkan infrastruktur penanganan pesan yang ada.

Memperbarui aplikasi utama untuk menghubungkan notifikasi pemilihan warna

Sekarang, ubah file lib/main.dart untuk meneruskan fungsi notifikasi pemilihan warna ke layar utama:

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

Perubahan utamanya adalah menambahkan callback notifyColorSelection, yang menghubungkan peristiwa UI (memilih warna dari histori) ke sistem notifikasi LLM.

Memperbarui perintah sistem

Sekarang, Anda perlu memperbarui perintah sistem untuk memberi tahu LLM cara merespons notifikasi pemilihan warna. Ubah file assets/system_prompt.md Anda:

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

Penambahan utamanya adalah bagian "Saat Pengguna Memilih Warna Historis", yang:

  1. Menjelaskan konsep notifikasi pemilihan histori ke LLM
  2. Memberikan contoh tampilan notifikasi ini
  3. Menampilkan contoh respons yang sesuai
  4. Menetapkan ekspektasi untuk mengonfirmasi pilihan dan mengomentari warna

Hal ini membantu LLM memahami cara merespons pesan khusus ini dengan tepat.

Membuat Kode Riverpod

Jalankan perintah runner build untuk membuat kode Riverpod yang diperlukan:

dart run build_runner build --delete-conflicting-outputs

Menjalankan dan menguji Sinkronisasi Konteks LLM

Jalankan aplikasi Anda:

flutter run -d DEVICE

Screenshot Aplikasi Colorist yang menampilkan LLM Gemini yang merespons pilihan dari histori warna

Pengujian Sinkronisasi Konteks LLM melibatkan:

  1. Pertama, buat beberapa warna dengan mendeskripsikannya di chat
    • "Tampilkan warna ungu cerah"
    • "Saya ingin warna hijau hutan"
    • "Berikan warna merah terang"
  2. Kemudian, klik salah satu thumbnail warna di strip histori

Anda harus mengamati:

  1. Warna yang dipilih akan muncul di layar utama
  2. Pesan pengguna muncul di chat yang menunjukkan pemilihan warna
  3. LLM merespons dengan mengonfirmasi pilihan dan mengomentari warna
  4. Seluruh interaksi terasa alami dan kohesif

Hal ini menciptakan pengalaman yang lancar, dengan LLM mengetahui dan merespons pesan langsung dan interaksi UI dengan tepat.

Cara kerja Sinkronisasi Konteks LLM

Mari kita pelajari detail teknis cara kerja sinkronisasi ini:

Aliran Data

  1. Tindakan pengguna: Pengguna mengklik warna di strip histori
  2. Peristiwa UI: Widget MainScreen mendeteksi pilihan ini
  3. Eksekusi callback: Callback notifyColorSelection dipicu
  4. Pembuatan pesan: Pesan berformat khusus dibuat dengan data warna
  5. Pemrosesan LLM: Pesan dikirim ke Gemini, yang mengenali format
  6. Respons kontekstual: Gemini merespons dengan tepat berdasarkan perintah sistem
  7. Pembaruan UI: Respons muncul di chat, sehingga menciptakan pengalaman yang kohesif

Serialisasi data

Aspek utama dari pendekatan ini adalah cara Anda melakukan serialisasi data warna:

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

Metode toLLMContextMap() (disediakan oleh paket colorist_ui) mengonversi objek ColorData menjadi peta dengan properti utama yang dapat dipahami LLM. Hal ini biasanya mencakup:

  • Nilai RGB (merah, hijau, biru)
  • Representasi kode heksadesimal
  • Nama atau deskripsi apa pun yang terkait dengan warna

Dengan memformat data ini secara konsisten dan menyertakannya dalam pesan, Anda memastikan LLM memiliki semua informasi yang diperlukan untuk merespons dengan tepat.

Aplikasi yang lebih luas dari Sinkronisasi Konteks LLM

Pola pemberitahuan LLM tentang peristiwa UI ini memiliki banyak aplikasi di luar pemilihan warna:

Kasus penggunaan lainnya

  1. Perubahan filter: Memberi tahu LLM saat pengguna menerapkan filter ke data
  2. Peristiwa navigasi: Memberi tahu LLM saat pengguna membuka berbagai bagian
  3. Perubahan pilihan: Memperbarui LLM saat pengguna memilih item dari daftar atau petak
  4. Pembaruan preferensi: Memberi tahu LLM saat pengguna mengubah setelan atau preferensi
  5. Manipulasi data: Memberi tahu LLM saat pengguna menambahkan, mengedit, atau menghapus data

Dalam setiap kasus, polanya tetap sama:

  1. Mendeteksi peristiwa UI
  2. Membuat serialisasi data yang relevan
  3. Mengirim notifikasi yang diformat secara khusus ke LLM
  4. Memandu LLM untuk merespons secara tepat melalui perintah sistem

Praktik terbaik untuk Sinkronisasi Konteks LLM

Berdasarkan penerapan Anda, berikut beberapa praktik terbaik untuk Sinkronisasi Konteks LLM yang efektif:

1. Format yang konsisten

Gunakan format yang konsisten untuk notifikasi sehingga LLM dapat dengan mudah mengidentifikasinya:

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

2. Konteks yang kaya

Sertakan detail yang cukup dalam notifikasi agar LLM dapat merespons dengan cerdas. Untuk warna, ini berarti nilai RGB, kode hex, dan properti relevan lainnya.

3. Petunjuk yang jelas

Berikan petunjuk yang jelas di perintah sistem tentang cara menangani notifikasi, idealnya dengan contoh.

4. Integrasi alami

Desain notifikasi agar mengalir secara alami dalam percakapan, bukan sebagai gangguan teknis.

5. Notifikasi selektif

Hanya beri tahu LLM tentang tindakan yang relevan dengan percakapan. Tidak semua peristiwa UI perlu dikomunikasikan.

Pemecahan masalah

Masalah notifikasi

Jika LLM tidak merespons dengan benar pemilihan warna:

  • Pastikan format pesan notifikasi cocok dengan yang dijelaskan dalam perintah sistem
  • Memverifikasi bahwa data warna diserialisasi dengan benar
  • Pastikan perintah sistem memiliki petunjuk yang jelas untuk menangani pilihan
  • Mencari error di layanan chat saat mengirim notifikasi

Pengelolaan konteks

Jika LLM tampaknya kehilangan konteks:

  • Pastikan sesi chat dikelola dengan benar
  • Memverifikasi bahwa status percakapan bertransisi dengan benar
  • Memastikan notifikasi dikirim melalui sesi chat yang sama

Masalah umum

Untuk masalah umum:

  • Periksa log untuk menemukan error atau peringatan
  • Memverifikasi konektivitas Vertex AI in Firebase
  • Memeriksa ketidakcocokan jenis dalam parameter fungsi
  • Memastikan semua kode yang dihasilkan Riverpod sudah yang terbaru

Konsep utama yang dipelajari

  • Membuat Sinkronisasi Konteks LLM antara UI dan LLM
  • Membuat serial peristiwa UI ke dalam konteks yang kompatibel dengan LLM
  • Memandu perilaku LLM untuk berbagai pola interaksi
  • Membuat pengalaman yang kohesif di seluruh interaksi pesan dan non-pesan
  • Meningkatkan kesadaran LLM tentang status aplikasi yang lebih luas

Dengan menerapkan Sinkronisasi Konteks LLM, Anda telah menciptakan pengalaman yang benar-benar terintegrasi, dengan LLM yang terasa seperti asisten yang tanggap dan sadar, bukan sekadar generator teks. Pola ini dapat diterapkan ke banyak aplikasi lain untuk membuat antarmuka yang lebih alami dan intuitif yang didukung AI.

9. Selamat!

Anda berhasil menyelesaikan codelab Colorist. 🎉

Yang telah Anda build

Anda telah membuat aplikasi Flutter yang berfungsi penuh dan mengintegrasikan Gemini API Google untuk menafsirkan deskripsi warna dalam bahasa sehari-hari. Aplikasi Anda kini dapat:

  • Memproses deskripsi bahasa alami seperti "oranye matahari terbenam" atau "biru laut dalam"
  • Gunakan Gemini untuk menerjemahkan deskripsi ini menjadi nilai RGB secara cerdas
  • Menampilkan warna yang ditafsirkan secara real time dengan respons streaming
  • Menangani interaksi pengguna melalui elemen chat dan UI
  • Mempertahankan kesadaran kontekstual di berbagai metode interaksi

Tujuan berikutnya

Setelah Anda menguasai dasar-dasar integrasi Gemini dengan Flutter, berikut beberapa cara untuk melanjutkan perjalanan Anda:

Meningkatkan kualitas aplikasi Colorist

  • Palet warna: Menambahkan fungsi untuk menghasilkan skema warna pelengkap atau yang cocok
  • Input suara: Mengintegrasikan pengenalan ucapan untuk deskripsi warna verbal
  • Pengelolaan histori: Menambahkan opsi untuk memberi nama, mengatur, dan mengekspor kumpulan warna
  • Perintah kustom: Membuat antarmuka bagi pengguna untuk menyesuaikan perintah sistem
  • Analisis lanjutan: Melacak deskripsi yang paling efektif atau menyebabkan masalah

Jelajahi fitur Gemini lainnya

  • Input multimodal: Menambahkan input gambar untuk mengekstrak warna dari foto
  • Pembuatan konten: Menggunakan Gemini untuk membuat konten terkait warna seperti deskripsi atau cerita
  • Peningkatan panggilan fungsi: Membuat integrasi alat yang lebih kompleks dengan beberapa fungsi
  • Setelan keamanan: Jelajahi berbagai setelan keamanan dan dampaknya terhadap respons

Menerapkan pola ini ke domain lain

  • Analisis dokumen: Membuat aplikasi yang dapat memahami dan menganalisis dokumen
  • Bantuan penulisan kreatif: Membuat alat penulisan dengan saran yang didukung LLM
  • Otomatisasi tugas: Mendesain aplikasi yang menerjemahkan bahasa alami ke dalam tugas otomatis
  • Aplikasi berbasis pengetahuan: Membuat sistem pakar di domain tertentu

Resource

Berikut adalah beberapa referensi berharga untuk melanjutkan pembelajaran Anda:

Dokumentasi resmi

Kursus dan panduan perintah

Komunitas

Seri Agentic Flutter yang dapat diamati

Dalam episode #59, Craig Labenz dan Andrew Brogden menjelajahi codelab ini, yang menyoroti bagian-bagian menarik dari build aplikasi.

Di episode #60, bergabunglah kembali dengan Craig dan Andrew saat mereka memperluas aplikasi codelab dengan kemampuan baru dan berjuang untuk membuat LLM melakukan apa yang diperintahkan.

Di episode #61, Craig bergabung dengan Chris Sells untuk mendapatkan perspektif baru dalam menganalisis judul berita dan membuat gambar yang sesuai.

Masukan

Kami ingin mendengar pengalaman Anda dengan codelab ini. Harap pertimbangkan untuk memberikan masukan melalui:

Terima kasih telah menyelesaikan codelab ini, dan kami harap Anda terus menjelajahi kemungkinan menarik di persimpangan Flutter dan AI.