Mem-build aplikasi Flutter yang didukung Gemini

1. Membangun 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 membiarkan pengguna mengontrol aplikasi Anda melalui bahasa alami, tetapi tidak tahu harus mulai dari mana? Codelab ini menunjukkan caranya.

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

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

Screenshot Aplikasi Colorist yang menampilkan tampilan warna dan antarmuka chat

Aplikasi ini memiliki antarmuka layar terpisah dengan area tampilan berwarna dan sistem chat interaktif di satu sisi, serta panel log mendetail yang menampilkan interaksi LLM mentah di sisi lainnya. 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 menimbulkan tantangan tersendiri. Codelab ini mengajarkan pola praktis yang tidak hanya sekadar panggilan API mentah.

Perjalanan pembelajaran Anda

Codelab ini akan memandu Anda dalam 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 Firebase AI Logic dan terapkan komunikasi LLM
  3. Perintah yang efektif - Buat perintah sistem yang memandu LLM untuk memahami deskripsi warna
  4. Deklarasi fungsi - Menentukan alat yang dapat digunakan LLM untuk menyetel 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 - Menciptakan pengalaman yang kohesif dengan memberi tahu LLM tentang tindakan pengguna

Yang akan Anda pelajari

  • Mengonfigurasi Firebase AI Logic untuk aplikasi Flutter
  • Buat perintah sistem yang efektif untuk memandu perilaku LLM
  • Menerapkan deklarasi fungsi yang menghubungkan 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: Gambaran tentang apa yang akan Anda terapkan

Berikut sekilas deklarasi fungsi yang akan Anda buat agar LLM dapat 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 mendiskusikan codelab ini di episode #59 Observable Flutter:

Prasyarat

Untuk mendapatkan hasil maksimal dari codelab ini, Anda harus:

  • Pengalaman pengembangan Flutter - Pemahaman tentang dasar-dasar Flutter dan sintaksis Dart
  • Pengetahuan pemrograman asinkron - Pemahaman tentang Futures, async/await, dan stream
  • Akun Firebase - Anda memerlukan Akun Google untuk menyiapkan Firebase

Mari mulai membangun 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 yang nantinya akan digantikan dengan integrasi Gemini API. Hal ini 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
  • Bekerja dengan paket colorist_ui untuk komponen UI
  • Menerapkan layanan pesan echo dan menghubungkannya ke UI

Membuat project Flutter baru

Mulailah dengan membuat project Flutter baru menggunakan 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 untuk 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

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 dan linting kode

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.9.2

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

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

flutter:
  uses-material-design: true

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(chatStateProvider.notifier);
    final logStateNotifier = ref.read(logStateProvider.notifier);

    chatStateNotifier.addUserMessage(message);
    logStateNotifier.logUserText(message);
    chatStateNotifier.addLlmMessage(message, MessageState.complete);
    logStateNotifier.logLlmText(message);
  }
}

Hal ini menyiapkan aplikasi Flutter yang mengimplementasikan layanan echo yang meniru perilaku LLM dengan menampilkan pesan pengguna.

Memahami arsitektur

Mari luangkan waktu sejenak untuk memahami arsitektur aplikasi colorist:

Paket colorist_ui

Paket colorist_ui menyediakan komponen UI siap pakai dan alat pengelolaan status:

  1. MainScreen: Komponen UI utama yang menampilkan:
    • Tata letak layar terpisah di desktop (area interaksi dan panel log)
    • Antarmuka bertab 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 dalam proses 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. Service Layer: Saat ini berisi layanan echo sederhana Anda, yang akan diganti dengan Gemini Chat Service
  4. Integrasi LLM: Akan ditambahkan pada langkah selanjutnya

Dengan pemisahan ini, Anda dapat berfokus pada penerapan integrasi LLM, sementara 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 layanan echo yang merender markdown

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 selanjutnya, Anda akan mengganti nilai ini dengan interpretasi warna sebenarnya menggunakan Firebase AI Logic.

Apa langkah selanjutnya?

Pada langkah berikutnya, Anda akan mengonfigurasi Firebase dan menerapkan integrasi Gemini API dasar untuk menggantikan layanan echo dengan layanan chat Gemini. Hal ini akan memungkinkan aplikasi menafsirkan deskripsi warna dan memberikan respons 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
  • Periksa apakah ada versi paket yang bertentangan

Error build

Jika Anda melihat error build:

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

Konsep utama yang dipelajari

  • Menyiapkan project Flutter dengan dependensi yang diperlukan
  • Memahami arsitektur aplikasi dan tanggung jawab komponen
  • 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 Firebase AI Logic. 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 Firebase AI Logic untuk akses Gemini
  • Membuat penyedia Riverpod untuk layanan Firebase dan Gemini
  • Menerapkan layanan chat dasar dengan Gemini API
  • Menangani respons API asinkron dan status error

Menyiapkan Firebase

Pertama, Anda perlu menyiapkan Firebase untuk project Flutter Anda. Hal ini mencakup pembuatan project Firebase, penambahan aplikasi Anda ke project tersebut, dan konfigurasi setelan Logika AI Firebase 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.

Menyiapkan Logika AI Firebase di project Firebase Anda

  1. Di Firebase console, buka project Anda.
  2. Di sidebar kiri, pilih AI.
  3. Di menu drop-down AI, pilih AI Logic.
  4. Di kartu Firebase AI Logic, pilih Mulai.
  5. Ikuti perintah untuk mengaktifkan Gemini Developer API untuk project Anda.

Instal 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 Firebase AI Logic ke project Anda:
flutter pub add firebase_core firebase_ai
  1. Jalankan perintah konfigurasi FlutterFire:
flutterfire configure

Perintah ini akan:

  • Meminta Anda memilih project Firebase yang baru saja Anda buat
  • Mendaftarkan aplikasi Flutter Anda ke Firebase
  • Buat file firebase_options.dart dengan konfigurasi project Anda

Perintah 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 daripada default untuk Flutter. Aplikasi ini juga memerlukan akses jaringan untuk berkomunikasi dengan server Firebase AI Logic.

Mengonfigurasi izin macOS

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

  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.

Mengonfigurasi setelan iOS

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

ios/Podfile

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

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_ai/firebase_ai.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';

import '../firebase_options.dart';

part 'gemini.g.dart';

@riverpod
Future<FirebaseApp> firebaseApp(Ref ref) =>
    Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);

@riverpod
Future<GenerativeModel> geminiModel(Ref ref) async {
  await ref.watch(firebaseAppProvider.future);

  final model = FirebaseAI.googleAI().generativeModel(
    model: 'gemini-2.0-flash',
  );
  return model;
}

@Riverpod(keepAlive: true)
Future<ChatSession> chatSession(Ref ref) async {
  final model = await ref.watch(geminiModelProvider.future);
  return model.startChat();
}

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

  1. firebaseAppProvider: Menginisialisasi 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 memastikan sesi tersebut tetap ada di seluruh siklus proses aplikasi, sehingga mempertahankan konteks percakapan.

Menerapkan layanan chat Gemini

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

lib/services/gemini_chat_service.dart

import 'dart:async';

import 'package:colorist_ui/colorist_ui.dart';
import 'package:firebase_ai/firebase_ai.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';

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

part 'gemini_chat_service.g.dart';

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

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

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

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

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

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: Jendela Log akan terlihat hampir sama dengan jendela chat pada tahap ini. Log akan menjadi lebih menarik setelah Anda memperkenalkan panggilan fungsi dan kemudian respons streaming.

Membuat kode Riverpod

Jalankan perintah build runner 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.

Perbarui file main.dart

Perbarui file lib/main.dart Anda untuk menggunakan layanan Gemini Chat 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 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 kita 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 Firebase AI Logic
  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 pada penyedia sesi chat Anda memastikan konteks ini tetap ada di seluruh siklus proses aplikasi. Konteks persisten ini sangat penting untuk mempertahankan alur percakapan alami dengan LLM.

Apa langkah selanjutnya?

Pada tahap ini, Anda dapat bertanya apa pun kepada Gemini API, karena tidak ada batasan terkait apa yang akan diresponsnya. Misalnya, Anda dapat memintanya untuk membuat 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. Bagian 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 saat inisialisasi Firebase:

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

Error akses API

Jika Anda mengalami error saat mengakses Gemini API:

  • Konfirmasi bahwa penagihan telah disiapkan dengan benar di project Firebase Anda
  • Periksa apakah Firebase AI Logic 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 percakapan:

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

Masalah khusus platform

Untuk masalah khusus platform:

  • iOS/macOS: Pastikan hak yang tepat ditetapkan dan versi minimum dikonfigurasi
  • Android: Verifikasi bahwa versi SDK minimum telah ditetapkan dengan benar
  • Periksa pesan error khusus platform di konsol

Konsep utama yang dipelajari

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

4. Penulisan 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 efektif untuk menyesuaikan perilaku LLM untuk tugas tertentu tanpa mengubah kode Anda.

Yang akan Anda pelajari di langkah ini

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

Memahami perintah sistem

Sebelum mempelajari implementasinya, mari kita pahami apa itu perintah sistem dan mengapa perintah tersebut penting:

Apa itu perintah sistem?

Perintah sistem adalah jenis petunjuk khusus yang diberikan kepada 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 "deskripsi tugas" LLM - perintah ini memberi tahu model cara berperilaku sepanjang percakapan.

Mengapa perintah sistem itu penting

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

  1. Memastikan konsistensi: Memandu model untuk memberikan respons dalam format yang konsisten
  2. Meningkatkan relevansi: Memfokuskan model pada domain spesifik Anda (dalam kasus Anda, warna)
  3. Menetapkan batasan: Tentukan hal-hal yang boleh dan tidak boleh dilakukan model
  4. Meningkatkan pengalaman pengguna: Menciptakan pola interaksi yang lebih alami dan bermanfaat
  5. Mengurangi pasca-pemrosesan: Dapatkan respons dalam format yang lebih mudah diuraikan atau ditampilkan

Untuk aplikasi Colorist, Anda memerlukan LLM agar secara konsisten menafsirkan deskripsi warna 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 assets/system_prompt.md baru 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 ahli warna"
  2. Penjelasan tugas: Menentukan tugas utama sebagai menafsirkan deskripsi warna ke dalam nilai RGB
  3. Format respons: Menentukan secara persis cara nilai RGB harus diformat agar konsisten
  4. Contoh pertukaran: Memberikan contoh konkret dari pola interaksi yang diharapkan
  5. Penanganan kasus ekstrem: Memberikan petunjuk cara menangani deskripsi yang tidak jelas
  6. Batasan dan panduan: Menetapkan batas seperti menjaga nilai RGB antara 0,0 dan 1,0

Pendekatan terstruktur ini memastikan respons LLM akan konsisten, informatif, dan diformat dengan cara yang mudah diuraikan jika Anda ingin mengekstrak nilai RGB secara terprogram.

Perbarui pubspec.yaml

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

pubspec.yaml

flutter:
  uses-material-design: true

  assets:
    - assets/

Jalankan flutter pub get untuk memperbarui 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: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.

Memperbarui 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_ai/firebase_ai.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';

import '../firebase_options.dart';
import 'system_prompt.dart';                                          // Add this import

part 'gemini.g.dart';

@riverpod
Future<FirebaseApp> firebaseApp(Ref ref) =>
    Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);

@riverpod
Future<GenerativeModel> geminiModel(Ref ref) async {
  await ref.watch(firebaseAppProvider.future);
  final systemPrompt = await ref.watch(systemPromptProvider.future);  // Add this line

  final model = FirebaseAI.googleAI().generativeModel(
    model: 'gemini-2.0-flash',
    systemInstruction: Content.system(systemPrompt),                  // And this line
  );
  return model;
}

@Riverpod(keepAlive: true)
Future<ChatSession> chatSession(Ref ref) async {
  final model = await ref.watch(geminiModelProvider.future);
  return model.startChat();
}

Perubahan utamanya 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 percakapan ini.

Membuat kode Riverpod

Jalankan perintah build runner 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 merespons dengan respons sesuai karakter untuk aplikasi pemilihan warna

Coba uji dengan berbagai deskripsi warna:

  • "Saya ingin warna biru langit"
  • "Give me a forest green" (Beri saya warna hijau hutan)
  • "Buat warna oranye senja yang cerah"
  • "Saya ingin warna lavender segar"
  • "Show me something like a deep ocean blue" (Tampilkan sesuatu seperti warna biru laut dalam)

Anda akan melihat bahwa Gemini kini merespons dengan penjelasan percakapan tentang warna beserta nilai RGB yang diformat secara konsisten. Perintah sistem telah secara efektif memandu LLM 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 sains. Hal ini merupakan bagian penting dari integrasi LLM yang dapat memengaruhi secara signifikan seberapa berguna model tersebut untuk aplikasi spesifik Anda. Yang telah Anda lakukan di sini adalah bentuk rekayasa prompt - menyesuaikan petunjuk agar model berperilaku sesuai dengan kebutuhan aplikasi Anda.

Rekayasa perintah yang efektif melibatkan:

  1. Definisi peran yang jelas: Menetapkan tujuan LLM
  2. Petunjuk eksplisit: Menjelaskan secara persis bagaimana LLM harus merespons
  3. Contoh konkret: Menunjukkan, bukan hanya memberi tahu seperti apa respons yang baik
  4. Penanganan kasus ekstrem: Menginstruksikan LLM tentang cara menangani skenario 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 interpretasi warna khusus yang memberikan respons yang diformat secara khusus untuk kebutuhan aplikasi Anda. Ini adalah pola canggih yang dapat Anda terapkan ke berbagai domain dan tugas.

Apa langkah selanjutnya?

Pada langkah berikutnya, Anda akan membangun 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 Anda mencantumkan direktori aset dengan benar
  • Pastikan jalur di rootBundle.loadString() cocok dengan lokasi file Anda
  • Jalankan flutter clean, diikuti dengan flutter pub get untuk memperbarui paket aset

Respons tidak konsisten

Jika LLM tidak secara konsisten mengikuti petunjuk format Anda:

  • Coba buat persyaratan format yang lebih jelas dalam perintah sistem
  • Tambahkan lebih banyak contoh 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 Firebase AI Logic memiliki batas penggunaan
  • Pertimbangkan untuk menerapkan logika percobaan ulang dengan backoff eksponensial
  • Periksa Firebase console Anda untuk mengetahui apakah ada 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
  • Memandu 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 Anda - cukup dengan memberikan petunjuk yang jelas dalam 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 juga menetapkannya di UI aplikasi Anda melalui panggilan alat khusus. Namun, langkah berikutnya diperlukan untuk melihat permintaan LLM yang dieksekusi 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 Anda
  • Memperbarui perintah sistem untuk memanfaatkan kemampuan alat

Memahami pemanggilan fungsi

Sebelum menerapkan deklarasi fungsi, mari kita pahami apa itu dan mengapa hal ini penting:

Apa itu panggilan fungsi?

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

  1. Mengenali kapan permintaan pengguna akan diuntungkan dengan memanggil fungsi tertentu
  2. Buat objek JSON terstruktur dengan parameter yang diperlukan untuk fungsi tersebut
  3. Biarkan aplikasi Anda menjalankan fungsi dengan parameter tersebut
  4. Menerima hasil fungsi dan memasukkannya ke dalam responsnya

Daripada hanya mendeskripsikan apa yang harus dilakukan, panggilan fungsi memungkinkan LLM memicu tindakan konkret di aplikasi Anda.

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 perhitungan, 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 mengatakan "Saya ingin warna hijau hutan" dan UI akan segera diperbarui dengan warna tersebut, tanpa harus mengurai nilai RGB dari teks.

Menentukan deklarasi fungsi

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

lib/services/gemini_tools.dart

import 'package:firebase_ai/firebase_ai.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';

part 'gemini_tools.g.dart';

class GeminiTools {
  GeminiTools(this.ref);

  final Ref ref;

  FunctionDeclaration get setColorFuncDecl => FunctionDeclaration(
    'set_color',
    'Set the color of the display square based on red, green, and blue values.',
    parameters: {
      'red': Schema.number(description: 'Red component value (0.0 - 1.0)'),
      'green': Schema.number(description: 'Green component value (0.0 - 1.0)'),
      'blue': Schema.number(description: 'Blue component value (0.0 - 1.0)'),
    },
  );

  List<Tool> get tools => [
    Tool.functionDeclarations([setColorFuncDecl]),
  ];
}

@riverpod
GeminiTools geminiTools(Ref ref) => GeminiTools(ref);

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 untuk membantu LLM memahami kapan harus menggunakannya
  3. Definisi parameter: Anda menentukan parameter terstruktur dengan deskripsinya sendiri:
    • red: Komponen merah RGB, ditentukan sebagai angka antara 0,0 dan 1,0
    • green: Komponen hijau RGB, ditentukan sebagai angka antara 0,0 dan 1,0
    • blue: Komponen biru RGB, 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 Anda

Pendekatan terstruktur ini membantu LLM Gemini memahami:

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

Memperbarui penyedia model Gemini

Sekarang, ubah file lib/providers/gemini.dart Anda untuk menyertakan deklarasi fungsi saat menginisialisasi model Gemini:

lib/providers/gemini.dart

import 'dart:async';

import 'package:firebase_ai/firebase_ai.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';

import '../firebase_options.dart';
import '../services/gemini_tools.dart';                              // Add this import
import 'system_prompt.dart';

part 'gemini.g.dart';

@riverpod
Future<FirebaseApp> firebaseApp(Ref ref) =>
    Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);

@riverpod
Future<GenerativeModel> geminiModel(Ref ref) async {
  await ref.watch(firebaseAppProvider.future);
  final systemPrompt = await ref.watch(systemPromptProvider.future);
  final geminiTools = ref.watch(geminiToolsProvider);                // Add this line

  final model = FirebaseAI.googleAI().generativeModel(
    model: 'gemini-2.0-flash',
    systemInstruction: Content.system(systemPrompt),
    tools: geminiTools.tools,                                        // And this line
  );
  return model;
}

@Riverpod(keepAlive: true)
Future<ChatSession> chatSession(Ref ref) async {
  final model = await ref.watch(geminiModelProvider.future);
  return model.startChat();
}

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

Memperbarui perintah sistem

Sekarang Anda perlu mengubah perintah sistem untuk menginstruksikan LLM tentang penggunaan alat set_color yang 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. Pengenalan alat: Alih-alih meminta nilai RGB yang diformat, Anda sekarang memberi tahu LLM tentang alat set_color
  2. Proses yang diubah: Anda mengubah langkah 3 dari "format nilai dalam respons" menjadi "gunakan alat untuk menetapkan nilai"
  3. Contoh yang diperbarui: Anda menunjukkan cara respons harus menyertakan panggilan alat, bukan teks yang diformat
  4. Persyaratan pemformatan dihapus: Karena Anda menggunakan panggilan fungsi terstruktur, Anda tidak lagi memerlukan 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 build runner 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 handler untuk panggilan fungsi. Saat Anda 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 merespons dengan respons sebagian

Coba deskripsikan warna seperti "biru laut dalam" atau "hijau hutan" dan amati responsnya. LLM mencoba memanggil fungsi yang ditentukan di atas, tetapi kode Anda belum mendeteksi panggilan fungsi.

Proses panggilan fungsi

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

  1. Pemilihan fungsi: LLM memutuskan apakah panggilan fungsi akan bermanfaat berdasarkan permintaan pengguna
  2. Pembuatan parameter: LLM membuat nilai parameter yang sesuai dengan skema fungsi
  3. Format panggilan fungsi: LLM mengirim 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 bolak-balik, LLM mengharapkan hasil fungsi ditampilkan

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

Detail teknis: Cara Gemini memutuskan kapan harus menggunakan fungsi

Gemini membuat keputusan cerdas tentang kapan harus menggunakan fungsi berdasarkan:

  1. Maksud pengguna: Apakah permintaan pengguna akan lebih baik dipenuhi oleh fungsi
  2. Relevansi fungsi: Seberapa baik kecocokan fungsi yang tersedia dengan tugas
  3. Ketersediaan parameter: Apakah dapat menentukan nilai parameter dengan yakin
  4. Petunjuk sistem: Panduan dari perintah sistem Anda 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 handler untuk panggilan fungsi yang berasal dari Gemini. Hal ini akan melengkapi siklus, sehingga deskripsi pengguna dapat memicu perubahan warna aktual 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 dalam perintah sistem menunjukkan penggunaan fungsi
  • Coba buat petunjuk penggunaan alat lebih jelas

Masalah umum

Jika Anda mengalami masalah lain:

  • Periksa konsol untuk mengetahui apakah ada error terkait deklarasi fungsi
  • Pastikan alat diteruskan dengan benar ke model
  • Pastikan semua kode yang dihasilkan Riverpod sudah 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, sehingga meletakkan dasar untuk integrasi yang lancar antara percakapan dan fitur aplikasi.

6. Menerapkan penanganan alat

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

Yang akan Anda pelajari di langkah ini

  • Memahami pipeline panggilan fungsi lengkap dalam aplikasi LLM
  • Memproses panggilan fungsi dari Gemini di aplikasi Flutter
  • Menerapkan handler 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 end-to-end

  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 diberikan
  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 melakukan panggilan fungsi, LLM tidak hanya mengirimkan permintaan dan melanjutkan. Sebagai gantinya, fungsi ini menunggu aplikasi Anda menjalankan fungsi dan menampilkan hasil. Kemudian, LLM menggunakan hasil ini untuk merumuskan respons akhirnya, sehingga menciptakan alur percakapan alami yang mengakui tindakan yang telah dilakukan.

Menerapkan pengendali fungsi

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

lib/services/gemini_tools.dart

import 'package:colorist_ui/colorist_ui.dart';
import 'package:firebase_ai/firebase_ai.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';

part 'gemini_tools.g.dart';

class GeminiTools {
  GeminiTools(this.ref);

  final Ref ref;

  FunctionDeclaration get setColorFuncDecl => FunctionDeclaration(
    'set_color',
    'Set the color of the display square based on red, green, and blue values.',
    parameters: {
      'red': Schema.number(description: 'Red component value (0.0 - 1.0)'),
      'green': Schema.number(description: 'Green component value (0.0 - 1.0)'),
      'blue': Schema.number(description: 'Blue component value (0.0 - 1.0)'),
    },
  );

  List<Tool> get tools => [
    Tool.functionDeclarations([setColorFuncDecl]),
  ];

  Map<String, Object?> handleFunctionCall(                           // Add from here
    String functionName,
    Map<String, Object?> arguments,
  ) {
    final logStateNotifier = ref.read(logStateProvider.notifier);
    logStateNotifier.logFunctionCall(functionName, arguments);
    return switch (functionName) {
      'set_color' => handleSetColor(arguments),
      _ => handleUnknownFunction(functionName),
    };
  }

  Map<String, Object?> handleSetColor(Map<String, Object?> arguments) {
    final colorStateNotifier = ref.read(colorStateProvider.notifier);
    final red = (arguments['red'] as num).toDouble();
    final green = (arguments['green'] as num).toDouble();
    final blue = (arguments['blue'] as num).toDouble();
    final functionResults = {
      'success': true,
      'current_color': colorStateNotifier
          .updateColor(red: red, green: green, blue: blue)
          .toLLMContextMap(),
    };

    final logStateNotifier = ref.read(logStateProvider.notifier);
    logStateNotifier.logFunctionResults(functionResults);
    return functionResults;
  }

  Map<String, Object?> handleUnknownFunction(String functionName) {
    final logStateNotifier = ref.read(logStateProvider.notifier);
    logStateNotifier.logWarning('Unsupported function call $functionName');
    return {
      'success': false,
      'reason': 'Unsupported function call $functionName',
    };
  }                                                                  // To here.
}

@riverpod
GeminiTools geminiTools(Ref ref) => GeminiTools(ref);

Memahami handler fungsi

Mari kita uraikan fungsi handler ini:

  1. handleFunctionCall: Dispatcher 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: Handler khusus untuk fungsi set_color Anda yang:
    • Mengekstrak nilai RGB dari peta argumen
    • Mengonversinya ke jenis yang diharapkan (double)
    • Memperbarui status warna aplikasi menggunakan colorStateNotifier
    • Membuat respons terstruktur dengan status berhasil dan informasi warna saat ini
    • Mencatat hasil fungsi 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 yang 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 mengirimkan hasilnya kembali ke LLM:

lib/services/gemini_chat_service.dart

import 'dart:async';

import 'package:colorist_ui/colorist_ui.dart';
import 'package:firebase_ai/firebase_ai.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';

import '../providers/gemini.dart';
import 'gemini_tools.dart';                                          // Add this import

part 'gemini_chat_service.g.dart';

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

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

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

      final responseText = response.text;
      if (responseText != null) {
        logStateNotifier.logLlmText(responseText);
        chatStateNotifier.appendToMessage(llmMessage.id, responseText);
      }

      if (response.functionCalls.isNotEmpty) {                       // Add from here
        final geminiTools = ref.read(geminiToolsProvider);
        final functionResultResponse = await chatSession.sendMessage(
          Content.functionResponses([
            for (final functionCall in response.functionCalls)
              FunctionResponse(
                functionCall.name,
                geminiTools.handleFunctionCall(
                  functionCall.name,
                  functionCall.args,
                ),
              ),
          ]),
        );
        final responseText = functionResultResponse.text;
        if (responseText != null) {
          logStateNotifier.logLlmText(responseText);
          chatStateNotifier.appendToMessage(llmMessage.id, responseText);
        }
      }                                                              // To here.
    } catch (e, st) {
      logStateNotifier.logError(e, st: st);
      chatStateNotifier.appendToMessage(
        llmMessage.id,
        "\nI'm sorry, I encountered an error processing your request. "
        "Please try again.",
      );
    } finally {
      chatStateNotifier.finalizeMessage(llmMessage.id);
    }
  }
}

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

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 kembali hasil ini ke LLM menggunakan Content.functionResponses
  5. Memproses respons LLM terhadap hasil fungsi
  6. Mengupdate UI dengan teks respons akhir

Tindakan ini akan membuat alur perjalanan pulang pergi:

  • 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 build runner 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 merespons dengan panggilan fungsi

Coba masukkan berbagai deskripsi warna:

  • "Saya ingin warna merah kirmizi tua"
  • "Show me a calming sky blue" (Tampilkan warna biru langit yang menenangkan)
  • "Beri tahu saya warna daun mint segar"
  • "I want to see a warm sunset orange" (Saya ingin melihat warna oranye senja yang hangat)
  • "Buat warna ungu kerajaan yang kaya"

Sekarang Anda akan melihat:

  1. Pesan Anda muncul di antarmuka chat
  2. Respons Gemini muncul dalam percakapan
  3. Panggilan fungsi yang dicatat dalam panel log
  4. Hasil fungsi dicatat dalam log segera setelah
  5. Persegi panjang warna diperbarui 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 persis yang dilakukan Gemini
  • Parameter yang dipilihnya untuk setiap nilai RGB
  • Hasil yang ditampilkan fungsi Anda
  • Respons lanjutan dari Gemini

Pemberi tahu 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)
  • Pemberitahuan 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: Membungkus semua interaksi LLM untuk menangkap pengecualian apa pun
  2. Pencatatan error: Mencatat error di panel log dengan stack trace
  3. Masukan pengguna: Memberikan pesan error yang mudah dipahami dalam chat
  4. Pembersihan status: Menyelesaikan status pesan meskipun terjadi error

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

Manfaat panggilan fungsi untuk pengalaman pengguna

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

  1. Antarmuka natural language: Pengguna menyatakan maksud dalam bahasa sehari-hari
  2. Interpretasi 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 untuk menjembatani bahasa alami dan tindakan UI ini dapat diperluas ke banyak domain lain di luar pemilihan warna.

Apa langkah selanjutnya?

Pada langkah berikutnya, Anda akan meningkatkan kualitas 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 Anda atau parameter salah:

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

Masalah respons fungsi

Jika hasil fungsi tidak dikirim kembali ke LLM dengan benar:

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

Masalah tampilan warna

Jika warna tidak ditampilkan dengan benar:

  • Pastikan nilai RGB dikonversi dengan benar menjadi 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 error atau peringatan dalam log
  • Memverifikasi konektivitas Firebase AI Logic
  • Periksa apakah ada ketidakcocokan jenis dalam parameter fungsi
  • Pastikan semua kode yang dihasilkan Riverpod sudah 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 dimasukkan ke dalam respons
  • Menggunakan panel log untuk mendapatkan visibilitas ke dalam interaksi aplikasi LLM
  • Menghubungkan input bahasa alami dengan perubahan UI yang konkret

Setelah langkah ini selesai, aplikasi Anda kini menunjukkan salah satu pola paling efektif untuk integrasi LLM: menerjemahkan input bahasa alami ke dalam tindakan UI yang konkret, sekaligus mempertahankan percakapan yang koheren yang mengakui 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 kualitas 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
  • Menerapkan respons LLM streaming di aplikasi Flutter
  • Memproses potongan teks parsial 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

Mengapa streaming penting untuk aplikasi LLM

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

Pengalaman pengguna yang ditingkatkan

Respons streaming memberikan beberapa manfaat signifikan bagi pengalaman pengguna:

  1. Mengurangi latensi yang dirasakan: Pengguna melihat teks mulai muncul dengan segera (biasanya dalam waktu 100-300 md), bukan menunggu beberapa detik untuk mendapatkan respons lengkap. Persepsi tentang keinstanan ini secara signifikan meningkatkan kepuasan pengguna.
  2. Ritme percakapan alami: Teks yang muncul secara bertahap meniru cara manusia berkomunikasi, sehingga menciptakan pengalaman dialog yang lebih alami.
  3. Pemrosesan informasi progresif: Pengguna dapat mulai memproses informasi saat informasi tersebut tiba, daripada kewalahan dengan blok teks besar sekaligus.
  4. Peluang untuk interupsi awal: Dalam aplikasi lengkap, pengguna berpotensi menginterupsi atau mengalihkan LLM jika mereka melihatnya bergerak 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 dalam stream, tanpa menunggu respons lengkap.
  2. Update UI inkremental: Anda dapat memperbarui UI secara progresif saat informasi baru tiba, sehingga menciptakan pengalaman yang lebih dinamis.
  3. Pengelolaan status percakapan: Streaming memberikan sinyal yang jelas tentang kapan respons selesai dan kapan masih dalam proses, sehingga memungkinkan pengelolaan status yang lebih baik.
  4. Mengurangi risiko waktu tunggu habis: Dengan respons non-streaming, pembuatan yang berjalan lama berisiko menyebabkan waktu tunggu koneksi habis. Streaming membuat koneksi lebih awal dan mempertahankannya.

Untuk aplikasi Colorist Anda, penerapan streaming berarti pengguna akan melihat respons teks dan perubahan warna yang 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. Perbarui 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_ai/firebase_ai.dart';
import 'package:flutter_riverpod/legacy.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';

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

part 'gemini_chat_service.g.dart';

final conversationStateProvider = StateProvider(                     // Add from here...
  (ref) => ConversationState.idle,
);                                                                   // To here.

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

  Future<void> sendMessage(String message) async {
    final chatSession = await ref.read(chatSessionProvider.future);
    final conversationState = ref.read(conversationStateProvider);   // Add this line
    final chatStateNotifier = ref.read(chatStateProvider.notifier);
    final logStateNotifier = ref.read(logStateProvider.notifier);

    if (conversationState == ConversationState.busy) {               // Add from here...
      logStateNotifier.logWarning(
        "Can't send a message while a conversation is in progress",
      );
      throw Exception(
        "Can't send a message while a conversation is in progress",
      );
    }
    final conversationStateNotifier = ref.read(
      conversationStateProvider.notifier,
    );
    conversationStateNotifier.state = ConversationState.busy;        // To here.
    chatStateNotifier.addUserMessage(message);
    logStateNotifier.logUserText(message);
    final llmMessage = chatStateNotifier.createLlmMessage();
    try {                                                            // Modify from here...
      final responseStream = chatSession.sendMessageStream(
        Content.text(message),
      );
      await for (final block in responseStream) {
        await _processBlock(block, llmMessage.id);
      }                                                              // To here.
    } catch (e, st) {
      logStateNotifier.logError(e, st: st);
      chatStateNotifier.appendToMessage(
        llmMessage.id,
        "\nI'm sorry, I encountered an error processing your request. "
        "Please try again.",
      );
    } finally {
      chatStateNotifier.finalizeMessage(llmMessage.id);
      conversationStateNotifier.state = ConversationState.idle;      // Add this line.
    }
  }

  Future<void> _processBlock(                                        // Add from here...
    GenerateContentResponse block,
    String llmMessageId,
  ) async {
    final chatSession = await ref.read(chatSessionProvider.future);
    final chatStateNotifier = ref.read(chatStateProvider.notifier);
    final logStateNotifier = ref.read(logStateProvider.notifier);
    final blockText = block.text;

    if (blockText != null) {
      logStateNotifier.logLlmText(blockText);
      chatStateNotifier.appendToMessage(llmMessageId, blockText);
    }

    if (block.functionCalls.isNotEmpty) {
      final geminiTools = ref.read(geminiToolsProvider);
      final responseStream = chatSession.sendMessageStream(
        Content.functionResponses([
          for (final functionCall in block.functionCalls)
            FunctionResponse(
              functionCall.name,
              geminiTools.handleFunctionCall(
                functionCall.name,
                functionCall.args,
              ),
            ),
        ]),
      );
      await for (final response in responseStream) {
        final responseText = response.text;
        if (responseText != null) {
          logStateNotifier.logLlmText(responseText);
          chatStateNotifier.appendToMessage(llmMessageId, responseText);
        }
      }
    }
  }                                                                  // To here.
}

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

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 diproses, 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 potongan saat tiba secara real-time
    • Teks ditambahkan ke UI secara langsung, sehingga menciptakan efek streaming
    • Panggilan fungsi akan dieksekusi segera setelah terdeteksi
  4. Penanganan panggilan fungsi:
    • Saat panggilan fungsi terdeteksi dalam chunk, panggilan tersebut akan segera dieksekusi
    • Hasil dikirim kembali ke LLM melalui panggilan streaming lain
    • Respons LLM terhadap hasil ini juga diproses secara streaming
  5. Penanganan error dan pembersihan:
    • try/catch menyediakan 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 di mana UI mencerminkan status percakapan saat ini.

Membuat kode Riverpod

Jalankan perintah build runner 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 merespons secara streaming

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

  • "Show me the deep teal color of the ocean at twilight" (Tampilkan warna biru kehijauan laut saat senja)
  • "Saya ingin melihat karang cerah yang mengingatkan saya pada bunga tropis"
  • "Buat warna hijau zaitun redup seperti seragam tentara lama"

Alur teknis streaming secara mendetail

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

Pembuatan koneksi

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

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

Transmisi bagian

Saat Gemini membuat konten, potongan konten dikirim melalui aliran:

  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. Chunk teks tambahan dapat mengikuti panggilan fungsi
  4. Streaming berlanjut hingga pembuatan selesai

Pemrosesan progresif

Aplikasi Anda memproses setiap bagian secara inkremental:

  1. Setiap bagian teks ditambahkan ke respons yang ada
  2. Panggilan fungsi akan 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. Aliran ditutup oleh server
  2. Loop await for Anda keluar secara alami
  3. Pesan ditandai sebagai selesai
  4. Status percakapan disetel 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:

Rasio

Non-Streaming

Streaming

Latensi yang dirasakan

Pengguna tidak melihat apa pun hingga respons lengkap siap

Pengguna melihat kata-kata pertama dalam milidetik

Pengalaman pengguna

Penantian lama yang diikuti dengan kemunculan teks secara tiba-tiba

Tampilan teks yang alami dan progresif

Pengelolaan status

Lebih sederhana (pesan tertunda atau selesai)

Lebih kompleks (pesan dapat 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

Tidak terlalu rumit

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 lancar 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: Desain UI Anda untuk menangani hasil fungsi yang muncul di tengah proses
  8. Pengoptimalan performa: Minimalkan pembangunan ulang UI selama pembaruan streaming cepat

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

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 di mana LLM mengetahui perubahan yang dimulai pengguna pada status aplikasi.

Pemecahan masalah

Masalah pemrosesan streaming

Jika Anda mengalami masalah dengan pemrosesan streaming:

  • Gejala: Respons sebagian, teks hilang, atau penghentian streaming yang tiba-tiba
  • Solusi: Periksa konektivitas jaringan dan pastikan pola async/await yang tepat dalam kode Anda
  • Diagnosis: Periksa panel log untuk melihat pesan error atau peringatan terkait pemrosesan streaming
  • Perbaikan: Pastikan semua pemrosesan streaming menggunakan penanganan error yang tepat dengan blok try/catch

Panggilan fungsi yang tidak ada

Jika panggilan fungsi tidak terdeteksi dalam 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 menginstruksikan LLM secara lebih eksplisit agar menggunakan alat set_color

Penanganan error umum

Untuk masalah lainnya:

  • Langkah 1: Periksa panel log untuk melihat pesan error
  • Langkah 2: Verifikasi konektivitas Firebase AI Logic
  • Langkah 3: Pastikan semua kode yang dibuat Riverpod sudah terbaru
  • Langkah 4: Tinjau penerapan streaming untuk menemukan pernyataan tunggu yang tidak ada

Konsep utama yang dipelajari

  • Menerapkan respons streaming dengan Gemini API untuk UX yang lebih responsif
  • Mengelola status percakapan untuk menangani interaksi streaming dengan benar
  • Memproses teks real-time dan panggilan fungsi saat tiba
  • 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 terasa benar-benar seperti percakapan.

8. Sinkronisasi Konteks LLM

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

Yang akan Anda bahas dalam langkah ini

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

Memahami Sinkronisasi Konteks LLM

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

Alasan 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 Anda memberitahunya secara eksplisit. Sinkronisasi Konteks LLM:

  1. Mempertahankan konteks: Terus memberi tahu LLM tentang semua tindakan pengguna yang relevan
  2. Menciptakan koherensi: Menghasilkan pengalaman yang kohesif saat LLM mengakui 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 cerdas tentang warna yang dipilih, sehingga mempertahankan ilusi asisten yang lancar dan sadar.

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. Perbarui 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_ai/firebase_ai.dart';
import 'package:flutter_riverpod/legacy.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';

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

part 'gemini_chat_service.g.dart';

final conversationStateProvider = StateProvider(
  (ref) => ConversationState.idle,
);

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

  Future<void> notifyColorSelection(ColorData color) => sendMessage(  // Add from here...
    'User selected color from history: ${json.encode(color.toLLMContextMap())}',
  );                                                                  // To here.

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

    if (conversationState == ConversationState.busy) {
      logStateNotifier.logWarning(
        "Can't send a message while a conversation is in progress",
      );
      throw Exception(
        "Can't send a message while a conversation is in progress",
      );
    }
    final conversationStateNotifier = ref.read(
      conversationStateProvider.notifier,
    );
    conversationStateNotifier.state = ConversationState.busy;
    chatStateNotifier.addUserMessage(message);
    logStateNotifier.logUserText(message);
    final llmMessage = chatStateNotifier.createLlmMessage();
    try {
      final responseStream = chatSession.sendMessageStream(
        Content.text(message),
      );
      await for (final block in responseStream) {
        await _processBlock(block, llmMessage.id);
      }
    } catch (e, st) {
      logStateNotifier.logError(e, st: st);
      chatStateNotifier.appendToMessage(
        llmMessage.id,
        "\nI'm sorry, I encountered an error processing your request. "
        "Please try again.",
      );
    } finally {
      chatStateNotifier.finalizeMessage(llmMessage.id);
      conversationStateNotifier.state = ConversationState.idle;
    }
  }

  Future<void> _processBlock(
    GenerateContentResponse block,
    String llmMessageId,
  ) async {
    final chatSession = await ref.read(chatSessionProvider.future);
    final chatStateNotifier = ref.read(chatStateProvider.notifier);
    final logStateNotifier = ref.read(logStateProvider.notifier);
    final blockText = block.text;

    if (blockText != null) {
      logStateNotifier.logLlmText(blockText);
      chatStateNotifier.appendToMessage(llmMessageId, blockText);
    }

    if (block.functionCalls.isNotEmpty) {
      final geminiTools = ref.read(geminiToolsProvider);
      final responseStream = chatSession.sendMessageStream(
        Content.functionResponses([
          for (final functionCall in block.functionCalls)
            FunctionResponse(
              functionCall.name,
              geminiTools.handleFunctionCall(
                functionCall.name,
                functionCall.args,
              ),
            ),
        ]),
      );
      await for (final response in responseStream) {
        final responseText = response.text;
        if (responseText != null) {
          logStateNotifier.logLlmText(responseText);
          chatStateNotifier.appendToMessage(llmMessageId, responseText);
        }
      }
    }
  }
}

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

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 pilihan pengguna
  4. Menggunakan kembali metode sendMessage yang ada untuk menangani notifikasi

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

Memperbarui aplikasi utama untuk menghubungkan notifikasi pilihan warna

Sekarang, ubah file lib/main.dart Anda 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 menginstruksikan LLM tentang 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 utama adalah bagian "Saat Pengguna Memilih Warna Historis", yang:

  1. Menjelaskan konsep notifikasi pemilihan histori kepada 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 build runner 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 merespons pilihan dari histori warna

Pengujian Sinkronisasi Konteks LLM melibatkan:

  1. Pertama, buat beberapa warna dengan mendeskripsikannya di chat
    • "Show me a vibrant purple" (Tampilkan warna ungu cerah)
    • "Saya ingin warna hijau hutan"
    • "Give me a bright red" (Beri saya warna merah cerah)
  2. Kemudian, klik salah satu thumbnail warna di deretan histori

Anda akan melihat:

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

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

Cara kerja Sinkronisasi Konteks LLM

Mari kita pelajari detail teknis cara kerja sinkronisasi ini:

Aliran Data

  1. Tindakan pengguna: Pengguna mengklik warna di deretan 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 formatnya
  6. Respons kontekstual: Gemini merespons dengan tepat berdasarkan perintah sistem
  7. Pembaruan UI: Respons muncul dalam 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 hex
  • 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 bagian yang berbeda
  3. Perubahan pilihan: Perbarui 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. Menserialkan data yang relevan
  3. Mengirim notifikasi berformat khusus ke LLM
  4. Mengarahkan LLM untuk merespons dengan 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 agar LLM dapat mengidentifikasinya dengan mudah:

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

2. Konteks lengkap

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 dalam perintah sistem tentang cara menangani notifikasi, sebaiknya 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 pemilihan warna dengan benar:

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

Pengelolaan konteks

Jika LLM tampaknya kehilangan konteks:

  • Pastikan sesi chat dipertahankan dengan benar
  • Pastikan transisi status percakapan sudah benar
  • Pastikan notifikasi dikirim melalui sesi chat yang sama

Masalah umum

Untuk masalah umum:

  • Periksa error atau peringatan dalam log
  • Memverifikasi konektivitas Firebase AI Logic
  • Periksa apakah ada ketidakcocokan jenis dalam parameter fungsi
  • Pastikan semua kode yang dihasilkan Riverpod sudah terbaru

Konsep utama yang dipelajari

  • Membuat Sinkronisasi Konteks LLM antara UI dan LLM
  • Menyerialkan peristiwa UI ke dalam konteks yang kompatibel dengan LLM
  • Mengarahkan perilaku LLM untuk pola interaksi yang berbeda
  • Menciptakan pengalaman yang kohesif di seluruh interaksi pesan dan non-pesan
  • Meningkatkan pemahaman LLM tentang status aplikasi yang lebih luas

Dengan menerapkan Sinkronisasi Konteks LLM, Anda telah menciptakan pengalaman yang benar-benar terintegrasi di mana LLM terasa seperti asisten yang responsif dan sadar, bukan hanya 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 bangun

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

  • Memproses deskripsi bahasa alami seperti "oranye senja" atau "biru laut dalam"
  • Menggunakan Gemini untuk menerjemahkan deskripsi ini secara cerdas ke dalam nilai RGB
  • Menampilkan warna yang diinterpretasikan 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 mengintegrasikan Gemini dengan Flutter, berikut beberapa cara untuk melanjutkan perjalanan Anda:

Meningkatkan kualitas aplikasi Colorist Anda

  • Palet warna: Menambahkan fungsi untuk membuat 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 set warna
  • Perintah kustom: Buat antarmuka bagi pengguna untuk menyesuaikan perintah sistem
  • Analisis lanjutan: Melacak deskripsi mana yang paling efektif atau menyebabkan kesulitan

Menjelajahi 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: Buat integrasi alat yang lebih kompleks dengan beberapa fungsi
  • Setelan keamanan: Jelajahi berbagai setelan keamanan dan dampaknya terhadap respons

Terapkan pola ini ke domain lain

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

Resource

Berikut beberapa referensi berharga untuk melanjutkan pembelajaran Anda:

Dokumentasi resmi

Kursus dan panduan penulisan perintah

Komunitas

Seri Observable Flutter Agentic

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

Dalam 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.

Dalam episode #61, Craig ditemani Chris Sells untuk 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 berbagai kemungkinan menarik di persimpangan Flutter dan AI.