1. Создайте приложение Flutter на базе Gemini
Что вы построите
В этой лабораторной работе вы создадите Colorist — интерактивное приложение Flutter, которое перенесёт мощь API Gemini прямо в ваше приложение Flutter. Вы когда-нибудь хотели позволить пользователям управлять вашим приложением с помощью естественного языка, но не знали, с чего начать? Эта лабораторная работа покажет вам, как это сделать.
Colorist позволяет пользователям описывать цвета на естественном языке (например, «оранжевый цвет заката» или «глубокий синий океан»), а приложение:
- Обрабатывает эти описания с помощью API Gemini от Google.
- Интерпретирует описания в точные значения цветов RGB.
- Отображает цвет на экране в режиме реального времени
- Предоставляет технические сведения о цвете и интересный контекст, связанный с ним.
- Сохраняет историю недавно созданных цветов
Приложение имеет интерфейс с разделённым экраном: с одной стороны — цветная область отображения и интерактивный чат, а с другой — подробная панель журнала, отображающая необработанные взаимодействия LLM. Этот журнал позволяет лучше понять, как на самом деле работает интеграция LLM.
Почему это важно для разработчиков Flutter
Программы магистратуры по управлению правами (LLM) кардинально меняют взаимодействие пользователей с приложениями, но их эффективная интеграция в мобильные и настольные приложения представляет собой особую сложность. Эта практическая работа познакомит вас с практическими подходами, выходящими за рамки простых вызовов API.
Ваш учебный путь
В этой лабораторной работе вы шаг за шагом пройдете процесс создания Colorist:
- Настройка проекта . Вы начнете с базовой структуры приложения Flutter и пакета
colorist_ui
. - Базовая интеграция Gemini — подключите свое приложение к Firebase AI Logic и реализуйте LLM-коммуникацию
- Эффективные подсказки . Создайте системную подсказку, которая поможет LLM понять описания цветов.
- Объявления функций — определение инструментов, которые LLM может использовать для установки цветов в вашем приложении.
- Обработка инструментов — обработка вызовов функций из LLM и их подключение к состоянию вашего приложения.
- Потоковые ответы — Улучшите пользовательский опыт с помощью потоковых ответов LLM в режиме реального времени.
- Синхронизация контекста LLM — создание целостного опыта путем информирования LLM о действиях пользователя.
Чему вы научитесь
- Настройка Firebase AI Logic для приложений Flutter
- Разработать эффективные системные подсказки для управления поведением LLM
- Реализуйте объявления функций , которые связывают естественный язык и функции приложения.
- Обработка потоковых ответов для обеспечения отзывчивого пользовательского опыта
- Синхронизировать состояние между событиями пользовательского интерфейса и LLM
- Управление состоянием беседы LLM с помощью Riverpod
- Корректная обработка ошибок в приложениях на базе LLM
Предварительный просмотр кода: пример того, что вы реализуете
Вот краткий обзор объявления функции, которую вы создадите, чтобы позволить LLM устанавливать цвета в вашем приложении:
FunctionDeclaration get setColorFuncDecl => FunctionDeclaration(
'set_color',
'Set the color of the display square based on red, green, and blue values.',
parameters: {
'red': Schema.number(description: 'Red component value (0.0 - 1.0)'),
'green': Schema.number(description: 'Green component value (0.0 - 1.0)'),
'blue': Schema.number(description: 'Blue component value (0.0 - 1.0)'),
},
);
Видеообзор этой лабораторной работы
Посмотрите, как Крейг Лабенц и Эндрю Брогдон обсуждают эту лабораторную работу в выпуске Observable Flutter #59:
Предпосылки
Чтобы извлечь максимальную пользу из этого практического занятия, вам необходимо:
- Опыт разработки Flutter — знакомство с основами Flutter и синтаксисом Dart
- Знание асинхронного программирования — понимание Futures, async/await и потоков
- Учетная запись Firebase . Для настройки Firebase вам понадобится учетная запись Google.
Давайте начнем создавать ваше первое приложение Flutter на базе LLM!
2. Настройка проекта и эхо-сервис
На первом этапе вы настроите структуру проекта и реализуете эхо-сервис, который впоследствии будет заменён интеграцией с API Gemini. Это задаёт архитектуру приложения и обеспечивает корректную работу пользовательского интерфейса перед добавлением сложных вызовов LLM.
Чему вы научитесь на этом этапе
- Настройка проекта Flutter с необходимыми зависимостями
- Работа с пакетом
colorist_ui
для компонентов пользовательского интерфейса - Реализация службы эхо-сообщений и подключение ее к пользовательскому интерфейсу
Создайте новый проект Flutter
Начните с создания нового проекта Flutter с помощью следующей команды:
flutter create -e colorist --platforms=android,ios,macos,web,windows
Флаг -e
указывает на то, что вам нужен пустой проект без приложения- counter
по умолчанию. Приложение предназначено для работы на компьютерах, мобильных устройствах и в веб-браузерах. Однако в настоящее время flutterfire
не поддерживает Linux.
Добавить зависимости
Перейдите в каталог вашего проекта и добавьте необходимые зависимости:
cd colorist
flutter pub add colorist_ui flutter_riverpod riverpod_annotation
flutter pub add --dev build_runner riverpod_generator riverpod_lint json_serializable
Это добавит следующие ключевые пакеты:
-
colorist_ui
: пользовательский пакет, предоставляющий компоненты пользовательского интерфейса для приложения Colorist. -
flutter_riverpod
иriverpod_annotation
: для управления состоянием -
logging
: для структурированного ведения журнала - Зависимости разработки для генерации кода и линтинга
Ваш pubspec.yaml
будет выглядеть примерно так:
pubspec.yaml
name: colorist
description: "A new Flutter project."
publish_to: 'none'
version: 0.1.0
environment:
sdk: ^3.9.2
dependencies:
flutter:
sdk: flutter
colorist_ui: ^0.3.0
flutter_riverpod: ^3.0.0
riverpod_annotation: ^3.0.0
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^6.0.0
build_runner: ^2.7.1
riverpod_generator: ^3.0.0
riverpod_lint: ^3.0.0
json_serializable: ^6.11.1
flutter:
uses-material-design: true
Реализовать файл main.dart
Замените содержимое lib/main.dart
следующим:
lib/main.dart
import 'package:colorist_ui/colorist_ui.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
void main() async {
runApp(ProviderScope(child: MainApp()));
}
class MainApp extends ConsumerWidget {
const MainApp({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
return MaterialApp(
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
),
home: MainScreen(
sendMessage: (message) {
sendMessage(message, ref);
},
),
);
}
// A fake LLM that just echoes back what it receives.
void sendMessage(String message, WidgetRef ref) {
final chatStateNotifier = ref.read(chatStateProvider.notifier);
final logStateNotifier = ref.read(logStateProvider.notifier);
chatStateNotifier.addUserMessage(message);
logStateNotifier.logUserText(message);
chatStateNotifier.addLlmMessage(message, MessageState.complete);
logStateNotifier.logLlmText(message);
}
}
Это настраивает приложение Flutter, реализующее эхо-сервис, который имитирует поведение LLM, возвращая сообщение пользователя.
Понимание архитектуры
Давайте уделим минуту изучению архитектуры приложения colorist
:
Пакет colorist_ui
Пакет colorist_ui
предоставляет готовые компоненты пользовательского интерфейса и инструменты управления состоянием:
- MainScreen : основной компонент пользовательского интерфейса, который отображает:
- Разделенный экран на рабочем столе (область взаимодействия и панель журнала)
- Интерфейс с вкладками на мобильном устройстве
- Цветной дисплей, интерфейс чата и миниатюры истории
- Управление состоянием : приложение использует несколько уведомлений о состоянии:
- ChatStateNotifier : управляет сообщениями чата.
- ColorStateNotifier : управляет текущим цветом и историей
- LogStateNotifier : управляет записями журнала для отладки.
- Обработка сообщений : приложение использует модель сообщений с различными состояниями:
- Сообщения пользователя : Введено пользователем
- Сообщения LLM : генерируются LLM (или вашей службой эха на данный момент)
- MessageState : отслеживает, завершены ли сообщения LLM или они все еще передаются
Архитектура приложения
Приложение имеет следующую архитектуру:
- Уровень пользовательского интерфейса : предоставляется пакетом
colorist_ui
- Управление состоянием : использует Riverpod для реактивного управления состоянием.
- Уровень обслуживания : в настоящее время содержит простую службу эха, которая будет заменена службой чата Gemini.
- Интеграция LLM : будет добавлена на последующих этапах
Такое разделение позволяет вам сосредоточиться на реализации интеграции LLM, в то время как компоненты пользовательского интерфейса уже реализованы.
Запустите приложение
Запустите приложение с помощью следующей команды:
flutter run -d DEVICE
Замените DEVICE
на ваше целевое устройство, например macos
, windows
, chrome
или идентификатор устройства.
Теперь вы должны увидеть приложение Colorist со следующими параметрами:
- Цветная область отображения с цветом по умолчанию
- Интерфейс чата, в котором вы можете печатать сообщения
- Панель журнала, показывающая взаимодействие в чате
Попробуйте ввести сообщение, например, «Мне нужен тёмно-синий цвет», и нажмите «Отправить». Служба Echo просто повторит ваше сообщение. На последующих этапах вы замените его на фактическую цветовую интерпретацию с помощью Firebase AI Logic.
Что дальше?
На следующем этапе вы настроите Firebase и реализуете базовую интеграцию с API Gemini, чтобы заменить ваш эхо-сервис на чат-сервис Gemini. Это позволит приложению интерпретировать описания цветов и предоставлять интеллектуальные ответы.
Поиск неисправностей
Проблемы с пакетом пользовательского интерфейса
Если у вас возникли проблемы с пакетом colorist_ui
:
- Убедитесь, что вы используете последнюю версию.
- Убедитесь, что вы правильно добавили зависимость.
- Проверьте наличие конфликтующих версий пакетов.
Ошибки сборки
Если вы видите ошибки сборки:
- Убедитесь, что у вас установлена последняя стабильная версия Flutter SDK.
- Запустите
flutter clean
а затемflutter pub get
- Проверьте вывод консоли на наличие конкретных сообщений об ошибках.
Ключевые изученные концепции
- Настройка проекта Flutter с необходимыми зависимостями
- Понимание архитектуры приложения и обязанностей компонентов
- Реализация простого сервиса, имитирующего поведение LLM
- Подключение сервиса к компонентам пользовательского интерфейса
- Использование Riverpod для управления состоянием
3. Базовая интеграция чата Gemini
На этом этапе вы замените эхо-сервис из предыдущего шага на интеграцию с Gemini API с помощью Firebase AI Logic. Вы настроите Firebase, настроите необходимых поставщиков и реализуете базовый чат-сервис, взаимодействующий с Gemini API.
Чему вы научитесь на этом этапе
- Настройка Firebase в приложении Flutter
- Настройка Firebase AI Logic для доступа Gemini
- Создание поставщиков Riverpod для сервисов Firebase и Gemini
- Реализация базового чат-сервиса с использованием API Gemini
- Обработка асинхронных ответов API и состояний ошибок
Настройте Firebase
Сначала вам необходимо настроить Firebase для вашего проекта Flutter. Это включает в себя создание проекта Firebase, добавление в него вашего приложения и настройку необходимых параметров Firebase AI Logic.
Создать проект Firebase
- Перейдите в консоль Firebase и войдите в свою учетную запись Google.
- Нажмите «Создать проект Firebase» или выберите существующий проект.
- Следуйте указаниям мастера настройки, чтобы создать свой проект.
Настройте Firebase AI Logic в своем проекте Firebase
- В консоли Firebase перейдите к своему проекту.
- На левой боковой панели выберите AI .
- В раскрывающемся меню ИИ выберите Логика ИИ .
- На карточке Firebase AI Logic выберите Get Started .
- Следуйте инструкциям, чтобы включить API разработчика Gemini для вашего проекта.
Установите FlutterFire CLI
Интерфейс командной строки FlutterFire упрощает настройку Firebase в приложениях Flutter:
dart pub global activate flutterfire_cli
Добавьте Firebase в свое приложение Flutter
- Добавьте пакеты Firebase core и Firebase AI Logic в свой проект:
flutter pub add firebase_core firebase_ai
- Выполните команду настройки FlutterFire:
flutterfire configure
Эта команда выполнит:
- Попросите вас выбрать проект Firebase, который вы только что создали.
- Зарегистрируйте свои приложения Flutter в Firebase
- Создайте файл
firebase_options.dart
с конфигурацией вашего проекта.
Команда автоматически определит выбранные вами платформы (iOS, Android, macOS, Windows, веб) и настроит их соответствующим образом.
Конфигурация, зависящая от платформы
Firebase требует версии как минимум выше, чем те, что по умолчанию используются для Flutter. Также требуется доступ к сети для взаимодействия с серверами Firebase AI Logic.
Настройте разрешения macOS
Для macOS вам необходимо включить сетевой доступ в правах вашего приложения:
- Откройте
macos/Runner/DebugProfile.entitlements
и добавьте:
macos/Runner/DebugProfile.entitlements
<key>com.apple.security.network.client</key>
<true/>
- Также откройте
macos/Runner/Release.entitlements
и добавьте ту же запись.
Настройте параметры iOS
Для iOS обновите минимальную версию в верхней части ios/Podfile
:
ios/Podfile
# Firebase requires at least iOS 15.0
platform :ios, '15.0'
Создайте поставщиков моделей Gemini
Теперь нужно создать поставщиков Riverpod для Firebase и Gemini. Создайте новый файл lib/providers/gemini.dart
:
lib/providers/gemini.dart
import 'dart:async';
import 'package:firebase_ai/firebase_ai.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import '../firebase_options.dart';
part 'gemini.g.dart';
@riverpod
Future<FirebaseApp> firebaseApp(Ref ref) =>
Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
@riverpod
Future<GenerativeModel> geminiModel(Ref ref) async {
await ref.watch(firebaseAppProvider.future);
final model = FirebaseAI.googleAI().generativeModel(
model: 'gemini-2.0-flash',
);
return model;
}
@Riverpod(keepAlive: true)
Future<ChatSession> chatSession(Ref ref) async {
final model = await ref.watch(geminiModelProvider.future);
return model.startChat();
}
Этот файл определяет основу для трёх ключевых поставщиков. Эти поставщики генерируются при запуске dart run build_runner
генераторами кода Riverpod.
-
firebaseAppProvider
: инициализирует Firebase с конфигурацией вашего проекта. -
geminiModelProvider
: создает экземпляр генеративной модели Gemini. -
chatSessionProvider
: создает и поддерживает сеанс чата с моделью Gemini.
Аннотация keepAlive: true
в сеансе чата гарантирует его сохранение на протяжении всего жизненного цикла приложения, поддерживая контекст разговора.
Реализовать чат-сервис Gemini
Создайте новый файл lib/services/gemini_chat_service.dart
для реализации службы чата:
lib/services/gemini_chat_service.dart
import 'dart:async';
import 'package:colorist_ui/colorist_ui.dart';
import 'package:firebase_ai/firebase_ai.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import '../providers/gemini.dart';
part 'gemini_chat_service.g.dart';
class GeminiChatService {
GeminiChatService(this.ref);
final Ref ref;
Future<void> sendMessage(String message) async {
final chatSession = await ref.read(chatSessionProvider.future);
final chatStateNotifier = ref.read(chatStateProvider.notifier);
final logStateNotifier = ref.read(logStateProvider.notifier);
chatStateNotifier.addUserMessage(message);
logStateNotifier.logUserText(message);
final llmMessage = chatStateNotifier.createLlmMessage();
try {
final response = await chatSession.sendMessage(Content.text(message));
final responseText = response.text;
if (responseText != null) {
logStateNotifier.logLlmText(responseText);
chatStateNotifier.appendToMessage(llmMessage.id, responseText);
}
} catch (e, st) {
logStateNotifier.logError(e, st: st);
chatStateNotifier.appendToMessage(
llmMessage.id,
"\nI'm sorry, I encountered an error processing your request. "
"Please try again.",
);
} finally {
chatStateNotifier.finalizeMessage(llmMessage.id);
}
}
}
@riverpod
GeminiChatService geminiChatService(Ref ref) => GeminiChatService(ref);
Эта услуга:
- Принимает сообщения пользователей и отправляет их в API Gemini
- Обновляет интерфейс чата ответами модели.
- Регистрирует все коммуникации для облегчения понимания реального потока LLM
- Обрабатывает ошибки с соответствующей обратной связью от пользователей.
Примечание: на этом этапе окно журнала будет выглядеть практически идентично окну чата. Журнал станет интереснее, когда вы добавите вызовы функций и потоковые ответы.
Сгенерировать код Riverpod
Запустите команду build runner, чтобы сгенерировать необходимый код Riverpod:
dart run build_runner build --delete-conflicting-outputs
Это создаст файлы .g.dart
, необходимые для работы Riverpod.
Обновите файл main.dart
Обновите файл lib/main.dart
, чтобы использовать новую службу чата Gemini:
lib/main.dart
import 'package:colorist_ui/colorist_ui.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'providers/gemini.dart';
import 'services/gemini_chat_service.dart';
void main() async {
runApp(ProviderScope(child: MainApp()));
}
class MainApp extends ConsumerWidget {
const MainApp({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final model = ref.watch(geminiModelProvider);
return MaterialApp(
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
),
home: model.when(
data: (data) => MainScreen(
sendMessage: (text) {
ref.read(geminiChatServiceProvider).sendMessage(text);
},
),
loading: () => LoadingScreen(message: 'Initializing Gemini Model'),
error: (err, st) => ErrorScreen(error: err),
),
);
}
}
Ключевые изменения в этом обновлении:
- Замена эхо-сервиса на чат-сервис на базе API Gemini
- Добавление экранов загрузки и ошибок с использованием шаблона Riverpod
AsyncValue
с методомwhen
- Подключение пользовательского интерфейса к новой службе чата через обратный вызов
sendMessage
Запустите приложение
Запустите приложение с помощью следующей команды:
flutter run -d DEVICE
Замените DEVICE
на ваше целевое устройство, например macos
, windows
, chrome
или идентификатор устройства.
Теперь при вводе сообщения оно будет отправлено в API Gemini, и вы получите ответ от LLM, а не эхо. В панели журнала будет отображаться взаимодействие с API.
Понимание коммуникации LLM
Давайте на минутку разберемся, что происходит при взаимодействии с API Gemini:
Поток коммуникации
- Пользовательский ввод : пользователь вводит текст в интерфейс чата.
- Форматирование запроса : приложение форматирует текст как объект
Content
для API Gemini. - API-коммуникация : текст отправляется в API Gemini через Firebase AI Logic.
- Обработка LLM : модель Gemini обрабатывает текст и генерирует ответ.
- Обработка ответа : приложение получает ответ и обновляет пользовательский интерфейс.
- Ведение журнала : все сообщения регистрируются для обеспечения прозрачности.
Сеансы чата и контекст разговора
В чате Gemini сохраняется контекст между сообщениями, что позволяет вести диалог. Это означает, что LLM «запоминает» предыдущие сообщения в текущем сеансе, что позволяет вести более связное общение.
Аннотация keepAlive: true
в вашем провайдере сеанса чата гарантирует сохранение этого контекста на протяжении всего жизненного цикла приложения. Этот постоянный контекст критически важен для поддержания естественного течения диалога с LLM.
Что дальше?
На этом этапе вы можете задавать Gemini API любые запросы, поскольку нет ограничений на ответы. Например, вы можете запросить краткое содержание «Войны Роз», что не имеет отношения к назначению вашего приложения для работы с цветом.
На следующем этапе вы создадите системную подсказку, которая поможет Gemini более эффективно интерпретировать описания цветов. Это продемонстрирует, как настроить поведение LLM в соответствии с потребностями конкретного приложения и сосредоточить его возможности на предметной области вашего приложения.
Поиск неисправностей
Проблемы с конфигурацией Firebase
Если у вас возникли ошибки при инициализации Firebase:
- Убедитесь, что файл
firebase_options.dart
был правильно сгенерирован. - Убедитесь, что вы перешли на тарифный план Blaze для доступа к Firebase AI Logic.
Ошибки доступа к API
Если вы получаете ошибки при доступе к API Gemini:
- Убедитесь, что выставление счетов в вашем проекте Firebase настроено правильно.
- Проверьте, включены ли Firebase AI Logic и Cloud AI API в вашем проекте Firebase.
- Проверьте сетевое подключение и настройки брандмауэра.
- Убедитесь, что название модели (
gemini-2.0-flash
) указано правильно и доступно.
Проблемы контекста разговора
Если вы заметили, что Gemini не помнит предыдущий контекст чата:
- Убедитесь, что функция
chatSession
аннотирована@Riverpod(keepAlive: true)
- Убедитесь, что вы повторно используете один и тот же сеанс чата для всех обменов сообщениями.
- Перед отправкой сообщений убедитесь, что сеанс чата правильно инициализирован.
Проблемы, специфичные для платформы
По вопросам, связанным с платформой:
- iOS/macOS: убедитесь, что установлены правильные права и настроены минимальные версии.
- Android: убедитесь, что минимальная версия SDK установлена правильно.
- Проверьте сообщения об ошибках, специфичные для платформы, в консоли.
Ключевые изученные концепции
- Настройка Firebase в приложении Flutter
- Настройка Firebase AI Logic для доступа к Gemini
- Создание поставщиков Riverpod для асинхронных сервисов
- Реализация чат-сервиса для взаимодействия с LLM
- Обработка асинхронных состояний API (загрузка, ошибка, данные)
- Понимание потока коммуникации LLM и сеансов чата
4. Эффективное напоминание о цвете
На этом этапе вы создадите и реализуете системную подсказку, которая поможет Gemini интерпретировать описания цветов. Системные подсказки — это мощный способ настроить поведение LLM для конкретных задач без изменения кода.
Чему вы научитесь на этом этапе
- Понимание системных подсказок и их важности в заявках на получение степени магистра права
- Разработка эффективных подсказок для задач, специфичных для предметной области
- Загрузка и использование системных подсказок в приложении Flutter
- Помощь LLM в предоставлении единообразных ответов
- Тестирование того, как системные подсказки влияют на поведение LLM
Понимание системных подсказок
Прежде чем углубляться в реализацию, давайте разберемся, что такое системные подсказки и почему они важны:
Что такое системные подсказки?
Системное приглашение — это особый тип инструкции, предоставляемой LLM, которая задаёт контекст, правила поведения и ожидания от его ответов. В отличие от пользовательских сообщений, системные приглашения:
- Определить роль и личность магистра права
- Определить специализированные знания или возможности
- Предоставьте инструкции по форматированию
- Установить ограничения на ответы
- Опишите, как действовать в различных ситуациях
Представьте, что системная подсказка — это «должностная инструкция» для магистра права: она сообщает модели, как себя вести на протяжении всего разговора.
Почему системные подсказки имеют значение
Системные подсказки имеют решающее значение для создания последовательных и полезных взаимодействий LLM, поскольку они:
- Обеспечьте согласованность : направляйте модель на предоставление ответов в согласованном формате.
- Повышение релевантности : сфокусируйте модель на вашей конкретной области (в вашем случае — на цветах).
- Установите границы : определите, что модель должна и не должна делать.
- Улучшите пользовательский опыт : создайте более естественный и полезный шаблон взаимодействия.
- Сократите постобработку : получайте ответы в форматах, которые легче анализировать и отображать.
Для вашего приложения Colorist вам понадобится степень магистра права, чтобы последовательно интерпретировать описания цветов и предоставлять значения RGB в определенном формате.
Создать системный запрос
Сначала вы создадите файл системного приглашения, который будет загружаться во время выполнения. Такой подход позволяет изменять приглашение без перекомпиляции приложения.
Создайте новый файл assets/system_prompt.md
со следующим содержимым:
assets/system_prompt.md
# Colorist System Prompt
You are a color expert assistant integrated into a desktop app called Colorist. Your job is to interpret natural language color descriptions and provide the appropriate RGB values that best represent that description.
## Your Capabilities
You are knowledgeable about colors, color theory, and how to translate natural language descriptions into specific RGB values. When users describe a color, you should:
1. Analyze their description to understand the color they are trying to convey
2. Determine the appropriate RGB values (values should be between 0.0 and 1.0)
3. Respond with a conversational explanation and explicitly state the RGB values
## How to Respond to User Inputs
When users describe a color:
1. First, acknowledge their color description with a brief, friendly response
2. Interpret what RGB values would best represent that color description
3. Always include the RGB values clearly in your response, formatted as: `RGB: (red=X.X, green=X.X, blue=X.X)`
4. Provide a brief explanation of your interpretation
Example:
User: "I want a sunset orange"
You: "Sunset orange is a warm, vibrant color that captures the golden-red hues of the setting sun. It combines a strong red component with moderate orange tones.
RGB: (red=1.0, green=0.5, blue=0.25)
I've selected values with high red, moderate green, and low blue to capture that beautiful sunset glow. This creates a warm orange with a slightly reddish tint, reminiscent of the sun low on the horizon."
## When Descriptions are Unclear
If a color description is ambiguous or unclear, please ask the user clarifying questions, one at a time.
## Important Guidelines
- Always keep RGB values between 0.0 and 1.0
- Always format RGB values as: `RGB: (red=X.X, green=X.X, blue=X.X)` for easy parsing
- Provide thoughtful, knowledgeable responses about colors
- When possible, include color psychology, associations, or interesting facts about colors
- Be conversational and engaging in your responses
- Focus on being helpful and accurate with your color interpretations
Понимание структуры системных подсказок
Давайте разберем, что делает эта подсказка:
- Определение роли : определяет магистра права как «ассистента эксперта по цвету».
- Пояснение к задаче : определяет основную задачу как интерпретацию описаний цветов в значения RGB.
- Формат ответа : точно определяет, как следует форматировать значения RGB для обеспечения согласованности.
- Пример обмена : дает конкретный пример ожидаемой модели взаимодействия.
- Обработка пограничных случаев : инструкции по обработке неясных описаний.
- Ограничения и рекомендации : устанавливает границы, например, поддерживая значения RGB в диапазоне от 0,0 до 1,0.
Такой структурированный подход гарантирует, что ответы LLM будут последовательными, информативными и отформатированными таким образом, чтобы их было легко анализировать, если вы захотите извлечь значения RGB программным способом.
Обновление pubspec.yaml
Теперь обновите нижнюю часть файла pubspec.yaml
, включив в нее каталог ресурсов:
pubspec.yaml
flutter:
uses-material-design: true
assets:
- assets/
Запустите flutter pub get
, чтобы обновить набор ресурсов.
Создайте поставщика системных подсказок
Создайте новый файл lib/providers/system_prompt.dart
для загрузки системного приглашения:
lib/providers/system_prompt.dart
import 'package:flutter/services.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'system_prompt.g.dart';
@riverpod
Future<String> systemPrompt(Ref ref) =>
rootBundle.loadString('assets/system_prompt.md');
Этот поставщик использует систему загрузки ресурсов Flutter для чтения файла подсказки во время выполнения.
Обновите поставщика модели Gemini
Теперь измените файл lib/providers/gemini.dart
включив в него системную подсказку:
lib/providers/gemini.dart
import 'dart:async';
import 'package:firebase_ai/firebase_ai.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import '../firebase_options.dart';
import 'system_prompt.dart'; // Add this import
part 'gemini.g.dart';
@riverpod
Future<FirebaseApp> firebaseApp(Ref ref) =>
Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
@riverpod
Future<GenerativeModel> geminiModel(Ref ref) async {
await ref.watch(firebaseAppProvider.future);
final systemPrompt = await ref.watch(systemPromptProvider.future); // Add this line
final model = FirebaseAI.googleAI().generativeModel(
model: 'gemini-2.0-flash',
systemInstruction: Content.system(systemPrompt), // And this line
);
return model;
}
@Riverpod(keepAlive: true)
Future<ChatSession> chatSession(Ref ref) async {
final model = await ref.watch(geminiModelProvider.future);
return model.startChat();
}
Ключевым изменением является добавление systemInstruction: Content.system(systemPrompt)
при создании генеративной модели. Это указывает Gemini использовать ваши инструкции в качестве системного запроса для всех взаимодействий в этом сеансе чата.
Сгенерировать код Riverpod
Запустите команду build runner, чтобы сгенерировать необходимый код Riverpod:
dart run build_runner build --delete-conflicting-outputs
Запустите и протестируйте приложение
Теперь запустите ваше приложение:
flutter run -d DEVICE
Попробуйте протестировать его с различными описаниями цветов:
- «Я бы хотел небесно-голубой».
- «Дайте мне лесной зеленый»
- «Создайте яркий оранжевый закат»
- «Я хочу цвет свежей лаванды»
- «Покажите мне что-нибудь похожее на глубокий синий океан»
Обратите внимание, что Gemini теперь отвечает пояснениями о цветах в формате диалога, а также предоставляет значения RGB в едином формате. Системные подсказки эффективно направляют LLM к предоставлению необходимых вам ответов.
Попробуйте также задать ему вопросы, не связанные с цветами. Например, о главных причинах Войны Роз. Вы должны заметить разницу по сравнению с предыдущим шагом.
Важность оперативного проектирования для специализированных задач
Системные подсказки — это одновременно и искусство, и наука. Они — важнейшая часть интеграции LLM, которая может существенно повлиять на полезность модели для вашего конкретного приложения. Вы выполнили здесь своего рода проектирование подсказок — адаптировали инструкции, чтобы модель вела себя в соответствии с потребностями вашего приложения.
Эффективное оперативное проектирование включает в себя:
- Четкое определение роли : установление цели получения степени магистра права
- Четкие инструкции : подробное описание того, как должен реагировать LLM
- Конкретные примеры : демонстрация, а не просто рассказ о том, как выглядят хорошие ответы.
- Обработка граничных случаев : обучение LLM тому, как справляться с неоднозначными сценариями
- Требования к форматированию : обеспечение единообразной и удобной структуры ответов.
Созданная вами системная подсказка преобразует стандартные возможности Gemini в специализированный помощник по интерпретации цветов, предоставляющий ответы, отформатированные специально для вашего приложения. Это мощный шаблон, который можно применять во множестве различных областей и задач.
Что дальше?
На следующем этапе вы будете развивать эту основу, добавляя объявления функций, которые позволят LLM не просто предлагать значения RGB, но и вызывать функции в вашем приложении для непосредственной установки цвета. Это демонстрирует, как LLM могут преодолеть разрыв между естественным языком и конкретными функциями приложения.
Поиск неисправностей
Проблемы с загрузкой активов
Если при загрузке системы возникли ошибки, выполните следующие действия:
- Убедитесь, что в файле
pubspec.yaml
правильно указан каталог ресурсов. - Проверьте, что путь в
rootBundle.loadString()
соответствует расположению вашего файла. - Запустите
flutter clean
а затемflutter pub get
, чтобы обновить набор ресурсов.
Непоследовательные ответы
Если LLM не всегда следует вашим инструкциям по форматированию:
- Попробуйте сделать требования к формату более явными в системном запросе.
- Добавьте больше примеров, чтобы продемонстрировать ожидаемую закономерность.
- Убедитесь, что запрашиваемый вами формат соответствует модели.
Ограничение скорости API
Если у вас возникли ошибки, связанные с ограничением скорости:
- Имейте в виду, что сервис Firebase AI Logic имеет ограничения по использованию.
- Рассмотрите возможность реализации логики повторных попыток с экспоненциальной задержкой.
- Проверьте консоль Firebase на наличие проблем с квотами.
Ключевые изученные концепции
- Понимание роли и важности системных подсказок в заявках на получение степени магистра права
- Разработка эффективных подсказок с четкими инструкциями, примерами и ограничениями
- Загрузка и использование системных подсказок в приложении Flutter
- Руководство поведением LLM для задач, специфичных для предметной области
- Использование оперативной инженерии для формирования ответов LLM
Этот шаг демонстрирует, как можно добиться значительной настройки поведения LLM, не изменяя код, — просто предоставив четкие инструкции в системном приглашении.
5. Объявления функций для инструментов LLM
На этом этапе вы начнёте работу по реализации Gemini для выполнения действий в вашем приложении, реализуя объявления функций. Эта мощная функция позволяет LLM не только предлагать значения RGB, но и фактически задавать их в пользовательском интерфейсе вашего приложения посредством вызовов специализированных инструментов. Однако для выполнения LLM-запросов в приложении Flutter потребуется следующий шаг.
Чему вы научитесь на этом этапе
- Понимание вызова функций LLM и его преимуществ для приложений Flutter
- Определение деклараций функций на основе схемы для Gemini
- Интеграция объявлений функций с моделью Gemini
- Обновление системного запроса на использование возможностей инструмента
Понимание вызова функций
Прежде чем реализовывать объявления функций, давайте разберемся, что они собой представляют и в чем их ценность:
Что такое вызов функции?
Вызов функций (иногда называемый «использованием инструмента») — это возможность, которая позволяет LLM:
- Определите, когда запрос пользователя выиграет от вызова определенной функции
- Создайте структурированный объект JSON с параметрами, необходимыми для этой функции.
- Позвольте вашему приложению выполнить функцию с этими параметрами.
- Получите результат функции и включите его в свой ответ.
Вместо того, чтобы просто описывать, что делать, вызов функций позволяет LLM инициировать конкретные действия в вашем приложении.
Почему вызов функций важен для приложений Flutter
Вызов функций создает мощный мост между естественным языком и функциями приложения:
- Прямое действие : пользователи могут описать свои желания на естественном языке, а приложение отвечает конкретными действиями.
- Структурированный вывод : LLM выдает чистые, структурированные данные, а не текст, требующий анализа.
- Сложные операции : позволяют LLM получать доступ к внешним данным, выполнять вычисления или изменять состояние приложения.
- Лучший пользовательский опыт : обеспечивает идеальную интеграцию между общением и функциональностью.
В приложении Colorist вызов функции позволяет пользователям сказать: «Я хочу темно-зеленый цвет», и пользовательский интерфейс немедленно обновится с использованием этого цвета, без необходимости анализа значений RGB из текста.
Определить объявления функций
Создайте новый файл lib/services/gemini_tools.dart
для определения объявлений функций:
lib/services/gemini_tools.dart
import 'package:firebase_ai/firebase_ai.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'gemini_tools.g.dart';
class GeminiTools {
GeminiTools(this.ref);
final Ref ref;
FunctionDeclaration get setColorFuncDecl => FunctionDeclaration(
'set_color',
'Set the color of the display square based on red, green, and blue values.',
parameters: {
'red': Schema.number(description: 'Red component value (0.0 - 1.0)'),
'green': Schema.number(description: 'Green component value (0.0 - 1.0)'),
'blue': Schema.number(description: 'Blue component value (0.0 - 1.0)'),
},
);
List<Tool> get tools => [
Tool.functionDeclarations([setColorFuncDecl]),
];
}
@riverpod
GeminiTools geminiTools(Ref ref) => GeminiTools(ref);
Понимание объявлений функций
Давайте разберем, что делает этот код:
- Именование функций : вы называете свою функцию
set_color
, чтобы четко обозначить ее назначение. - Описание функции : вы предоставляете четкое описание, которое помогает LLM понять, когда ее использовать.
- Определения параметров : вы определяете структурированные параметры с их собственными описаниями:
-
red
: красный компонент RGB, указанный как число от 0,0 до 1,0 -
green
: зеленый компонент RGB, указанный как число от 0,0 до 1,0 -
blue
: синий компонент RGB, указанный как число от 0,0 до 1,0
-
- Типы схем :
Schema.number()
используется для указания того, что это числовые значения. - Коллекция инструментов : вы создаете список инструментов, содержащий объявление вашей функции.
Этот структурированный подход помогает студентам программы Gemini LLM понять:
- Когда следует вызывать эту функцию
- Какие параметры он должен предоставить?
- Какие ограничения применяются к этим параметрам (например, диапазон значений)
Обновите поставщика модели Gemini
Теперь измените файл lib/providers/gemini.dart
включив в него объявления функций при инициализации модели Gemini:
lib/providers/gemini.dart
import 'dart:async';
import 'package:firebase_ai/firebase_ai.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import '../firebase_options.dart';
import '../services/gemini_tools.dart'; // Add this import
import 'system_prompt.dart';
part 'gemini.g.dart';
@riverpod
Future<FirebaseApp> firebaseApp(Ref ref) =>
Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
@riverpod
Future<GenerativeModel> geminiModel(Ref ref) async {
await ref.watch(firebaseAppProvider.future);
final systemPrompt = await ref.watch(systemPromptProvider.future);
final geminiTools = ref.watch(geminiToolsProvider); // Add this line
final model = FirebaseAI.googleAI().generativeModel(
model: 'gemini-2.0-flash',
systemInstruction: Content.system(systemPrompt),
tools: geminiTools.tools, // And this line
);
return model;
}
@Riverpod(keepAlive: true)
Future<ChatSession> chatSession(Ref ref) async {
final model = await ref.watch(geminiModelProvider.future);
return model.startChat();
}
Ключевым изменением является добавление параметра tools: geminiTools.tools
при создании генеративной модели. Это позволяет Gemini получать информацию о функциях, которые доступны для вызова.
Обновите системное приглашение
Теперь вам нужно изменить системное приглашение, чтобы указать LLM об использовании нового инструмента set_color
. Обновите assets/system_prompt.md
:
assets/system_prompt.md
# Colorist System Prompt
You are a color expert assistant integrated into a desktop app called Colorist. Your job is to interpret natural language color descriptions and set the appropriate color values using a specialized tool.
## Your Capabilities
You are knowledgeable about colors, color theory, and how to translate natural language descriptions into specific RGB values. You have access to the following tool:
`set_color` - Sets the RGB values for the color display based on a description
## How to Respond to User Inputs
When users describe a color:
1. First, acknowledge their color description with a brief, friendly response
2. Interpret what RGB values would best represent that color description
3. Use the `set_color` tool to set those values (all values should be between 0.0 and 1.0)
4. After setting the color, provide a brief explanation of your interpretation
Example:
User: "I want a sunset orange"
You: "Sunset orange is a warm, vibrant color that captures the golden-red hues of the setting sun. It combines a strong red component with moderate orange tones."
[Then you would call the set_color tool with approximately: red=1.0, green=0.5, blue=0.25]
After the tool call: "I've set a warm orange with strong red, moderate green, and minimal blue components that is reminiscent of the sun low on the horizon."
## When Descriptions are Unclear
If a color description is ambiguous or unclear, please ask the user clarifying questions, one at a time.
## Important Guidelines
- Always keep RGB values between 0.0 and 1.0
- Provide thoughtful, knowledgeable responses about colors
- When possible, include color psychology, associations, or interesting facts about colors
- Be conversational and engaging in your responses
- Focus on being helpful and accurate with your color interpretations
Ключевые изменения в системном приглашении:
- Введение в инструмент : вместо того, чтобы запрашивать отформатированные значения RGB, теперь вы сообщаете LLM об инструменте
set_color
- Изменённый процесс : вы меняете шаг 3 с «форматировать значения в ответе» на «использовать инструмент для установки значений».
- Обновленный пример : вы показываете, что ответ должен включать вызов инструмента вместо форматированного текста.
- Убрано требование к форматированию : поскольку вы используете структурированные вызовы функций, вам больше не нужен определенный текстовый формат.
Это обновленное приглашение предписывает LLM использовать вызов функций, а не просто предоставлять значения RGB в текстовой форме.
Сгенерировать код Riverpod
Запустите команду build runner, чтобы сгенерировать необходимый код Riverpod:
dart run build_runner build --delete-conflicting-outputs
Запустить приложение
На этом этапе Gemini сгенерирует контент, который пытается использовать вызов функций, но вы ещё не реализовали обработчики для этих вызовов. При запуске приложения и описании цвета вы увидите реакцию Gemini, как будто он вызвал инструмент, но никаких изменений цвета в пользовательском интерфейсе вы не увидите до следующего шага.
Запустите приложение:
flutter run -d DEVICE
Попробуйте описать цвет, например, «глубокий океанский синий» или «лесной зелёный», и понаблюдайте за реакцией. LLM пытается вызвать функции, определённые выше, но ваш код пока не обнаруживает вызовы функций.
Процесс вызова функции
Давайте разберемся, что происходит, когда Gemini использует вызов функций:
- Выбор функции : LLM решает, будет ли полезен вызов функции, основываясь на запросе пользователя.
- Генерация параметров : LLM генерирует значения параметров, соответствующие схеме функции.
- Формат вызова функции : LLM отправляет в своем ответе структурированный объект вызова функции.
- Обработка приложения : Ваше приложение получит этот вызов и выполнит соответствующую функцию (реализованную на следующем шаге).
- Интеграция ответов : в многооборотных диалогах LLM ожидает возврата результата функции.
В текущем состоянии вашего приложения первые три шага выполняются, но вы еще не реализовали шаг 4 или 5 (обработку вызовов функций), что вы сделаете на следующем шаге.
Технические подробности: как Gemini решает, когда использовать функции
Gemini принимает разумные решения о том, когда использовать функции, основываясь на:
- Намерение пользователя : будет ли функция наилучшим образом удовлетворять запрос пользователя
- Релевантность функций : насколько хорошо доступные функции соответствуют задаче.
- Доступность параметров : может ли он уверенно определять значения параметров
- Системные инструкции : руководство от вашей системной подсказки по использованию функций
Предоставляя понятные объявления функций и системные инструкции, вы настроили Gemini на распознавание запросов на описание цвета как возможностей для вызова функции set_color
.
Что дальше?
На следующем этапе вы реализуете обработчики вызовов функций из Gemini. Это замкнет круг, позволяя описаниям пользователей инициировать реальные изменения цветов в пользовательском интерфейсе посредством вызовов функций LLM.
Поиск неисправностей
Проблемы с объявлением функций
Если вы столкнулись с ошибками при объявлении функций:
- Проверьте, соответствуют ли имена и типы параметров ожидаемым.
- Убедитесь, что имя функции понятное и описательное.
- Убедитесь, что описание функции точно отражает ее назначение.
Проблемы с системными подсказками
Если LLM не пытается использовать функцию:
- Убедитесь, что системное приглашение четко указывает LLM использовать инструмент
set_color
- Убедитесь, что пример в системном приглашении демонстрирует использование функции.
- Попробуйте сделать инструкцию по использованию инструмента более точной.
Общие вопросы
Если у вас возникли другие проблемы:
- Проверьте консоль на наличие ошибок, связанных с декларациями функций.
- Убедитесь, что инструменты правильно переданы модели.
- Убедитесь, что весь сгенерированный Riverpod код актуален.
Ключевые изученные концепции
- Определение объявлений функций для расширения возможностей LLM в приложениях Flutter
- Создание схем параметров для сбора структурированных данных
- Интеграция объявлений функций с моделью Gemini
- Обновление системных подсказок для поощрения использования функций
- Понимание того, как LLM выбирают и вызывают функции
Этот шаг демонстрирует, как LLM могут преодолеть разрыв между вводом на естественном языке и структурированными вызовами функций, закладывая основу для бесшовной интеграции между функциями общения и приложения.
6. Реализация обработки инструментов
На этом этапе вы реализуете обработчики вызовов функций, поступающих из Gemini. Это замыкает цикл взаимодействия между вводом на естественном языке и конкретными функциями приложения, позволяя LLM напрямую управлять вашим пользовательским интерфейсом на основе пользовательских описаний.
Чему вы научитесь на этом этапе
- Понимание полного конвейера вызовов функций в приложениях LLM
- Обработка вызовов функций из Gemini в приложении Flutter
- Реализация обработчиков функций, изменяющих состояние приложения
- Обработка ответов функций и возврат результатов в LLM
- Создание полноценного коммуникационного потока между LLM и UI
- Ведение журнала вызовов и ответов функций для прозрачности
Понимание конвейера вызова функций
Прежде чем углубляться в реализацию, давайте разберемся с полным конвейером вызова функций:
Сквозной поток
- Пользовательский ввод : пользователь описывает цвет на естественном языке (например, «лесной зеленый»).
- Обработка LLM : Gemini анализирует описание и решает вызвать функцию
set_color
- Генерация вызова функции : Gemini создает структурированный JSON с параметрами (красные, зеленые, синие значения).
- Прием вызова функции : ваше приложение получает структурированные данные от Gemini.
- Выполнение функции : ваше приложение выполняет функцию с предоставленными параметрами.
- Обновление состояния : функция обновляет состояние вашего приложения (меняя отображаемый цвет).
- Генерация ответа : ваша функция возвращает результаты обратно в LLM.
- Включение ответа : LLM включает эти результаты в свой окончательный ответ.
- Обновление пользовательского интерфейса : ваш пользовательский интерфейс реагирует на изменение состояния, отображая новый цвет.
Полный цикл коммуникации необходим для правильной интеграции LLM. Когда LLM вызывает функцию, он не просто отправляет запрос и движется дальше. Вместо этого он ждет, пока ваше приложение выполнит функцию и вернет результаты. Затем LLM использует эти результаты для формулирования окончательного ответа, создавая естественный поток разговора, подтверждающий предпринятые действия.
Реализация обработчиков функций
Давайте обновим ваш файл lib/services/gemini_tools.dart
, чтобы добавить обработчики вызовов функций:
lib/services/gemini_tools.dart
import 'package:colorist_ui/colorist_ui.dart';
import 'package:firebase_ai/firebase_ai.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'gemini_tools.g.dart';
class GeminiTools {
GeminiTools(this.ref);
final Ref ref;
FunctionDeclaration get setColorFuncDecl => FunctionDeclaration(
'set_color',
'Set the color of the display square based on red, green, and blue values.',
parameters: {
'red': Schema.number(description: 'Red component value (0.0 - 1.0)'),
'green': Schema.number(description: 'Green component value (0.0 - 1.0)'),
'blue': Schema.number(description: 'Blue component value (0.0 - 1.0)'),
},
);
List<Tool> get tools => [
Tool.functionDeclarations([setColorFuncDecl]),
];
Map<String, Object?> handleFunctionCall( // Add from here
String functionName,
Map<String, Object?> arguments,
) {
final logStateNotifier = ref.read(logStateProvider.notifier);
logStateNotifier.logFunctionCall(functionName, arguments);
return switch (functionName) {
'set_color' => handleSetColor(arguments),
_ => handleUnknownFunction(functionName),
};
}
Map<String, Object?> handleSetColor(Map<String, Object?> arguments) {
final colorStateNotifier = ref.read(colorStateProvider.notifier);
final red = (arguments['red'] as num).toDouble();
final green = (arguments['green'] as num).toDouble();
final blue = (arguments['blue'] as num).toDouble();
final functionResults = {
'success': true,
'current_color': colorStateNotifier
.updateColor(red: red, green: green, blue: blue)
.toLLMContextMap(),
};
final logStateNotifier = ref.read(logStateProvider.notifier);
logStateNotifier.logFunctionResults(functionResults);
return functionResults;
}
Map<String, Object?> handleUnknownFunction(String functionName) {
final logStateNotifier = ref.read(logStateProvider.notifier);
logStateNotifier.logWarning('Unsupported function call $functionName');
return {
'success': false,
'reason': 'Unsupported function call $functionName',
};
} // To here.
}
@riverpod
GeminiTools geminiTools(Ref ref) => GeminiTools(ref);
Понимание обработчиков функций
Давайте разберем, что делают эти обработчики функций:
-
handleFunctionCall
: Центральный диспетчер, который:- Регистрирует вызов функции для обеспечения прозрачности на панели журнала.
- Направляется к соответствующему обработчику на основе имени функции.
- Возвращает структурированный ответ, который будет отправлен обратно в LLM.
-
handleSetColor
: конкретный обработчик для вашей функцииset_color
, который:- Извлекает значения RGB из карты аргументов.
- Преобразует их в ожидаемые типы (двойные).
- Обновляет состояние цвета приложения с помощью
colorStateNotifier
- Создает структурированный ответ со статусом успеха и текущей информацией о цвете.
- Регистрирует результаты функции для отладки
-
handleUnknownFunction
: резервный обработчик для неизвестных функций, которые:- Регистрирует предупреждение о неподдерживаемой функции.
- Возвращает ответ об ошибке в LLM
Функция handleSetColor
особенно важна, поскольку она устраняет разрыв между пониманием естественного языка LLM и конкретными изменениями пользовательского интерфейса.
Обновите службу чата Gemini для обработки вызовов функций и ответов.
Теперь давайте обновим файл lib/services/gemini_chat_service.dart
для обработки вызовов функций из ответов LLM и отправки результатов обратно в LLM:
lib/services/gemini_chat_service.dart
import 'dart:async';
import 'package:colorist_ui/colorist_ui.dart';
import 'package:firebase_ai/firebase_ai.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import '../providers/gemini.dart';
import 'gemini_tools.dart'; // Add this import
part 'gemini_chat_service.g.dart';
class GeminiChatService {
GeminiChatService(this.ref);
final Ref ref;
Future<void> sendMessage(String message) async {
final chatSession = await ref.read(chatSessionProvider.future);
final chatStateNotifier = ref.read(chatStateProvider.notifier);
final logStateNotifier = ref.read(logStateProvider.notifier);
chatStateNotifier.addUserMessage(message);
logStateNotifier.logUserText(message);
final llmMessage = chatStateNotifier.createLlmMessage();
try {
final response = await chatSession.sendMessage(Content.text(message));
final responseText = response.text;
if (responseText != null) {
logStateNotifier.logLlmText(responseText);
chatStateNotifier.appendToMessage(llmMessage.id, responseText);
}
if (response.functionCalls.isNotEmpty) { // Add from here
final geminiTools = ref.read(geminiToolsProvider);
final functionResultResponse = await chatSession.sendMessage(
Content.functionResponses([
for (final functionCall in response.functionCalls)
FunctionResponse(
functionCall.name,
geminiTools.handleFunctionCall(
functionCall.name,
functionCall.args,
),
),
]),
);
final responseText = functionResultResponse.text;
if (responseText != null) {
logStateNotifier.logLlmText(responseText);
chatStateNotifier.appendToMessage(llmMessage.id, responseText);
}
} // To here.
} catch (e, st) {
logStateNotifier.logError(e, st: st);
chatStateNotifier.appendToMessage(
llmMessage.id,
"\nI'm sorry, I encountered an error processing your request. "
"Please try again.",
);
} finally {
chatStateNotifier.finalizeMessage(llmMessage.id);
}
}
}
@riverpod
GeminiChatService geminiChatService(Ref ref) => GeminiChatService(ref);
Понимание потока общения
Ключевым дополнением здесь является полная обработка вызовов функций и ответов:
if (response.functionCalls.isNotEmpty) {
final geminiTools = ref.read(geminiToolsProvider);
final functionResultResponse = await chatSession.sendMessage(
Content.functionResponses([
for (final functionCall in response.functionCalls)
FunctionResponse(
functionCall.name,
geminiTools.handleFunctionCall(
functionCall.name,
functionCall.args,
),
),
]),
);
final responseText = functionResultResponse.text;
if (responseText != null) {
logStateNotifier.logLlmText(responseText);
chatStateNotifier.appendToMessage(llmMessage.id, responseText);
}
}
Этот код:
- Проверяет, содержит ли ответ LLM какие-либо вызовы функций.
- Для каждого вызова функции вызывает метод
handleFunctionCall
с именем функции и аргументами. - Собирает результаты каждого вызова функции
- Отправляет эти результаты обратно в LLM, используя
Content.functionResponses
- Обрабатывает ответ LLM на результаты функции.
- Обновляет пользовательский интерфейс с окончательным текстом ответа.
Это создает поток туда и обратно:
- Пользователь → LLM: запрашивает цвет
- LLM → Приложение: вызовы функций с параметрами
- Приложение → Пользователь: отображается новый цвет
- Приложение → LLM: Результаты функции
- LLM → Пользователь: окончательный ответ, включающий результаты функции
Создать код Riverpod
Запустите команду build runner, чтобы сгенерировать необходимый код Riverpod:
dart run build_runner build --delete-conflicting-outputs
Запустите и протестируйте весь поток
Теперь запустите ваше приложение:
flutter run -d DEVICE
Попробуйте ввести различные описания цветов:
- «Мне нужен глубокий малиново-красный»
- «Покажи мне успокаивающий небесно-голубой»
- «Подари мне цвет листьев свежей мяты»
- «Я хочу увидеть теплый оранжевый закат»
- «Сделайте его насыщенного королевского фиолетового цвета»
Теперь вы должны увидеть:
- Ваше сообщение появляется в интерфейсе чата
- Ответ Близнецов появляется в чате
- Вызовы функций регистрируются на панели журнала.
- Результаты функции записываются сразу после
- Цветной прямоугольник обновляется для отображения описанного цвета.
- Значения RGB обновляются, чтобы показать компоненты нового цвета.
- Появляется последний ответ Близнецов, часто комментирующий установленный цвет.
Панель журнала дает представление о том, что происходит за кулисами. Вы увидите:
- Точные вызовы функций, которые выполняет Gemini
- Параметры, которые он выбирает для каждого значения RGB.
- Результаты, которые возвращает ваша функция
- Последующие ответы от Близнецов
Уведомление о состоянии цвета
colorStateNotifier
который вы используете для обновления цветов, является частью пакета colorist_ui
. Он управляет:
- Текущий цвет, отображаемый в пользовательском интерфейсе
- История цвета (последние 10 цветов)
- Уведомление об изменении состояния компонентов пользовательского интерфейса
Когда вы вызываете updateColor
с новыми значениями RGB, это:
- Создает новый объект
ColorData
с предоставленными значениями. - Обновляет текущий цвет в состоянии приложения.
- Добавляет цвет в историю
- Запускает обновления пользовательского интерфейса посредством управления состоянием Riverpod.
Компоненты пользовательского интерфейса в пакете colorist_ui
отслеживают это состояние и автоматически обновляются при его изменении, создавая реактивный интерфейс.
Понимание обработки ошибок
Ваша реализация включает в себя надежную обработку ошибок:
- Блок Try-catch : оборачивает все взаимодействия LLM для перехвата любых исключений.
- Журнал ошибок : записывает ошибки на панели журнала с трассировкой стека.
- Отзывы пользователей : предоставляет дружественное сообщение об ошибке в чате.
- Очистка состояния : завершает состояние сообщения, даже если возникает ошибка.
Это гарантирует, что приложение останется стабильным и предоставит соответствующую обратную связь даже в случае возникновения проблем со службой LLM или выполнением функции.
Сила функции, призванной улучшить пользовательский опыт
То, чего вы здесь достигли, демонстрирует, как LLM могут создавать мощные естественные интерфейсы:
- Интерфейс на естественном языке : пользователи выражают намерения на повседневном языке.
- Интеллектуальная интерпретация : LLM переводит расплывчатые описания в точные значения.
- Прямое манипулирование : пользовательский интерфейс обновляется в ответ на естественный язык.
- Контекстуальные ответы : LLM обеспечивает контекст разговора об изменениях.
- Низкая когнитивная нагрузка : пользователям не нужно понимать значения RGB или теорию цвета.
Этот шаблон использования вызова функций LLM для объединения естественного языка и действий пользовательского интерфейса может быть распространен на бесчисленное множество других областей, помимо выбора цвета.
Что дальше?
На следующем этапе вы улучшите взаимодействие с пользователем, реализовав потоковые ответы. Вместо того, чтобы ждать полного ответа, вы будете обрабатывать фрагменты текста и вызовы функций по мере их получения, создавая более отзывчивое и привлекательное приложение.
Поиск неисправностей
Проблемы с вызовом функций
Если Gemini не вызывает ваши функции или параметры неверны:
- Убедитесь, что объявление вашей функции соответствует описанию в системном приглашении.
- Убедитесь, что имена и типы параметров совпадают.
- Убедитесь, что ваша системная подсказка явно указывает LLM использовать этот инструмент.
- Убедитесь, что имя функции в вашем обработчике точно соответствует тому, что указано в объявлении.
- Изучите панель журнала для получения подробной информации о вызовах функций.
Проблемы с ответом функции
Если результаты функции не отправляются обратно в LLM должным образом:
- Убедитесь, что ваша функция возвращает правильно отформатированную карту.
- Убедитесь, что Content.functionResponses создается правильно.
- Найдите в журнале любые ошибки, связанные с ответами функций.
- Убедитесь, что вы используете тот же сеанс чата для ответа.
Проблемы с цветным отображением
Если цвета отображаются неправильно:
- Убедитесь, что значения RGB правильно преобразованы в двойные значения (LLM может отправлять их как целые числа).
- Убедитесь, что значения находятся в ожидаемом диапазоне (от 0,0 до 1,0).
- Убедитесь, что уведомитель о состоянии цвета вызывается правильно.
- Проверьте журнал на предмет точных значений, передаваемых в функцию.
Общие проблемы
По общим вопросам:
- Проверьте журналы на наличие ошибок или предупреждений.
- Проверьте подключение Firebase AI Logic
- Проверьте наличие несоответствий типов в параметрах функции.
- Убедитесь, что весь сгенерированный Riverpod код обновлен.
Ключевые понятия изучены
- Реализация полного конвейера вызова функций во Flutter
- Создание полной связи между LLM и вашим приложением
- Обработка структурированных данных из ответов LLM
- Отправка результатов функции обратно в LLM для включения в ответы.
- Использование панели журналов для наглядности взаимодействия LLM-приложения.
- Соединение ввода на естественном языке с конкретными изменениями пользовательского интерфейса
После завершения этого шага ваше приложение теперь демонстрирует один из самых мощных шаблонов интеграции LLM: перевод входных данных на естественном языке в конкретные действия пользовательского интерфейса, сохраняя при этом связный диалог, подтверждающий эти действия. Это создает интуитивно понятный диалоговый интерфейс, который кажется пользователям волшебным.
7. Потоковая передача ответов для улучшения UX
На этом этапе вы улучшите взаимодействие с пользователем, внедрив потоковую передачу ответов от Gemini. Вместо того, чтобы ждать формирования всего ответа, вы будете обрабатывать фрагменты текста и вызовы функций по мере их получения, создавая более отзывчивое и привлекательное приложение.
Что вы изучите на этом этапе
- Важность потоковой передачи для приложений на базе LLM
- Реализация потоковой передачи ответов LLM в приложении Flutter
- Обработка частичных фрагментов текста по мере их поступления от API
- Управление состоянием диалога для предотвращения конфликтов сообщений
- Обработка вызовов функций в потоковых ответах
- Создание визуальных индикаторов текущих ответов
Почему потоковая передача важна для приложений LLM
Прежде чем приступить к реализации, давайте поймем, почему потоковая передача ответов имеет решающее значение для создания превосходного пользовательского опыта с LLM:
Улучшенный пользовательский интерфейс
Потоковая передача ответов обеспечивает несколько существенных преимуществ для пользователей:
- Снижение воспринимаемой задержки : пользователи видят, что текст начинает появляться немедленно (обычно в течение 100–300 мс), а не ждут полного ответа несколько секунд. Такое ощущение оперативности значительно повышает удовлетворенность пользователей.
- Естественный разговорный ритм . Постепенное появление текста имитирует общение людей, создавая более естественный диалог.
- Прогрессивная обработка информации . Пользователи могут начать обрабатывать информацию по мере ее поступления, а не сразу перегружаться большим блоком текста.
- Возможность раннего прерывания : в полной версии приложения пользователи потенциально могут прервать или перенаправить LLM, если увидят, что он движется в бесполезном направлении.
- Визуальное подтверждение активности : потоковый текст обеспечивает немедленную информацию о том, что система работает, что снижает неопределенность.
Технические преимущества
Помимо улучшений UX, потоковая передача предлагает технические преимущества:
- Раннее выполнение функции : вызовы функций могут быть обнаружены и выполнены сразу после их появления в потоке, не дожидаясь полного ответа.
- Дополнительные обновления пользовательского интерфейса . Вы можете постепенно обновлять свой пользовательский интерфейс по мере поступления новой информации, создавая более динамичный интерфейс.
- Управление состоянием разговора : потоковая передача обеспечивает четкие сигналы о том, когда ответы завершены, а когда ответы еще находятся в процессе, что позволяет лучше управлять состоянием.
- Снижение рисков тайм-аута . При непотоковых ответах длительные поколения рискуют потерять время ожидания соединения. Потоковая передача устанавливает соединение заранее и поддерживает его.
Для вашего приложения Colorist реализация потоковой передачи означает, что пользователи будут видеть как текстовые ответы, так и изменения цвета, происходящие быстрее, что обеспечивает значительно более быстрый отклик.
Добавить управление состоянием разговора
Во-первых, давайте добавим поставщика состояний, чтобы отслеживать, обрабатывает ли приложение в данный момент потоковый ответ. Обновите файл lib/services/gemini_chat_service.dart
:
lib/services/gemini_chat_service.dart
import 'dart:async';
import 'package:colorist_ui/colorist_ui.dart';
import 'package:firebase_ai/firebase_ai.dart';
import 'package:flutter_riverpod/legacy.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import '../providers/gemini.dart';
import 'gemini_tools.dart';
part 'gemini_chat_service.g.dart';
final conversationStateProvider = StateProvider( // Add from here...
(ref) => ConversationState.idle,
); // To here.
class GeminiChatService {
GeminiChatService(this.ref);
final Ref ref;
Future<void> sendMessage(String message) async {
final chatSession = await ref.read(chatSessionProvider.future);
final conversationState = ref.read(conversationStateProvider); // Add this line
final chatStateNotifier = ref.read(chatStateProvider.notifier);
final logStateNotifier = ref.read(logStateProvider.notifier);
if (conversationState == ConversationState.busy) { // Add from here...
logStateNotifier.logWarning(
"Can't send a message while a conversation is in progress",
);
throw Exception(
"Can't send a message while a conversation is in progress",
);
}
final conversationStateNotifier = ref.read(
conversationStateProvider.notifier,
);
conversationStateNotifier.state = ConversationState.busy; // To here.
chatStateNotifier.addUserMessage(message);
logStateNotifier.logUserText(message);
final llmMessage = chatStateNotifier.createLlmMessage();
try { // Modify from here...
final responseStream = chatSession.sendMessageStream(
Content.text(message),
);
await for (final block in responseStream) {
await _processBlock(block, llmMessage.id);
} // To here.
} catch (e, st) {
logStateNotifier.logError(e, st: st);
chatStateNotifier.appendToMessage(
llmMessage.id,
"\nI'm sorry, I encountered an error processing your request. "
"Please try again.",
);
} finally {
chatStateNotifier.finalizeMessage(llmMessage.id);
conversationStateNotifier.state = ConversationState.idle; // Add this line.
}
}
Future<void> _processBlock( // Add from here...
GenerateContentResponse block,
String llmMessageId,
) async {
final chatSession = await ref.read(chatSessionProvider.future);
final chatStateNotifier = ref.read(chatStateProvider.notifier);
final logStateNotifier = ref.read(logStateProvider.notifier);
final blockText = block.text;
if (blockText != null) {
logStateNotifier.logLlmText(blockText);
chatStateNotifier.appendToMessage(llmMessageId, blockText);
}
if (block.functionCalls.isNotEmpty) {
final geminiTools = ref.read(geminiToolsProvider);
final responseStream = chatSession.sendMessageStream(
Content.functionResponses([
for (final functionCall in block.functionCalls)
FunctionResponse(
functionCall.name,
geminiTools.handleFunctionCall(
functionCall.name,
functionCall.args,
),
),
]),
);
await for (final response in responseStream) {
final responseText = response.text;
if (responseText != null) {
logStateNotifier.logLlmText(responseText);
chatStateNotifier.appendToMessage(llmMessageId, responseText);
}
}
}
} // To here.
}
@riverpod
GeminiChatService geminiChatService(Ref ref) => GeminiChatService(ref);
Понимание реализации потоковой передачи
Давайте разберем, что делает этот код:
- Отслеживание состояния разговора :
-
conversationStateProvider
отслеживает, обрабатывает ли приложение в данный момент ответ. - Состояние переходит из
idle
→busy
во время обработки, а затем обратно вidle
- Это предотвращает несколько одновременных запросов, которые могут конфликтовать.
-
- Инициализация потока :
-
sendMessageStream()
возвращает поток фрагментов ответа вместоFuture
с полным ответом. - Каждый фрагмент может содержать текст, вызовы функций или и то, и другое.
-
- Прогрессивная обработка :
-
await for
обработки каждого фрагмента по мере его поступления в режиме реального времени - Текст немедленно добавляется в пользовательский интерфейс, создавая эффект потоковой передачи.
- Вызовы функций выполняются сразу после их обнаружения.
-
- Обработка вызова функции :
- Когда вызов функции обнаруживается в фрагменте, он выполняется немедленно.
- Результаты отправляются обратно в LLM через другой потоковый вызов.
- Ответ LLM на эти результаты также обрабатывается в потоковом режиме.
- Обработка и очистка ошибок :
-
try
/catch
обеспечивает надежную обработку ошибок - Блок
finally
обеспечивает правильный сброс состояния разговора. - Сообщение всегда завершается, даже если возникают ошибки
-
Эта реализация обеспечивает быстроту и надежность потоковой передачи, сохраняя при этом правильное состояние разговора.
Обновите главный экран, чтобы подключить состояние разговора.
Измените файл lib/main.dart
, чтобы передать состояние разговора на главный экран:
lib/main.dart
import 'package:colorist_ui/colorist_ui.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'providers/gemini.dart';
import 'services/gemini_chat_service.dart';
void main() async {
runApp(ProviderScope(child: MainApp()));
}
class MainApp extends ConsumerWidget {
const MainApp({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final model = ref.watch(geminiModelProvider);
final conversationState = ref.watch(conversationStateProvider); // Add this line
return MaterialApp(
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
),
home: model.when(
data: (data) => MainScreen(
conversationState: conversationState, // And this line
sendMessage: (text) {
ref.read(geminiChatServiceProvider).sendMessage(text);
},
),
loading: () => LoadingScreen(message: 'Initializing Gemini Model'),
error: (err, st) => ErrorScreen(error: err),
),
);
}
}
Ключевым изменением здесь является передача conversationState
в виджет MainScreen
. MainScreen
(предоставленный пакетом colorist_ui
) будет использовать это состояние для отключения ввода текста во время обработки ответа.
Это создает целостный пользовательский интерфейс, где пользовательский интерфейс отражает текущее состояние разговора.
Создать код Riverpod
Запустите команду build runner, чтобы сгенерировать необходимый код Riverpod:
dart run build_runner build --delete-conflicting-outputs
Запустите и протестируйте потоковые ответы
Запустите ваше приложение:
flutter run -d DEVICE
Теперь попробуйте протестировать поведение потоковой передачи с различными описаниями цветов. Попробуйте такие описания:
- «Покажи мне глубокий бирюзовый цвет океана в сумерках»
- «Я бы хотел увидеть яркий коралл, напоминающий мне тропические цветы»
- «Создайте приглушенный оливково-зеленый цвет, как в старой армейской форме»
Технический поток потоковой передачи в деталях
Давайте подробно рассмотрим, что происходит при потоковой передаче ответа:
Установление соединения
Когда вы вызываете sendMessageStream()
, происходит следующее:
- Приложение устанавливает соединение со службой Firebase AI Logic.
- Запрос пользователя передается в сервис
- Сервер начинает обработку запроса
- Потоковое соединение остается открытым и готово к передаче фрагментов.
Передача фрагментов
Когда Gemini генерирует контент, фрагменты отправляются через поток:
- Сервер отправляет фрагменты текста по мере их создания (обычно несколько слов или предложений).
- Когда Gemini решает выполнить вызов функции, он отправляет информацию о вызове функции.
- Дополнительные фрагменты текста могут следовать за вызовами функций.
- Поток продолжается до тех пор, пока генерация не завершится.
Прогрессивная обработка
Ваше приложение обрабатывает каждый фрагмент постепенно:
- Каждый текстовый фрагмент добавляется к существующему ответу.
- Вызовы функций выполняются сразу после их обнаружения.
- Пользовательский интерфейс обновляется в режиме реального времени, отображая как текстовые, так и функциональные результаты.
- Состояние отслеживается, чтобы показать, что ответ все еще передается в потоковом режиме.
Завершение потока
Когда генерация завершена:
- Поток закрыт сервером
- Ваше
await for
цикла завершается естественным образом - Сообщение помечено как завершенное
- Состояние разговора возвращается в режим ожидания
- Пользовательский интерфейс обновляется, чтобы отразить завершенное состояние.
Сравнение потоковой передачи и непотоковой передачи
Чтобы лучше понять преимущества потоковой передачи, давайте сравним подходы с потоковой передачей и без потоковой передачи:
Аспект | Непотоковая передача | Потоковое вещание |
Воспринимаемая задержка | Пользователь ничего не видит, пока не будет готов полный ответ | Пользователь видит первые слова в течение миллисекунд |
Пользовательский опыт | Долгое ожидание с последующим внезапным появлением сообщения | Естественный, прогрессивный внешний вид текста |
Государственное управление | Проще (сообщения либо ожидаются, либо завершены) | Более сложный (сообщения могут находиться в потоковом состоянии) |
Выполнение функции | Происходит только после полного ответа | Происходит во время генерации ответа |
Сложность реализации | Проще реализовать | Требуется дополнительное управление состоянием |
Восстановление ошибок | Ответ «все или ничего» | Частичные ответы все еще могут быть полезны |
Сложность кода | Менее сложный | Более сложный из-за обработки потока |
Для такого приложения, как Colorist, преимущества потоковой передачи UX перевешивают сложность реализации, особенно для интерпретации цвета, создание которой может занять несколько секунд.
Лучшие практики для потоковой передачи UX
При реализации потоковой передачи в ваших собственных приложениях LLM учитывайте следующие рекомендации:
- Четкие визуальные индикаторы . Всегда предоставляйте четкие визуальные подсказки, которые отличают потоковые сообщения от полных.
- Блокировка ввода : отключите пользовательский ввод во время потоковой передачи, чтобы предотвратить несколько перекрывающихся запросов.
- Восстановление после ошибок : спроектируйте свой пользовательский интерфейс для плавного восстановления в случае прерывания потоковой передачи.
- Переходы между состояниями : Обеспечьте плавный переход между состояниями ожидания, потоковой передачи и завершения.
- Визуализация прогресса : рассмотрите возможность использования тонких анимаций или индикаторов, показывающих активную обработку.
- Варианты отмены : в готовом приложении предоставьте пользователям возможность отменить незавершенные генерации.
- Интеграция результатов функции : спроектируйте свой пользовательский интерфейс для обработки результатов функции, появляющихся в середине потока.
- Оптимизация производительности : минимизация перестроений пользовательского интерфейса во время быстрых обновлений потока.
Пакет colorist_ui
реализует многие из этих передовых методов, но они важны для любой реализации потоковой LLM.
Что дальше?
На следующем этапе вы реализуете синхронизацию LLM, уведомляя Gemini, когда пользователи выбирают цвета из истории. Это создаст более целостный опыт, когда LLM будет знать об изменениях состояния приложения, инициированных пользователем.
Поиск неисправностей
Проблемы с обработкой потока
Если у вас возникли проблемы с обработкой потока:
- Симптомы : частичные ответы, отсутствие текста или резкое прекращение потока.
- Решение . Проверьте сетевое подключение и убедитесь, что в вашем коде используются правильные шаблоны асинхронности/ожидания.
- Диагностика : Проверьте панель журнала на наличие сообщений об ошибках или предупреждений, связанных с обработкой потока.
- Исправлено : убедитесь, что при всей потоковой обработке используется правильная обработка ошибок с помощью блоков
try
/catch
Отсутствующие вызовы функций
Если вызовы функций не обнаруживаются в потоке:
- Симптомы : текст появляется, но цвета не обновляются, или в журнале не отображаются вызовы функций.
- Решение . Проверьте инструкции системной подсказки по использованию вызовов функций.
- Диагностика : проверьте панель журнала, чтобы увидеть, принимаются ли вызовы функций.
- Исправление : настройте системное приглашение, чтобы более явно указать LLM использовать инструмент
set_color
Общая обработка ошибок
По любым другим вопросам:
- Шаг 1. Проверьте панель журнала на наличие сообщений об ошибках.
- Шаг 2. Проверьте подключение Firebase AI Logic.
- Шаг 3. Убедитесь, что весь сгенерированный Riverpod код обновлен.
- Шаг 4. Проверьте реализацию потоковой передачи на наличие отсутствующих операторов ожидания.
Ключевые понятия изучены
- Реализация потоковой передачи ответов с помощью API Gemini для более гибкого взаимодействия с пользователем.
- Управление состоянием разговора для правильной обработки потокового взаимодействия
- Обработка текстовых вызовов и вызовов функций в реальном времени по мере их поступления.
- Создание адаптивных пользовательских интерфейсов, которые постепенно обновляются во время потоковой передачи.
- Обработка параллельных потоков с использованием правильных асинхронных шаблонов
- Обеспечение соответствующей визуальной обратной связи во время потоковой передачи ответов
Внедрив потоковую передачу, вы значительно улучшили взаимодействие с пользователем вашего приложения Colorist, создав более отзывчивый и привлекательный интерфейс, который выглядит по-настоящему диалоговым.
8. Синхронизация контекста LLM
На этом бонусном этапе вы реализуете синхронизацию контекста LLM, уведомляя Gemini, когда пользователи выбирают цвета из истории. Это создает более целостный опыт, когда LLM осознает действия пользователей в интерфейсе, а не только их явные сообщения.
Что вы изучите на этом этапе
- Создание синхронизации контекста LLM между вашим пользовательским интерфейсом и LLM
- Сериализация событий пользовательского интерфейса в контекст, который может понять LLM.
- Обновление контекста разговора на основе действий пользователя
- Создание согласованного опыта с помощью различных методов взаимодействия
- Повышение осведомленности о контексте LLM за пределами явных сообщений в чате
Понимание синхронизации контекста LLM
Традиционные чат-боты отвечают только на явные сообщения пользователей, создавая разрыв связи, когда пользователи взаимодействуют с приложением другими способами. Синхронизация контекста LLM устраняет это ограничение:
Почему важна синхронизация контекста LLM
Когда пользователи взаимодействуют с вашим приложением через элементы пользовательского интерфейса (например, выбор цвета из истории), LLM не может узнать, что произошло, если вы явно не сообщите об этом. Синхронизация контекста LLM:
- Поддерживает контекст : информирует LLM обо всех соответствующих действиях пользователя.
- Создает согласованность : создает целостный опыт, при котором LLM признает взаимодействие пользовательского интерфейса.
- Повышает интеллект : позволяет LLM соответствующим образом реагировать на все действия пользователя.
- Улучшает взаимодействие с пользователем : делает все приложение более интегрированным и отзывчивым.
- Уменьшает усилия пользователя : устраняет необходимость пользователям вручную объяснять свои действия в пользовательском интерфейсе.
В вашем приложении Colorist, когда пользователь выбирает цвет из истории, вы хотите, чтобы Gemini подтвердил это действие и разумно прокомментировал выбранный цвет, сохраняя иллюзию четкого и осведомленного помощника.
Обновите службу чата Gemini для уведомлений о выборе цвета.
Сначала вы добавите в GeminiChatService
метод, который будет уведомлять LLM, когда пользователь выбирает цвет из истории. Обновите файл lib/services/gemini_chat_service.dart
:
lib/services/gemini_chat_service.dart
import 'dart:async';
import 'dart:convert'; // Add this import
import 'package:colorist_ui/colorist_ui.dart';
import 'package:firebase_ai/firebase_ai.dart';
import 'package:flutter_riverpod/legacy.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import '../providers/gemini.dart';
import 'gemini_tools.dart';
part 'gemini_chat_service.g.dart';
final conversationStateProvider = StateProvider(
(ref) => ConversationState.idle,
);
class GeminiChatService {
GeminiChatService(this.ref);
final Ref ref;
Future<void> notifyColorSelection(ColorData color) => sendMessage( // Add from here...
'User selected color from history: ${json.encode(color.toLLMContextMap())}',
); // To here.
Future<void> sendMessage(String message) async {
final chatSession = await ref.read(chatSessionProvider.future);
final conversationState = ref.read(conversationStateProvider);
final chatStateNotifier = ref.read(chatStateProvider.notifier);
final logStateNotifier = ref.read(logStateProvider.notifier);
if (conversationState == ConversationState.busy) {
logStateNotifier.logWarning(
"Can't send a message while a conversation is in progress",
);
throw Exception(
"Can't send a message while a conversation is in progress",
);
}
final conversationStateNotifier = ref.read(
conversationStateProvider.notifier,
);
conversationStateNotifier.state = ConversationState.busy;
chatStateNotifier.addUserMessage(message);
logStateNotifier.logUserText(message);
final llmMessage = chatStateNotifier.createLlmMessage();
try {
final responseStream = chatSession.sendMessageStream(
Content.text(message),
);
await for (final block in responseStream) {
await _processBlock(block, llmMessage.id);
}
} catch (e, st) {
logStateNotifier.logError(e, st: st);
chatStateNotifier.appendToMessage(
llmMessage.id,
"\nI'm sorry, I encountered an error processing your request. "
"Please try again.",
);
} finally {
chatStateNotifier.finalizeMessage(llmMessage.id);
conversationStateNotifier.state = ConversationState.idle;
}
}
Future<void> _processBlock(
GenerateContentResponse block,
String llmMessageId,
) async {
final chatSession = await ref.read(chatSessionProvider.future);
final chatStateNotifier = ref.read(chatStateProvider.notifier);
final logStateNotifier = ref.read(logStateProvider.notifier);
final blockText = block.text;
if (blockText != null) {
logStateNotifier.logLlmText(blockText);
chatStateNotifier.appendToMessage(llmMessageId, blockText);
}
if (block.functionCalls.isNotEmpty) {
final geminiTools = ref.read(geminiToolsProvider);
final responseStream = chatSession.sendMessageStream(
Content.functionResponses([
for (final functionCall in block.functionCalls)
FunctionResponse(
functionCall.name,
geminiTools.handleFunctionCall(
functionCall.name,
functionCall.args,
),
),
]),
);
await for (final response in responseStream) {
final responseText = response.text;
if (responseText != null) {
logStateNotifier.logLlmText(responseText);
chatStateNotifier.appendToMessage(llmMessageId, responseText);
}
}
}
}
}
@riverpod
GeminiChatService geminiChatService(Ref ref) => GeminiChatService(ref);
Ключевым дополнением является метод notifyColorSelection
, который:
- Принимает объект
ColorData
представляющий выбранный цвет. - Кодирует его в формат JSON, который можно включить в сообщение.
- Отправляет специально отформатированное сообщение в LLM, указывающее выбор пользователя.
- Повторно использует существующий метод
sendMessage
для обработки уведомления.
Этот подход позволяет избежать дублирования за счет использования существующей инфраструктуры обработки сообщений.
Обновите основное приложение, чтобы подключить уведомления о выборе цвета.
Теперь измените файл lib/main.dart
чтобы передать функцию уведомления о выборе цвета на главный экран:
lib/main.dart
import 'package:colorist_ui/colorist_ui.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'providers/gemini.dart';
import 'services/gemini_chat_service.dart';
void main() async {
runApp(ProviderScope(child: MainApp()));
}
class MainApp extends ConsumerWidget {
const MainApp({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final model = ref.watch(geminiModelProvider);
final conversationState = ref.watch(conversationStateProvider);
return MaterialApp(
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
),
home: model.when(
data: (data) => MainScreen(
conversationState: conversationState,
notifyColorSelection: (color) { // Add from here...
ref.read(geminiChatServiceProvider).notifyColorSelection(color);
}, // To here.
sendMessage: (text) {
ref.read(geminiChatServiceProvider).sendMessage(text);
},
),
loading: () => LoadingScreen(message: 'Initializing Gemini Model'),
error: (err, st) => ErrorScreen(error: err),
),
);
}
}
Ключевым изменением является добавление обратного вызова notifyColorSelection
, который подключает событие пользовательского интерфейса (выбор цвета из истории) к системе уведомлений LLM.
Обновите системное приглашение
Теперь вам необходимо обновить системное приглашение, чтобы указать LLM, как реагировать на уведомления о выборе цвета. Измените файл assets/system_prompt.md
:
активы/system_prompt.md
# Colorist System Prompt
You are a color expert assistant integrated into a desktop app called Colorist. Your job is to interpret natural language color descriptions and set the appropriate color values using a specialized tool.
## Your Capabilities
You are knowledgeable about colors, color theory, and how to translate natural language descriptions into specific RGB values. You have access to the following tool:
`set_color` - Sets the RGB values for the color display based on a description
## How to Respond to User Inputs
When users describe a color:
1. First, acknowledge their color description with a brief, friendly response
2. Interpret what RGB values would best represent that color description
3. Use the `set_color` tool to set those values (all values should be between 0.0 and 1.0)
4. After setting the color, provide a brief explanation of your interpretation
Example:
User: "I want a sunset orange"
You: "Sunset orange is a warm, vibrant color that captures the golden-red hues of the setting sun. It combines a strong red component with moderate orange tones."
[Then you would call the set_color tool with approximately: red=1.0, green=0.5, blue=0.25]
After the tool call: "I've set a warm orange with strong red, moderate green, and minimal blue components that is reminiscent of the sun low on the horizon."
## When Descriptions are Unclear
If a color description is ambiguous or unclear, please ask the user clarifying questions, one at a time.
## When Users Select Historical Colors
Sometimes, the user will manually select a color from the history panel. When this happens, you'll receive a notification about this selection that includes details about the color. Acknowledge this selection with a brief response that recognizes what they've done and comments on the selected color.
Example notification:
User: "User selected color from history: {red: 0.2, green: 0.5, blue: 0.8, hexCode: #3380CC}"
You: "I see you've selected an ocean blue from your history. This tranquil blue with a moderate intensity has a calming, professional quality to it. Would you like to explore similar shades or create a contrasting color?"
## Important Guidelines
- Always keep RGB values between 0.0 and 1.0
- Provide thoughtful, knowledgeable responses about colors
- When possible, include color psychology, associations, or interesting facts about colors
- Be conversational and engaging in your responses
- Focus on being helpful and accurate with your color interpretations
Ключевым дополнением является раздел «Когда пользователи выбирают исторические цвета», который:
- Объясняет концепцию уведомлений о выборе истории для LLM.
- Приводит пример того, как выглядят эти уведомления.
- Показывает пример подходящего ответа
- Устанавливает ожидания подтверждения выбора и комментариев по поводу цвета.
Это помогает LLM понять, как правильно реагировать на эти специальные сообщения.
Создать код Riverpod
Запустите команду build runner, чтобы сгенерировать необходимый код Riverpod:
dart run build_runner build --delete-conflicting-outputs
Запустите и протестируйте синхронизацию контекста LLM
Запустите ваше приложение:
flutter run -d DEVICE
Тестирование синхронизации контекста LLM включает в себя:
- Сначала сгенерируйте несколько цветов, описав их в чате.
- «Покажи мне яркий фиолетовый»
- «Мне нужен лесной зеленый»
- «Дайте мне ярко-красный»
- Затем нажмите на одну из цветных миниатюр на полосе истории.
Вам следует наблюдать:
- Выбранный цвет отображается на главном дисплее.
- В чате пользователя появляется сообщение с указанием выбора цвета.
- В ответ LLM подтверждает выбор и комментирует цвет.
- Все взаимодействие кажется естественным и сплоченным.
Это создает беспрепятственный опыт, когда LLM осознает и соответствующим образом реагирует как на прямые сообщения, так и на взаимодействия с пользовательским интерфейсом.
Как работает синхронизация контекста LLM
Давайте рассмотрим технические детали того, как работает эта синхронизация:
Поток данных
- Действие пользователя : Пользователь щелкает цвет на полосе истории.
- Событие пользовательского интерфейса : виджет
MainScreen
обнаруживает этот выбор. - Выполнение обратного вызова : запускается обратный вызов
notifyColorSelection
- Создание сообщения : создается специально отформатированное сообщение с данными цвета.
- Обработка LLM : сообщение отправляется в Gemini, который распознает формат.
- Контекстуальный ответ : Близнецы реагируют соответствующим образом в соответствии с подсказкой системы.
- Обновление пользовательского интерфейса : ответ появляется в чате, создавая целостный интерфейс.
Сериализация данных
Ключевым аспектом этого подхода является то, как вы сериализуете данные цвета:
'User selected color from history: ${json.encode(color.toLLMContextMap())}'
Метод toLLMContextMap()
(предоставляемый пакетом colorist_ui
) преобразует объект ColorData
в карту с ключевыми свойствами, понятными LLM. Обычно это включает в себя:
- Значения RGB (красный, зеленый, синий)
- Представление шестнадцатеричного кода
- Любое имя или описание, связанное с цветом
Последовательно форматируя эти данные и включая их в сообщение, вы гарантируете, что у LLM есть вся информация, необходимая для надлежащего ответа.
Более широкие применения синхронизации контекста LLM
Этот шаблон уведомления LLM о событиях пользовательского интерфейса имеет множество применений, помимо выбора цвета:
Другие варианты использования
- Изменения фильтров : уведомляйте LLM, когда пользователи применяют фильтры к данным.
- События навигации : информируйте LLM, когда пользователи переходят к различным разделам.
- Изменения выбора : обновляйте LLM, когда пользователи выбирают элементы из списков или сеток.
- Обновления предпочтений . Сообщайте LLM, когда пользователи меняют настройки или предпочтения.
- Манипулирование данными : уведомляйте LLM, когда пользователи добавляют, редактируют или удаляют данные.
В каждом случае схема остается той же:
- Обнаружить событие пользовательского интерфейса
- Сериализация соответствующих данных
- Отправьте в LLM специально отформатированное уведомление.
- Попросите LLM отреагировать соответствующим образом с помощью системной подсказки.
Лучшие практики синхронизации контекста LLM
Ниже приведены некоторые рекомендации по эффективной синхронизации контекста LLM, основанные на вашей реализации:
1. Единообразное форматирование
Используйте единый формат уведомлений, чтобы LLM мог легко их идентифицировать:
"User [action] [object]: [structured data]"
2. Богатый контекст
Включите в уведомления достаточно подробностей, чтобы LLM мог разумно отреагировать. Для цветов это означает значения RGB, шестнадцатеричные коды и любые другие соответствующие свойства.
3. Четкие инструкции
Предоставьте в системной подсказке подробные инструкции о том, как обрабатывать уведомления, в идеале с примерами.
4. Естественная интеграция
Создавайте уведомления так, чтобы они естественным образом отображались в разговоре, а не создавали технические помехи.
5. Выборочное уведомление
Уведомляйте LLM только о действиях, имеющих отношение к разговору. Не каждое событие пользовательского интерфейса необходимо сообщать.
Поиск неисправностей
Проблемы с уведомлениями
Если LLM не реагирует должным образом на выбор цвета:
- Убедитесь, что формат уведомления соответствует описанию в системной подсказке.
- Убедитесь, что данные цвета сериализуются правильно.
- Убедитесь, что в системном приглашении есть четкие инструкции по обработке выбора.
- Ищите ошибки в чат-сервисе при отправке уведомлений.
Управление контекстом
Если кажется, что LLM теряет контекст:
- Убедитесь, что сеанс чата поддерживается правильно.
- Убедитесь, что переход между состояниями разговора правильный.
- Убедитесь, что уведомления отправляются через один и тот же сеанс чата.
Общие проблемы
По общим вопросам:
- Проверьте журналы на наличие ошибок или предупреждений.
- Проверьте подключение Firebase AI Logic
- Проверьте наличие несоответствий типов в параметрах функции.
- Убедитесь, что весь сгенерированный Riverpod код обновлен.
Ключевые понятия изучены
- Создание синхронизации контекста LLM между пользовательским интерфейсом и LLM
- Сериализация событий пользовательского интерфейса в контексте, удобном для LLM.
- Управление поведением LLM для различных моделей взаимодействия
- Создание связного опыта посредством взаимодействия с сообщениями и без сообщений
- Повышение осведомленности LLM о более широком состоянии приложения
Внедрив синхронизацию контекста LLM, вы создали по-настоящему интегрированную среду, в которой LLM ощущает себя как осведомленный и отзывчивый помощник, а не просто генератор текста. Этот шаблон можно применить к множеству других приложений для создания более естественных и интуитивно понятных интерфейсов на базе искусственного интеллекта.
9. Поздравляем!
Вы успешно завершили кодовую лабораторию Colorist! 🎉
Что вы построили
Вы создали полнофункциональное приложение Flutter, которое интегрирует Gemini API Google для интерпретации описаний цветов на естественном языке. Ваше приложение теперь может:
- Обрабатывайте описания на естественном языке, такие как «оранжевый закат» или «глубокий океанский синий».
- Используйте Gemini для интеллектуального перевода этих описаний в значения RGB.
- Отображение интерпретированных цветов в режиме реального времени с потоковой передачей ответов.
- Обработка взаимодействия с пользователем через чат и элементы пользовательского интерфейса.
- Поддерживать контекстуальную осведомленность при использовании различных методов взаимодействия.
Куда идти отсюда
Теперь, когда вы освоили основы интеграции Gemini с Flutter, вот несколько способов продолжить ваше путешествие:
Улучшите свое приложение Colorist
- Цветовые палитры : добавьте функциональность для создания дополнительных или совпадающих цветовых схем.
- Голосовой ввод : Интегрируйте распознавание речи для словесных описаний цветов.
- Управление историей : добавьте параметры для именования, организации и экспорта наборов цветов.
- Пользовательские подсказки : создайте интерфейс, позволяющий пользователям настраивать системные подсказки.
- Расширенная аналитика : отслеживайте, какие описания работают лучше всего или вызывают трудности.
Узнайте больше о функциях Gemini
- Мультимодальные входные данные : добавьте входные изображения для извлечения цветов из фотографий.
- Генерация контента : используйте Gemini для создания контента, связанного с цветом, например описаний или историй.
- Улучшения в вызове функций : создание более сложных интеграций инструментов с несколькими функциями.
- Настройки безопасности : изучите различные настройки безопасности и их влияние на реакцию.
Примените эти шаблоны к другим доменам
- Анализ документов : создавайте приложения, которые могут понимать и анализировать документы.
- Помощь в творческом письме : создавайте инструменты для письма с предложениями на основе LLM.
- Автоматизация задач : создавайте приложения, которые переводят естественный язык в автоматизированные задачи.
- Приложения, основанные на знаниях : создание экспертных систем в конкретных областях.
Ресурсы
Вот несколько ценных ресурсов для продолжения обучения:
Официальная документация
Курс и руководство
Сообщество
Серия Observable Flutter Agentic
В эпизоде №59 Крейг Лабенс и Эндрю Брогден исследуют эту кодовую лабораторию, выделяя интересные части сборки приложения.
В эпизоде №60 снова присоединяйтесь к Крейгу и Эндрю, которые расширяют приложение Codelab новыми возможностями и борются за то, чтобы заставить LLM делать то, что им говорят.
В эпизоде №61 к Крейгу присоединяется Крис Селлс, чтобы по-новому взглянуть на заголовки новостей и создать соответствующие изображения.
Обратная связь
Мы будем рады услышать о вашем опыте работы с этой лабораторией кода! Пожалуйста, рассмотрите возможность предоставления обратной связи через:
Благодарим вас за завершение этой лаборатории и надеемся, что вы продолжите исследовать захватывающие возможности на стыке Flutter и искусственного интеллекта!