1. إنشاء تطبيق Flutter مستنِد إلى Gemini
ما ستنشئه
في هذا الدرس التطبيقي حول الترميز، ستنشئ تطبيق Colorist، وهو تطبيق تفاعلي من Flutter يتيح لك الاستفادة من إمكانات Gemini API مباشرةً في تطبيقك. هل أردت يومًا أن تتيح للمستخدمين التحكّم في تطبيقك من خلال اللغة الطبيعية ولكن لم تعرف من أين تبدأ؟ يوضّح لك هذا الدرس التطبيقي حول الترميز كيفية إجراء ذلك.
يتيح تطبيق Colorist للمستخدمين وصف الألوان باللغة الطبيعية (مثل "برتقالي غروب الشمس" أو "أزرق المحيط العميق")، ويقوم التطبيق بما يلي:
- تعالج هذه الأوصاف باستخدام Gemini API من Google
- تفسير الأوصاف إلى قيم ألوان دقيقة بنظام RGB
- تعرض اللون على الشاشة في الوقت الفعلي
- تقدّم تفاصيل فنية عن اللون وسياقًا مثيرًا للاهتمام حوله
- الاحتفاظ بسجلّ للألوان التي تم إنشاؤها مؤخرًا

يتميّز التطبيق بواجهة شاشة مقسَّمة تتضمّن مساحة عرض ملونة ونظام محادثة تفاعليًا على أحد الجانبين، ولوحة سجلّ مفصّلة تعرض التفاعلات الأولية مع النموذج اللغوي الكبير على الجانب الآخر. يتيح لك هذا السجلّ فهمًا أفضل لكيفية عمل عملية دمج نموذج لغوي كبير.
أهمية ذلك لمطوّري Flutter
تُحدث النماذج اللغوية الكبيرة ثورة في طريقة تفاعل المستخدمين مع التطبيقات، ولكن دمجها بفعالية في تطبيقات الأجهزة الجوّالة وأجهزة الكمبيوتر يطرح تحديات فريدة. يعلّمك هذا الدرس التطبيقي حول الترميز أنماطًا عملية تتجاوز مجرد طلبات البيانات من واجهة برمجة التطبيقات.
رحلة التعلّم
يرشدك هذا الدرس التطبيقي حول الترميز إلى خطوات إنشاء تطبيق Colorist:
- إعداد المشروع: ستبدأ ببنية تطبيق Flutter الأساسية وحزمة
colorist_ui - عملية الدمج الأساسية مع Gemini: ربط تطبيقك بخدمة Firebase AI Logic وتنفيذ عملية التواصل مع النموذج اللغوي الكبير
- توجيه الطلبات بفعالية: إنشاء تعليمات نظام توجّه النموذج اللغوي الكبير لفهم أوصاف الألوان
- تعريفات الدوال: تحديد الأدوات التي يمكن للنموذج اللغوي الكبير استخدامها لضبط الألوان في تطبيقك
- التعامل مع الأدوات: معالجة طلبات الدوال من نموذج اللغة الكبير وربطها بحالة تطبيقك
- عرض الردود تدريجيًا: تحسين تجربة المستخدم من خلال عرض ردود النموذج اللغوي الكبير تدريجيًا في الوقت الفعلي
- مزامنة سياق النموذج اللغوي الكبير: يمكنك إنشاء تجربة متماسكة من خلال إعلام النموذج اللغوي الكبير بإجراءات المستخدم.
أهداف الدورة التعليمية
- ضبط Firebase AI Logic لتطبيقات Flutter
- صياغة طلبات نظام فعّالة لتوجيه سلوك النموذج اللغوي الكبير
- تنفيذ تعريفات الدوال التي تربط بين اللغة الطبيعية وميزات التطبيق
- معالجة الردود المتدفقة لتوفير تجربة مستخدم متجاوبة
- مزامنة الحالة بين أحداث واجهة المستخدم ونموذج اللغة الكبير
- إدارة حالة محادثة النموذج اللغوي الكبير باستخدام Riverpod
- معالجة الأخطاء بسلاسة في التطبيقات المستندة إلى نماذج اللغات الكبيرة
معاينة الرمز البرمجي: نظرة سريعة على ما ستنفّذه
في ما يلي نظرة سريعة على تعريف الدالة الذي ستنشئه للسماح للنموذج اللغوي الكبير بتحديد الألوان في تطبيقك:
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 من برنامج Observable Flutter:
المتطلبات الأساسية
للاستفادة إلى أقصى حدّ من هذا الدرس التطبيقي، يجب أن يكون لديك:
- تجربة تطوير تطبيقات Flutter: الإلمام بأساسيات Flutter وبنية Dart
- معرفة البرمجة غير المتزامنة: فهم Futures وasync/await وstreams
- حساب على Firebase: ستحتاج إلى حساب Google لإعداد Firebase
لنبدأ بإنشاء تطبيق Flutter الأول المستند إلى نموذج لغوي كبير.
2. إعداد المشروع وخدمة المحاكاة
في هذه الخطوة الأولى، عليك إعداد بنية المشروع وتنفيذ خدمة echo التي سيتم استبدالها لاحقًا بعملية دمج Gemini API. يؤدي ذلك إلى إنشاء بنية التطبيق والتأكّد من عمل واجهة المستخدم بشكلٍ صحيح قبل إضافة تعقيد طلبات LLM.
ما ستتعرّف عليه في هذه الخطوة
- إعداد مشروع Flutter مع التبعيات المطلوبة
- العمل مع حزمة
colorist_uiلمكوّنات واجهة المستخدم - تنفيذ خدمة رسائل الصدى وربطها بواجهة المستخدم
إنشاء مشروع Flutter جديد
ابدأ بإنشاء مشروع Flutter جديد باستخدام الأمر التالي:
flutter create -e colorist --platforms=android,ios,macos,web,windows
تشير العلامة -e إلى أنّك تريد مشروعًا فارغًا بدون تطبيق counter التلقائي. تم تصميم التطبيق ليعمل على أجهزة الكمبيوتر والأجهزة الجوّالة والويب. يُرجى العِلم أنّ flutterfire لا يتوافق مع نظام التشغيل Linux في الوقت الحالي.
إضافة التبعيات
انتقِل إلى دليل مشروعك وأضِف التبعيات المطلوبة:
cd colorist
flutter pub add colorist_ui flutter_riverpod riverpod_annotation
flutter pub add --dev build_runner riverpod_generator riverpod_lint json_serializable custom_lint
سيؤدي ذلك إلى إضافة حِزم المفاتيح التالية:
-
colorist_ui: حزمة مخصّصة توفّر مكوّنات واجهة المستخدم لتطبيق 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.9.2
dependencies:
flutter:
sdk: flutter
colorist_ui: ^0.3.0
flutter_riverpod: ^3.0.0
riverpod_annotation: ^3.0.0
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^6.0.0
build_runner: ^2.7.1
riverpod_generator: ^3.0.0
riverpod_lint: ^3.0.0
json_serializable: ^6.11.1
flutter:
uses-material-design: true
ضبط خيارات التحليل
أضِف 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(chatStateProvider.notifier);
final logStateNotifier = ref.read(logStateProvider.notifier);
chatStateNotifier.addUserMessage(message);
logStateNotifier.logUserText(message);
chatStateNotifier.addLlmMessage(message, MessageState.complete);
logStateNotifier.logLlmText(message);
}
}
يؤدي ذلك إلى إعداد تطبيق Flutter ينفّذ خدمة صدى تحاكي سلوك نموذج لغوي كبير من خلال عرض رسالة المستخدم.
فهم البنية
لنتعرّف على بنية تطبيق "colorist":
حزمة colorist_ui
توفر حزمة colorist_ui مكوّنات واجهة مستخدم مُنشأة مسبقًا وأدوات لإدارة الحالة:
- MainScreen: مكوّن واجهة المستخدم الرئيسية الذي يعرض ما يلي:
- تنسيق تقسيم الشاشة على الكمبيوتر المكتبي (مساحة التفاعل ولوحة السجلّ)
- واجهة مقسّمة إلى علامات تبويب على جهاز جوّال
- عرض الألوان وواجهة المحادثة والصور المصغّرة لسجلّ المحادثات
- إدارة الحالة: يستخدم التطبيق العديد من أدوات إشعار الحالة:
- ChatStateNotifier: يدير رسائل المحادثة
- ColorStateNotifier: لإدارة اللون الحالي والسجلّ
- LogStateNotifier: لإدارة إدخالات السجلّ لتصحيح الأخطاء
- معالجة الرسائل: يستخدم التطبيق نموذج رسائل يتضمّن حالات مختلفة:
- رسائل المستخدم: الرسائل التي أدخلها المستخدم
- رسائل النموذج اللغوي الكبير: يتم إنشاؤها بواسطة النموذج اللغوي الكبير (أو خدمة المحاكاة الصوتية حاليًا)
- MessageState: يتتبّع ما إذا كانت رسائل النموذج اللغوي الكبير مكتملة أو لا تزال قيد البث
بنية التطبيق
يتبع التطبيق البنية التالية:
- طبقة واجهة المستخدم: مقدَّمة من حزمة
colorist_ui - إدارة الحالة: تستخدم حزمة Riverpod لإدارة الحالة التفاعلية
- طبقة الخدمة: تحتوي حاليًا على خدمة الصدى البسيطة، وسيتم استبدالها بخدمة Gemini Chat
- دمج النموذج اللغوي الكبير: سيتم إضافته في خطوات لاحقة
يتيح لك هذا الفصل التركيز على تنفيذ عملية دمج النموذج اللغوي الكبير، بينما يتم الاهتمام بمكوّنات واجهة المستخدم.
تشغيل التطبيق
شغِّل التطبيق باستخدام الأمر التالي:
flutter run -d DEVICE
استبدِل DEVICE بالجهاز المستهدَف، مثل macos أو windows أو chrome أو رقم تعريف الجهاز.

من المفترض أن يظهر لك الآن تطبيق Colorist مع:
- مساحة عرض الألوان مع لون تلقائي
- واجهة محادثة يمكنك كتابة الرسائل فيها
- لوحة سجلّ تعرض تفاعلات المحادثة
جرِّب كتابة رسالة مثل "أريد لونًا أزرق داكنًا" واضغط على "إرسال". ستكرّر خدمة الصدى رسالتك ببساطة. في الخطوات اللاحقة، ستستبدل هذا الرمز بتفسير الألوان الفعلي باستخدام Firebase AI Logic.
ما هي الخطوات التالية؟
في الخطوة التالية، ستضبط إعدادات Firebase وتنفّذ عملية دمج أساسية لواجهة Gemini API من أجل استبدال خدمة الردّ بصدى الصوت بخدمة المحادثة في Gemini. سيسمح ذلك للتطبيق بتفسير أوصاف الألوان وتقديم ردود ذكية.
تحديد المشاكل وحلّها
مشاكل في حزمة واجهة المستخدم
في حال مواجهة مشاكل في حزمة colorist_ui:
- تأكَّد من استخدام أحدث إصدار
- التأكّد من إضافة التبعية بشكل صحيح
- التحقّق من توفّر أي إصدارات حِزم متعارضة
أخطاء الإنشاء
إذا ظهرت لك أخطاء في الإصدار، اتّبِع الخطوات التالية:
- تأكَّد من تثبيت أحدث إصدار ثابت من حزمة تطوير البرامج (SDK) الخاصة بمنصة Flutter.
- تشغيل
flutter cleanمتبوعًا بـflutter pub get - التحقّق من ناتج وحدة التحكّم بحثًا عن رسائل خطأ معيّنة
المفاهيم الرئيسية التي تم تعلّمها
- إعداد مشروع Flutter مع التبعيات اللازمة
- فهم بنية التطبيق ومسؤوليات المكوّنات
- تنفيذ خدمة بسيطة تحاكي سلوك نموذج لغوي كبير
- ربط الخدمة بمكوّنات واجهة المستخدم
- استخدام Riverpod لإدارة الحالة
3- عمليات الدمج الأساسية في Gemini Chat
في هذه الخطوة، ستستبدل خدمة الردّ التي استخدمتها في الخطوة السابقة بعملية دمج Gemini API باستخدام Firebase AI Logic. ستضبط إعدادات Firebase، وتُعدّ موفّري الخدمات اللازمين، وتنفّذ خدمة محادثة أساسية تتواصل مع Gemini API.
ما ستتعرّف عليه في هذه الخطوة
- إعداد Firebase في تطبيق Flutter
- إعداد Firebase AI Logic للوصول إلى Gemini
- إنشاء أدوات توفير Riverpod لخدمات Firebase وGemini
- تنفيذ خدمة محادثة أساسية باستخدام Gemini API
- التعامل مع الردود غير المتزامنة من واجهة برمجة التطبيقات وحالات الخطأ
إعداد Firebase
أولاً، عليك إعداد Firebase لمشروع Flutter. يتضمّن ذلك إنشاء مشروع على Firebase وإضافة تطبيقك إليه وإعداد إعدادات Firebase AI Logic اللازمة.
إنشاء مشروع على Firebase
- انتقِل إلى وحدة تحكّم Firebase وسجِّل الدخول باستخدام حسابك على Google.
- انقر على إنشاء مشروع على Firebase أو اختَر مشروعًا حاليًا.
- اتّبِع معالج الإعداد لإنشاء مشروعك.
إعداد Firebase AI Logic في مشروع Firebase
- في "وحدة تحكّم Firebase"، انتقِل إلى مشروعك.
- في الشريط الجانبي الأيمن، انقر على الذكاء الاصطناعي.
- في القائمة المنسدلة الخاصة بالذكاء الاصطناعي، اختَر منطق الذكاء الاصطناعي.
- في بطاقة Firebase AI Logic، انقر على البدء.
- اتّبِع التعليمات لتفعيل Gemini Developer API لمشروعك.
تثبيت FlutterFire CLI
تسهّل أداة FlutterFire CLI عملية إعداد Firebase في تطبيقات Flutter من خلال ما يلي:
dart pub global activate flutterfire_cli
إضافة Firebase إلى تطبيق Flutter
- أضِف حِزمتَي Firebase الأساسية وFirebase AI Logic إلى مشروعك:
flutter pub add firebase_core firebase_ai
- نفِّذ أمر إعداد FlutterFire:
flutterfire configure
سيؤدي هذا الأمر إلى:
- يطلب منك اختيار مشروع Firebase الذي أنشأته للتو
- تسجيل تطبيقات Flutter في Firebase
- إنشاء ملف
firebase_options.dartيتضمّن إعدادات مشروعك
سيرصد الأمر تلقائيًا الأنظمة الأساسية التي اخترتها (iOS وAndroid وmacOS وWindows والويب) ويضبطها بشكلٍ مناسب.
الإعدادات الخاصة بالنظام الأساسي
تتطلّب Firebase إصدارات أدنى أعلى من الإصدارات التلقائية في Flutter. ويتطلّب أيضًا إذنًا بالوصول إلى الشبكة للتواصل مع خوادم Firebase AI Logic.
ضبط أذونات macOS
بالنسبة إلى macOS، عليك تفعيل إذن الوصول إلى الشبكة في أذونات التطبيق باتّباع الخطوات التالية:
- افتح
macos/Runner/DebugProfile.entitlementsوأضِف:
macos/Runner/DebugProfile.entitlements
<key>com.apple.security.network.client</key>
<true/>
- افتح أيضًا
macos/Runner/Release.entitlementsوأضِف الإدخال نفسه.
ضبط إعدادات iOS
بالنسبة إلى أجهزة iOS، يجب تعديل الحد الأدنى للإصدار في أعلى صفحة ios/Podfile:
ios/Podfile
# Firebase requires at least iOS 15.0
platform :ios, '15.0'
إنشاء موفّري نماذج Gemini
الآن، عليك إنشاء موفّري Riverpod لكل من Firebase وGemini. إنشاء ملف جديد lib/providers/gemini.dart:
lib/providers/gemini.dart
import 'dart:async';
import 'package:firebase_ai/firebase_ai.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import '../firebase_options.dart';
part 'gemini.g.dart';
@Riverpod(keepAlive: true)
Future<FirebaseApp> firebaseApp(Ref ref) =>
Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
@Riverpod(keepAlive: true)
Future<GenerativeModel> geminiModel(Ref ref) async {
await ref.watch(firebaseAppProvider.future);
final model = FirebaseAI.googleAI().generativeModel(
model: 'gemini-3.1-flash-lite',
);
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. يستخدم هذا الرمز أسلوبًا مستندًا إلى التعليقات التوضيحية في Riverpod 3 مع أنماط موفّر محدَّثة.
-
firebaseAppProvider: تهيئة Firebase باستخدام إعدادات مشروعك -
geminiModelProvider: لإنشاء مثيل لنموذج Gemini التوليدي -
chatSessionProvider: إنشاء جلسة محادثة مع نموذج Gemini والحفاظ عليها
تضمن التعليق التوضيحي keepAlive: true على جلسة المحادثة استمرارها طوال دورة حياة التطبيق، ما يحافظ على سياق المحادثة.
تنفيذ خدمة Gemini Chat
أنشئ ملفًا جديدًا 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_ai/firebase_ai.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import '../providers/gemini.dart';
part 'gemini_chat_service.g.dart';
class GeminiChatService {
GeminiChatService(this.ref);
final Ref ref;
Future<void> sendMessage(String message) async {
final chatSession = await ref.read(chatSessionProvider.future);
final chatStateNotifier = ref.read(chatStateProvider.notifier);
final logStateNotifier = ref.read(logStateProvider.notifier);
chatStateNotifier.addUserMessage(message);
logStateNotifier.logUserText(message);
final llmMessage = chatStateNotifier.createLlmMessage();
try {
final response = await chatSession.sendMessage(Content.text(message));
final responseText = response.text;
if (responseText != null) {
logStateNotifier.logLlmText(responseText);
chatStateNotifier.appendToMessage(llmMessage.id, responseText);
}
} catch (e, st) {
logStateNotifier.logError(e, st: st);
chatStateNotifier.appendToMessage(
llmMessage.id,
"\nI'm sorry, I encountered an error processing your request. "
"Please try again.",
);
} finally {
chatStateNotifier.finalizeMessage(llmMessage.id);
}
}
}
@Riverpod(keepAlive: true)
GeminiChatService geminiChatService(Ref ref) => GeminiChatService(ref);
تتيح لك هذه الخدمة:
- يقبل رسائل المستخدمين ويرسلها إلى Gemini API
- تعديل واجهة المحادثة باستخدام الردود من النموذج
- تسجيل جميع عمليات التواصل لتسهيل فهم سير عمل النموذج اللغوي الكبير الفعلي
- التعامل مع الأخطاء من خلال تقديم ملاحظات مناسبة للمستخدم
ملاحظة: ستكون نافذة السجلّ مطابقة تقريبًا لنافذة المحادثة في هذه المرحلة. سيصبح السجلّ أكثر إثارة للاهتمام عند تقديم طلبات استدعاء الدوال ثم الردود المتدفقة.
إنشاء رمز Riverpod
شغِّل أمر build runner لإنشاء رمز Riverpod اللازم:
dart run build_runner build --delete-conflicting-outputs
سيؤدي ذلك إلى إنشاء ملفات .g.dart التي يحتاجها Riverpod ليعمل.
تعديل ملف main.dart
تعديل ملف lib/main.dart لاستخدام خدمة Gemini Chat الجديدة:
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),
),
);
}
}
في ما يلي التغييرات الرئيسية في هذا التحديث:
- استبدال خدمة الردّ التلقائي بخدمة محادثة مستندة إلى Gemini API
- إضافة شاشات التحميل والخطأ باستخدام نمط
AsyncValueفي Riverpod مع طريقةwhen - ربط واجهة المستخدم بخدمة المحادثة الجديدة من خلال عملية رد الاتصال
sendMessage
تشغيل التطبيق
شغِّل التطبيق باستخدام الأمر التالي:
flutter run -d DEVICE
استبدِل DEVICE بالجهاز المستهدَف، مثل macos أو windows أو chrome أو رقم تعريف الجهاز.

عند كتابة رسالة، سيتم إرسالها إلى Gemini API، وستتلقّى ردًا من النموذج اللغوي الكبير بدلاً من سماع صدى صوتك. ستعرض لوحة السجلّ التفاعلات مع واجهة برمجة التطبيقات.
فهم التواصل مع النماذج اللغوية الكبيرة
لنتعرّف على ما يحدث عند التواصل مع Gemini API:
مسار التواصل
- إدخال المستخدم: يُدخل المستخدم نصًا في واجهة المحادثة
- تنسيق الطلب: ينسّق التطبيق النص كعنصر
Contentلواجهة Gemini API - التواصل مع واجهة برمجة التطبيقات: يتم إرسال النص إلى Gemini API من خلال Firebase AI Logic
- معالجة النموذج اللغوي الكبير: يعالج نموذج Gemini النص وينشئ ردًا
- معالجة الردود: يتلقّى التطبيق الردّ ويعدّل واجهة المستخدم.
- التسجيل: يتم تسجيل جميع عمليات التواصل لضمان الشفافية
جلسات المحادثة وسياق المحادثة
تحتفظ جلسة المحادثة مع Gemini بالسياق بين الرسائل، ما يتيح التفاعل بشكل حواري. وهذا يعني أنّ النموذج اللغوي الكبير "يتذكّر" المراسلات السابقة في الجلسة الحالية، ما يتيح إجراء محادثات أكثر اتساقًا.
تضمن التعليق التوضيحي keepAlive: true على موفّر جلسة المحادثة استمرار هذا السياق طوال دورة حياة التطبيق. هذا السياق الثابت ضروري للحفاظ على سير المحادثة بشكل طبيعي مع النموذج اللغوي الكبير.
ما هي الخطوات التالية؟
في هذه المرحلة، يمكنك طرح أي سؤال على Gemini API، إذ لا توجد قيود على ما سيردّ عليه. على سبيل المثال، يمكنك أن تطلب منه تقديم ملخّص عن "حروب الوردتين"، وهو موضوع لا صلة له بالغرض من تطبيق الألوان.
في الخطوة التالية، ستنشئ طلب نظام لتوجيه Gemini في تفسير أوصاف الألوان بفعالية أكبر. سيوضّح هذا كيفية تخصيص سلوك نموذج لغوي كبير (LLM) لتلبية احتياجات خاصة بالتطبيق والتركيز على إمكاناته في نطاق تطبيقك.
تحديد المشاكل وحلّها
مشاكل في إعداد Firebase
في حال مواجهة أخطاء عند إعداد Firebase:
- التأكّد من إنشاء ملف
firebase_options.dartبشكل صحيح - تأكَّد من أنّك قد رقّيت إلى خطة Blaze للاستفادة من ميزة Firebase AI Logic
أخطاء الوصول إلى واجهة برمجة التطبيقات
في حال ظهور أخطاء عند محاولة الوصول إلى Gemini API، اتّبِع الخطوات التالية:
- التأكّد من إعداد الفوترة بشكلٍ صحيح في مشروعك على Firebase
- التأكّد من تفعيل Firebase AI Logic وCloud AI API في مشروع Firebase
- التحقّق من إعدادات الاتصال بالشبكة وجدار الحماية
- تأكَّد من أنّ اسم النموذج (
gemini-3.1-flash-lite) صحيح ومتوفّر
مشاكل سياق المحادثة
إذا لاحظت أنّ Gemini لا يتذكّر سياق الحديث السابق من المحادثة، اتّبِع الخطوات التالية:
- تأكَّد من أنّ الدالة
chatSessionمزوّدة بالتعليق التوضيحي@Riverpod(keepAlive: true) - تأكَّد من إعادة استخدام جلسة المحادثة نفسها لجميع عمليات تبادل الرسائل
- التأكّد من تهيئة جلسة المحادثة بشكل صحيح قبل إرسال الرسائل
المشاكل المتعلّقة بمنصّة معيّنة
بالنسبة إلى المشاكل الخاصة بمنصة معيّنة:
- iOS/macOS: التأكّد من ضبط الأذونات المناسبة وإعداد الحد الأدنى للإصدارات
- Android: التحقّق من ضبط الحد الأدنى لإصدار حزمة تطوير البرامج (SDK) بشكلٍ صحيح
- الاطّلاع على رسائل الخطأ الخاصة بمنصة معيّنة في وحدة التحكّم
المفاهيم الرئيسية التي تم تعلّمها
- إعداد Firebase في تطبيق Flutter
- إعداد Firebase AI Logic للوصول إلى Gemini
- إنشاء موفّري Riverpod للخدمات غير المتزامنة
- تنفيذ خدمة دردشة تتواصل مع نموذج لغوي كبير
- التعامل مع حالات واجهة برمجة التطبيقات غير المتزامنة (التحميل، الخطأ، البيانات)
- فهم مسار التواصل والجلسات في النماذج اللغوية الكبيرة
4. كتابة طلبات فعّالة للحصول على أوصاف الألوان
في هذه الخطوة، ستنشئ وتنفّذ طلب نظام يرشد Gemini في تفسير أوصاف الألوان. طلبات النظام هي طريقة فعّالة لتخصيص سلوك النماذج اللغوية الكبيرة لمهام معيّنة بدون تغيير الرمز البرمجي.
ما ستتعرّف عليه في هذه الخطوة
- فهم طلبات النظام وأهميتها في تطبيقات النماذج اللغوية الكبيرة
- صياغة طلبات فعّالة للمهام الخاصة بمجال معيّن
- تحميل طلبات النظام واستخدامها في تطبيق Flutter
- توجيه نموذج لغوي كبير لتقديم ردود بتنسيق متّسق
- اختبار كيفية تأثير طلبات النظام في سلوك النموذج اللغوي الكبير
التعرّف على طلبات النظام
قبل الخوض في تفاصيل التنفيذ، دعونا نتعرّف على إشعارات النظام وأهميتها:
ما هي طلبات النظام؟
طلب النظام هو نوع خاص من التعليمات المقدَّمة إلى نموذج لغوي كبير يحدّد السياق وإرشادات السلوك وتوقّعات الردود. على عكس رسائل المستخدمين، فإنّ طلبات النظام:
- تحديد دور النموذج اللغوي الكبير وشخصيته
- تحديد المعرفة أو القدرات المتخصصة
- تقديم تعليمات التنسيق
- ضبط قيود على الردود
- وصف كيفية التعامل مع سيناريوهات مختلفة
يمكنك اعتبار طلب النظام بمثابة "الوصف الوظيفي" للنموذج اللغوي الكبير، فهو يحدد طريقة تفاعل النموذج طوال المحادثة.
أهمية طلبات النظام
تُعدّ تعليمات النظام ضرورية لإنشاء تفاعلات متّسقة ومفيدة مع النماذج اللغوية الكبيرة، وذلك للأسباب التالية:
- ضمان الاتّساق: توجيه النموذج لتقديم الردود بتنسيق متّسق
- تحسين مدى الصلة بالموضوع: تركيز النموذج على مجالك المحدّد (في حالتك، الألوان)
- وضع حدود: تحديد الإجراءات التي يجب أن يتّخذها النموذج والإجراءات التي يجب تجنُّبها
- تحسين تجربة المستخدم: إنشاء نمط تفاعل أكثر طبيعية وفائدة
- تقليل المعالجة اللاحقة: الحصول على ردود بتنسيقات يسهل تحليلها أو عرضها
بالنسبة إلى تطبيق Colorist، تحتاج إلى نموذج لغوي كبير يفسّر أوصاف الألوان باستمرار ويقدّم قيم 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
فهم بنية طلب النظام
لنحلّل ما يفعله هذا الطلب:
- تعريف الدور: تحديد دور النموذج اللغوي الكبير على أنّه "مساعد خبير في الألوان"
- شرح المهمة: تحدّد المهمة الأساسية على أنّها ترجمة أوصاف الألوان إلى قيم أحمر وأخضر وأزرق
- تنسيق الردّ: يحدّد بدقة كيفية تنسيق قيم RGB لضمان الاتساق
- مثال على التبادل: يقدّم مثالاً ملموسًا على نمط التفاعل المتوقّع
- التعامل مع الحالات الهامشية: يوضّح كيفية التعامل مع الأوصاف غير الواضحة
- القيود والإرشادات: تضبط الحدود، مثل إبقاء قيم النموذج اللوني أحمر أخضر أزرق (RGB) بين 0.0 و1.0
يضمن هذا النهج المنظَّم أن تكون ردود النموذج اللغوي الكبير متسقة وغنية بالمعلومات ومنسَّقة بطريقة يسهل تحليلها إذا أردت استخراج قيم 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:riverpod_annotation/riverpod_annotation.dart';
part 'system_prompt.g.dart';
@Riverpod(keepAlive: true)
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_ai/firebase_ai.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import '../firebase_options.dart';
import 'system_prompt.dart'; // Add this import
part 'gemini.g.dart';
@Riverpod(keepAlive: true)
Future<FirebaseApp> firebaseApp(Ref ref) =>
Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
@Riverpod(keepAlive: true)
Future<GenerativeModel> geminiModel(Ref ref) async {
await ref.watch(firebaseAppProvider.future);
final systemPrompt = await ref.watch(systemPromptProvider.future); // Add this line
final model = FirebaseAI.googleAI().generativeModel(
model: 'gemini-3.1-flash-lite',
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
نفِّذ أمر أداة إنشاء الإصدار لإنشاء رمز Riverpod المطلوب:
dart run build_runner build --delete-conflicting-outputs
تشغيل التطبيق واختباره
شغِّل تطبيقك الآن:
flutter run -d DEVICE

جرِّب اختبارها باستخدام أوصاف مختلفة للألوان:
- "أريد لونًا أزرق سماويًا"
- "أريد لونًا أخضر داكنًا"
- "أريد صورة لغروب الشمس بلون برتقالي ساطع"
- "أريد لون الخزامى الطازج"
- "أريد لونًا أزرق داكنًا مثل لون المحيط"
ستلاحظ أنّ Gemini يردّ الآن بشروحات حوارية حول الألوان بالإضافة إلى قيم الأحمر والأخضر والأزرق المنسّقة بشكل متّسق. لقد وجّه طلب النظام النموذج اللغوي الكبير بشكل فعّال لتقديم نوع الردود التي تحتاج إليها.
جرِّب أيضًا أن تطلب منه محتوًى خارج سياق الألوان. على سبيل المثال، الأسباب الرئيسية لحروب الوردتين يجب أن تلاحظ اختلافًا عن الخطوة السابقة.
أهمية هندسة الطلبات لإنجاز مهام متخصّصة
تتطلّب طلبات النظام مهارة وفهمًا. وهي جزء أساسي من عملية دمج النماذج اللغوية الكبيرة (LLM) ويمكن أن تؤثر بشكل كبير في مدى فائدة النموذج لتطبيقك المحدّد. ما فعلته هنا هو شكل من أشكال هندسة الطلبات، أي تخصيص التعليمات لجعل النموذج يتصرف بطرق تناسب احتياجات تطبيقك.
تتضمّن هندسة الطلبات الفعّالة ما يلي:
- تحديد الدور بوضوح: تحديد الغرض من استخدام النموذج اللغوي الكبير
- تعليمات صريحة: توضيح الطريقة التي يجب أن يردّ بها النموذج اللغوي الكبير
- أمثلة ملموسة: عرض أمثلة على الردود الجيدة بدلاً من مجرد إخبار المستخدمين بها
- التعامل مع الحالات الهامشية: توجيه النموذج اللغوي الكبير بشأن كيفية التعامل مع السيناريوهات الغامضة
- مواصفات التنسيق: ضمان تنظيم الردود بطريقة موحَّدة وسهلة الاستخدام
إنّ طلب النظام الذي أنشأته يحوّل الإمكانات العامة في Gemini إلى مساعد متخصص في تفسير الألوان، ويقدّم ردودًا منسّقة خصيصًا لتلبية احتياجات تطبيقك. هذا نمط قوي يمكنك تطبيقه على العديد من المجالات والمهام المختلفة.
ما هي الخطوات التالية؟
في الخطوة التالية، ستستند إلى هذه الأساسيات من خلال إضافة تعريفات الدوال التي تتيح للنموذج اللغوي الكبير (LLM) اقتراح قيم RGB فحسب، بل استدعاء الدوال في تطبيقك لضبط اللون مباشرةً. يوضّح هذا كيف يمكن للنماذج اللغوية الكبيرة أن تسدّ الفجوة بين اللغة الطبيعية وميزات التطبيق الملموسة.
تحديد المشاكل وحلّها
مشاكل تحميل مواد العرض
في حال مواجهة أخطاء عند تحميل طلب النظام، يمكنك اتّباع الخطوات التالية:
- تأكَّد من أنّ
pubspec.yamlيسرد دليل مواد العرض بشكل صحيح - تأكَّد من أنّ المسار في
rootBundle.loadString()يتطابق مع موقع ملفك - تشغيل
flutter cleanمتبوعًا بـflutter pub getلإعادة تحميل حزمة مواد العرض
الردود غير المتسقة
إذا لم تلتزم النماذج اللغوية الكبيرة بشكلٍ متّسق بتعليمات التنسيق، اتّبِع الخطوات التالية:
- محاولة توضيح متطلبات التنسيق بشكل أكبر في طلب النظام
- إضافة المزيد من الأمثلة لتوضيح النمط المتوقّع
- تأكَّد من أنّ التنسيق الذي تطلبه مناسب للنموذج
الحدّ من معدّل استخدام واجهة برمجة التطبيقات
في حال مواجهة أخطاء متعلّقة بالحدّ الأقصى لعدد الطلبات في الدقيقة، اتّبِع الخطوات التالية:
- يُرجى العِلم أنّ خدمة Firebase AI Logic لها حدود استخدام
- ننصحك بتنفيذ منطق إعادة المحاولة باستخدام خوارزمية الرقود الأسي الثنائي
- التحقّق من عدم وجود أي مشاكل متعلّقة بالحصة في "وحدة تحكّم Firebase"
المفاهيم الرئيسية التي تم تعلّمها
- فهم دور وأهمية طلبات النظام في تطبيقات النماذج اللغوية الكبيرة
- صياغة طلبات فعّالة تتضمّن تعليمات وأمثلة وقيودًا واضحة
- تحميل طلبات النظام واستخدامها في تطبيق Flutter
- توجيه سلوك النماذج اللغوية الكبيرة لتنفيذ مهام خاصة بمجال معيّن
- استخدام هندسة الطلبات لتشكيل ردود النماذج اللغوية الكبيرة
توضّح هذه الخطوة كيف يمكنك تخصيص سلوك النموذج اللغوي الكبير بشكل كبير بدون تغيير الرمز، وذلك ببساطة من خلال تقديم تعليمات واضحة في طلب النظام.
5- بيانات الدوال لأدوات النموذج اللغوي الكبير
في هذه الخطوة، ستبدأ عملية السماح لـ Gemini باتّخاذ إجراءات في تطبيقك من خلال تنفيذ تعريفات الدوال. تتيح هذه الميزة الفعّالة للنموذج اللغوي الكبير اقتراح قيم RGB وضبطها في واجهة مستخدم تطبيقك من خلال طلبات الأدوات المتخصصة. ومع ذلك، سيتطلّب ذلك الخطوة التالية للاطّلاع على طلبات النموذج اللغوي الكبير التي تم تنفيذها في تطبيق Flutter.
ما ستتعرّف عليه في هذه الخطوة
- التعرّف على ميزة "استدعاء الدوال" في النماذج اللغوية الكبيرة ومزاياها لتطبيقات Flutter
- تحديد تعريفات الدوال المستندة إلى المخطط لـ Gemini
- دمج تعريفات الدوال مع نموذج Gemini
- تعديل الطلب من النظام للاستفادة من إمكانات الأدوات
التعرّف على ميزة "استدعاء الدالة"
قبل تنفيذ تعريفات الدوال، دعونا نفهم ماهيتها وأهميتها:
ما هي ميزة "استدعاء الدالة"؟
تتيح ميزة "استدعاء الدوال" (المعروفة أحيانًا باسم "استخدام الأدوات") للنماذج اللغوية الكبيرة إجراء ما يلي:
- التعرّف على الحالات التي يمكن فيها الاستفادة من استدعاء وظيفة معيّنة عند تلقّي طلب من المستخدم
- إنشاء عنصر JSON منظَّم يتضمّن المَعلمات اللازمة لهذه الدالة
- اسمح لتطبيقك بتنفيذ الدالة مع هذه المَعلمات
- تلقّي نتيجة الدالة ودمجها في الردّ
بدلاً من أن يكتفي النموذج اللغوي الكبير بوصف الإجراءات المطلوب تنفيذها، تتيح ميزة "استدعاء الدوال البرمجية" للنموذج اللغوي الكبير تنفيذ إجراءات ملموسة في تطبيقك.
أهمية ميزة "استدعاء الدوال" لتطبيقات Flutter
تتيح ميزة "استدعاء الدوال" إنشاء جسر قوي بين اللغة الطبيعية وميزات التطبيق:
- الإجراء المباشر: يمكن للمستخدمين وصف ما يريدونه بلغة طبيعية، ويستجيب التطبيق بإجراءات ملموسة
- الناتج المنظَّم: ينتج النموذج اللغوي الكبير بيانات منظَّمة وواضحة بدلاً من نص يحتاج إلى تحليل.
- العمليات المعقّدة: تتيح للنموذج اللغوي الكبير الوصول إلى البيانات الخارجية أو إجراء العمليات الحسابية أو تعديل حالة التطبيق.
- تجربة مستخدم أفضل: إنشاء تكامل سلس بين المحادثة والوظائف
في تطبيق Colorist، تتيح ميزة "استدعاء الدوال" للمستخدمين قول "أريد لونًا أخضر غامقًا"، وسيتم تعديل واجهة المستخدم على الفور بهذا اللون، بدون الحاجة إلى تحليل قيم RGB من النص.
تحديد تعريفات الدوال
أنشئ ملفًا جديدًا lib/services/gemini_tools.dart لتحديد تعريفات الدوال:
lib/services/gemini_tools.dart
import 'package:firebase_ai/firebase_ai.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'gemini_tools.g.dart';
class GeminiTools {
GeminiTools(this.ref);
final Ref ref;
FunctionDeclaration get setColorFuncDecl => FunctionDeclaration(
'set_color',
'Set the color of the display square based on red, green, and blue values.',
parameters: {
'red': Schema.number(description: 'Red component value (0.0 - 1.0)'),
'green': Schema.number(description: 'Green component value (0.0 - 1.0)'),
'blue': Schema.number(description: 'Blue component value (0.0 - 1.0)'),
},
);
List<Tool> get tools => [
Tool.functionDeclarations([setColorFuncDecl]),
];
}
@Riverpod(keepAlive: true)
GeminiTools geminiTools(Ref ref) => GeminiTools(ref);
فهم تعريفات الدوال
لنستعرض بالتفصيل ما يفعله هذا الرمز:
- تسمية الدالة: يمكنك تسمية الدالة
set_colorللإشارة بوضوح إلى الغرض منها - وصف الدالة: يمكنك تقديم وصف واضح يساعد النموذج اللغوي الكبير في معرفة متى يجب استخدامه.
- تعريفات المَعلمات: يمكنك تحديد مَعلمات منظَّمة مع أوصافها الخاصة:
red: المكوّن الأحمر من النموذج اللوني أحمر أخضر أزرق، ويتم تحديده كرقم بين 0.0 و1.0green: المكوّن الأخضر من نموذج RGB، ويتم تحديده كرقم بين 0.0 و1.0-
blue: مكوّن اللون الأزرق في النموذج اللوني أحمر أخضر أزرق، ويتم تحديده كرقم بين 0.0 و1.0
- أنواع المخططات: يمكنك استخدام
Schema.number()للإشارة إلى أنّ هذه القيم عددية. - مجموعة الأدوات: يمكنك إنشاء قائمة أدوات تحتوي على تعريف الدالة.
يساعد هذا الأسلوب المنظَّم نموذج Gemini اللغوي الكبير في فهم ما يلي:
- الوقت الذي يجب فيه استدعاء هذه الدالة
- المَعلمات التي يجب توفيرها
- القيود التي تنطبق على هذه المَعلمات (مثل نطاق القيم)
تعديل موفّر نموذج Gemini
الآن، عدِّل ملف lib/providers/gemini.dart لتضمين تعريفات الدوال عند إعداد نموذج Gemini:
lib/providers/gemini.dart
import 'dart:async';
import 'package:firebase_ai/firebase_ai.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import '../firebase_options.dart';
import '../services/gemini_tools.dart'; // Add this import
import 'system_prompt.dart';
part 'gemini.g.dart';
@Riverpod(keepAlive: true)
Future<FirebaseApp> firebaseApp(Ref ref) =>
Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
@Riverpod(keepAlive: true)
Future<GenerativeModel> geminiModel(Ref ref) async {
await ref.watch(firebaseAppProvider.future);
final systemPrompt = await ref.watch(systemPromptProvider.future);
final geminiTools = ref.watch(geminiToolsProvider); // Add this line
final model = FirebaseAI.googleAI().generativeModel(
model: 'gemini-3.1-flash-lite',
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 في التعرّف على الدوال المتاحة له لاستدعائها.
تعديل الطلب من النظام
عليك الآن تعديل طلب النظام لإعطاء تعليمات للنموذج اللغوي الكبير بشأن استخدام أداة 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 - العملية المعدَّلة: يمكنك تغيير الخطوة 3 من "تنسيق القيم في الاستجابة" إلى "استخدام الأداة لضبط القيم".
- مثال معدَّل: توضّح كيف يجب أن يتضمّن الردّ طلب استخدام أداة بدلاً من نص منسَّق
- إزالة شرط التنسيق: بما أنّك تستخدم استدعاءات الدوال المنظَّمة، لم يعُد عليك الالتزام بتنسيق نص معيّن.
يوجه هذا الطلب المعدَّل النموذج اللغوي الكبير إلى استخدام ميزة "استدعاء الدوال" بدلاً من مجرد تقديم قيم RGB في شكل نصي.
إنشاء رمز Riverpod
نفِّذ أمر أداة إنشاء الإصدار لإنشاء رمز Riverpod المطلوب:
dart run build_runner build --delete-conflicting-outputs
تشغيل التطبيق
في هذه المرحلة، سينشئ Gemini محتوًى يحاول استخدام ميزة "استدعاء الدوال البرمجية"، ولكن لم يتم بعد تنفيذ معالجات لعمليات استدعاء الدوال البرمجية. عند تشغيل التطبيق ووصف لون، سيردّ Gemini كما لو أنّه استدعى أداة، ولكن لن تظهر أي تغييرات في الألوان في واجهة المستخدم حتى الخطوة التالية.
شغِّل تطبيقك:
flutter run -d DEVICE

جرِّب وصف لون مثل "أزرق المحيط العميق" أو "أخضر الغابة" ولاحظ الردود. تحاول نماذج اللغات الكبيرة استدعاء الدوال المحدّدة أعلاه، ولكن لم يرصد الرمز البرمجي استدعاءات الدوال بعد.
عملية استدعاء الدالة
لنتعرّف على ما يحدث عندما تستخدم Gemini ميزة "استدعاء الدوال":
- اختيار الدالة: يقرّر النموذج اللغوي الكبير ما إذا كان استدعاء الدالة سيكون مفيدًا استنادًا إلى طلب المستخدم.
- إنشاء المَعلمات: ينشئ النموذج اللغوي الكبير قيم المَعلمات التي تتناسب مع مخطط الدالة.
- تنسيق استدعاء الدالة: يرسل النموذج اللغوي الكبير كائنًا منظَّمًا لاستدعاء الدالة في رده.
- معالجة التطبيق: سيتلقّى تطبيقك هذا الاستدعاء وينفّذ الوظيفة ذات الصلة (التي سيتم تنفيذها في الخطوة التالية).
- دمج الردود: في المحادثات المتعددة الأدوار، يتوقّع النموذج اللغوي الكبير أن يتم عرض نتيجة الدالة.
في الحالة الحالية لتطبيقك، تحدث الخطوات الثلاث الأولى، ولكن لم تنفّذ بعد الخطوة 4 أو 5 (التعامل مع طلبات الدوال)، وهو ما ستفعله في الخطوة التالية.
التفاصيل الفنية: كيف يقرّر Gemini متى يستخدم الدوال؟
يتخذ Gemini قرارات ذكية بشأن وقت استخدام الوظائف استنادًا إلى ما يلي:
- نية المستخدم: ما إذا كان سيتم تلبية طلب المستخدم على أفضل وجه من خلال إحدى الدوال
- مدى صلة الوظيفة بالمهمة: مدى تطابق الوظائف المتاحة مع المهمة
- توفّر المَعلمات: ما إذا كان بإمكانها تحديد قيم المَعلمات بدقة
- تعليمات النظام: إرشادات من طلب النظام حول استخدام الدوال
من خلال تقديم تعريفات واضحة للدوال وتعليمات النظام، تكون قد أعددت Gemini للتعرّف على طلبات وصف الألوان كفرص لاستدعاء الدالة set_color.
ما هي الخطوات التالية؟
في الخطوة التالية، ستنفّذ معالجات لعمليات استدعاء الدوال الواردة من Gemini. سيؤدي ذلك إلى إكمال الدائرة، ما يسمح لأوصاف المستخدمين بتفعيل تغييرات فعلية في الألوان في واجهة المستخدم من خلال استدعاءات الدوال في النموذج اللغوي الكبير.
تحديد المشاكل وحلّها
مشاكل في تعريف الدوال
في حال مواجهة أخطاء في تعريفات الدوال، يمكنك اتّباع الخطوات التالية:
- التأكّد من أنّ أسماء المَعلمات وأنواعها تتطابق مع ما هو متوقّع
- التأكّد من أنّ اسم الدالة واضح ومعبّر
- التأكّد من أنّ وصف الدالة يوضّح الغرض منها بدقة
مشاكل في تلقين النظام
إذا لم تحاول النماذج اللغوية الكبيرة استخدام الدالة:
- تأكَّد من أنّ طلب النظام يوجّه النموذج اللغوي الكبير بوضوح إلى استخدام أداة
set_color - تأكَّد من أنّ المثال في طلب النظام يوضّح كيفية استخدام الدالة
- حاوِل أن تكون التعليمات أكثر وضوحًا بشأن كيفية استخدام الأداة
مشاكل عامة
في حال مواجهة مشاكل أخرى:
- التحقّق من وحدة التحكّم بحثًا عن أي أخطاء متعلّقة بتعريفات الدوال
- تأكَّد من تمرير الأدوات إلى النموذج بشكل سليم.
- التأكّد من أنّ جميع الرموز البرمجية التي تم إنشاؤها في Riverpod محدَّثة
المفاهيم الرئيسية التي تم تعلّمها
- تحديد تعريفات الدوال لتوسيع إمكانات النماذج اللغوية الكبيرة في تطبيقات Flutter
- إنشاء مخططات المَعلمات لجمع البيانات المنظَّمة
- دمج تعريفات الدوال مع نموذج Gemini
- تعديل طلبات النظام لتشجيع استخدام الوظائف
- فهم كيفية اختيار النماذج اللغوية الكبيرة واستدعاء الدوال
توضّح هذه الخطوة كيف يمكن لنماذج اللغات الكبيرة سدّ الفجوة بين إدخال اللغة الطبيعية واستدعاء الدوال المنظَّمة، ما يمهّد الطريق للتكامل السلس بين المحادثة وميزات التطبيق.
6. تنفيذ معالجة الأدوات
في هذه الخطوة، ستنفّذ معالجات لطلبات الدوال الواردة من Gemini. بهذا يكتمل مسار التواصل بين المدخلات المكتوبة بلغة طبيعية وميزات التطبيق الملموسة، ما يسمح للنموذج اللغوي الكبير بالتعامل مباشرةً مع واجهة المستخدم استنادًا إلى أوصاف المستخدمين.
ما ستتعرّف عليه في هذه الخطوة
- فهم مسار استدعاء الدوال الكامل في تطبيقات النماذج اللغوية الكبيرة
- معالجة طلبات استدعاء الدوال من Gemini في تطبيق Flutter
- تنفيذ معالجات الدوال التي تعدّل حالة التطبيق
- معالجة ردود الدوال وإرجاع النتائج إلى النموذج اللغوي الكبير
- إنشاء تدفق تواصل كامل بين النموذج اللغوي الكبير وواجهة المستخدم
- تسجيل طلبات الوظائف وردودها لضمان الشفافية
فهم مسار استدعاء الدوال
قبل الخوض في عملية التنفيذ، دعنا نتعرّف على مسار عملية استدعاء الدوال الكامل:
سير العمل الكامل
- إدخال المستخدم: يصف المستخدم لونًا بلغة طبيعية (مثل "أخضر غامق")
- معالجة النموذج اللغوي الكبير: يحلّل Gemini الوصف ويقرّر استدعاء الدالة
set_color - إنشاء طلبات الدوال: ينشئ Gemini ملف JSON منظَّمًا يتضمّن مَعلمات (قيم الأحمر والأخضر والأزرق)
- تلقّي استدعاء الدالة: يتلقّى تطبيقك هذه البيانات المنظَّمة من Gemini
- تنفيذ الدالة: ينفّذ تطبيقك الدالة باستخدام المَعلمات المقدَّمة
- تعديل الحالة: تعدّل الدالة حالة تطبيقك (تغيير اللون المعروض).
- إنشاء الرد: تعرض الدالة النتائج مرة أخرى للنموذج اللغوي الكبير
- دمج الردود: يدمج النموذج اللغوي الكبير هذه النتائج في ردّه النهائي
- تعديل واجهة المستخدم: تتفاعل واجهة المستخدم مع تغيير الحالة، وتعرض اللون الجديد
تُعدّ دورة التواصل الكاملة ضرورية لدمج النماذج اللغوية الكبيرة بشكل سليم. عندما ينفّذ نموذج لغوي كبير استدعاء دالة، لا يرسل الطلب ببساطة وينتقل إلى الخطوة التالية. بدلاً من ذلك، ينتظر أن ينفّذ تطبيقك الدالة ويعرض النتائج. بعد ذلك، يستخدم النموذج اللغوي الكبير هذه النتائج لصياغة الردّ النهائي، ما يؤدي إلى إنشاء سير محادثة طبيعي يقرّ بالإجراءات المتّخذة.
تنفيذ معالِجات الدوال
لنحدّث ملف lib/services/gemini_tools.dart لإضافة معالِجات لطلبات الدوال:
lib/services/gemini_tools.dart
import 'package:colorist_ui/colorist_ui.dart';
import 'package:firebase_ai/firebase_ai.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'gemini_tools.g.dart';
class GeminiTools {
GeminiTools(this.ref);
final Ref ref;
FunctionDeclaration get setColorFuncDecl => FunctionDeclaration(
'set_color',
'Set the color of the display square based on red, green, and blue values.',
parameters: {
'red': Schema.number(description: 'Red component value (0.0 - 1.0)'),
'green': Schema.number(description: 'Green component value (0.0 - 1.0)'),
'blue': Schema.number(description: 'Blue component value (0.0 - 1.0)'),
},
);
List<Tool> get tools => [
Tool.functionDeclarations([setColorFuncDecl]),
];
Map<String, Object?> handleFunctionCall( // Add from here
String functionName,
Map<String, Object?> arguments,
) {
final logStateNotifier = ref.read(logStateProvider.notifier);
logStateNotifier.logFunctionCall(functionName, arguments);
return switch (functionName) {
'set_color' => handleSetColor(arguments),
_ => handleUnknownFunction(functionName),
};
}
Map<String, Object?> handleSetColor(Map<String, Object?> arguments) {
final colorStateNotifier = ref.read(colorStateProvider.notifier);
final red = (arguments['red'] as num).toDouble();
final green = (arguments['green'] as num).toDouble();
final blue = (arguments['blue'] as num).toDouble();
final functionResults = {
'success': true,
'current_color': colorStateNotifier
.updateColor(red: red, green: green, blue: blue)
.toLLMContextMap(),
};
final logStateNotifier = ref.read(logStateProvider.notifier);
logStateNotifier.logFunctionResults(functionResults);
return functionResults;
}
Map<String, Object?> handleUnknownFunction(String functionName) {
final logStateNotifier = ref.read(logStateProvider.notifier);
logStateNotifier.logWarning('Unsupported function call $functionName');
return {
'success': false,
'reason': 'Unsupported function call $functionName',
};
} // To here.
}
@Riverpod(keepAlive: true)
GeminiTools geminiTools(Ref ref) => GeminiTools(ref);
فهم معالجات الدوال
في ما يلي تفصيل لما تفعله معالجات الدوال هذه:
-
handleFunctionCall: هو أداة إرسال مركزية تعمل على:- تسجيل استدعاء الدالة لتحقيق الشفافية في لوحة السجلّ
- توجيه الطلبات إلى المعالج المناسب استنادًا إلى اسم الدالة
- إرجاع ردّ منظَّم سيتم إرساله إلى النموذج اللغوي الكبير
handleSetColor: المعالج المحدّد للدالةset_colorالذي:- تستخرج هذه الدالة قيم النموذج اللوني أحمر أخضر أزرق (RGB) من خريطة الوسيطات.
- تحويلها إلى الأنواع المتوقّعة (أرقام مضاعفة)
- تعديل حالة لون التطبيق باستخدام
colorStateNotifier - إنشاء استجابة منظَّمة تتضمّن حالة النجاح ومعلومات الألوان الحالية
- تسجيل نتائج الدالة لتصحيح الأخطاء
-
handleUnknownFunction: معالج احتياطي للدوال غير المعروفة التي:- تسجيل تحذير بشأن الدالة غير المتوافقة
- عرض ردّ يتضمّن خطأً للنموذج اللغوي الكبير
تُعدّ وظيفة handleSetColor مهمة بشكل خاص لأنّها تسدّ الفجوة بين فهم اللغة الطبيعية في النموذج اللغوي الكبير والتغييرات الملموسة في واجهة المستخدم.
تعديل خدمة Gemini Chat لمعالجة طلبات الوظائف والردود
الآن، لنعدّل الملف 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_ai/firebase_ai.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import '../providers/gemini.dart';
import 'gemini_tools.dart'; // Add this import
part 'gemini_chat_service.g.dart';
class GeminiChatService {
GeminiChatService(this.ref);
final Ref ref;
Future<void> sendMessage(String message) async {
final chatSession = await ref.read(chatSessionProvider.future);
final chatStateNotifier = ref.read(chatStateProvider.notifier);
final logStateNotifier = ref.read(logStateProvider.notifier);
chatStateNotifier.addUserMessage(message);
logStateNotifier.logUserText(message);
final llmMessage = chatStateNotifier.createLlmMessage();
try {
final response = await chatSession.sendMessage(Content.text(message));
final responseText = response.text;
if (responseText != null) {
logStateNotifier.logLlmText(responseText);
chatStateNotifier.appendToMessage(llmMessage.id, responseText);
}
if (response.functionCalls.isNotEmpty) { // Add from here
final geminiTools = ref.read(geminiToolsProvider);
final functionResultResponse = await chatSession.sendMessage(
Content.functionResponses([
for (final functionCall in response.functionCalls)
FunctionResponse(
functionCall.name,
geminiTools.handleFunctionCall(
functionCall.name,
functionCall.args,
),
),
]),
);
final responseText = functionResultResponse.text;
if (responseText != null) {
logStateNotifier.logLlmText(responseText);
chatStateNotifier.appendToMessage(llmMessage.id, responseText);
}
} // To here.
} catch (e, st) {
logStateNotifier.logError(e, st: st);
chatStateNotifier.appendToMessage(
llmMessage.id,
"\nI'm sorry, I encountered an error processing your request. "
"Please try again.",
);
} finally {
chatStateNotifier.finalizeMessage(llmMessage.id);
}
}
}
@Riverpod(keepAlive: true)
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);
}
}
هذا الرمز:
- تتحقّق هذه السمة مما إذا كان ردّ النموذج اللغوي الكبير يتضمّن أي طلبات وظائف.
- في كل مرة يتم فيها استدعاء دالة، يتم استدعاء طريقتك
handleFunctionCallمع اسم الدالة ووسيطاتها - تجمع نتائج كل استدعاء دالة
- إعادة هذه النتائج إلى النموذج اللغوي الكبير باستخدام
Content.functionResponses - معالجة ردّ النموذج اللغوي الكبير على نتائج الدالة
- تعديل واجهة المستخدم باستخدام نص الردّ النهائي
يؤدي ذلك إلى إنشاء سير عمل ذهاب وإياب:
- المستخدم → النموذج اللغوي الكبير: يطلب لونًا
- النموذج اللغوي الكبير → التطبيق: استدعاء الدوال مع المَعلمات
- التطبيق → المستخدم: تم عرض لون جديد
- التطبيق → النموذج اللغوي الكبير: نتائج الدالة
- النموذج اللغوي الكبير → المستخدم: الردّ النهائي الذي يتضمّن نتائج الوظيفة
إنشاء رمز Riverpod
نفِّذ أمر أداة إنشاء الإصدار لإنشاء رمز Riverpod المطلوب:
dart run build_runner build --delete-conflicting-outputs
تشغيل المسار الكامل واختباره
شغِّل تطبيقك الآن:
flutter run -d DEVICE

جرِّب إدخال أوصاف مختلفة للألوان:
- "أريد لونًا أحمر قرمزيًا داكنًا"
- "أريد لونًا أزرق سماويًا مهدئًا"
- "أريد معرفة لون أوراق النعناع الطازجة"
- "أريد رؤية لون برتقالي دافئ عند غروب الشمس"
- "أريد لونًا أرجوانيًا ملكيًا غنيًا"
من المفترض أن يظهر لك الآن ما يلي:
- ظهور رسالتك في واجهة المحادثة
- ظهور ردّ Gemini في المحادثة
- تسجيل استدعاءات الدوال في لوحة السجلّ
- تسجيل نتائج الدالة فورًا بعد
- تعديل مستطيل الألوان لعرض اللون الموصوف
- تعديل قيم النموذج اللوني أحمر أخضر أزرق (RGB) لعرض مكونات اللون الجديد
- الردّ النهائي من Gemini الذي يظهر، وغالبًا ما يعلّق على اللون الذي تم ضبطه
تقدّم لوحة السجلّ إحصاءات حول ما يحدث في الخلفية. وسترى ما يلي:
- طلبات الدوال التي يجريها Gemini
- المَعلمات التي يختارها لكل قيمة RGB
- النتائج التي تعرضها الدالة
- ردود المتابعة من Gemini
The color state notifier
إنّ colorStateNotifier الذي تستخدمه لتعديل الألوان هو جزء من حزمة colorist_ui. وتدير ما يلي:
- اللون الحالي المعروض في واجهة المستخدم
- سجلّ الألوان (آخر 10 ألوان)
- إشعار بالتغييرات في حالة عناصر واجهة المستخدم
عند استدعاء updateColor باستخدام قيم RGB جديدة، سيتم تنفيذ ما يلي:
- تُنشئ عنصر
ColorDataجديدًا بالقيم المقدَّمة - تعديل اللون الحالي في حالة التطبيق
- تتم إضافة اللون إلى السجلّ
- تفعيل تعديلات واجهة المستخدم من خلال إدارة الحالة في Riverpod
تراقب مكوّنات واجهة المستخدم في حزمة colorist_ui هذه الحالة ويتم تعديلها تلقائيًا عند تغيُّرها، ما يؤدي إلى إنشاء تجربة تفاعلية.
فهم معالجة الأخطاء
يتضمّن التنفيذ إجراءات فعالة لمعالجة الأخطاء:
- كتلة try-catch: تغلف جميع التفاعلات مع النماذج اللغوية الكبيرة (LLM) لرصد أي استثناءات
- تسجيل الأخطاء: تسجيل الأخطاء في لوحة السجلّ مع عمليات تتبُّع تسلسل استدعاء الدوال البرمجية
- ملاحظات المستخدم: تقديم رسالة خطأ سهلة الفهم في المحادثة
- تنظيف الحالة: يتم وضع اللمسات الأخيرة على حالة الرسالة حتى في حال حدوث خطأ.
يضمن ذلك بقاء التطبيق ثابتًا وتقديم ملاحظات مناسبة حتى عند حدوث مشاكل في خدمة النموذج اللغوي الكبير أو تنفيذ الوظائف.
فاعلية ميزة "استدعاء الدوال" في تحسين تجربة المستخدم
توضّح الإنجازات التي حققتها هنا كيف يمكن للنماذج اللغوية الكبيرة إنشاء واجهات طبيعية فعّالة:
- واجهة اللغة الطبيعية: يعبّر المستخدمون عن نيتهم بلغة يومية
- التفسير الذكي: يترجم النموذج اللغوي الكبير الأوصاف الغامضة إلى قيم دقيقة
- التلاعب المباشر: يتم تعديل واجهة المستخدم استجابةً للغة الطبيعية
- الردود السياقية: يقدّم النموذج اللغوي الكبير سياقًا حواريًا حول التغييرات
- الحدّ من الجهد الذهني: لا يحتاج المستخدمون إلى فهم قيم الأحمر والأخضر والأزرق أو نظرية الألوان
يمكن توسيع نطاق هذا النمط من استخدام ميزة "استدعاء الدوال" في النماذج اللغوية الكبيرة لتشمل مجالات أخرى لا حصر لها تتجاوز اختيار الألوان، وذلك بهدف الربط بين اللغة الطبيعية وإجراءات واجهة المستخدم.
ما هي الخطوات التالية؟
في الخطوة التالية، ستحسّن تجربة المستخدم من خلال تنفيذ الردود المتدفّقة. بدلاً من انتظار الرد الكامل، ستتم معالجة أجزاء النص واستدعاءات الدوال عند تلقّيها، ما يؤدي إلى إنشاء تطبيق أكثر استجابة وتفاعلية.
تحديد المشاكل وحلّها
مشاكل استدعاء الدالة
إذا كان Gemini لا يستدعي الدوال أو كانت المَعلمات غير صحيحة:
- التأكّد من أنّ بيان الوظيفة يتطابق مع ما هو موصوف في طلب النظام
- التأكّد من أنّ أسماء المَعلمات وأنواعها متطابقة
- التأكّد من أنّ طلب النظام يوجّه نموذج اللغة الكبير بشكل صريح إلى استخدام الأداة
- تأكَّد من أنّ اسم الدالة في المعالج يتطابق تمامًا مع ما هو وارد في البيان
- فحص لوحة السجلّ للحصول على معلومات تفصيلية حول استدعاءات الدوال
مشاكل في استجابة الدالة
إذا لم يتم إرسال نتائج الدالة بشكلٍ صحيح إلى النموذج اللغوي الكبير:
- تأكَّد من أنّ الدالة تعرض خريطة منسَّقة بشكل صحيح
- التأكّد من إنشاء Content.functionResponses بشكلٍ صحيح
- ابحث عن أي أخطاء في السجلّ مرتبطة بردود الدوال
- تأكَّد من استخدام جلسة المحادثة نفسها للردّ.
مشاكل في عرض الألوان
إذا لم يتم عرض الألوان بشكل صحيح، اتّبِع الخطوات التالية:
- التأكّد من تحويل قيم RGB بشكلٍ صحيح إلى أرقام مضاعفة (قد يرسلها النموذج اللغوي الكبير كأعداد صحيحة)
- تأكَّد من أنّ القيم تقع ضمن النطاق المتوقّع (من 0.0 إلى 1.0).
- تأكَّد من استدعاء أداة إشعار حالة اللون بشكل صحيح
- فحص السجلّ بحثًا عن القيم الدقيقة التي يتم تمريرها إلى الدالة
المشاكل العامة
بالنسبة إلى المشاكل العامة:
- فحص السجلات بحثًا عن أخطاء أو تحذيرات
- التأكّد من ربط Firebase AI Logic
- التحقّق من عدم تطابق الأنواع في مَعلمات الدالة
- التأكّد من أنّ جميع الرموز البرمجية التي تم إنشاؤها في Riverpod محدَّثة
المفاهيم الرئيسية التي تم تعلّمها
- تنفيذ مسار كامل لاستدعاء الدوال في Flutter
- إنشاء تواصل كامل بين نموذج لغوي كبير وتطبيقك
- معالجة البيانات المنظَّمة من ردود النماذج اللغوية الكبيرة
- إرسال نتائج الدالة إلى النموذج اللغوي الكبير لدمجها في الردود
- استخدام لوحة السجلّ للحصول على معلومات عن التفاعلات بين التطبيقات ونماذج اللغات الكبيرة
- ربط إدخالات اللغة الطبيعية بتغييرات ملموسة في واجهة المستخدم
بعد إكمال هذه الخطوة، سيعرض تطبيقك الآن أحد أقوى أنماط دمج النماذج اللغوية الكبيرة: ترجمة مدخلات اللغة الطبيعية إلى إجراءات ملموسة في واجهة المستخدم، مع الحفاظ على محادثة متماسكة تعترف بهذه الإجراءات. يؤدي ذلك إلى إنشاء واجهة محادثة سهلة الاستخدام تبدو ساحرة للمستخدمين.
7. بث الردود لتحسين تجربة المستخدم
في هذه الخطوة، ستحسّن تجربة المستخدم من خلال تنفيذ الردود المتدفقة من Gemini. بدلاً من انتظار إنشاء الردّ بأكمله، ستتم معالجة أجزاء النص واستدعاءات الدوال عند تلقّيها، ما يؤدي إلى إنشاء تطبيق أكثر استجابة وتفاعلية.
المواضيع التي سنتناولها في هذه الخطوة
- أهمية البث المباشر للتطبيقات المستندة إلى نماذج اللغات الكبيرة
- تنفيذ الردود التدريجية من النماذج اللغوية الكبيرة في تطبيق Flutter
- معالجة أجزاء النص عند وصولها من واجهة برمجة التطبيقات
- إدارة حالة المحادثة لتجنُّب تعارض الرسائل
- التعامل مع طلبات تنفيذ الدوال في الاستجابات المتدفقة
- إنشاء مؤشرات مرئية للردود قيد التقدم
أهمية البث المباشر لتطبيقات النماذج اللغوية الكبيرة
قبل البدء في التنفيذ، دعونا نفهم سبب أهمية عرض الردود أثناء بثها لتقديم تجارب مستخدم ممتازة باستخدام النماذج اللغوية الكبيرة:
تجربة مستخدم محسّنة
توفّر الردود المتدفّقة العديد من المزايا المهمة لتجربة المستخدم:
- تقليل وقت الاستجابة المُدرَك: يرى المستخدمون النص يبدأ في الظهور على الفور (عادةً في غضون 100 إلى 300 ملي ثانية)، بدلاً من الانتظار لعدة ثوانٍ للحصول على رد كامل. ويؤدي هذا الشعور بالاستجابة الفورية إلى تحسين رضا المستخدمين بشكل كبير.
- إيقاع المحادثة الطبيعي: يتيح ظهور النص تدريجيًا محاكاة طريقة تواصل البشر، ما يوفّر تجربة حوار أكثر طبيعية.
- معالجة المعلومات بشكل تدريجي: يمكن للمستخدمين البدء في معالجة المعلومات فور وصولها، بدلاً من أن يواجهوا كمية كبيرة من النصوص في وقت واحد.
- فرصة التدخّل مبكرًا: في التطبيق الكامل، يمكن للمستخدمين التدخّل أو إعادة توجيه النموذج اللغوي الكبير إذا لاحظوا أنّه يتّجه في اتجاه غير مفيد.
- تأكيد مرئي للنشاط: يقدّم النص المتدفّق ملاحظات فورية بأنّ النظام يعمل، ما يقلّل من حالة عدم اليقين.
المزايا الفنية
بالإضافة إلى تحسين تجربة المستخدم، يقدّم البث مزايا فنية:
- تنفيذ الوظيفة مبكرًا: يمكن رصد طلبات الوظائف وتنفيذها فور ظهورها في مصدر البيانات، بدون انتظار الرد الكامل.
- تحديثات تدريجية لواجهة المستخدم: يمكنك تعديل واجهة المستخدم بشكل تدريجي عند وصول معلومات جديدة، ما يؤدي إلى إنشاء تجربة أكثر ديناميكية.
- إدارة حالة المحادثة: يوفّر البث إشارات واضحة حول اكتمال الردود أو استمرارها، ما يتيح إدارة الحالة بشكل أفضل.
- تقليل مخاطر انتهاء المهلة: مع الردود غير المتدفقة، تكون عمليات الإنشاء الطويلة عُرضة لانتهاء مهلة الاتصال. تنشئ عملية البث الاتصال مبكرًا وتحافظ عليه.
بالنسبة إلى تطبيق 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_ai/firebase_ai.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';
class ConversationStateNotifier extends Notifier<ConversationState> { // Add from here...
@override
ConversationState build() => ConversationState.idle;
void busy() {
state = ConversationState.busy;
}
void idle() {
state = ConversationState.idle;
}
}
final conversationStateProvider =
NotifierProvider<ConversationStateNotifier, ConversationState>(
ConversationStateNotifier.new,
); // To here.
class GeminiChatService {
GeminiChatService(this.ref);
final Ref ref;
Future<void> sendMessage(String message) async {
final chatSession = await ref.read(chatSessionProvider.future);
final conversationState = ref.read(conversationStateProvider); // Add this line
final chatStateNotifier = ref.read(chatStateProvider.notifier);
final logStateNotifier = ref.read(logStateProvider.notifier);
if (conversationState == ConversationState.busy) { // Add from here...
logStateNotifier.logWarning(
"Can't send a message while a conversation is in progress",
);
throw Exception(
"Can't send a message while a conversation is in progress",
);
}
final conversationStateNotifier = ref.read(
conversationStateProvider.notifier,
);
conversationStateNotifier.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.idle(); // Add this line.
}
}
Future<void> _processBlock( // Add from here...
GenerateContentResponse block,
String llmMessageId,
) async {
final chatSession = await ref.read(chatSessionProvider.future);
final chatStateNotifier = ref.read(chatStateProvider.notifier);
final logStateNotifier = ref.read(logStateProvider.notifier);
final blockText = block.text;
if (blockText != null) {
logStateNotifier.logLlmText(blockText);
chatStateNotifier.appendToMessage(llmMessageId, blockText);
}
if (block.functionCalls.isNotEmpty) {
final geminiTools = ref.read(geminiToolsProvider);
final responseStream = chatSession.sendMessageStream(
Content.functionResponses([
for (final functionCall in block.functionCalls)
FunctionResponse(
functionCall.name,
geminiTools.handleFunctionCall(
functionCall.name,
functionCall.args,
),
),
]),
);
await for (final response in responseStream) {
final responseText = response.text;
if (responseText != null) {
logStateNotifier.logLlmText(responseText);
chatStateNotifier.appendToMessage(llmMessageId, responseText);
}
}
}
} // To here.
}
@Riverpod(keepAlive: true)
GeminiChatService geminiChatService(Ref ref) => GeminiChatService(ref);
فهم عملية تنفيذ البث
لنستعرض بالتفصيل ما يفعله هذا الرمز:
- تتبُّع حالة المحادثة:
- يتتبّع
conversationStateProviderما إذا كان التطبيق يعالج حاليًا ردًا - تنتقل الحالة من
idle→busyأثناء المعالجة، ثم تعود إلىidle - يمنع ذلك إرسال طلبات متزامنة متعددة قد تتعارض مع بعضها البعض.
- يتتبّع
- بدء البث:
- تعرض الدالة
sendMessageStream()"دفقًا" من أجزاء الرد بدلاً منFutureمع الرد الكامل - قد تحتوي كل قطعة على نص أو طلبات وظائف أو كليهما
- تعرض الدالة
- المعالجة التقدّمية:
- تعالج
await forكل جزء فور وصوله في الوقت الفعلي - تتم إضافة النص إلى واجهة المستخدم على الفور، ما يؤدي إلى إنشاء تأثير البث
- يتم تنفيذ طلبات الدوال فور رصدها
- تعالج
- التعامل مع طلبات تنفيذ الدوال:
- عند رصد طلب تنفيذ دالة في جزء من الرمز، يتم تنفيذه على الفور.
- يتم إرسال النتائج مرة أخرى إلى النموذج اللغوي الكبير من خلال مكالمة بث أخرى.
- تتم أيضًا معالجة ردّ النموذج اللغوي الكبير على هذه النتائج بطريقة البث المباشر
- التعامل مع الأخطاء وتنظيف البيانات:
try/catchتوفّر معالجة فعّالة للأخطاء- يضمن الحظر
finallyإعادة ضبط حالة المحادثة بشكل سليم - يتم دائمًا وضع اللمسات الأخيرة على الرسالة، حتى إذا حدثت أخطاء
يؤدي هذا التنفيذ إلى إنشاء تجربة بث سريعة الاستجابة وموثوقة مع الحفاظ على حالة المحادثة المناسبة.
تعديل الشاشة الرئيسية لربط حالة المحادثة
عدِّل ملف lib/main.dart لتمرير حالة المحادثة إلى الشاشة الرئيسية:
lib/main.dart
import 'package:colorist_ui/colorist_ui.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'providers/gemini.dart';
import 'services/gemini_chat_service.dart';
void main() async {
runApp(ProviderScope(child: MainApp()));
}
class MainApp extends ConsumerWidget {
const MainApp({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final model = ref.watch(geminiModelProvider);
final conversationState = ref.watch(conversationStateProvider); // Add this line
return MaterialApp(
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
),
home: model.when(
data: (data) => MainScreen(
conversationState: conversationState, // And this line
sendMessage: (text) {
ref.read(geminiChatServiceProvider).sendMessage(text);
},
),
loading: () => LoadingScreen(message: 'Initializing Gemini Model'),
error: (err, st) => ErrorScreen(error: err),
),
);
}
}
التغيير الرئيسي هنا هو تمرير conversationState إلى أداة MainScreen. سيستخدم MainScreen (الذي توفّره حزمة colorist_ui) هذه الحالة لإيقاف إدخال النص أثناء معالجة الردّ.
يؤدي ذلك إلى إنشاء تجربة مستخدم متماسكة يعكس فيها تصميم واجهة المستخدم الحالة الحالية للمحادثة.
إنشاء رمز Riverpod
نفِّذ أمر أداة إنشاء الإصدار لإنشاء رمز Riverpod المطلوب:
dart run build_runner build --delete-conflicting-outputs
تشغيل الردود التدريجية واختبارها
شغِّل تطبيقك:
flutter run -d DEVICE

جرِّب الآن اختبار سلوك البث باستخدام أوصاف ألوان مختلفة. جرِّب أوصافًا مثل:
- "أريد رؤية لون المحيط الأزرق الداكن عند الغسق"
- "أريد رؤية مرجان نابض بالحياة يذكّرني بالزهور الاستوائية"
- "أريد إنشاء لون أخضر زيتوني باهت مثل ملابس الجيش القديمة"
التسلسل الفني للبث المباشر بالتفصيل
لنلقِ نظرة على ما يحدث بالضبط عند بث ردّ:
إنشاء اتصال
عند الاتصال بالرقم sendMessageStream()، يحدث ما يلي:
- ينشئ التطبيق اتصالاً بخدمة Firebase AI Logic
- يتم إرسال طلب المستخدم إلى الخدمة
- يبدأ الخادم في معالجة الطلب
- يبقى اتصال البث مفتوحًا وجاهزًا لنقل الأجزاء
نقل الأجزاء
أثناء إنشاء Gemini للمحتوى، يتم إرسال أجزاء من خلال البث:
- يرسل الخادم أجزاء من النص أثناء إنشائها (عادةً بضع كلمات أو جمل)
- عندما يقرّر Gemini إجراء طلب استدعاء دالة، يرسل معلومات طلب استدعاء الدالة.
- قد تتبع استدعاءات الدوال أجزاء نصية إضافية
- يستمر البث إلى أن يكتمل إنشاء الفيديو
المعالجة التدريجية
يعالج تطبيقك كل جزء بشكل تدريجي:
- تتم إضافة كل جزء من النص إلى الرد الحالي
- يتم تنفيذ طلبات الدوال فور رصدها
- يتم تعديل واجهة المستخدم في الوقت الفعلي باستخدام كلّ من النص ونتائج الوظائف
- يتم تتبُّع الحالة للإشارة إلى أنّ الردّ لا يزال قيد البث
إكمال البث
عند اكتمال عملية الإنشاء:
- أغلق الخادم البث
- سيتم إنهاء حلقة
await forبشكل طبيعي - تم وضع علامة "مكتملة" على الرسالة
- تمت إعادة ضبط حالة المحادثة إلى "غير نشطة"
- يتم تعديل واجهة المستخدم لتعكس الحالة المكتملة
مقارنة بين البث المباشر وغير المباشر
لفهم مزايا البث بشكل أفضل، لنقارن بين طرق البث والطرق الأخرى:
جانب | غير متوفّر للبث | البث |
وقت الاستجابة المحسوس | لا يرى المستخدم أي شيء إلى أن يصبح الردّ الكامل جاهزًا | يمكن للمستخدم رؤية الكلمات الأولى في غضون أجزاء من الثانية |
تجربة المستخدم | انتظار طويل يتبعه ظهور مفاجئ للنص | ظهور النص بشكل طبيعي وتدريجي |
إدارة الحالة | أبسط (الرسائل إما معلّقة أو مكتملة) | أكثر تعقيدًا (يمكن أن تكون الرسائل في حالة بث) |
تنفيذ الدالة | يحدث ذلك فقط بعد اكتمال الردّ | يحدث أثناء إنشاء الرد |
مدى تعقيد عملية التنفيذ | أسهل في التنفيذ | يتطلّب إدارة حالة إضافية |
استعادة الخطأ | ردّ الكل أو لا شيء | قد تظل الردود الجزئية مفيدة |
تعقيد الرمز | أقل تعقيدًا | أكثر تعقيدًا بسبب معالجة البث |
بالنسبة إلى تطبيق مثل Colorist، تفوق مزايا تجربة المستخدم الناتجة عن البث المباشر تعقيد التنفيذ، لا سيما بالنسبة إلى تفسيرات الألوان التي قد تستغرق عدة ثوانٍ لإنشائها.
أفضل الممارسات المتعلّقة بتجربة المستخدم في البث
عند تنفيذ البث في تطبيقات النماذج اللغوية الكبيرة الخاصة بك، ننصحك باتّباع أفضل الممارسات التالية:
- مؤشرات مرئية واضحة: يجب دائمًا توفير إشارات مرئية واضحة تميّز بين الرسائل المتدفقة والرسائل الكاملة
- حظر الإدخال: إيقاف إدخال المستخدم أثناء البث لمنع الطلبات المتعددة المتداخلة
- التعافي من الأخطاء: صمِّم واجهة المستخدم للتعامل مع عملية التعافي السلس في حال انقطاع البث
- عمليات الانتقال بين الحالات: التأكّد من سلاسة عمليات الانتقال بين حالات عدم النشاط والبث والاكتمال
- تصوّر التقدّم: استخدِم رسومات متحركة أو مؤشرات بسيطة تعرض المعالجة النشطة
- خيارات الإلغاء: في تطبيق كامل، يجب توفير طرق للمستخدمين لإلغاء عمليات الإنشاء الجارية
- دمج نتائج الدوال: تصميم واجهة المستخدم للتعامل مع نتائج الدوال التي تظهر أثناء المحادثة
- تحسين الأداء: تقليل عمليات إعادة إنشاء واجهة المستخدم أثناء تحديثات البث السريع
تنفِّذ حزمة colorist_ui العديد من أفضل الممارسات هذه نيابةً عنك، ولكنّها اعتبارات مهمة لأي عملية تنفيذ لنموذج لغوي كبير (LLM) للبث.
ما هي الخطوات التالية؟
في الخطوة التالية، ستنفّذ عملية مزامنة نموذج اللغة الكبير من خلال إرسال إشعار إلى Gemini عندما يختار المستخدمون ألوانًا من السجلّ. سيؤدي ذلك إلى إنشاء تجربة أكثر اتساقًا حيث تكون النماذج اللغوية الكبيرة على دراية بالتغييرات التي يجريها المستخدم على حالة التطبيق.
تحديد المشاكل وحلّها
مشاكل معالجة البث
في حال مواجهة مشاكل في معالجة البث:
- الأعراض: ردود جزئية أو نص مفقود أو إنهاء مفاجئ للبث
- الحلّ: تحقَّق من اتصال الشبكة وتأكَّد من صحة أنماط async/await في الرمز البرمجي
- بيانات التشخيص: افحص لوحة السجلّ بحثًا عن رسائل الخطأ أو التحذيرات المتعلّقة بمعالجة البث
- إصلاح: التأكّد من أنّ جميع عمليات معالجة البيانات المتدفقة تستخدم معالجة الأخطاء بشكل سليم مع حظر
try/catch
استدعاءات الدوال غير المتوفّرة
إذا لم يتم رصد استدعاءات الدوال في مصدر البيانات:
- الأعراض: يظهر النص ولكن لا يتم تعديل الألوان، أو لا يعرض السجلّ أي طلبات وظائف
- الحلّ: تحقَّق من تعليمات طلب النظام بشأن استخدام طلبات الدوال
- التشخيص: تحقَّق من لوحة السجلّ لمعرفة ما إذا كان يتم تلقّي طلبات الدوال.
- الحل: عدِّل تعليمات النظام لتوجيه النموذج اللغوي الكبير بشكل أكثر وضوحًا لاستخدام أداة
set_color
معالجة الأخطاء العامة
في حال حدوث أي مشاكل أخرى:
- الخطوة 1: التحقّق من لوحة السجلّ بحثًا عن رسائل الخطأ
- الخطوة 2: التحقّق من اتصال Firebase AI Logic
- الخطوة 3: التأكّد من أنّ جميع الرموز التي تم إنشاؤها باستخدام Riverpod هي الأحدث
- الخطوة 4: مراجعة عملية تنفيذ البث بحثًا عن أي عبارات await ناقصة
المفاهيم الرئيسية التي تم تعلّمها
- تنفيذ الردود المتدفقة باستخدام Gemini API للحصول على تجربة مستخدم أكثر استجابة
- إدارة حالة المحادثة للتعامل مع تفاعلات البث بشكل صحيح
- معالجة النصوص في الوقت الفعلي ومكالمات الوظائف عند وصولها
- إنشاء واجهات مستخدم متجاوبة يتم تعديلها بشكل تدريجي أثناء البث
- التعامل مع أحداث البث المتزامنة باستخدام أنماط غير متزامنة مناسبة
- تقديم ملاحظات مرئية مناسبة أثناء عرض الردود أثناء البث
من خلال تنفيذ البث، حسّنت بشكل كبير تجربة المستخدم في تطبيق Colorist، ما أدى إلى إنشاء واجهة أكثر استجابةً وجاذبيةً تبدو تفاعلية حقًا.
8. مزامنة السياق في النماذج اللغوية الكبيرة
في هذه الخطوة الإضافية، ستنفّذ عملية "مزامنة سياق النماذج اللغوية الكبيرة" من خلال إرسال إشعار إلى Gemini عندما يختار المستخدمون ألوانًا من السجلّ. يؤدي ذلك إلى إنشاء تجربة أكثر اتساقًا يكون فيها النموذج اللغوي الكبير على دراية بإجراءات المستخدم في الواجهة، وليس فقط برسائله الواضحة.
المواضيع التي سنتناولها في هذه الخطوة
- إنشاء مزامنة سياق نموذج لغوي كبير بين واجهة المستخدم والنموذج اللغوي الكبير
- تحويل أحداث واجهة المستخدم إلى سياق يمكن للنموذج اللغوي الكبير فهمه
- تعديل سياق المحادثة استنادًا إلى إجراءات المستخدم
- إنشاء تجربة متسقة على مستوى طرق التفاعل المختلفة
- تحسين إدراك السياق في النماذج اللغوية الكبيرة إلى ما هو أبعد من رسائل المحادثة الواضحة
فهم مزامنة السياق في النماذج اللغوية الكبيرة
تستجيب برامج الدردشة التقليدية فقط لرسائل المستخدمين الواضحة، ما يؤدي إلى حدوث انقطاع عند تفاعل المستخدمين مع التطبيق من خلال وسائل أخرى. تعالج ميزة "مزامنة سياق النموذج اللغوي الكبير" هذا القيد من خلال ما يلي:
أهمية مزامنة السياق في النماذج اللغوية الكبيرة
عندما يتفاعل المستخدمون مع تطبيقك من خلال عناصر واجهة المستخدِم (مثل اختيار لون من السجلّ)، لا يمكن للنموذج اللغوي الكبير معرفة ما حدث إلا إذا أخبرته بذلك صراحةً. مزامنة السياق في النماذج اللغوية الكبيرة:
- الحفاظ على السياق: إبقاء النموذج اللغوي الكبير على اطّلاع على جميع إجراءات المستخدم ذات الصلة
- إنشاء تجربة متماسكة: تقديم تجربة متماسكة حيث يدرك النموذج اللغوي الكبير التفاعلات مع واجهة المستخدم
- تعزيز الذكاء: يتيح للنموذج اللغوي الكبير الردّ بشكل مناسب على جميع إجراءات المستخدم
- تحسين تجربة المستخدم: يجعل التطبيق بأكمله يبدو أكثر تكاملاً واستجابةً
- تقليل الجهد الذي يبذله المستخدم: لا يحتاج المستخدمون إلى شرح إجراءات واجهة المستخدم يدويًا
في تطبيق Colorist، عندما يختار المستخدم لونًا من السجلّ، تريد أن يتعرّف Gemini على هذا الإجراء ويعلّق بذكاء على اللون المحدّد، مع الحفاظ على وهم المساعد السلس والواعي.
تعديل خدمة Gemini Chat لتلقّي إشعارات بشأن اختيار الألوان
أولاً، ستضيف طريقة إلى GeminiChatService لإعلام النموذج اللغوي الكبير عندما يختار المستخدم لونًا من السجلّ. عدِّل ملف 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_ai/firebase_ai.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';
class ConversationStateNotifier extends Notifier<ConversationState> {
@override
ConversationState build() => ConversationState.idle;
void busy() {
state = ConversationState.busy;
}
void idle() {
state = ConversationState.idle;
}
}
final conversationStateProvider =
NotifierProvider<ConversationStateNotifier, ConversationState>(
ConversationStateNotifier.new,
);
class GeminiChatService {
GeminiChatService(this.ref);
final Ref ref;
Future<void> notifyColorSelection(ColorData color) => sendMessage( // Add from here...
'User selected color from history: ${json.encode(color.toLLMContextMap())}',
); // To here.
Future<void> sendMessage(String message) async {
final chatSession = await ref.read(chatSessionProvider.future);
final conversationState = ref.read(conversationStateProvider);
final chatStateNotifier = ref.read(chatStateProvider.notifier);
final logStateNotifier = ref.read(logStateProvider.notifier);
if (conversationState == ConversationState.busy) {
logStateNotifier.logWarning(
"Can't send a message while a conversation is in progress",
);
throw Exception(
"Can't send a message while a conversation is in progress",
);
}
final conversationStateNotifier = ref.read(
conversationStateProvider.notifier,
);
conversationStateNotifier.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.idle();
}
}
Future<void> _processBlock(
GenerateContentResponse block,
String llmMessageId,
) async {
final chatSession = await ref.read(chatSessionProvider.future);
final chatStateNotifier = ref.read(chatStateProvider.notifier);
final logStateNotifier = ref.read(logStateProvider.notifier);
final blockText = block.text;
if (blockText != null) {
logStateNotifier.logLlmText(blockText);
chatStateNotifier.appendToMessage(llmMessageId, blockText);
}
if (block.functionCalls.isNotEmpty) {
final geminiTools = ref.read(geminiToolsProvider);
final responseStream = chatSession.sendMessageStream(
Content.functionResponses([
for (final functionCall in block.functionCalls)
FunctionResponse(
functionCall.name,
geminiTools.handleFunctionCall(
functionCall.name,
functionCall.args,
),
),
]),
);
await for (final response in responseStream) {
final responseText = response.text;
if (responseText != null) {
logStateNotifier.logLlmText(responseText);
chatStateNotifier.appendToMessage(llmMessageId, responseText);
}
}
}
}
}
@Riverpod(keepAlive: true)
GeminiChatService geminiChatService(Ref ref) => GeminiChatService(ref);
الإضافة الرئيسية هي طريقة notifyColorSelection التي:
- تتلقّى عنصر
ColorDataيمثّل اللون المحدّد - ترميزها بتنسيق JSON يمكن تضمينه في رسالة
- إرسال رسالة منسَّقة خصيصًا إلى النموذج اللغوي الكبير تشير إلى اختيار المستخدم
- إعادة استخدام طريقة
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 التي تربط حدث واجهة المستخدم (اختيار لون من السجلّ) بنظام الإشعارات الخاص بنموذج اللغة الكبير.
تعديل الطلب من النظام
الآن، عليك تعديل طلب النظام لتوجيه النموذج اللغوي الكبير بشأن كيفية الردّ على إشعارات اختيار الألوان. عدِّل ملف assets/system_prompt.md باتّباع الخطوات التالية:
assets/system_prompt.md
# Colorist System Prompt
You are a color expert assistant integrated into a desktop app called Colorist. Your job is to interpret natural language color descriptions and set the appropriate color values using a specialized tool.
## Your Capabilities
You are knowledgeable about colors, color theory, and how to translate natural language descriptions into specific RGB values. You have access to the following tool:
`set_color` - Sets the RGB values for the color display based on a description
## How to Respond to User Inputs
When users describe a color:
1. First, acknowledge their color description with a brief, friendly response
2. Interpret what RGB values would best represent that color description
3. Use the `set_color` tool to set those values (all values should be between 0.0 and 1.0)
4. After setting the color, provide a brief explanation of your interpretation
Example:
User: "I want a sunset orange"
You: "Sunset orange is a warm, vibrant color that captures the golden-red hues of the setting sun. It combines a strong red component with moderate orange tones."
[Then you would call the set_color tool with approximately: red=1.0, green=0.5, blue=0.25]
After the tool call: "I've set a warm orange with strong red, moderate green, and minimal blue components that is reminiscent of the sun low on the horizon."
## When Descriptions are Unclear
If a color description is ambiguous or unclear, please ask the user clarifying questions, one at a time.
## When Users Select Historical Colors
Sometimes, the user will manually select a color from the history panel. When this happens, you'll receive a notification about this selection that includes details about the color. Acknowledge this selection with a brief response that recognizes what they've done and comments on the selected color.
Example notification:
User: "User selected color from history: {red: 0.2, green: 0.5, blue: 0.8, hexCode: #3380CC}"
You: "I see you've selected an ocean blue from your history. This tranquil blue with a moderate intensity has a calming, professional quality to it. Would you like to explore similar shades or create a contrasting color?"
## Important Guidelines
- Always keep RGB values between 0.0 and 1.0
- Provide thoughtful, knowledgeable responses about colors
- When possible, include color psychology, associations, or interesting facts about colors
- Be conversational and engaging in your responses
- Focus on being helpful and accurate with your color interpretations
الإضافة الرئيسية هي قسم "عندما يختار المستخدمون ألوانًا سابقة"، الذي يتيح لك إجراء ما يلي:
- يشرح هذا القسم مفهوم إشعارات اختيار السجلّ للنموذج اللغوي الكبير
- تقدّم مثالاً على شكل هذه الإشعارات
- عرض مثال على ردّ مناسب
- تحديد التوقعات بشأن الإقرار بالاختيار والتعليق على اللون
يساعد ذلك النموذج اللغوي الكبير في فهم كيفية الردّ بشكل مناسب على هذه الرسائل الخاصة.
إنشاء رمز Riverpod
نفِّذ أمر أداة إنشاء الإصدار لإنشاء رمز Riverpod المطلوب:
dart run build_runner build --delete-conflicting-outputs
تشغيل ميزة "مزامنة السياق مع النماذج اللغوية الكبيرة" واختبارها
شغِّل تطبيقك:
flutter run -d DEVICE

يتضمّن اختبار مزامنة السياق في النموذج اللغوي الكبير ما يلي:
- أولاً، أنشئ بعض الألوان من خلال وصفها في المحادثة
- "أريد رؤية لون أرجواني زاهٍ"
- "أريد لونًا أخضر داكنًا"
- "أريد لونًا أحمر زاهيًا"
- بعد ذلك، انقر على إحدى الصور المصغّرة للألوان في شريط السجلّ.
يجب مراعاة ما يلي:
- يظهر اللون المحدّد في الشاشة الرئيسية
- تظهر رسالة مستخدم في المحادثة تشير إلى اختيار اللون
- يردّ النموذج اللغوي الكبير من خلال الإقرار بالاختيار والتعليق على اللون
- يبدو التفاعل بأكمله طبيعيًا ومتماسكًا
يؤدي ذلك إلى إنشاء تجربة سلسة يكون فيها النموذج اللغوي الكبير على دراية بالرسائل المباشرة وتفاعلات واجهة المستخدم ويردّ عليها بشكل مناسب.
طريقة عمل ميزة "مزامنة السياق مع النماذج اللغوية الكبيرة"
في ما يلي تفاصيل فنية حول طريقة عمل هذه المزامنة:
تدفّق البيانات
- إجراء المستخدم: ينقر المستخدم على لون في شريط السجلّ
- حدث واجهة المستخدم: يرصد التطبيق المصغّر
MainScreenهذا الاختيار - تنفيذ معاودة الاتصال: يتم تشغيل معاودة الاتصال
notifyColorSelection - إنشاء الرسالة: يتم إنشاء رسالة بتنسيق خاص تتضمّن بيانات الألوان
- معالجة النموذج اللغوي الكبير: يتم إرسال الرسالة إلى Gemini الذي يتعرّف على التنسيق
- الردّ السياقي: يردّ Gemini بشكل مناسب استنادًا إلى طلب النظام
- تعديل على واجهة المستخدم: يظهر الردّ في المحادثة، ما يوفّر تجربة متماسكة
تسلسل البيانات
أحد الجوانب الرئيسية لهذا النهج هو كيفية تسلسل بيانات الألوان:
'User selected color from history: ${json.encode(color.toLLMContextMap())}'
يحوّل الإجراء toLLMContextMap() (المقدَّم من خلال الحزمة colorist_ui) العنصر ColorData إلى خريطة تتضمّن خصائص مفتاحية يمكن للنموذج اللغوي الكبير فهمها. يشمل ذلك عادةً ما يلي:
- قيم النموذج اللوني أحمر أخضر أزرق (RGB)
- تمثيل الرمز السداسي العشري
- أي اسم أو وصف مرتبط باللون
من خلال تنسيق هذه البيانات بشكل متّسق وتضمينها في الرسالة، تضمن حصول النموذج اللغوي الكبير على جميع المعلومات التي يحتاج إليها للردّ بشكل مناسب.
تطبيقات أوسع لميزة "مزامنة السياق" في النماذج اللغوية الكبيرة
يمكن استخدام نمط إعلام النموذج اللغوي الكبير بأحداث واجهة المستخدم في العديد من التطبيقات الأخرى غير اختيار الألوان، ومنها:
حالات الاستخدام الأخرى
- تغييرات الفلتر: إعلام النموذج اللغوي الكبير عندما يطبّق المستخدمون فلاتر على البيانات
- أحداث التنقّل: إعلام النموذج اللغوي الكبير عندما يتنقّل المستخدمون إلى أقسام مختلفة
- تغييرات في الاختيار: تعديل النموذج اللغوي الكبير عندما يختار المستخدمون عناصر من القوائم أو الجداول
- تعديلات على الإعدادات المفضّلة: إخبار النموذج اللغوي الكبير عندما يغيّر المستخدمون الإعدادات أو الإعدادات المفضّلة
- معالجة البيانات: إرسال إشعار إلى النموذج اللغوي الكبير عندما يضيف المستخدمون بيانات أو يعدّلونها أو يحذفونها
في كل حالة، يبقى النمط كما هو:
- رصد حدث واجهة المستخدم
- تسلسل البيانات ذات الصلة
- إرسال إشعار منسَّق خصيصًا إلى النموذج اللغوي الكبير
- توجيه النموذج اللغوي الكبير للرد بشكل مناسب من خلال طلب النظام
أفضل الممارسات لمزامنة السياق في النماذج اللغوية الكبيرة
استنادًا إلى عملية التنفيذ، إليك بعض أفضل الممارسات لمزامنة السياق الفعّالة في النماذج اللغوية الكبيرة:
1. اعتماد تنسيق موحَّد
استخدِم تنسيقًا متسقًا للإشعارات حتى يتمكّن النموذج اللغوي الكبير من التعرّف عليها بسهولة:
"User [action] [object]: [structured data]"
2. سياق وافٍ
تضمين تفاصيل كافية في الإشعارات لكي يستجيب النموذج اللغوي الكبير بذكاء بالنسبة إلى الألوان، يعني ذلك قيم الأحمر والأخضر والأزرق ورموز Hex وأي خصائص أخرى ذات صلة.
3- تعليمات واضحة
قدِّم تعليمات واضحة في طلب النظام حول كيفية التعامل مع الإشعارات، ويُفضّل أن تتضمّن أمثلة.
4. التكامل الطبيعي
صمِّم الإشعارات لتظهر بشكل طبيعي في المحادثة، وليس كمقاطعات فنية.
5- الإشعارات الانتقائية
إشعار النموذج اللغوي الكبير بالإجراءات ذات الصلة بالمحادثة فقط ليس من الضروري إبلاغ المستخدم بكل حدث في واجهة المستخدم.
تحديد المشاكل وحلّها
المشاكل المتعلّقة بالإشعارات
إذا لم يستجِب النموذج اللغوي الكبير بشكل صحيح لخيارات الألوان:
- التأكّد من أنّ تنسيق رسالة الإشعار يتطابق مع ما هو موضح في طلب النظام
- التأكّد من تسلسل بيانات الألوان بشكل سليم
- التأكّد من أنّ طلب النظام يتضمّن تعليمات واضحة للتعامل مع عمليات التحديد
- البحث عن أي أخطاء في خدمة المحادثة عند إرسال الإشعارات
إدارة السياق
إذا بدا أنّ النموذج اللغوي الكبير يفقد السياق:
- التأكّد من الحفاظ على جلسة المحادثة بشكلٍ سليم
- التأكّد من أنّ حالات المحادثة تنتقل بشكلٍ سليم
- التأكّد من إرسال الإشعارات من خلال جلسة المحادثة نفسها
المشاكل العامة
بالنسبة إلى المشاكل العامة:
- فحص السجلات بحثًا عن أخطاء أو تحذيرات
- التأكّد من ربط Firebase AI Logic
- التحقّق من عدم تطابق الأنواع في مَعلمات الدالة
- التأكّد من أنّ جميع الرموز البرمجية التي تم إنشاؤها في Riverpod محدَّثة
المفاهيم الرئيسية التي تم تعلّمها
- إنشاء مزامنة سياق نموذج لغوي كبير بين واجهة المستخدم والنموذج اللغوي الكبير
- تحويل أحداث واجهة المستخدم إلى سياق متوافق مع النماذج اللغوية الكبيرة
- توجيه سلوك النماذج اللغوية الكبيرة لأنماط التفاعل المختلفة
- توفير تجربة متسقة في التفاعلات المتعلقة بالرسائل وغير المتعلقة بها
- تعزيز فهم النماذج اللغوية الكبيرة لحالة التطبيق بشكل عام
من خلال تنفيذ ميزة "مزامنة سياق النموذج اللغوي الكبير"، تكون قد أنشأت تجربة متكاملة حقًا، حيث يبدو النموذج اللغوي الكبير وكأنّه مساعد مدرك ومتجاوب وليس مجرد أداة لإنشاء النصوص. يمكن تطبيق هذا النمط على عدد لا يحصى من التطبيقات الأخرى لإنشاء واجهات أكثر طبيعية وسهولة في الاستخدام مستندة إلى الذكاء الاصطناعي.
9. تهانينا!
لقد أكملت الدرس التطبيقي حول الترميز Colorist بنجاح. 🎉
ما أنشأته
لقد أنشأت تطبيق Flutter يعمل بكامل طاقته ويتضمّن Google Gemini API لتفسير أوصاف الألوان بلغة طبيعية. يمكن لتطبيقك الآن إجراء ما يلي:
- معالجة أوصاف اللغات الطبيعية، مثل "برتقالي عند غروب الشمس" أو "أزرق المحيط العميق"
- استخدِم Gemini لترجمة هذه الأوصاف بذكاء إلى قيم RGB
- عرض الألوان المفسّرة في الوقت الفعلي باستخدام الردود المتدفقة
- التعامل مع تفاعلات المستخدمين من خلال كلّ من المحادثة وعناصر واجهة المستخدم
- الحفاظ على الوعي السياقي في مختلف طرق التفاعل
الخطوة التالية
بعد أن أتقنت أساسيات دمج Gemini مع Flutter، إليك بعض الطرق لمواصلة رحلتك:
تحسين تطبيق Colorist
- لوحات الألوان: إضافة وظيفة لإنشاء أنظمة ألوان متطابقة أو متكاملة
- الإدخال الصوتي: دمج ميزة التعرّف على الكلام لتقديم أوصاف شفهية للألوان
- إدارة السجلّ: إضافة خيارات لتسمية مجموعات الألوان وتنظيمها وتصديرها
- الطلبات المخصّصة: إنشاء واجهة تتيح للمستخدمين تخصيص طلبات النظام
- التحليلات المتقدّمة: تتبُّع الأوصاف التي تحقّق أفضل أداء أو تسبّب صعوبات
استكشاف المزيد من ميزات Gemini
- الإدخالات المتعددة الوسائط: إضافة إدخالات صور لاستخراج الألوان من الصور
- إنشاء المحتوى: استخدِم Gemini لإنشاء محتوى مرتبط بالألوان، مثل الأوصاف أو القصص
- تحسينات على ميزة "طلب تنفيذ وظيفة": إنشاء عمليات تكامل أكثر تعقيدًا للأدوات باستخدام وظائف متعددة
- إعدادات الأمان: استكشاف إعدادات الأمان المختلفة وتأثيرها في الردود
تطبيق هذه الأنماط على نطاقات أخرى
- تحليل المستندات: إنشاء تطبيقات يمكنها فهم المستندات وتحليلها
- المساعدة في الكتابة الإبداعية: إنشاء أدوات كتابة مزوّدة باقتراحات مستندة إلى نماذج اللغات الكبيرة
- أتمتة المهام: تصميم تطبيقات تحوّل اللغة الطبيعية إلى مهام مبرمَجة
- التطبيقات المستندة إلى المعرفة: إنشاء أنظمة خبيرة في مجالات معيّنة
الموارد
في ما يلي بعض المراجع القيّمة لمواصلة التعلّم:
المستندات الرسمية
دورة تدريبية ودليل إرشادي لكتابة الطلبات
المنتدى
سلسلة Observable Flutter Agentic
في الحلقة رقم 59، يستكشف "كريغ لابينز" و"أندرو بروغدن" هذا الدرس التطبيقي حول الترميز، مع إبراز الأجزاء المثيرة للاهتمام في إنشاء التطبيق.
في الحلقة 60، انضم إلى "كريغ" و"أندرو" مرة أخرى وهما يضيفان إمكانات جديدة إلى تطبيق الدرس العملي ويواجهان صعوبات في جعل النماذج اللغوية الكبيرة تنفّذ ما يُطلب منها.
في الحلقة رقم 61، ينضم "كريس سيلز" إلى "كريغ" لتقديم منظور جديد حول تحليل عناوين الأخبار وإنشاء الصور ذات الصلة.
الملاحظات
يسرّنا معرفة رأيك بشأن هذا الدرس العملي. يُرجى تقديم الملاحظات من خلال:
نشكرك على إكمال هذا الدرس العملي، ونأمل أن تواصل استكشاف الإمكانات الرائعة التي تجمع بين Flutter والذكاء الاصطناعي.