Thông tin về lớp học lập trình này
1. Tạo ứng dụng Flutter dựa trên Gemini
Sản phẩm bạn sẽ tạo ra
Trong lớp học lập trình này, bạn sẽ tạo Colorist – một ứng dụng Flutter có thể tương tác, mang sức mạnh của Gemini API trực tiếp vào ứng dụng Flutter của bạn. Bạn có bao giờ muốn cho phép người dùng kiểm soát ứng dụng của bạn thông qua ngôn ngữ tự nhiên nhưng không biết bắt đầu từ đâu không? Lớp học lập trình này sẽ hướng dẫn bạn cách thực hiện.
Colorist cho phép người dùng mô tả màu sắc bằng ngôn ngữ tự nhiên (chẳng hạn như "màu cam của hoàng hôn" hoặc "màu xanh dương đậm của đại dương"), và ứng dụng này:
- Xử lý những nội dung mô tả này bằng Gemini API của Google
- Diễn giải nội dung mô tả thành các giá trị màu RGB chính xác
- Hiển thị màu trên màn hình theo thời gian thực
- Cung cấp thông tin chi tiết về màu sắc kỹ thuật và bối cảnh thú vị về màu sắc
- Duy trì nhật ký các màu được tạo gần đây
Ứng dụng này có giao diện chia đôi màn hình với một vùng hiển thị màu và hệ thống trò chuyện tương tác ở một bên, cùng một bảng nhật ký chi tiết cho thấy các hoạt động tương tác thô của LLM ở bên còn lại. Nhật ký này giúp bạn hiểu rõ hơn về cách hoạt động thực sự của một quy trình tích hợp LLM.
Lý do điều này quan trọng đối với nhà phát triển Flutter
Mô hình ngôn ngữ lớn (LLM) đang tạo ra cuộc cách mạng về cách người dùng tương tác với các ứng dụng, nhưng việc tích hợp hiệu quả các mô hình này vào ứng dụng di động và ứng dụng dành cho máy tính có những thách thức riêng. Lớp học lập trình này hướng dẫn bạn các mẫu thực tế không chỉ là các lệnh gọi API thô.
Hành trình học tập của bạn
Lớp học lập trình này sẽ hướng dẫn bạn từng bước trong quy trình tạo Colorist:
- Thiết lập dự án – Bạn sẽ bắt đầu với cấu trúc ứng dụng Flutter cơ bản và gói
colorist_ui
- Tích hợp Gemini cơ bản – Kết nối ứng dụng của bạn với Firebase AI Logic và triển khai tính năng giao tiếp bằng LLM
- Câu lệnh hiệu quả – Tạo một câu lệnh hệ thống hướng dẫn LLM hiểu nội dung mô tả màu sắc
- Khai báo hàm – Xác định những công cụ mà LLM có thể dùng để đặt màu trong ứng dụng của bạn
- Xử lý công cụ – Xử lý các lệnh gọi hàm từ LLM và kết nối các lệnh gọi đó với trạng thái của ứng dụng
- Truyền trực tuyến câu trả lời – Nâng cao trải nghiệm người dùng bằng cách truyền trực tuyến câu trả lời của LLM theo thời gian thực
- Đồng bộ hoá bối cảnh LLM – Tạo trải nghiệm nhất quán bằng cách thông báo cho LLM về hành động của người dùng
Kiến thức bạn sẽ học được
- Định cấu hình Firebase AI Logic cho các ứng dụng Flutter
- Tạo câu lệnh hệ thống hiệu quả để định hướng hành vi của LLM
- Triển khai các khai báo hàm kết nối ngôn ngữ tự nhiên và các tính năng của ứng dụng
- Xử lý các phản hồi truyền trực tuyến để mang lại trải nghiệm người dùng thích ứng
- Đồng bộ hoá trạng thái giữa các sự kiện trên giao diện người dùng và LLM
- Quản lý trạng thái cuộc trò chuyện của LLM bằng Riverpod
- Xử lý lỗi một cách thoả đáng trong các ứng dụng dựa trên LLM
Xem trước mã: Xem trước nội dung bạn sẽ triển khai
Sau đây là thông tin sơ lược về khai báo hàm mà bạn sẽ tạo để cho phép LLM đặt màu trong ứng dụng của bạn:
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)'),
},
);
Video tổng quan về lớp học lập trình này
Xem Craig Labenz và Andrew Brogdon thảo luận về lớp học lập trình này trong tập 59 của Observable Flutter:
Điều kiện tiên quyết
Để khai thác tối đa lớp học lập trình này, bạn cần:
- Kinh nghiệm phát triển Flutter – Nắm vững kiến thức cơ bản về Flutter và cú pháp Dart
- Kiến thức về lập trình không đồng bộ – Hiểu biết về Futures, async/await và các luồng
- Tài khoản Firebase – Bạn cần có Tài khoản Google để thiết lập Firebase
Hãy bắt đầu tạo ứng dụng Flutter đầu tiên dựa trên LLM!
2. Thiết lập dự án và dịch vụ phản hồi
Trong bước đầu tiên này, bạn sẽ thiết lập cấu trúc dự án và triển khai một dịch vụ lặp lại. Dịch vụ này sẽ được thay thế bằng tính năng tích hợp Gemini API sau này. Việc này thiết lập cấu trúc ứng dụng và đảm bảo giao diện người dùng của bạn hoạt động chính xác trước khi thêm độ phức tạp của các lệnh gọi LLM.
Kiến thức bạn sẽ học được trong bước này
- Thiết lập một dự án Flutter với các phần phụ thuộc bắt buộc
- Làm việc với gói
colorist_ui
cho các thành phần giao diện người dùng - Triển khai dịch vụ tin nhắn phản hồi và kết nối dịch vụ đó với giao diện người dùng
Tạo dự án Flutter mới
Bắt đầu bằng cách tạo một dự án Flutter mới bằng lệnh sau:
flutter create -e colorist --platforms=android,ios,macos,web,windows
Cờ -e
cho biết bạn muốn một dự án trống mà không có ứng dụng counter
mặc định. Ứng dụng này được thiết kế để hoạt động trên máy tính, thiết bị di động và web. Tuy nhiên, flutterfire
hiện không hỗ trợ Linux.
Thêm phần phụ thuộc
Chuyển đến thư mục dự án của bạn rồi thêm các phần phụ thuộc bắt buộc:
cd colorist
flutter pub add colorist_ui flutter_riverpod riverpod_annotation
flutter pub add --dev build_runner riverpod_generator riverpod_lint json_serializable
Thao tác này sẽ thêm các gói khoá sau:
colorist_ui
: Một gói tuỳ chỉnh cung cấp các thành phần giao diện người dùng cho ứng dụng Coloristflutter_riverpod
vàriverpod_annotation
: Để quản lý trạng tháilogging
: Để ghi nhật ký có cấu trúc- Các phần phụ thuộc phát triển để tạo và kiểm tra mã
pubspec.yaml
của bạn sẽ có dạng như sau:
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
Triển khai tệp main.dart
Thay thế nội dung của lib/main.dart
bằng nội dung sau:
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);
}
}
Thao tác này sẽ thiết lập một ứng dụng Flutter triển khai dịch vụ lặp lại mô phỏng hành vi của một LLM bằng cách trả về thông báo của người dùng.
Tìm hiểu về cấu trúc
Hãy dành chút thời gian để tìm hiểu cấu trúc của ứng dụng colorist
:
Gói colorist_ui
Gói colorist_ui
cung cấp các thành phần giao diện người dùng và công cụ quản lý trạng thái được tạo sẵn:
- MainScreen: Thành phần giao diện người dùng chính hiển thị:
- Bố cục chia đôi màn hình trên máy tính (vùng tương tác và bảng điều khiển nhật ký)
- Giao diện theo thẻ trên thiết bị di động
- Màn hình màu, giao diện trò chuyện và hình thu nhỏ của nhật ký
- Quản lý trạng thái: Ứng dụng sử dụng một số trình thông báo trạng thái:
- ChatStateNotifier: Quản lý tin nhắn trò chuyện
- ColorStateNotifier: Quản lý màu hiện tại và nhật ký
- LogStateNotifier: Quản lý các mục nhật ký để gỡ lỗi
- Xử lý thông báo: Ứng dụng sử dụng một mô hình thông báo với nhiều trạng thái:
- Thông báo cho người dùng: Do người dùng nhập
- Tin nhắn LLM: Do LLM tạo (hoặc dịch vụ phản hồi của bạn hiện tại)
- MessageState: Theo dõi xem tin nhắn của mô hình ngôn ngữ lớn đã hoàn tất hay vẫn đang truyền trực tuyến
Cấu trúc ứng dụng
Ứng dụng tuân theo cấu trúc sau:
- Lớp giao diện người dùng: Do gói
colorist_ui
cung cấp - Quản lý trạng thái: Sử dụng Riverpod để quản lý trạng thái phản ứng
- Lớp dịch vụ: Hiện chứa dịch vụ phản hồi đơn giản của bạn, lớp này sẽ được thay thế bằng Dịch vụ Gemini Chat
- Tích hợp LLM: Sẽ được thêm vào các bước sau
Việc tách biệt này cho phép bạn tập trung vào việc triển khai tích hợp LLM trong khi các thành phần giao diện người dùng đã được xử lý.
Chạy ứng dụng
Chạy ứng dụng bằng lệnh sau:
flutter run -d DEVICE
Thay thế DEVICE
bằng thiết bị mục tiêu của bạn, chẳng hạn như macos
, windows
, chrome
hoặc một mã nhận dạng thiết bị.
Giờ đây, bạn sẽ thấy ứng dụng Colorist có:
- Một vùng hiển thị màu có màu mặc định
- Giao diện trò chuyện nơi bạn có thể nhập tin nhắn
- Bảng nhật ký cho thấy các hoạt động tương tác trong cuộc trò chuyện
Hãy thử nhập một tin nhắn như "Tôi muốn có màu xanh dương đậm" rồi nhấn vào biểu tượng Gửi. Dịch vụ phản hồi sẽ chỉ lặp lại tin nhắn của bạn. Trong các bước sau, bạn sẽ thay thế phần này bằng cách diễn giải màu sắc thực tế bằng Firebase AI Logic.
Tiếp theo là gì?
Trong bước tiếp theo, bạn sẽ định cấu hình Firebase và triển khai quy trình tích hợp Gemini API cơ bản để thay thế dịch vụ phản hồi bằng dịch vụ trò chuyện Gemini. Điều này sẽ cho phép ứng dụng diễn giải nội dung mô tả màu sắc và đưa ra câu trả lời thông minh.
Khắc phục sự cố
Vấn đề về gói giao diện người dùng
Nếu bạn gặp vấn đề với gói colorist_ui
:
- Đảm bảo bạn đang sử dụng phiên bản mới nhất
- Xác minh rằng bạn đã thêm đúng phần phụ thuộc
- Kiểm tra xem có phiên bản gói nào xung đột không
Lỗi khi tạo bản dựng
Nếu bạn thấy lỗi bản dựng:
- Đảm bảo bạn đã cài đặt Flutter SDK mới nhất trên kênh ổn định
- Chạy
flutter clean
rồi chạyflutter pub get
- Kiểm tra đầu ra của bảng điều khiển để xem các thông báo lỗi cụ thể
Các khái niệm chính đã học
- Thiết lập một dự án Flutter với các phần phụ thuộc cần thiết
- Hiểu rõ cấu trúc và trách nhiệm của các thành phần trong ứng dụng
- Triển khai một dịch vụ đơn giản mô phỏng hành vi của LLM
- Kết nối dịch vụ với các thành phần trên giao diện người dùng
- Sử dụng Riverpod để quản lý trạng thái
3. Tích hợp Gemini Chat cơ bản
Ở bước này, bạn sẽ thay thế dịch vụ lặp lại ở bước trước bằng tính năng tích hợp Gemini API bằng Firebase AI Logic. Bạn sẽ định cấu hình Firebase, thiết lập các nhà cung cấp cần thiết và triển khai một dịch vụ trò chuyện cơ bản giao tiếp với Gemini API.
Kiến thức bạn sẽ học được trong bước này
- Thiết lập Firebase trong ứng dụng Flutter
- Định cấu hình Firebase AI Logic để truy cập vào Gemini
- Tạo các nhà cung cấp Riverpod cho các dịch vụ của Firebase và Gemini
- Triển khai một dịch vụ trò chuyện cơ bản bằng Gemini API
- Xử lý các trạng thái lỗi và phản hồi API không đồng bộ
Thiết lập Firebase
Trước tiên, bạn cần thiết lập Firebase cho dự án Flutter của mình. Việc này bao gồm tạo một dự án Firebase, thêm ứng dụng của bạn vào dự án đó và định cấu hình các chế độ cài đặt cần thiết cho Logic AI của Firebase.
Tạo một dự án Firebase
- Truy cập vào Bảng điều khiển Firebase rồi đăng nhập bằng Tài khoản Google của bạn.
- Nhấp vào Tạo dự án Firebase hoặc chọn một dự án hiện có.
- Làm theo trình hướng dẫn thiết lập để tạo dự án.
Thiết lập Firebase AI Logic trong dự án Firebase
- Trong bảng điều khiển của Firebase, hãy chuyển đến dự án của bạn.
- Trong thanh bên trái, hãy chọn AI.
- Trong trình đơn thả xuống AI, hãy chọn Logic AI.
- Trong thẻ Firebase AI Logic, hãy chọn Bắt đầu.
- Làm theo lời nhắc để bật Gemini Developer API cho dự án của bạn.
Cài đặt FlutterFire CLI
FlutterFire CLI giúp đơn giản hoá việc thiết lập Firebase trong các ứng dụng Flutter:
dart pub global activate flutterfire_cli
Thêm Firebase vào ứng dụng Flutter
- Thêm gói Firebase Core và Firebase AI Logic vào dự án của bạn:
flutter pub add firebase_core firebase_ai
- Chạy lệnh cấu hình FlutterFire:
flutterfire configure
Lệnh này sẽ:
- Yêu cầu bạn chọn dự án Firebase mà bạn vừa tạo
- Đăng ký(các) ứng dụng Flutter của bạn với Firebase
- Tạo tệp
firebase_options.dart
bằng cấu hình dự án
Lệnh này sẽ tự động phát hiện các nền tảng bạn chọn (iOS, Android, macOS, Windows, web) và định cấu hình các nền tảng đó một cách thích hợp.
Cấu hình dành riêng cho nền tảng
Firebase yêu cầu phiên bản tối thiểu cao hơn phiên bản mặc định cho Flutter. Ứng dụng này cũng cần có quyền truy cập vào mạng để giao tiếp với các máy chủ Firebase AI Logic.
Định cấu hình quyền trên macOS
Đối với macOS, bạn cần bật quyền truy cập mạng trong các quyền của ứng dụng:
- Mở
macos/Runner/DebugProfile.entitlements
rồi thêm:
macos/Runner/DebugProfile.entitlements
<key>com.apple.security.network.client</key>
<true/>
- Ngoài ra, hãy mở
macos/Runner/Release.entitlements
rồi thêm cùng một mục.
Định cấu hình chế độ cài đặt iOS
Đối với iOS, hãy cập nhật phiên bản tối thiểu ở đầu ios/Podfile
:
ios/Podfile
# Firebase requires at least iOS 15.0
platform :ios, '15.0'
Tạo nhà cung cấp mô hình Gemini
Bây giờ, bạn sẽ tạo các trình cung cấp Riverpod cho Firebase và Gemini. Tạo tệp mới lib/providers/gemini.dart
:
lib/providers/gemini.dart
import 'dart:async';
import 'package:firebase_ai/firebase_ai.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import '../firebase_options.dart';
part 'gemini.g.dart';
@riverpod
Future<FirebaseApp> firebaseApp(Ref ref) =>
Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
@riverpod
Future<GenerativeModel> geminiModel(Ref ref) async {
await ref.watch(firebaseAppProvider.future);
final model = FirebaseAI.googleAI().generativeModel(
model: 'gemini-2.0-flash',
);
return model;
}
@Riverpod(keepAlive: true)
Future<ChatSession> chatSession(Ref ref) async {
final model = await ref.watch(geminiModelProvider.future);
return model.startChat();
}
Tệp này xác định cơ sở cho 3 nhà cung cấp khoá chính. Các nhà cung cấp này được tạo khi bạn chạy dart run build_runner
bằng trình tạo mã Riverpod.
firebaseAppProvider
: Khởi động Firebase bằng cấu hình dự án của bạngeminiModelProvider
: Tạo một thực thể mô hình tạo sinh GeminichatSessionProvider
: Tạo và duy trì một phiên trò chuyện với mô hình Gemini
Chú thích keepAlive: true
trên phiên trò chuyện đảm bảo phiên trò chuyện vẫn tồn tại trong suốt vòng đời của ứng dụng, duy trì bối cảnh trò chuyện.
Triển khai dịch vụ trò chuyện Gemini
Tạo một tệp lib/services/gemini_chat_service.dart
mới để triển khai dịch vụ trò chuyện:
lib/services/gemini_chat_service.dart
import 'dart:async';
import 'package:colorist_ui/colorist_ui.dart';
import 'package:firebase_ai/firebase_ai.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import '../providers/gemini.dart';
part 'gemini_chat_service.g.dart';
class GeminiChatService {
GeminiChatService(this.ref);
final Ref ref;
Future<void> sendMessage(String message) async {
final chatSession = await ref.read(chatSessionProvider.future);
final chatStateNotifier = ref.read(chatStateProvider.notifier);
final logStateNotifier = ref.read(logStateProvider.notifier);
chatStateNotifier.addUserMessage(message);
logStateNotifier.logUserText(message);
final llmMessage = chatStateNotifier.createLlmMessage();
try {
final response = await chatSession.sendMessage(Content.text(message));
final responseText = response.text;
if (responseText != null) {
logStateNotifier.logLlmText(responseText);
chatStateNotifier.appendToMessage(llmMessage.id, responseText);
}
} catch (e, st) {
logStateNotifier.logError(e, st: st);
chatStateNotifier.appendToMessage(
llmMessage.id,
"\nI'm sorry, I encountered an error processing your request. "
"Please try again.",
);
} finally {
chatStateNotifier.finalizeMessage(llmMessage.id);
}
}
}
@riverpod
GeminiChatService geminiChatService(Ref ref) => GeminiChatService(ref);
Dịch vụ này:
- Chấp nhận tin nhắn của người dùng và gửi đến Gemini API
- Cập nhật giao diện trò chuyện bằng các câu trả lời của mô hình
- Ghi lại tất cả thông tin liên lạc để dễ dàng hiểu được quy trình LLM thực tế
- Xử lý lỗi bằng ý kiến phản hồi phù hợp của người dùng
Lưu ý: Cửa sổ Nhật ký sẽ gần giống với cửa sổ trò chuyện tại thời điểm này. Nhật ký sẽ trở nên thú vị hơn khi bạn giới thiệu các lệnh gọi hàm và sau đó là các phản hồi truyền trực tuyến.
Tạo mã Riverpod
Chạy lệnh build runner để tạo mã Riverpod cần thiết:
dart run build_runner build --delete-conflicting-outputs
Thao tác này sẽ tạo các tệp .g.dart
mà Riverpod cần để hoạt động.
Cập nhật tệp main.dart
Cập nhật tệp lib/main.dart
để sử dụng dịch vụ trò chuyện Gemini mới:
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),
),
);
}
}
Sau đây là những thay đổi chính trong bản cập nhật này:
- Thay thế dịch vụ lặp lại bằng dịch vụ trò chuyện dựa trên Gemini API
- Thêm màn hình lỗi và màn hình đang tải bằng cách sử dụng mẫu
AsyncValue
của Riverpod với phương thứcwhen
- Kết nối giao diện người dùng với dịch vụ trò chuyện mới thông qua lệnh gọi lại
sendMessage
Chạy ứng dụng
Chạy ứng dụng bằng lệnh sau:
flutter run -d DEVICE
Thay thế DEVICE
bằng thiết bị mục tiêu của bạn, chẳng hạn như macos
, windows
, chrome
hoặc một mã nhận dạng thiết bị.
Giờ đây, khi bạn nhập một tin nhắn, tin nhắn đó sẽ được gửi đến Gemini API và bạn sẽ nhận được phản hồi từ LLM thay vì một câu lệnh lặp lại. Bảng điều khiển nhật ký sẽ cho thấy các hoạt động tương tác với API.
Tìm hiểu về cách giao tiếp với LLM
Hãy dành chút thời gian để tìm hiểu điều gì sẽ xảy ra khi bạn giao tiếp với Gemini API:
Quy trình giao tiếp
- Thông tin đầu vào của người dùng: Người dùng nhập văn bản vào giao diện trò chuyện
- Định dạng yêu cầu: Ứng dụng định dạng văn bản dưới dạng một đối tượng
Content
cho Gemini API - Giao tiếp API: Văn bản được gửi đến Gemini API thông qua Firebase AI Logic
- Xử lý bằng mô hình ngôn ngữ lớn (LLM): Mô hình Gemini xử lý văn bản và tạo câu trả lời
- Xử lý phản hồi: Ứng dụng nhận được phản hồi và cập nhật giao diện người dùng
- Ghi nhật ký: Mọi hoạt động giao tiếp đều được ghi nhật ký để đảm bảo tính minh bạch
Phiên trò chuyện và bối cảnh trò chuyện
Phiên trò chuyện với Gemini duy trì ngữ cảnh giữa các tin nhắn, cho phép bạn trò chuyện tương tác. Điều này có nghĩa là LLM "ghi nhớ" các lượt trao đổi trước đó trong phiên hiện tại, cho phép các cuộc trò chuyện diễn ra mạch lạc hơn.
Chú thích keepAlive: true
trên trình cung cấp phiên trò chuyện của bạn đảm bảo ngữ cảnh này vẫn tồn tại trong suốt vòng đời của ứng dụng. Bối cảnh liên tục này đóng vai trò quan trọng trong việc duy trì một luồng trò chuyện tự nhiên với LLM.
Tiếp theo là gì?
Đến đây, bạn có thể hỏi Gemini API bất cứ điều gì vì không có hạn chế nào về nội dung mà API này sẽ phản hồi. Ví dụ: bạn có thể yêu cầu Gemini tóm tắt về Chiến tranh Hoa hồng, một chủ đề không liên quan đến mục đích của ứng dụng màu sắc.
Trong bước tiếp theo, bạn sẽ tạo một câu lệnh hệ thống để hướng dẫn Gemini diễn giải nội dung mô tả màu sắc một cách hiệu quả hơn. Điều này sẽ minh hoạ cách tuỳ chỉnh hành vi của một LLM cho các nhu cầu cụ thể của ứng dụng và tập trung các chức năng của LLM vào miền của ứng dụng.
Khắc phục sự cố
Các vấn đề về cấu hình Firebase
Nếu bạn gặp lỗi khi khởi động Firebase:
- Đảm bảo rằng tệp
firebase_options.dart
của bạn được tạo đúng cách - Xác minh rằng bạn đã nâng cấp lên gói Blaze để có quyền truy cập vào Firebase AI Logic
Lỗi khi truy cập vào API
Nếu bạn gặp lỗi khi truy cập vào Gemini API:
- Xác nhận rằng bạn đã thiết lập thông tin thanh toán đúng cách cho dự án Firebase
- Kiểm tra để đảm bảo bạn đã bật Firebase AI Logic và Cloud AI API trong dự án Firebase
- Kiểm tra kết nối mạng và chế độ cài đặt tường lửa
- Xác minh rằng tên mô hình (
gemini-2.0-flash
) là chính xác và có sẵn
Các vấn đề về bối cảnh trong cuộc trò chuyện
Nếu bạn nhận thấy Gemini không nhớ được bối cảnh trước đó trong cuộc trò chuyện:
- Xác nhận rằng hàm
chatSession
được chú giải bằng@Riverpod(keepAlive: true)
- Kiểm tra để đảm bảo bạn đang sử dụng lại cùng một phiên trò chuyện cho tất cả các lượt trao đổi tin nhắn
- Xác minh rằng phiên trò chuyện được khởi tạo đúng cách trước khi gửi tin nhắn
Vấn đề về nền tảng
Đối với các vấn đề cụ thể về nền tảng:
- iOS/macOS: Đảm bảo bạn đã đặt các quyền phù hợp và định cấu hình phiên bản tối thiểu
- Android: Xác minh rằng bạn đã đặt phiên bản SDK tối thiểu một cách chính xác
- Kiểm tra thông báo lỗi dành riêng cho nền tảng trong bảng điều khiển
Các khái niệm chính đã học
- Thiết lập Firebase trong ứng dụng Flutter
- Định cấu hình Firebase AI Logic để truy cập vào Gemini
- Tạo trình cung cấp Riverpod cho các dịch vụ không đồng bộ
- Triển khai dịch vụ trò chuyện giao tiếp với LLM
- Xử lý các trạng thái API không đồng bộ (đang tải, lỗi, dữ liệu)
- Tìm hiểu quy trình giao tiếp và các phiên trò chuyện của LLM
4. Viết câu lệnh hiệu quả để mô tả màu sắc
Trong bước này, bạn sẽ tạo và triển khai một câu lệnh hệ thống hướng dẫn Gemini diễn giải nội dung mô tả màu sắc. Câu lệnh hệ thống là một cách hiệu quả để tuỳ chỉnh hành vi của LLM cho các tác vụ cụ thể mà không cần thay đổi mã.
Kiến thức bạn sẽ học được trong bước này
- Tìm hiểu về câu lệnh hệ thống và tầm quan trọng của câu lệnh này trong các ứng dụng LLM
- Tạo câu lệnh hiệu quả cho các tác vụ theo miền cụ thể
- Tải và sử dụng câu lệnh hệ thống trong ứng dụng Flutter
- Hướng dẫn LLM đưa ra các câu trả lời có định dạng nhất quán
- Thử nghiệm cách câu lệnh hệ thống ảnh hưởng đến hành vi của LLM
Tìm hiểu về câu lệnh hệ thống
Trước khi đi vào quá trình triển khai, hãy tìm hiểu xem câu lệnh hệ thống là gì và tại sao chúng lại quan trọng:
Câu lệnh hệ thống là gì?
Câu lệnh hệ thống là một loại chỉ dẫn đặc biệt được đưa ra cho LLM để đặt bối cảnh, nguyên tắc về hành vi và kỳ vọng đối với các câu trả lời của LLM. Không giống như tin nhắn của người dùng, câu lệnh hệ thống:
- Thiết lập vai trò và tính cách của LLM
- Xác định kiến thức hoặc khả năng chuyên môn
- Cung cấp hướng dẫn định dạng
- Đặt điều kiện ràng buộc cho câu trả lời
- Mô tả cách xử lý nhiều tình huống
Hãy coi câu lệnh hệ thống như việc cung cấp cho LLM "bản mô tả công việc" – câu lệnh này cho mô hình biết cách hành xử trong suốt cuộc trò chuyện.
Lý do câu lệnh hệ thống có vai trò quan trọng
Câu lệnh hệ thống đóng vai trò quan trọng trong việc tạo ra các lượt tương tác nhất quán và hữu ích với LLM vì:
- Đảm bảo tính nhất quán: Hướng dẫn mô hình đưa ra câu trả lời ở định dạng nhất quán
- Cải thiện mức độ liên quan: Tập trung mô hình vào miền cụ thể của bạn (trong trường hợp của bạn là màu sắc)
- Thiết lập ranh giới: Xác định những việc mà mô hình nên và không nên làm
- Nâng cao trải nghiệm người dùng: Tạo một kiểu tương tác tự nhiên và hữu ích hơn
- Giảm quá trình xử lý hậu kỳ: Nhận phản hồi ở những định dạng dễ phân tích cú pháp hoặc hiển thị hơn
Đối với ứng dụng Colorist, bạn cần LLM diễn giải nhất quán nội dung mô tả màu sắc và cung cấp các giá trị RGB theo một định dạng cụ thể.
Tạo một thành phần câu lệnh hệ thống
Trước tiên, bạn sẽ tạo một tệp câu lệnh hệ thống sẽ được tải trong thời gian chạy. Phương pháp này cho phép bạn sửa đổi lời nhắc mà không cần biên dịch lại ứng dụng.
Tạo tệp assets/system_prompt.md
mới có nội dung sau:
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
Tìm hiểu cấu trúc câu lệnh hệ thống
Hãy cùng phân tích chức năng của câu lệnh này:
- Định nghĩa vai trò: Thiết lập LLM làm "trợ lý chuyên gia về màu sắc"
- Giải thích về nhiệm vụ: Xác định nhiệm vụ chính là diễn giải nội dung mô tả màu thành các giá trị RGB
- Định dạng phản hồi: Chỉ định chính xác cách định dạng các giá trị RGB để đảm bảo tính nhất quán
- Ví dụ về lượt trao đổi: Cung cấp một ví dụ cụ thể về mẫu tương tác dự kiến
- Xử lý trường hợp hiếm gặp: Hướng dẫn cách xử lý nội dung mô tả không rõ ràng
- Các quy tắc ràng buộc và nguyên tắc: Đặt các ranh giới như giữ giá trị RGB trong khoảng từ 0 đến 1
Phương pháp có cấu trúc này đảm bảo câu trả lời của LLM sẽ nhất quán, giàu thông tin và được định dạng theo cách dễ phân tích cú pháp nếu bạn muốn trích xuất các giá trị RGB theo phương thức lập trình.
Cập nhật pubspec.yaml
Bây giờ, hãy cập nhật phần cuối của pubspec.yaml
để thêm thư mục tài sản:
pubspec.yaml
flutter:
uses-material-design: true
assets:
- assets/
Chạy flutter pub get
để làm mới gói tài sản.
Tạo trình cung cấp câu lệnh hệ thống
Tạo một tệp lib/providers/system_prompt.dart
mới để tải câu lệnh hệ thống:
lib/providers/system_prompt.dart
import 'package:flutter/services.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'system_prompt.g.dart';
@riverpod
Future<String> systemPrompt(Ref ref) =>
rootBundle.loadString('assets/system_prompt.md');
Trình cung cấp này sử dụng hệ thống tải tài sản của Flutter để đọc tệp lời nhắc trong thời gian chạy.
Cập nhật trình cung cấp mô hình Gemini
Bây giờ, hãy sửa đổi tệp lib/providers/gemini.dart
để đưa lời nhắc hệ thống vào:
lib/providers/gemini.dart
import 'dart:async';
import 'package:firebase_ai/firebase_ai.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import '../firebase_options.dart';
import 'system_prompt.dart'; // Add this import
part 'gemini.g.dart';
@riverpod
Future<FirebaseApp> firebaseApp(Ref ref) =>
Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
@riverpod
Future<GenerativeModel> geminiModel(Ref ref) async {
await ref.watch(firebaseAppProvider.future);
final systemPrompt = await ref.watch(systemPromptProvider.future); // Add this line
final model = FirebaseAI.googleAI().generativeModel(
model: 'gemini-2.0-flash',
systemInstruction: Content.system(systemPrompt), // And this line
);
return model;
}
@Riverpod(keepAlive: true)
Future<ChatSession> chatSession(Ref ref) async {
final model = await ref.watch(geminiModelProvider.future);
return model.startChat();
}
Thay đổi chính là việc thêm systemInstruction: Content.system(systemPrompt)
khi tạo mô hình tạo sinh. Thao tác này cho phép Gemini sử dụng chỉ dẫn của bạn làm câu lệnh hệ thống cho mọi lượt tương tác trong phiên trò chuyện này.
Tạo mã Riverpod
Chạy lệnh build runner để tạo mã Riverpod cần thiết:
dart run build_runner build --delete-conflicting-outputs
Chạy và kiểm thử ứng dụng
Giờ thì hãy chạy ứng dụng của bạn:
flutter run -d DEVICE
Hãy thử kiểm tra bằng nhiều nội dung mô tả màu sắc:
- "Tôi muốn có một chiếc màu xanh da trời"
- "Cho tôi màu xanh lục của rừng"
- "Tạo màu cam hoàng hôn tươi sáng"
- "Tôi muốn màu hoa oải hương tươi"
- "Cho tôi xem màu gì đó như xanh dương đậm của đại dương"
Bạn sẽ thấy rằng giờ đây Gemini phản hồi bằng những lời giải thích mang tính trò chuyện về các màu cùng với các giá trị RGB được định dạng nhất quán. Câu lệnh hệ thống đã hướng dẫn LLM một cách hiệu quả để cung cấp loại câu trả lời mà bạn cần.
Bạn cũng có thể thử yêu cầu AI tạo nội dung bên ngoài bối cảnh màu sắc. Ví dụ: nguyên nhân chính dẫn đến Chiến tranh Hoa hồng. Bạn sẽ nhận thấy sự khác biệt so với bước trước.
Tầm quan trọng của việc thiết kế câu lệnh cho các tác vụ chuyên biệt
Câu lệnh hệ thống vừa là nghệ thuật vừa là khoa học. Đây là một phần quan trọng của quá trình tích hợp LLM, có thể ảnh hưởng đáng kể đến mức độ hữu ích của mô hình đối với ứng dụng cụ thể của bạn. Những gì bạn đã làm ở đây là một dạng kỹ thuật tạo câu lệnh – điều chỉnh hướng dẫn để mô hình hoạt động theo cách phù hợp với nhu cầu của ứng dụng.
Thiết kế câu lệnh hiệu quả bao gồm:
- Định nghĩa rõ ràng về vai trò: Xác định mục đích của LLM
- Hướng dẫn rõ ràng: Nêu chi tiết chính xác cách LLM nên phản hồi
- Ví dụ cụ thể: Cho thấy thay vì chỉ nói về những phản hồi phù hợp
- Xử lý trường hợp đặc biệt: Hướng dẫn LLM cách xử lý các tình huống mơ hồ
- Quy cách định dạng: Đảm bảo các câu trả lời được cấu trúc theo cách nhất quán và hữu ích
Câu lệnh hệ thống mà bạn đã tạo sẽ biến các chức năng chung của Gemini thành một trợ lý diễn giải màu sắc chuyên biệt, cung cấp các câu trả lời được định dạng dành riêng cho nhu cầu của ứng dụng. Đây là một mẫu mạnh mẽ mà bạn có thể áp dụng cho nhiều miền và nhiệm vụ khác nhau.
Tiếp theo là gì?
Ở bước tiếp theo, bạn sẽ xây dựng dựa trên nền tảng này bằng cách thêm các khai báo hàm. Nhờ đó, LLM không chỉ đề xuất các giá trị RGB mà còn thực sự gọi các hàm trong ứng dụng của bạn để đặt màu trực tiếp. Điều này minh hoạ cách LLM có thể thu hẹp khoảng cách giữa ngôn ngữ tự nhiên và các tính năng cụ thể của ứng dụng.
Khắc phục sự cố
Vấn đề khi tải thành phần
Nếu bạn gặp lỗi khi tải câu lệnh hệ thống, hãy làm như sau:
- Xác minh rằng
pubspec.yaml
liệt kê chính xác thư mục tài sản - Kiểm tra để đảm bảo đường dẫn trong
rootBundle.loadString()
khớp với vị trí tệp của bạn - Chạy
flutter clean
, sau đó chạyflutter pub get
để làm mới gói tài sản
Câu trả lời không nhất quán
Nếu LLM không nhất quán tuân theo hướng dẫn về định dạng của bạn:
- Hãy thử nêu rõ hơn các yêu cầu về định dạng trong câu lệnh hệ thống
- Thêm nhiều ví dụ hơn để minh hoạ mẫu dự kiến
- Đảm bảo rằng định dạng bạn yêu cầu là hợp lý đối với mô hình
Giới hạn tốc độ API
Nếu bạn gặp lỗi liên quan đến việc giới hạn tốc độ:
- Xin lưu ý rằng dịch vụ Firebase AI Logic có giới hạn sử dụng
- Cân nhắc triển khai logic thử lại bằng thuật toán thời gian đợi luỹ thừa
- Kiểm tra bảng điều khiển của Firebase để xem có vấn đề nào về hạn mức hay không
Các khái niệm chính đã học
- Tìm hiểu vai trò và tầm quan trọng của câu lệnh hệ thống trong các ứng dụng LLM
- Tạo câu lệnh hiệu quả bằng hướng dẫn, ví dụ và các quy tắc ràng buộc rõ ràng
- Tải và sử dụng câu lệnh hệ thống trong ứng dụng Flutter
- Hướng dẫn hành vi của LLM cho các tác vụ dành riêng cho miền
- Sử dụng kỹ thuật tạo câu lệnh để định hình câu trả lời của LLM
Bước này minh hoạ cách bạn có thể tuỳ chỉnh đáng kể hành vi của LLM mà không cần thay đổi mã – chỉ cần đưa ra hướng dẫn rõ ràng trong lời nhắc hệ thống.
5. Khai báo hàm cho các công cụ LLM
Ở bước này, bạn sẽ bắt đầu công việc cho phép Gemini thực hiện hành động trong ứng dụng của bạn bằng cách triển khai khai báo hàm. Tính năng mạnh mẽ này cho phép LLM không chỉ đề xuất các giá trị RGB mà còn thực sự đặt các giá trị đó trong giao diện người dùng của ứng dụng thông qua các lệnh gọi công cụ chuyên dụng. Tuy nhiên, bạn sẽ cần thực hiện bước tiếp theo để xem các yêu cầu LLM được thực thi trong ứng dụng Flutter.
Kiến thức bạn sẽ học được trong bước này
- Tìm hiểu về tính năng gọi hàm LLM và lợi ích của tính năng này đối với các ứng dụng Flutter
- Xác định khai báo hàm dựa trên giản đồ cho Gemini
- Tích hợp khai báo hàm với mô hình Gemini
- Cập nhật câu lệnh hệ thống để tận dụng các chức năng của công cụ
Tìm hiểu về tính năng gọi hàm
Trước khi triển khai khai báo hàm, hãy tìm hiểu xem khai báo hàm là gì và tại sao khai báo hàm lại có giá trị:
Gọi hàm là gì?
Gọi hàm (đôi khi được gọi là "sử dụng công cụ") là một khả năng cho phép LLM:
- Nhận biết thời điểm người dùng yêu cầu một chức năng cụ thể
- Tạo một đối tượng JSON có cấu trúc với các tham số cần thiết cho hàm đó
- Cho phép ứng dụng của bạn thực thi hàm bằng các tham số đó
- Nhận kết quả của hàm và kết hợp kết quả đó vào phản hồi của hàm
Thay vì chỉ mô tả những việc cần làm, tính năng gọi hàm cho phép LLM kích hoạt các hành động cụ thể trong ứng dụng của bạn.
Tại sao tính năng gọi hàm lại quan trọng đối với các ứng dụng Flutter
Tính năng gọi hàm tạo ra một cầu nối mạnh mẽ giữa ngôn ngữ tự nhiên và các tính năng của ứng dụng:
- Hành động trực tiếp: Người dùng có thể mô tả những gì họ muốn bằng ngôn ngữ tự nhiên và ứng dụng sẽ phản hồi bằng các hành động cụ thể
- Đầu ra có cấu trúc: LLM tạo ra dữ liệu có cấu trúc rõ ràng thay vì văn bản cần phân tích cú pháp
- Các thao tác phức tạp: Cho phép LLM truy cập vào dữ liệu bên ngoài, thực hiện các phép tính hoặc sửa đổi trạng thái ứng dụng
- Trải nghiệm người dùng tốt hơn: Tạo sự tích hợp liền mạch giữa cuộc trò chuyện và chức năng
Trong ứng dụng Colorist, tính năng gọi hàm cho phép người dùng nói "Tôi muốn màu xanh lục đậm" và giao diện người dùng sẽ cập nhật ngay màu đó mà không cần phân tích cú pháp các giá trị RGB từ văn bản.
Xác định khai báo hàm
Tạo một tệp lib/services/gemini_tools.dart
mới để xác định các khai báo hàm:
lib/services/gemini_tools.dart
import 'package:firebase_ai/firebase_ai.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'gemini_tools.g.dart';
class GeminiTools {
GeminiTools(this.ref);
final Ref ref;
FunctionDeclaration get setColorFuncDecl => FunctionDeclaration(
'set_color',
'Set the color of the display square based on red, green, and blue values.',
parameters: {
'red': Schema.number(description: 'Red component value (0.0 - 1.0)'),
'green': Schema.number(description: 'Green component value (0.0 - 1.0)'),
'blue': Schema.number(description: 'Blue component value (0.0 - 1.0)'),
},
);
List<Tool> get tools => [
Tool.functionDeclarations([setColorFuncDecl]),
];
}
@riverpod
GeminiTools geminiTools(Ref ref) => GeminiTools(ref);
Tìm hiểu về khai báo hàm
Hãy phân tích những gì mà đoạn mã này thực hiện:
- Đặt tên hàm: Bạn đặt tên cho hàm
set_color
để cho biết rõ mục đích của hàm đó - Mô tả hàm: Bạn cung cấp nội dung mô tả rõ ràng để giúp LLM hiểu được thời điểm sử dụng hàm
- Định nghĩa tham số: Bạn xác định các tham số có cấu trúc bằng nội dung mô tả riêng:
red
: Thành phần màu đỏ của RGB, được chỉ định dưới dạng một số từ 0 đến 1green
: Thành phần màu xanh lục của RGB, được chỉ định dưới dạng một số từ 0 đến 1blue
: Thành phần màu xanh dương của RGB, được chỉ định dưới dạng một số từ 0 đến 1
- Các loại giản đồ: Bạn sử dụng
Schema.number()
để cho biết đây là các giá trị số - Tập hợp công cụ: Bạn tạo một danh sách công cụ chứa khai báo hàm của bạn
Phương pháp có cấu trúc này giúp LLM của Gemini hiểu được:
- Thời điểm cần gọi hàm này
- Những tham số cần cung cấp
- Những ràng buộc nào áp dụng cho các tham số đó (chẳng hạn như phạm vi giá trị)
Cập nhật trình cung cấp mô hình Gemini
Bây giờ, hãy sửa đổi tệp lib/providers/gemini.dart
để thêm các khai báo hàm khi khởi động mô hình Gemini:
lib/providers/gemini.dart
import 'dart:async';
import 'package:firebase_ai/firebase_ai.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import '../firebase_options.dart';
import '../services/gemini_tools.dart'; // Add this import
import 'system_prompt.dart';
part 'gemini.g.dart';
@riverpod
Future<FirebaseApp> firebaseApp(Ref ref) =>
Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
@riverpod
Future<GenerativeModel> geminiModel(Ref ref) async {
await ref.watch(firebaseAppProvider.future);
final systemPrompt = await ref.watch(systemPromptProvider.future);
final geminiTools = ref.watch(geminiToolsProvider); // Add this line
final model = FirebaseAI.googleAI().generativeModel(
model: 'gemini-2.0-flash',
systemInstruction: Content.system(systemPrompt),
tools: geminiTools.tools, // And this line
);
return model;
}
@Riverpod(keepAlive: true)
Future<ChatSession> chatSession(Ref ref) async {
final model = await ref.watch(geminiModelProvider.future);
return model.startChat();
}
Thay đổi chính là việc thêm tham số tools: geminiTools.tools
khi tạo mô hình tạo sinh. Điều này giúp Gemini biết được những hàm mà Gemini có thể gọi.
Cập nhật câu lệnh hệ thống
Giờ đây, bạn cần sửa đổi câu lệnh hệ thống để hướng dẫn LLM về cách sử dụng công cụ set_color
mới. Cập nhật 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
Sau đây là những thay đổi chính đối với câu lệnh hệ thống:
- Giới thiệu về công cụ: Thay vì yêu cầu các giá trị RGB được định dạng, giờ đây, bạn có thể cho LLM biết về công cụ
set_color
- Quy trình đã sửa đổi: Bạn thay đổi bước 3 từ "định dạng các giá trị trong phản hồi" thành "sử dụng công cụ để đặt các giá trị"
- Ví dụ mới: Bạn cho biết cách phản hồi phải bao gồm một lệnh gọi công cụ thay vì văn bản được định dạng
- Xoá yêu cầu về định dạng: Vì đang sử dụng lệnh gọi hàm có cấu trúc, nên bạn không cần định dạng văn bản cụ thể nữa
Câu lệnh đã cập nhật này hướng dẫn LLM sử dụng tính năng gọi hàm thay vì chỉ cung cấp các giá trị RGB ở dạng văn bản.
Tạo mã Riverpod
Chạy lệnh build runner để tạo mã Riverpod cần thiết:
dart run build_runner build --delete-conflicting-outputs
Chạy ứng dụng
Tại thời điểm này, Gemini sẽ tạo nội dung cố gắng sử dụng tính năng gọi hàm, nhưng bạn chưa triển khai trình xử lý cho các lệnh gọi hàm. Khi chạy ứng dụng và mô tả một màu sắc, bạn sẽ thấy Gemini phản hồi như thể đã gọi một công cụ, nhưng bạn sẽ không thấy bất kỳ thay đổi nào về màu sắc trong giao diện người dùng cho đến bước tiếp theo.
Chạy ứng dụng:
flutter run -d DEVICE
Hãy thử mô tả một màu như "xanh dương đậm của đại dương" hoặc "xanh lá cây của rừng" và quan sát các câu trả lời. LLM đang cố gắng gọi các hàm được xác định ở trên, nhưng mã của bạn chưa phát hiện được các lệnh gọi hàm.
Quy trình gọi hàm
Hãy tìm hiểu điều gì sẽ xảy ra khi Gemini sử dụng tính năng gọi hàm:
- Lựa chọn chức năng: LLM quyết định xem lệnh gọi hàm có hữu ích hay không dựa trên yêu cầu của người dùng
- Tạo tham số: LLM tạo ra các giá trị tham số phù hợp với giản đồ của hàm
- Định dạng lệnh gọi hàm: LLM gửi một đối tượng lệnh gọi hàm có cấu trúc trong phản hồi của mình
- Xử lý ứng dụng: Ứng dụng của bạn sẽ nhận được lệnh gọi này và thực thi hàm có liên quan (được triển khai ở bước tiếp theo)
- Tích hợp phản hồi: Trong các cuộc trò chuyện nhiều lượt, LLM dự kiến kết quả của hàm sẽ được trả về
Trong trạng thái hiện tại của ứng dụng, 3 bước đầu tiên đang diễn ra, nhưng bạn chưa triển khai bước 4 hoặc 5 (xử lý các lệnh gọi hàm). Bạn sẽ thực hiện việc này ở bước tiếp theo.
Thông tin kỹ thuật: Cách Gemini quyết định thời điểm sử dụng các hàm
Gemini đưa ra quyết định thông minh về thời điểm sử dụng các hàm dựa trên:
- Ý định của người dùng: Liệu yêu cầu của người dùng có được đáp ứng tốt nhất bằng một hàm hay không
- Mức độ liên quan của hàm: Mức độ phù hợp giữa các hàm hiện có với nhiệm vụ
- Mức độ sẵn có của tham số: Liệu có thể xác định chắc chắn các giá trị tham số hay không
- Hướng dẫn của hệ thống: Hướng dẫn từ câu lệnh hệ thống về cách sử dụng hàm
Bằng cách cung cấp các khai báo hàm rõ ràng và hướng dẫn hệ thống, bạn đã thiết lập Gemini để nhận dạng các yêu cầu mô tả màu sắc là cơ hội gọi hàm set_color
.
Tiếp theo là gì?
Trong bước tiếp theo, bạn sẽ triển khai trình xử lý cho các lệnh gọi hàm đến từ Gemini. Thao tác này sẽ hoàn tất vòng tròn, cho phép nội dung mô tả của người dùng kích hoạt các thay đổi thực tế về màu sắc trong giao diện người dùng thông qua các lệnh gọi hàm của LLM.
Khắc phục sự cố
Vấn đề về khai báo hàm
Nếu bạn gặp lỗi với khai báo hàm:
- Kiểm tra để đảm bảo tên và loại tham số khớp với những gì bạn mong đợi
- Xác minh rằng tên hàm rõ ràng và mang tính mô tả
- Đảm bảo nội dung mô tả hàm giải thích chính xác mục đích của hàm
Vấn đề về câu lệnh hệ thống
Nếu LLM không cố gắng sử dụng hàm:
- Xác minh rằng câu lệnh hệ thống của bạn hướng dẫn rõ ràng cho LLM sử dụng công cụ
set_color
- Kiểm tra để đảm bảo ví dụ trong câu lệnh hệ thống minh hoạ cách sử dụng hàm
- Hãy thử đưa ra chỉ dẫn rõ ràng hơn về cách sử dụng công cụ này
Vấn đề chung
Nếu bạn gặp phải vấn đề khác, hãy làm như sau:
- Kiểm tra bảng điều khiển để xem có lỗi nào liên quan đến khai báo hàm hay không
- Xác minh rằng các công cụ được chuyển đến mô hình đúng cách
- Đảm bảo tất cả mã do Riverpod tạo đều là mã mới nhất
Các khái niệm chính đã học
- Xác định các khai báo hàm để mở rộng khả năng của LLM trong các ứng dụng Flutter
- Tạo giản đồ tham số để thu thập dữ liệu có cấu trúc
- Tích hợp khai báo hàm với mô hình Gemini
- Cập nhật câu lệnh hệ thống để khuyến khích việc sử dụng hàm
- Tìm hiểu cách LLM chọn và gọi hàm
Bước này minh hoạ cách LLM có thể thu hẹp khoảng cách giữa dữ liệu đầu vào bằng ngôn ngữ tự nhiên và các lệnh gọi hàm có cấu trúc, đặt nền tảng cho việc tích hợp liền mạch giữa cuộc trò chuyện và các tính năng của ứng dụng.
6. Triển khai việc xử lý công cụ
Ở bước này, bạn sẽ triển khai trình xử lý cho các lệnh gọi hàm đến từ Gemini. Điều này hoàn tất vòng tròn giao tiếp giữa các đầu vào bằng ngôn ngữ tự nhiên và các tính năng cụ thể của ứng dụng, cho phép LLM trực tiếp thao tác với giao diện người dùng dựa trên nội dung mô tả của người dùng.
Kiến thức bạn sẽ học được trong bước này
- Tìm hiểu quy trình gọi hàm hoàn chỉnh trong các ứng dụng LLM
- Xử lý lệnh gọi hàm từ Gemini trong ứng dụng Flutter
- Triển khai trình xử lý hàm sửa đổi trạng thái ứng dụng
- Xử lý các phản hồi của hàm và trả về kết quả cho LLM
- Tạo một luồng giao tiếp hoàn chỉnh giữa LLM và giao diện người dùng
- Ghi nhật ký các lệnh gọi và phản hồi của hàm để đảm bảo tính minh bạch
Tìm hiểu quy trình gọi hàm
Trước khi đi sâu vào quá trình triển khai, hãy tìm hiểu quy trình gọi hàm hoàn chỉnh:
Quy trình toàn diện
- Hoạt động đầu vào của người dùng: Người dùng mô tả một màu bằng ngôn ngữ tự nhiên (ví dụ: "forest green")
- Xử lý LLM: Gemini phân tích nội dung mô tả và quyết định gọi hàm
set_color
- Tạo lệnh gọi hàm: Gemini tạo một JSON có cấu trúc với các tham số (giá trị màu đỏ, xanh lục, xanh dương)
- Nhận lệnh gọi hàm: Ứng dụng của bạn nhận dữ liệu có cấu trúc này từ Gemini
- Thực thi hàm: Ứng dụng của bạn thực thi hàm bằng các tham số được cung cấp
- Cập nhật trạng thái: Hàm này cập nhật trạng thái của ứng dụng (thay đổi màu hiển thị)
- Tạo câu trả lời: Hàm của bạn trả về kết quả cho LLM
- Kết hợp phản hồi: LLM kết hợp những kết quả này vào phản hồi cuối cùng
- Cập nhật giao diện người dùng: Giao diện người dùng của bạn phản ứng với sự thay đổi trạng thái, hiển thị màu mới
Chu trình giao tiếp hoàn chỉnh là điều cần thiết để tích hợp LLM đúng cách. Khi thực hiện một lệnh gọi hàm, LLM không chỉ gửi yêu cầu rồi tiếp tục. Thay vào đó, nó sẽ đợi ứng dụng của bạn thực thi hàm và trả về kết quả. Sau đó, LLM sẽ sử dụng những kết quả này để đưa ra câu trả lời cuối cùng, tạo ra một luồng trò chuyện tự nhiên ghi nhận những hành động đã thực hiện.
Triển khai trình xử lý hàm
Hãy cập nhật tệp lib/services/gemini_tools.dart
để thêm trình xử lý cho các lệnh gọi hàm:
lib/services/gemini_tools.dart
import 'package:colorist_ui/colorist_ui.dart';
import 'package:firebase_ai/firebase_ai.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'gemini_tools.g.dart';
class GeminiTools {
GeminiTools(this.ref);
final Ref ref;
FunctionDeclaration get setColorFuncDecl => FunctionDeclaration(
'set_color',
'Set the color of the display square based on red, green, and blue values.',
parameters: {
'red': Schema.number(description: 'Red component value (0.0 - 1.0)'),
'green': Schema.number(description: 'Green component value (0.0 - 1.0)'),
'blue': Schema.number(description: 'Blue component value (0.0 - 1.0)'),
},
);
List<Tool> get tools => [
Tool.functionDeclarations([setColorFuncDecl]),
];
Map<String, Object?> handleFunctionCall( // Add from here
String functionName,
Map<String, Object?> arguments,
) {
final logStateNotifier = ref.read(logStateProvider.notifier);
logStateNotifier.logFunctionCall(functionName, arguments);
return switch (functionName) {
'set_color' => handleSetColor(arguments),
_ => handleUnknownFunction(functionName),
};
}
Map<String, Object?> handleSetColor(Map<String, Object?> arguments) {
final colorStateNotifier = ref.read(colorStateProvider.notifier);
final red = (arguments['red'] as num).toDouble();
final green = (arguments['green'] as num).toDouble();
final blue = (arguments['blue'] as num).toDouble();
final functionResults = {
'success': true,
'current_color': colorStateNotifier
.updateColor(red: red, green: green, blue: blue)
.toLLMContextMap(),
};
final logStateNotifier = ref.read(logStateProvider.notifier);
logStateNotifier.logFunctionResults(functionResults);
return functionResults;
}
Map<String, Object?> handleUnknownFunction(String functionName) {
final logStateNotifier = ref.read(logStateProvider.notifier);
logStateNotifier.logWarning('Unsupported function call $functionName');
return {
'success': false,
'reason': 'Unsupported function call $functionName',
};
} // To here.
}
@riverpod
GeminiTools geminiTools(Ref ref) => GeminiTools(ref);
Tìm hiểu về trình xử lý hàm
Hãy xem xét chức năng của các trình xử lý hàm này:
handleFunctionCall
: Một bộ điều phối trung tâm có:- Ghi lại lệnh gọi hàm để đảm bảo tính minh bạch trong bảng điều khiển nhật ký
- Định tuyến đến trình xử lý thích hợp dựa trên tên hàm
- Trả về một phản hồi có cấu trúc sẽ được gửi lại cho LLM
handleSetColor
: Trình xử lý cụ thể cho hàmset_color
của bạn, có chức năng:- Trích xuất các giá trị RGB từ bản đồ đối số
- Chuyển đổi các giá trị này thành các loại dự kiến (số thực có độ chính xác gấp đôi)
- Cập nhật trạng thái màu của ứng dụng bằng cách sử dụng
colorStateNotifier
- Tạo một phản hồi có cấu trúc với trạng thái thành công và thông tin về màu hiện tại
- Ghi lại kết quả của hàm để gỡ lỗi
handleUnknownFunction
: Trình xử lý dự phòng cho các hàm không xác định:- Ghi nhật ký cảnh báo về hàm không được hỗ trợ
- Trả về một thông báo lỗi cho LLM
Hàm handleSetColor
đặc biệt quan trọng vì nó thu hẹp khoảng cách giữa khả năng hiểu ngôn ngữ tự nhiên của LLM và những thay đổi cụ thể về giao diện người dùng.
Cập nhật dịch vụ trò chuyện Gemini để xử lý các lệnh gọi và phản hồi của hàm
Bây giờ, hãy cập nhật tệp lib/services/gemini_chat_service.dart
để xử lý các lệnh gọi hàm từ phản hồi của LLM và gửi kết quả trở lại LLM:
lib/services/gemini_chat_service.dart
import 'dart:async';
import 'package:colorist_ui/colorist_ui.dart';
import 'package:firebase_ai/firebase_ai.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import '../providers/gemini.dart';
import 'gemini_tools.dart'; // Add this import
part 'gemini_chat_service.g.dart';
class GeminiChatService {
GeminiChatService(this.ref);
final Ref ref;
Future<void> sendMessage(String message) async {
final chatSession = await ref.read(chatSessionProvider.future);
final chatStateNotifier = ref.read(chatStateProvider.notifier);
final logStateNotifier = ref.read(logStateProvider.notifier);
chatStateNotifier.addUserMessage(message);
logStateNotifier.logUserText(message);
final llmMessage = chatStateNotifier.createLlmMessage();
try {
final response = await chatSession.sendMessage(Content.text(message));
final responseText = response.text;
if (responseText != null) {
logStateNotifier.logLlmText(responseText);
chatStateNotifier.appendToMessage(llmMessage.id, responseText);
}
if (response.functionCalls.isNotEmpty) { // Add from here
final geminiTools = ref.read(geminiToolsProvider);
final functionResultResponse = await chatSession.sendMessage(
Content.functionResponses([
for (final functionCall in response.functionCalls)
FunctionResponse(
functionCall.name,
geminiTools.handleFunctionCall(
functionCall.name,
functionCall.args,
),
),
]),
);
final responseText = functionResultResponse.text;
if (responseText != null) {
logStateNotifier.logLlmText(responseText);
chatStateNotifier.appendToMessage(llmMessage.id, responseText);
}
} // To here.
} catch (e, st) {
logStateNotifier.logError(e, st: st);
chatStateNotifier.appendToMessage(
llmMessage.id,
"\nI'm sorry, I encountered an error processing your request. "
"Please try again.",
);
} finally {
chatStateNotifier.finalizeMessage(llmMessage.id);
}
}
}
@riverpod
GeminiChatService geminiChatService(Ref ref) => GeminiChatService(ref);
Tìm hiểu quy trình giao tiếp
Điểm bổ sung chính ở đây là việc xử lý hoàn chỉnh các lệnh gọi hàm và phản hồi:
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);
}
}
Mã này:
- Kiểm tra xem phản hồi của LLM có chứa lệnh gọi hàm nào hay không
- Đối với mỗi lệnh gọi hàm, hãy gọi phương thức
handleFunctionCall
bằng tên và đối số của hàm - Thu thập kết quả của từng lệnh gọi hàm
- Gửi những kết quả này trở lại LLM bằng cách sử dụng
Content.functionResponses
- Xử lý phản hồi của LLM đối với kết quả của hàm
- Cập nhật giao diện người dùng bằng văn bản phản hồi cuối cùng
Thao tác này sẽ tạo ra một quy trình khứ hồi:
- Người dùng → LLM: Yêu cầu một màu
- LLM → Ứng dụng: Lệnh gọi hàm có tham số
- Ứng dụng → Người dùng: Màu mới xuất hiện
- Ứng dụng → LLM: Kết quả của hàm
- LLM → Người dùng: Câu trả lời cuối cùng kết hợp kết quả của hàm
Tạo mã Riverpod
Chạy lệnh build runner để tạo mã Riverpod cần thiết:
dart run build_runner build --delete-conflicting-outputs
Chạy và kiểm thử toàn bộ quy trình
Giờ thì hãy chạy ứng dụng của bạn:
flutter run -d DEVICE
Hãy thử nhập nhiều nội dung mô tả màu sắc:
- "Tôi muốn có màu đỏ thẫm"
- "Cho tôi xem màu xanh dương dịu mắt"
- "Cho tôi biết màu của lá bạc hà tươi"
- "Tôi muốn xem màu cam hoàng hôn ấm áp"
- "Hãy làm cho nó có màu tím hoàng gia đậm"
Giờ đây, bạn sẽ thấy:
- Tin nhắn của bạn xuất hiện trong giao diện trò chuyện
- Câu trả lời của Gemini xuất hiện trong cuộc trò chuyện
- Các lệnh gọi hàm được ghi vào nhật ký trong bảng điều khiển nhật ký
- Kết quả của hàm được ghi lại ngay sau đó
- Hình chữ nhật màu cập nhật để hiển thị màu được mô tả
- Giá trị RGB cập nhật để cho biết các thành phần của màu mới
- Câu trả lời cuối cùng của Gemini xuất hiện, thường nhận xét về màu sắc đã được đặt
Bảng điều khiển nhật ký cung cấp thông tin chi tiết về những gì đang diễn ra ở chế độ nền. Bạn sẽ thấy:
- Chính xác những lệnh gọi hàm mà Gemini đang thực hiện
- Các thông số mà nó chọn cho từng giá trị RGB
- Kết quả mà hàm của bạn đang trả về
- Các câu trả lời nối tiếp của Gemini
Trình thông báo trạng thái màu
colorStateNotifier
mà bạn đang dùng để cập nhật màu sắc là một phần của gói colorist_ui
. Chiến dịch này quản lý:
- Màu hiện tại hiển thị trong giao diện người dùng
- Nhật ký màu (10 màu gần đây nhất)
- Thông báo về các thay đổi trạng thái cho thành phần giao diện người dùng
Khi bạn gọi updateColor
bằng các giá trị RGB mới, thao tác này sẽ:
- Tạo một đối tượng
ColorData
mới bằng các giá trị được cung cấp - Cập nhật màu hiện tại trong trạng thái ứng dụng
- Thêm màu vào nhật ký
- Kích hoạt các bản cập nhật giao diện người dùng thông qua tính năng quản lý trạng thái của Riverpod
Các thành phần giao diện người dùng trong gói colorist_ui
sẽ theo dõi trạng thái này và tự động cập nhật khi trạng thái thay đổi, tạo ra một trải nghiệm phản ứng nhanh.
Tìm hiểu về cách xử lý lỗi
Quá trình triển khai của bạn có tính năng xử lý lỗi mạnh mẽ:
- Khối try-catch: Bao bọc tất cả các hoạt động tương tác với LLM để nắm bắt mọi trường hợp ngoại lệ
- Ghi nhật ký lỗi: Ghi lại các lỗi trong bảng điều khiển nhật ký bằng dấu vết ngăn xếp
- Ý kiến phản hồi của người dùng: Cung cấp thông báo lỗi thân thiện trong cuộc trò chuyện
- Dọn dẹp trạng thái: Hoàn tất trạng thái thông báo ngay cả khi xảy ra lỗi
Điều này giúp đảm bảo ứng dụng vẫn ổn định và cung cấp thông tin phản hồi phù hợp ngay cả khi xảy ra vấn đề với dịch vụ LLM hoặc quá trình thực thi chức năng.
Sức mạnh của tính năng gọi hàm đối với trải nghiệm người dùng
Những gì bạn đã hoàn thành ở đây cho thấy cách LLM có thể tạo ra các giao diện tự nhiên mạnh mẽ:
- Giao diện ngôn ngữ tự nhiên: Người dùng thể hiện ý định bằng ngôn ngữ hằng ngày
- Diễn giải thông minh: LLM dịch nội dung mô tả mơ hồ thành các giá trị chính xác
- Thao tác trực tiếp: Giao diện người dùng cập nhật để phản hồi ngôn ngữ tự nhiên
- Câu trả lời theo bối cảnh: LLM cung cấp bối cảnh trò chuyện về các thay đổi
- Tải nhận thức thấp: Người dùng không cần hiểu các giá trị RGB hoặc lý thuyết về màu sắc
Bạn có thể mở rộng mẫu sử dụng tính năng gọi hàm LLM này để kết nối ngôn ngữ tự nhiên và các thao tác trên giao diện người dùng cho vô số miền khác ngoài việc chọn màu.
Tiếp theo là gì?
Trong bước tiếp theo, bạn sẽ nâng cao trải nghiệm người dùng bằng cách triển khai các phản hồi truyền trực tuyến. Thay vì chờ phản hồi hoàn chỉnh, bạn sẽ xử lý các đoạn văn bản và lệnh gọi hàm khi nhận được, tạo ra một ứng dụng phản hồi nhanh hơn và hấp dẫn hơn.
Khắc phục sự cố
Vấn đề về lệnh gọi hàm
Nếu Gemini không gọi các hàm của bạn hoặc các tham số không chính xác:
- Xác minh rằng khai báo hàm của bạn khớp với nội dung được mô tả trong lời nhắc hệ thống
- Kiểm tra để đảm bảo tên và loại tham số nhất quán
- Đảm bảo câu lệnh hệ thống của bạn hướng dẫn rõ ràng LLM sử dụng công cụ này
- Xác minh để đảm bảo tên hàm trong trình xử lý khớp chính xác với tên hàm trong khai báo
- Kiểm tra bảng điều khiển nhật ký để biết thông tin chi tiết về các lệnh gọi hàm
Vấn đề về phản hồi của hàm
Nếu kết quả của hàm không được gửi lại cho LLM đúng cách:
- Kiểm tra để đảm bảo hàm của bạn trả về một Bản đồ được định dạng đúng cách
- Xác minh rằng Content.functionResponses đang được tạo đúng cách
- Tìm mọi lỗi trong nhật ký liên quan đến phản hồi của hàm
- Đảm bảo bạn đang sử dụng cùng một phiên trò chuyện cho câu trả lời
Vấn đề về màn hình màu
Nếu màu sắc hiển thị không chính xác:
- Đảm bảo các giá trị RGB được chuyển đổi đúng cách thành số thực (LLM có thể gửi các giá trị này dưới dạng số nguyên)
- Xác minh rằng các giá trị nằm trong phạm vi dự kiến (0,0 đến 1,0)
- Kiểm tra để đảm bảo rằng trình thông báo trạng thái màu đang được gọi đúng cách
- Kiểm tra nhật ký để biết chính xác các giá trị được truyền vào hàm
Các vấn đề chung
Đối với các vấn đề chung:
- Kiểm tra nhật ký để tìm lỗi hoặc cảnh báo
- Xác minh khả năng kết nối Firebase AI Logic
- Kiểm tra xem có loại nào không khớp trong các tham số hàm không
- Đảm bảo tất cả mã do Riverpod tạo đều là mã mới nhất
Các khái niệm chính đã học
- Triển khai một quy trình gọi hàm hoàn chỉnh trong Flutter
- Tạo mối giao tiếp đầy đủ giữa một LLM và ứng dụng của bạn
- Xử lý dữ liệu có cấu trúc từ các phản hồi của LLM
- Gửi kết quả của hàm trở lại LLM để kết hợp vào các câu trả lời
- Sử dụng bảng điều khiển nhật ký để nắm được thông tin về các hoạt động tương tác giữa ứng dụng và LLM
- Kết nối câu lệnh sử dụng ngôn ngữ tự nhiên với các thay đổi cụ thể về giao diện người dùng
Sau khi hoàn tất bước này, ứng dụng của bạn hiện minh hoạ một trong những mẫu mạnh mẽ nhất để tích hợp LLM: dịch nội dung đầu vào bằng ngôn ngữ tự nhiên thành các hành động cụ thể trên giao diện người dùng, đồng thời duy trì một cuộc trò chuyện mạch lạc có ghi nhận những hành động này. Điều này tạo ra một giao diện đàm thoại trực quan, mang đến cảm giác kỳ diệu cho người dùng.
7. Truyền trực tuyến các câu trả lời để cải thiện trải nghiệm người dùng
Trong bước này, bạn sẽ nâng cao trải nghiệm người dùng bằng cách triển khai các câu trả lời truyền trực tuyến từ Gemini. Thay vì chờ toàn bộ phản hồi được tạo, bạn sẽ xử lý các đoạn văn bản và lệnh gọi hàm khi nhận được, tạo ra một ứng dụng phản hồi nhanh và hấp dẫn hơn.
Nội dung bạn sẽ tìm hiểu trong bước này
- Tầm quan trọng của tính năng truyền trực tuyến đối với các ứng dụng dựa trên LLM
- Triển khai các câu trả lời của LLM theo thời gian thực trong ứng dụng Flutter
- Xử lý các đoạn văn bản một phần khi chúng đến từ API
- Quản lý trạng thái cuộc trò chuyện để tránh xung đột tin nhắn
- Xử lý lệnh gọi hàm trong phản hồi truyền trực tuyến
- Tạo chỉ báo trực quan cho các câu trả lời đang được xử lý
Tại sao tính năng truyền phát trực tiếp lại quan trọng đối với các ứng dụng LLM
Trước khi triển khai, hãy tìm hiểu lý do tại sao việc truyền trực tuyến các câu trả lời lại rất quan trọng để tạo ra trải nghiệm tuyệt vời cho người dùng bằng LLM:
Nâng cao trải nghiệm người dùng
Phản hồi truyền trực tuyến mang lại một số lợi ích đáng kể cho trải nghiệm người dùng:
- Giảm độ trễ cảm nhận: Người dùng thấy văn bản bắt đầu xuất hiện ngay lập tức (thường trong vòng 100 – 300 mili giây), thay vì phải đợi vài giây để nhận được phản hồi hoàn chỉnh. Cảm giác về tính tức thời này giúp cải thiện đáng kể mức độ hài lòng của người dùng.
- Nhịp điệu đàm thoại tự nhiên: Văn bản xuất hiện từ từ, mô phỏng cách con người giao tiếp, tạo ra trải nghiệm đối thoại tự nhiên hơn.
- Xử lý thông tin từng bước: Người dùng có thể bắt đầu xử lý thông tin khi thông tin đến, thay vì bị choáng ngợp bởi một khối văn bản lớn cùng một lúc.
- Cơ hội để ngắt sớm: Trong một ứng dụng đầy đủ, người dùng có thể ngắt hoặc chuyển hướng LLM nếu thấy LLM đi theo hướng không hữu ích.
- Xác nhận trực quan về hoạt động: Văn bản truyền trực tiếp cung cấp thông tin phản hồi ngay lập tức rằng hệ thống đang hoạt động, giúp giảm sự không chắc chắn.
Ưu điểm về kỹ thuật
Ngoài những điểm cải thiện về trải nghiệm người dùng, tính năng phát trực tuyến còn mang lại những lợi ích về kỹ thuật:
- Thực thi hàm sớm: Các lệnh gọi hàm có thể được phát hiện và thực thi ngay khi xuất hiện trong luồng mà không cần chờ phản hồi hoàn chỉnh.
- Cập nhật giao diện người dùng gia tăng: Bạn có thể cập nhật giao diện người dùng một cách tăng dần khi có thông tin mới, tạo ra trải nghiệm linh hoạt hơn.
- Quản lý trạng thái trò chuyện: Tính năng truyền phát trực tiếp cung cấp tín hiệu rõ ràng về thời điểm hoàn tất phản hồi so với thời điểm phản hồi vẫn đang diễn ra, giúp quản lý trạng thái tốt hơn.
- Giảm rủi ro hết thời gian chờ: Với các phản hồi không truyền trực tuyến, các thế hệ chạy trong thời gian dài có nguy cơ hết thời gian chờ kết nối. Tính năng phát trực tuyến thiết lập kết nối sớm và duy trì kết nối đó.
Đối với ứng dụng Colorist, việc triển khai tính năng truyền phát trực tiếp có nghĩa là người dùng sẽ thấy cả phản hồi bằng văn bản và các thay đổi về màu sắc xuất hiện nhanh chóng hơn, mang lại trải nghiệm phản hồi nhanh hơn đáng kể.
Thêm tính năng quản lý trạng thái cuộc trò chuyện
Trước tiên, hãy thêm một trình cung cấp trạng thái để theo dõi xem ứng dụng hiện có đang xử lý phản hồi truyền trực tuyến hay không. Cập nhật tệp 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/legacy.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import '../providers/gemini.dart';
import 'gemini_tools.dart';
part 'gemini_chat_service.g.dart';
final conversationStateProvider = StateProvider( // Add from here...
(ref) => ConversationState.idle,
); // To here.
class GeminiChatService {
GeminiChatService(this.ref);
final Ref ref;
Future<void> sendMessage(String message) async {
final chatSession = await ref.read(chatSessionProvider.future);
final conversationState = ref.read(conversationStateProvider); // Add this line
final chatStateNotifier = ref.read(chatStateProvider.notifier);
final logStateNotifier = ref.read(logStateProvider.notifier);
if (conversationState == ConversationState.busy) { // Add from here...
logStateNotifier.logWarning(
"Can't send a message while a conversation is in progress",
);
throw Exception(
"Can't send a message while a conversation is in progress",
);
}
final conversationStateNotifier = ref.read(
conversationStateProvider.notifier,
);
conversationStateNotifier.state = ConversationState.busy; // To here.
chatStateNotifier.addUserMessage(message);
logStateNotifier.logUserText(message);
final llmMessage = chatStateNotifier.createLlmMessage();
try { // Modify from here...
final responseStream = chatSession.sendMessageStream(
Content.text(message),
);
await for (final block in responseStream) {
await _processBlock(block, llmMessage.id);
} // To here.
} catch (e, st) {
logStateNotifier.logError(e, st: st);
chatStateNotifier.appendToMessage(
llmMessage.id,
"\nI'm sorry, I encountered an error processing your request. "
"Please try again.",
);
} finally {
chatStateNotifier.finalizeMessage(llmMessage.id);
conversationStateNotifier.state = ConversationState.idle; // Add this line.
}
}
Future<void> _processBlock( // Add from here...
GenerateContentResponse block,
String llmMessageId,
) async {
final chatSession = await ref.read(chatSessionProvider.future);
final chatStateNotifier = ref.read(chatStateProvider.notifier);
final logStateNotifier = ref.read(logStateProvider.notifier);
final blockText = block.text;
if (blockText != null) {
logStateNotifier.logLlmText(blockText);
chatStateNotifier.appendToMessage(llmMessageId, blockText);
}
if (block.functionCalls.isNotEmpty) {
final geminiTools = ref.read(geminiToolsProvider);
final responseStream = chatSession.sendMessageStream(
Content.functionResponses([
for (final functionCall in block.functionCalls)
FunctionResponse(
functionCall.name,
geminiTools.handleFunctionCall(
functionCall.name,
functionCall.args,
),
),
]),
);
await for (final response in responseStream) {
final responseText = response.text;
if (responseText != null) {
logStateNotifier.logLlmText(responseText);
chatStateNotifier.appendToMessage(llmMessageId, responseText);
}
}
}
} // To here.
}
@riverpod
GeminiChatService geminiChatService(Ref ref) => GeminiChatService(ref);
Tìm hiểu về việc triển khai tính năng phát trực tuyến
Hãy phân tích những gì mà đoạn mã này thực hiện:
- Theo dõi trạng thái cuộc trò chuyện:
conversationStateProvider
theo dõi xem ứng dụng có đang xử lý một phản hồi hay không- Trạng thái chuyển từ
idle
→busy
trong khi xử lý, sau đó quay lạiidle
- Điều này ngăn chặn nhiều yêu cầu đồng thời có thể xung đột
- Khởi tạo luồng:
sendMessageStream()
trả về một luồng các khối phản hồi thay vìFuture
với phản hồi hoàn chỉnh- Mỗi đoạn có thể chứa văn bản, lệnh gọi hàm hoặc cả hai
- Xử lý từng bước:
await for
xử lý từng đoạn khi đoạn đó đến theo thời gian thực- Văn bản được thêm vào giao diện người dùng ngay lập tức, tạo hiệu ứng phát trực tuyến
- Các lệnh gọi hàm sẽ được thực thi ngay khi được phát hiện
- Xử lý lệnh gọi hàm:
- Khi một lệnh gọi hàm được phát hiện trong một đoạn, lệnh gọi đó sẽ được thực thi ngay lập tức
- Kết quả được gửi lại cho LLM thông qua một lệnh gọi truyền phát trực tiếp khác
- Phản hồi của LLM đối với những kết quả này cũng được xử lý theo cách truyền trực tuyến
- Xử lý lỗi và dọn dẹp:
try
/catch
cung cấp khả năng xử lý lỗi mạnh mẽ- Khối
finally
đảm bảo trạng thái cuộc trò chuyện được đặt lại đúng cách - Thông báo luôn được hoàn tất, ngay cả khi xảy ra lỗi
Việc triển khai này tạo ra trải nghiệm phát trực tuyến đáng tin cậy và có khả năng thích ứng trong khi vẫn duy trì trạng thái trò chuyện phù hợp.
Cập nhật màn hình chính để kết nối trạng thái trò chuyện
Sửa đổi tệp lib/main.dart
để truyền trạng thái cuộc trò chuyện đến màn hình chính:
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),
),
);
}
}
Thay đổi chính ở đây là việc truyền conversationState
đến tiện ích MainScreen
. MainScreen
(do gói colorist_ui
cung cấp) sẽ sử dụng trạng thái này để tắt tính năng nhập văn bản trong khi một phản hồi đang được xử lý.
Điều này tạo ra trải nghiệm nhất quán cho người dùng, trong đó giao diện người dùng phản ánh trạng thái hiện tại của cuộc trò chuyện.
Tạo mã Riverpod
Chạy lệnh build runner để tạo mã Riverpod cần thiết:
dart run build_runner build --delete-conflicting-outputs
Chạy và kiểm thử các câu trả lời truyền trực tuyến
Chạy ứng dụng của bạn:
flutter run -d DEVICE
Bây giờ, hãy thử kiểm tra hành vi phát trực tuyến với nhiều nội dung mô tả màu sắc. Hãy thử các nội dung mô tả như:
- "Cho tôi xem màu xanh dương đậm của đại dương lúc hoàng hôn"
- "Tôi muốn xem một rạn san hô rực rỡ gợi nhớ đến những bông hoa nhiệt đới"
- "Tạo màu xanh ô liu xỉn như màu áo quân phục cũ"
Quy trình kỹ thuật phát trực tuyến chi tiết
Hãy xem xét chính xác những gì xảy ra khi truyền trực tuyến một câu trả lời:
Thiết lập kết nối
Khi bạn gọi sendMessageStream()
, những điều sau sẽ xảy ra:
- Ứng dụng thiết lập kết nối với dịch vụ Firebase AI Logic
- Yêu cầu của người dùng được gửi đến dịch vụ
- Máy chủ bắt đầu xử lý yêu cầu
- Kết nối luồng vẫn mở, sẵn sàng truyền các đoạn
Truyền phân đoạn
Khi Gemini tạo nội dung, các khối sẽ được gửi qua luồng:
- Máy chủ sẽ gửi các đoạn văn bản khi chúng được tạo (thường là một vài từ hoặc câu)
- Khi quyết định thực hiện một lệnh gọi hàm, Gemini sẽ gửi thông tin về lệnh gọi hàm
- Các đoạn văn bản bổ sung có thể theo sau các lệnh gọi hàm
- Luồng này sẽ tiếp tục cho đến khi quá trình tạo hoàn tất
Xử lý từng bước
Ứng dụng của bạn xử lý từng đoạn tăng dần:
- Mỗi đoạn văn bản sẽ được thêm vào câu trả lời hiện có
- Các lệnh gọi hàm sẽ được thực thi ngay khi được phát hiện
- Giao diện người dùng cập nhật theo thời gian thực cả văn bản và kết quả của hàm
- Trạng thái được theo dõi để cho biết phản hồi vẫn đang được truyền trực tuyến
Hoàn tất sự kiện phát trực tiếp
Khi quá trình tạo hoàn tất:
- Máy chủ đóng luồng
- Vòng lặp
await for
của bạn sẽ thoát một cách tự nhiên - Thông báo được đánh dấu là đã hoàn tất
- Trạng thái cuộc trò chuyện được đặt lại thành rảnh
- Giao diện người dùng cập nhật để phản ánh trạng thái đã hoàn tất
So sánh giữa nội dung phát trực tuyến và nội dung không phát trực tuyến
Để hiểu rõ hơn về lợi ích của việc phát trực tuyến, hãy so sánh phương pháp phát trực tuyến với phương pháp không phát trực tuyến:
Tỷ lệ | Không phát trực tuyến | Phát trực tiếp |
Độ trễ cảm nhận được | Người dùng không thấy gì cho đến khi câu trả lời hoàn chỉnh sẵn sàng | Người dùng thấy những từ đầu tiên trong vòng vài mili giây |
Trải nghiệm người dùng | Đợi lâu rồi văn bản mới đột ngột xuất hiện | Văn bản xuất hiện tự nhiên và tăng dần |
Quản lý trạng thái | Đơn giản hơn (thông báo đang chờ xử lý hoặc đã hoàn tất) | Phức tạp hơn (thông báo có thể ở trạng thái truyền phát trực tiếp) |
Thực thi hàm | Chỉ xảy ra sau khi hoàn tất phản hồi | Xảy ra trong quá trình tạo câu trả lời |
Mức độ phức tạp khi triển khai | Triển khai đơn giản hơn | Cần có thêm tính năng quản lý trạng thái |
Khôi phục lỗi | Phản ứng tất cả hoặc không có gì | Câu trả lời một phần vẫn có thể hữu ích |
Độ phức tạp của mã | Ít phức tạp hơn | Phức tạp hơn do xử lý luồng |
Đối với một ứng dụng như Colorist, lợi ích về trải nghiệm người dùng của việc truyền phát trực tuyến lớn hơn độ phức tạp khi triển khai, đặc biệt là đối với những cách diễn giải màu sắc có thể mất vài giây để tạo.
Các phương pháp hay nhất về trải nghiệm người dùng khi phát trực tuyến
Khi triển khai tính năng truyền phát trực tiếp trong các ứng dụng LLM của riêng bạn, hãy cân nhắc những phương pháp hay nhất sau đây:
- Chỉ báo trực quan rõ ràng: Luôn cung cấp các tín hiệu trực quan rõ ràng để phân biệt giữa thông báo phát trực tiếp và thông báo hoàn chỉnh
- Chặn dữ liệu đầu vào: Tắt dữ liệu đầu vào của người dùng trong quá trình phát trực tuyến để ngăn nhiều yêu cầu trùng lặp
- Khôi phục lỗi: Thiết kế giao diện người dùng để xử lý quá trình khôi phục một cách suôn sẻ nếu quá trình phát trực tuyến bị gián đoạn
- Chuyển đổi trạng thái: Đảm bảo chuyển đổi mượt mà giữa trạng thái rảnh, phát trực tuyến và hoàn tất
- Hình ảnh trực quan về tiến trình: Cân nhắc sử dụng ảnh động hoặc chỉ báo tinh tế cho biết quá trình xử lý đang diễn ra
- Lựa chọn huỷ: Trong một ứng dụng hoàn chỉnh, hãy cung cấp cách để người dùng huỷ các hoạt động tạo đang diễn ra
- Tích hợp kết quả của hàm: Thiết kế giao diện người dùng để xử lý kết quả của hàm xuất hiện ở giữa luồng
- Tối ưu hoá hiệu suất: Giảm thiểu việc tạo lại giao diện người dùng trong quá trình cập nhật luồng nhanh
Gói colorist_ui
triển khai nhiều phương pháp hay nhất trong số này cho bạn, nhưng đây là những điểm quan trọng cần cân nhắc đối với mọi hoạt động triển khai LLM truyền trực tuyến.
Tiếp theo là gì?
Trong bước tiếp theo, bạn sẽ triển khai tính năng đồng bộ hoá LLM bằng cách thông báo cho Gemini khi người dùng chọn màu trong nhật ký. Điều này sẽ tạo ra một trải nghiệm gắn kết hơn, trong đó LLM nhận biết được những thay đổi do người dùng thực hiện đối với trạng thái ứng dụng.
Khắc phục sự cố
Vấn đề về xử lý luồng dữ liệu
Nếu bạn gặp vấn đề về quá trình xử lý luồng phát:
- Triệu chứng: Phản hồi một phần, thiếu văn bản hoặc luồng bị chấm dứt đột ngột
- Giải pháp: Kiểm tra khả năng kết nối mạng và đảm bảo các mẫu async/await phù hợp trong mã của bạn
- Chẩn đoán: Kiểm tra bảng điều khiển nhật ký để xem thông báo lỗi hoặc cảnh báo liên quan đến quá trình xử lý luồng
- Khắc phục: Đảm bảo mọi quy trình xử lý luồng đều sử dụng tính năng xử lý lỗi thích hợp bằng các khối
try
/catch
Thiếu lệnh gọi hàm
Nếu không phát hiện thấy lệnh gọi hàm trong luồng:
- Triệu chứng: Văn bản xuất hiện nhưng màu sắc không cập nhật hoặc nhật ký không cho thấy lệnh gọi hàm
- Giải pháp: Xác minh hướng dẫn của lời nhắc hệ thống về cách sử dụng lệnh gọi hàm
- Chẩn đoán: Kiểm tra bảng điều khiển nhật ký để xem có nhận được lệnh gọi hàm hay không
- Cách khắc phục: Điều chỉnh câu lệnh hệ thống để hướng dẫn LLM một cách rõ ràng hơn về cách sử dụng công cụ
set_color
Xử lý lỗi chung
Đối với mọi vấn đề khác:
- Bước 1: Kiểm tra bảng điều khiển nhật ký để xem thông báo lỗi
- Bước 2: Xác minh khả năng kết nối của Firebase AI Logic
- Bước 3: Đảm bảo tất cả mã do Riverpod tạo đều là mã mới nhất
- Bước 4: Xem xét việc triển khai truyền phát trực tiếp để tìm các câu lệnh await bị thiếu
Các khái niệm chính đã học
- Triển khai câu trả lời phát trực tuyến bằng Gemini API để có trải nghiệm người dùng phản hồi nhanh hơn
- Quản lý trạng thái trò chuyện để xử lý đúng cách các hoạt động tương tác phát trực tuyến
- Xử lý văn bản theo thời gian thực và các lệnh gọi hàm khi chúng đến
- Tạo giao diện người dùng thích ứng, cập nhật tăng dần trong quá trình truyền phát trực tiếp
- Xử lý các luồng đồng thời bằng các mẫu không đồng bộ thích hợp
- Cung cấp phản hồi trực quan phù hợp trong quá trình truyền trực tuyến các câu trả lời
Bằng cách triển khai tính năng truyền phát trực tiếp, bạn đã cải thiện đáng kể trải nghiệm người dùng của ứng dụng Colorist, tạo ra một giao diện phản hồi nhanh hơn, hấp dẫn hơn và mang lại cảm giác như đang trò chuyện thực sự.
8. Đồng bộ hoá bối cảnh LLM
Trong bước bổ sung này, bạn sẽ triển khai tính năng Đồng bộ hoá ngữ cảnh LLM bằng cách thông báo cho Gemini khi người dùng chọn màu trong nhật ký. Điều này tạo ra trải nghiệm mạch lạc hơn, trong đó LLM nhận biết được các hành động của người dùng trong giao diện, chứ không chỉ các thông báo rõ ràng của họ.
Nội dung bạn sẽ tìm hiểu trong bước này
- Tạo tính năng Đồng bộ hoá bối cảnh LLM giữa giao diện người dùng và LLM
- Chuyển đổi các sự kiện trên giao diện người dùng thành ngữ cảnh mà LLM có thể hiểu
- Cập nhật bối cảnh trò chuyện dựa trên hành động của người dùng
- Tạo trải nghiệm nhất quán trên nhiều phương thức tương tác
- Nâng cao khả năng nhận biết bối cảnh của LLM ngoài các tin nhắn trò chuyện rõ ràng
Tìm hiểu về tính năng Đồng bộ hoá bối cảnh LLM
Các chatbot truyền thống chỉ phản hồi những tin nhắn rõ ràng của người dùng, tạo ra sự gián đoạn khi người dùng tương tác với ứng dụng thông qua các phương tiện khác. Tính năng Đồng bộ hoá bối cảnh LLM giúp giải quyết hạn chế này:
Tại sao tính năng Đồng bộ hoá ngữ cảnh LLM lại quan trọng
Khi người dùng tương tác với ứng dụng của bạn thông qua các phần tử giao diện người dùng (chẳng hạn như chọn một màu trong nhật ký), LLM không có cách nào biết được điều gì đã xảy ra trừ phi bạn cho LLM biết một cách rõ ràng. Đồng bộ hoá bối cảnh LLM:
- Duy trì bối cảnh: Thông báo cho LLM về mọi hành động liên quan của người dùng
- Tạo sự nhất quán: Tạo ra trải nghiệm nhất quán, trong đó LLM ghi nhận các hoạt động tương tác trên giao diện người dùng
- Tăng cường khả năng thông minh: Cho phép LLM phản hồi phù hợp với mọi hành động của người dùng
- Cải thiện trải nghiệm người dùng: Giúp toàn bộ ứng dụng có cảm giác tích hợp và phản hồi nhanh hơn
- Giảm bớt công sức của người dùng: Không cần người dùng giải thích các thao tác trên giao diện người dùng theo cách thủ công
Trong ứng dụng Colorist, khi người dùng chọn một màu trong nhật ký, bạn muốn Gemini ghi nhận hành động này và bình luận một cách thông minh về màu đã chọn, duy trì cảm giác về một trợ lý liền mạch và nhận biết.
Cập nhật dịch vụ trò chuyện Gemini để nhận thông báo về lựa chọn màu sắc
Trước tiên, bạn sẽ thêm một phương thức vào GeminiChatService
để thông báo cho LLM khi người dùng chọn một màu trong nhật ký. Cập nhật tệp 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/legacy.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import '../providers/gemini.dart';
import 'gemini_tools.dart';
part 'gemini_chat_service.g.dart';
final conversationStateProvider = StateProvider(
(ref) => ConversationState.idle,
);
class GeminiChatService {
GeminiChatService(this.ref);
final Ref ref;
Future<void> notifyColorSelection(ColorData color) => sendMessage( // Add from here...
'User selected color from history: ${json.encode(color.toLLMContextMap())}',
); // To here.
Future<void> sendMessage(String message) async {
final chatSession = await ref.read(chatSessionProvider.future);
final conversationState = ref.read(conversationStateProvider);
final chatStateNotifier = ref.read(chatStateProvider.notifier);
final logStateNotifier = ref.read(logStateProvider.notifier);
if (conversationState == ConversationState.busy) {
logStateNotifier.logWarning(
"Can't send a message while a conversation is in progress",
);
throw Exception(
"Can't send a message while a conversation is in progress",
);
}
final conversationStateNotifier = ref.read(
conversationStateProvider.notifier,
);
conversationStateNotifier.state = ConversationState.busy;
chatStateNotifier.addUserMessage(message);
logStateNotifier.logUserText(message);
final llmMessage = chatStateNotifier.createLlmMessage();
try {
final responseStream = chatSession.sendMessageStream(
Content.text(message),
);
await for (final block in responseStream) {
await _processBlock(block, llmMessage.id);
}
} catch (e, st) {
logStateNotifier.logError(e, st: st);
chatStateNotifier.appendToMessage(
llmMessage.id,
"\nI'm sorry, I encountered an error processing your request. "
"Please try again.",
);
} finally {
chatStateNotifier.finalizeMessage(llmMessage.id);
conversationStateNotifier.state = ConversationState.idle;
}
}
Future<void> _processBlock(
GenerateContentResponse block,
String llmMessageId,
) async {
final chatSession = await ref.read(chatSessionProvider.future);
final chatStateNotifier = ref.read(chatStateProvider.notifier);
final logStateNotifier = ref.read(logStateProvider.notifier);
final blockText = block.text;
if (blockText != null) {
logStateNotifier.logLlmText(blockText);
chatStateNotifier.appendToMessage(llmMessageId, blockText);
}
if (block.functionCalls.isNotEmpty) {
final geminiTools = ref.read(geminiToolsProvider);
final responseStream = chatSession.sendMessageStream(
Content.functionResponses([
for (final functionCall in block.functionCalls)
FunctionResponse(
functionCall.name,
geminiTools.handleFunctionCall(
functionCall.name,
functionCall.args,
),
),
]),
);
await for (final response in responseStream) {
final responseText = response.text;
if (responseText != null) {
logStateNotifier.logLlmText(responseText);
chatStateNotifier.appendToMessage(llmMessageId, responseText);
}
}
}
}
}
@riverpod
GeminiChatService geminiChatService(Ref ref) => GeminiChatService(ref);
Điểm bổ sung chính là phương thức notifyColorSelection
, phương thức này:
- Lấy một đối tượng
ColorData
đại diện cho màu đã chọn - Mã hoá thành định dạng JSON có thể được đưa vào một thông báo
- Gửi một thông báo có định dạng đặc biệt đến LLM cho biết lựa chọn của người dùng
- Sử dụng lại phương thức
sendMessage
hiện có để xử lý thông báo
Phương pháp này tránh trùng lặp bằng cách tận dụng cơ sở hạ tầng xử lý thông báo hiện có.
Cập nhật ứng dụng chính để kết nối thông báo lựa chọn màu
Bây giờ, hãy sửa đổi tệp lib/main.dart
để truyền hàm thông báo lựa chọn màu sắc đến màn hình chính:
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),
),
);
}
}
Thay đổi chính là việc thêm lệnh gọi lại notifyColorSelection
, kết nối sự kiện trên giao diện người dùng (chọn một màu trong nhật ký) với hệ thống thông báo LLM.
Cập nhật câu lệnh hệ thống
Giờ đây, bạn cần cập nhật câu lệnh hệ thống để hướng dẫn LLM cách phản hồi thông báo chọn màu. Sửa đổi tệp 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
Điểm bổ sung chính là phần "Khi người dùng chọn màu trong nhật ký", trong đó:
- Giải thích khái niệm thông báo lựa chọn trong nhật ký cho LLM
- Đưa ra ví dụ về giao diện của những thông báo này
- Cho thấy ví dụ về một câu trả lời phù hợp
- Đặt kỳ vọng về việc xác nhận lựa chọn và bình luận về màu sắc
Điều này giúp LLM hiểu cách phản hồi phù hợp với những thông báo đặc biệt này.
Tạo mã Riverpod
Chạy lệnh build runner để tạo mã Riverpod cần thiết:
dart run build_runner build --delete-conflicting-outputs
Chạy và kiểm thử tính năng Đồng bộ hoá bối cảnh LLM
Chạy ứng dụng của bạn:
flutter run -d DEVICE
Quy trình kiểm thử tính năng Đồng bộ hoá bối cảnh LLM bao gồm:
- Trước tiên, hãy tạo một vài màu bằng cách mô tả chúng trong cuộc trò chuyện
- "Show me a vibrant purple" (Tôi muốn xem màu tím tươi)
- "Tôi muốn màu xanh lục"
- "Cho tôi màu đỏ tươi"
- Sau đó, hãy nhấp vào một trong các hình thu nhỏ màu trong dải màu gần đây
Bạn nên chú ý:
- Màu đã chọn sẽ xuất hiện trên màn hình chính
- Một thông báo của người dùng sẽ xuất hiện trong cuộc trò chuyện cho biết lựa chọn màu sắc
- LLM phản hồi bằng cách xác nhận lựa chọn và bình luận về màu sắc
- Toàn bộ quá trình tương tác diễn ra tự nhiên và mạch lạc
Điều này tạo ra một trải nghiệm liền mạch, trong đó LLM nhận biết và phản hồi phù hợp với cả tin nhắn trực tiếp và các hoạt động tương tác trên giao diện người dùng.
Cách hoạt động của tính năng Đồng bộ hoá bối cảnh LLM
Hãy cùng tìm hiểu chi tiết về kỹ thuật của quy trình đồng bộ hoá này:
Luồng dữ liệu
- Hành động của người dùng: Người dùng nhấp vào một màu trong dải màu đã dùng
- Sự kiện giao diện người dùng: Tiện ích
MainScreen
phát hiện lựa chọn này - Thực thi lệnh gọi lại: Lệnh gọi lại
notifyColorSelection
được kích hoạt - Tạo thông báo: Một thông báo có định dạng đặc biệt được tạo bằng dữ liệu màu
- Xử lý bằng mô hình ngôn ngữ lớn (LLM): Tin nhắn được gửi đến Gemini, sau đó Gemini sẽ nhận dạng định dạng
- Phản hồi theo ngữ cảnh: Gemini phản hồi phù hợp dựa trên câu lệnh hệ thống
- Cập nhật giao diện người dùng: Câu trả lời xuất hiện trong cuộc trò chuyện, tạo ra trải nghiệm nhất quán
Chuyển đổi dữ liệu tuần tự
Một khía cạnh quan trọng của phương pháp này là cách bạn chuyển đổi tuần tự dữ liệu màu:
'User selected color from history: ${json.encode(color.toLLMContextMap())}'
Phương thức toLLMContextMap()
(do gói colorist_ui
cung cấp) chuyển đổi một đối tượng ColorData
thành một bản đồ có các thuộc tính khoá mà LLM có thể hiểu. Thông tin này thường bao gồm:
- Giá trị RGB (đỏ, xanh lục, xanh dương)
- Biểu thị mã thập lục phân
- Mọi tên hoặc nội dung mô tả liên quan đến màu sắc
Bằng cách định dạng dữ liệu này một cách nhất quán và đưa dữ liệu vào thông báo, bạn đảm bảo LLM có tất cả thông tin cần thiết để phản hồi một cách phù hợp.
Ứng dụng rộng hơn của tính năng Đồng bộ hoá bối cảnh LLM
Mô hình thông báo cho LLM về các sự kiện trên giao diện người dùng này có nhiều ứng dụng ngoài việc chọn màu:
Các trường hợp sử dụng khác
- Lọc các thay đổi: Thông báo cho LLM khi người dùng áp dụng bộ lọc cho dữ liệu
- Sự kiện điều hướng: Thông báo cho LLM khi người dùng chuyển đến các phần khác nhau
- Thay đổi lựa chọn: Cập nhật LLM khi người dùng chọn các mục trong danh sách hoặc lưới
- Cập nhật lựa chọn ưu tiên: Cho LLM biết khi người dùng thay đổi chế độ cài đặt hoặc lựa chọn ưu tiên
- Thao tác dữ liệu: Thông báo cho LLM khi người dùng thêm, chỉnh sửa hoặc xoá dữ liệu
Trong mỗi trường hợp, mẫu vẫn giữ nguyên:
- Phát hiện sự kiện trên giao diện người dùng
- Chuyển đổi dữ liệu có liên quan thành chuỗi
- Gửi một thông báo có định dạng đặc biệt đến LLM
- Hướng dẫn LLM phản hồi một cách phù hợp thông qua câu lệnh hệ thống
Các phương pháp hay nhất để đồng bộ hoá bối cảnh LLM
Dựa trên quá trình triển khai của bạn, sau đây là một số phương pháp hay nhất để đồng bộ hoá bối cảnh LLM một cách hiệu quả:
1. Định dạng nhất quán
Sử dụng định dạng nhất quán cho thông báo để LLM có thể dễ dàng xác định các thông báo đó:
"User [action] [object]: [structured data]"
2. Bối cảnh phong phú
Đưa ra đủ thông tin chi tiết trong thông báo để LLM có thể phản hồi một cách thông minh. Đối với màu sắc, điều này có nghĩa là các giá trị RGB, mã thập lục phân và mọi thuộc tính có liên quan khác.
3. Xoá hướng dẫn
Đưa ra hướng dẫn rõ ràng trong lời nhắc hệ thống về cách xử lý thông báo, tốt nhất là có ví dụ.
4. Tích hợp tự nhiên
Thiết kế thông báo sao cho xuất hiện tự nhiên trong cuộc trò chuyện, chứ không phải là gián đoạn về mặt kỹ thuật.
5. Thông báo có chọn lọc
Chỉ thông báo cho LLM về những hành động liên quan đến cuộc trò chuyện. Không phải sự kiện nào trên giao diện người dùng cũng cần được truyền đạt.
Khắc phục sự cố
Vấn đề về thông báo
Nếu LLM không phản hồi đúng cách đối với lựa chọn màu sắc:
- Kiểm tra để đảm bảo định dạng thông báo khớp với nội dung được mô tả trong lời nhắc hệ thống
- Xác minh rằng dữ liệu màu đang được chuyển đổi đúng cách
- Đảm bảo lời nhắc hệ thống có hướng dẫn rõ ràng về cách xử lý các lựa chọn
- Tìm lỗi trong dịch vụ trò chuyện khi gửi thông báo
Quản lý ngữ cảnh
Nếu LLM có vẻ mất ngữ cảnh:
- Kiểm tra để đảm bảo phiên trò chuyện đang được duy trì đúng cách
- Xác minh rằng các trạng thái trò chuyện chuyển đổi đúng cách
- Đảm bảo rằng thông báo đang được gửi qua cùng một phiên trò chuyện
Các vấn đề chung
Đối với các vấn đề chung:
- Kiểm tra nhật ký để tìm lỗi hoặc cảnh báo
- Xác minh khả năng kết nối Firebase AI Logic
- Kiểm tra xem có loại nào không khớp trong các tham số hàm không
- Đảm bảo tất cả mã do Riverpod tạo đều là mã mới nhất
Các khái niệm chính đã học
- Tạo tính năng Đồng bộ hoá ngữ cảnh LLM giữa giao diện người dùng và LLM
- Chuyển đổi các sự kiện trên giao diện người dùng thành ngữ cảnh phù hợp với LLM
- Hướng dẫn hành vi của LLM cho các mẫu tương tác khác nhau
- Tạo trải nghiệm nhất quán giữa các lượt tương tác qua tin nhắn và không qua tin nhắn
- Nâng cao nhận thức của LLM về trạng thái ứng dụng rộng hơn
Bằng cách triển khai tính năng Đồng bộ hoá bối cảnh LLM, bạn đã tạo ra một trải nghiệm thực sự tích hợp, trong đó LLM hoạt động như một trợ lý có nhận thức và phản hồi thay vì chỉ là một trình tạo văn bản. Bạn có thể áp dụng mẫu này cho vô số ứng dụng khác để tạo ra các giao diện tự nhiên và trực quan hơn dựa trên AI.
9. Xin chúc mừng!
Bạn đã hoàn tất thành công lớp học lập trình Colorist! 🎉
Những gì bạn đã tạo
Bạn đã tạo một ứng dụng Flutter hoạt động đầy đủ, tích hợp Gemini API của Google để diễn giải nội dung mô tả màu bằng ngôn ngữ tự nhiên. Giờ đây, ứng dụng của bạn có thể:
- Xử lý nội dung mô tả bằng ngôn ngữ tự nhiên, chẳng hạn như "cam hoàng hôn" hoặc "xanh dương đại dương"
- Sử dụng Gemini để dịch những nội dung mô tả này thành giá trị RGB một cách thông minh
- Hiển thị màu sắc được diễn giải theo thời gian thực bằng các câu trả lời truyền trực tuyến
- Xử lý các hoạt động tương tác của người dùng thông qua cả các phần tử trò chuyện và giao diện người dùng
- Duy trì nhận thức theo bối cảnh trên nhiều phương thức tương tác
Nội dung tiếp theo nên tìm hiểu
Giờ đây, khi bạn đã nắm vững kiến thức cơ bản về việc tích hợp Gemini với Flutter, bạn có thể tiếp tục hành trình của mình theo một số cách sau:
Cải thiện ứng dụng Colorist
- Bảng màu: Thêm chức năng tạo bảng phối màu bổ sung hoặc phù hợp
- Nhập bằng giọng nói: Tích hợp tính năng nhận dạng giọng nói để mô tả màu bằng lời nói
- Quản lý nhật ký: Thêm các lựa chọn để đặt tên, sắp xếp và xuất các bộ màu
- Câu lệnh tuỳ chỉnh: Tạo giao diện để người dùng tuỳ chỉnh câu lệnh hệ thống
- Số liệu phân tích nâng cao: Theo dõi những nội dung mô tả hiệu quả nhất hoặc gây khó khăn
Khám phá thêm các tính năng của Gemini
- Thông tin đầu vào đa phương thức: Thêm thông tin đầu vào là hình ảnh để trích xuất màu từ ảnh
- Tạo nội dung: Dùng Gemini để tạo nội dung liên quan đến màu sắc, chẳng hạn như nội dung mô tả hoặc câu chuyện
- Các điểm cải tiến về tính năng gọi hàm: Tạo các tích hợp công cụ phức tạp hơn với nhiều hàm
- Chế độ cài đặt an toàn: Khám phá các chế độ cài đặt an toàn và tác động của chúng đối với câu trả lời
Áp dụng các mẫu này cho các miền khác
- Phân tích tài liệu: Tạo các ứng dụng có thể hiểu và phân tích tài liệu
- Hỗ trợ viết sáng tạo: Xây dựng các công cụ viết bằng đề xuất dựa trên LLM
- Tự động hoá nhiệm vụ: Thiết kế các ứng dụng chuyển đổi ngôn ngữ tự nhiên thành nhiệm vụ tự động
- Ứng dụng dựa trên kiến thức: Tạo hệ thống chuyên gia trong các miền cụ thể
Tài nguyên
Sau đây là một số tài nguyên giá trị để bạn tiếp tục học tập:
Tài liệu chính thức
Khoá học và hướng dẫn đặt câu lệnh
Cộng đồng
Observable Flutter Agentic series
Trong tập 59, Craig Labenz và Andrew Brogden sẽ khám phá lớp học lập trình này, đồng thời nêu bật những phần thú vị trong quá trình tạo ứng dụng.
Trong tập 60, hãy cùng Craig và Andrew mở rộng ứng dụng lớp học lập trình bằng các chức năng mới và tìm cách khiến các LLM làm theo những gì chúng được yêu cầu.
Trong tập 61, Craig cùng với Chris Sells phân tích các tiêu đề tin tức theo một cách mới và tạo ra những hình ảnh tương ứng.
Phản hồi
Chúng tôi rất mong được biết trải nghiệm của bạn với lớp học lập trình này! Vui lòng cân nhắc gửi ý kiến phản hồi thông qua:
Cảm ơn bạn đã hoàn thành lớp học lập trình này. Chúng tôi hy vọng bạn sẽ tiếp tục khám phá những khả năng thú vị khi kết hợp Flutter và AI!