درباره این codelab
1. یک برنامه فلاتر مجهز به Gemini بسازید
چیزی که خواهی ساخت
در این نرم افزار کد، Colorist را می سازید - یک برنامه تعاملی Flutter که قدرت Gemini API را مستقیماً به برنامه Flutter شما می آورد. تا به حال خواسته اید به کاربران اجازه دهید برنامه شما را از طریق زبان طبیعی کنترل کنند اما نمی دانستید از کجا شروع کنید؟ این کد لبه به شما نشان می دهد که چگونه.
Colorist به کاربران اجازه می دهد تا رنگ ها را به زبان طبیعی توصیف کنند (مانند "نارنجی غروب خورشید" یا "آبی عمیق اقیانوس")، و برنامه:
- این توضیحات را با استفاده از API Gemini Google پردازش می کند
- توضیحات را به مقادیر دقیق رنگ RGB تفسیر می کند
- رنگ را در زمان واقعی روی صفحه نمایش می دهد
- جزئیات رنگ فنی و زمینه جالب در مورد رنگ را ارائه می دهد
- تاریخچه ای از رنگ های اخیرا تولید شده را حفظ می کند
این برنامه دارای یک رابط صفحه نمایش تقسیم شده با یک منطقه نمایش رنگی و یک سیستم چت تعاملی در یک طرف، و یک پانل گزارش دقیق است که تعاملات خام LLM را در طرف دیگر نشان می دهد. این گزارش شما را قادر می سازد تا بهتر درک کنید که چگونه یک ادغام LLM واقعاً در زیر هود کار می کند.
چرا این موضوع برای توسعه دهندگان فلاتر اهمیت دارد
LLM ها نحوه تعامل کاربران با برنامه ها را متحول می کنند، اما ادغام آنها به طور موثر در برنامه های موبایل و دسکتاپ چالش های منحصر به فردی را ایجاد می کند. این نرم افزار کد الگوهای عملی را به شما آموزش می دهد که فراتر از فراخوانی های خام API هستند.
سفر یادگیری شما
این کد لبه شما را مرحله به مرحله مراحل ساخت Colorist را هدایت می کند:
- راهاندازی پروژه - شما با یک ساختار اولیه برنامه Flutter و بسته
colorist_ui
شروع میکنید - ادغام اولیه Gemini - برنامه خود را به Vertex AI در Firebase متصل کنید و ارتباطات ساده LLM را پیاده سازی کنید
- درخواست موثر - یک سیستم اعلان ایجاد کنید که LLM را برای درک توضیحات رنگ راهنمایی کند
- اعلان های عملکرد - ابزارهایی را که LLM می تواند برای تنظیم رنگ ها در برنامه شما استفاده کند، تعریف کنید
- مدیریت ابزار - فراخوانی های تابع را از LLM پردازش کنید و آنها را به وضعیت برنامه خود متصل کنید
- پاسخهای جریانی - تجربه کاربر را با پاسخهای جریانی LLM در زمان واقعی افزایش دهید
- همگام سازی زمینه LLM - با اطلاع رسانی به LLM از اقدامات کاربر، یک تجربه منسجم ایجاد کنید.
چیزی که یاد خواهید گرفت
- Vertex AI را در Firebase برای برنامه های Flutter پیکربندی کنید
- سیستم موثر Craft دستور هدایت رفتار LLM را می دهد
- اعلانهای عملکردی را پیادهسازی کنید که ویژگیهای زبان طبیعی و برنامه را پل میکنند
- پاسخهای جریانی را برای تجربه کاربری پاسخگو پردازش کنید
- همگام سازی حالت بین رویدادهای UI و 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)'),
},
);
نمای کلی ویدیویی از این کد لبه
تماشا کنید که کریگ لابنز و اندرو بروگدون در مورد این آزمایشگاه کد در شرح شماره 59 مشهود فلاتر بحث می کنند:
پیش نیازها
برای استفاده حداکثری از این کد لبه، باید موارد زیر را داشته باشید:
- تجربه توسعه فلاتر - آشنایی با مبانی فلاتر و نحو دارت
- دانش برنامهنویسی ناهمزمان - درک آیندهها، همگامسازی/انتظار، و جریانها
- حساب Firebase - برای راه اندازی Firebase به یک حساب Google نیاز دارید
- پروژه Firebase با صورتحساب فعال - Vertex AI در Firebase به یک حساب صورتحساب نیاز دارد
بیایید شروع به ساخت اولین برنامه Flutter با LLM کنیم!
2. راه اندازی پروژه و خدمات اکو
در این مرحله اول، ساختار پروژه را تنظیم کرده و یک سرویس اکو ساده را پیاده سازی می کنید که بعداً با ادغام Gemini API جایگزین خواهد شد. این معماری برنامه را ایجاد می کند و اطمینان حاصل می کند که رابط کاربری شما قبل از افزودن پیچیدگی تماس های LLM به درستی کار می کند.
آنچه در این مرحله خواهید آموخت
- راه اندازی پروژه فلاتر با وابستگی های مورد نیاز
- کار با بسته
colorist_ui
برای اجزای UI - پیاده سازی سرویس پیام اکو و اتصال آن به UI
یک نکته مهم در مورد قیمت گذاری
یک پروژه فلاتر جدید ایجاد کنید
با ایجاد یک پروژه Flutter جدید با دستور زیر شروع کنید:
flutter create -e colorist --platforms=android,ios,macos,web,windows
پرچم -e
نشان می دهد که شما یک پروژه خالی بدون برنامه counter
می خواهید. این برنامه برای کار در دسکتاپ، موبایل و وب طراحی شده است. با این حال، flutterfire
در حال حاضر از لینوکس پشتیبانی نمی کند.
وابستگی ها را اضافه کنید
به فهرست پروژه خود بروید و وابستگی های مورد نیاز را اضافه کنید:
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
: یک بسته سفارشی که اجزای رابط کاربری را برای برنامه Colorist فراهم می کند -
flutter_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
این پیکربندی، پرزهای مخصوص 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
اجزای رابط کاربری از پیش ساخته شده و ابزارهای مدیریت حالت را ارائه می دهد:
- صفحه اصلی : مؤلفه اصلی رابط کاربری که نمایش می دهد:
- طرح بندی صفحه تقسیم بر روی دسکتاپ (منطقه تعامل و پانل گزارش)
- یک رابط زبانهدار در موبایل
- نمایش رنگی، رابط چت، و تصاویر کوچک تاریخچه
- مدیریت ایالت : این برنامه از چندین اعلان کننده وضعیت استفاده می کند:
- ChatStateNotifier : پیام های چت را مدیریت می کند
- ColorStateNotifier : رنگ و تاریخچه فعلی را مدیریت می کند
- LogStateNotifier : ورودی های گزارش را برای اشکال زدایی مدیریت می کند
- مدیریت پیام : برنامه از یک مدل پیام با حالت های مختلف استفاده می کند:
- پیام های کاربر : توسط کاربر وارد شده است
- پیامهای LLM : توسط LLM (یا سرویس اکو شما در حال حاضر) ایجاد شده است.
- MessageState : پیگیری میکند که آیا پیامهای LLM کامل هستند یا هنوز در جریان هستند
معماری اپلیکیشن
این برنامه از معماری زیر پیروی می کند:
- لایه رابط کاربری : توسط بسته
colorist_ui
ارائه شده است - مدیریت حالت : از Riverpod برای مدیریت حالت واکنشی استفاده می کند
- لایه سرویس : در حال حاضر شامل سرویس اکو ساده شما است، این با سرویس چت Gemini جایگزین خواهد شد
- یکپارچه سازی LLM : در مراحل بعدی اضافه خواهد شد
این جداسازی به شما امکان میدهد تا روی اجرای یکپارچهسازی LLM تمرکز کنید در حالی که اجزای UI قبلاً مراقبت شدهاند.
برنامه را اجرا کنید
برنامه را با دستور زیر اجرا کنید:
flutter run -d DEVICE
DEVICE
با دستگاه مورد نظر خود، مانند macos
، windows
، chrome
یا شناسه دستگاه جایگزین کنید.
اکنون باید برنامه Colorist را با موارد زیر مشاهده کنید:
- یک ناحیه نمایش رنگی با رنگ پیشفرض
- یک رابط چت که در آن می توانید پیام ها را تایپ کنید
- پانل گزارشی که تعاملات چت را نشان می دهد
پیامی مانند "I'd like a blue color" را تایپ کنید و Send را فشار دهید. سرویس اکو به سادگی پیام شما را تکرار می کند. در مراحل بعدی، این را با تفسیر رنگ واقعی با استفاده از Gemini API از طریق Vertex AI در Firebase جایگزین خواهید کرد.
بعدش چی؟
در مرحله بعد، شما Firebase را پیکربندی کرده و یکپارچه سازی اولیه Gemini API را برای جایگزینی سرویس echo خود با سرویس چت Gemini پیاده سازی می کنید. این به برنامه اجازه می دهد تا توضیحات رنگ را تفسیر کند و پاسخ های هوشمندانه ای ارائه دهد.
عیب یابی
مشکلات بسته UI
اگر با بسته colorist_ui
با مشکل مواجه شدید:
- مطمئن شوید که از آخرین نسخه استفاده می کنید
- بررسی کنید که وابستگی را به درستی اضافه کرده اید
- نسخه های بسته متناقض را بررسی کنید
خطاهای ساخت
اگر خطاهای ساخت را مشاهده کردید:
- مطمئن شوید که آخرین کانال پایدار Flutter SDK را نصب کرده اید
-
flutter clean
را اجرا کنید و سپسflutter pub get
- خروجی کنسول را برای پیام های خطای خاص بررسی کنید
مفاهیم کلیدی آموخته شده
- راه اندازی پروژه فلاتر با وابستگی های لازم
- درک معماری برنامه و مسئولیت های اجزاء
- پیاده سازی یک سرویس ساده که رفتار یک LLM را تقلید می کند
- اتصال سرویس به اجزای UI
- استفاده از ریورپاد برای مدیریت دولتی
3. ادغام چت اولیه Gemini
در این مرحله، سرویس echo مرحله قبل را با ادغام 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 خود وارد شوید.
- روی ایجاد پروژه Firebase کلیک کنید یا یک پروژه موجود را انتخاب کنید.
- برای ایجاد پروژه خود، جادوگر راه اندازی را دنبال کنید.
- هنگامی که پروژه شما ایجاد شد، برای دسترسی به خدمات Vertex AI باید به پلن Blaze (پرداخت در صورت تمایل) ارتقا دهید. روی دکمه ارتقا در پایین سمت چپ کنسول Firebase کلیک کنید.
Vertex AI را در پروژه Firebase خود راه اندازی کنید
- در کنسول Firebase، به پروژه خود بروید.
- در نوار کناری سمت چپ، AI را انتخاب کنید.
- در کارت Vertex AI در Firebase، Get Started را انتخاب کنید.
- برای فعال کردن Vertex AI در APIهای Firebase برای پروژه خود، دستورات را دنبال کنید.
FlutterFire CLI را نصب کنید
FlutterFire CLI راه اندازی 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، web) را شناسایی کرده و آنها را به درستی پیکربندی می کند.
پیکربندی پلتفرم خاص
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/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();
}
این فایل مبنایی را برای سه ارائه دهنده کلیدی تعریف می کند. این ارائهدهندگان زمانی ایجاد میشوند که dart run build_runner
توسط مولدهای کد Riverpod اجرا میکنید.
-
firebaseAppProvider
: Firebase را با پیکربندی پروژه شما راه اندازی می کند -
geminiModelProvider
: یک نمونه مدل مولد Gemini ایجاد می کند -
chatSessionProvider
: یک جلسه چت با مدل Gemini ایجاد و نگهداری می کند
keepAlive: true
در جلسه چت تضمین می کند که در طول چرخه عمر برنامه باقی بماند و زمینه گفتگو حفظ شود.
سرویس چت جمینی را پیاده سازی کنید
یک فایل جدید 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 ثبت می کند
- خطاها را با بازخورد مناسب کاربر مدیریت می کند
توجه: پنجره Log تقریباً شبیه به پنجره چت در این مرحله خواهد بود. هنگامی که فراخوانی های تابع و سپس پاسخ های جریانی را معرفی کنید، گزارش جالب تر می شود.
کد 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),
),
);
}
}
تغییرات کلیدی در این به روز رسانی عبارتند از:
- جایگزینی سرویس اکو با سرویس چت مبتنی بر API Gemini
- افزودن صفحات بارگذاری و خطا با استفاده از الگوی
AsyncValue
ریورپاد با روشwhen
- اتصال رابط کاربری به سرویس چت جدید خود از طریق پاسخ تماس
sendMessage
برنامه را اجرا کنید
برنامه را با دستور زیر اجرا کنید:
flutter run -d DEVICE
DEVICE
با دستگاه مورد نظر خود، مانند macos
، windows
، chrome
یا شناسه دستگاه جایگزین کنید.
اکنون وقتی پیامی را تایپ میکنید، به API Gemini ارسال میشود و به جای پژواک، پاسخی از LLM دریافت خواهید کرد. پانل گزارش، تعاملات با API را نشان می دهد.
درک ارتباطات LLM
بیایید لحظه ای وقت بگذاریم تا بفهمیم هنگام برقراری ارتباط با Gemini API چه اتفاقی می افتد:
جریان ارتباطی
- ورودی کاربر : کاربر متنی را در رابط چت وارد می کند
- درخواست قالببندی : برنامه متن را به عنوان یک شی
Content
برای Gemini API قالببندی میکند - ارتباط API : متن از طریق Vertex AI در Firebase به API Gemini ارسال می شود.
- پردازش LLM : مدل Gemini متن را پردازش کرده و پاسخی را ایجاد می کند
- مدیریت پاسخ : برنامه پاسخ را دریافت می کند و رابط کاربری را به روز می کند
- ورود به سیستم : تمام ارتباطات برای شفافیت ثبت شده است
جلسات چت و زمینه گفتگو
جلسه چت جمینی زمینه بین پیام ها را حفظ می کند و امکان تعاملات مکالمه را فراهم می کند. این بدان معناست که LLM تبادلات قبلی را در جلسه جاری "به خاطر می آورد" و امکان گفتگوهای منسجم تر را فراهم می کند.
keepAlive: true
در ارائه دهنده جلسه چت شما تضمین می کند که این زمینه در طول چرخه عمر برنامه باقی بماند. این زمینه پایدار برای حفظ یک جریان گفتگوی طبیعی با LLM بسیار مهم است.
بعدش چی؟
در این مرحله، میتوانید هر چیزی را از Gemini API بپرسید، زیرا هیچ محدودیتی برای پاسخ دادن به آن وجود ندارد. برای مثال، میتوانید خلاصهای از جنگهای رز را از آن بخواهید، که به هدف برنامه رنگی شما مرتبط نیست.
در مرحله بعد، یک سیستم اعلان ایجاد می کنید تا Gemini را در تفسیر موثرتر توضیحات رنگ راهنمایی کند. این نشان می دهد که چگونه می توان رفتار LLM را برای نیازهای خاص برنامه سفارشی کرد و قابلیت های آن را روی دامنه برنامه خود متمرکز کرد.
عیب یابی
مشکلات پیکربندی Firebase
اگر با خطاهای اولیه Firebase مواجه شدید:
- مطمئن شوید که فایل
firebase_options.dart
شما به درستی تولید شده است - بررسی کنید که به طرح Blaze برای دسترسی به هوش مصنوعی Vertex ارتقا داده اید
خطاهای دسترسی به 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 چگونه باید برای سازگاری فرمت شوند
- مبادله مثال : یک مثال عینی از الگوی تعامل مورد انتظار ارائه می دهد
- Edge case handling : نحوه رسیدگی به توضیحات نامشخص را آموزش می دهد
- محدودیت ها و دستورالعمل ها : مرزهایی مانند حفظ مقادیر 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
سعی کنید آن را با توضیحات رنگ های مختلف آزمایش کنید:
- "من یک آبی آسمانی می خواهم"
- "به من یک جنگل سبز بدهید"
- "یک نارنجی غروب پر جنب و جوش بسازید"
- "من رنگ اسطوخودوس تازه را می خواهم"
- "چیزی شبیه آبی عمیق اقیانوس به من نشان بده"
باید توجه داشته باشید که 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
- ایجاد اعلانهای مؤثر با دستورالعملها، مثالها و محدودیتهای واضح
- بارگیری و استفاده از اعلان های سیستم در یک برنامه فلاتر
- هدایت رفتار LLM برای وظایف خاص دامنه
- استفاده از مهندسی سریع برای شکل دادن به پاسخ های LLM
این مرحله نشان می دهد که چگونه می توانید به سفارشی سازی قابل توجهی از رفتار LLM بدون تغییر کد خود دست یابید - به سادگی با ارائه دستورالعمل های واضح در اعلان سیستم.
5. اعلان عملکرد برای ابزارهای LLM
در این مرحله، کار فعال کردن Gemini را برای انجام اقداماتی در برنامه خود با اجرای اعلانهای عملکرد آغاز میکنید. این ویژگی قدرتمند به LLM اجازه می دهد تا نه تنها مقادیر RGB را پیشنهاد کند، بلکه آنها را در UI برنامه شما از طریق فراخوانی ابزار تخصصی تنظیم کند. با این حال، برای دیدن درخواستهای 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.0 مشخص شده است -
green
: جزء سبز رنگ RGB که به صورت عددی بین 0.0 و 1.0 مشخص شده است -
blue
: جزء آبی RGB که به عنوان عددی بین 0.0 و 1.0 مشخص شده است
-
- انواع طرحواره : شما از
Schema.number()
برای نشان دادن این مقادیر عددی استفاده می کنید - مجموعه ابزار : شما فهرستی از ابزارهای حاوی اعلان تابع خود را ایجاد می کنید
این رویکرد ساختاریافته به Gemini LLM کمک می کند تا درک کند:
- زمانی که باید این تابع را فراخوانی کند
- چه پارامترهایی را باید ارائه دهد
- چه محدودیت هایی برای آن پارامترها اعمال می شود (مانند محدوده مقدار)
ارائه دهنده مدل 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 فرمت شده، اکنون در مورد ابزار
set_color
به LLM بگویید. - فرآیند اصلاح شده : شما مرحله 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
این مرحله نشان می دهد که چگونه LLM ها می توانند شکاف بین ورودی زبان طبیعی و تماس های عملکردی ساختار یافته را ایجاد کنند ، زمینه را برای ادغام یکپارچه بین مکالمه و ویژگی های کاربردی قرار دهند.
6. اجرای کنترل ابزار
در این مرحله ، شما برای تماس های عملکردی که از Gemini حاصل می شود ، دستگیرندگان را پیاده سازی می کنید. این امر حلقه ارتباط بین ورودی های زبان طبیعی و ویژگی های کاربردی بتونی را تکمیل می کند ، و به LLM این امکان را می دهد تا مستقیماً UI خود را بر اساس توضیحات کاربر دستکاری کند.
آنچه در این مرحله یاد خواهید گرفت
- درک خط لوله فراخوانی عملکرد کامل در برنامه های LLM
- پردازش فراخوانی از جمینی در یک برنامه Flutter
- اجرای دستگیره های عملکردی که وضعیت برنامه را تغییر می دهند
- پاسخگویی به عملکرد و بازگشت نتایج به LLM
- ایجاد یک جریان ارتباطی کامل بین LLM و UI
- تماس و پاسخ های عملکردی برای شفافیت
درک خط لوله عملکردی
قبل از غواصی به اجرای ، بیایید خط لوله فراخوانی عملکرد کامل را درک کنیم:
جریان پایان به پایان
- ورودی کاربر : کاربر رنگی را در زبان طبیعی توصیف می کند (به عنوان مثال ، "جنگل سبز")
- پردازش LLM : جمینی توضیحات را تجزیه و تحلیل می کند و تصمیم می گیرد با عملکرد
set_color
تماس بگیرید - تولید تماس عملکرد : جمینی JSON ساختاری را با پارامترها (مقادیر قرمز ، سبز ، آبی) ایجاد می کند
- پذیرش تماس عملکرد : برنامه شما این داده های ساختاری را از Gemini دریافت می کند
- اجرای عملکرد : برنامه شما عملکرد را با پارامترهای ارائه شده اجرا می کند
- بروزرسانی حالت : عملکرد وضعیت برنامه شما را به روز می کند (تغییر رنگ نمایش داده شده)
- تولید پاسخ : عملکرد شما نتایج به LLM برمی گردد
- ترکیب پاسخ : LLM این نتایج را در پاسخ نهایی خود گنجانیده است
- UI Update : UI شما به تغییر حالت واکنش نشان می دهد و رنگ جدید را نشان می دهد
چرخه ارتباط کامل برای ادغام مناسب LLM ضروری است. هنگامی که یک LLM یک تماس با عملکرد برقرار می کند ، به سادگی درخواست را ارسال نمی کند و حرکت می کند. در عوض ، منتظر است تا برنامه شما عملکرد را اجرا کند و نتایج را برگرداند. سپس LLM از این نتایج برای تدوین پاسخ نهایی خود استفاده می کند و یک جریان مکالمه طبیعی ایجاد می کند که اقدامات انجام شده را تصدیق می کند.
پیاده سازی های عملکردی
بیایید فایل 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);
درک دستگیره های عملکرد
بیایید آنچه را که این دستگیرندگان عملکرد انجام می دهند را تجزیه کنیم:
-
handleFunctionCall
: یک توزیع کننده مرکزی که:- ورود به سیستم برای شفافیت در پانل ورود به سیستم را ثبت می کند
- مسیرهای مربوط به کنترل کننده مناسب بر اساس نام عملکرد
- پاسخ ساختاری را برمی گرداند که به LLM ارسال می شود
-
handleSetColor
: کنترل کننده خاص برای عملکردset_color
شما که:- مقادیر RGB را از نقشه آرگومان استخراج می کند
- آنها را به انواع مورد انتظار تبدیل می کند (دو برابر)
- حالت رنگ برنامه را با استفاده از
colorStateNotifier
به روز می کند - با وضعیت موفقیت و اطلاعات رنگی فعلی یک پاسخ ساختاری ایجاد می کند
- نتایج عملکرد را برای اشکال زدایی ثبت می کند
-
handleUnknownFunction
: یک کنترل کننده Fallback برای عملکردهای ناشناخته که:- هشدار در مورد عملکرد پشتیبانی نشده را ثبت می کند
- پاسخ خطا به LLM را برمی گرداند
عملکرد handleSetColor
از اهمیت ویژه ای برخوردار است زیرا شکاف بین درک زبان طبیعی LLM و تغییر UI بتن را ایجاد می کند.
سرویس چت جمینی را برای پردازش تماس ها و پاسخ های عملکرد به روز کنید
حال ، بیایید پرونده 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 شامل هرگونه تماس عملکردی است
- برای هر تماس عملکردی ، با نام عملکرد و آرگومان ها از روش
handleFunctionCall
خود فراخوانی می کند - نتایج هر تماس عملکرد را جمع می کند
- این نتایج را با استفاده از
Content.functionResponses
به LLM ارسال می کند - پاسخ LLM به نتایج عملکرد را پردازش می کند
- UI را با متن پاسخ نهایی به روز می کند
این یک جریان سفر دور ایجاد می کند:
- کاربر → LLM: درخواست رنگ می کند
- LLM → برنامه: تماس با عملکرد با پارامترها
- برنامه → کاربر: رنگ جدید نمایش داده می شود
- برنامه → LLM: نتایج عملکرد
- LLM → کاربر: پاسخ نهایی شامل نتایج عملکرد
کد Riverpod را تولید کنید
دستور ساخت Runner را برای تولید کد مورد نیاز RiverPod اجرا کنید:
dart run build_runner build --delete-conflicting-outputs
جریان کامل را اجرا و آزمایش کنید
اکنون برنامه خود را اجرا کنید:
flutter run -d DEVICE
سعی کنید توضیحات مختلف رنگ را وارد کنید:
- "من یک قمار قرمز عمیق می خواهم"
- "به من یک آبی آرام آسمان به من نشان دهید"
- "رنگ برگهای نعناع تازه را به من بدهید"
- "من می خواهم یک پرتقال غروب خورشید را ببینم"
- "آن را به یک بنفش سلطنتی غنی تبدیل کنید"
حالا باید ببینید:
- پیام شما در رابط چت ظاهر می شود
- پاسخ جمینی که در گپ ظاهر می شود
- تماس های عملکردی که در پانل ورود به سیستم وارد می شوند
- نتایج عملکرد بلافاصله پس از ثبت نام
- به روزرسانی مستطیل رنگ برای نمایش رنگ توصیف شده
- مقادیر RGB به روزرسانی برای نشان دادن اجزای رنگ جدید
- پاسخ نهایی جمینی ظاهر می شود ، اغلب در مورد رنگی که تنظیم شده است اظهار نظر می کند
پانل ورود به سیستم بینشی در مورد آنچه در پشت صحنه اتفاق می افتد ارائه می دهد. خواهید دید:
- عملکرد دقیق با جمینی تماس می گیرد
- پارامترهایی که برای هر مقدار RGB انتخاب می کند
- نتیجه عملکرد شما در حال بازگشت است
- پاسخ های پیگیری از جمینی
اعلان کننده حالت رنگ
colorStateNotifier
که برای به روزرسانی رنگ ها استفاده می کنید بخشی از بسته colorist_ui
است. مدیریت می کند:
- رنگ فعلی نمایش داده شده در UI
- تاریخچه رنگ (10 رنگ آخر)
- اطلاع رسانی در مورد تغییرات دولت در مؤلفه های UI
هنگامی که با updateColor
با مقادیر جدید RGB تماس می گیرید ، آن:
- با مقادیر ارائه شده یک شیء جدید
ColorData
ایجاد می کند - رنگ فعلی را در حالت برنامه به روز می کند
- رنگ را به تاریخ اضافه می کند
- به روزرسانی های UI از طریق مدیریت دولت Riverpod را تحریک می کند
اجزای UI در بسته colorist_ui
این حالت را تماشا می کنند و هنگام تغییر به طور خودکار به روز می شوند و یک تجربه واکنشی ایجاد می کنند.
درک رسیدگی به خطای
اجرای شما شامل رسیدگی به خطای قوی است:
- بلوک Try-Catch : تمام تعامل های LLM را برای گرفتن هرگونه استثنا می کند
- ورود به خطا : خطاهای سوابق موجود در صفحه ورود به سیستم با آثار پشته
- بازخورد کاربر : یک پیام خطای دوستانه در گپ ارائه می دهد
- پاکسازی حالت : حتی اگر خطایی رخ دهد ، وضعیت پیام را نهایی می کند
این تضمین می کند که برنامه پایدار باقی مانده و بازخورد مناسبی را ارائه می دهد حتی در صورت بروز مشکلات با سرویس LLM یا اجرای عملکرد.
قدرت عملکرد خواستار تجربه کاربر
آنچه در اینجا انجام داده اید نشان می دهد که چگونه LLM ها می توانند رابط های طبیعی قدرتمند ایجاد کنند:
- رابط زبان طبیعی : کاربران در زبان روزمره قصد دارند
- تفسیر هوشمند : LLM توضیحات مبهم را به ارزشهای دقیق ترجمه می کند
- دستکاری مستقیم : UI در پاسخ به زبان طبیعی به روز می شود
- پاسخ های متنی : LLM زمینه مکالمه ای را در مورد تغییرات ارائه می دهد
- بار شناختی کم : کاربران نیازی به درک مقادیر RGB یا نظریه رنگ ندارند
این الگوی استفاده از عملکرد LLM که خواستار ایجاد زبان طبیعی و اقدامات UI است ، می تواند به دامنه های بی شماری دیگر فراتر از انتخاب رنگ گسترش یابد.
بعدش چی؟
در مرحله بعدی ، با اجرای پاسخ های جریان ، تجربه کاربر را ارتقا می بخشید. به جای اینکه منتظر پاسخ کامل باشید ، تکه های متنی و تماس های عملکردی را در هنگام دریافت آنها پردازش می کنید و یک برنامه پاسخگوتر و جذاب تر ایجاد می کنید.
عیب یابی
مسائل مربوط به تماس عملکرد
اگر جمینی با توابع یا پارامترهای شما تماس نگیرد نادرست است:
- تأیید عملکرد خود را با آنچه در سریع سیستم توضیح داده شده است مطابقت دهید
- بررسی کنید که نام ها و انواع پارامترها سازگار هستند
- اطمینان حاصل کنید که سریع سیستم شما صریحاً به LLM دستور می دهد تا از ابزار استفاده کند
- نام عملکرد را در کنترل کننده خود دقیقاً مطابق آنچه در اعلامیه است مطابقت دهید
- برای اطلاعات دقیق در مورد تماس های عملکرد ، پانل ورود را بررسی کنید
مسائل پاسخ عملکرد
اگر نتایج عملکرد به درستی به LLM ارسال نمی شود:
- بررسی کنید که عملکرد شما یک نقشه به درستی فرمت شده را برمی گرداند
- تأیید کنید که Content.FunctionResponses به درستی ساخته شده است
- به دنبال هرگونه خطایی در سیاهه مربوط به پاسخهای عملکرد باشید
- اطمینان حاصل کنید که از همان جلسه چت برای پاسخ استفاده می کنید
مشکلات نمایش رنگ
اگر رنگ ها به درستی نمایش داده نمی شوند:
- اطمینان حاصل کنید که مقادیر RGB به درستی به دونفره تبدیل شده است (LLM ممکن است آنها را به عنوان عدد صحیح ارسال کند)
- تأیید کنید که مقادیر در محدوده مورد انتظار هستند (0.0 تا 1.0)
- بررسی کنید که اطلاعات حالت رنگی به درستی خوانده می شود
- برای مقادیر دقیق منتقل شده به عملکرد ، ورود به سیستم را بررسی کنید
مشکلات عمومی
برای مسائل عمومی:
- سیاهههای مربوط به خطاها یا هشدارها را بررسی کنید
- Vertex AI را در اتصال Firebase تأیید کنید
- هر نوع عدم تطابق را در پارامترهای عملکرد بررسی کنید
- اطمینان حاصل کنید که تمام کد تولید شده Riverpod به روز است
مفاهیم کلیدی آموخته شده
- اجرای یک خط لوله فراخوانی عملکرد کامل در Flutter
- ایجاد ارتباط کامل بین LLM و برنامه شما
- پردازش داده های ساختاری از پاسخ های LLM
- ارسال نتایج عملکرد به LLM برای ترکیب در پاسخ ها
- با استفاده از پانل ورود به سیستم برای تعامل LLM-Application
- اتصال ورودی های زبان طبیعی به تغییرات UI بتن
با تکمیل این مرحله ، برنامه شما اکنون یکی از قدرتمندترین الگوهای برای ادغام LLM را نشان می دهد: ترجمه ورودی های زبان طبیعی به اقدامات UI بتن ، ضمن حفظ مکالمه منسجم که این اقدامات را تصدیق می کند. این یک رابط بصری و مکالمه ای ایجاد می کند که برای کاربران جادویی احساس می کند.
7. پاسخ های جریان برای بهتر UX
در این مرحله ، با اجرای پاسخ های جریان از جمینی ، تجربه کاربر را ارتقا می بخشید. به جای انتظار برای تولید کل پاسخ ، تکه های متنی و تماس های عملکردی را که دریافت می شود پردازش می کنید و یک برنامه پاسخگو تر و جذاب تر ایجاد می کنید.
آنچه را در این مرحله پوشش خواهید داد
- اهمیت جریان برای برنامه های کاربردی LLM
- اجرای پاسخ های جریان LLM در یک برنامه Flutter
- پردازش تکه های متن جزئی هنگام ورود از API
- مدیریت وضعیت مکالمه برای جلوگیری از درگیری پیام
- تماس با عملکرد تماس در پاسخ های جریان
- ایجاد شاخص های بصری برای پاسخ های در حال پیشرفت
چرا جریان برای برنامه های LLM مهم است
قبل از اجرای ، بیایید درک کنیم که چرا پاسخ های جریان برای ایجاد تجربیات عالی کاربر با LLM ها بسیار مهم هستند:
تجربه کاربری بهبود یافته
پاسخ های جریان چندین مزایای مهم تجربه کاربر را ارائه می دهد:
- کاهش تأخیر درک شده : کاربران می بینند که متن بلافاصله شروع می شود (به طور معمول در 100-300ms) ، به جای اینکه منتظر چندین ثانیه برای پاسخ کامل باشید. این برداشت از فوری به طرز چشمگیری رضایت کاربر را بهبود می بخشد.
- ریتم مکالمه طبیعی : ظاهر تدریجی متن از نحوه ارتباط انسان تقلید می کند و یک تجربه گفتگوی طبیعی تر ایجاد می کند.
- پردازش اطلاعات مترقی : کاربران می توانند پردازش اطلاعات را به محض ورود به جای اینکه در یک بلوک بزرگ متن به یکباره غرق شوند ، شروع به پردازش اطلاعات کنند.
- فرصتی برای وقفه زودهنگام : در یک برنامه کامل ، کاربران به طور بالقوه می توانند LLM را قطع یا هدایت کنند در صورتی که ببینند در یک جهت غیرقانونی قرار دارد.
- تأیید بصری فعالیت : متن جریان بازخورد فوری را ارائه می دهد که سیستم در حال کار است و باعث عدم اطمینان می شود.
مزایای فنی
فراتر از پیشرفت های UX ، جریان مزایای فنی را ارائه می دهد:
- اجرای عملکرد اولیه : تماس های عملکردی به محض ظاهر شدن در جریان ، بدون انتظار برای پاسخ کامل ، قابل شناسایی و اجرا هستند.
- به روزرسانی های افزایشی UI : با ورود اطلاعات جدید می توانید UI خود را به تدریج به روز کنید و یک تجربه پویاتر را ایجاد کنید.
- مدیریت وضعیت مکالمه : جریان سیگنال های روشنی در مورد زمان تکمیل پاسخ ها در مقابل هنوز در حال انجام است ، و امکان مدیریت بهتر دولت را فراهم می کند.
- کاهش خطرات زمان بندی : با پاسخ های غیر پخش ، نسل های طولانی مدت زمان اتصال به خطر. جریان اتصال را زود برقرار می کند و آن را حفظ می کند.
برای برنامه 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()
به جایFuture
با پاسخ کامل ، یک جریان از بخش های پاسخ را برمی گرداند - هر تکه ممکن است حاوی متن ، تماس عملکرد یا هر دو باشد
-
- پردازش مترقی :
-
await for
فرآیندها هر قطعه در زمان واقعی شدن است - متن بلافاصله به UI اضافه می شود و اثر جریان را ایجاد می کند
- تماس های عملکردی به محض شناسایی اجرا می شوند
-
- عملکرد تماس با عملکرد :
- هنگامی که یک تماس عملکردی در یک تکه تشخیص داده می شود ، بلافاصله اجرا می شود
- نتایج از طریق یک تماس جریان دیگر به 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
) از این حالت برای غیرفعال کردن ورودی متن در حالی که پاسخ در حال پردازش است ، استفاده می کند.
این یک تجربه کاربر منسجم را ایجاد می کند که در آن UI وضعیت فعلی مکالمه را منعکس می کند.
کد Riverpod را تولید کنید
دستور ساخت Runner را برای تولید کد مورد نیاز RiverPod اجرا کنید:
dart run build_runner build --delete-conflicting-outputs
پاسخ های جریان را اجرا و آزمایش کنید
برنامه خود را اجرا کنید:
flutter run -d DEVICE
اکنون سعی کنید رفتار جریان را با توضیحات مختلف رنگ آزمایش کنید. توضیحات مانند:
- "رنگ چاشنی عمیق اقیانوس را در گرگ و میش به من نشان دهید"
- "من می خواهم یک مرجان پر جنب و جوش را ببینم که مرا به یاد گلهای گرمسیری می اندازد"
- "یک سبز زیتون خاموش مانند خستگی های ارتش قدیمی ایجاد کنید"
جریان فنی جریان با جزئیات
بیایید دقیقاً بررسی کنیم که هنگام پخش پاسخ چه اتفاقی می افتد:
ایجاد ارتباط
هنگامی که با sendMessageStream()
تماس می گیرید ، موارد زیر اتفاق می افتد:
- برنامه ارتباطی به سرویس Vertex AI برقرار می کند
- درخواست کاربر به سرویس ارسال می شود
- سرور شروع به پردازش درخواست می کند
- اتصال جریان باز است ، آماده انتقال تکه ها
گیربکس
همانطور که جمینی محتوا تولید می کند ، تکه ها از طریق جریان ارسال می شوند:
- سرور تکه های متن را به عنوان تولید می کند (به طور معمول چند کلمه یا جملات)
- هنگامی که جمینی تصمیم به برقراری تماس عملکردی گرفت ، اطلاعات تماس عملکرد را ارسال می کند
- تکه های متن اضافی ممکن است تماس های عملکردی را دنبال کنند
- جریان تا زمان کامل شدن نسل ادامه می یابد
پردازش پیشرونده
برنامه شما هر قطعه را به صورت تدریجی پردازش می کند:
- هر قطعه متن به پاسخ موجود اضافه می شود
- تماس های عملکردی به محض شناسایی اجرا می شوند
- UI در زمان واقعی با نتایج متن و عملکرد به روز می شود
- حالت ردیابی می شود تا نشان دهد که پاسخ هنوز جریان دارد
اتمام جریان
وقتی نسل کامل شد:
- جریان توسط سرور بسته شده است
-
await for
خروج حلقه به طور طبیعی است - پیام به صورت کامل مشخص شده است
- حالت مکالمه به حالت بیکار باز می گردد
- به روزرسانی های UI برای بازتاب وضعیت تکمیل شده
جریان در مقابل مقایسه غیر جریان
برای درک بهتر فواید جریان ، بیایید با رویکردهای غیر پخش کننده جریان را مقایسه کنیم:
جنبه | غیر زنده | پخش جریانی |
تأخیر درک شده | کاربر تا زمان آماده شدن پاسخ کامل چیزی نمی بیند | کاربر اولین کلمات را در میلی ثانیه می بیند |
تجربه کاربری | انتظار طولانی به دنبال ظاهر ناگهانی متن | ظاهر متن طبیعی و مترقی |
مدیریت دولتی | ساده تر (پیام ها در انتظار یا کامل هستند) | پیچیده تر (پیام ها می توانند در حالت جریان باشند) |
اجرای عملکرد | فقط پس از پاسخ کامل رخ می دهد | در طول تولید پاسخ رخ می دهد |
پیچیدگی اجرا | ساده تر برای اجرای | به مدیریت اضافی دولت نیاز دارد |
بازیابی خطا | پاسخ همه یا هیچ چیز | پاسخ های جزئی هنوز هم ممکن است مفید باشد |
پیچیدگی کد | پیچیدگی کمتر | پیچیده تر به دلیل استفاده از جریان |
برای برنامه ای مانند Colorist ، مزایای UX از جریان از پیچیدگی اجرای ، به ویژه برای تفسیرهای رنگی که ممکن است چندین ثانیه طول بکشد ، بیشتر است.
بهترین روشها برای پخش UX
هنگام اجرای جریان در برنامه های LLM خود ، این بهترین روش ها را در نظر بگیرید:
- شاخص های بصری پاک : همیشه نشانه های بصری شفاف ارائه دهید که جریان را در مقابل پیام های کامل متمایز می کند
- مسدود کردن ورودی : ورودی کاربر را در حین پخش غیرفعال کنید تا از چندین درخواست همپوشانی جلوگیری کنید
- بازیابی خطا : در صورت قطع جریان ، UI خود را برای کنترل بازیابی برازنده طراحی کنید
- انتقال حالت : از انتقال صاف بین حالت های بیکار ، جریان و کامل اطمینان حاصل کنید
- تجسم پیشرفت : انیمیشن ها یا شاخص های ظریف را نشان می دهد که پردازش فعال را نشان می دهد
- گزینه های لغو : در یک برنامه کامل ، راه هایی را برای کاربران فراهم کنید تا نسل های در حال پیشرفت را لغو کنند
- یکپارچه سازی نتیجه عملکرد : UI خود را برای کنترل نتایج عملکردی که در اواسط جریان ظاهر می شود طراحی کنید
- بهینه سازی عملکرد : بازسازی های UI را در حین به روزرسانی سریع جریان به حداقل برسانید
بسته colorist_ui
بسیاری از این بهترین شیوه ها را برای شما پیاده سازی می کند ، اما آنها برای هرگونه اجرای LLM ، ملاحظات مهمی هستند.
بعدش چی؟
در مرحله بعدی ، همگام سازی LLM را با اطلاع رسانی به جمینی هنگامی که کاربران رنگ های تاریخ را انتخاب می کنند ، پیاده سازی می کنید. این یک تجربه منسجم تر ایجاد می کند که LLM از تغییرات ناشی از کاربر در حالت برنامه آگاه است.
عیب یابی
مسائل مربوط به پردازش جریان
اگر با پردازش جریان با مشکل روبرو شدید:
- علائم : پاسخ های جزئی ، متن از دست رفته یا خاتمه جریان ناگهانی
- راه حل : اتصال به شبکه را بررسی کنید و از الگوهای مناسب Async/Atait در کد خود اطمینان حاصل کنید
- تشخیص : پانل ورود به سیستم را برای پیام های خطا یا هشدارهای مربوط به پردازش جریان بررسی کنید
- رفع : اطمینان حاصل کنید که تمام پردازش جریان از استفاده از خطای مناسب با بلوک های
try
/catch
استفاده می کند
تماس های عملکردی از دست رفته
اگر تماس های عملکردی در جریان شناسایی نمی شوند:
- علائم : متن ظاهر می شود اما رنگ ها به روز نمی شوند ، یا ورود به سیستم هیچ تماسی را نشان نمی دهد
- راه حل : دستورالعمل های سریع سیستم در مورد استفاده از تماس های عملکرد را تأیید کنید
- تشخیص : پانل ورود را بررسی کنید تا ببینید تماس های عملکردی دریافت می شود
- رفع : سریع سیستم خود را تنظیم کنید تا صریح تر به LLM دستور دهد تا از ابزار
set_color
استفاده کند
رسیدگی به خطای عمومی
برای هر موضوع دیگر:
- مرحله 1 : پانل ورود به سیستم را برای پیام های خطا بررسی کنید
- مرحله 2 : Vertex AI را در اتصال Firebase تأیید کنید
- مرحله 3 : اطمینان حاصل کنید که تمام کد تولید شده Riverpod به روز است
- مرحله 4 : اجرای جریان را برای هرگونه اظهارات در انتظار مفقودالاتی مرور کنید
مفاهیم کلیدی آموخته شده
- اجرای پاسخ های جریان با API Gemini برای پاسخگوتر UX
- مدیریت وضعیت مکالمه برای رسیدگی به درستی جریان
- پردازش متن و عملکرد در زمان واقعی هنگام ورود
- ایجاد UI های پاسخگو که به طور تدریجی در حین پخش به روز می شود
- دست زدن به جریانهای همزمان با الگوهای مناسب async
- ارائه بازخورد بصری مناسب در هنگام پاسخ های جریان
با اجرای جریان ، شما تجربه کاربر برنامه Colorist خود را به طور قابل توجهی ارتقا داده اید و یک رابط کاربری با پاسخگوتر و جذاب تر ایجاد می کنید که واقعاً مکالمه است.
8. هماهنگ سازی زمینه LLM
در این مرحله پاداش ، همگام سازی زمینه LLM را با اطلاع رسانی جمینی هنگام انتخاب کاربران از تاریخ ، پیاده سازی خواهید کرد. این یک تجربه منسجم تر را ایجاد می کند که LLM از اقدامات کاربر در رابط آگاه باشد ، نه فقط پیام های صریح آنها.
آنچه را در این مرحله پوشش خواهید داد
- ایجاد هماهنگ سازی زمینه LLM بین UI و LLM
- سریال سازی رویدادهای UI در متن LLM می تواند درک کند
- به روزرسانی زمینه مکالمه بر اساس اقدامات کاربر
- ایجاد یک تجربه منسجم در روش های مختلف تعامل
- افزایش آگاهی زمینه LLM فراتر از پیام های چت صریح
درک هماهنگ سازی زمینه LLM
Chatbots سنتی فقط به پیام های صریح کاربر پاسخ می دهد و هنگام تعامل کاربران با برنامه دیگر با برنامه ارتباط برقرار می کند. هماهنگ سازی زمینه LLM به این محدودیت می پردازد:
چرا همگام سازی زمینه LLM اهمیت دارد
هنگامی که کاربران از طریق عناصر UI با برنامه شما ارتباط برقرار می کنند (مانند انتخاب رنگ از تاریخ) ، LLM هیچ راهی برای دانستن آنچه اتفاق افتاده است ، مگر اینکه صریحاً آن را بگویید. هماهنگ سازی زمینه LLM:
- زمینه را حفظ می کند : LLM را در مورد کلیه اقدامات کاربر مربوطه آگاه نگه می دارد
- انسجام ایجاد می کند : یک تجربه منسجم ایجاد می کند که LLM تعامل UI را تصدیق می کند
- افزایش هوش : به LLM اجازه می دهد تا به همه اقدامات کاربر پاسخ مناسب دهد
- تجربه کاربر را بهبود می بخشد : باعث می شود کل برنامه یکپارچه تر و پاسخگوتر شود
- تلاش کاربر را کاهش می دهد : نیاز کاربران را برای توضیح دستی اقدامات UI خود از بین می برد
در برنامه Colorist شما ، هنگامی که کاربر رنگی را از تاریخ انتخاب می کند ، شما می خواهید جمینی این عمل را تصدیق کند و به طور هوشمندانه درباره رنگ انتخاب شده اظهار نظر کند و توهم یک دستیار یکپارچه و آگاه را حفظ کند.
سرویس چت جمینی را برای اعلان های انتخاب رنگ به روز کنید
ابتدا روشی را به 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),
),
);
}
}
تغییر کلیدی اضافه کردن پاسخ به تماس notifyColorSelection
است که رویداد UI (انتخاب رنگ از تاریخ) را به سیستم اعلان LLM متصل می کند.
سریع سیستم را به روز کنید
حال ، شما باید سریع سیستم خود را به روز کنید تا LLM را در مورد نحوه پاسخگویی به اعلان های انتخاب رنگ آموزش دهید. assets/system_prompt.md
خود را اصلاح کنید:
دارایی/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 را تولید کنید
دستور ساخت Runner را برای تولید کد مورد نیاز RiverPod اجرا کنید:
dart run build_runner build --delete-conflicting-outputs
همگام سازی زمینه LLM را اجرا و آزمایش کنید
برنامه خود را اجرا کنید:
flutter run -d DEVICE
آزمایش همگام سازی زمینه LLM شامل موارد زیر است:
- ابتدا با توصیف آنها در گپ چند رنگ ایجاد کنید
- "یک بنفش پر جنب و جوش به من نشان دهید"
- "من یک جنگل سبز می خواهم"
- "به من یک قرمز روشن بده"
- سپس ، روی یکی از تصاویر کوچک رنگ در نوار تاریخ کلیک کنید
باید رعایت کنید:
- رنگ انتخاب شده در صفحه اصلی ظاهر می شود
- یک پیام کاربر در چت ظاهر می شود که نشان دهنده انتخاب رنگ است
- LLM با تأیید انتخاب و اظهار نظر در مورد رنگ پاسخ می دهد
- کل تعامل طبیعی و منسجم است
این یک تجربه یکپارچه را ایجاد می کند که LLM از آن آگاه است و به طور مناسب به پیام های مستقیم و تعامل UI پاسخ می دهد.
چگونه همگام سازی زمینه LLM کار می کند
بیایید جزئیات فنی نحوه عملکرد این هماهنگ سازی را بررسی کنیم:
جریان داده
- اقدام کاربر : کاربر روی یک رنگ در نوار تاریخ کلیک می کند
- رویداد UI : ویجت
MainScreen
این انتخاب را تشخیص می دهد - اعدام پاسخ به تماس : پاسخ به تماس
notifyColorSelection
شروع می شود - ایجاد پیام : یک پیام مخصوص فرمت شده با داده های رنگ ایجاد می شود
- پردازش LLM : پیام به Gemini ارسال می شود ، که قالب را تشخیص می دهد
- پاسخ متنی : جمینی بر اساس سریع سیستم پاسخ مناسب می دهد
- UI Update : پاسخ در گپ ظاهر می شود و یک تجربه منسجم ایجاد می کند
داده ها
جنبه اصلی این رویکرد این است که چگونه داده های رنگ را سریال می کنید:
'User selected color from history: ${json.encode(color.toLLMContextMap())}'
روش toLLMContextMap()
(ارائه شده توسط بسته colorist_ui
) یک شیء ColorData
را با خصوصیات کلیدی که LLM می تواند درک کند ، به یک نقشه تبدیل می کند. این به طور معمول شامل:
- مقادیر RGB (قرمز ، سبز ، آبی)
- نمایندگی کد هگز
- هر نام یا توضیحی در ارتباط با رنگ
با قالب بندی این داده ها به طور مداوم و از جمله آن در پیام ، اطمینان حاصل می کنید که LLM تمام اطلاعات مورد نیاز برای پاسخ مناسب را دارد.
برنامه های گسترده تر از همگام سازی زمینه LLM
این الگوی اطلاع رسانی LLM در مورد رویدادهای UI دارای برنامه های بی شماری فراتر از انتخاب رنگ است:
موارد استفاده دیگر
- Filter Changes : هنگامی که کاربران فیلترها را به داده ها اعمال می کنند ، به LLM اطلاع دهید
- رویدادهای ناوبری : هنگامی که کاربران به بخش های مختلف حرکت می کنند ، LLM را به LLM اطلاع دهید
- تغییرات انتخاب : هنگامی که کاربران مواردی را از لیست یا شبکه انتخاب می کنند ، LLM را به روز کنید
- به روزرسانی های اولویت : وقتی کاربران تنظیمات یا تنظیمات برگزیده را تغییر می دهند به LLM بگویید
- دستکاری داده ها : هنگام اضافه کردن ، ویرایش یا حذف داده ها ، LLM را به LLM اطلاع دهید
در هر حالت ، الگوی یکسان است:
- رویداد UI را تشخیص دهید
- داده های مربوطه را سریال کنید
- اعلان مخصوص فرمت شده را به LLM ارسال کنید
- LLM را راهنمایی کنید تا از طریق سریع سیستم پاسخ مناسب دهید
بهترین شیوه ها برای هماهنگ سازی زمینه LLM
بر اساس اجرای شما ، در اینجا بهترین روشها برای همگام سازی زمینه LLM مؤثر آورده شده است:
1. قالب بندی مداوم
برای اعلان ها از یک قالب ثابت استفاده کنید تا LLM بتواند آنها را به راحتی شناسایی کند:
"User [action] [object]: [structured data]"
2. زمینه غنی
جزئیات کافی را در اعلان ها برای LLM برای پاسخ هوشمندانه درج کنید. برای رنگ ها ، این به معنای مقادیر RGB ، کدهای هگز و سایر خصوصیات مربوطه است.
3. دستورالعمل ها را روشن کنید
دستورالعمل های صریح را در سیستم سریع در مورد نحوه رسیدگی به اعلان ها ، ایده آل با مثال ارائه دهید.
4. ادغام طبیعی
اعلان های طراحی برای جریان طبیعی در مکالمه ، نه به عنوان وقفه های فنی.
5. اعلان انتخابی
فقط LLM را در مورد اقداماتی که مربوط به مکالمه است ، به LLM اطلاع دهید. هر رویداد UI نیازی به ابلاغ نیست.
عیب یابی
مسائل مربوط به اطلاع رسانی
اگر LLM به درستی به انتخاب رنگ پاسخ نمی دهد:
- بررسی کنید که فرمت پیام اعلان با آنچه در سیستم توضیح داده شده است مطابقت دارد
- تأیید کنید که داده های رنگ به درستی سریال می شوند
- اطمینان حاصل کنید که سریع سیستم دستورالعمل های روشنی برای انتخاب انتخاب دارد
- هنگام ارسال اعلان ها به دنبال هرگونه خطایی در سرویس چت باشید
مدیریت زمینه
اگر به نظر می رسد LLM زمینه را از دست می دهد:
- بررسی کنید که جلسه چت به درستی نگهداری می شود
- تأیید کنید که مکالمه به درستی انتقال می دهد
- اطمینان حاصل کنید که اعلان ها از طریق همان جلسه گپ ارسال می شوند
مشکلات عمومی
برای مسائل عمومی:
- سیاهههای مربوط به خطاها یا هشدارها را بررسی کنید
- Vertex AI را در اتصال Firebase تأیید کنید
- هر نوع عدم تطابق را در پارامترهای عملکرد بررسی کنید
- اطمینان حاصل کنید که تمام کد تولید شده Riverpod به روز است
مفاهیم کلیدی آموخته شده
- ایجاد هماهنگ سازی زمینه LLM بین UI و LLM
- سریال سازی رویدادهای UI در زمینه دوستانه LLM
- هدایت رفتار LLM برای الگوهای مختلف تعامل
- ایجاد یک تجربه منسجم در تعامل پیام و غیر پیام
- افزایش آگاهی LLM از وضعیت برنامه گسترده تر
با اجرای همگام سازی زمینه LLM ، شما یک تجربه واقعاً یکپارچه ایجاد کرده اید که LLM به جای یک ژنراتور متن ، مانند یک دستیار آگاه و پاسخگو احساس می کند. این الگوی برای ایجاد رابط های طبیعی و شهودی تر هوش مصنوعی می تواند در برنامه های بی شماری دیگر اعمال شود.
9. تبریک می گویم!
شما با موفقیت CodeLab Colorist را تکمیل کرده اید! 🎉
آنچه شما ساخته اید
شما یک برنامه کاملاً کاربردی Flutter ایجاد کرده اید که API Gemini Google را برای تفسیر توضیحات رنگ زبان طبیعی ادغام می کند. برنامه شما اکنون می تواند:
- توضیحات زبان طبیعی مانند "غروب نارنجی" یا "آبی عمیق اقیانوس" را پردازش کنید
- برای ترجمه هوشمندانه این توضیحات به مقادیر RGB از جمینی استفاده کنید
- رنگهای تفسیر شده را در زمان واقعی با پاسخ های جریان نشان دهید
- تعامل کاربر را از طریق عناصر چت و UI انجام دهید
- آگاهی متنی را در روشهای مختلف تعامل حفظ کنید
از اینجا به کجا برویم
اکنون که به اصول اولیه ادغام جمینی با فلاتر تسلط داده اید ، در اینجا چند راه برای ادامه سفر خود وجود دارد:
برنامه Colorist خود را تقویت کنید
- پالت های رنگی : برای تولید طرح های رنگی مکمل یا تطبیق عملکردی اضافه کنید
- ورودی صدا : تشخیص گفتار را برای توضیحات رنگ کلامی ادغام کنید
- مدیریت تاریخ : گزینه های نام ، سازماندهی و صادرات را اضافه کنید
- درخواست سفارشی : ایجاد رابط برای کاربران برای سفارشی کردن درخواست های سیستم
- تجزیه و تحلیل پیشرفته : پیگیری کدام توضیحات به بهترین وجه کار می کنند یا باعث ایجاد مشکل می شوند
ویژگی های بیشتر جمینی را کاوش کنید
- ورودی های چند حالته : برای استخراج رنگ ها از عکس ها ، ورودی های تصویر را اضافه کنید
- تولید محتوا : از جمینی برای تولید محتوای مرتبط با رنگ مانند توضیحات یا داستانها استفاده کنید
- پیشرفت های فراخوانی عملکرد : ادغام ابزار پیچیده تری با چندین توابع ایجاد کنید
- تنظیمات ایمنی : تنظیمات مختلف ایمنی و تأثیر آنها بر پاسخ ها را کشف کنید
این الگوهای را در حوزه های دیگر اعمال کنید
- تجزیه و تحلیل اسناد : برنامه هایی ایجاد کنید که بتوانند اسناد را درک و تجزیه و تحلیل کنند
- کمک به نوشتن خلاق : ایجاد ابزار نوشتن با پیشنهادات دارای LLM
- اتوماسیون کار : برنامه هایی را طراحی کنید که زبان طبیعی را به کارهای خودکار ترجمه می کنند
- برنامه های مبتنی بر دانش : ایجاد سیستم های متخصص در حوزه های خاص
منابع
در اینجا برخی از منابع ارزشمند برای ادامه یادگیری شما آورده شده است:
اسناد رسمی
دوره و راهنما
جامعه
سری عامل های قابل مشاهده Flutter
در Expisode #59 ، کریگ لبانز و اندرو بروگدن این CodeLab را کشف کردند و قسمتهای جالب ساخت برنامه را برجسته کردند.
در قسمت شماره 60 ، دوباره به کریگ و اندرو بپیوندید زیرا آنها برنامه CodeLab را با قابلیت های جدید گسترش می دهند و با انجام LLM ها همانطور که گفته می شود ، می جنگند.
در قسمت شماره 61 ، کریگ به کریس فروش پیوسته است تا در تجزیه و تحلیل عناوین خبری تازه ای داشته باشد و تصاویر مربوطه را تولید کند.
بازخورد
ما دوست داریم در مورد تجربه شما با این CodeLab بشنویم! لطفا ارائه بازخورد از طریق:
با تشکر از شما برای تکمیل این CodeLab ، و امیدواریم که به بررسی امکانات هیجان انگیز در تقاطع Flutter و AI بپردازید!
درباره این codelab
1. یک برنامه Flutter Gemini را بسازید
چیزی که خواهی ساخت
In this codelab, you'll build Colorist - an interactive Flutter application that brings the power of Gemini API directly into your Flutter app. Ever wanted to let users control your app through natural language but didn't know where to start? This codelab shows you how.
Colorist allows users to describe colors in natural language (like "the orange of a sunset" or "deep ocean blue"), and the app:
- Processes these descriptions using Google's Gemini API
- Interprets the descriptions into precise RGB color values
- Displays the color on screen in real-time
- Provides technical color details and interesting context about the color
- Maintains a history of recently generated colors
The app features a split-screen interface with a color display area and an interactive chat system on one side, and a detailed log panel showing the raw LLM interactions on the other side. This log enables you to better understand how an LLM integration really works under the hood.
Why this matters for Flutter developers
LLMs are revolutionizing how users interact with applications, but integrating them effectively into mobile and desktop apps presents unique challenges. This codelab teaches you practical patterns that go beyond just the raw API calls.
سفر یادگیری شما
This codelab takes you through the process of building Colorist step by step:
- Project setup - You'll start with a basic Flutter app structure and the
colorist_ui
package - Basic Gemini integration - Connect your app to Vertex AI in Firebase and implement simple LLM communication
- Effective prompting - Create a system prompt that guides the LLM to understand color descriptions
- Function declarations - Define tools that the LLM can use to set colors in your application
- Tool handling - Process function calls from the LLM and connect them to your app's state
- Streaming responses - Enhance the user experience with real-time streaming LLM responses
- LLM Context Synchronization - Create a cohesive experience by informing the LLM of user actions
چیزی که یاد خواهید گرفت
- Configure Vertex AI in Firebase for Flutter applications
- Craft effective system prompts to guide LLM behavior
- Implement function declarations that bridge natural language and app features
- Process streaming responses for a responsive user experience
- Synchronize state between UI events and the LLM
- Manage LLM conversation state using Riverpod
- Handle errors gracefully in LLM-powered applications
Code preview: A taste of what you'll implement
Here's a glimpse of the function declaration you'll create to let the LLM set colors in your app:
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)'),
},
);
A video overview of this codelab
Watch Craig Labenz and Andrew Brogdon discuss this codelab in Observable Flutter expisode #59:
پیش نیازها
To get the most out of this codelab, you should have:
- Flutter development experience - Familiarity with Flutter basics and Dart syntax
- Asynchronous programming knowledge - Understanding of Futures, async/await, and streams
- Firebase account - You'll need a Google Account to set up Firebase
- Firebase project with billing enabled - Vertex AI in Firebase requires a billing account
Let's get started building your first LLM-powered Flutter app!
2. Project setup & echo service
In this first step, you'll set up the project structure and implement a simple echo service that will later be replaced with the Gemini API integration. This establishes the application architecture and ensures your UI is working correctly before adding the complexity of LLM calls.
What you'll learn in this step
- Setting up a Flutter project with the required dependencies
- Working with the
colorist_ui
package for UI components - Implementing an echo message service and connecting it to the UI
An important note on pricing
Create a new Flutter project
Start by creating a new Flutter project with the following command:
flutter create -e colorist --platforms=android,ios,macos,web,windows
The -e
flag indicates that you want an empty project without the default counter
app. The app is designed to work across desktop, mobile, and web. However, flutterfire
does not support Linux at this time.
وابستگی ها را اضافه کنید
Navigate to your project directory and add the required dependencies:
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
This will add the following key packages:
-
colorist_ui
: A custom package that provides the UI components for the Colorist app -
flutter_riverpod
andriverpod_annotation
: For state management -
logging
: For structured logging - Development dependencies for code generation and linting
Your pubspec.yaml
will look similar to this:
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
Configure analysis options
Add custom_lint
to your analysis_options.yaml
file at the root of your project:
include: package:flutter_lints/flutter.yaml
analyzer:
plugins:
- custom_lint
This configuration enables Riverpod-specific lints to help maintain code quality.
Implement the main.dart
file
Replace the content of lib/main.dart
with the following:
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);
}
}
This sets up a Flutter app implements a simple echo service that mimics the behavior of an LLM by simply returning the user's message.
Understanding the architecture
Let's take a minute to understand the architecture of the colorist
app:
The colorist_ui
package
The colorist_ui
package provides pre-built UI components and state management tools:
- MainScreen : The main UI component that displays:
- A split-screen layout on desktop (interaction area and log panel)
- A tabbed interface on mobile
- Color display, chat interface, and history thumbnails
- State Management : The app uses several state notifiers:
- ChatStateNotifier : Manages the chat messages
- ColorStateNotifier : Manages the current color and history
- LogStateNotifier : Manages the log entries for debugging
- Message Handling : The app uses a message model with different states:
- User messages : Entered by the user
- LLM messages : Generated by the LLM (or your echo service for now)
- MessageState : Tracks whether LLM messages are complete or still streaming in
معماری اپلیکیشن
The app follows the following architecture:
- UI Layer : Provided by the
colorist_ui
package - State Management : Uses Riverpod for reactive state management
- Service Layer : Currently contains your simple echo service, this will be replaced with Gemini Chat Service
- LLM Integration : Will be added in later steps
This separation allows you to focus on implementing the LLM integration while the UI components are already taken care of.
برنامه را اجرا کنید
Run the app with the following command:
flutter run -d DEVICE
Replace DEVICE
with your target device, such as macos
, windows
, chrome
, or a device ID.
You should now see the Colorist app with:
- A color display area with a default color
- A chat interface where you can type messages
- A log panel showing the chat interactions
Try typing a message like "I'd like a deep blue color" and press Send. The echo service will simply repeat your message. In later steps, you'll replace this with actual color interpretation using the Gemini API via Vertex AI in Firebase.
بعدش چی؟
In the next step, you'll configure Firebase and implement basic Gemini API integration to replace your echo service with the Gemini chat service. This will allow the app to interpret color descriptions and provide intelligent responses.
عیب یابی
UI package issues
If you encounter issues with the colorist_ui
package:
- Make sure you're using the latest version
- Verify that you've added the dependency correctly
- Check for any conflicting package versions
Build errors
If you see build errors:
- Make sure you have the latest stable channel Flutter SDK installed
- Run
flutter clean
followed byflutter pub get
- Check the console output for specific error messages
Key concepts learned
- Setting up a Flutter project with the necessary dependencies
- Understanding the application's architecture and component responsibilities
- Implementing a simple service that mimics the behavior of an LLM
- Connecting the service to the UI components
- Using Riverpod for state management
3. Basic Gemini chat integration
In this step, you'll replace the echo service from the previous step with Gemini API integration using Vertex AI in Firebase. You'll configure Firebase, set up the necessary providers, and implement a basic chat service that communicates with the Gemini API.
What you'll learn in this step
- Setting up Firebase in a Flutter application
- Configuring Vertex AI in Firebase for Gemini access
- Creating Riverpod providers for Firebase and Gemini services
- Implementing a basic chat service with the Gemini API
- Handling asynchronous API responses and error states
Set up Firebase
First, you need to set up Firebase for your Flutter project. This involves creating a Firebase project, adding your app to it, and configuring the necessary Vertex AI settings.
Create a Firebase project
- Go to the Firebase Console and sign in with your Google Account.
- Click Create a Firebase project or select an existing project.
- Follow the setup wizard to create your project.
- Once your project is created, you'll need to upgrade to the Blaze plan (pay-as-you-go) to access Vertex AI services. Click the Upgrade button in the lower left of the Firebase console.
Set up Vertex AI in your Firebase project
- In the Firebase console, navigate to your project.
- In the left sidebar, select AI .
- In the Vertex AI in Firebase card, select Get Started .
- Follow the prompts to enable the Vertex AI in Firebase APIs for your project.
Install the FlutterFire CLI
The FlutterFire CLI simplifies Firebase setup in Flutter apps:
dart pub global activate flutterfire_cli
Add Firebase to your Flutter app
- Add the Firebase core and Vertex AI packages to your project:
flutter pub add firebase_core firebase_vertexai
- Run the FlutterFire configuration command:
flutterfire configure
این دستور:
- Ask you to select the Firebase project you just created
- Register your Flutter app(s) with Firebase
- Generate a
firebase_options.dart
file with your project configuration
The command will automatically detect your selected platforms (iOS, Android, macOS, Windows, web) and configure them appropriately.
Platform-specific configuration
Firebase requires minimum versions higher than are default for Flutter. It also requires network access, to talk to the Vertex AI in Firebase servers.
Configure macOS permissions
For macOS, you need to enable network access in your app's entitlements:
- Open
macos/Runner/DebugProfile.entitlements
and add:
macos/Runner/DebugProfile.entitlements
<key>com.apple.security.network.client</key>
<true/>
- Also open
macos/Runner/Release.entitlements
and add the same entry. - Update the minimum macOS version at the top of
macos/Podfile
:
macos/Podfile
# Firebase requires at least macOS 10.15
platform :osx, '10.15'
Configure iOS permissions
For iOS, update the minimum version at the top of ios/Podfile
:
ios/Podfile
# Firebase requires at least iOS 13.0
platform :ios, '13.0'
Configure Android settings
For Android, update android/app/build.gradle.kts
:
android/app/build.gradle.kts
android {
// ...
ndkVersion = "27.0.12077973"
defaultConfig {
// ...
minSdk = 23
// ...
}
}
Create Gemini model providers
Now you'll create the Riverpod providers for Firebase and Gemini. Create a new file 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();
}
This file defines the basis for three key providers. These providers are generated when you run dart run build_runner
by the Riverpod code generators.
-
firebaseAppProvider
: Initializes Firebase with your project configuration -
geminiModelProvider
: Creates a Gemini generative model instance -
chatSessionProvider
: Creates and maintains a chat session with the Gemini model
The keepAlive: true
annotation on the chat session ensures it persists throughout the app's lifecycle, maintaining conversation context.
Implement the Gemini chat service
Create a new file lib/services/gemini_chat_service.dart
to implement the chat service:
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);
این سرویس:
- Accepts user messages and sends them to the Gemini API
- Updates the chat interface with responses from the model
- Logs all communications for ease of understanding the real LLM flow
- Handles errors with appropriate user feedback
Note: The Log window will look almost identical to the chat window at this point. The log will become more interesting once you introduce function calls and then streaming responses.
Generate Riverpod code
Run the build runner command to generate the necessary Riverpod code:
dart run build_runner build --delete-conflicting-outputs
This will create the .g.dart
files that Riverpod needs to function.
Update the main.dart file
Update your lib/main.dart
file to use the new Gemini chat service:
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),
),
);
}
}
The key changes in this update are:
- Replacing the echo service with the Gemini API based chat service
- Adding loading and error screens using Riverpod's
AsyncValue
pattern with thewhen
method - Connecting the UI to your new chat service through the
sendMessage
callback
برنامه را اجرا کنید
Run the app with the following command:
flutter run -d DEVICE
Replace DEVICE
with your target device, such as macos
, windows
, chrome
, or a device ID.
Now when you type a message, it will be sent to the Gemini API, and you'll receive a response from the LLM rather than an echo. The log panel will show the interactions with the API.
Understanding LLM communication
Let's take a moment to understand what's happening when you communicate with the Gemini API:
The communication flow
- User input : The user enters text in the chat interface
- Request Formatting : The app formats the text as a
Content
object for the Gemini API - API Communication : The text is sent to the Gemini API via Vertex AI in Firebase
- LLM Processing : The Gemini model processes the text and generates a response
- Response Handling : The app receives the response and updates the UI
- Logging : All communication is logged for transparency
Chat sessions and conversation context
The Gemini chat session maintains context between messages, allowing for conversational interactions. This means the LLM "remembers" previous exchanges in the current session, enabling more coherent conversations.
The keepAlive: true
annotation on your chat session provider ensures this context persists throughout the app's lifecycle. This persistent context is crucial for maintaining a natural conversation flow with the LLM.
بعدش چی؟
At this point, you can ask Gemini API anything, as there are no restrictions in place on what it will respond to. For instance, you could ask it for a summary of the Wars of the Roses, which isn't related to your color app's purpose.
In the next step, you'll create a system prompt to guide Gemini in interpreting color descriptions more effectively. This will demonstrate how to customize an LLM's behavior for application-specific needs and focus its capabilities on your app's domain.
عیب یابی
Firebase configuration issues
If you encounter errors with Firebase initialization:
- Ensure your
firebase_options.dart
file was correctly generated - Verify that you've upgraded to the Blaze plan for Vertex AI access
API access errors
If you receive errors accessing the Gemini API:
- Confirm that billing is properly set up on your Firebase project
- Check that Vertex AI and the Cloud AI API are enabled in your Firebase project
- Check your network connectivity and firewall settings
- Verify that the model name (
gemini-2.0-flash
) is correct and available
Conversation context issues
If you notice that Gemini doesn't remember previous context from the chat:
- Confirm that the
chatSession
function is annotated with@Riverpod(keepAlive: true)
- Check that you're reusing the same chat session for all message exchanges
- Verify that the chat session is properly initialized before sending messages
Platform-specific issues
For platform-specific issues:
- iOS/macOS: Ensure the proper entitlements are set and minimum versions are configured
- Android: Verify the minimum SDK version is set correctly
- Check platform-specific error messages in the console
Key concepts learned
- Setting up Firebase in a Flutter application
- Configuring Vertex AI in Firebase for access to Gemini
- Creating Riverpod providers for asynchronous services
- Implementing a chat service that communicates with an LLM
- Handling asynchronous API states (loading, error, data)
- Understanding LLM communication flow and chat sessions
4. Effective prompting for color descriptions
In this step, you'll create and implement a system prompt that guides Gemini in interpreting color descriptions. System prompts are a powerful way to customize LLM behavior for specific tasks without changing your code.
What you'll learn in this step
- Understanding system prompts and their importance in LLM applications
- Crafting effective prompts for domain-specific tasks
- Loading and using system prompts in a Flutter app
- Guiding an LLM to provide consistently formatted responses
- Testing how system prompts affect LLM behavior
Understanding system prompts
Before diving into implementation, let's understand what system prompts are and why they're important:
What are system prompts?
A system prompt is a special type of instruction given to an LLM that sets the context, behavior guidelines, and expectations for its responses. Unlike user messages, system prompts:
- Establish the LLM's role and persona
- Define specialized knowledge or capabilities
- Provide formatting instructions
- Set constraints on responses
- Describe how to handle various scenarios
Think of a system prompt as giving the LLM its "job description" - it tells the model how to behave throughout the conversation.
Why system prompts matter
System prompts are critical for creating consistent, useful LLM interactions because they:
- Ensure consistency : Guide the model to provide responses in a consistent format
- Improve relevance : Focus the model on your specific domain (in your case, colors)
- Establish boundaries : Define what the model should and shouldn't do
- Enhance user experience : Create a more natural, helpful interaction pattern
- Reduce post-processing : Get responses in formats that are easier to parse or display
For your Colorist app, you need the LLM to consistently interpret color descriptions and provide RGB values in a specific format.
Create a system prompt asset
First, you'll create a system prompt file that will be loaded at runtime. This approach allows you to modify the prompt without recompiling your app.
Create a new file assets/system_prompt.md
with the following content:
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
Understanding the system prompt structure
Let's break down what this prompt does:
- Definition of role : Establishes the LLM as a "color expert assistant"
- Task explanation : Defines the primary task as interpreting color descriptions into RGB values
- Response format : Specifies exactly how RGB values should be formatted for consistency
- Example exchange : Provides a concrete example of the expected interaction pattern
- Edge case handling : Instructs how to handle unclear descriptions
- Constraints and guidelines : Sets boundaries like keeping RGB values between 0.0 and 1.0
This structured approach ensures the LLM's responses will be consistent, informative, and formatted in a way that would be easy to parse if you wanted to extract the RGB values programmatically.
Update pubspec.yaml
Now, update the bottom of your pubspec.yaml
to include the assets directory:
pubspec.yaml
flutter:
uses-material-design: true
assets:
- assets/
Run flutter pub get
to refresh the asset bundle.
Create a system prompt provider
Create a new file lib/providers/system_prompt.dart
to load the system prompt:
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');
This provider uses Flutter's asset loading system to read the prompt file at runtime.
Update the Gemini model provider
Now modify your lib/providers/gemini.dart
file to include the system prompt:
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();
}
The key change is adding systemInstruction: Content.system(systemPrompt)
when creating the generative model. This tells Gemini to use your instructions as the system prompt for all interactions in this chat session.
Generate Riverpod code
Run the build runner command to generate the needed Riverpod code:
dart run build_runner build --delete-conflicting-outputs
Run and test the application
Now run your application:
flutter run -d DEVICE
Try testing it with various color descriptions:
- "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"
You should notice that Gemini now responds with conversational explanations about the colors along with consistently formatted RGB values. The system prompt has effectively guided the LLM to provide the type of responses you need.
Also try asking it for content outside the context of colors. Say, the leading causes of the Wars of the Roses. You should notice a difference from the previous step.
The importance of prompt engineering for specialized tasks
System prompts are both art and science. They're a critical part of LLM integration that can dramatically affect how useful the model is for your specific application. What you've done here is a form of prompt engineering - tailoring instructions to get the model to behave in ways that suit your application's needs.
Effective prompt engineering involves:
- Clear role definition : Establishing what the LLM's purpose is
- Explicit instructions : Detailing exactly how the LLM should respond
- Concrete examples : Showing rather than just telling what good responses look like
- Edge case handling : Instructing the LLM on how to deal with ambiguous scenarios
- Formatting specifications : Ensuring responses are structured in a consistent, usable way
The system prompt you've created transforms the generic capabilities of Gemini into a specialized color interpretation assistant that provides responses formatted specifically for your application's needs. This is a powerful pattern you can apply to many different domains and tasks.
بعدش چی؟
In the next step, you'll build on this foundation by adding function declarations, which allow the LLM to not just suggest RGB values, but actually call functions in your app to set the color directly. This demonstrates how LLMs can bridge the gap between natural language and concrete application features.
عیب یابی
Asset loading issues
If you encounter errors loading the system prompt:
- Verify that your
pubspec.yaml
correctly lists the assets directory - Check that the path in
rootBundle.loadString()
matches your file location - Run
flutter clean
followed byflutter pub get
to refresh the asset bundle
Inconsistent responses
If the LLM isn't consistently following your format instructions:
- Try making format requirements more explicit in the system prompt
- Add more examples to demonstrate the expected pattern
- Ensure the format you're requesting is reasonable for the model
محدود کردن نرخ API
If you encounter errors related to rate limiting:
- Be aware that the Vertex AI service has usage limits
- Consider implementing retry logic with exponential backoff
- Check your Firebase console for any quota issues
Key concepts learned
- Understanding the role and importance of system prompts in LLM applications
- Crafting effective prompts with clear instructions, examples, and constraints
- Loading and using system prompts in a Flutter application
- Guiding LLM behavior for domain-specific tasks
- Using prompt engineering to shape LLM responses
This step demonstrates how you can achieve significant customization of LLM behavior without changing your code - simply by providing clear instructions in the system prompt.
5. Function declarations for LLM tools
In this step, you'll start the work of enabling Gemini to take action in your app by implementing function declarations. This powerful feature allows the LLM to not just suggest RGB values but actually set them in your app's UI through specialized tool calls. However, it will require the next step to see the LLM requests executed in the Flutter app.
What you'll learn in this step
- Understanding LLM function calling and its benefits for Flutter applications
- Defining schema-based function declarations for Gemini
- Integrating function declarations with your Gemini model
- Updating the system prompt to utilize tool capabilities
Understanding function calling
Before implementing function declarations, let's understand what they are and why they're valuable:
فراخوانی تابع چیست؟
Function calling (sometimes called "tool use") is a capability that allows an LLM to:
- Recognize when a user request would benefit from invoking a specific function
- Generate a structured JSON object with the parameters needed for that function
- Let your application execute the function with those parameters
- Receive the function's result and incorporate it into its response
Rather than the LLM just describing what to do, function calling empowers the LLM to trigger concrete actions in your application.
Why function calling matters for Flutter apps
Function calling creates a powerful bridge between natural language and application features:
- Direct action : Users can describe what they want in natural language, and the app responds with concrete actions
- Structured output : The LLM produces clean, structured data rather than text that needs parsing
- Complex operations : Enables the LLM to access external data, perform calculations, or modify application state
- Better user experience : Creates seamless integration between conversation and functionality
In your Colorist app, function calling allows users to say "I want a forest green" and have the UI immediately update with that color, without having to parse RGB values from text.
Define function declarations
Create a new file lib/services/gemini_tools.dart
to define your function declarations:
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);
Understanding function declarations
Let's break down what this code does:
- Function naming : You name your function
set_color
to clearly indicate its purpose - Function description : You provide a clear description that helps the LLM understand when to use it
- Parameter definitions : You define structured parameters with their own descriptions:
-
red
: The red component of RGB, specified as a number between 0.0 and 1.0 -
green
: The green component of RGB, specified as a number between 0.0 and 1.0 -
blue
: The blue component of RGB, specified as a number between 0.0 and 1.0
-
- Schema types : You use
Schema.number()
to indicate these are numeric values - Tools collection : You create a list of tools containing your function declaration
This structured approach helps the Gemini LLM understand:
- When it should call this function
- What parameters it needs to provide
- What constraints apply to those parameters (like the value range)
Update the Gemini model provider
Now, modify your lib/providers/gemini.dart
file to include the function declarations when initializing the Gemini model:
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();
}
The key change is adding the tools: geminiTools.tools
parameter when creating the generative model. This makes Gemini aware of the functions that are available for it to call.
Update the system prompt
Now you need to modify your system prompt to instruct the LLM about using the new set_color
tool. 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
The key changes to the system prompt are:
- Tool introduction : Instead of asking for formatted RGB values, you now tell the LLM about the
set_color
tool - Modified process : You change step 3 from "format values in the response" to "use the tool to set values"
- Updated example : You show how the response should include a tool call instead of formatted text
- Removed formatting requirement : Since you're using structured function calls, you no longer need a specific text format
This updated prompt directs the LLM to use function calling rather than just providing RGB values in text form.
Generate Riverpod code
Run the build runner command to generate the needed Riverpod code:
dart run build_runner build --delete-conflicting-outputs
برنامه را اجرا کنید
At this point, Gemini will generate content that attempts to use function calling, but you haven't yet implemented handlers for the function calls. When you run the app and describe a color, you'll see Gemini responding as if it has invoked a tool, but you won't see any color changes in the UI until the next step.
برنامه خود را اجرا کنید:
flutter run -d DEVICE
Try describing a color like "deep ocean blue" or "forest green" and observe the responses. The LLM is attempting to call the functions defined above, but your code isn't detecting function calls yet.
The function calling process
Let's understand what happens when Gemini uses function calling:
- Function selection : The LLM decides if a function call would be helpful based on the user's request
- Parameter generation : The LLM generates parameter values that fit the function's schema
- Function call format : The LLM sends a structured function call object in its response
- Application handling : Your app would receive this call and execute the relevant function (implemented in the next step)
- Response integration : In multi-turn conversations, the LLM expects the function's result to be returned
In the current state of your app, the first three steps are occurring, but you haven't yet implemented step 4 or 5 (handling the function calls), which you'll do in the next step.
Technical details: How Gemini decides when to use functions
Gemini makes intelligent decisions about when to use functions based on:
- User intent : Whether the user's request would be best served by a function
- Function relevance : How well the available functions match the task
- Parameter availability : Whether it can confidently determine parameter values
- System instructions : Guidance from your system prompt about function usage
By providing clear function declarations and system instructions, you've set up Gemini to recognize color description requests as opportunities to call the set_color
function.
بعدش چی؟
In the next step, you'll implement handlers for the function calls coming from Gemini. This will complete the circle, allowing user descriptions to trigger actual color changes in the UI through the LLM's function calls.
عیب یابی
Function declaration issues
If you encounter errors with function declarations:
- Check that the parameter names and types match what's expected
- Verify that the function name is clear and descriptive
- Ensure the function description accurately explains its purpose
System prompt problems
If the LLM isn't attempting to use the function:
- Verify that your system prompt clearly instructs the LLM to use the
set_color
tool - Check that the example in the system prompt demonstrates function usage
- Try making the instruction to use the tool more explicit
مسائل کلی
If you encounter other problems:
- Check the console for any errors related to function declarations
- Verify that the tools are properly passed to the model
- Ensure all Riverpod generated code is up to date
Key concepts learned
- Defining function declarations to extend LLM capabilities in Flutter apps
- Creating parameter schemas for structured data collection
- Integrating function declarations with the Gemini model
- Updating system prompts to encourage function usage
- Understanding how LLMs select and call functions
This step demonstrates how LLMs can bridge the gap between natural language input and structured function calls, laying the groundwork for seamless integration between conversation and application features.
6. Implementing tool handling
In this step, you'll implement handlers for the function calls coming from Gemini. This completes the circle of communication between natural language inputs and concrete application features, allowing the LLM to directly manipulate your UI based on user descriptions.
What you'll learn in this step
- Understanding the complete function calling pipeline in LLM applications
- Processing function calls from Gemini in a Flutter application
- Implementing function handlers that modify application state
- Handling function responses and returning results to the LLM
- Creating a complete communication flow between LLM and UI
- Logging function calls and responses for transparency
Understanding the function calling pipeline
Before diving into implementation, let's understand the complete function calling pipeline:
The end-to-end flow
- User input : User describes a color in natural language (eg, "forest green")
- LLM processing : Gemini analyzes the description and decides to call the
set_color
function - Function call generation : Gemini creates a structured JSON with parameters (red, green, blue values)
- Function call reception : Your app receives this structured data from Gemini
- Function execution : Your app executes the function with the provided parameters
- State update : The function updates your app's state (changing the displayed color)
- Response generation : Your function returns results back to the LLM
- Response incorporation : The LLM incorporates these results into its final response
- UI update : Your UI reacts to the state change, displaying the new color
The complete communication cycle is essential for proper LLM integration. When an LLM makes a function call, it doesn't simply send the request and move on. Instead, it waits for your application to execute the function and return results. The LLM then uses these results to formulate its final response, creating a natural conversation flow that acknowledges the actions taken.
Implement function handlers
Let's update your lib/services/gemini_tools.dart
file to add handlers for function calls:
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);
Understanding the function handlers
Let's break down what these function handlers do:
-
handleFunctionCall
: A central dispatcher that:- Logs the function call for transparency in the log panel
- Routes to the appropriate handler based on the function name
- Returns a structured response that will be sent back to the LLM
-
handleSetColor
: The specific handler for yourset_color
function that:- Extracts RGB values from the arguments map
- Converts them to the expected types (doubles)
- Updates the application's color state using the
colorStateNotifier
- Creates a structured response with success status and current color information
- Logs the function results for debugging
-
handleUnknownFunction
: A fallback handler for unknown functions that:- Logs a warning about the unsupported function
- Returns an error response to the LLM
The handleSetColor
function is particularly important as it bridges the gap between the LLM's natural language understanding and concrete UI changes.
Update the Gemini chat service to process function calls and responses
Now, let's update the lib/services/gemini_chat_service.dart
file to process function calls from the LLM responses and send the results back to the 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);
Understanding the flow of communication
The key addition here is the complete handling of function calls and responses:
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);
}
}
این کد:
- Checks if the LLM response contains any function calls
- For each function call, invokes your
handleFunctionCall
method with the function name and arguments - Collects the results of each function call
- Sends these results back to the LLM using
Content.functionResponses
- Processes the LLM's response to the function results
- Updates the UI with the final response text
This creates a round trip flow:
- User → LLM: Requests a color
- LLM → App: Function calls with parameters
- App → User: New color displayed
- App → LLM: Function results
- LLM → User: Final response incorporating function results
Generate Riverpod code
Run the build runner command to generate the needed Riverpod code:
dart run build_runner build --delete-conflicting-outputs
Run and test the complete flow
Now run your application:
flutter run -d DEVICE
Try entering various color descriptions:
- "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"
Now you should see:
- Your message appearing in the chat interface
- Gemini's response appearing in the chat
- Function calls being logged in the log panel
- Function results being logged immediately after
- The color rectangle updating to display the described color
- RGB values updating to show the new color's components
- Gemini's final response appearing, often commenting on the color that was set
The log panel provides insight into what's happening behind the scenes. خواهید دید:
- The exact function calls Gemini is making
- The parameters it's choosing for each RGB value
- The results your function is returning
- The follow-up responses from Gemini
The color state notifier
The colorStateNotifier
you're using to update colors is part of the colorist_ui
package. مدیریت می کند:
- The current color displayed in the UI
- The color history (last 10 colors)
- Notification of state changes to UI components
When you call updateColor
with new RGB values, it:
- Creates a new
ColorData
object with the provided values - Updates the current color in the app state
- Adds the color to the history
- Triggers UI updates through Riverpod's state management
The UI components in the colorist_ui
package watch this state and automatically update when it changes, creating a reactive experience.
Understanding error handling
Your implementation includes robust error handling:
- Try-catch block : Wraps all LLM interactions to catch any exceptions
- Error logging : Records errors in the log panel with stack traces
- User feedback : Provides a friendly error message in the chat
- State cleanup : Finalizes the message state even if an error occurs
This ensures the app remains stable and provides appropriate feedback even when issues occur with the LLM service or function execution.
The power of function calling for user experience
What you've accomplished here demonstrates how LLMs can create powerful natural interfaces:
- Natural language interface : Users express intent in everyday language
- Intelligent interpretation : The LLM translates vague descriptions into precise values
- Direct manipulation : The UI updates in response to natural language
- Contextual responses : The LLM provides conversational context about the changes
- Low cognitive load : Users don't need to understand RGB values or color theory
This pattern of using LLM function calling to bridge natural language and UI actions can be extended to countless other domains beyond color selection.
بعدش چی؟
In the next step, you'll enhance the user experience by implementing streaming responses. Rather than waiting for the complete response, you'll process text chunks and function calls as they are received, creating a more responsive and engaging application.
عیب یابی
Function call issues
If Gemini isn't calling your functions or parameters are incorrect:
- Verify your function declaration matches what's described in the system prompt
- Check that parameter names and types are consistent
- Ensure your system prompt explicitly instructs the LLM to use the tool
- Verify the function name in your handler matches exactly what's in the declaration
- Examine the log panel for detailed information on function calls
Function response issues
If function results aren't being properly sent back to the LLM:
- Check that your function returns a properly formatted Map
- Verify that the Content.functionResponses is being constructed correctly
- Look for any errors in the log related to function responses
- Ensure you're using the same chat session for the response
Color display issues
If colors aren't displaying correctly:
- Ensure RGB values are properly converted to doubles (LLM might send them as integers)
- Verify that values are in the expected range (0.0 to 1.0)
- Check that the color state notifier is being called correctly
- Examine the log for the exact values being passed to the function
مشکلات عمومی
For general issues:
- Examine the logs for errors or warnings
- Verify Vertex AI in Firebase connectivity
- Check for any type mismatches in function parameters
- Ensure all Riverpod generated code is up to date
Key concepts learned
- Implementing a complete function calling pipeline in Flutter
- Creating full communication between an LLM and your application
- Processing structured data from LLM responses
- Sending function results back to the LLM for incorporation into responses
- Using the log panel to gain visibility into LLM-application interactions
- Connecting natural language inputs to concrete UI changes
With this step complete, your app now demonstrates one of the most powerful patterns for LLM integration: translating natural language inputs into concrete UI actions, while maintaining a coherent conversation that acknowledges these actions. This creates an intuitive, conversational interface that feels magical to users.
7. Streaming responses for better UX
In this step, you'll enhance the user experience by implementing streaming responses from Gemini. Instead of waiting for the entire response to be generated, you'll process text chunks and function calls as they are received, creating a more responsive and engaging application.
What you'll cover in this step
- The importance of streaming for LLM-powered applications
- Implementing streaming LLM responses in a Flutter application
- Processing partial text chunks as they arrive from the API
- Managing conversation state to prevent message conflicts
- Handling function calls in streaming responses
- Creating visual indicators for in-progress responses
Why streaming matters for LLM applications
Before implementing, let's understand why streaming responses are crucial for creating excellent user experiences with LLMs:
تجربه کاربری بهبود یافته
Streaming responses provide several significant user experience benefits:
- Reduced perceived latency : Users see text start appearing immediately (typically within 100-300ms), rather than waiting several seconds for a complete response. This perception of immediacy dramatically improves user satisfaction.
- Natural conversational rhythm : The gradual appearance of text mimics how humans communicate, creating a more natural dialogue experience.
- Progressive information processing : Users can begin processing information as it arrives, rather than being overwhelmed by a large block of text all at once.
- Opportunity for early interruption : In a full application, users could potentially interrupt or redirect the LLM if they see it going in an unhelpful direction.
- Visual confirmation of activity : The streaming text provides immediate feedback that the system is working, reducing uncertainty.
Technical advantages
Beyond UX improvements, streaming offers technical benefits:
- Early function execution : Function calls can be detected and executed as soon as they appear in the stream, without waiting for the complete response.
- Incremental UI updates : You can update your UI progressively as new information arrives, creating a more dynamic experience.
- Conversation state management : Streaming provides clear signals about when responses are complete vs. still in progress, enabling better state management.
- Reduced timeout risks : With non-streaming responses, long-running generations risk connection timeouts. Streaming establishes the connection early and maintains it.
For your Colorist app, implementing streaming means users will see both text responses and color changes appearing more promptly, creating a significantly more responsive experience.
Add conversation state management
First, let's add a state provider to track whether the app is currently handling a streaming response. Update your lib/services/gemini_chat_service.dart
file:
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);
Understanding the streaming implementation
Let's break down what this code does:
- Conversation state tracking :
- A
conversationStateProvider
tracks whether the app is currently processing a response - The state transitions from
idle
→busy
while processing, then back toidle
- This prevents multiple concurrent requests that could conflict
- A
- Stream initialization :
-
sendMessageStream()
returns a Stream of response chunks instead of aFuture
with the complete response - Each chunk may contain text, function calls, or both
-
- Progressive processing :
-
await for
processes each chunk as it arrives in real-time - Text is appended to the UI immediately, creating the streaming effect
- Function calls are executed as soon as they're detected
-
- Function call handling :
- When a function call is detected in a chunk, it's executed immediately
- Results are sent back to the LLM through another streaming call
- The LLM's response to these results is also processed in a streaming fashion
- Error handling and cleanup :
-
try
/catch
provides robust error handling - The
finally
block ensures conversation state is reset properly - Message is always finalized, even if errors occur
-
This implementation creates a responsive, reliable streaming experience while maintaining proper conversation state.
Update the main screen to connect conversation state
Modify your lib/main.dart
file to pass the conversation state to the main screen:
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),
),
);
}
}
The key change here is passing the conversationState
to the MainScreen
widget. The MainScreen
(provided by the colorist_ui
package) will use this state to disable the text input while a response is being processed.
This creates a cohesive user experience where the UI reflects the current state of the conversation.
Generate Riverpod code
Run the build runner command to generate the needed Riverpod code:
dart run build_runner build --delete-conflicting-outputs
Run and test streaming responses
Run your application:
flutter run -d DEVICE
Now try testing the streaming behavior with various color descriptions. Try descriptions like:
- "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"
- "Create a muted olive green like old army fatigues"
The streaming technical flow in detail
Let's examine exactly what happens when streaming a response:
Connection establishment
When you call sendMessageStream()
, the following happens:
- The app establishes a connection to the Vertex AI service
- The user request is sent to the service
- The server begins processing the request
- The stream connection remains open, ready to transmit chunks
Chunk transmission
As Gemini generates content, chunks are sent through the stream:
- The server sends text chunks as they're generated (typically a few words or sentences)
- When Gemini decides to make a function call, it sends the function call information
- Additional text chunks may follow function calls
- The stream continues until the generation is complete
Progressive processing
Your app processes each chunk incrementally:
- Each text chunk is appended to the existing response
- Function calls are executed as soon as they're detected
- The UI updates in real-time with both text and function results
- State is tracked to show the response is still streaming
Stream completion
When the generation is complete:
- The stream is closed by the server
- Your
await for
loop exits naturally - The message is marked as complete
- The conversation state is set back to idle
- The UI updates to reflect the completed state
Streaming vs. non-streaming comparison
To better understand the benefits of streaming, let's compare streaming vs. non-streaming approaches:
جنبه | Non-Streaming | پخش جریانی |
Perceived latency | User sees nothing until complete response is ready | User sees first words within milliseconds |
تجربه کاربری | Long wait followed by sudden text appearance | Natural, progressive text appearance |
مدیریت دولتی | Simpler (messages are either pending or complete) | More complex (messages can be in a streaming state) |
Function execution | Occurs only after complete response | Occurs during response generation |
Implementation complexity | Simpler to implement | Requires additional state management |
Error recovery | All-or-nothing response | Partial responses may still be useful |
پیچیدگی کد | پیچیدگی کمتر | More complex due to stream handling |
For an application like Colorist, the UX benefits of streaming outweigh the implementation complexity, especially for color interpretations that might take several seconds to generate.
Best practices for streaming UX
When implementing streaming in your own LLM applications, consider these best practices:
- Clear visual indicators : Always provide clear visual cues that distinguish streaming vs. complete messages
- Input blocking : Disable user input during streaming to prevent multiple overlapping requests
- Error recovery : Design your UI to handle graceful recovery if streaming is interrupted
- State transitions : Ensure smooth transitions between idle, streaming, and complete states
- Progress visualization : Consider subtle animations or indicators that show active processing
- Cancellation options : In a complete app, provide ways for users to cancel in-progress generations
- Function result integration : Design your UI to handle function results appearing mid-stream
- Performance optimization : Minimize UI rebuilds during rapid stream updates
The colorist_ui
package implements many of these best practices for you, but they're important considerations for any streaming LLM implementation.
بعدش چی؟
In the next step, you'll implement LLM synchronization by notifying Gemini when users select colors from history. This will create a more cohesive experience where the LLM is aware of user-initiated changes to the application state.
عیب یابی
Stream processing issues
If you encounter issues with stream processing:
- Symptoms : Partial responses, missing text, or abrupt stream termination
- Solution : Check network connectivity and ensure proper async/await patterns in your code
- Diagnosis : Examine the log panel for error messages or warnings related to stream processing
- Fix : Ensure all stream processing uses proper error handling with
try
/catch
blocks
Missing function calls
If function calls aren't being detected in the stream:
- Symptoms : Text appears but colors don't update, or log shows no function calls
- Solution : Verify the system prompt's instructions about using function calls
- Diagnosis : Check the log panel to see if function calls are being received
- Fix : Adjust your system prompt to more explicitly instruct the LLM to use the
set_color
tool
General error handling
For any other issues:
- Step 1 : Check the log panel for error messages
- Step 2 : Verify Vertex AI in Firebase connectivity
- Step 3 : Ensure all Riverpod generated code is up to date
- Step 4 : Review the streaming implementation for any missing await statements
Key concepts learned
- Implementing streaming responses with the Gemini API for more responsive UX
- Managing conversation state to handle streaming interactions properly
- Processing real-time text and function calls as they arrive
- Creating responsive UIs that update incrementally during streaming
- Handling concurrent streams with proper async patterns
- Providing appropriate visual feedback during streaming responses
By implementing streaming, you've significantly enhanced the user experience of your Colorist app, creating a more responsive, engaging interface that feels truly conversational.
8. LLM Context Synchronization
In this bonus step, you'll implement LLM Context Synchronization by notifying Gemini when users select colors from history. This creates a more cohesive experience where the LLM is aware of user actions in the interface, not just their explicit messages.
What you'll cover in this step
- Creating LLM Context Synchronization between your UI and the LLM
- Serializing UI events into context the LLM can understand
- Updating conversation context based on user actions
- Creating a coherent experience across different interaction methods
- Enhancing LLM context awareness beyond explicit chat messages
Understanding LLM Context Synchronization
Traditional chatbots only respond to explicit user messages, creating a disconnect when users interact with the app through other means. LLM Context Synchronization addresses this limitation:
Why LLM Context Synchronization matters
When users interact with your app through UI elements (like selecting a color from history), the LLM has no way of knowing what happened unless you explicitly tell it. LLM Context Synchronization:
- Maintains context : Keeps the LLM informed about all relevant user actions
- Creates coherence : Produces a cohesive experience where the LLM acknowledges UI interactions
- Enhances intelligence : Allows the LLM to respond appropriately to all user actions
- Improves user experience : Makes the entire application feel more integrated and responsive
- Reduces user effort : Eliminates the need for users to manually explain their UI actions
In your Colorist app, when a user selects a color from history, you want Gemini to acknowledge this action and comment intelligently about the selected color, maintaining the illusion of a seamless, aware assistant.
Update the Gemini chat service for color selection notifications
First, you'll add a method to the GeminiChatService
to notify the LLM when a user selects a color from history. Update your lib/services/gemini_chat_service.dart
file:
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);
The key addition is the notifyColorSelection
method, which:
- Takes a
ColorData
object representing the selected color - Encodes it to a JSON format that can be included in a message
- Sends a specially formatted message to the LLM indicating a user selection
- Reuses the existing
sendMessage
method to handle the notification
This approach avoids duplication by utilizing your existing message handling infrastructure.
Update main app to connect color selection notifications
Now, modify your lib/main.dart
file to pass the color selection notification function to the main screen:
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),
),
);
}
}
The key change is adding the notifyColorSelection
callback, which connects the UI event (selecting a color from history) to the LLM notification system.
Update the system prompt
Now, you need to update your system prompt to instruct the LLM on how to respond to color selection notifications. Modify your assets/system_prompt.md
file:
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
The key addition is the "When Users Select Historical Colors" section, which:
- Explains the concept of history selection notifications to the LLM
- Provides an example of what these notifications look like
- Shows an example of an appropriate response
- Sets expectations for acknowledging the selection and commenting on the color
This helps the LLM understand how to respond appropriately to these special messages.
Generate Riverpod Code
Run the build runner command to generate the needed Riverpod code:
dart run build_runner build --delete-conflicting-outputs
Run and test LLM Context Synchronization
Run your application:
flutter run -d DEVICE
Testing the LLM Context Synchronization involves:
- First, generate a few colors by describing them in the chat
- "Show me a vibrant purple"
- "I'd like a forest green"
- "Give me a bright red"
- Then, click on one of the color thumbnails in the history strip
باید رعایت کنید:
- The selected color appears in the main display
- A user message appears in the chat indicating the color selection
- The LLM responds by acknowledging the selection and commenting on the color
- The entire interaction feels natural and cohesive
This creates a seamless experience where the LLM is aware of and responds appropriately to both direct messages and UI interactions.
How LLM Context Synchronization works
Let's explore the technical details of how this synchronization works:
جریان داده
- User action : User clicks a color in the history strip
- UI event : The
MainScreen
widget detects this selection - Callback execution : The
notifyColorSelection
callback is triggered - Message creation : A specially formatted message is created with the color data
- LLM processing : The message is sent to Gemini, which recognizes the format
- Contextual response : Gemini responds appropriately based on the system prompt
- UI update : The response appears in the chat, creating a cohesive experience
Data serialization
A key aspect of this approach is how you serialize the color data:
'User selected color from history: ${json.encode(color.toLLMContextMap())}'
The toLLMContextMap()
method (provided by the colorist_ui
package) converts a ColorData
object into a map with key properties that the LLM can understand. این به طور معمول شامل:
- RGB values (red, green, blue)
- Hex code representation
- Any name or description associated with the color
By formatting this data consistently and including it in the message, you ensure the LLM has all the information it needs to respond appropriately.
Broader applications of LLM Context Synchronization
This pattern of notifying the LLM about UI events has numerous applications beyond color selection:
موارد استفاده دیگر
- Filter changes : Notify the LLM when users apply filters to data
- Navigation events : Inform the LLM when users navigate to different sections
- Selection changes : Update the LLM when users select items from lists or grids
- Preference updates : Tell the LLM when users change settings or preferences
- Data manipulation : Notify the LLM when users add, edit, or delete data
In each case, the pattern remains the same:
- Detect the UI event
- Serialize relevant data
- Send a specially formatted notification to the LLM
- Guide the LLM to respond appropriately through the system prompt
Best practices for LLM Context Synchronization
Based on your implementation, here are some best practices for effective LLM Context Synchronization:
1. Consistent formatting
Use a consistent format for notifications so the LLM can easily identify them:
"User [action] [object]: [structured data]"
2. Rich context
Include enough detail in notifications for the LLM to respond intelligently. For colors, this means RGB values, hex codes, and any other relevant properties.
3. دستورالعمل ها را روشن کنید
Provide explicit instructions in the system prompt about how to handle notifications, ideally with examples.
4. Natural integration
Design notifications to flow naturally in the conversation, not as technical interruptions.
5. Selective notification
Only notify the LLM about actions that are relevant to the conversation. Not every UI event needs to be communicated.
عیب یابی
Notification issues
If the LLM isn't responding properly to color selections:
- Check that the notification message format matches what's described in the system prompt
- Verify that the color data is being properly serialized
- Ensure the system prompt has clear instructions for handling selections
- Look for any errors in the chat service when sending notifications
مدیریت زمینه
If the LLM seems to lose context:
- Check that the chat session is being maintained properly
- Verify that conversation states transition correctly
- Ensure that notifications are being sent through the same chat session
مشکلات عمومی
For general issues:
- Examine the logs for errors or warnings
- Verify Vertex AI in Firebase connectivity
- Check for any type mismatches in function parameters
- Ensure all Riverpod generated code is up to date
Key concepts learned
- Creating LLM Context Synchronization between UI and LLM
- Serializing UI events into LLM-friendly context
- Guiding LLM behavior for different interaction patterns
- Creating a cohesive experience across message and non-message interactions
- Enhancing LLM awareness of the broader application state
By implementing LLM Context Synchronization, you've created a truly integrated experience where the LLM feels like an aware, responsive assistant rather than just a text generator. This pattern can be applied to countless other applications to create more natural, intuitive AI-powered interfaces.
9. تبریک می گویم!
You've successfully completed the Colorist codelab! 🎉
What you've built
You've created a fully functional Flutter application that integrates Google's Gemini API to interpret natural language color descriptions. Your app can now:
- Process natural language descriptions like "sunset orange" or "deep ocean blue"
- Use Gemini to intelligently translate these descriptions into RGB values
- Display the interpreted colors in real-time with streaming responses
- Handle user interactions through both chat and UI elements
- Maintain contextual awareness across different interaction methods
از اینجا به کجا برویم
Now that you've mastered the basics of integrating Gemini with Flutter, here are some ways to continue your journey:
Enhance your Colorist app
- Color palettes : Add functionality to generate complementary or matching color schemes
- Voice input : Integrate speech recognition for verbal color descriptions
- History management : Add options to name, organize, and export color sets
- Custom prompting : Create an interface for users to customize system prompts
- Advanced analytics : Track which descriptions work best or cause difficulties
Explore more Gemini features
- Multimodal inputs : Add image inputs to extract colors from photos
- Content generation : Use Gemini to generate color-related content like descriptions or stories
- Function calling enhancements : Create more complex tool integrations with multiple functions
- Safety settings : Explore different safety settings and their impact on responses
Apply these patterns to other domains
- Document analysis : Create apps that can understand and analyze documents
- Creative writing assistance : Build writing tools with LLM-powered suggestions
- Task automation : Design apps that translate natural language into automated tasks
- Knowledge-based applications : Create expert systems in specific domains
منابع
Here are some valuable resources to continue your learning:
اسناد رسمی
- Vertex AI in Firebase Documentation
- Flutter Documentation
- Riverpod Documentation
- Gemini API Documentation
Prompting course and guide
جامعه
Observable Flutter Agentic series
In expisode #59, Craig Labenz and Andrew Brogden explore this codelab, highlighting interesting parts of the app build.
In episode #60, join Craig and Andrew again as they extend the codelab app with new capabilities and fight with making LLMs do as they are told.
In episode #61, Craig is joined by Chris Sells to have a fresh take at analysing news headlines and generates corresponding images.
بازخورد
We'd love to hear about your experience with this codelab! Please consider providing feedback through:
Thank you for completing this codelab, and we hope you continue exploring the exciting possibilities at the intersection of Flutter and AI!