Tạo ứng dụng Flutter dựa trên Gemini

Tạo ứng dụng Flutter dựa trên Gemini

Thông tin về lớp học lập trình này

subjectLần cập nhật gần đây nhất: thg 5 19, 2025
account_circleTác giả: Brett Morgan

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ẽ xây dựng Colorist – một ứng dụng Flutter có thể tương tác, mang sức mạnh của API Gemini trực tiếp vào ứng dụng Flutter của bạn. Bạn đã bao giờ muốn cho phép người dùng điều khiển ứ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? 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:

  • 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à ngữ cảnh thú vị về màu sắc
  • Duy trì nhật ký các màu được tạo gần đây

Ảnh chụp màn hình ứng dụng Colorist cho thấy màn hình hiển thị màu và giao diện trò chuyện

Ứng dụng này có giao diện chia đôi màn hình với một khu vực hiển thị màu và hệ thống trò chuyện tương tác ở một bên, còn một bên là bảng nhật ký chi tiết cho thấy các hoạt động tương tác LLM thô. 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 tính năng tích hợp LLM.

Lý do điều này quan trọng đối với nhà phát triển Flutter

LLM đang cách mạng hoá 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 LLM vào ứng dụng di động và ứng dụng máy tính lại đặt ra 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ỉ bao gồm 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 xây dựng Colorist:

  1. 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
  2. Tích hợp Gemini cơ bản – Kết nối ứng dụng của bạn với Vertex AI trong Firebase và triển khai giao tiếp LLM đơn giản
  3. Câu lệnh hiệu quả – Tạo câu lệnh hệ thống hướng dẫn LLM hiểu được nội dung mô tả màu
  4. Khai báo hàm – Xác định các công cụ mà LLM có thể sử dụng để đặt màu trong ứng dụng
  5. 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
  6. Phản hồi trực tuyến – Cải thiện trải nghiệm người dùng bằng phản hồi LLM trực tuyến theo thời gian thực
  7. Đồng bộ hoá ngữ cảnh LLM – Tạo trải nghiệm gắn kết bằng cách thông báo cho LLM về các hành động của người dùng

Kiến thức bạn sẽ học được

  • Định cấu hình Vertex AI in Firebase cho các ứng dụng Flutter
  • Tạo câu lệnh hiệu quả cho hệ thống để hướng dẫn hành vi của LLM
  • Triển khai khai báo hàm giúp kết nối ngôn ngữ tự nhiên và các tính năng của ứng dụng
  • Xử lý phản hồi truyền trực tuyến để mang lại trải nghiệm thích ứng cho người dù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 LLM bằng Riverpod
  • Xử lý lỗi một cách linh hoạt trong các ứng dụng sử dụng LLM

Xem trước mã: Tìm hiểu sơ lược về những gì bạn sẽ triển khai

Dưới đây là thông tin sơ lược về nội dung khai báo hàm mà bạn sẽ tạo để cho phép LLM đặt màu trong ứng dụng:

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 có:

  • Kinh nghiệm phát triển Flutter – Quen thuộc với 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ộ – Tìm hiểu về Futures, async/await và luồng
  • Tài khoản Firebase – Bạn cần có Tài khoản Google để thiết lập Firebase
  • Dự án Firebase đã bật tính năng thanh toán – Vertex AI trong Firebase yêu cầu tài khoản thanh toán

Hãy bắt đầu xây dựng ứng dụng Flutter đầu tiên sử dụng 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ụ phản hồi đơn giản. Sau đó, dịch vụ này sẽ được thay thế bằng tính năng tích hợp API Gemini. Thao tác này sẽ 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 đang 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 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

Lưu ý quan trọng về giá

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 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 và 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 custom_lint

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 Colorist
  • flutter_riverpodriverpod_annotation: Dành cho việc quản lý trạng thái
  • logging: Dành cho tính năng ghi nhật ký có cấu trúc
  • Phần phụ thuộc phát triển để tạo mã và tìm lỗi mã nguồn

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

dependencies:
  flutter:
    sdk: flutter
  colorist_ui: ^0.2.3
  flutter_riverpod: ^2.6.1
  riverpod_annotation: ^2.6.1

dev_dependencies:
  flutter_test:
    sdk: flutter
  flutter_lints: ^5.0.0
  build_runner: ^2.4.15
  riverpod_generator: ^2.6.5
  riverpod_lint: ^2.6.5
  json_serializable: ^6.9.5
  custom_lint: ^0.7.5

flutter:
  uses-material-design: true

Định cấu hình các tuỳ chọn phân tích

Thêm custom_lint vào tệp analysis_options.yaml ở thư mục gốc của dự án:

include: package:flutter_lints/flutter.yaml

analyzer:
  plugins:
    - custom_lint

Cấu hình này cho phép các công cụ tìm lỗi mã nguồn dành riêng cho Riverpod giúp duy trì chất lượng mã.

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(chatStateNotifierProvider.notifier);
   
final logStateNotifier = ref.read(logStateNotifierProvider.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 một dịch vụ phản hồi đơn giản mô phỏng hành vi của LLM bằng cách trả về tin nhắn 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 tạo sẵn và các công cụ quản lý trạng thái:

  1. 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 (khu vực tương tác và bảng 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ỏ nhật ký
  2. 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ập nhật ký để gỡ lỗi
  3. Xử lý thông báo: Ứng dụng sử dụng 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
    • Thông báo 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 LLM đã 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:

  1. Lớp giao diện người dùng: Do gói colorist_ui cung cấp
  2. Quản lý trạng thái: Sử dụng Riverpod để quản lý trạng thái phản ứng
  3. Lớp dịch vụ: Hiện chứa dịch vụ phản hồi đơn giản, dịch vụ này sẽ được thay thế bằng Dịch vụ Gemini Chat
  4. 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ính năng 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, chẳng hạn như macos, windows, chrome hoặc mã thiết bị.

Ảnh chụp màn hình ứng dụng Colorist cho thấy dịch vụ echo hiển thị markdown

Bây giờ, bạn sẽ thấy ứng dụng Colorist với:

  1. Khu vực hiển thị màu có màu mặc định
  2. Giao diện trò chuyện nơi bạn có thể nhập tin nhắn
  3. 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 màu xanh dương đậm" rồi nhấn vào Gửi. Dịch vụ phản hồi chỉ lặp lại thông báo của bạn. Trong các bước sau, bạn sẽ thay thế nội dung này bằng cách diễn giải màu thực tế bằng cách sử dụng Gemini API thông qua Vertex AI trong Firebase.

Tiếp theo là gì?

Trong bước tiếp theo, bạn sẽ định cấu hình Firebase và triển khai tính năng tích hợp API Gemini 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 phần phụ thuộc đúng cách
  • Kiểm tra xem có phiên bản gói nào xung đột không

Lỗi bản dựng

Nếu bạn thấy lỗi bản dựng:

  • Đảm bảo bạn đã cài đặt SDK Flutter kênh chính thức mới nhất
  • Chạy flutter clean, theo sau là flutter pub get
  • Kiểm tra đầu ra của bảng điều khiển để xem thông báo lỗi cụ thể

Các khái niệm chính đã học

  • Thiết lập dự án Flutter với các phần phụ thuộc cần thiết
  • Tìm hiểu cấu trúc và trách nhiệm của 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 tính năng trò chuyện cơ bản của Gemini

Trong bước này, bạn sẽ thay thế dịch vụ phản hồi từ bước trước bằng cách tích hợp Gemini API bằng Vertex AI trong Firebase. 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 API Gemini.

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 Vertex AI in Firebase để truy cập vào Gemini
  • Tạo trình cung cấp Riverpod cho các dịch vụ Firebase và Gemini
  • Triển khai dịch vụ trò chuyện cơ bản bằng API Gemini
  • Xử lý các phản hồi API không đồng bộ và trạng thái lỗi

Thiết lập Firebase

Trước tiên, bạn cần thiết lập Firebase cho dự án Flutter. Quá trình này bao gồm việc 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 Vertex AI cần thiết.

Tạo một dự án Firebase

  1. 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.
  2. Nhấp vào Tạo dự án Firebase hoặc chọn một dự án hiện có.
  3. Làm theo trình hướng dẫn thiết lập để tạo dự án.
  4. Sau khi tạo dự án, bạn cần nâng cấp lên gói Blaze (trả tiền theo mức dùng) để truy cập vào các dịch vụ của Vertex AI. Nhấp vào nút Nâng cấp ở dưới cùng bên trái của bảng điều khiển Firebase.

Thiết lập Vertex AI trong dự án Firebase

  1. Trong bảng điều khiển của Firebase, hãy chuyển đến dự án của bạn.
  2. Trong thanh bên trái, hãy chọn AI (Trí tuệ nhân tạo).
  3. Trong thẻ Vertex AI in Firebase, hãy chọn Bắt đầu.
  4. Làm theo lời nhắc để bật API Vertex AI in Firebase cho dự án của bạn.

Cài đặt FlutterFire CLI

FlutterFire CLI đơ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

  1. Thêm các gói Firebase core và Vertex AI vào dự án:
flutter pub add firebase_core firebase_vertexai
  1. 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 đó cho phù 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 mạng để giao tiếp với máy chủ Vertex AI in Firebase.

Đị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:

  1. Mở macos/Runner/DebugProfile.entitlements rồi thêm:

macos/Runner/DebugProfile.entitlements

<key>com.apple.security.network.client</key>
<true/>
  1. Ngoài ra, hãy mở macos/Runner/Release.entitlements và thêm cùng một mục nhập.
  2. Cập nhật phiên bản macOS tối thiểu ở đầu macos/Podfile:

macos/Podfile

# Firebase requires at least macOS 10.15
platform
:osx, '10.15'

Định cấu hình quyền trên 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 13.0
platform
:ios, '13.0'

Định cấu hình chế độ cài đặt Android

Đối với Android, hãy cập nhật android/app/build.gradle.kts:

android/app/build.gradle.kts

android {
   
// ...
    ndkVersion
= "27.0.12077973"

    defaultConfig
{
       
// ...
        minSdk
= 23
       
// ...
   
}
}

Tạo trình cung cấp mô hình Gemini

Bây giờ, bạn sẽ tạo 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_core/firebase_core.dart';
import 'package:firebase_vertexai/firebase_vertexai.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';

import '../firebase_options.dart';

part 'gemini.g.dart';

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

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

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

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

Tệp này xác định cơ sở cho 3 trình cung cấp chính. Các trì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.

  1. firebaseAppProvider: Khởi chạy Firebase bằng cấu hình dự án
  2. geminiModelProvider: Tạo một thực thể mô hình tạo sinh Gemini
  3. chatSessionProvider: Tạo và duy trì 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 đó tồn tại trong suốt vòng đời của ứng dụng, duy trì ngữ cảnh cuộc trò chuyện.

Triển khai dịch vụ trò chuyện Gemini

Tạo tệp mới lib/services/gemini_chat_service.dart để 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_vertexai/firebase_vertexai.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';

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

part 'gemini_chat_service.g.dart';

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

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

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

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

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

Dịch vụ này:

  1. Chấp nhận tin nhắn của người dùng và gửi đến API Gemini
  2. Cập nhật giao diện trò chuyện bằng các phản hồi từ mô hình
  3. 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ế
  4. Xử lý lỗi bằng 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 sau khi bạn giới thiệu các lệnh gọi hàm, sau đó truyền trực tuyến các phản hồi.

Tạo mã Riverpod

Chạy lệnh trình chạy bản dựng để 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 mới của Gemini:

lib/main.dart

import 'package:colorist_ui/colorist_ui.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

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

void main() async {
 
runApp(ProviderScope(child: MainApp()));
}

class MainApp extends ConsumerWidget {
 
const MainApp({super.key});

 
@override
 
Widget build(BuildContext context, WidgetRef ref) {
   
final model = ref.watch(geminiModelProvider);

   
return MaterialApp(
     
theme: ThemeData(
       
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
     
),
     
home: model.when(
       
data: (data) => MainScreen(
         
sendMessage: (text) {
           
ref.read(geminiChatServiceProvider).sendMessage(text);
         
},
       
),
       
loading: () => LoadingScreen(message: 'Initializing Gemini Model'),
       
error: (err, st) => ErrorScreen(error: err),
     
),
   
);
 
}
}

Sau đây là các thay đổi chính trong bản cập nhật này:

  1. Thay thế dịch vụ phản hồi bằng dịch vụ trò chuyện dựa trên API Gemini
  2. Thêm màn hình đang tải và màn hình lỗi bằng cách sử dụng mẫu AsyncValue của Riverpod với phương thức when
  3. 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, chẳng hạn như macos, windows, chrome hoặc mã thiết bị.

Ảnh chụp màn hình ứng dụng Colorist cho thấy LLM của Gemini đang phản hồi yêu cầu về màu vàng tươi

Giờ đây, khi bạn nhập một tin nhắn, tin nhắn đó sẽ được gửi đến API Gemini và bạn sẽ nhận được phản hồi từ LLM thay vì âm vang. Bảng điều khiển nhật ký sẽ hiển thị các hoạt động tương tác với API.

Tìm hiểu về hoạt động giao tiếp của LLM

Hãy dành chút thời gian để tìm hiểu những gì đang xảy ra khi bạn giao tiếp với API Gemini:

Quy trình giao tiếp

  1. Dữ liệu đầ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
  2. Yêu cầu định dạng: Ứng dụng định dạng văn bản dưới dạng đối tượng Content cho Gemini API
  3. Giao tiếp API: Văn bản được gửi đến Gemini API thông qua Vertex AI in Firebase
  4. Xử lý LLM: Mô hình Gemini xử lý văn bản và tạo câu trả lời
  5. 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
  6. Ghi nhật ký: Tất cả thông tin liên lạc đều được ghi nhật ký để đảm bảo tính minh bạch

Phiên trò chuyện và ngữ cảnh cuộc trò chuyện

Phiên trò chuyện trên Gemini duy trì ngữ cảnh giữa các tin nhắn, cho phép tương tác theo kiểu trò chuyện. Đ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 mạch lạc hơn.

Chú thích keepAlive: true trên trình cung cấp phiên trò chuyệ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. Ngữ cảnh liên tục này rất quan trọng để duy trì luồng trò chuyện tự nhiên với LLM.

Tiếp theo là gì?

Tại thời điểm này, bạn có thể hỏi Gemini API bất cứ điều gì vì không có quy định hạn chế nào về nội dung mà Gemini API sẽ phản hồi. Ví dụ: bạn có thể yêu cầu trợ lý tóm tắt về cuộc chiến 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 lời nhắc hệ thống để hướng dẫn Gemini diễn giải nội dung mô tả màu sắc hiệu quả hơn. Phần này sẽ minh hoạ cách tuỳ chỉnh hành vi của LLM cho các nhu cầu cụ thể của ứng dụng và tập trung vào các chức năng của LLM trên miền của ứng dụng.

Khắc phục sự 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 tệp firebase_options.dart của bạn được tạo chính xác
  • Xác minh rằng bạn đã nâng cấp lên gói Blaze để có quyền truy cập vào Vertex AI

Lỗi truy cập API

Nếu bạn gặp lỗi khi truy cập vào API Gemini:

  • Xác nhận rằng bạn đã thiết lập thông tin thanh toán đúng cách trên dự án Firebase
  • Kiểm tra để đảm bảo bạn đã bật Vertex AI 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

Vấn đề về ngữ cảnh cuộc trò chuyện

Nếu bạn nhận thấy Gemini không nhớ ngữ 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 chạy đúng cách trước khi gửi tin nhắn

Vấn đề theo nền tảng

Đối với các vấn đề dành riêng cho nền tảng:

  • iOS/macOS: Đảm bảo bạn đã thiết lập các quyền thích hợp và định cấu hình phiên bản tối thiểu
  • Android: Xác minh rằng bạn đã thiết lập phiên bản SDK tối thiểu 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 Vertex AI in Firebase để 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 về quy trình giao tiếp LLM và các phiên trò chuyện

4. Câu lệnh hiệu quả để mô tả màu sắc

Ở bước này, bạn sẽ tạo và triển khai lời nhắc hệ thống hướng dẫn Gemini diễn giải nội dung mô tả màu sắc. Lời nhắc của 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ề lời nhắc của hệ thống và tầm quan trọng của lời nhắc đó trong các ứng dụng LLM
  • Tạo lời nhắc hiệu quả cho các nhiệm vụ theo miền cụ thể
  • Tải và sử dụng lời nhắc hệ thống trong ứng dụng Flutter
  • Hướng dẫn LLM cung cấp câu trả lời được định dạng nhất quán
  • Kiểm thử cách lời nhắc của hệ thống ảnh hưởng đến hành vi của LLM

Tìm hiểu về lời nhắc của hệ thống

Trước khi tìm hiểu cách triển khai, hãy cùng tìm hiểu về lời nhắc của hệ thống và lý do lời nhắc này quan trọng:

Lời nhắc của hệ thống là gì?

Lệnh gọi của hệ thống là một loại hướng dẫn đặc biệt được đưa ra cho LLM để thiết lập ngữ cảnh, nguyên tắc hành vi và kỳ vọng đối với các phản hồi của LLM. Không giống như thông báo của người dùng, lời nhắc của 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 năng lực chuyên biệt
  • 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 của hệ thống là "mô tả công việc" cho LLM – câu lệnh này cho mô hình biết cách cư xử trong suốt cuộc trò chuyện.

Lý do lời nhắc của hệ thống quan trọng

Lời nhắc của hệ thống rất quan trọng để tạo ra các hoạt động tương tác LLM nhất quán và hữu ích vì chúng:

  1. Đảm bảo tính nhất quán: Hướng dẫn mô hình cung cấp câu trả lời ở định dạng nhất quán
  2. 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 này là màu sắc)
  3. Xác lập ranh giới: Xác định những việc mô hình nên và không nên làm
  4. Nâng cao trải nghiệm người dùng: Tạo một mẫu tương tác tự nhiên và hữu ích hơn
  5. Giảm bớt quá trình xử lý sau: Nhận phản hồi ở đị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 để liên tục diễn giải nội dung mô tả màu và cung cấp giá trị RGB theo một định dạng cụ thể.

Tạo thành phần lời nhắc hệ thống

Trước tiên, bạn sẽ tạo một tệp lời nhắc 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 mới assets/system_prompt.md 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 lời nhắc của hệ thống

Hãy cùng phân tích chức năng của lời nhắc này:

  1. Xác định vai trò: Thiết lập LLM làm "trợ lý chuyên gia về màu sắc"
  2. Giải thích 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 giá trị RGB
  3. Đị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
  4. Ví dụ về giao dịch: Cung cấp ví dụ cụ thể về mẫu tương tác dự kiến
  5. 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
  6. Giới hạn 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 rằng các phản hồi của LLM sẽ nhất quán, đầy đủ thông tin và được định dạng theo cách dễ dàng 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 dưới cùng 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 lời nhắc hệ thống

Tạo tệp mới lib/providers/system_prompt.dart để tải lời nhắc của hệ thống:

lib/providers/system_prompt.dart

import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';

part 'system_prompt.g.dart';

@riverpod
Future<String> systemPrompt(Ref ref) =>
   
rootBundle.loadString('assets/system_prompt.md');

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 nhà cung cấp mô hình Gemini

Bây giờ, hãy sửa đổi tệp lib/providers/gemini.dart để thêm lời nhắc của hệ thống:

lib/providers/gemini.dart

import 'dart:async';

import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_vertexai/firebase_vertexai.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';

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

part 'gemini.g.dart';

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

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

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

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

Thay đổi chính là thêm systemInstruction: Content.system(systemPrompt) khi tạo mô hình tạo sinh. Thao tác này sẽ yêu cầu Gemini sử dụng hướng dẫn của bạn làm câu lệnh của 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 trình chạy bản dựng để 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

Bây giờ, hãy chạy ứng dụng:

flutter run -d DEVICE

Ảnh chụp màn hình ứng dụng Colorist cho thấy LLM của Gemini phản hồi bằng một câu trả lời theo nhân vật cho ứng dụng chọn màu

Hãy thử kiểm tra bằng nhiều nội dung mô tả màu sắc:

  • "Tôi muốn màu xanh dương"
  • "Cho tôi một màu xanh lục rừng"
  • "Tạo màu cam hoàng hôn rực rỡ"
  • "Tôi muốn màu hoa oải hương tươi"
  • "Cho tôi xem một màu như xanh dương đậm"

Bạn sẽ thấy Gemini hiện phản hồi bằng cách giải thích về màu sắc theo cách trò chuyện cùng với các giá trị RGB được định dạng nhất quán. Lời nhắc của hệ thống đã hướng dẫn hiệu quả cho LLM cung cấp loại câu trả lời mà bạn cần.

Ngoài ra, hãy thử hỏi trợ lý về nội dung nằm ngoài ngữ cảnh của 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 kỹ thuật câu lệnh đối với các tác vụ chuyên biệt

Lời nhắc của hệ thống vừa là nghệ thuật vừa là khoa học. Đây là một phần quan trọng trong 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 nhắc – đ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:

  1. Xác định rõ vai trò: Xác định mục đích của LLM
  2. Hướng dẫn rõ ràng: Chi tiết chính xác cách LLM phản hồi
  3. Ví dụ cụ thể: Cho thấy thay vì chỉ nói về cách trả lời hiệu quả
  4. Xử lý trường hợp đặc biệt: Hướng dẫn LLM cách xử lý các tình huống không rõ ràng
  5. Thông số kỹ thuật về định dạng: Đảm bảo câu trả lời được sắp xếp theo cách nhất quán và hữu dụng

Lời nhắc hệ thống mà bạn đã tạo sẽ biến đổi các chức năng chung của Gemini thành một trợ lý diễn giải màu chuyên biệt, cung cấp 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à tác vụ.

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 nội dung khai báo hàm. Điều này cho phép 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 để trực tiếp đặt màu. Điều này minh hoạ cách LLM có thể bắc cầu giữa ngôn ngữ tự nhiên và các tính năng ứng dụng cụ thể.

Khắc phục sự cố

Vấn đề về việc tải thành phần

Nếu bạn gặp lỗi khi tải lời nhắc của hệ thống:

  • 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, theo sau là flutter pub get để làm mới gói tài sản

Phản hồi không nhất quán

Nếu LLM không tuân thủ hướng dẫn định dạng một cách nhất quán:

  • Thử làm rõ hơn các yêu cầu về định dạng trong lời nhắc của hệ thống
  • Thêm ví dụ khác để minh hoạ mẫu dự kiến
  • Đảm bảo định dạng mà 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ụ Vertex AI 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 Firebase để xem có vấn đề về hạn mức nào 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 lời nhắc 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à quy tắc ràng buộc rõ ràng
  • Tải và sử dụng lời nhắc hệ thống trong ứng dụng Flutter
  • Hướng dẫn hành vi LLM cho các tác vụ dành riêng cho miền
  • Sử dụng kỹ thuật 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 cung cấp hướng dẫn rõ ràng trong lời nhắc của 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 việc cho phép Gemini thực hiện hành động trong ứng dụng bằng cách triển khai phần 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 biệt. Tuy nhiên, bạn 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ề lệnh gọi hàm LLM và lợi ích của lệnh gọi này đối với các ứng dụng Flutter
  • Xác định phần khai báo hàm dựa trên giản đồ cho Gemini
  • Tích hợp nội dung khai báo hàm với mô hình Gemini
  • Cập nhật lời nhắc của hệ thống để sử dụng các tính năng của công cụ

Tìm hiểu về lệnh gọi hàm

Trước khi triển khai phần khai báo hàm, hãy tìm hiểu về phần khai báo hàm và lý do phần khai báo hàm có giá trị:

Gọi hàm là gì?

Lệnh gọi hàm (đôi khi được gọi là "sử dụng công cụ") là một chức năng cho phép LLM:

  1. Nhận biết thời điểm một yêu cầu của người dùng sẽ được hưởng lợi từ việc gọi một hàm cụ thể
  2. 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 đó
  3. Cho phép ứng dụng thực thi hàm bằng các tham số đó
  4. Nhận kết quả của hàm và đưa kết quả đó vào phản hồi

Thay vì chỉ mô tả việc cần làm, lệnh gọi hàm giúp LLM kích hoạt các hành động cụ thể trong ứng dụng.

Lý do việc gọi hàm quan trọng đối với ứng dụng Flutter

Lệnh 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:

  1. Thao tác 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ể
  2. Kết quả có cấu trúc: LLM tạo ra dữ liệu sạch, có cấu trúc thay vì văn bản cần phân tích cú pháp
  3. 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 phép tính hoặc sửa đổi trạng thái ứng dụng
  4. Trải nghiệm người dùng tốt hơn: Tạo khả năng 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, lệnh gọi hàm cho phép người dùng nói "Tôi muốn màu xanh lục rừng" và giao diện người dùng sẽ cập nhật ngay màu đó mà không cần phải phân tích cú pháp các giá trị RGB từ văn bản.

Xác định phần khai báo hàm

Tạo một tệp mới lib/services/gemini_tools.dart để xác định phần khai báo hàm:

lib/services/gemini_tools.dart

import 'package:firebase_vertexai/firebase_vertexai.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';

part 'gemini_tools.g.dart';

class GeminiTools {
 
GeminiTools(this.ref);

 
final Ref ref;

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

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

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

Tìm hiểu về nội dung khai báo hàm

Hãy phân tích những gì mã này thực hiện:

  1. Đặt tên hàm: Bạn đặt tên hàm là set_color để cho biết rõ mục đích của hàm
  2. 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
  3. Đị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 1
    • green: 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 1
    • blue: 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
  4. Loại giản đồ: Bạn sử dụng Schema.number() để cho biết đây là các giá trị số
  5. Bộ sưu tập công cụ: Bạn tạo một danh sách công cụ chứa nội dung khai báo hàm

Phương pháp có cấu trúc này giúp LLM của Gemini hiểu được:

  • Thời điểm gọi hàm này
  • Những tham số cần cung cấp
  • Những quy tắc 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 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 phần khai báo hàm khi khởi chạy mô hình Gemini:

lib/providers/gemini.dart

import 'dart:async';

import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_vertexai/firebase_vertexai.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';

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

part 'gemini.g.dart';

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

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

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

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

Thay đổi chính là thêm tham số tools: geminiTools.tools khi tạo mô hình tạo sinh. Điều này giúp Gemini nhận biết được các hàm có sẵn để gọi.

Cập nhật lời nhắc của hệ thống

Bây giờ, bạn cần sửa đổi lời nhắc 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 lời nhắc của hệ thống:

  1. Giới thiệu công cụ: Thay vì yêu cầu các giá trị RGB được định dạng, giờ đây, bạn sẽ cho LLM biết về công cụ set_color
  2. Quy trình đã sửa đổi: Bạn thay đổi bước 3 từ "định dạng giá trị trong phản hồi" thành "sử dụng công cụ để đặt giá trị"
  3. Ví dụ đã cập nhật: Bạn cho thấy cách phản hồi bao gồm lệnh gọi công cụ thay vì văn bản được định dạng
  4. Xoá yêu cầu đị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

Lời nhắc cập nhật này hướng dẫn LLM sử dụng lệnh gọi hàm thay vì chỉ cung cấp giá trị RGB ở dạng văn bản.

Tạo mã Riverpod

Chạy lệnh trình chạy bản dựng để 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 lệnh gọi hàm, nhưng bạn chưa triển khai trình xử lý cho lệnh gọi hàm. Khi chạy ứng dụng và mô tả một màu, 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

Ảnh chụp màn hình ứng dụng Colorist cho thấy LLM của Gemini phản hồi một phần

Hãy thử mô tả một màu sắc như "xanh dương đậm" hoặc "xanh lục rừng" và quan sát phản hồ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 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 lệnh gọi hàm:

  1. Lựa chọn hàm: 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
  2. Tạo tham số: LLM tạo các giá trị tham số phù hợp với giản đồ của hàm
  3. Đị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
  4. 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 liên quan (được triển khai ở bước tiếp theo)
  5. 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ề

Ở trạng thái hiện tại của ứng dụng, ba 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ý lệnh gọi hàm). Bạn sẽ thực hiện các bước này trong bước tiếp theo.

Thông tin kỹ thuật: Cách Gemini quyết định thời điểm sử dụng 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:

  1. Ý định của người dùng: Liệu một hàm có thể phân phát yêu cầu của người dùng một cách hiệu quả nhất hay không
  2. Mức độ liên quan của hàm: Mức độ phù hợp của các hàm có sẵn với tác vụ
  3. Khả năng sử dụng thông số: Liệu thông số đó có thể tự tin xác định giá trị thông số hay không
  4. Hướng dẫn của hệ thống: Hướng dẫn từ lời nhắc của hệ thống về cách sử dụng hàm

Bằng cách cung cấp nội dung khai báo hàm và hướng dẫn hệ thống rõ ràng, bạn đã thiết lập Gemini để nhận ra 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 về màu sắc thực tế 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 phần khai báo hàm:

  • Kiểm tra để đảm bảo tên và loại tham số khớp với dự kiến
  • 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

Sự cố về lời nhắc của hệ thống

Nếu LLM không cố gắng sử dụng hàm:

  • Xác minh rằng lời nhắc của hệ thống 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 lời nhắc của hệ thống minh hoạ cách sử dụng hàm
  • Hãy cố gắng làm cho hướng dẫn sử dụng công cụ rõ ràng hơn

Vấn đề chung

Nếu bạn gặp 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 phần khai báo hàm hay không
  • Xác minh rằng các công cụ được truyền đến mô hình đúng cách
  • Đảm bảo tất cả mã do Riverpod tạo đều mới nhất

Các khái niệm chính đã học

  • Xác định phần khai báo hàm để mở rộng chức năng LLM trong ứ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 nội dung khai báo hàm với mô hình Gemini
  • Cập nhật lời nhắc của hệ thống để khuyến khích 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à lệnh gọi hàm có cấu trúc, tạo nền tảng để tích hợp liền mạch giữa các tính năng trò chuyện và ứng dụng.

6. Triển khai tính năng 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. Việc này hoàn tất vòng tròn giao tiếp giữa dữ liệu đầu vào bằng ngôn ngữ tự nhiên và các tính năng ứng dụng cụ thể, 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ý phản hồi hàm và trả về kết quả cho LLM
  • Tạo 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 hàm và phản hồi để đảm bảo tính minh bạch

Tìm hiểu về quy trình gọi hàm

Trước khi đi sâu vào cách 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

  1. Dữ liệu đầ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")
  2. Xử lý LLM: Gemini phân tích nội dung mô tả và quyết định gọi hàm set_color
  3. 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ị đỏ, xanh lục, xanh lam)
  4. 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
  5. 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
  6. 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ị)
  7. Tạo phản hồi: Hàm của bạn trả về kết quả cho LLM
  8. Tích hợp phản hồi: LLM tích hợp các kết quả này vào phản hồi cuối cùng
  9. Cập nhật giao diện người dùng: Giao diện người dùng phản ứng với sự thay đổi trạng thái, hiển thị màu mới

Vòng giao tiếp hoàn chỉnh là yếu tố cần thiết để tích hợp LLM đúng cách. Khi thực hiện lệnh gọi hàm, LLM không chỉ gửi yêu cầu và tiếp tục. Thay vào đó, trình xử lý này sẽ đợi ứng dụng của bạn thực thi hàm và trả về kết quả. Sau đó, LLM sử dụng những kết quả này để xây dựng câu trả lời cuối cùng, tạo ra một luồng trò chuyện tự nhiên để xác nhận các 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_vertexai/firebase_vertexai.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';

part 'gemini_tools.g.dart';

class GeminiTools {
 
GeminiTools(this.ref);

 
final Ref ref;

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

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

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

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

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

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

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

Tìm hiểu về trình xử lý hàm

Hãy cùng phân tích những gì các trình xử lý hàm này làm:

  1. handleFunctionCall: Trình điều phối trung tâm có chức năng:
    • 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
  2. handleSetColor: Trình xử lý cụ thể cho hàm set_color của bạn:
    • Trích xuất 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 (double)
    • Cập nhật trạng thái màu của ứng dụng bằ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 sắc hiện tại
    • Ghi nhật ký kết quả hàm để gỡ lỗi
  3. handleUnknownFunction: Trình xử lý dự phòng cho các hàm không xác định:
    • Ghi lại cảnh báo về hàm không được hỗ trợ
    • Trả về phản hồi lỗi cho LLM

Hàm handleSetColor đặc biệt quan trọng vì nó giúp thu hẹp khoảng cách giữa khả năng hiểu ngôn ngữ tự nhiên của LLM và các 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 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 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_vertexai/firebase_vertexai.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';

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

part 'gemini_chat_service.g.dart';

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

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

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

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

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

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

Tìm hiểu quy trình giao tiếp

Điểm bổ sung chính ở đây là xử lý hoàn toàn 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:

  1. Kiểm tra xem phản hồi LLM có chứa lệnh gọi hàm nào không
  2. Đối với mỗi lệnh gọi hàm, hãy gọi phương thức handleFunctionCall bằng tên hàm và đối số
  3. Thu thập kết quả của mỗi lệnh gọi hàm
  4. Gửi các kết quả này trở lại LLM bằng Content.functionResponses
  5. Xử lý phản hồi của LLM đối với kết quả hàm
  6. 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 một luồng đi và về:

  • 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: Hiển thị màu mới
  • Ứng dụng → LLM: Kết quả hàm
  • LLM → Người dùng: Phản hồi cuối cùng kết hợp kết quả hàm

Tạo mã Riverpod

Chạy lệnh trình chạy bản dựng để 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

Bây giờ, hãy chạy ứng dụng:

flutter run -d DEVICE

Ảnh chụp màn hình ứng dụng Colorist cho thấy LLM Gemini phản hồi bằng lệnh gọi hàm

Hãy thử nhập nhiều nội dung mô tả màu sắc:

  • "Tôi muốn màu đỏ đậm"
  • "Hãy cho tôi xem màu xanh dương dịu mắt"
  • "Cho tôi màu lá bạc hà tươi"
  • "Tôi muốn thấy màu cam ấm áp của hoàng hôn"
  • "Đổi thành màu tím đậm"

Lúc này, bạn sẽ thấy:

  1. Tin nhắn của bạn xuất hiện trong giao diện trò chuyện
  2. Câu trả lời của Gemini xuất hiện trong cuộc trò chuyện
  3. Lệnh gọi hàm được ghi lại trong bảng điều khiển nhật ký
  4. Kết quả hàm được ghi lại ngay sau đó
  5. Hình chữ nhật màu cập nhật để hiển thị màu được mô tả
  6. Giá trị RGB cập nhật để hiển thị các thành phần của màu mới
  7. Câu trả lời cuối cùng của Gemini xuất hiện, thường là bình luận 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 ở hậu trường. Bạn sẽ thấy:

  • Lệnh gọi hàm chính xác mà Gemini đang thực hiện
  • Các thông số mà nó đang chọn cho từng giá trị RGB
  • Kết quả mà hàm của bạn trả về
  • Câu trả lời tiếp theo của Gemini

Trình thông báo trạng thái màu

colorStateNotifier mà bạn đang sử 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 đối với các thành phần trên giao diện người dùng

Khi bạn gọi updateColor bằng các giá trị RGB mới, hàm này sẽ:

  1. Tạo một đối tượng ColorData mới bằng các giá trị được cung cấp
  2. Cập nhật màu hiện tại trong trạng thái ứng dụng
  3. Thêm màu vào nhật ký
  4. 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 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 trải nghiệm phản ứng.

Tìm hiểu về cách xử lý lỗi

Quá trình triển khai của bạn bao gồm cả việc xử lý lỗi mạnh mẽ:

  1. Khối try-catch: Bao bọc tất cả các hoạt động tương tác với LLM để phát hiện mọi ngoại lệ
  2. Ghi nhật ký lỗi: Ghi lại lỗi trong bảng điều khiển nhật ký bằng dấu vết ngăn xếp
  3. Ý kiến phản hồi của người dùng: Cung cấp thông báo lỗi dễ hiểu trong cuộc trò chuyện
  4. 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 đảm bảo ứng dụng luôn ổn định và cung cấp phản hồi phù hợp ngay cả khi xảy ra sự cố với dịch vụ LLM hoặc quá trình thực thi hàm.

Sức mạnh của lệnh 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 minh hoạ cách LLM có thể tạo ra các giao diện tự nhiên mạnh mẽ:

  1. 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
  2. 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
  3. 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
  4. Câu trả lời theo ngữ cảnh: LLM cung cấp ngữ cảnh cuộc trò chuyện về những thay đổi này
  5. Gánh nặng 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 màu

Mẫu sử dụng lệnh gọi hàm LLM để 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 này có thể được mở rộng sang vô số lĩnh vực 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 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 hàm hoặc tham số của bạn không chính xác:

  • Xác minh nội dung khai báo hàm khớp với nội dung mô tả trong lời nhắc của hệ thống
  • Kiểm tra để đảm bảo tên và loại tham số nhất quán
  • Đảm bảo lời nhắc của hệ thống hướng dẫn rõ ràng cho LLM sử dụng công cụ này
  • Xác minh tên hàm trong trình xử lý khớp chính xác với tên trong phần 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 hàm

Nếu kết quả hàm không được gửi lại đúng cách đến LLM:

  • Kiểm tra để đảm bảo hàm của bạn trả về một Map được định dạng đúng cách
  • Xác minh rằng Content.functionResponses đang được tạo chính xác
  • Tìm mọi lỗi trong nhật ký liên quan đến phản hồi hàm
  • Đảm bảo bạn đang sử dụng cùng một phiên trò chuyện để trả lời

Vấn đề về màn hình màu

Nếu màu sắc không hiển thị 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 được gọi chính xác
  • Kiểm tra nhật ký để biết các giá trị chính xác đang đượ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 của Vertex AI trong Firebase
  • Kiểm tra xem có loại nào không khớp trong tham số hàm không
  • Đảm bảo tất cả mã do Riverpod tạo đều mới nhất

Các khái niệm chính đã học

  • Triển khai quy trình gọi hàm hoàn chỉnh trong Flutter
  • Tạo giao tiếp đầy đủ giữa LLM và ứng dụng
  • Xử lý dữ liệu có cấu trúc từ các phản hồi LLM
  • Gửi kết quả hàm trở lại LLM để đưa vào các phản hồi
  • Sử dụng bảng điều khiển nhật ký để nắm được các hoạt động tương tác giữa LLM và ứng dụng
  • Kết nối dữ liệu đầu vào bằng ngôn ngữ tự nhiên với các thay đổi cụ thể về giao diện người dùng

Khi hoàn tất bước này, ứng dụng của bạn sẽ thể hiện một trong những mẫu mạnh mẽ nhất để tích hợp LLM: dịch dữ liệu đầ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 xác nhận các hành động này. Điều này tạo ra một giao diện trò chuyện trực quan, thú vị cho người dùng.

7. Truyền trực tuyến phản hồi để cải thiện trải nghiệm người dùng

Ở 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 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 sử dụng LLM
  • Triển khai phản hồi LLM truyền trực tuyến trong ứng dụng Flutter
  • Xử lý một phần văn bản khi các phần văn bản đó đế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 hình ảnh cho các phản hồi đang diễn ra

Lý do việc truyền trực tuyến 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âu trả lời là rất quan trọng để tạo ra trải nghiệm tuyệt vời cho người dùng bằng LLM:

Cải thiện trải nghiệm người dùng

Tính năng truyền trực tuyến phản hồi mang lại một số lợi ích đáng kể về trải nghiệm người dùng:

  1. Giảm độ trễ nhận thấy: Người dùng thấy văn bản bắt đầu xuất hiện ngay lập tức (thường là trong vòng 100 đến 300 mili giây), thay vì chờ vài giây để nhận được phản hồi đầy đủ. Cảm nhận về tính tức thì này giúp cải thiện đáng kể mức độ hài lòng của người dùng.
  2. Nhịp điệu trò chuyện tự nhiên: Văn bản xuất hiện dần dần mô phỏng cách con người giao tiếp, tạo ra trải nghiệm trò chuyện tự nhiên hơn.
  3. Xử lý thông tin tăng dần: 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.
  4. Cơ hội để gián đoạn sớm: Trong một ứng dụng đầy đủ, người dùng có thể gián đoạn hoặc chuyển hướng LLM nếu họ thấy LLM đang đi theo hướng không hữu ích.
  5. Xác nhận hoạt động bằng hình ảnh: Văn bản truyền trực tuyến cung cấp phản hồi tức thì rằng hệ thống đang hoạt động, giúp giảm sự không chắc chắn.

Ưu điểm kỹ thuật

Ngoài việc cải thiện trải nghiệm người dùng, tính năng truyền trực tuyến còn mang lại các lợi ích kỹ thuật:

  1. Thực thi hàm sớm: Bạn có thể phát hiện và thực thi các lệnh gọi hàm ngay khi chúng xuất hiện trong luồng mà không cần chờ phản hồi hoàn chỉnh.
  2. Cập nhật giao diện người dùng tăng dần: Bạn có thể cập nhật giao diện người dùng theo từng bước khi có thông tin mới, tạo ra trải nghiệm linh động hơn.
  3. Quản lý trạng thái cuộc trò chuyện: Tính năng truyền trực tuyến cung cấp các tín hiệu rõ ràng về thời điểm phản hồi hoàn tất so với vẫn đang diễn ra, cho phép quản lý trạng thái hiệu quả hơn.
  4. 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 truyền 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 trực tuyến có nghĩa là người dùng sẽ thấy cả nội dung phản hồi văn bản và thay đổi màu sắc xuất hiện nhanh chóng hơn, tạo ra 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 nhà cung cấp trạng thái để theo dõi xem ứng dụng 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_vertexai/firebase_vertexai.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';

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

part 'gemini_chat_service.g.dart';

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

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

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

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

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

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

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

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

Tìm hiểu về cách triển khai tính năng truyền trực tuyến

Hãy phân tích những gì mã này thực hiện:

  1. Theo dõi trạng thái cuộc trò chuyện:
    • conversationStateProvider theo dõi xem ứng dụng có đang xử lý phản hồi hay không
    • Trạng thái chuyển đổi từ idlebusy trong khi xử lý, sau đó quay lại idle
    • Điều này giúp ngăn chặn nhiều yêu cầu đồng thời có thể xung đột
  2. Khởi chạy luồng:
    • sendMessageStream() trả về một Luồng các phần phản hồi thay vì Future có 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
  3. Xử lý tăng dần:
    • await for xử lý từng phần khi phầ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 truyền trực tuyến
    • Lệnh gọi hàm được thực thi ngay khi được phát hiện
  4. Xử lý lệnh gọi hàm:
    • Khi phát hiện thấy lệnh gọi hàm trong một đoạn, lệnh gọi hàm đó 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 trực tuyến 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 phương thức truyền trực tuyến
  5. Xử lý và dọn dẹp lỗi:
    • 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

Cách triển khai này tạo ra trải nghiệm truyền trực tuyến linh hoạt, đáng tin cậy trong khi vẫn duy trì trạng thái cuộc trò chuyện phù hợp.

Cập nhật màn hình chính để kết nối trạng thái cuộc trò chuyện

Sửa đổi tệp lib/main.dart để chuyể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à 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 đang xử lý phản hồi.

Điều này tạo ra trải nghiệm người dùng liền mạch, 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 trình chạy bản dựng để tạo mã Riverpod cần thiết:

dart run build_runner build --delete-conflicting-outputs

Chạy và kiểm thử phản hồi truyền trực tuyến

Chạy ứng dụng:

flutter run -d DEVICE

Ảnh chụp màn hình ứng dụng Colorist cho thấy LLM Gemini phản hồi theo kiểu truyền trực tuyến

Bây giờ, hãy thử kiểm thử hành vi truyền 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 lục đậm của đại dương vào lúc hoàng hôn"
  • "Tôi muốn thấy một màu san hô rực rỡ gợi nhớ đến hoa nhiệt đới"
  • "Tạo màu xanh ô liu nhạt như bộ đồ quân đội cũ"

Quy trình kỹ thuật phát trực tuyến chi tiết

Hãy cùng xem xét chính xác những gì xảy ra khi truyền trực tuyến một phản hồi:

Thiết lập kết nối

Khi bạn gọi sendMessageStream(), những điều sau sẽ xảy ra:

  1. Ứng dụng thiết lập kết nối với dịch vụ Vertex AI
  2. Yêu cầu của người dùng được gửi đến dịch vụ
  3. Máy chủ bắt đầu xử lý yêu cầu
  4. Kết nối luồng vẫn mở, sẵn sàng truyền các đoạn

Truyền dữ liệu theo khối

Khi Gemini tạo nội dung, các đoạn sẽ được gửi qua luồng:

  1. Máy chủ 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)
  2. Khi quyết định thực hiện lệnh gọi hàm, Gemini sẽ gửi thông tin về lệnh gọi hàm
  3. Các đoạn văn bản bổ sung có thể theo sau lệnh gọi hàm
  4. 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 dần

Ứng dụng của bạn xử lý từng phần tăng dần:

  1. Mỗi đoạn văn bản được thêm vào phản hồi hiện có
  2. Lệnh gọi hàm được thực thi ngay khi được phát hiện
  3. Giao diện người dùng cập nhật theo thời gian thực với cả kết quả văn bản và hàm
  4. Trạng thái được theo dõi để cho biết phản hồi vẫn đang 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:

  1. Máy chủ đã đóng luồng
  2. Vòng lặp await for của bạn thoát một cách tự nhiên
  3. Thông báo được đánh dấu là đã hoàn tất
  4. Trạng thái cuộc trò chuyện được đặt lại thành rảnh
  5. 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 nội dung phát trực tuyến với nội dung không phát trực tuyến

Để hiểu rõ hơn về lợi ích của tính năng 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ễ nhận thấy

Người dùng không thấy gì cho đến khi phản hồi hoàn chỉnh sẵn sàng

Người dùng thấy các từ đầu tiên trong vòng vài mili giây

Trải nghiệm người dùng

Phải đợi lâu rồi văn bản mới xuất hiện đột ngột

Giao diện văn bản tự nhiên, tăng dần

Quản lý trạng thái

Đơn giản hơn (tin nhắn đang chờ xử lý hoặc đã hoàn tất)

Phức tạp hơn (tin nhắn có thể ở trạng thái truyền trực tuyến)

Thực thi hàm

Chỉ xảy ra sau khi phản hồi hoàn tất

Xảy ra trong quá trình tạo phản hồi

Mức độ phức tạp khi triển khai

Dễ triển khai hơn

Yêu cầu quản lý trạng thái bổ sung

Khôi phục lỗi

Phản hồi tất cả hoặc không có gì

Một phần câu trả lời vẫn có thể hữu ích

Mức độ 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 tính năng truyền trực tuyến lớn hơn mức độ phức tạp khi triển khai, đặc biệt là đối với việc 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 để cải thiện trải nghiệm người dùng khi xem trực tuyến

Khi triển khai tính năng truyền trực tuyến trong các ứng dụng LLM của riêng bạn, hãy cân nhắc các phương pháp hay nhất sau:

  1. 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 tin nhắn phát trực tiếp với tin nhắn hoàn chỉnh
  2. Chặn hoạt động đầu vào: Tắt hoạt động đầu vào của người dùng trong khi truyền trực tuyến để ngăn nhiều yêu cầu trùng lặp
  3. Khôi phục lỗi: Thiết kế giao diện người dùng để xử lý việc khôi phục một cách linh hoạt nếu quá trình truyền phát bị gián đoạn
  4. Chuyển đổi trạng thái: Đảm bảo chuyển đổi mượt mà giữa trạng thái rảnh, truyền trực tuyến và hoàn tất
  5. Hình ảnh hoá tiến trình: Hãy 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
  6. Tuỳ 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ỷ quá trình tạo đang diễn ra
  7. Tích hợp kết quả hàm: Thiết kế giao diện người dùng để xử lý kết quả hàm xuất hiện giữa luồng
  8. 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 cần cân nhắc quan trọng đố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 trải nghiệm gắn kết hơn, trong đó LLM nhận biết được các thay đổi do người dùng khởi tạo đối với trạng thái ứng dụng.

Khắc phục sự cố

Vấn đề về xử lý luồng

Nếu bạn gặp vấn đề khi xử lý luồng:

  • Triệu chứng: Chỉ nhận được một phần phản hồi, thiếu văn bản hoặc kết thúc luồng độ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 không đồng bộ/chờ phù hợp trong mã
  • 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 tất cả quá trình xử lý luồng đều sử dụng phương thức 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 được 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 hiển thị 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 liệu có nhận được lệnh gọi hàm hay không
  • Khắc phục: Điều chỉnh lời nhắc hệ thống để hướng dẫn rõ ràng hơn cho LLM 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 Vertex AI trong Firebase
  • Bước 3: Đảm bảo tất cả mã do Riverpod tạo đều được cập nhật
  • Bước 4: Xem lại cách triển khai tính năng truyền phát để tìm mọi câu lệnh await bị thiếu

Các khái niệm chính đã học

  • Triển khai phản hồi phát trực tuyến bằng Gemini API để có trải nghiệm người dùng thích ứng hơn
  • Quản lý trạng thái cuộc trò chuyện để xử lý đúng cách các lượt tương tác phát trực tuyến
  • Xử lý văn bản theo thời gian thực và 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 dần trong khi truyền trực tuyến
  • 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 phát phản hồi

Bằng cách triển khai tính năng truyền trực tuyến, bạn đã nâng cao đá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à thực sự mang tính trò chuyện.

8. Đồng bộ hoá ngữ 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 gắn kết 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ỉ 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á ngữ cảnh LLM giữa giao diện người dùng và LLM
  • Tạo tuần tự 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 ngữ cảnh cuộc 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 ngữ cảnh của LLM ngoài các tin nhắn trò chuyện phản cảm

Tìm hiểu về tính năng Đồng bộ hoá ngữ cảnh LLM

Các chatbot truyền thống chỉ phản hồi tin nhắn rõ ràng của người dùng, tạo ra sự ngắt kết nối 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á ngữ cảnh LLM 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 thành phần trên 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 điều gì đã xảy ra trừ phi bạn cho biết rõ ràng. Đồng bộ hoá ngữ cảnh LLM:

  1. Duy trì ngữ cảnh: Thông báo cho LLM về tất cả hành động liên quan của người dùng
  2. Tạo tính nhất quán: Tạo trải nghiệm gắn kết trong đó LLM ghi nhận các hoạt động tương tác trên giao diện người dùng
  3. Cải thiện trí tuệ: Cho phép LLM phản hồi phù hợp với mọi hành động của người dùng
  4. Cải thiện trải nghiệm người dùng: Giúp toàn bộ ứng dụng trở nên tích hợp và phản hồi nhanh hơn
  5. Giảm công sức của người dùng: Người dùng không cần phải 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 xác nhận hành động này và nhận xét một cách thông minh về màu đã chọn, duy trì ảo tưởng về một trợ lý liền mạch và nhận biết được.

Cập nhật dịch vụ trò chuyện Gemini để nhận thông báo về lựa chọn màu

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_vertexai/firebase_vertexai.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';

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

part 'gemini_chat_service.g.dart';

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

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

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

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

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

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

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

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

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

Điểm bổ sung chính là phương thức notifyColorSelection, phương thức này:

  1. Lấy đối tượng ColorData đại diện cho màu đã chọn
  2. Mã hoá thông báo đó thành định dạng JSON có thể được đưa vào một thông báo
  3. 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
  4. 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 giúp 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 đế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à thêm lệnh gọi lại notifyColorSelection, kết nối sự kiệ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 lời nhắc của hệ thống

Bây giờ, bạn cần cập nhật lời nhắc của hệ thống để hướng dẫn LLM về cách phản hồi thông báo lựa 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 trước đây", trong đó:

  1. Giải thích khái niệm thông báo lựa chọn nhật ký cho LLM
  2. Cung cấp ví dụ về giao diện của các thông báo này
  3. Cho thấy ví dụ về một câu trả lời phù hợp
  4. Đặ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 trình chạy bản dựng để 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á ngữ cảnh LLM

Chạy ứng dụng:

flutter run -d DEVICE

Ảnh chụp màn hình ứng dụng Colorist cho thấy LLM của Gemini phản hồi một lựa chọn trong nhật ký màu

Quy trình kiểm thử tính năng Đồng bộ hoá ngữ cảnh LLM bao gồm:

  1. Trước tiên, hãy tạo một vài màu bằng cách mô tả các màu đó trong cuộc trò chuyện
    • "Hiển thị màu tím rực rỡ"
    • "Tôi muốn màu xanh lục rừng"
    • "Cho tôi một màu đỏ tươi"
  2. Sau đó, hãy nhấp vào một trong các hình thu nhỏ màu trong dải nhật ký

Bạn nên quan sát:

  1. Màu đã chọn sẽ xuất hiện trên màn hình chính
  2. Tin nhắn của người dùng xuất hiện trong cuộc trò chuyện cho biết lựa chọn màu
  3. LLM phản hồi bằng cách xác nhận lựa chọn và nhận xét về màu sắc
  4. Toàn bộ quá trình tương tác diễn ra tự nhiên và gắn kết

Điều này tạo ra trải nghiệm liền mạch, trong đó LLM nhận biết và phản hồi phù hợp cả tin nhắn trực tiếp và 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á ngữ cảnh LLM

Hãy cùng tìm hiểu thông tin kỹ thuật về cách hoạt động của tính năng đồng bộ hoá này:

Luồng dữ liệu

  1. Thao tác của người dùng: Người dùng nhấp vào một màu trong dải nhật ký
  2. Sự kiện giao diện người dùng: Tiện ích MainScreen phát hiện lựa chọn này
  3. Thực thi lệnh gọi lại: Lệnh gọi lại notifyColorSelection được kích hoạt
  4. Tạo thông báo: Một thông báo được định dạng đặc biệt sẽ được tạo bằng dữ liệu màu
  5. Xử lý LLM: Tin nhắn được gửi đến Gemini để nhận dạng định dạng
  6. Phản hồi theo ngữ cảnh: Gemini phản hồi phù hợp dựa trên câu lệnh của hệ thống
  7. 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 liền mạch

Tuần tự hoá dữ liệu

Một khía cạnh chính 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 đối tượng ColorData thành một bản đồ có các thuộc tính chính mà LLM có thể hiểu được. 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

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 rằng LLM có tất cả thông tin cần thiết để phản hồi một cách phù hợp.

Các ứng dụng rộng rãi hơn của tính năng Đồng bộ hoá ngữ cảnh LLM

Mẫu 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

  1. Thay đổi bộ lọc: Thông báo cho LLM khi người dùng áp dụng bộ lọc cho dữ liệu
  2. Sự kiện điều hướng: Thông báo cho LLM khi người dùng điều hướng đến các phần khác nhau
  3. 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
  4. Cập nhật lựa chọn ưu tiên: Thông báo cho LLM khi người dùng thay đổi chế độ cài đặt hoặc lựa chọn ưu tiên
  5. Thao tác với 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:

  1. Phát hiện sự kiện trên giao diện người dùng
  2. Tạo tuần tự dữ liệu có liên quan
  3. Gửi thông báo có định dạng đặc biệt đến LLM
  4. Hướng dẫn LLM phản hồi phù hợp thông qua lời nhắc của hệ thống

Các phương pháp hay nhất để đồng bộ hoá ngữ cảnh LLM

Dựa trên cách triển khai của bạn, sau đây là một số phương pháp hay nhất để Đồng bộ hoá ngữ cảnh LLM 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:

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

2. Ngữ cảnh đa dạng thức

Cung cấp đủ thông tin chi tiết trong thông báo để LLM 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à giá trị RGB, mã thập lục phân và mọi thuộc tính liên quan khác.

3. Hướng dẫn rõ ràng

Cung cấp hướng dẫn rõ ràng trong lời nhắc của hệ thống về cách xử lý thông báo, tốt nhất là kèm theo ví dụ.

4. Tích hợp tự nhiên

Thiết kế thông báo để diễn ra tự nhiên trong cuộc trò chuyện, chứ không phải là sự gián đoạn về 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 có 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 thông báo.

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 với các lựa chọn màu:

  • Kiểm tra để đảm bảo định dạng thông báo khớp với nội dung mô tả trong lời nhắc của hệ thống
  • Xác minh rằng dữ liệu màu đang được chuyển đổi tuần tự đúng cách
  • Đảm bảo lời nhắc của hệ thống có hướng dẫn rõ ràng để 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 được duy trì đúng cách
  • Xác minh rằng trạng thái cuộc trò chuyện chuyển đổi chính xác
  • Đảm bảo rằng thông báo đượ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 của Vertex AI trong Firebase
  • Kiểm tra xem có loại nào không khớp trong tham số hàm không
  • Đảm bảo tất cả mã do Riverpod tạo đều 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
  • Tạo tuần tự các sự kiện trên giao diện người dùng thành ngữ cảnh thân thiện với LLM
  • Hướng dẫn hành vi của LLM cho nhiều mẫu tương tác
  • Tạo trải nghiệm gắn kết giữa các hoạt động 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á ngữ cảnh LLM, bạn đã tạo ra một trải nghiệm tích hợp thực sự, trong đó LLM giống như một trợ lý nhạy bén, phản hồi nhanh 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 giao diện tự nhiên, trực quan hơn nhờ 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 về Colorist! 🎉

Sản phẩm bạn đã tạo

Bạn đã tạo một ứng dụng Flutter đầy đủ chức năng, tích hợp API Gemini của Google để diễn giải nội dung mô tả màu bằng ngôn ngữ tự nhiên. Ứng dụng của bạn hiện có thể:

  • Xử lý nội dung mô tả bằng ngôn ngữ tự nhiên như "cam hoàng hôn" hoặc "xanh dương đậm"
  • Sử dụng Gemini để dịch thông tin mô tả này thành giá trị RGB một cách thông minh
  • Hiển thị màu được diễn giải theo thời gian thực bằng các phản hồ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ả phần tử trò chuyện và giao diện người dùng
  • Duy trì khả năng nhận biết ngữ 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ề cách tích hợp Gemini với Flutter, hãy tham khảo một số cách sau để tiếp tục hành trình:

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 lời nói để mô tả màu sắc bằng lời nói
  • Quản lý nhật ký: Thêm các tuỳ chọn để đặt tên, sắp xếp và xuất bộ màu
  • Lời nhắc tuỳ chỉnh: Tạo giao diện để người dùng tuỳ chỉnh lời nhắc của 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 hình ảnh để trích xuất màu từ ảnh
  • Tạo nội dung: Sử dụng Gemini để tạo nội dung liên quan đến màu sắc như nội dung mô tả hoặc câu chuyện
  • Các tính năng nâng cao về lệnh gọi hàm: Tạo các công cụ tích hợp 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 các chế độ cài đặt đó đến phản hồ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 ứ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ông cụ viết bằng các đề xuất dựa trên LLM
  • Tự động hoá tác vụ: Thiết kế ứng dụng dịch ngôn ngữ tự nhiên thành tác 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 lĩnh vực cụ thể

Tài nguyên

Dưới đây là một số tài nguyên hữu ích để 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 về câu lệnh

Cộng đồng

Loạt bài về Tác nhân Flutter có thể quan sát

Trong tập 59, Craig Labenz và Andrew Brogden khám phá lớp học lập trình này, làm nổi bật các phần thú vị của bản dựng ứ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 tính năng mới và cố gắng làm cho LLM làm theo yêu cầu.

Trong tập 61, Craig cùng với Chris Sells sẽ mang đến một góc nhìn mới về việc phân tích dòng tiêu đề tin tức và tạo hình ảnh tương ứng.

Phản hồi

Chúng tôi rất mong được nghe bạn chia sẻ về 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ị ở điểm giao thoa giữa Flutter và AI!