מידע על Codelab זה
1. פיתוח אפליקציה מבוססת-Gemini ב-Flutter
מה תפַתחו
בקודלאב הזה תלמדו ליצור את Colorist – אפליקציית Flutter אינטראקטיבית שמאפשרת לכם להשתמש ב-Gemini API ישירות באפליקציית Flutter. רצית פעם לאפשר למשתמשים לשלוט באפליקציה באמצעות שפה טבעית, אבל לא ידעת מאיפה להתחיל? בשיעור הקוד הזה תלמדו איך עושים את זה.
Colorist מאפשר למשתמשים לתאר צבעים בשפה טבעית (למשל 'כתום של שקיעה' או 'כחול עמוק של ים'), והאפליקציה:
- מעבדת את התיאורים האלה באמצעות Gemini API של Google
- הפרשנות של התיאורים לערכי צבע מדויקים בפורמט RGB
- הצגת הצבע במסך בזמן אמת
- פרטים טכניים על הצבע והקשר מעניין לגבי הצבע
- שמירה של היסטוריית הצבעים שנוצרו לאחרונה
האפליקציה כוללת ממשק מסך מפוצל עם אזור תצוגה צבעוני ומערכת צ'אט אינטראקטיבית בצד אחד, וחלונית יומן מפורטת שמציגה את האינטראקציות הגולמיות של LLM בצד השני. היומן הזה מאפשר לכם להבין טוב יותר איך שילוב של LLM פועל בפועל.
למה זה חשוב למפתחי Flutter
מודלים LLM חוללו מהפכה באופן שבו משתמשים מקיימים אינטראקציה עם אפליקציות, אבל יש אתגרים ייחודיים בשילוב שלהם באפליקציות לנייד ולמחשב. ב-codelab הזה תלמדו דפוסים מעשיים שחורגים מקריאות ה-API הגולמיות.
תהליך הלמידה שלכם
בשיעור הקוד הזה תלמדו איך ליצור את Colorist שלב אחרי שלב:
- הגדרת הפרויקט – נתחיל עם מבנה אפליקציה בסיסי של Flutter ועם החבילה
colorist_ui
- שילוב בסיסי של Gemini – חיבור האפליקציה ל-Vertex AI ב-Firebase והטמעת תקשורת פשוטה של LLM
- הנחיות יעילות – יצירת הנחיה מערכתית שתעזור ל-LLM להבין תיאורי צבעים
- הצהרות על פונקציות – הגדרת כלים שבהם ה-LLM יכול להשתמש כדי להגדיר צבעים באפליקציה
- טיפול בכלים – עיבוד קריאות פונקציה מ-LLM וחיבור שלהן למצב האפליקציה
- תגובות בסטרימינג – שיפור חוויית המשתמש באמצעות תגובות LLM בסטרימינג בזמן אמת
- סנכרון הקשר של LLM – יצירת חוויה עקבית על ידי עדכון ה-LLM לגבי פעולות המשתמשים
מה תלמדו
- הגדרת Vertex AI ב-Firebase לאפליקציות Flutter
- כתיבה של הנחיות יעילות למערכת כדי להנחות את ההתנהגות של LLM
- הטמעת הצהרות פונקציה שמקשרות בין שפה טבעית לבין תכונות של אפליקציות
- עיבוד תגובות בסטרימינג לחוויית משתמש רספונסיבית
- סנכרון המצב בין אירועי ממשק המשתמש ל-LLM
- ניהול מצב השיחה ב-LLM באמצעות Riverpod
- טיפול בשגיאות בצורה חלקה באפליקציות שמבוססות על LLM
תצוגה מקדימה של הקוד: טעימת מה שתטמיעו
לפניכם הצצה להצהרת הפונקציה שתיצרו כדי לאפשר ל-LLM להגדיר צבעים באפליקציה:
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)'),
},
);
סקירה כללית בסרטון של Codelab הזה
אפשר לצפות בקרייג לאבנס (Craig Labenz) ובאנדרו ברוגדון (Andrew Brogdon) מדברים על סדנת הקוד הזו בפרק 59 של Observable Flutter:
דרישות מוקדמות
כדי להפיק את המרב מה-Codelab הזה, צריך:
- ניסיון בפיתוח ב-Flutter – היכרות עם העקרונות הבסיסיים של Flutter ועם תחביר Dart
- ידע בתכנות אסינכרוני – הבנה של Futures, async/await ו-streams
- חשבון Firebase – כדי להגדיר את Firebase, תצטרכו חשבון Google.
- פרויקט Firebase שבו החיוב מופעל – כדי להשתמש ב-Vertex AI ב-Firebase נדרש חשבון לחיוב
נתחיל ליצור את אפליקציית Flutter הראשונה שלכם שמבוססת על LLM.
2. הגדרת הפרויקט ושירות הדהוד
בשלב הראשון הזה, תגדירו את מבנה הפרויקט ותטמיעו שירות הד פשוט, שיוחליף מאוחר יותר בשילוב של Gemini API. כך מגדירים את ארכיטקטורת האפליקציה ומוודאים שממשק המשתמש פועל כמו שצריך לפני שמוסיפים את המורכבות של קריאות LLM.
מה תלמדו בשלב הזה
- הגדרת פרויקט Flutter עם יחסי התלות הנדרשים
- עבודה עם החבילה
colorist_ui
לרכיבי ממשק המשתמש - הטמעת שירות של הודעות הדהוד וחיבור שלו לממשק המשתמש
הערה חשובה לגבי התמחור
יצירת פרויקט חדש ב-Flutter
מתחילים ביצירת פרויקט Flutter חדש באמצעות הפקודה הבאה:
flutter create -e colorist --platforms=android,ios,macos,web,windows
הדגל -e
מציין שאתם רוצים ליצור פרויקט ריק בלי אפליקציית ברירת המחדל counter
. האפליקציה מיועדת לעבודה במחשב, בנייד ובאינטרנט. עם זאת, בשלב הזה אין ב-flutterfire
תמיכה ב-Linux.
הוספת יחסי תלות
עוברים לספריית הפרויקט ומוסיפים את יחסי התלות הנדרשים:
cd colorist
flutter pub add colorist_ui flutter_riverpod riverpod_annotation
flutter pub add --dev build_runner riverpod_generator riverpod_lint json_serializable custom_lint
הפעולה הזו תוסיף את חבילות המפתחות הבאות:
colorist_ui
: חבילת רכיבים מותאמת אישית שמספקת את רכיבי ממשק המשתמש של אפליקציית Coloristflutter_riverpod
ו-riverpod_annotation
: לניהול המצבlogging
: לרישום ביומן מובנה- יחסי תלות בפיתוח ליצירת קוד ולניפוי שגיאות
השדה pubspec.yaml
ייראה כך:
pubspec.yaml
name: colorist
description: "A new Flutter project."
publish_to: 'none'
version: 0.1.0
environment:
sdk: ^3.8.0
dependencies:
flutter:
sdk: flutter
colorist_ui: ^0.2.3
flutter_riverpod: ^2.6.1
riverpod_annotation: ^2.6.1
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^5.0.0
build_runner: ^2.4.15
riverpod_generator: ^2.6.5
riverpod_lint: ^2.6.5
json_serializable: ^6.9.5
custom_lint: ^0.7.5
flutter:
uses-material-design: true
הגדרת אפשרויות הניתוח
מוסיפים את custom_lint
לקובץ analysis_options.yaml
ברמה הבסיסית של הפרויקט:
include: package:flutter_lints/flutter.yaml
analyzer:
plugins:
- custom_lint
ההגדרה הזו מאפשרת להשתמש ב-lints ספציפיים ל-Riverpod כדי לשמור על איכות הקוד.
הטמעת הקובץ main.dart
מחליפים את התוכן של lib/main.dart
בקוד הבא:
lib/main.dart
import 'package:colorist_ui/colorist_ui.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
void main() async {
runApp(ProviderScope(child: MainApp()));
}
class MainApp extends ConsumerWidget {
const MainApp({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
return MaterialApp(
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
),
home: MainScreen(
sendMessage: (message) {
sendMessage(message, ref);
},
),
);
}
// A fake LLM that just echoes back what it receives.
void sendMessage(String message, WidgetRef ref) {
final chatStateNotifier = ref.read(chatStateNotifierProvider.notifier);
final logStateNotifier = ref.read(logStateNotifierProvider.notifier);
chatStateNotifier.addUserMessage(message);
logStateNotifier.logUserText(message);
chatStateNotifier.addLlmMessage(message, MessageState.complete);
logStateNotifier.logLlmText(message);
}
}
כך מגדירים אפליקציית Flutter שמטמיעה שירות הדהוד פשוט שמחקה את ההתנהגות של LLM על ידי החזרת ההודעה של המשתמש.
הסבר על הארכיטקטורה
נקדיש כמה דקות כדי להבין את הארכיטקטורה של אפליקציית colorist
:
החבילה colorist_ui
החבילה colorist_ui
מספקת רכיבי ממשק משתמש מוכנים מראש וכלים לניהול מצבים:
- MainScreen: רכיב ממשק המשתמש הראשי שבו מוצגים:
- פריסה של מסך מפוצל במחשב (אזור האינטראקציה וחלונית היומנים)
- ממשק עם כרטיסיות בנייד
- תצוגה צבעונית, ממשק צ'אט ותמונות ממוזערות של היסטוריה
- ניהול המצב: האפליקציה משתמשת בכמה מודעות על המצב:
- ChatStateNotifier: ניהול הודעות הצ'אט
- ColorStateNotifier: ניהול הצבע הנוכחי וההיסטוריה
- LogStateNotifier: ניהול הרשומות ביומן לצורך ניפוי באגים
- טיפול בהודעות: האפליקציה משתמשת במודל הודעות עם מצבים שונים:
- הודעות של משתמשים: הוזנו על ידי המשתמש
- הודעות LLM: נוצרות על ידי ה-LLM (או על ידי שירות ההד שלכם בינתיים)
- MessageState: מעקב אחרי הודעות LLM שהושלמו או שעדיין נשלחות
ארכיטקטורה של אפליקציות
האפליקציה מבוססת על הארכיטקטורה הבאה:
- שכבת ממשק המשתמש: מסופקת על ידי חבילת
colorist_ui
- ניהול מצב: שימוש ב-Riverpod לניהול מצב תגובתי
- שכבת השירות: מכילה כרגע את שירות הדהוד הפשוט, והוא יוחלף בשירות Gemini Chat
- שילוב LLM: יתווסף בשלבים מאוחרים יותר
ההפרדה הזו מאפשרת לכם להתמקד בהטמעת השילוב של LLM, בזמן שרכיבי ממשק המשתמש כבר מטופלים.
הפעלת האפליקציה
מריצים את האפליקציה באמצעות הפקודה הבאה:
flutter run -d DEVICE
מחליפים את DEVICE
במכשיר היעד, למשל macos
, windows
, chrome
או מזהה מכשיר.
עכשיו אמורה להופיע אפליקציית Colorist עם:
- אזור תצוגה בצבע עם צבע ברירת מחדל
- ממשק צ'אט שבו אפשר להקליד הודעות
- חלונית יומן שבה מוצגות האינטראקציות בצ'אט
אפשר לנסות להקליד הודעה כמו 'אני רוצה צבע כחול כהה' וללחוץ על 'שליחה'. שירות הדהוד פשוט יחזור על ההודעה שלכם. בשלבים הבאים, תחליפו את הפונקציה הזו בפרשנות צבעים בפועל באמצעות Gemini API דרך Vertex AI ב-Firebase.
מה השלב הבא?
בשלב הבא תגדירו את Firebase ותטמיעו שילוב בסיסי של Gemini API כדי להחליף את שירות הדגל בשירות הצ'אט של Gemini. כך האפליקציה תוכל לפרש תיאורי צבעים ולספק תשובות חכמות.
פתרון בעיות
בעיות בחבילת ממשק המשתמש
אם נתקלת בבעיות בחבילה colorist_ui
:
- מוודאים שמשתמשים בגרסה העדכנית ביותר
- מוודאים שהוספתם את התלות בצורה נכונה
- בודקים אם יש גרסאות חבילות מתנגשות
שגיאות ב-build
אם מופיעות שגיאות build:
- מוודאים שגרסת Flutter SDK היציבה האחרונה מותקנת בערוץ היציב
- מריצים את
flutter clean
ואז אתflutter pub get
- בדיקת הפלט של המסוף כדי למצוא הודעות שגיאה ספציפיות
מושגים מרכזיים שנלמדו
- הגדרת פרויקט Flutter עם יחסי התלות הנדרשים
- הסבר על הארכיטקטורה של האפליקציה ועל האחריות של הרכיבים
- הטמעת שירות פשוט שמחקה את ההתנהגות של LLM
- חיבור השירות לרכיבי ממשק המשתמש
- שימוש ב-Riverpod לניהול מצבים
3. שילוב בסיסי של Gemini Chat
בשלב הזה מחליפים את שירות ההדהוד מהשלב הקודם בשילוב של Gemini API באמצעות Vertex AI ב-Firebase. תגדירו את Firebase, תגדירו את הספקים הנדרשים ותטמיעו שירות צ'אט בסיסי שמתקשר עם Gemini API.
מה תלמדו בשלב הזה
- הגדרת Firebase באפליקציית Flutter
- הגדרת Vertex AI ב-Firebase לצורך גישה ל-Gemini
- יצירת ספקים של Riverpod לשירותי Firebase ו-Gemini
- הטמעת שירות צ'אט בסיסי באמצעות Gemini API
- טיפול בתגובות אסינכררוניות של ממשקי API ובמצבי שגיאה
הגדרת Firebase
קודם כול, צריך להגדיר את Firebase לפרויקט Flutter. לשם כך, צריך ליצור פרויקט Firebase, להוסיף אליו את האפליקציה ולהגדיר את ההגדרות הנדרשות ב-Vertex AI.
יצירת פרויקט Firebase
- נכנסים למסוף Firebase ונכנסים באמצעות חשבון Google.
- לוחצים על Create a Firebase project (יצירת פרויקט Firebase) או בוחרים פרויקט קיים.
- פועלים לפי אשף ההגדרה כדי ליצור את הפרויקט.
- אחרי שיוצרים את הפרויקט, צריך לשדרג לתוכנית Blaze (תשלום לפי שימוש) כדי לגשת לשירותי Vertex AI. לוחצים על הלחצן שדרוג בפינה הימנית התחתונה של מסוף Firebase.
הגדרת Vertex AI בפרויקט Firebase
- במסוף Firebase, עוברים אל הפרויקט.
- בסרגל הצד שמימין, בוחרים באפשרות AI.
- בכרטיס Vertex AI ב-Firebase, בוחרים באפשרות תחילת העבודה.
- פועלים לפי ההנחיות כדי להפעיל את Vertex AI בממשקי ה-API של Firebase בפרויקט.
התקנת ה-CLI של FlutterFire
ה-CLI של FlutterFire מפשט את ההגדרה של Firebase באפליקציות Flutter:
dart pub global activate flutterfire_cli
הוספת Firebase לאפליקציה ב-Flutter
- מוסיפים לפרויקט את חבילות הליבה של Firebase ואת Vertex AI:
flutter pub add firebase_core firebase_vertexai
- מריצים את הפקודה להגדרת FlutterFire:
flutterfire configure
הפקודה הזו:
- תתבקשו לבחור את פרויקט Firebase שיצרתם זה עתה.
- רישום אפליקציות Flutter ב-Firebase
- יצירת קובץ
firebase_options.dart
עם הגדרות הפרויקט
הפקודה תזהה באופן אוטומטי את הפלטפורמות שבחרתם (iOS, Android, macOS, Windows, אינטרנט) ותגדיר אותן בהתאם.
הגדרה ספציפית לפלטפורמה
כדי להשתמש ב-Firebase נדרשות גרסאות מינימום גבוהות יותר מברירת המחדל של Flutter. בנוסף, נדרשת גישה לרשת כדי לתקשר עם Vertex AI בשרתים של Firebase.
הגדרת הרשאות ב-macOS
ב-macOS, צריך להפעיל את הגישה לרשת בהרשאות של האפליקציה:
- פותחים את
macos/Runner/DebugProfile.entitlements
ומוסיפים:
macos/Runner/DebugProfile.entitlements
<key>com.apple.security.network.client</key>
<true/>
- פותחים גם את
macos/Runner/Release.entitlements
ומוסיפים את אותה רשומה. - מעדכנים את גרסת macOS המינימלית בחלק העליון של
macos/Podfile
:
macos/Podfile
# Firebase requires at least macOS 10.15
platform :osx, '10.15'
הגדרת הרשאות ב-iOS
ב-iOS, מעדכנים את הגרסה המינימלית בחלק העליון של ios/Podfile
:
ios/Podfile
# Firebase requires at least iOS 13.0
platform :ios, '13.0'
הגדרת ההגדרות של Android
ב-Android, מעדכנים את android/app/build.gradle.kts
:
android/app/build.gradle.kts
android {
// ...
ndkVersion = "27.0.12077973"
defaultConfig {
// ...
minSdk = 23
// ...
}
}
יצירת ספקי מודלים של Gemini
עכשיו תיצורו את הספקים של Riverpod ל-Firebase ול-Gemini. יוצרים קובץ חדש lib/providers/gemini.dart
:
lib/providers/gemini.dart
import 'dart:async';
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_vertexai/firebase_vertexai.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import '../firebase_options.dart';
part 'gemini.g.dart';
@riverpod
Future<FirebaseApp> firebaseApp(Ref ref) =>
Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
@riverpod
Future<GenerativeModel> geminiModel(Ref ref) async {
await ref.watch(firebaseAppProvider.future);
final model = FirebaseVertexAI.instance.generativeModel(
model: 'gemini-2.0-flash',
);
return model;
}
@Riverpod(keepAlive: true)
Future<ChatSession> chatSession(Ref ref) async {
final model = await ref.watch(geminiModelProvider.future);
return model.startChat();
}
הקובץ הזה מגדיר את הבסיס לשלושה ספקי מפתחות. הספקים האלה נוצרים על ידי הגנרטורים של הקוד של Riverpod כשמריצים את dart run build_runner
.
firebaseAppProvider
: הפעלת Firebase עם הגדרות הפרויקטgeminiModelProvider
: יצירת מופע של מודל גנרטיבי של GeminichatSessionProvider
: יצירת סשן צ'אט עם מודל Gemini ותחזוקתו
ההערה keepAlive: true
בסשן הצ'אט מבטיחה שהוא יישאר לאורך מחזור החיים של האפליקציה, תוך שמירה על הקשר של השיחה.
הטמעת שירות הצ'אט של Gemini
יוצרים קובץ חדש lib/services/gemini_chat_service.dart
כדי להטמיע את שירות הצ'אט:
lib/services/gemini_chat_service.dart
import 'dart:async';
import 'package:colorist_ui/colorist_ui.dart';
import 'package:firebase_vertexai/firebase_vertexai.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import '../providers/gemini.dart';
part 'gemini_chat_service.g.dart';
class GeminiChatService {
GeminiChatService(this.ref);
final Ref ref;
Future<void> sendMessage(String message) async {
final chatSession = await ref.read(chatSessionProvider.future);
final chatStateNotifier = ref.read(chatStateNotifierProvider.notifier);
final logStateNotifier = ref.read(logStateNotifierProvider.notifier);
chatStateNotifier.addUserMessage(message);
logStateNotifier.logUserText(message);
final llmMessage = chatStateNotifier.createLlmMessage();
try {
final response = await chatSession.sendMessage(Content.text(message));
final responseText = response.text;
if (responseText != null) {
logStateNotifier.logLlmText(responseText);
chatStateNotifier.appendToMessage(llmMessage.id, responseText);
}
} catch (e, st) {
logStateNotifier.logError(e, st: st);
chatStateNotifier.appendToMessage(
llmMessage.id,
"\nI'm sorry, I encountered an error processing your request. "
"Please try again.",
);
} finally {
chatStateNotifier.finalizeMessage(llmMessage.id);
}
}
}
@riverpod
GeminiChatService geminiChatService(Ref ref) => GeminiChatService(ref);
השירות הזה:
- מקבלת הודעות ממשתמשים ושולחת אותן ל-Gemini API
- מעדכן את ממשק הצ'אט בתשובות מהמודל
- רישום ביומן של כל התקשורת כדי להקל על הבנת התהליך האמיתי של LLM
- טיפול בשגיאות באמצעות משוב מתאים מהמשתמשים
הערה: בשלב הזה, חלון היומן ייראה כמעט זהה לחלון הצ'אט. היומן יהפוך למעניין יותר אחרי שתוסיפו קריאות לפונקציות ואז תגובות בסטרימינג.
יצירת קוד של Riverpod
מריצים את הפקודה build runner כדי ליצור את הקוד הנדרש של Riverpod:
dart run build_runner build --delete-conflicting-outputs
הפקודה הזו תיצור את הקבצים מסוג .g.dart
שנדרשים ל-Riverpod כדי לפעול.
מעדכנים את הקובץ main.dart
מעדכנים את הקובץ lib/main.dart
כדי להשתמש בשירות הצ'אט החדש של Gemini:
lib/main.dart
import 'package:colorist_ui/colorist_ui.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'providers/gemini.dart';
import 'services/gemini_chat_service.dart';
void main() async {
runApp(ProviderScope(child: MainApp()));
}
class MainApp extends ConsumerWidget {
const MainApp({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final model = ref.watch(geminiModelProvider);
return MaterialApp(
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
),
home: model.when(
data: (data) => MainScreen(
sendMessage: (text) {
ref.read(geminiChatServiceProvider).sendMessage(text);
},
),
loading: () => LoadingScreen(message: 'Initializing Gemini Model'),
error: (err, st) => ErrorScreen(error: err),
),
);
}
}
השינויים העיקריים בעדכון הזה הם:
- החלפת שירות echo בשירות צ'אט מבוסס Gemini API
- הוספת מסכי טעינה ושגיאה באמצעות התבנית
AsyncValue
של Riverpod עם השיטהwhen
- חיבור ממשק המשתמש לשירות הצ'אט החדש באמצעות קריאה חוזרת (callback) של
sendMessage
הפעלת האפליקציה
מריצים את האפליקציה באמצעות הפקודה הבאה:
flutter run -d DEVICE
מחליפים את DEVICE
במכשיר היעד, למשל macos
, windows
, chrome
או מזהה מכשיר.
עכשיו, כשמקלידים הודעה, היא נשלחת ל-Gemini API, ומקבלים תשובה מ-LLM במקום הדהוד. בחלונית היומן יוצגו האינטראקציות עם ה-API.
הסבר על התקשורת עם LLM
ננסה להבין מה קורה כשאתם מתקשרים עם Gemini API:
תהליך התקשורת
- קלט משתמש: המשתמש מזין טקסט בממשק הצ'אט
- Request Formatting: האפליקציה מעצבת את הטקסט כאובייקט
Content
ל-Gemini API - תקשורת API: הטקסט נשלח ל-Gemini API דרך Vertex AI ב-Firebase
- עיבוד על ידי LLM: מודל Gemini מעבד את הטקסט ויוצר תשובה
- טיפול בתגובה: האפליקציה מקבלת את התגובה ומעדכנת את ממשק המשתמש
- רישום ביומן: כל התקשורת מתועדת ביומן לשם שקיפות
סשנים של צ'אט והקשר של השיחה
סשן הצ'אט ב-Gemini שומר על ההקשר בין ההודעות, ומאפשר אינטראקציות שיחה. המשמעות היא ש-LLM "נזכר" בשיחות קודמות בסשן הנוכחי, וכך מאפשר שיחות עקביות יותר.
ההערה keepAlive: true
בספק של סשן הצ'אט מבטיחה שההקשר הזה יישמר לאורך מחזור החיים של האפליקציה. ההקשר המתמיד הזה חיוני לשמירה על זרימה טבעית של השיחה עם ה-LLM.
מה השלב הבא?
בשלב הזה, אפשר לשאול את Gemini API כל דבר, כי אין הגבלות על הנושאים שהוא יגיב להם. לדוגמה, אפשר לבקש ממנה סיכום של מלחמות משפחת רוז, שאינן קשורות למטרה של אפליקציית הצבעים.
בשלב הבא תיצורו הנחיה למערכת שתעזור ל-Gemini לפרש תיאורי צבעים בצורה יעילה יותר. במאמר הזה נסביר איך להתאים אישית את ההתנהגות של LLM לצרכים ספציפיים לאפליקציה ולהתמקד ביכולות שלו בדומיין של האפליקציה.
פתרון בעיות
בעיות בהגדרות של Firebase
אם נתקלתם בשגיאות בהפעלה של Firebase:
- מוודאים שקובץ
firebase_options.dart
נוצר בצורה נכונה - איך בודקים אם השדרוג לתוכנית Blaze בוצע כדי לקבל גישה ל-Vertex AI
שגיאות גישה ל-API
אם מופיעות שגיאות בגישה ל-Gemini API:
- מוודאים שהחיוב מוגדר כראוי בפרויקט Firebase
- בודקים ש-Vertex AI ו-Cloud AI API מופעלים בפרויקט Firebase
- בדיקת החיבור לרשת והגדרות חומת האש
- מוודאים ששם הדגם (
gemini-2.0-flash
) נכון וזמין
בעיות בהקשר של השיחה
אם שמתם לב ש-Gemini לא זוכר את ההקשר הקודם מהצ'אט:
- מוודאים שהפונקציה
chatSession
מסומנת ב-@Riverpod(keepAlive: true)
- מוודאים שמשתמשים באותה סשן צ'אט לכל חילופי ההודעות
- איך מוודאים שסשן הצ'אט הופעל כראוי לפני שליחת הודעות
בעיות ספציפיות לפלטפורמה
במקרה של בעיות ספציפיות לפלטפורמה:
- iOS/macOS: מוודאים שההרשאות המתאימות מוגדרות והגרסאות המינימליות מוגדרות
- Android: מוודאים שגרסת ה-SDK המינימלית מוגדרת בצורה נכונה
- בדיקת הודעות שגיאה ספציפיות לפלטפורמה במסוף
מושגים מרכזיים שנלמדו
- הגדרת Firebase באפליקציית Flutter
- הגדרת Vertex AI ב-Firebase כדי לקבל גישה ל-Gemini
- יצירת ספקי Riverpod לשירותים אסינכררוניים
- הטמעת שירות צ'אט שמתקשר עם LLM
- טיפול במצבים אסינכרוניים של ממשק API (טעינה, שגיאה, נתונים)
- הסבר על תהליך התקשורת של LLM ועל סשנים של צ'אט
4. הנחיות יעילות לתיאור צבעים
בשלב הזה תיצורו ותטמיעו הנחיה מערכתית שתעזור ל-Gemini לפרש תיאורי צבעים. הנחיות מערכת הן דרך יעילה להתאים אישית את התנהגות ה-LLM למשימות ספציפיות בלי לשנות את הקוד.
מה תלמדו בשלב הזה
- הסבר על הנחיות המערכת והחשיבות שלהן באפליקציות LLM
- יצירת הנחיות יעילות למשימות ספציפיות לדומיין
- טעינת הנחיות מערכת והשימוש בהן באפליקציית Flutter
- הדרכת LLM כדי לספק תשובות בפורמט עקבי
- בדיקה של ההשפעה של הנחיות המערכת על התנהגות ה-LLM
הסבר על הנחיות המערכת
לפני שנתחיל בהטמעה, חשוב להבין מהן הנחיות המערכת ולמה הן חשובות:
מהן הנחיות המערכת?
הנחיה מערכתית היא סוג מיוחד של הוראה שניתנת ל-LLM ומגדירה את ההקשר, הנחיות ההתנהגות והציפיות לתשובות שלו. בניגוד להודעות של משתמשים, הנחיות המערכת:
- הגדרת התפקיד והפרסונה של LLM
- הגדרת ידע או יכולות מיוחדים
- נותנים הוראות לגבי העיצוב
- הגדרת אילוצים על התשובות
- תיאור האופן שבו מטפלים בתרחישים שונים
אפשר לחשוב על הנחיה מערכתית כ'תיאור התפקיד' של ה-LLM – היא מורה למודל איך להתנהג במהלך השיחה.
למה ההנחיות של המערכת חשובות
הנחיות המערכת הן קריטיות ליצירת אינטראקציות עקביות ומועילות עם LLM, כי הן:
- לשמור על עקביות: להנחות את המודל לספק תשובות בפורמט עקבי
- שיפור הרלוונטיות: ממקדים את המודל בדומיין הספציפי שלכם (במקרה שלכם, צבעים)
- הגדרת גבולות: מגדירים מה המודל אמור לעשות ומה הוא לא אמור לעשות
- שיפור חוויית המשתמש: יצירת דפוס אינטראקציה טבעי ומועיל יותר
- צמצום של תהליך העיבוד לאחר האירוע: קבלת תשובות בפורמטים שקל יותר לנתח או להציג
באפליקציית Colorist, LLM צריך לפרש באופן עקבי תיאורי צבעים ולספק ערכים של RGB בפורמט ספציפי.
יצירת נכס של הנחיה מערכתית
קודם יוצרים קובץ הנחיה למערכת שייטען בזמן הריצה. הגישה הזו מאפשרת לשנות את ההנחיה בלי לבצע הידור מחדש של האפליקציה.
יוצרים קובץ חדש 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 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
הסבר על המבנה של הנחיות המערכת
בואו נראה מה ההנחיה הזו עושה:
- הגדרת התפקיד: מגדיר את LLM כ'עוזרת מומחית בצבעים'
- הסבר על המשימה: הגדרת המשימה הראשית כפרשנות של תיאורי צבעים לערכי RGB
- פורמט התגובה: קובע בדיוק איך צריך לעצב את ערכי ה-RGB כדי לשמור על עקביות
- דוגמה להחלפה: דוגמה קונקרטית לדפוס האינטראקציה הצפוי
- טיפול במקרי קצה: הוראות לטיפול בתיאורים לא ברורים
- אילוצים והנחיות: הגדרת גבולות, כמו שמירה על ערכי RGB בין 0.0 ל-1.0
הגישה המובנית הזו מבטיחה שהתשובות של ה-LLM יהיו עקביות, מפורטות ומעוצבות בצורה שקל לנתח אם רוצים לחלץ את ערכי ה-RGB באופן פרוגרמטי.
עדכון הקובץ pubspec.yaml
עכשיו מעדכנים את החלק התחתון של pubspec.yaml
כך שיכלול את ספריית הנכסים:
pubspec.yaml
flutter:
uses-material-design: true
assets:
- assets/
מריצים את הפקודה flutter pub get
כדי לרענן את חבילת הנכסים.
יצירת ספק של הנחיות מערכת
יוצרים קובץ חדש lib/providers/system_prompt.dart
כדי לטעון את הנחיית המערכת:
lib/providers/system_prompt.dart
import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'system_prompt.g.dart';
@riverpod
Future<String> systemPrompt(Ref ref) =>
rootBundle.loadString('assets/system_prompt.md');
הספק הזה משתמש במערכת טעינת הנכסים של Flutter כדי לקרוא את קובץ ההנחיה בזמן הריצה.
עדכון הספק של מודל Gemini
עכשיו משנים את קובץ lib/providers/gemini.dart
כך שיכלול את ההנחיה של המערכת:
lib/providers/gemini.dart
import 'dart:async';
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_vertexai/firebase_vertexai.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import '../firebase_options.dart';
import 'system_prompt.dart'; // Add this import
part 'gemini.g.dart';
@riverpod
Future<FirebaseApp> firebaseApp(Ref ref) =>
Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
@riverpod
Future<GenerativeModel> geminiModel(Ref ref) async {
await ref.watch(firebaseAppProvider.future);
final systemPrompt = await ref.watch(systemPromptProvider.future); // Add this line
final model = FirebaseVertexAI.instance.generativeModel(
model: 'gemini-2.0-flash',
systemInstruction: Content.system(systemPrompt), // And this line
);
return model;
}
@Riverpod(keepAlive: true)
Future<ChatSession> chatSession(Ref ref) async {
final model = await ref.watch(geminiModelProvider.future);
return model.startChat();
}
שינוי המפתח הוא הוספת systemInstruction: Content.system(systemPrompt)
כשיוצרים את המודל הגנרטיבי. הפעולה הזו מאפשרת ל-Gemini להשתמש בהוראות שלכם כהודעת המערכת לכל האינטראקציות בסשן הצ'אט הזה.
יצירת קוד Riverpod
מריצים את הפקודה build runner כדי ליצור את הקוד הנדרש של Riverpod:
dart run build_runner build --delete-conflicting-outputs
הרצה ובדיקה של האפליקציה
עכשיו מריצים את האפליקציה:
flutter run -d DEVICE
כדאי לנסות את האפשרות הזו עם תיאורי צבעים שונים:
- "I'd like a sky blue"
- "Give me a forest green"
- "Make a vibrant sunset orange"
- "I want the color of fresh lavender"
- "Show me something like a deep ocean blue"
עכשיו תראו ש-Gemini משיב עם הסברים בשפה פשוטה על הצבעים, יחד עם ערכים של RGB בפורמט עקבי. ההנחיה של המערכת הובילה את ה-LLM לספק את סוג התשובות שאתם צריכים.
אפשר גם לנסות לבקש ממנו תוכן מחוץ להקשר של צבעים. למשל, הסיבות העיקריות למלחמות בין בית לנקסטר לבית יורק. אמורה להיות לכם אפשרות לראות הבדל מהשלב הקודם.
החשיבות של תכנון הנחיות למשימות מיוחדות
הנחיות המערכת הן גם אומנות וגם מדע. הם חלק קריטי בשילוב של LLM, שיכול להשפיע באופן משמעותי על מידת התועלת של המודל לאפליקציה הספציפית שלכם. מה שעשיתם כאן הוא סוג של תכנון של הנחיות – התאמה אישית של ההוראות כדי לגרום למודל להתנהג בדרכים שמתאימות לצרכים של האפליקציה.
הנדסת פרומפטים יעילה כוללת:
- הגדרת תפקיד ברורה: הגדרת המטרה של LLM
- הוראות מפורשות: פירוט מדויק של האופן שבו ה-LLM צריך להגיב
- דוגמאות קונקרטיות: הצגה של תגובות טובות במקום רק לומר איך הן נראות
- טיפול במקרי קצה: הדרכה של ה-LLM איך לטפל בתרחישים לא ברורים
- מפרטי עיצוב: מוודאים שהתשובות מובנות באופן עקבי ושימושי
ההנחיה למערכת שיצרתם הופכת את היכולות הכלליות של Gemini לעוזרת ניתוח צבעים מיוחדת שמספקת תשובות בפורמט שמותאם במיוחד לצרכים של האפליקציה. זהו תבנית חזקה שאפשר להחיל על דומיינים ומשימות רבים.
מה השלב הבא?
בשלב הבא תבנו על הבסיס הזה על ידי הוספת הצהרות פונקציות, שמאפשרות ל-LLM לא רק להציע ערכים של RGB, אלא גם לבצע קריאה לפונקציות באפליקציה כדי להגדיר את הצבע ישירות. הדוגמה הזו ממחישה איך מודלים של LLM יכולים לגשר על הפער בין שפה טבעית לבין תכונות קונקרטיות של אפליקציות.
פתרון בעיות
בעיות בטעינה של נכסים
אם נתקלתם בשגיאות בחיבור להנחיה של המערכת:
- מוודאים שספריית הנכסים מופיעה ב-
pubspec.yaml
בצורה נכונה - בודקים שהנתיב ב-
rootBundle.loadString()
תואם למיקום הקובץ - מריצים את הפקודה
flutter clean
ואז את הפקודהflutter pub get
כדי לרענן את חבילת הנכסים.
תגובות לא עקביות
אם ה-LLM לא פועל באופן עקבי לפי הוראות הפורמט שלכם:
- כדאי לנסות להבהיר את דרישות הפורמט בהנחיה של המערכת
- מוסיפים עוד דוגמאות כדי להמחיש את התבנית הצפויה
- מוודאים שהפורמט המבוקש מתאים לדרישות של המודל
הגבלת קצב של יצירת בקשות ל-API
אם נתקלתם בשגיאות שקשורות להגבלת קצב שליחת הבקשות:
- חשוב לדעת שלשירות Vertex AI יש מגבלות שימוש
- כדאי להטמיע לוגיקה לניסיון חוזר עם השהיה מעריכית לפני ניסיון חוזר
- בדיקה של בעיות במכסות במסוף Firebase
מושגים מרכזיים שנלמדו
- הסבר על התפקיד והחשיבות של הנחיות המערכת באפליקציות LLM
- איך יוצרים הנחיות יעילות עם הוראות, דוגמאות והגבלות ברורות
- טעינה של הנחיות מערכת ושימוש בהן באפליקציית Flutter
- הדרכה של התנהגות LLM למשימות ספציפיות לדומיין
- שימוש בתכנון הנחיות כדי לעצב תשובות של LLM
בשלב הזה נסביר איך אפשר לבצע התאמה אישית משמעותית של התנהגות LLM בלי לשנות את הקוד – פשוט על ידי מתן הוראות ברורות בהנחיה של המערכת.
5. הצהרות על פונקציות לכלים של LLM
בשלב הזה תתחילו לאפשר ל-Gemini לבצע פעולות באפליקציה באמצעות הטמעת הצהרות על פונקציות. התכונה החזקה הזו מאפשרת ל-LLM לא רק להציע ערכי RGB, אלא גם להגדיר אותם בממשק המשתמש של האפליקציה באמצעות קריאות מיוחדות לכלי. עם זאת, בשלב הבא נראה את בקשות ה-LLM שמבוצעות באפליקציית Flutter.
מה תלמדו בשלב הזה
- הסבר על קריאה לפונקציות LLM והיתרונות שלה לאפליקציות Flutter
- הגדרת הצהרות פונקציה מבוססות-סכימה ל-Gemini
- שילוב של הצהרות על פונקציות במודל של Gemini
- עדכון ההנחיה של המערכת כדי לנצל את יכולות הכלי
הסבר על קריאה לפונקציות
לפני שמטמיעים הצהרות על פונקציות, חשוב להבין מה הן ומה הערך שלהן:
מהי קריאה לפונקציה?
קריאה לפונקציה (לפעמים נקראת 'שימוש בכלי') היא יכולת שמאפשרת ל-LLM:
- זיהוי מקרים שבהם כדאי להפעיל פונקציה ספציפית עבור בקשת משתמש
- יצירת אובייקט JSON מובנה עם הפרמטרים הנדרשים לפונקציה הזו
- מאפשרים לאפליקציה להריץ את הפונקציה עם הפרמטרים האלה
- קבלת התוצאה של הפונקציה ושילוב שלה בתגובה שלה
במקום שה-LLM רק מתאר מה צריך לעשות, קריאה לפונקציה מאפשרת ל-LLM להפעיל פעולות קונקרטיות באפליקציה.
למה חשוב להפעיל פונקציות באפליקציות Flutter
קריאת פונקציות יוצרת גשר חזק בין שפה טבעית לבין תכונות האפליקציה:
- פעולה ישירה: המשתמשים יכולים לתאר את מה שהם רוצים בשפה טבעית, והאפליקציה מגיבה בפעולות קונקרטיות.
- פלט מובנה: LLM יוצר נתונים מובְנים נקיים, במקום טקסט שצריך לנתח.
- פעולות מורכבות: מאפשרות ל-LLM לגשת לנתונים חיצוניים, לבצע חישובים או לשנות את מצב האפליקציה
- חוויית משתמש טובה יותר: שילוב חלק בין השיחה לבין הפונקציונליות
באפליקציית Colorist, קריאה לפונקציה מאפשרת למשתמשים לומר "אני רוצה ירוק יער" ולעדכן את ממשק המשתמש באופן מיידי בצבע הזה, בלי לנתח ערכים של RGB מטקסט.
הגדרת הצהרות על פונקציות
יוצרים קובץ חדש lib/services/gemini_tools.dart
כדי להגדיר את הצהרות הפונקציות:
lib/services/gemini_tools.dart
import 'package:firebase_vertexai/firebase_vertexai.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'gemini_tools.g.dart';
class GeminiTools {
GeminiTools(this.ref);
final Ref ref;
FunctionDeclaration get setColorFuncDecl => FunctionDeclaration(
'set_color',
'Set the color of the display square based on red, green, and blue values.',
parameters: {
'red': Schema.number(description: 'Red component value (0.0 - 1.0)'),
'green': Schema.number(description: 'Green component value (0.0 - 1.0)'),
'blue': Schema.number(description: 'Blue component value (0.0 - 1.0)'),
},
);
List<Tool> get tools => [
Tool.functionDeclarations([setColorFuncDecl]),
];
}
@riverpod
GeminiTools geminiTools(Ref ref) => GeminiTools(ref);
הסבר על הצהרות על פונקציות
בואו נראה מה הקוד הזה עושה:
- מתן שם לפונקציה: נותנים לפונקציה
set_color
שם שמציין בבירור את המטרה שלה - תיאור הפונקציה: תיאור ברור שיעזור ל-LLM להבין מתי להשתמש בה
- הגדרות של פרמטרים: מגדירים פרמטרים מובְנים עם תיאורים משלהם:
red
: הרכיב האדום של RGB, שמצוין כמספר בין 0.0 ל-1.0green
: הרכיב הירוק של RGB, שמצוין כמספר בין 0.0 ל-1.0blue
: הרכיב הכחול של RGB, שמצוין כמספר בין 0.0 ל-1.0
- סוגי סכימה: משתמשים ב-
Schema.number()
כדי לציין שאלה ערכים מספריים - אוסף כלים: יוצרים רשימה של כלים שמכילה את הצהרת הפונקציה
הגישה המובנית הזו עוזרת ל-LLM של Gemini להבין:
- מתי צריך לקרוא לפונקציה הזו
- אילו פרמטרים צריך לספק
- אילו אילוצים חלים על הפרמטרים האלה (כמו טווח הערכים)
עדכון הספק של מודל Gemini
עכשיו משנים את הקובץ lib/providers/gemini.dart
כך שיכלול את הצהרות הפונקציות בזמן האתחול של מודל Gemini:
lib/providers/gemini.dart
import 'dart:async';
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_vertexai/firebase_vertexai.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import '../firebase_options.dart';
import '../services/gemini_tools.dart'; // Add this import
import 'system_prompt.dart';
part 'gemini.g.dart';
@riverpod
Future<FirebaseApp> firebaseApp(Ref ref) =>
Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
@riverpod
Future<GenerativeModel> geminiModel(Ref ref) async {
await ref.watch(firebaseAppProvider.future);
final systemPrompt = await ref.watch(systemPromptProvider.future);
final geminiTools = ref.watch(geminiToolsProvider); // Add this line
final model = FirebaseVertexAI.instance.generativeModel(
model: 'gemini-2.0-flash',
systemInstruction: Content.system(systemPrompt),
tools: geminiTools.tools, // And this line
);
return model;
}
@Riverpod(keepAlive: true)
Future<ChatSession> chatSession(Ref ref) async {
final model = await ref.watch(geminiModelProvider.future);
return model.startChat();
}
השינוי במפתח הוא הוספת הפרמטר tools: geminiTools.tools
כשיוצרים את המודל הגנרטיבי. כך Gemini יידע אילו פונקציות זמינות להפעלה.
עדכון ההנחיה של המערכת
עכשיו צריך לשנות את ההנחיה של המערכת כדי להנחות את ה-LLM להשתמש בכלי החדש set_color
. עדכון של 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
השינויים העיקריים בהנחיה של המערכת הם:
- הצגת הכלי: במקום לבקש ערכי RGB בפורמט, עכשיו עליכם לספר ל-LLM על הכלי
set_color
- תהליך ששונה: משנים את שלב 3 מ'עיצוב הערכים בתגובה' ל'שימוש בכלי להגדרת ערכים'
- דוגמה מעודכנת: אתם מראים איך התגובה צריכה לכלול קריאה לכלי במקום טקסט בפורמט
- הוסרה הדרישה לפורמט: מאחר שאתם משתמשים בקריאות פונקציה מובנות, אין צורך יותר בפורמט טקסט ספציפי.
ההנחיה המעודכנת הזו מנחה את ה-LLM להשתמש בקריאה לפונקציה במקום לספק רק ערכים של RGB בצורת טקסט.
יצירת קוד Riverpod
מריצים את הפקודה build runner כדי ליצור את הקוד הנדרש של Riverpod:
dart run build_runner build --delete-conflicting-outputs
הרצת האפליקציה
בשלב הזה, Gemini ייצור תוכן שמנסה להשתמש בקריאה לפונקציה, אבל עדיין לא הטמעתם טיפולים לקריאות הפונקציה. כשמריצים את האפליקציה ומתארים צבע, Gemini מגיב כאילו הופעל כלי, אבל לא רואים שינויים בצבע בממשק המשתמש עד לשלב הבא.
מריצים את האפליקציה:
flutter run -d DEVICE
נסו לתאר צבע כמו 'כחול ים עמוק' או 'ירוק יער' ולבחון את התשובות. ה-LLM מנסה לקרוא לפונקציות שהוגדרו למעלה, אבל הקוד עדיין לא מזהה קריאות לפונקציות.
תהליך הקריאה לפונקציה
הבה נבין מה קורה כש-Gemini משתמש בקריאה לפונקציה:
- בחירת פונקציה: ה-LLM מחליט אם קריאה לפונקציה תהיה מועילה על סמך הבקשה של המשתמש.
- יצירת פרמטרים: ה-LLM יוצר ערכי פרמטרים שתואמים לסכימה של הפונקציה
- פורמט של קריאה לפונקציה: ה-LLM שולח אובייקט של קריאה לפונקציה מובנית בתגובה שלו
- טיפול באפליקציה: האפליקציה תקבל את הקריאה הזו ותריץ את הפונקציה הרלוונטית (הטמעה בשלב הבא)
- שילוב תשובות: בשיחות עם כמה סבבים, ה-LLM מצפה שהתוצאה של הפונקציה תוחזר
במצב הנוכחי של האפליקציה, שלושת השלבים הראשונים מתרחשים, אבל עדיין לא הטמעתם את שלב 4 או 5 (טיפול בקריאות הפונקציה). בשלב הבא נעשה זאת.
פרטים טכניים: איך Gemini מחליט מתי להשתמש בפונקציות
מערכת Gemini מקבלת החלטות חכמות לגבי מתי להשתמש בפונקציות על סמך:
- כוונת המשתמש: האם פונקציה מסוימת תתאים בצורה הטובה ביותר לבקשה של המשתמש
- רלוונטיות הפונקציה: מידת ההתאמה של הפונקציות הזמינות למשימה
- זמינות הפרמטר: האם ניתן לקבוע בביטחון את ערכי הפרמטרים
- הוראות מערכת: הנחיות מההנחיה של המערכת לגבי השימוש בפונקציה
בעזרת הצהרות ברורות על פונקציות והוראות מערכת, הגדרתם את Gemini לזהות בקשות לתיאור צבעים כאפשרויות לקריאה לפונקציה set_color
.
מה השלב הבא?
בשלב הבא נטמיע מנהלים לבקשות להפעלת פונקציות שמגיעות מ-Gemini. כך נשלימה המעגלה, ומאפשרת לתיאורי המשתמשים להפעיל שינויי צבעים בפועל בממשק המשתמש באמצעות קריאות הפונקציה של LLM.
פתרון בעיות
בעיות בהצהרה על פונקציות
אם נתקלתם בשגיאות בהצהרות על פונקציות:
- בודקים שהשמות והסוגים של הפרמטרים תואמים למה שציפיתם
- מוודאים ששם הפונקציה ברור ומכיל תיאור
- מוודאים שהתיאור של הפונקציה מסביר במדויק את המטרה שלה
בעיות בהנחיות המערכת
אם ה-LLM לא מנסה להשתמש בפונקציה:
- מוודאים שההנחיה של המערכת מורה בבירור ל-LLM להשתמש בכלי
set_color
- בודקים שהדוגמה בהנחיה של המערכת ממחישה את השימוש בפונקציה
- כדאי לנסות להפוך את ההוראות לשימוש בכלי לברורות יותר
בעיות כלליות
אם נתקלת בבעיות אחרות:
- בודקים אם יש שגיאות במסוף שקשורות להצהרות על פונקציות.
- מוודאים שהכלים מועברים כראוי למודל
- מוודאים שכל הקוד שנוצר על ידי Riverpod עדכני
מושגים מרכזיים שנלמדו
- הגדרת הצהרות על פונקציות כדי להרחיב את היכולות של LLM באפליקציות Flutter
- יצירת סכימות של פרמטרים לאיסוף נתונים מובְנים
- שילוב של הצהרות על פונקציות עם מודל Gemini
- עדכון ההנחיות של המערכת כדי לעודד שימוש בפונקציות
- הסבר על האופן שבו מודלים גדולים של שפה (LLM) בוחרים פונקציות ומפעילים אותן
השלב הזה מראה איך מודלים עמוקים של שפה יכולים לגשר על הפער בין קלט בשפה טבעית לבין קריאות פונקציה מובנות, ומניחים את הבסיס לשילוב חלק בין שיחות לבין תכונות של אפליקציות.
6. הטמעת טיפול בכלים
בשלב הזה, תרתמו מנהלים (handlers) לשיחות הפונקציה שמגיעות מ-Gemini. כך נסגר מעגל התקשורת בין הקלט בשפה טבעית לבין תכונות אפליקציה קונקרטיות, ומאפשר ל-LLM לבצע מניפולציות ישירות בממשק המשתמש על סמך תיאורים של משתמשים.
מה תלמדו בשלב הזה
- הסבר על צינור עיבוד הנתונים המלא של קריאת פונקציות באפליקציות LLM
- עיבוד קריאות פונקציה מ-Gemini באפליקציית Flutter
- הטמעת פונקציות טיפול (handler) שמשנות את מצב האפליקציה
- טיפול בתגובות של פונקציות והחזרת תוצאות ל-LLM
- יצירת תהליך תקשורת מלא בין LLM לבין ממשק המשתמש
- רישום ביומן של קריאות פונקציות ותגובות לשם שקיפות
הסבר על צינור עיבוד הנתונים של קריאת הפונקציה
לפני שנמשיך להטמעה, נבין את צינור עיבוד הנתונים המלא של קריאת הפונקציה:
התהליך מקצה לקצה
- קלט של משתמש: המשתמש מתאר צבע בשפה טבעית (למשל, "forest green")
- עיבוד LLM: Gemini מנתח את התיאור ומחליט לקרוא לפונקציה
set_color
- יצירת קריאה לפונקציה: Gemini יוצרת קובץ JSON מובנה עם פרמטרים (ערכים אדום, ירוק וכחול)
- קבלת קריאה לפונקציה: האפליקציה מקבלת את הנתונים המובְנים האלה מ-Gemini
- הפעלת פונקציה: האפליקציה מפעילה את הפונקציה עם הפרמטרים שסופקו
- עדכון המצב: הפונקציה מעדכנת את המצב של האפליקציה (שינוי הצבע המוצג)
- יצירת תגובה: הפונקציה מחזירה את התוצאות חזרה ל-LLM
- שילוב התשובה: ה-LLM משלב את התוצאות האלה בתשובה הסופית שלו
- עדכון ממשק המשתמש: ממשק המשתמש מגיב לשינוי המצב ומציג את הצבע החדש.
מחזור התקשורת המלא חיוני לשילוב תקין של LLM. כש-LLM מבצע קריאה לפונקציה, הוא לא פשוט שולח את הבקשה וממשיך הלאה. במקום זאת, הוא ממתין לאפליקציה כדי להריץ את הפונקציה ולהחזיר תוצאות. לאחר מכן, ה-LLM משתמש בתוצאות האלה כדי לנסח את התשובה הסופית שלו, וכך יוצר זרימה טבעית של שיחה שמציינת את הפעולות שבוצעו.
הטמעת רכיבי handler של פונקציות
נעדכן את הקובץ lib/services/gemini_tools.dart
כדי להוסיף מנהלים לשיחות פונקציה:
lib/services/gemini_tools.dart
import 'package:colorist_ui/colorist_ui.dart';
import 'package:firebase_vertexai/firebase_vertexai.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'gemini_tools.g.dart';
class GeminiTools {
GeminiTools(this.ref);
final Ref ref;
FunctionDeclaration get setColorFuncDecl => FunctionDeclaration(
'set_color',
'Set the color of the display square based on red, green, and blue values.',
parameters: {
'red': Schema.number(description: 'Red component value (0.0 - 1.0)'),
'green': Schema.number(description: 'Green component value (0.0 - 1.0)'),
'blue': Schema.number(description: 'Blue component value (0.0 - 1.0)'),
},
);
List<Tool> get tools => [
Tool.functionDeclarations([setColorFuncDecl]),
];
Map<String, Object?> handleFunctionCall( // Add from here
String functionName,
Map<String, Object?> arguments,
) {
final logStateNotifier = ref.read(logStateNotifierProvider.notifier);
logStateNotifier.logFunctionCall(functionName, arguments);
return switch (functionName) {
'set_color' => handleSetColor(arguments),
_ => handleUnknownFunction(functionName),
};
}
Map<String, Object?> handleSetColor(Map<String, Object?> arguments) {
final colorStateNotifier = ref.read(colorStateNotifierProvider.notifier);
final red = (arguments['red'] as num).toDouble();
final green = (arguments['green'] as num).toDouble();
final blue = (arguments['blue'] as num).toDouble();
final functionResults = {
'success': true,
'current_color': colorStateNotifier
.updateColor(red: red, green: green, blue: blue)
.toLLMContextMap(),
};
final logStateNotifier = ref.read(logStateNotifierProvider.notifier);
logStateNotifier.logFunctionResults(functionResults);
return functionResults;
}
Map<String, Object?> handleUnknownFunction(String functionName) {
final logStateNotifier = ref.read(logStateNotifierProvider.notifier);
logStateNotifier.logWarning('Unsupported function call $functionName');
return {
'success': false,
'reason': 'Unsupported function call $functionName',
};
} // To here.
}
@riverpod
GeminiTools geminiTools(Ref ref) => GeminiTools(ref);
הסבר על פונקציות הטיפול (handlers)
נבחן את הפונקציות של מנהלי הפונקציות האלה:
handleFunctionCall
: מרכז ניהול שמבצע את הפעולות הבאות:- רישום ביומן של קריאת הפונקציה לשקיפות בחלונית היומן
- מנתבת לטיפול המתאים על סמך שם הפונקציה
- החזרת תגובה מובנית שתישלח בחזרה ל-LLM
handleSetColor
: הטיפולן הספציפי של פונקצייתset_color
:- חילוץ ערכי RGB ממפת הארגומנטים
- ממירה אותם לסוגי הנתונים הצפויים (doubles)
- עדכון מצב הצבע של האפליקציה באמצעות
colorStateNotifier
- יצירת תגובה מובנית עם סטטוס הצלחה ומידע על הצבע הנוכחי
- רישום ביומן של תוצאות הפונקציה לצורך ניפוי באגים
handleUnknownFunction
: טיפול חלופי לפונקציות לא מוכרות:- הוספת אזהרה ביומן לגבי הפונקציה שלא נתמכת
- החזרת תגובת שגיאה ל-LLM
הפונקציה handleSetColor
חשובה במיוחד כי היא משלימה את הפער בין הבנת השפה הטבעית של ה-LLM לבין שינויים קונקרטיים בממשק המשתמש.
עדכון שירות הצ'אט של Gemini לעיבוד קריאות פונקציות ותשובות
עכשיו נעדכן את הקובץ lib/services/gemini_chat_service.dart
כדי לעבד קריאות לפונקציות מהתשובות של ה-LLM ולשלוח את התוצאות חזרה ל-LLM:
lib/services/gemini_chat_service.dart
import 'dart:async';
import 'package:colorist_ui/colorist_ui.dart';
import 'package:firebase_vertexai/firebase_vertexai.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import '../providers/gemini.dart';
import 'gemini_tools.dart'; // Add this import
part 'gemini_chat_service.g.dart';
class GeminiChatService {
GeminiChatService(this.ref);
final Ref ref;
Future<void> sendMessage(String message) async {
final chatSession = await ref.read(chatSessionProvider.future);
final chatStateNotifier = ref.read(chatStateNotifierProvider.notifier);
final logStateNotifier = ref.read(logStateNotifierProvider.notifier);
chatStateNotifier.addUserMessage(message);
logStateNotifier.logUserText(message);
final llmMessage = chatStateNotifier.createLlmMessage();
try {
final response = await chatSession.sendMessage(Content.text(message));
final responseText = response.text;
if (responseText != null) {
logStateNotifier.logLlmText(responseText);
chatStateNotifier.appendToMessage(llmMessage.id, responseText);
}
if (response.functionCalls.isNotEmpty) { // Add from here
final geminiTools = ref.read(geminiToolsProvider);
final functionResultResponse = await chatSession.sendMessage(
Content.functionResponses([
for (final functionCall in response.functionCalls)
FunctionResponse(
functionCall.name,
geminiTools.handleFunctionCall(
functionCall.name,
functionCall.args,
),
),
]),
);
final responseText = functionResultResponse.text;
if (responseText != null) {
logStateNotifier.logLlmText(responseText);
chatStateNotifier.appendToMessage(llmMessage.id, responseText);
}
} // To here.
} catch (e, st) {
logStateNotifier.logError(e, st: st);
chatStateNotifier.appendToMessage(
llmMessage.id,
"\nI'm sorry, I encountered an error processing your request. "
"Please try again.",
);
} finally {
chatStateNotifier.finalizeMessage(llmMessage.id);
}
}
}
@riverpod
GeminiChatService geminiChatService(Ref ref) => GeminiChatService(ref);
הסבר על תהליך התקשורת
התוספת העיקרית כאן היא הטיפול המלא בקריאות לפונקציות ובתגובות:
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);
}
}
הקוד הזה:
- הבדיקה אם התשובה של LLM מכילה קריאות לפונקציות
- לכל קריאה לפונקציה, מפעילה את method
handleFunctionCall
עם שם הפונקציה והארגומנטים - איסוף התוצאות של כל קריאה לפונקציה
- שולחת את התוצאות האלה חזרה ל-LLM באמצעות
Content.functionResponses
- מעבד את התגובה של ה-LLM לתוצאות הפונקציה
- עדכון ממשק המשתמש בטקסט התשובה הסופי
כך נוצר תהליך הלוך ושוב:
- משתמש → LLM: בקשה לבחירת צבע
- LLM → אפליקציה: קריאות לפונקציות עם פרמטרים
- אפליקציה → משתמש: מוצג צבע חדש
- אפליקציה → LLM: תוצאות הפונקציה
- LLM → משתמש: תגובה סופית שמשלבת את תוצאות הפונקציה
יצירת קוד Riverpod
מריצים את הפקודה build runner כדי ליצור את הקוד הנדרש של Riverpod:
dart run build_runner build --delete-conflicting-outputs
הרצת התהליך המלא ובדיקה שלו
עכשיו מריצים את האפליקציה:
flutter run -d DEVICE
כדאי לנסות להזין תיאורים שונים של צבעים:
- "I'd like a deep crimson red"
- "Show me a calming sky blue"
- "Give me the color of fresh mint leaves"
- "I want to see a warm sunset orange"
- "Make it a rich royal purple"
עכשיו אמור להופיע:
- ההודעה שלכם מופיעה בממשק הצ'אט
- התשובה של Gemini מופיעה בצ'אט
- קריאות לפונקציות שמתועדות ביומן בחלונית היומן
- תוצאות הפונקציה מתועדות ביומן מיד לאחר
- ריבוע הצבע מתעדכן כדי להציג את הצבע המתואר
- ערכי RGB מתעדכנים כדי להציג את הרכיבים של הצבע החדש
- הצגת התשובה הסופית של Gemini, לרוב עם הערה לגבי הצבע שהוגדר
בחלונית היומן מוצגות תובנות לגבי מה שקורה מאחורי הקלעים. הפרטים שמוצגים הם:
- קריאות הפונקציה המדויקות ש-Gemini מבצע
- הפרמטרים שבוחרת המערכת לכל ערך RGB
- התוצאות שהפונקציה מחזירה
- התשובות הבאות מ-Gemini
הכלי להתרעה על מצב הצבע
ה-colorStateNotifier
שבו אתם משתמשים כדי לעדכן את הצבעים הוא חלק מחבילת colorist_ui
. הוא מנהל את הדברים הבאים:
- הצבע הנוכחי שמוצג בממשק המשתמש
- היסטוריית הצבעים (10 הצבעים האחרונים)
- התראות על שינויים במצב של רכיבי ממשק המשתמש
כשקוראים לפונקציה updateColor
עם ערכי RGB חדשים, היא:
- יצירת אובייקט
ColorData
חדש עם הערכים שסופקו - מעדכן את הצבע הנוכחי במצב האפליקציה
- הוספת הצבע להיסטוריה
- מפעיל עדכונים של ממשק המשתמש באמצעות ניהול המצב של Riverpod
רכיבי ממשק המשתמש בחבילה colorist_ui
עוקבים אחרי המצב הזה ומעדכנים אותו באופן אוטומטי כשהוא משתנה, וכך יוצרים חוויית משתמש תגובה.
הסבר על טיפול בשגיאות
ההטמעה כוללת טיפול יעיל בשגיאות:
- בלוק Try-catch: עוטף את כל האינטראקציות עם LLM כדי לזהות חריגים
- רישום שגיאות ביומן: רישום שגיאות בחלונית היומן עם מעקב סטאק
- משוב ממשתמשים: הצגת הודעת שגיאה ידידותית בצ'אט
- ניקוי המצב: סיום של מצב ההודעה גם אם מתרחשת שגיאה
כך מובטח שהאפליקציה תישאר יציבה ותספק משוב מתאים גם כשיהיו בעיות בשירות LLM או בהפעלת הפונקציה.
היתרון של קריאה לפונקציות לחוויית המשתמש
מה שהצלחתם לעשות כאן מראה איך מודלים גדולים של שפה יכולים ליצור ממשקים טבעיים חזקים:
- ממשק בשפה טבעית: משתמשים מביעים את הכוונה שלהם בשפה יומיומית
- פרשנות חכמה: ה-LLM מתרגם תיאורים מעורפלים לערכי נתונים מדויקים.
- מניפולציה ישירה: ממשק המשתמש מתעדכן בתגובה לשפה טבעית
- תשובות לפי הקשר: ה-LLM מספק הקשר לשיחה לגבי השינויים
- עומס קוגניטיבי נמוך: המשתמשים לא צריכים להבין ערכי RGB או תיאוריית צבעים
אפשר להרחיב את התבנית הזו של שימוש בקריאה לפונקציות של LLM כדי לגשר בין שפה טבעית לפעולות בממשק המשתמש למגוון עצום של תחומים אחרים, מעבר לבחירת צבעים.
מה השלב הבא?
בשלב הבא תלמדו איך לשפר את חוויית המשתמש באמצעות הטמעת תגובות בסטרימינג. במקום להמתין לתשובה המלאה, תעבדו קטעי טקסט וקריאות לפונקציות ברגע שהם מתקבלים, וכך תיצרו אפליקציה רספונסיבית ומעניינת יותר.
פתרון בעיות
בעיות בקריאה לפונקציה
אם Gemini לא קורא לפונקציות שלכם או שהפרמטרים שגויים:
- מוודאים שהצהרת הפונקציה תואמת לתיאור שמופיע בהנחיה של המערכת
- בדיקה שהשמות והסוגים של הפרמטרים עקביים
- מוודאים שההנחיה של המערכת מורה במפורש ל-LLM להשתמש בכלי
- מוודאים ששם הפונקציה בטיפולן תואם בדיוק לשם שמופיע בהצהרה
- בדיקת חלונית היומן כדי לקבל מידע מפורט על קריאות לפונקציות
בעיות בתשובה של פונקציה
אם תוצאות הפונקציה לא נשלחות בחזרה ל-LLM כראוי:
- בודקים שהפונקציה מחזירה מפה בפורמט תקין
- מוודאים שהמבנה של Content.functionResponses תקין
- מחפשים שגיאות ביומן שקשורות לתשובות של הפונקציות
- חשוב לוודא שאתם משתמשים באותה סשן צ'אט כדי לשלוח את התשובה.
בעיות בתצוגת הצבעים
אם הצבעים לא מוצגים כמו שצריך:
- מוודאים שערכי RGB מומרו כראוי למספרים כפולים (LLM עשוי לשלוח אותם כמספרים שלמים)
- מוודאים שהערכים נמצאים בטווח הצפוי (0.0 עד 1.0)
- בדיקה שהקריאה למודיע על מצב הצבע מתבצעת בצורה נכונה
- בודקים ביומן את הערכים המדויקים שהועברו לפונקציה
בעיות כלליות
בעיות כלליות:
- בדיקת היומנים כדי לאתר שגיאות או אזהרות
- אימות הקישוריות של Vertex AI ב-Firebase
- בדיקה של אי-התאמות בסוגים של פרמטרים של פונקציות
- מוודאים שכל הקוד שנוצר על ידי Riverpod עדכני
מושגים מרכזיים שנלמדו
- הטמעת צינור עיבוד נתונים מלא לקריאה לפונקציות ב-Flutter
- יצירת תקשורת מלאה בין LLM לאפליקציה
- עיבוד נתונים מובְנים מתשובות של LLM
- שליחת תוצאות הפונקציה חזרה ל-LLM כדי לשלב אותן בתשובות
- שימוש בחלונית היומנים כדי לקבל תובנות על אינטראקציות של LLM עם אפליקציות
- קישור של קלט בשפה טבעית לשינויים קונקרטיים בממשק המשתמש
אחרי השלמת השלב הזה, האפליקציה שלכם תציג את אחד מהדפוסים החזקים ביותר לשילוב של LLM: תרגום של קלט בשפה טבעית לפעולות קונקרטיות בממשק המשתמש, תוך שמירה על שיחה עקבית שמתייחסת לפעולות האלה. כך נוצר ממשק אינטואיטיבי מבוסס-שיחה שנראה למשתמש כמו קסם.
7. הצגת תשובות באופן שוטף לשיפור חוויית המשתמש
בשלב הזה משפרים את חוויית המשתמש על ידי הטמעת תשובות בסטרימינג מ-Gemini. במקום להמתין ליצירה של התשובה כולה, תעבדו על קטעי טקסט ועל קריאות לפונקציות ברגע שהם מתקבלים, וכך תיצרו אפליקציה רספונסיבית ומעניינת יותר.
מה תלמדו בשלב הזה
- החשיבות של סטרימינג לאפליקציות שמבוססות על LLM
- הטמעת תשובות LLM בסטרימינג באפליקציית Flutter
- עיבוד קטעי טקסט חלקיים כשהם מגיעים מה-API
- ניהול מצב השיחה כדי למנוע התנגשויות בין הודעות
- טיפול בקריאות לפונקציות בתשובות בסטרימינג
- יצירת אינדיקטורים חזותיים לתשובות שנמצאות בתהליך טיפול
למה סטרימינג חשוב לאפליקציות של LLM
לפני שמטמיעים את התכונה, חשוב להבין למה העברת תשובות בסטרימינג חיונית ליצירת חוויות משתמש מעולות באמצעות מודלים מסוג LLM:
חוויית משתמש משופרת
לשידור תשובות יש כמה יתרונות משמעותיים לחוויית המשתמש:
- זמן אחזור משוער קצר יותר: הטקסט מתחיל להופיע באופן מיידי (בדרך כלל תוך 100-300 אלפיות השנייה), במקום להמתין כמה שניות לתשובה מלאה. התפיסה הזו של מיידיות משפרת באופן משמעותי את שביעות הרצון של המשתמשים.
- קצב שיחה טבעי: הצגת הטקסט בהדרגה מחקה את אופן התקשורת של בני אדם, ומאפשרת לכם ליהנות מחוויית שיחה טבעית יותר.
- עיבוד מידע הדרגתי: המשתמשים יכולים להתחיל לעבד את המידע ברגע שהוא מגיע, במקום להיעלם מול בלוק גדול של טקסט בבת אחת.
- הזדמנות להפסקה מוקדמת: באפליקציה מלאה, המשתמשים יכולים להפסיק או להפנות את ה-LLM לכיוון אחר אם הם רואים שהוא הולך לכיוון לא מועיל.
- אישור חזותי של הפעילות: הטקסט בסטרימינג מספק משוב מיידי על כך שהמערכת פועלת, ומפחית את חוסר הוודאות.
יתרונות טכניים
בנוסף לשיפורים בחוויית המשתמש, לסטרימינג יש יתרונות טכניים:
- ביצוע פונקציות מוקדם: ניתן לזהות קריאות פונקציות ולבצע אותן ברגע שהן מופיעות בסטרימינג, בלי לחכות לתגובה המלאה.
- עדכונים מצטברים של ממשק המשתמש: אתם יכולים לעדכן את ממשק המשתמש בהדרגה ככל שמגיע מידע חדש, וכך ליצור חוויה דינמית יותר.
- ניהול מצב השיחה: כשמשתמשים בהעברה בזמן אמת, אפשר לדעת בבירור מתי התשובות הושלמו ומתי הן עדיין בתהליך, וכך לנהל את המצב בצורה טובה יותר.
- סיכוני זמן קצוב מופחתים: בתגובות ללא סטרימינג, יצירת גרסאות ממושכת עלולה לגרום לזמן קצוב לתפוגה של החיבור. בסטרימינג, החיבור נוצר מוקדם ומנוהל באופן שוטף.
כשמפעילים סטרימינג באפליקציית Colorist, התשובות בטקסט ושינויי הצבעים מופיעים מהר יותר, וכך חוויית המשתמש נעשית רספונסיבית יותר.
הוספת ניהול של מצב השיחה
קודם כול, נוסיף ספק מצב כדי לעקוב אחרי העובדה שהאפליקציה מטפלת כרגע בתגובה בסטרימינג. מעדכנים את הקובץ lib/services/gemini_chat_service.dart
:
lib/services/gemini_chat_service.dart
import 'dart:async';
import 'package:colorist_ui/colorist_ui.dart';
import 'package:firebase_vertexai/firebase_vertexai.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import '../providers/gemini.dart';
import 'gemini_tools.dart';
part 'gemini_chat_service.g.dart';
final conversationStateProvider = StateProvider( // Add from here...
(ref) => ConversationState.idle,
); // To here.
class GeminiChatService {
GeminiChatService(this.ref);
final Ref ref;
Future<void> sendMessage(String message) async {
final chatSession = await ref.read(chatSessionProvider.future);
final conversationState = ref.read(conversationStateProvider); // Add this line
final chatStateNotifier = ref.read(chatStateNotifierProvider.notifier);
final logStateNotifier = ref.read(logStateNotifierProvider.notifier);
if (conversationState == ConversationState.busy) { // Add from here...
logStateNotifier.logWarning(
"Can't send a message while a conversation is in progress",
);
throw Exception(
"Can't send a message while a conversation is in progress",
);
}
final conversationStateNotifier = ref.read(
conversationStateProvider.notifier,
);
conversationStateNotifier.state = ConversationState.busy; // To here.
chatStateNotifier.addUserMessage(message);
logStateNotifier.logUserText(message);
final llmMessage = chatStateNotifier.createLlmMessage();
try { // Modify from here...
final responseStream = chatSession.sendMessageStream(
Content.text(message),
);
await for (final block in responseStream) {
await _processBlock(block, llmMessage.id);
} // To here.
} catch (e, st) {
logStateNotifier.logError(e, st: st);
chatStateNotifier.appendToMessage(
llmMessage.id,
"\nI'm sorry, I encountered an error processing your request. "
"Please try again.",
);
} finally {
chatStateNotifier.finalizeMessage(llmMessage.id);
conversationStateNotifier.state = ConversationState.idle; // Add this line.
}
}
Future<void> _processBlock( // Add from here...
GenerateContentResponse block,
String llmMessageId,
) async {
final chatSession = await ref.read(chatSessionProvider.future);
final chatStateNotifier = ref.read(chatStateNotifierProvider.notifier);
final logStateNotifier = ref.read(logStateNotifierProvider.notifier);
final blockText = block.text;
if (blockText != null) {
logStateNotifier.logLlmText(blockText);
chatStateNotifier.appendToMessage(llmMessageId, blockText);
}
if (block.functionCalls.isNotEmpty) {
final geminiTools = ref.read(geminiToolsProvider);
final responseStream = chatSession.sendMessageStream(
Content.functionResponses([
for (final functionCall in block.functionCalls)
FunctionResponse(
functionCall.name,
geminiTools.handleFunctionCall(
functionCall.name,
functionCall.args,
),
),
]),
);
await for (final response in responseStream) {
final responseText = response.text;
if (responseText != null) {
logStateNotifier.logLlmText(responseText);
chatStateNotifier.appendToMessage(llmMessageId, responseText);
}
}
}
} // To here.
}
@riverpod
GeminiChatService geminiChatService(Ref ref) => GeminiChatService(ref);
הסבר על הטמעת הסטרימינג
בואו נראה מה הקוד הזה עושה:
- מעקב אחר מצב השיחה:
- הערך
conversationStateProvider
עוקב אחרי העובדה שהאפליקציה מעבדת כרגע תשובה - המצב עובר מ-
idle
ל-busy
במהלך העיבוד, ואז חזרה ל-idle
- כך אפשר למנוע שליחת כמה בקשות בו-זמנית שעלולות להתנגש זו בזו.
- הערך
- הפעלת מקור הנתונים:
sendMessageStream()
מחזיר Stream של קטעי תגובה במקוםFuture
עם התשובה המלאה- כל מקטע יכול להכיל טקסט, קריאות לפונקציות או את שניהם
- עיבוד הדרגתי:
await for
מעבד כל מקטע ברגע שהוא מגיע בזמן אמת- הטקסט מצורף לממשק המשתמש באופן מיידי, וכך נוצר אפקט הסטרימינג
- קריאות לפונקציות מתבצעות ברגע שהן מזוהות
- טיפול בקריאות לפונקציות:
- כשמזוהה קריאה לפונקציה ב-chunk, היא מבוצעת באופן מיידי
- התוצאות נשלחות בחזרה ל-LLM באמצעות שיחת סטרימינג נוספת
- התשובה של ה-LLM לתוצאות האלה מעובדת גם היא בסטרימינג.
- טיפול בשגיאות וניפוי:
try
/catch
מספק טיפול חזק בשגיאות- הבלוק
finally
מוודא שהמצב של השיחה מאופס כראוי - ההודעה תמיד תאושר, גם אם יהיו שגיאות
ההטמעה הזו יוצרת חוויית סטרימינג מהירה ואמינה, תוך שמירה על מצב השיחה הנכון.
עדכון המסך הראשי כדי לחבר את מצב השיחה
משנים את הקובץ lib/main.dart
כדי להעביר את מצב השיחה למסך הראשי:
lib/main.dart
import 'package:colorist_ui/colorist_ui.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'providers/gemini.dart';
import 'services/gemini_chat_service.dart';
void main() async {
runApp(ProviderScope(child: MainApp()));
}
class MainApp extends ConsumerWidget {
const MainApp({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final model = ref.watch(geminiModelProvider);
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),
),
);
}
}
השינוי העיקרי כאן הוא העברת conversationState
לווידג'ט MainScreen
. ה-MainScreen
(שסופק על ידי החבילה colorist_ui
) ישתמש במצב הזה כדי להשבית את קלט הטקסט בזמן עיבוד התגובה.
כך נוצרת חוויית משתמש עקבית שבה ממשק המשתמש משקף את המצב הנוכחי של השיחה.
יצירת קוד Riverpod
מריצים את הפקודה build runner כדי ליצור את הקוד הנדרש של Riverpod:
dart run build_runner build --delete-conflicting-outputs
הפעלה ובדיקה של תשובות בסטרימינג
מריצים את האפליקציה:
flutter run -d DEVICE
עכשיו ננסה לבדוק את התנהגות הסטרימינג עם תיאורי צבעים שונים. אפשר לנסות תיאורים כמו:
- "Show me the deep teal color of the ocean at twilight"
- "I'd like to see a vibrant coral that reminds me of tropical flowers"
- "יצירת ירוק זית עמום כמו מדי צבא ישנים"
התהליך הטכני של הסטרימינג בפירוט
בואו נראה מה קורה בדיוק כשמשדרים תשובה:
יצירת חיבור
כשקוראים ל-sendMessageStream()
, מתרחשים הדברים הבאים:
- האפליקציה יוצרת חיבור לשירות Vertex AI
- בקשת המשתמש נשלחת לשירות
- השרת מתחיל לעבד את הבקשה
- חיבור הסטרימינג נשאר פתוח ומוכן להעביר קטעים
העברת קטעים
כש-Gemini יוצר תוכן, קטעים נשלחים דרך הסטרימינג:
- השרת שולח קטעי טקסט בזמן שהם נוצרים (בדרך כלל כמה מילים או משפטים)
- כש-Gemini מחליט לבצע קריאה לפונקציה, הוא שולח את פרטי הקריאה לפונקציה
- יכול להיות שקטעי טקסט נוספים יבואו אחרי קריאות לפונקציות
- הסטרימינג ממשיך עד שהיצירה תושלם
עיבוד הדרגתי
האפליקציה מעבדת כל מקטע באופן מצטבר:
- כל מקטע טקסט מצורף לתשובה הקיימת
- קריאות לפונקציות מתבצעות ברגע שהן מזוהות
- ממשק המשתמש מתעדכן בזמן אמת עם תוצאות של טקסט ופונקציות
- מעקב אחרי המצב כדי להראות שהתגובה עדיין מועברת
השלמת הסטרימינג
בסיום היצירה:
- השרת סוגר את הסטרימינג
- הלולאה
await for
יוצאת באופן טבעי - ההודעה תסומן כ'הושלמה'
- מצב השיחה מוגדר חזרה ל'לא פעיל'
- ממשק המשתמש מתעדכן כך שישקף את המצב המלא.
השוואה בין סטרימינג לבין מודעות ללא סטרימינג
כדי להבין טוב יותר את היתרונות של סטרימינג, נבצע השוואה בין גישות של סטרימינג לבין גישות ללא סטרימינג:
יחס | ללא סטרימינג | סטרימינג |
זמן האחזור המשוער | המשתמש לא רואה כלום עד שהתשובה המלאה מוכנה | המשתמש רואה את המילים הראשונות תוך אלפיות השנייה |
חוויית משתמש | המתנה ארוכה ואז הופעה פתאומית של טקסט | מראה טקסט טבעי ומתפתח |
ניהול מצב | פשוטה יותר (הודעות הן בהמתנה או שהן הושלמו) | מורכבים יותר (הודעות יכולות להיות במצב סטרימינג) |
ביצוע פונקציה | מתרחש רק אחרי שליחת תשובה מלאה | מתרחש במהלך יצירת התשובה |
מורכבות ההטמעה | קל יותר להטמיע | נדרש ניהול מצב נוסף |
התאוששות משגיאה | תגובה מסוג 'הכול או כלום' | תשובות חלקיות עדיין יכולות להיות מועילות |
מורכבות הקוד | פחות מורכבים | מורכבים יותר בגלל טיפול במקור הנתונים |
באפליקציה כמו Colorist, היתרונות של חוויית המשתמש בסטרימינג עולים על המורכבות של ההטמעה, במיוחד לגבי פרשנויות של צבעים שיצירתן עשויה להימשך כמה שניות.
שיטות מומלצות לשיפור חוויית המשתמש בסטרימינג
כשאתם מטמיעים סטרימינג באפליקציות LLM משלכם, כדאי להשתמש בשיטות המומלצות הבאות:
- אינדיקטורים חזותיים ברורים: תמיד צריך לספק סימנים חזותיים ברורים שמבדילים בין הודעות בשידור חי לבין הודעות מלאות
- חסימה של קלט: השבתת קלט המשתמש במהלך הסטרימינג כדי למנוע מספר בקשות חופפות
- שחזור שגיאות: תכנון ממשק המשתמש כך שיוכל להתמודד עם שחזור קל במקרה שהסטרימינג מופרע
- מעבר בין מצבים: מוודאים שמעברים חלקים בין המצבים 'לא פעיל', 'סטרימינג' ו'הושלם'
- תצוגה חזותית של ההתקדמות: כדאי להשתמש באנימציות עדינות או בסמנים שמציגים עיבוד פעיל
- אפשרויות ביטול: באפליקציה מלאה, צריך לספק למשתמשים דרכים לבטל יצירות שנמצאות בתהליך יצירה.
- שילוב של תוצאות פונקציות: עיצוב ממשק המשתמש כך שיוכל לטפל בתוצאות של פונקציות שמופיעות באמצע תהליך
- אופטימיזציה של הביצועים: צמצום מספר הפעמים שממשק המשתמש צריך להיבנות מחדש במהלך עדכונים מהירים של שידור
החבילה colorist_ui
מטמיעה בשבילכם הרבה מהשיטות המומלצות האלה, אבל הן שיקולים חשובים בכל הטמעה של LLM בסטרימינג.
מה השלב הבא?
בשלב הבא, תחילו את תזמון ה-LLM על ידי שליחת התראה ל-Gemini כשמשתמשים בוחרים צבעים מההיסטוריה. כך תוכלו ליהנות מחוויית משתמש עקבית יותר, שבה LLM מודע לשינויים במצב האפליקציה שהמשתמשים יזמו.
פתרון בעיות
בעיות בעיבוד נתונים בזמן אמת
אם תיתקלו בבעיות בעיבוד הנתונים בזמן אמת:
- תסמינים: תשובות חלקיות, טקסט חסר או סיום פתאומי של הסטרימינג
- פתרון: בודקים את החיבור לרשת ומוודאים שהקוד כולל דפוסים תקינים של async/await
- אבחון: בודקים בחלונית היומן אם יש הודעות שגיאה או אזהרות שקשורות לעיבוד הסטרימינג.
- תיקון: מוודאים שכל עיבוד הסטרימינג כולל טיפול תקין בשגיאות באמצעות בלוקים של
try
/catch
קריאות חסרות לפונקציות
אם לא מזוהות קריאות לפונקציות בסטרימינג:
- התסמינים: הטקסט מופיע אבל הצבעים לא מתעדכנים, או שהיומן לא מציג קריאות לפונקציות
- הפתרון: מוודאים שההוראות של המערכת לגבי שימוש בקריאות לפונקציות הן נכונות
- אבחון: בודקים בחלונית היומנים אם מתקבלות קריאות לפונקציות
- תיקון: משנים את ההנחיה של המערכת כדי להנחות את LLM להשתמש בכלי
set_color
באופן מפורש יותר
טיפול כללי בשגיאות
אם יש בעיות אחרות:
- שלב 1: בודקים אם יש הודעות שגיאה בחלונית היומנים
- שלב 2: אימות הקישוריות של Vertex AI ב-Firebase
- שלב 3: מוודאים שכל הקוד שנוצר על ידי Riverpod מעודכן
- שלב 4: בודקים את הטמעת הסטרימינג כדי לאתר הצהרות await חסרות
מושגים מרכזיים שנלמדו
- הטמעת תשובות בסטרימינג באמצעות Gemini API לשיפור חוויית המשתמש
- ניהול מצב השיחה כדי לטפל כראוי באינטראקציות בשידור חי
- עיבוד טקסט קריאה לפונקציות בזמן אמת ברגע שהם מגיעים
- יצירת ממשקי משתמש רספונסיביים שמתעדכנים באופן מצטבר במהלך הסטרימינג
- טיפול בסטרימינג בו-זמני באמצעות דפוסים אסינכרונים מתאימים
- מתן משוב חזותי מתאים במהלך שידור התגובות
הטמעת סטרימינג שיפרה באופן משמעותי את חוויית המשתמש באפליקציית Colorist, ויצרה ממשק רספונסיבי ומעניין יותר שנראה כמו שיחה אמיתית.
8. סנכרון ההקשר של LLM
בשלב הבונוס הזה, נטמיע סנכרון הקשר של LLM על ידי שליחת הודעה ל-Gemini כשמשתמשים בוחרים צבעים מההיסטוריה. כך נוצרת חוויה עקבית יותר שבה ה-LLM מודע לפעולות של המשתמשים בממשק, ולא רק להודעות המפורשות שלהם.
מה תלמדו בשלב הזה
- יצירת סנכרון הקשר של LLM בין ממשק המשתמש ל-LLM
- סריאליזציה של אירועי ממשק משתמש להקשר ש-LLM יכול להבין
- עדכון ההקשר של השיחה על סמך פעולות של משתמשים
- יצירת חוויה עקבית בשיטות אינטראקציה שונות
- שיפור המודעות של LLM להקשר מעבר להודעות צ'אט בוטה
הסבר על סנכרון ההקשר של LLM
צ'אטבוטים רגילים מגיבים רק להודעות מפורשות של משתמשים, וכתוצאה מכך נוצרת ניתוק כשמשתמשים מבצעים אינטראקציה עם האפליקציה באמצעים אחרים. סנכרון ההקשר של LLM פותר את המגבלה הזו:
למה חשוב להשתמש בסנכרון ההקשר של LLM
כשמשתמשים מבצעים אינטראקציה עם האפליקציה דרך רכיבי ממשק המשתמש (למשל, בחירת צבע מההיסטוריה), ל-LLM אין דרך לדעת מה קרה, אלא אם תודיעו לו על כך במפורש. סנכרון ההקשר של LLM:
- שמירה על ההקשר: ה-LLM מעודכן לגבי כל פעולות המשתמש הרלוונטיות
- יצירת עקביות: יצירת חוויה עקבית שבה ה-LLM מתייחס לאינטראקציות בממשק המשתמש
- שיפור הבינה: מאפשרת ל-LLM להגיב בצורה מתאימה לכל פעולות המשתמש
- שיפור חוויית המשתמש: האפליקציה כולה נראית משולבת יותר ותגובה יותר מהר
- הפחתת המאמץ של המשתמשים: אין צורך להסביר למשתמשים באופן ידני את הפעולות שלהם בממשק המשתמש
באפליקציית Colorist, כשמשתמש בוחר צבע מההיסטוריה, אתם רוצים ש-Gemini יאשר את הפעולה הזו ויוסיף הערה חכמה על הצבע שנבחר, כדי לשמור על האשליה של עוזרת חכמה ומודעת.
עדכון שירות הצ'אט של Gemini לקבלת התראות על בחירת צבעים
קודם כול, מוסיפים שיטה ל-GeminiChatService
כדי להודיע ל-LLM כשמשתמש בוחר צבע מההיסטוריה. מעדכנים את הקובץ lib/services/gemini_chat_service.dart
:
lib/services/gemini_chat_service.dart
import 'dart:async';
import 'dart:convert'; // Add this import
import 'package:colorist_ui/colorist_ui.dart';
import 'package:firebase_vertexai/firebase_vertexai.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import '../providers/gemini.dart';
import 'gemini_tools.dart';
part 'gemini_chat_service.g.dart';
final conversationStateProvider = StateProvider(
(ref) => ConversationState.idle,
);
class GeminiChatService {
GeminiChatService(this.ref);
final Ref ref;
Future<void> notifyColorSelection(ColorData color) => sendMessage( // Add from here...
'User selected color from history: ${json.encode(color.toLLMContextMap())}',
); // To here.
Future<void> sendMessage(String message) async {
final chatSession = await ref.read(chatSessionProvider.future);
final conversationState = ref.read(conversationStateProvider);
final chatStateNotifier = ref.read(chatStateNotifierProvider.notifier);
final logStateNotifier = ref.read(logStateNotifierProvider.notifier);
if (conversationState == ConversationState.busy) {
logStateNotifier.logWarning(
"Can't send a message while a conversation is in progress",
);
throw Exception(
"Can't send a message while a conversation is in progress",
);
}
final conversationStateNotifier = ref.read(
conversationStateProvider.notifier,
);
conversationStateNotifier.state = ConversationState.busy;
chatStateNotifier.addUserMessage(message);
logStateNotifier.logUserText(message);
final llmMessage = chatStateNotifier.createLlmMessage();
try {
final responseStream = chatSession.sendMessageStream(
Content.text(message),
);
await for (final block in responseStream) {
await _processBlock(block, llmMessage.id);
}
} catch (e, st) {
logStateNotifier.logError(e, st: st);
chatStateNotifier.appendToMessage(
llmMessage.id,
"\nI'm sorry, I encountered an error processing your request. "
"Please try again.",
);
} finally {
chatStateNotifier.finalizeMessage(llmMessage.id);
conversationStateNotifier.state = ConversationState.idle;
}
}
Future<void> _processBlock(
GenerateContentResponse block,
String llmMessageId,
) async {
final chatSession = await ref.read(chatSessionProvider.future);
final chatStateNotifier = ref.read(chatStateNotifierProvider.notifier);
final logStateNotifier = ref.read(logStateNotifierProvider.notifier);
final blockText = block.text;
if (blockText != null) {
logStateNotifier.logLlmText(blockText);
chatStateNotifier.appendToMessage(llmMessageId, blockText);
}
if (block.functionCalls.isNotEmpty) {
final geminiTools = ref.read(geminiToolsProvider);
final responseStream = chatSession.sendMessageStream(
Content.functionResponses([
for (final functionCall in block.functionCalls)
FunctionResponse(
functionCall.name,
geminiTools.handleFunctionCall(
functionCall.name,
functionCall.args,
),
),
]),
);
await for (final response in responseStream) {
final responseText = response.text;
if (responseText != null) {
logStateNotifier.logLlmText(responseText);
chatStateNotifier.appendToMessage(llmMessageId, responseText);
}
}
}
}
}
@riverpod
GeminiChatService geminiChatService(Ref ref) => GeminiChatService(ref);
הוספת המפתח היא השיטה notifyColorSelection
, שמבצעת את הפעולות הבאות:
- הפונקציה מקבלת אובייקט
ColorData
שמייצג את הצבע שנבחר - מקודד אותו לפורמט JSON שאפשר לכלול בהודעה
- שליחת הודעה בפורמט מיוחד ל-LLM כדי לציין את הבחירה של המשתמש
- שימוש חוזר בשיטה הקיימת
sendMessage
לטיפול בהתראה
הגישה הזו מונעת כפילויות על ידי ניצול התשתית הקיימת לטיפול בהודעות.
עדכון האפליקציה הראשית כדי לקשר התראות על בחירת צבעים
עכשיו משנים את הקובץ lib/main.dart
כדי להעביר את פונקציית ההתראה על בחירת הצבע למסך הראשי:
lib/main.dart
import 'package:colorist_ui/colorist_ui.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'providers/gemini.dart';
import 'services/gemini_chat_service.dart';
void main() async {
runApp(ProviderScope(child: MainApp()));
}
class MainApp extends ConsumerWidget {
const MainApp({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final model = ref.watch(geminiModelProvider);
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),
),
);
}
}
השינוי העיקרי הוא הוספת פונקציית ה-callback notifyColorSelection
, שמחברת את האירוע בממשק המשתמש (בחירת צבע מההיסטוריה) למערכת ההתראות של LLM.
עדכון ההנחיה של המערכת
עכשיו צריך לעדכן את הנחיית המערכת כדי להנחות את ה-LLM איך להגיב להתראות על בחירת צבע. משנים את הקובץ assets/system_prompt.md
:
assets/system_prompt.md
# Colorist System Prompt
You are a color expert assistant integrated into a desktop app called Colorist. Your job is to interpret natural language color descriptions and set the appropriate color values using a specialized tool.
## Your Capabilities
You are knowledgeable about colors, color theory, and how to translate natural language descriptions into specific RGB values. You have access to the following tool:
`set_color` - Sets the RGB values for the color display based on a description
## How to Respond to User Inputs
When users describe a color:
1. First, acknowledge their color description with a brief, friendly response
2. Interpret what RGB values would best represent that color description
3. Use the `set_color` tool to set those values (all values should be between 0.0 and 1.0)
4. After setting the color, provide a brief explanation of your interpretation
Example:
User: "I want a sunset orange"
You: "Sunset orange is a warm, vibrant color that captures the golden-red hues of the setting sun. It combines a strong red component with moderate orange tones."
[Then you would call the set_color tool with approximately: red=1.0, green=0.5, blue=0.25]
After the tool call: "I've set a warm orange with strong red, moderate green, and minimal blue components that is reminiscent of the sun low on the horizon."
## When Descriptions are Unclear
If a color description is ambiguous or unclear, please ask the user clarifying questions, one at a time.
## When Users Select Historical Colors
Sometimes, the user will manually select a color from the history panel. When this happens, you'll receive a notification about this selection that includes details about the color. Acknowledge this selection with a brief response that recognizes what they've done and comments on the selected color.
Example notification:
User: "User selected color from history: {red: 0.2, green: 0.5, blue: 0.8, hexCode: #3380CC}"
You: "I see you've selected an ocean blue from your history. This tranquil blue with a moderate intensity has a calming, professional quality to it. Would you like to explore similar shades or create a contrasting color?"
## Important Guidelines
- Always keep RGB values between 0.0 and 1.0
- Provide thoughtful, knowledgeable responses about colors
- When possible, include color psychology, associations, or interesting facts about colors
- Be conversational and engaging in your responses
- Focus on being helpful and accurate with your color interpretations
התוספת העיקרית היא הקטע 'כאשר משתמשים בוחרים צבעים היסטוריים', שמאפשר:
- הסבר על המושג 'התראות על בחירת היסטוריה' ל-LLM
- דוגמה לאופן שבו נראות ההתראות האלה
- דוגמה לתגובה מתאימה
- הגדרת הציפיות לגבי אישור הבחירה והוספת תגובה לגבי הצבע
כך ה-LLM יוכל להבין איך להגיב בצורה מתאימה להודעות המיוחדות האלה.
יצירת קוד Riverpod
מריצים את הפקודה build runner כדי ליצור את הקוד הנדרש של Riverpod:
dart run build_runner build --delete-conflicting-outputs
הרצה ובדיקה של סנכרון ההקשר של LLM
מריצים את האפליקציה:
flutter run -d DEVICE
כדי לבדוק את סנכרון ההקשר של LLM, צריך:
- קודם כל, יוצרים כמה צבעים על ידי תיאור שלהם בצ'אט
- "Show me a vibrant purple"
- "I'd like a forest green"
- "Give me a bright red"
- לאחר מכן, לוחצים על אחת מהתמונות הממוזערות הצבעוניות בסרגל ההיסטוריה.
כדאי לשים לב לדברים הבאים:
- הצבע שנבחר מופיע במסך הראשי
- הודעה מהמשתמש תופיע בצ'אט עם ציון הצבע שנבחר
- ה-LLM מגיב על ידי אישור הבחירה והערה על הצבע
- כל האינטראקציה מרגישה טבעית ועקבית
כך נוצרת חוויה חלקה שבה ה-LLM מודע גם להודעות ישירות וגם לאינטראקציות בממשק המשתמש, ומגיב בהתאם.
איך פועל סנכרון ההקשר של LLM
עכשיו נרחיב על הפרטים הטכניים של הסנכרון הזה:
Data Flow
- פעולת משתמש: המשתמש לוחץ על צבע בסרגל ההיסטוריה
- אירוע בממשק המשתמש: הווידג'ט
MainScreen
מזהה את הבחירה הזו - ביצוע הקריאה החוזרת (callback): הקריאה החוזרת (callback)
notifyColorSelection
מופעלת - יצירת הודעה: נוצרת הודעה בפורמט מיוחד עם נתוני הצבע
- עיבוד LLM: ההודעה נשלחת אל Gemini, שמזהה את הפורמט
- תגובה לפי הקשר: Gemini מגיב בצורה הולמת על סמך ההנחיה של המערכת
- עדכון בממשק המשתמש: התשובה מופיעה בצ'אט, וכך נוצרת חוויה עקבית.
סריאליזציה של נתונים
היבט חשוב בגישה הזו הוא האופן שבו מבצעים סריאליזציה של נתוני הצבע:
'User selected color from history: ${json.encode(color.toLLMContextMap())}'
השיטה toLLMContextMap()
(שסופקת על ידי החבילה colorist_ui
) ממירה אובייקט ColorData
למפה עם מאפייני מפתח ש-LLM יכול להבין. בדרך כלל צוות כזה כולל:
- ערכי RGB (אדום, ירוק, כחול)
- ייצוג של קוד הקסדצימלי
- כל שם או תיאור שמשויכים לצבע
כשמגדירים את הפורמט של הנתונים האלה באופן עקבי ומצרפים אותם להודעה, מוודאים של-LLM יש את כל המידע הדרוש כדי לתת תשובה מתאימה.
יישומים רחבים יותר של סנכרון הקשר במודלים גדולים של שפה
לדפוס הזה של שליחת התראות ל-LLM על אירועים בממשק המשתמש יש יישומים רבים מעבר לבחירת צבע:
תרחישים אחרים לדוגמה
- שינויים במסננים: שליחת התראה ל-LLM כשמשתמשים מחילים מסננים על נתונים
- אירועי ניווט: דיווח ל-LLM על אירועים שבהם משתמשים עוברים לקטע אחר
- שינויים בבחירה: עדכון ה-LLM כשמשתמשים בוחרים פריטים מרשימות או מרשתות
- עדכוני העדפות: דיווח ל-LLM על שינויים בהגדרות או בהעדפות של המשתמשים
- מניפולציה בנתונים: דיווח ל-LLM כשמשתמשים מוסיפים, עורכים או מוחקים נתונים
בכל מקרה, התבנית נשארת זהה:
- זיהוי האירוע בממשק המשתמש
- סריאליזציה של נתונים רלוונטיים
- שליחת התראה בפורמט מיוחד ל-LLM
- הנחיה ל-LLM להגיב בצורה הולמת באמצעות הנחיה מהמערכת
שיטות מומלצות לסנכרון ההקשר של LLM
בהתאם להטמעה שלכם, ריכזנו כאן כמה שיטות מומלצות לסנכרון יעיל של ההקשר ב-LLM:
1. עיצוב עקבי
מומלץ להשתמש בפורמט עקבי להתראות כדי ש-LLM יוכל לזהות אותן בקלות:
"User [action] [object]: [structured data]"
2. הקשר עשיר
כדאי לכלול מספיק פרטים בהתראות כדי שה-LLM יוכל להגיב בצורה חכמה. לגבי צבעים, המשמעות היא ערכי RGB, קודי hex וכל מאפיין רלוונטי אחר.
3. הוראות ברורות
עליכם לספק הוראות מפורטות בהודעת המערכת לגבי אופן הטיפול בהתראות, רצוי עם דוגמאות.
4. שילוב טבעי
כדאי לתכנן את ההתראות כך שיתאימו באופן טבעי לשיחה, ולא יהיו הפרעות טכניות.
5. שליחת התראות באופן סלקטיבי
להודיע ל-LLM רק על פעולות שרלוונטיות לשיחה. לא צריך לדווח על כל אירוע בממשק המשתמש.
פתרון בעיות
בעיות של התראות
אם ה-LLM לא מגיב כראוי לבחירות של צבעים:
- בודקים שפורמט הודעת ההתראה תואם לתיאור בהנחיה של המערכת
- מוודאים שנתוני הצבע עוברים סריאליזציה בצורה תקינה
- מוודאים שההנחיה של המערכת כוללת הוראות ברורות לטיפול בבחירות
- מחפשים שגיאות בשירות הצ'אט כששולחים התראות
ניהול ההקשרים
אם נראה שה-LLM מאבד את ההקשר:
- בודקים שהשיחה בצ'אט מתנהלת בצורה תקינה
- איך מוודאים שהמעבר בין מצבי השיחה מתבצע בצורה תקינה
- מוודאים שההתראות נשלחות דרך אותה סשן צ'אט
בעיות כלליות
בעיות כלליות:
- בדיקת היומנים כדי לאתר שגיאות או אזהרות
- אימות הקישוריות של Vertex AI ב-Firebase
- בדיקה של אי-התאמות בסוגים של פרמטרים של פונקציות
- מוודאים שכל הקוד שנוצר על ידי Riverpod עדכני
מושגים מרכזיים שנלמדו
- יצירת סנכרון הקשר של LLM בין ממשק המשתמש ל-LLM
- סריאליזציה של אירועי ממשק משתמש להקשר שמתאים ל-LLM
- הדרכה של התנהגות LLM לדפוסי אינטראקציה שונים
- יצירת חוויה עקבית בכל האינטראקציות, גם בהודעות וגם בלי
- שיפור המוּדעוּת של LLM למצב האפליקציה הרחב יותר
הטמעת סנכרון ההקשר של LLM מאפשרת לכם ליצור חוויה משולבת באמת, שבה ה-LLM מרגיש כמו עוזר אישי מודע ומגיב ולא רק כמו גנרטור טקסט. אפשר להחיל את הדפוס הזה על אינספור אפליקציות אחרות כדי ליצור ממשקים טבעיים ואינטואיטיביים יותר שמבוססים על AI.
9. מעולה!
סיימת את הקודלהב של Colorist! 🎉
מה יצרתם
יצרתם אפליקציית Flutter פונקציונלית לחלוטין שמשלבת את Gemini API של Google כדי לפרש תיאורי צבעים בשפה טבעית. האפליקציה שלכם יכולה עכשיו:
- עיבוד תיאורים בשפה טבעית כמו 'כתום שקיעה' או 'כחול ים עמוק'
- להשתמש ב-Gemini כדי לתרגם את התיאורים האלה לערכי RGB באופן חכם
- הצגת הצבעים המתורגמים בזמן אמת באמצעות תשובות בסטרימינג
- טיפול באינטראקציות של משתמשים באמצעות רכיבי צ'אט וממשק המשתמש
- שמירה על מודעו להקשר בשיטות אינטראקציה שונות
מה עושים עכשיו
עכשיו, אחרי שלמדתם את העקרונות הבסיסיים של שילוב Gemini עם Flutter, ריכזנו כאן כמה דרכים להמשיך את הדרך:
שיפור האפליקציה Colorist
- לוחות צבעים: הוספת פונקציונליות ליצירת סכמות צבעים משלימים או תואמים
- קלט קולי: שילוב של זיהוי דיבור לתיאור מילולי של צבעים
- ניהול ההיסטוריה: הוספת אפשרויות לשמות, לארגון ולייצוא של ערכות צבעים
- הנחיות בהתאמה אישית: יצירת ממשק שמאפשר למשתמשים להתאים אישית את ההנחיות של המערכת
- ניתוח נתונים מתקדם: מעקב אחרי התיאורים שמניבים את התוצאות הטובות ביותר או גורמים לבעיות
תכונות נוספות של Gemini
- קלט רב-מודלי: הוספת קלט תמונות כדי לחלץ צבעים מתמונות
- יצירת תוכן: שימוש ב-Gemini ליצירת תוכן שקשור לצבע, כמו תיאורים או סיפורים
- שיפורים בקריאה לפונקציות: אפשר ליצור שילובים מורכבים יותר של כלים עם כמה פונקציות
- הגדרות בטיחות: כאן אפשר לקרוא על הגדרות בטיחות שונות ועל ההשפעה שלהן על התגובות
החלת התבניות האלה על דומיינים אחרים
- ניתוח מסמכים: יצירת אפליקציות שיכולות להבין ולנתח מסמכים
- עזרה בכתיבה יוצרת: פיתוח כלים לכתיבה עם הצעות מבוססות-LLM
- אוטומציה של משימות: עיצוב אפליקציות שמתרגמות שפה טבעית למשימות אוטומטיות
- אפליקציות מבוססות-ידע: יצירת מערכות מומחים בתחומים ספציפיים
משאבים
ריכזנו כאן כמה מקורות מידע חשובים שיעזרו לכם להמשיך ללמוד:
מסמכים רשמיים
קורס והדרכה ליצירת הנחיות
קהילה
סדרת Observable Flutter Agentic
בפרק 59, קרייג לאבנס (Craig Labenz) ואנדרו ברוגדן (Andrew Brogden) מדברים על סדנת הקוד הזו ומדגישים חלקים מעניינים בתהליך ה-build של האפליקציה.
בפרק 60, שוב נצטרף לקרייג ולאנדרו כשהם מוסיפים יכולות חדשות לאפליקציית codelab ומנסים לגרום למודלים מסוג LLM לפעול לפי ההוראות.
בפרק 61, כריס סלס מצטרף לקרייג כדי לבחון מחדש את האופן שבו מנתחים כותרות חדשותיות ויוצרים תמונות תואמות.
משוב
נשמח לשמוע על החוויה שלך עם הקודלהב הזה. נשמח לקבל ממך משוב באחת מהדרכים הבאות:
תודה שסיימת את הקודלהב הזה. אנחנו מקווים שתמשיך לחקור את האפשרויות המרגשות במפגש של Flutter עם AI.