Compila una app de Flutter potenciada por Gemini

Compila una app de Flutter potenciada por Gemini

Acerca de este codelab

subjectÚltima actualización: may 19, 2025
account_circleEscrito por Brett Morgan

1. Compila una app de Flutter potenciada por Gemini

Qué compilarás

En este codelab, compilarás Colorist, una aplicación interactiva de Flutter que lleva la potencia de la API de Gemini directamente a tu app de Flutter. ¿Alguna vez quisiste permitir que los usuarios controlen tu app a través de lenguaje natural, pero no sabías por dónde empezar? En este codelab, se muestra cómo hacerlo.

Colorist permite a los usuarios describir los colores en lenguaje natural (como "el naranja de un atardecer" o "azul océano profundo"), y la app hace lo siguiente:

  • Procesa estas descripciones con la API de Gemini de Google
  • Interpreta las descripciones en valores de color RGB precisos.
  • Muestra el color en la pantalla en tiempo real.
  • Proporciona detalles técnicos sobre el color y contexto interesante sobre él.
  • Mantiene un historial de los colores generados recientemente

Captura de pantalla de la app de Colorist que muestra la interfaz de chat y la pantalla de colores

La app tiene una interfaz de pantalla dividida con un área de visualización en color y un sistema de chat interactivo en un lado, y un panel de registro detallado que muestra las interacciones sin procesar de LLM en el otro. Este registro te permite comprender mejor cómo funciona realmente una integración de LLM.

Por qué esto es importante para los desarrolladores de Flutter

Los LLM están revolucionando la forma en que los usuarios interactúan con las aplicaciones, pero integrarlos de manera eficaz en las apps para dispositivos móviles y computadoras de escritorio presenta desafíos únicos. En este codelab, aprenderás patrones prácticos que van más allá de las llamadas a la API sin procesar.

Tu recorrido de aprendizaje

En este codelab, se explica paso a paso el proceso de compilación de Colorist:

  1. Configuración del proyecto: Comenzarás con una estructura básica de la app de Flutter y el paquete colorist_ui.
  2. Integración básica de Gemini: Conecta tu app a Vertex AI en Firebase y, luego, implementa una comunicación LLM simple.
  3. Instrucciones eficaces: Crea una instrucción del sistema que guíe al LLM para que comprenda las descripciones de colores.
  4. Declaraciones de funciones: Definen las herramientas que el LLM puede usar para establecer colores en tu aplicación.
  5. Manejo de herramientas: Procesa las llamadas a función desde el LLM y conéctalas al estado de tu app.
  6. Respuestas de transmisión: Mejora la experiencia del usuario con respuestas de LLM de transmisión en tiempo real.
  7. Sincronización de contexto de LLM: Informa al LLM sobre las acciones del usuario para crear una experiencia cohesiva.

Qué aprenderás

  • Configura Vertex AI en Firebase para aplicaciones de Flutter
  • Crea instrucciones del sistema eficaces para guiar el comportamiento del LLM
  • Implementa declaraciones de funciones que conecten el lenguaje natural y las funciones de la app.
  • Procesa las respuestas de transmisión continua para obtener una experiencia del usuario responsiva
  • Sincroniza el estado entre los eventos de la IU y el LLM.
  • Administra el estado de la conversación de LLM con Riverpod
  • Controla los errores de forma elegante en las aplicaciones con tecnología de LLM

Vista previa del código: Una muestra de lo que implementarás

A continuación, se muestra un resumen de la declaración de la función que crearás para permitir que el LLM establezca colores en tu app:

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

Un video de resumen de este codelab

Mira a Craig Labenz y Andrew Brogdon hablar sobre este codelab en el episodio 59 de Observable Flutter:

Requisitos previos

Para aprovechar al máximo este codelab, debes tener lo siguiente:

  • Experiencia en el desarrollo de Flutter: Familiaridad con los conceptos básicos de Flutter y la sintaxis de Dart
  • Conocimiento de programación asíncrona: Conocimiento de Futures, async/await y flujos
  • Cuenta de Firebase: Necesitarás una Cuenta de Google para configurar Firebase.
  • Proyecto de Firebase con la facturación habilitada: Vertex AI en Firebase requiere una cuenta de facturación.

Comencemos a compilar tu primera app de Flutter con LLM.

2. Configuración del proyecto y servicio de eco

En este primer paso, configurarás la estructura del proyecto y, luego, implementarás un servicio de eco simple que se reemplazará con la integración de la API de Gemini. Esto establece la arquitectura de la aplicación y garantiza que la IU funcione correctamente antes de agregar la complejidad de las llamadas a LLM.

Qué aprenderás en este paso

  • Cómo configurar un proyecto de Flutter con las dependencias necesarias
  • Cómo trabajar con el paquete colorist_ui para componentes de la IU
  • Implementa un servicio de mensajes de eco y conéctalo a la IU

Nota importante sobre los precios

Crea un nuevo proyecto de Flutter

Comienza por crear un nuevo proyecto de Flutter con el siguiente comando:

flutter create -e colorist --platforms=android,ios,macos,web,windows

La marca -e indica que deseas un proyecto vacío sin la app predeterminada de counter. La app está diseñada para funcionar en computadoras, dispositivos móviles y la Web. Sin embargo, flutterfire no es compatible con Linux en este momento.

Cómo agregar dependencias

Navega al directorio del proyecto y agrega las dependencias necesarias:

cd colorist
flutter pub add colorist_ui flutter_riverpod riverpod_annotation
flutter pub add
--dev build_runner riverpod_generator riverpod_lint json_serializable custom_lint

Se agregarán los siguientes paquetes de claves:

  • colorist_ui: Es un paquete personalizado que proporciona los componentes de la IU para la app de Colorist.
  • flutter_riverpod y riverpod_annotation: Para la administración de estados
  • logging: Para el registro estructurado
  • Dependencias de desarrollo para la generación de código y linting

Tu pubspec.yaml se verá de la siguiente manera:

pubspec.yaml

name: colorist
description: "A new Flutter project."
publish_to: 'none'
version: 0.1.0

environment:
  sdk: ^3.8.0

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

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

flutter:
  uses-material-design: true

Configura las opciones de análisis

Agrega custom_lint a tu archivo analysis_options.yaml en la raíz de tu proyecto:

include: package:flutter_lints/flutter.yaml

analyzer:
  plugins:
    - custom_lint

Esta configuración habilita lints específicos de Riverpod para ayudar a mantener la calidad del código.

Implementa el archivo main.dart

Reemplaza el contenido de lib/main.dart con la siguiente información:

lib/main.dart

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

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

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

 
@override
 
Widget build(BuildContext context, WidgetRef ref) {
   
return MaterialApp(
     
theme: ThemeData(
       
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
     
),
     
home: MainScreen(
       
sendMessage: (message) {
         
sendMessage(message, ref);
       
},
     
),
   
);
 
}

 
// A fake LLM that just echoes back what it receives.
 
void sendMessage(String message, WidgetRef ref) {
   
final chatStateNotifier = ref.read(chatStateNotifierProvider.notifier);
   
final logStateNotifier = ref.read(logStateNotifierProvider.notifier);

   
chatStateNotifier.addUserMessage(message);
   
logStateNotifier.logUserText(message);
   
chatStateNotifier.addLlmMessage(message, MessageState.complete);
   
logStateNotifier.logLlmText(message);
 
}
}

Esto configura una app de Flutter que implementa un servicio de eco simple que imita el comportamiento de un LLM simplemente mostrando el mensaje del usuario.

Información sobre la arquitectura

Tomémonos un momento para comprender la arquitectura de la app de colorist:

El paquete colorist_ui

El paquete colorist_ui proporciona componentes de IU precompilados y herramientas de administración de estado:

  1. MainScreen: Es el componente principal de la IU que muestra lo siguiente:
    • Un diseño de pantalla dividida en computadoras de escritorio (área de interacción y panel de registro)
    • Una interfaz con pestañas en dispositivos móviles
    • Pantalla en color, interfaz de chat y miniaturas del historial
  2. Administración de estados: La app usa varios notificadores de estado:
    • ChatStateNotifier: Administra los mensajes de chat.
    • ColorStateNotifier: Administra el color y el historial actuales.
    • LogStateNotifier: Administra las entradas de registro para la depuración.
  3. Manejo de mensajes: La app usa un modelo de mensaje con diferentes estados:
    • Mensajes de los usuarios: Ingresados por el usuario
    • Mensajes de LLM: Los genera el LLM (o tu servicio de eco por ahora).
    • MessageState: Realiza un seguimiento de si los mensajes de LLM están completos o aún se están transmitiendo.

Arquitectura de aplicaciones

La app sigue la siguiente arquitectura:

  1. Capa de la IU: proporcionada por el paquete colorist_ui
  2. Administración de estado: Usa Riverpod para la administración de estado reactivo.
  3. Capa de servicio: Actualmente, contiene tu servicio de eco simple, que se reemplazará por el servicio de Gemini Chat.
  4. Integración de LLM: Se agregará en pasos posteriores

Esta separación te permite enfocarte en implementar la integración de LLM mientras se ocupan de los componentes de la IU.

Ejecuta la app

Ejecuta la app con el siguiente comando:

flutter run -d DEVICE

Reemplaza DEVICE por tu dispositivo de destino, como macos, windows, chrome o un ID de dispositivo.

Captura de pantalla de la app de Colorist que muestra el servicio de eco que renderiza Markdown

Ahora deberías ver la app de Colorist con lo siguiente:

  1. Un área de visualización de color con un color predeterminado
  2. Una interfaz de chat en la que puedes escribir mensajes
  3. Un panel de registro que muestra las interacciones del chat

Prueba escribir un mensaje como "Quiero un color azul oscuro" y presiona Enviar. El servicio de eco simplemente repetirá tu mensaje. En pasos posteriores, reemplazarás esto por una interpretación de color real con la API de Gemini a través de Vertex AI en Firebase.

Próximos pasos

En el siguiente paso, configurarás Firebase e implementarás la integración básica de la API de Gemini para reemplazar tu servicio de eco por el servicio de chat de Gemini. Esto permitirá que la app interprete las descripciones de colores y proporcione respuestas inteligentes.

Solución de problemas

Problemas con el paquete de IU

Si tienes problemas con el paquete colorist_ui, haz lo siguiente:

  • Asegúrate de usar la versión más reciente
  • Verifica que hayas agregado la dependencia correctamente
  • Verifica si hay versiones de paquetes en conflicto

Errores de compilación

Si ves errores de compilación, haz lo siguiente:

  • Asegúrate de tener instalado el SDK de Flutter del canal estable más reciente.
  • Ejecuta flutter clean seguido de flutter pub get
  • Comprueba el resultado de la consola en busca de mensajes de error específicos

Conceptos clave aprendidos

  • Cómo configurar un proyecto de Flutter con las dependencias necesarias
  • Comprender la arquitectura de la aplicación y las responsabilidades de los componentes
  • Implementa un servicio simple que imite el comportamiento de un LLM
  • Cómo conectar el servicio a los componentes de la IU
  • Cómo usar Riverpod para la administración de estados

3. Integración básica de Gemini Chat

En este paso, reemplazarás el servicio de eco del paso anterior por la integración de la API de Gemini con Vertex AI en Firebase. Configurarás Firebase, configurarás los proveedores necesarios y, luego, implementarás un servicio de chat básico que se comunique con la API de Gemini.

Qué aprenderás en este paso

  • Cómo configurar Firebase en una aplicación de Flutter
  • Cómo configurar Vertex AI en Firebase para obtener acceso a Gemini
  • Cómo crear proveedores de Riverpod para los servicios de Firebase y Gemini
  • Implementa un servicio de chat básico con la API de Gemini
  • Cómo controlar las respuestas asíncronas de la API y los estados de error

Configura Firebase

Primero, debes configurar Firebase para tu proyecto de Flutter. Esto implica crear un proyecto de Firebase, agregarle tu app y configurar los parámetros de Vertex AI necesarios.

Crea un proyecto de Firebase

  1. Ve a Firebase console y accede con tu Cuenta de Google.
  2. Haz clic en Crear un proyecto de Firebase o selecciona un proyecto existente.
  3. Sigue el asistente de configuración para crear tu proyecto.
  4. Una vez que crees tu proyecto, deberás actualizar al plan Blaze (pago por uso) para acceder a los servicios de Vertex AI. Haz clic en el botón Upgrade en la parte inferior izquierda de Firebase console.

Configura Vertex AI en tu proyecto de Firebase

  1. En Firebase console, navega a tu proyecto.
  2. En la barra lateral izquierda, selecciona AI.
  3. En la tarjeta Vertex AI in Firebase, selecciona Comenzar.
  4. Sigue las indicaciones para habilitar las APIs de Vertex AI in Firebase en tu proyecto.

Instala la CLI de FlutterFire

La CLI de FlutterFire simplifica la configuración de Firebase en apps de Flutter:

dart pub global activate flutterfire_cli

Agrega Firebase a tu app de Flutter

  1. Agrega los paquetes de Firebase Core y Vertex AI a tu proyecto:
flutter pub add firebase_core firebase_vertexai
  1. Ejecuta el comando de configuración de FlutterFire:
flutterfire configure

Este comando hará lo siguiente:

  • Te pedirá que selecciones el proyecto de Firebase que acabas de crear.
  • Registra tus apps de Flutter con Firebase
  • Genera un archivo firebase_options.dart con la configuración de tu proyecto

El comando detectará automáticamente las plataformas seleccionadas (iOS, Android, macOS, Windows y Web) y las configurará de forma adecuada.

Configuración específica de la plataforma

Firebase requiere versiones mínimas superiores a las predeterminadas para Flutter. También requiere acceso a la red para comunicarse con Vertex AI en los servidores de Firebase.

Configura los permisos de macOS

En macOS, debes habilitar el acceso a la red en los derechos de tu app:

  1. Abre macos/Runner/DebugProfile.entitlements y agrega lo siguiente:

macos/Runner/DebugProfile.entitlements

<key>com.apple.security.network.client</key>
<true/>
  1. También abre macos/Runner/Release.entitlements y agrega la misma entrada.
  2. Actualiza la versión mínima de macOS en la parte superior de macos/Podfile:

macos/Podfile

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

Configura los permisos de iOS

En iOS, actualiza la versión mínima en la parte superior de ios/Podfile:

ios/Podfile

# Firebase requires at least iOS 13.0
platform
:ios, '13.0'

Configura la configuración de Android

En Android, actualiza android/app/build.gradle.kts:

android/app/build.gradle.kts

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

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

Crea proveedores de modelos de Gemini

Ahora, crearás los proveedores de Riverpod para Firebase y Gemini. Crea un archivo lib/providers/gemini.dart nuevo:

lib/providers/gemini.dart

import 'dart:async';

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

import '../firebase_options.dart';

part 'gemini.g.dart';

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

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

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

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

En este archivo, se define la base de tres proveedores de claves. Estos proveedores se generan cuando los generadores de código de Riverpod ejecutan dart run build_runner.

  1. firebaseAppProvider: Inicializa Firebase con la configuración de tu proyecto.
  2. geminiModelProvider: Crea una instancia de modelo generativo de Gemini.
  3. chatSessionProvider: Crea y mantiene una sesión de chat con el modelo de Gemini.

La anotación keepAlive: true en la sesión de chat garantiza que persista durante todo el ciclo de vida de la app y mantenga el contexto de la conversación.

Implementa el servicio de chat de Gemini

Crea un nuevo archivo lib/services/gemini_chat_service.dart para implementar el servicio de chat:

lib/services/gemini_chat_service.dart

import 'dart:async';

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

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

part 'gemini_chat_service.g.dart';

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

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

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

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

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

Este servicio tiene las siguientes características:

  1. Acepta los mensajes de los usuarios y los envía a la API de Gemini
  2. Actualiza la interfaz de chat con las respuestas del modelo.
  3. Registra todas las comunicaciones para facilitar la comprensión del flujo real de LLM
  4. Controla los errores con los comentarios adecuados de los usuarios

Nota: En este punto, la ventana de registro se verá casi idéntica a la ventana de chat. El registro será más interesante una vez que introduzcas las llamadas a función y, luego, las respuestas de transmisión.

Genera código de Riverpod

Ejecuta el comando del generador de compilaciones para generar el código de Riverpod necesario:

dart run build_runner build --delete-conflicting-outputs

Esto creará los archivos .g.dart que Riverpod necesita para funcionar.

Actualiza el archivo main.dart

Actualiza tu archivo lib/main.dart para usar el nuevo servicio de chat de 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),
     
),
   
);
 
}
}

Los cambios clave de esta actualización son los siguientes:

  1. Reemplazo del servicio de eco por el servicio de chat basado en la API de Gemini
  2. Cómo agregar pantallas de carga y error con el patrón AsyncValue de Riverpod con el método when
  3. Cómo conectar la IU a tu nuevo servicio de chat a través de la devolución de llamada sendMessage

Ejecuta la app

Ejecuta la app con el siguiente comando:

flutter run -d DEVICE

Reemplaza DEVICE por tu dispositivo de destino, como macos, windows, chrome o un ID de dispositivo.

Captura de pantalla de la app de Colorist que muestra el LLM de Gemini respondiendo a una solicitud de un color amarillo brillante

Ahora, cuando escribas un mensaje, se enviará a la API de Gemini y recibirás una respuesta del LLM en lugar de un eco. El panel de registro mostrará las interacciones con la API.

Información sobre la comunicación de LLM

Tomemos un momento para comprender qué sucede cuando te comunicas con la API de Gemini:

El flujo de comunicación

  1. Entrada del usuario: El usuario ingresa texto en la interfaz de chat.
  2. Formato de la solicitud: La app da formato al texto como un objeto Content para la API de Gemini.
  3. Comunicación con la API: El texto se envía a la API de Gemini a través de Vertex AI en Firebase.
  4. Procesamiento de LLM: El modelo Gemini procesa el texto y genera una respuesta.
  5. Manejo de respuestas: La app recibe la respuesta y actualiza la IU.
  6. Registro: Toda la comunicación se registra para garantizar la transparencia.

Sesiones de chat y contexto de conversación

La sesión de chat de Gemini mantiene el contexto entre los mensajes, lo que permite interacciones conversacionales. Esto significa que el LLM "recuerda" los intercambios anteriores en la sesión actual, lo que permite conversaciones más coherentes.

La anotación keepAlive: true en tu proveedor de sesión de chat garantiza que este contexto persista durante todo el ciclo de vida de la app. Este contexto persistente es fundamental para mantener un flujo de conversación natural con el LLM.

Próximos pasos

En este punto, puedes preguntarle cualquier cosa a la API de Gemini, ya que no hay restricciones sobre lo que responderá. Por ejemplo, podrías pedirle un resumen de las Guerras de las Rosas, que no se relaciona con el propósito de tu app de colores.

En el siguiente paso, crearás una instrucción del sistema para guiar a Gemini en la interpretación de las descripciones de colores de manera más eficaz. En este artículo, se muestra cómo personalizar el comportamiento de un LLM para las necesidades específicas de la aplicación y enfocar sus capacidades en el dominio de tu app.

Solución de problemas

Problemas de configuración de Firebase

Si encuentras errores con la inicialización de Firebase, haz lo siguiente:

  • Asegúrate de que el archivo firebase_options.dart se haya generado correctamente
  • Verifica que actualizaste al plan Blaze para obtener acceso a Vertex AI

Errores de acceso a la API

Si recibes errores de acceso a la API de Gemini, haz lo siguiente:

  • Confirma que la facturación esté configurada correctamente en tu proyecto de Firebase
  • Verifica que Vertex AI y la API de Cloud AI estén habilitadas en tu proyecto de Firebase
  • Verifica la conectividad de red y la configuración del firewall
  • Verifica que el nombre del modelo (gemini-2.0-flash) sea correcto y esté disponible.

Problemas con el contexto de la conversación

Si notas que Gemini no recuerda el contexto anterior del chat, haz lo siguiente:

  • Confirma que la función chatSession esté annotateada con @Riverpod(keepAlive: true).
  • Verifica que estés reutilizando la misma sesión de chat para todos los intercambios de mensajes.
  • Verifica que la sesión de chat se haya inicializado correctamente antes de enviar mensajes.

Problemas específicos de la plataforma

Para problemas específicos de la plataforma, haz lo siguiente:

  • iOS/macOS: Asegúrate de que se establezcan los derechos adecuados y de que se configuren las versiones mínimas
  • Android: Verifica que la versión mínima del SDK esté configurada correctamente
  • Cómo revisar los mensajes de error específicos de la plataforma en la consola

Conceptos clave aprendidos

  • Cómo configurar Firebase en una aplicación de Flutter
  • Cómo configurar Vertex AI in Firebase para acceder a Gemini
  • Cómo crear proveedores de Riverpod para servicios asíncronos
  • Implementa un servicio de chat que se comunique con un LLM
  • Cómo controlar los estados asíncronos de la API (carga, error y datos)
  • Comprende el flujo de comunicación y las sesiones de chat de los LLM

4. Instrucciones eficaces para las descripciones de colores

En este paso, crearás e implementarás una instrucción del sistema que guíe a Gemini en la interpretación de las descripciones de colores. Las instrucciones del sistema son una forma eficaz de personalizar el comportamiento del LLM para tareas específicas sin cambiar tu código.

Qué aprenderás en este paso

  • Comprender las indicaciones del sistema y su importancia en las aplicaciones de LLM
  • Cómo crear indicaciones eficaces para tareas específicas del dominio
  • Cómo cargar y usar mensajes del sistema en una app de Flutter
  • Guía a un LLM para que proporcione respuestas con formato coherente
  • Prueba cómo las instrucciones del sistema afectan el comportamiento del LLM

Comprende los mensajes del sistema

Antes de comenzar con la implementación, comprendamos qué son las indicaciones del sistema y por qué son importantes:

¿Qué son las indicaciones del sistema?

Una instrucción del sistema es un tipo especial de instrucción que se le da a un LLM que establece el contexto, los lineamientos de comportamiento y las expectativas para sus respuestas. A diferencia de los mensajes de los usuarios, las indicaciones del sistema hacen lo siguiente:

  • Establece el rol y el arquetipo del LLM
  • Define conocimientos o capacidades especializadas
  • Proporciona instrucciones de formato
  • Establece restricciones en las respuestas
  • Describe cómo controlar varias situaciones

Piensa en una instrucción del sistema como la “descripción del cargo” del LLM, ya que le indica al modelo cómo comportarse durante la conversación.

Por qué son importantes las indicaciones del sistema

Las instrucciones del sistema son fundamentales para crear interacciones de LLM coherentes y útiles, ya que hacen lo siguiente:

  1. Asegúrate de que haya coherencia: Guía al modelo para que proporcione respuestas en un formato coherente.
  2. Mejora la relevancia: Enfoca el modelo en tu dominio específico (en tu caso, los colores).
  3. Establece límites: Define lo que el modelo debe y no debe hacer.
  4. Mejora la experiencia del usuario: Crea un patrón de interacción más natural y útil.
  5. Reducir el procesamiento posterior: Obtén respuestas en formatos más fáciles de analizar o mostrar.

Para tu app de Colorist, necesitas que el LLM interprete de forma coherente las descripciones de colores y proporcione valores RGB en un formato específico.

Cómo crear un recurso de instrucción del sistema

Primero, crearás un archivo de instrucciones del sistema que se cargará durante el tiempo de ejecución. Este enfoque te permite modificar la instrucción sin volver a compilar la app.

Crea un archivo nuevo assets/system_prompt.md con el siguiente contenido:

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

Comprende la estructura de las instrucciones del sistema

Veamos qué hace esta instrucción:

  1. Definición del rol: Establece el LLM como un "asistente experto en colores".
  2. Explicación de la tarea: Define la tarea principal como interpretar las descripciones de colores en valores RGB.
  3. Formato de la respuesta: Especifica exactamente cómo deben tener formato los valores RGB para mantener la coherencia.
  4. Ejemplo de intercambio: Proporciona un ejemplo concreto del patrón de interacción esperado.
  5. Manejo de casos extremos: Indica cómo manejar descripciones poco claras.
  6. Restricciones y lineamientos: Establece límites, como mantener los valores RGB entre 0.0 y 1.0.

Este enfoque estructurado garantiza que las respuestas del LLM sean coherentes, informativas y tengan un formato que sea fácil de analizar si deseas extraer los valores RGB de forma programática.

Actualiza pubspec.yaml

Ahora, actualiza la parte inferior de tu pubspec.yaml para incluir el directorio de recursos:

pubspec.yaml

flutter:
  uses-material-design: true

  assets:
    - assets/

Ejecuta flutter pub get para actualizar el paquete de recursos.

Crea un proveedor de instrucciones del sistema

Crea un archivo nuevo lib/providers/system_prompt.dart para cargar la indicación del sistema:

lib/providers/system_prompt.dart

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

part 'system_prompt.g.dart';

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

Este proveedor usa el sistema de carga de recursos de Flutter para leer el archivo de instrucciones durante el tiempo de ejecución.

Actualiza el proveedor del modelo de Gemini

Ahora, modifica el archivo lib/providers/gemini.dart para incluir el mensaje del sistema:

lib/providers/gemini.dart

import 'dart:async';

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

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

part 'gemini.g.dart';

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

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

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

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

El cambio clave es agregar systemInstruction: Content.system(systemPrompt) cuando se crea el modelo generativo. Esto le indica a Gemini que use tus instrucciones como la instrucción del sistema para todas las interacciones en esta sesión de chat.

Genera código de Riverpod

Ejecuta el comando build runner para generar el código de Riverpod necesario:

dart run build_runner build --delete-conflicting-outputs

Ejecuta y prueba la aplicación

Ahora ejecuta tu aplicación:

flutter run -d DEVICE

Captura de pantalla de la app de Colorist que muestra el LLM de Gemini respondiendo con una respuesta en caracteres para una app de selección de colores

Prueba con varias descripciones de colores:

  • “Quiero un azul cielo”
  • "Dame un verde bosque"
  • "Cómo crear un naranja intenso de atardecer"
  • "Quiero el color de la lavanda fresca"
  • "Muéstrame algo como un azul profundo del océano"

Deberías notar que Gemini ahora responde con explicaciones coloquiales sobre los colores junto con valores RGB con un formato coherente. La instrucción del sistema guió de manera eficaz al LLM para que proporcione el tipo de respuestas que necesitas.

También puedes pedirle contenido fuera del contexto de los colores. Por ejemplo, las principales causas de las Guerras de las Rosas. Deberías notar una diferencia con respecto al paso anterior.

La importancia de la ingeniería de instrucciones para tareas especializadas

Las indicaciones del sistema son arte y ciencia. Son una parte fundamental de la integración de LLM que puede afectar de manera significativa la utilidad del modelo para tu aplicación específica. Lo que hiciste aquí es una forma de ingeniería de instrucciones: adaptar las instrucciones para que el modelo se comporte de manera que se adapte a las necesidades de tu aplicación.

La ingeniería de instrucciones eficaz implica lo siguiente:

  1. Definición clara del rol: Establece cuál es el propósito del LLM.
  2. Instrucciones explícitas: Detallan exactamente cómo debe responder el LLM.
  3. Ejemplos concretos: Muestra cómo se ven las respuestas buenas en lugar de solo explicarlas.
  4. Manejo de casos extremos: Indica al LLM cómo abordar situaciones ambiguas.
  5. Especificaciones de formato: Garantiza que las respuestas estén estructuradas de manera coherente y utilizable.

La instrucción del sistema que creaste transforma las capacidades genéricas de Gemini en un asistente especializado de interpretación de colores que proporciona respuestas con formato específico para las necesidades de tu aplicación. Este es un patrón potente que puedes aplicar a muchos dominios y tareas diferentes.

Próximos pasos

En el siguiente paso, agregarás declaraciones de funciones para aprovechar esta base, lo que permitirá que el LLM no solo sugiera valores RGB, sino que también llame a funciones en tu app para establecer el color directamente. Esto demuestra cómo los LLM pueden cerrar la brecha entre el lenguaje natural y las funciones concretas de la aplicación.

Solución de problemas

Problemas con la carga de recursos

Si encuentras errores al cargar el mensaje del sistema, haz lo siguiente:

  • Verifica que tu pubspec.yaml muestre correctamente el directorio de recursos
  • Verifica que la ruta de acceso en rootBundle.loadString() coincida con la ubicación de tu archivo.
  • Ejecuta flutter clean seguido de flutter pub get para actualizar el paquete de recursos.

Respuestas incoherentes

Si el LLM no sigue las instrucciones de formato de forma coherente, haz lo siguiente:

  • Intenta que los requisitos de formato sean más explícitos en la instrucción del sistema.
  • Agrega más ejemplos para demostrar el patrón esperado
  • Asegúrate de que el formato que solicitas sea razonable para el modelo.

Limitación de frecuencia de la API

Si encuentras errores relacionados con el límite de frecuencia, haz lo siguiente:

  • Ten en cuenta que el servicio de Vertex AI tiene límites de uso.
  • Considera implementar la lógica de reintento con retirada exponencial
  • Verifica si hay problemas de cuota en Firebase console

Conceptos clave aprendidos

  • Comprender el rol y la importancia de las indicaciones del sistema en las aplicaciones de LLM
  • Cómo crear propuestas eficaces con instrucciones, ejemplos y restricciones claras
  • Cómo cargar y usar mensajes del sistema en una aplicación de Flutter
  • Orienta el comportamiento de LLM para tareas específicas del dominio
  • Cómo usar la ingeniería de instrucciones para dar forma a las respuestas de LLM

En este paso, se muestra cómo puedes lograr una personalización significativa del comportamiento de LLM sin cambiar tu código, simplemente proporcionando instrucciones claras en la instrucción del sistema.

5. Declaraciones de funciones para herramientas de LLM

En este paso, comenzarás a trabajar para permitir que Gemini realice acciones en tu app mediante la implementación de declaraciones de funciones. Esta potente función permite que el LLM no solo sugiera valores RGB, sino que también los establezca en la IU de tu app a través de llamadas a herramientas especializadas. Sin embargo, se requerirá el siguiente paso para ver las solicitudes de LLM que se ejecutan en la app de Flutter.

Qué aprenderás en este paso

  • Comprende las llamadas a funciones de LLM y sus beneficios para las aplicaciones de Flutter
  • Define declaraciones de funciones basadas en esquemas para Gemini
  • Cómo integrar declaraciones de funciones con tu modelo de Gemini
  • Actualiza el mensaje del sistema para usar las funciones de la herramienta

Información sobre las llamadas a funciones

Antes de implementar las declaraciones de funciones, comprendamos qué son y por qué son valiosas:

¿Qué es una llamada a función?

Las llamadas a función (a veces llamadas "uso de herramientas") son una función que permite que un LLM haga lo siguiente:

  1. Reconocer cuándo una solicitud del usuario se beneficiaría de invocar una función específica
  2. Genera un objeto JSON estructurado con los parámetros necesarios para esa función.
  3. Permite que tu aplicación ejecute la función con esos parámetros.
  4. Recibir el resultado de la función y, luego, incorporarlo en su respuesta

En lugar de que el LLM solo describa lo que se debe hacer, las llamadas a funciones le permiten activar acciones concretas en tu aplicación.

Por qué es importante llamar a funciones para las apps de Flutter

Las llamadas a funciones crean un puente potente entre el lenguaje natural y las funciones de la aplicación:

  1. Acción directa: Los usuarios pueden describir lo que quieren en lenguaje natural, y la app responde con acciones concretas.
  2. Resultado estructurado: El LLM produce datos estructurados y limpios en lugar de texto que necesita análisis.
  3. Operaciones complejas: Permite que el LLM acceda a datos externos, realice cálculos o modifique el estado de la aplicación.
  4. Mejor experiencia del usuario: Crea una integración perfecta entre la conversación y la funcionalidad.

En tu app de Colorist, las llamadas a función permiten que los usuarios digan "Quiero un verde bosque" y que la IU se actualice de inmediato con ese color, sin tener que analizar los valores RGB del texto.

Define declaraciones de funciones

Crea un archivo lib/services/gemini_tools.dart nuevo para definir las declaraciones de tus funciones:

lib/services/gemini_tools.dart

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

part 'gemini_tools.g.dart';

class GeminiTools {
 
GeminiTools(this.ref);

 
final Ref ref;

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

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

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

Información sobre las declaraciones de funciones

Veamos qué hace este código:

  1. Nombre de la función: Le asignas el nombre set_color a tu función para indicar claramente su propósito.
  2. Descripción de la función: Proporcionas una descripción clara que ayuda al LLM a comprender cuándo usarlo.
  3. Definiciones de los parámetros: Define parámetros estructurados con sus propias descripciones:
    • red: Es el componente rojo del RGB, especificado como un número entre 0.0 y 1.0.
    • green: Es el componente verde de RGB, especificado como un número entre 0.0 y 1.0.
    • blue: Es el componente azul de RGB, especificado como un número entre 0.0 y 1.0.
  4. Tipos de esquemas: Usas Schema.number() para indicar que estos son valores numéricos.
  5. Colección de herramientas: Creas una lista de herramientas que contiene la declaración de tu función.

Este enfoque estructurado ayuda al LLM de Gemini a comprender lo siguiente:

  • Cuándo debe llamar a esta función
  • Qué parámetros debe proporcionar
  • Qué restricciones se aplican a esos parámetros (como el rango de valores)

Actualiza el proveedor del modelo de Gemini

Ahora, modifica tu archivo lib/providers/gemini.dart para incluir las declaraciones de funciones cuando inicialices el modelo de Gemini:

lib/providers/gemini.dart

import 'dart:async';

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

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

part 'gemini.g.dart';

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

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

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

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

El cambio clave es agregar el parámetro tools: geminiTools.tools cuando se crea el modelo generativo. Esto permite que Gemini conozca las funciones que están disponibles para llamar.

Actualiza el mensaje del sistema

Ahora, debes modificar el mensaje del sistema para indicarle al LLM que use la nueva herramienta set_color. Actualiza 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

Los cambios clave en la instrucción del sistema son los siguientes:

  1. Introducción a la herramienta: En lugar de solicitar valores RGB con formato, ahora le informas al LLM sobre la herramienta set_color.
  2. Proceso modificado: Cambias el paso 3 de “formatear los valores en la respuesta” a “usar la herramienta para establecer valores”.
  3. Ejemplo actualizado: Muestras cómo la respuesta debe incluir una llamada a la herramienta en lugar de texto con formato.
  4. Se quitó el requisito de formato: Como usas llamadas a función estructuradas, ya no necesitas un formato de texto específico.

Esta instrucción actualizada le indica al LLM que use llamadas a función en lugar de solo proporcionar valores RGB en formato de texto.

Genera código de Riverpod

Ejecuta el comando build runner para generar el código de Riverpod necesario:

dart run build_runner build --delete-conflicting-outputs

Ejecuta la aplicación

En este punto, Gemini generará contenido que intentará usar llamadas a función, pero aún no implementaste controladores para las llamadas a función. Cuando ejecutes la app y describas un color, verás que Gemini responde como si hubiera invocado una herramienta, pero no verás ningún cambio de color en la IU hasta el siguiente paso.

Ejecuta la app:

flutter run -d DEVICE

Captura de pantalla de la app de Colorist en la que se muestra que el LLM de Gemini responde con una respuesta parcial

Intenta describir un color como “azul océano profundo” o “verde bosque” y observa las respuestas. El LLM intenta llamar a las funciones definidas anteriormente, pero tu código aún no detecta las llamadas a función.

El proceso de llamada a función

Veamos qué sucede cuando Gemini usa llamadas a funciones:

  1. Selección de funciones: El LLM decide si una llamada a función sería útil en función de la solicitud del usuario.
  2. Generación de parámetros: El LLM genera valores de parámetros que se ajustan al esquema de la función.
  3. Formato de llamada a función: El LLM envía un objeto de llamada a función estructurado en su respuesta.
  4. Manejo de aplicaciones: Tu app recibiría esta llamada y ejecutaría la función relevante (implementada en el siguiente paso).
  5. Integración de respuestas: En las conversaciones de varios turnos, el LLM espera que se devuelva el resultado de la función.

En el estado actual de tu app, se producen los primeros tres pasos, pero aún no implementaste los pasos 4 o 5 (controlar las llamadas a función), que harás en el siguiente paso.

Detalles técnicos: Cómo decide Gemini cuándo usar funciones

Gemini toma decisiones inteligentes sobre cuándo usar funciones en función de lo siguiente:

  1. Intención del usuario: Indica si una función es la más adecuada para entregar la solicitud del usuario.
  2. Relevancia de la función: Indica qué tan bien las funciones disponibles coinciden con la tarea.
  3. Disponibilidad de los parámetros: Indica si se pueden determinar con confianza los valores de los parámetros.
  4. Instrucciones del sistema: Guía del mensaje del sistema sobre el uso de la función

Cuando proporcionas declaraciones de funciones y instrucciones del sistema claras, configuras Gemini para que reconozca las solicitudes de descripción de colores como oportunidades para llamar a la función set_color.

Próximos pasos

En el siguiente paso, implementarás controladores para las llamadas a funciones que provienen de Gemini. Esto completará el círculo, lo que permitirá que las descripciones de los usuarios activen cambios de color reales en la IU a través de las llamadas a función del LLM.

Solución de problemas

Problemas con la declaración de funciones

Si encuentras errores con las declaraciones de funciones, haz lo siguiente:

  • Verifica que los nombres y tipos de los parámetros coincidan con lo esperado.
  • Verifica que el nombre de la función sea claro y descriptivo
  • Asegúrate de que la descripción de la función explique con precisión su propósito.

Problemas con los mensajes del sistema

Si el LLM no intenta usar la función, haz lo siguiente:

  • Verifica que el mensaje del sistema le indique claramente al LLM que use la herramienta set_color.
  • Verifica que el ejemplo en la instrucción del sistema demuestre el uso de la función.
  • Intenta que la instrucción para usar la herramienta sea más explícita.

Problemas generales

Si tienes otros problemas, sigue estos pasos:

  • Revisa la consola en busca de errores relacionados con las declaraciones de funciones
  • Verifica que las herramientas se pasen correctamente al modelo
  • Asegúrate de que todo el código generado por Riverpod esté actualizado

Conceptos clave aprendidos

  • Cómo definir declaraciones de funciones para extender las capacidades de LLM en apps de Flutter
  • Cómo crear esquemas de parámetros para la recopilación de datos estructurados
  • Cómo integrar declaraciones de funciones con el modelo de Gemini
  • Actualiza los mensajes del sistema para fomentar el uso de las funciones
  • Comprende cómo los LLM seleccionan y llaman a funciones

Este paso demuestra cómo los LLM pueden cerrar la brecha entre la entrada de lenguaje natural y las llamadas a funciones estructuradas, sentando las bases para una integración perfecta entre las funciones de conversación y de la aplicación.

6. Cómo implementar el control de herramientas

En este paso, implementarás controladores para las llamadas a función que provienen de Gemini. Esto completa el círculo de comunicación entre las entradas de lenguaje natural y las funciones concretas de la aplicación, lo que permite que el LLM manipule directamente tu IU en función de las descripciones de los usuarios.

Qué aprenderás en este paso

  • Comprende la canalización completa de llamadas a funciones en las aplicaciones de LLM
  • Procesa llamadas a función desde Gemini en una aplicación de Flutter
  • Implementa controladores de funciones que modifiquen el estado de la aplicación
  • Controla las respuestas de las funciones y muestra los resultados al LLM
  • Creación de un flujo de comunicación completo entre el LLM y la IU
  • Cómo registrar llamadas y respuestas de funciones para lograr transparencia

Información sobre la canalización de llamadas a función

Antes de comenzar con la implementación, comprendamos la canalización de llamadas a funciones completa:

Flujo de extremo a extremo

  1. Entrada del usuario: El usuario describe un color en lenguaje natural (p.ej., "forest green")
  2. Procesamiento de LLM: Gemini analiza la descripción y decide llamar a la función set_color.
  3. Generación de llamadas a función: Gemini crea un JSON estructurado con parámetros (valores rojos, verdes y azules).
  4. Recepción de llamadas a funciones: Tu app recibe estos datos estructurados de Gemini.
  5. Ejecución de la función: Tu app ejecuta la función con los parámetros proporcionados.
  6. Actualización de estado: La función actualiza el estado de tu app (cambia el color que se muestra).
  7. Generación de respuestas: Tu función muestra los resultados al LLM.
  8. Incorporación de respuestas: El LLM incorpora estos resultados en su respuesta final.
  9. Actualización de la IU: La IU reacciona al cambio de estado y muestra el color nuevo.

El ciclo de comunicación completo es esencial para una integración adecuada de LLM. Cuando un LLM realiza una llamada a función, no solo envía la solicitud y continúa. En su lugar, espera a que tu aplicación ejecute la función y muestre los resultados. Luego, el LLM usa estos resultados para formular su respuesta final y crear un flujo de conversación natural que reconozca las acciones realizadas.

Implementa controladores de funciones

Actualicemos tu archivo lib/services/gemini_tools.dart para agregar controladores para las llamadas a función:

lib/services/gemini_tools.dart

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

part 'gemini_tools.g.dart';

class GeminiTools {
 
GeminiTools(this.ref);

 
final Ref ref;

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

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

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

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

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

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

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

Información sobre los controladores de funciones

Veamos qué hacen estos controladores de funciones:

  1. handleFunctionCall: Es un despachador central que hace lo siguiente:
    • Registra la llamada a la función para la transparencia en el panel de registro
    • Enruta al controlador adecuado según el nombre de la función
    • Muestra una respuesta estructurada que se enviará al LLM
  2. handleSetColor: Es el controlador específico de tu función set_color que hace lo siguiente:
    • Extrae valores RGB del mapa de argumentos.
    • Los convierte en los tipos esperados (dobles).
    • Actualiza el estado de color de la aplicación con colorStateNotifier.
    • Crea una respuesta estructurada con el estado de éxito y la información de color actual.
    • Registra los resultados de la función para la depuración
  3. handleUnknownFunction: Es un controlador de resguardo para funciones desconocidas que hace lo siguiente:
    • Registra una advertencia sobre la función no compatible
    • Muestra una respuesta de error al LLM

La función handleSetColor es particularmente importante, ya que cierra la brecha entre la comprensión del lenguaje natural del LLM y los cambios concretos de la IU.

Actualiza el servicio de chat de Gemini para procesar llamadas y respuestas de funciones

Ahora, actualicemos el archivo lib/services/gemini_chat_service.dart para procesar las llamadas a función de las respuestas del LLM y enviar los resultados al LLM:

lib/services/gemini_chat_service.dart

import 'dart:async';

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

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

part 'gemini_chat_service.g.dart';

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

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

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

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

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

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

Comprende el flujo de comunicación

La incorporación clave aquí es el manejo completo de las llamadas y respuestas a la función:

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);
 
}
}

Este código:

  1. Verifica si la respuesta del LLM contiene llamadas a funciones.
  2. Para cada llamada a función, invoca tu método handleFunctionCall con el nombre y los argumentos de la función.
  3. Recopila los resultados de cada llamada a función.
  4. Vuelve a enviar estos resultados al LLM con Content.functionResponses.
  5. Procesa la respuesta del LLM a los resultados de la función
  6. Actualiza la IU con el texto de la respuesta final.

Esto crea un flujo de ida y vuelta:

  • Usuario → LLM: Solicita un color
  • LLM → App: Llamadas a funciones con parámetros
  • App → Usuario: Se muestra un color nuevo
  • App → LLM: Resultados de la función
  • LLM → Usuario: Respuesta final que incorpora los resultados de la función

Genera código de Riverpod

Ejecuta el comando build runner para generar el código de Riverpod necesario:

dart run build_runner build --delete-conflicting-outputs

Ejecuta y prueba el flujo completo

Ahora ejecuta tu aplicación:

flutter run -d DEVICE

Captura de pantalla de la app de Colorist que muestra el LLM de Gemini respondiendo con una llamada a función

Prueba ingresar varias descripciones de colores:

  • “Quiero un rojo carmesí intenso”
  • "Muéstrame un tono azul cielo relajante"
  • "Dame el color de las hojas de menta fresca"
  • "Quiero ver un atardecer naranja cálido"
  • "Haz que sea un púrpura real intenso"

Ahora deberías ver lo siguiente:

  1. Tu mensaje aparece en la interfaz de chat
  2. La respuesta de Gemini que aparece en el chat
  3. Llamadas a funciones que se registran en el panel de registro
  4. Los resultados de la función se registran inmediatamente después
  5. El rectángulo de color se actualiza para mostrar el color descrito
  6. Valores RGB que se actualizan para mostrar los componentes del color nuevo
  7. Aparece la respuesta final de Gemini, que suele hacer comentarios sobre el color que se estableció

El panel de registro proporciona información sobre lo que sucede en segundo plano. En esta página verá lo siguiente:

  • Las llamadas a función exactas que realiza Gemini
  • Los parámetros que elige para cada valor RGB
  • Los resultados que muestra tu función
  • Las respuestas de seguimiento de Gemini

El notificador de estado de color

El colorStateNotifier que usas para actualizar los colores forma parte del paquete colorist_ui. Administra lo siguiente:

  • El color actual que se muestra en la IU
  • El historial de colores (últimos 10 colores)
  • Notificación de cambios de estado en los componentes de la IU

Cuando llamas a updateColor con nuevos valores RGB, sucede lo siguiente:

  1. Crea un nuevo objeto ColorData con los valores proporcionados.
  2. Actualiza el color actual en el estado de la app.
  3. Agrega el color al historial.
  4. Activa las actualizaciones de la IU a través de la administración de estado de Riverpod.

Los componentes de la IU del paquete colorist_ui supervisan este estado y se actualizan automáticamente cuando cambia, lo que crea una experiencia reactiva.

Información sobre el manejo de errores

Tu implementación incluye un manejo de errores sólido:

  1. Bloque try-catch: Une todas las interacciones de LLM para detectar excepciones.
  2. Registro de errores: Registra los errores en el panel de registro con seguimientos de pila.
  3. Comentarios del usuario: Proporciona un mensaje de error amigable en el chat.
  4. Limpieza de estado: Finaliza el estado del mensaje, incluso si se produce un error.

Esto garantiza que la app permanezca estable y proporcione comentarios adecuados, incluso cuando se produzcan problemas con el servicio de LLM o la ejecución de la función.

El poder de las llamadas a función para la experiencia del usuario

Lo que lograste aquí demuestra cómo los LLM pueden crear interfaces naturales potentes:

  1. Interfaz de lenguaje natural: Los usuarios expresan la intención en lenguaje cotidiano.
  2. Interpretación inteligente: El LLM traduce descripciones vagas en valores precisos.
  3. Manipulación directa: La IU se actualiza en respuesta al lenguaje natural.
  4. Respuestas contextuales: El LLM proporciona contexto de conversación sobre los cambios.
  5. Baja carga cognitiva: Los usuarios no necesitan comprender los valores RGB ni la teoría del color.

Este patrón de uso de llamadas a funciones de LLM para unir el lenguaje natural y las acciones de la IU se puede extender a innumerables otros dominios más allá de la selección de colores.

Próximos pasos

En el siguiente paso, implementarás respuestas de transmisión para mejorar la experiencia del usuario. En lugar de esperar la respuesta completa, procesarás los fragmentos de texto y las llamadas a función a medida que se reciban, lo que creará una aplicación más responsiva y atractiva.

Solución de problemas

Problemas con las llamadas a función

Si Gemini no llama a tus funciones o los parámetros son incorrectos, haz lo siguiente:

  • Verifica que la declaración de tu función coincida con lo que se describe en la instrucción del sistema.
  • Verifica que los nombres y los tipos de los parámetros sean coherentes
  • Asegúrate de que la instrucción del sistema le indique explícitamente al LLM que use la herramienta.
  • Verifica que el nombre de la función en tu controlador coincida exactamente con el de la declaración.
  • Examina el panel de registro para obtener información detallada sobre las llamadas a funciones

Problemas con la respuesta de la función

Si los resultados de la función no se envían correctamente al LLM, haz lo siguiente:

  • Verifica que tu función devuelva un mapa con el formato correcto.
  • Verifica que Content.functionResponses se esté construyendo correctamente.
  • Busca errores en el registro relacionados con las respuestas de la función
  • Asegúrate de usar la misma sesión de chat para la respuesta

Problemas con la pantalla a color

Si los colores no se muestran de forma correcta, haz lo siguiente:

  • Asegúrate de que los valores RGB se conviertan correctamente a números dobles (LLM podría enviarlos como números enteros).
  • Verifica que los valores estén dentro del rango esperado (de 0.0 a 1.0).
  • Verifica que se esté llamando correctamente al notificador de estado de color
  • Examina el registro para ver los valores exactos que se pasan a la función.

Problemas generales

Para problemas generales:

  • Examina los registros en busca de errores o advertencias
  • Verifica la conectividad de Vertex AI in Firebase
  • Verifica si hay algún tipo de discrepancia en los parámetros de la función.
  • Asegúrate de que todo el código generado por Riverpod esté actualizado

Conceptos clave aprendidos

  • Implementa una canalización de llamadas a función completa en Flutter
  • Cómo crear una comunicación completa entre un LLM y tu aplicación
  • Procesamiento de datos estructurados a partir de respuestas de LLM
  • Envía los resultados de la función al LLM para que se incorporen a las respuestas.
  • Cómo usar el panel de registro para obtener visibilidad de las interacciones entre la LLM y la aplicación
  • Cómo conectar entradas de lenguaje natural a cambios concretos de la IU

Una vez completado este paso, tu app ahora demuestra uno de los patrones más potentes para la integración de LLM: traducir entradas de lenguaje natural en acciones concretas de la IU, a la vez que se mantiene una conversación coherente que reconoce estas acciones. Esto crea una interfaz intuitiva y conversacional que resulta mágica para los usuarios.

7. Respuestas de transmisión para mejorar la UX

En este paso, implementarás respuestas de transmisión de Gemini para mejorar la experiencia del usuario. En lugar de esperar a que se genere toda la respuesta, procesarás los fragmentos de texto y las llamadas a función a medida que se reciban, lo que creará una aplicación más responsiva y atractiva.

Contenido de este paso

  • La importancia de la transmisión para las aplicaciones con tecnología de LLM
  • Cómo implementar respuestas de LLM de transmisión en una aplicación de Flutter
  • Procesa fragmentos de texto parciales a medida que llegan de la API
  • Cómo administrar el estado de la conversación para evitar conflictos de mensajes
  • Controla las llamadas a función en las respuestas de transmisión continua
  • Cómo crear indicadores visuales para respuestas en curso

Por qué las transmisiones son importantes para las aplicaciones de LLM

Antes de implementarlas, comprendamos por qué las respuestas de transmisión son fundamentales para crear experiencias del usuario excelentes con los LLM:

Experiencia del usuario mejorada

Las respuestas de transmisión proporcionan varios beneficios significativos para la experiencia del usuario:

  1. Latencia percibida reducida: Los usuarios ven que el texto comienza a aparecer de inmediato (por lo general, entre 100 y 300 ms), en lugar de esperar varios segundos para obtener una respuesta completa. Esta percepción de inmediatez mejora notablemente la satisfacción del usuario.
  2. Ritmo de conversación natural: La aparición gradual del texto imita la forma en que los humanos se comunican, lo que crea una experiencia de diálogo más natural.
  3. Procesamiento progresivo de la información: Los usuarios pueden comenzar a procesar la información a medida que llega, en lugar de sentirse abrumados por un gran bloque de texto de una sola vez.
  4. Oportunidad de interrupción anticipada: En una aplicación completa, los usuarios podrían interrumpir o redireccionar el LLM si ven que se dirige en una dirección poco útil.
  5. Confirmación visual de la actividad: El texto en tiempo real proporciona comentarios inmediatos de que el sistema está funcionando, lo que reduce la incertidumbre.

Ventajas técnicas

Además de las mejoras en la UX, la transmisión ofrece beneficios técnicos:

  1. Ejecución de funciones anticipada: Las llamadas a funciones se pueden detectar y ejecutar en cuanto aparecen en el flujo, sin esperar a que se complete la respuesta.
  2. Actualizaciones incrementales de la IU: Puedes actualizar tu IU de forma progresiva a medida que llega información nueva, lo que crea una experiencia más dinámica.
  3. Administración del estado de la conversación: La transmisión proporciona indicadores claros sobre cuándo las respuestas están completas o aún están en curso, lo que permite una mejor administración del estado.
  4. Menos riesgos de tiempo de espera: Con las respuestas sin transmisión, las generaciones de larga duración corren el riesgo de que se agote el tiempo de espera de la conexión. La transmisión establece la conexión con anticipación y la mantiene.

En el caso de tu app de Colorist, implementar la transmisión significa que los usuarios verán las respuestas de texto y los cambios de color más rápidamente, lo que creará una experiencia mucho más responsiva.

Cómo agregar administración de estado de conversación

Primero, agreguemos un proveedor de estado para hacer un seguimiento de si la app está controlando una respuesta de transmisión. Actualiza el archivo lib/services/gemini_chat_service.dart:

lib/services/gemini_chat_service.dart

import 'dart:async';

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

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

part 'gemini_chat_service.g.dart';

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

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

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

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

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

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

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

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

Información sobre la implementación de la transmisión

Veamos qué hace este código:

  1. Seguimiento del estado de la conversación:
    • Un conversationStateProvider realiza un seguimiento de si la app está procesando una respuesta en ese momento.
    • El estado pasa de idlebusy durante el procesamiento y, luego, vuelve a idle.
    • Esto evita que se realicen varias solicitudes simultáneas que podrían generar conflictos.
  2. Inicialización de la transmisión:
    • sendMessageStream() muestra un flujo de fragmentos de respuesta en lugar de un Future con la respuesta completa.
    • Cada fragmento puede contener texto, llamadas a funciones o ambos.
  3. Procesamiento progresivo:
    • await for procesa cada fragmento a medida que llega en tiempo real.
    • El texto se agrega a la IU de inmediato, lo que crea el efecto de transmisión.
    • Las llamadas a función se ejecutan en cuanto se detectan.
  4. Manejo de llamadas a función:
    • Cuando se detecta una llamada a función en un fragmento, se ejecuta de inmediato.
    • Los resultados se vuelven a enviar al LLM a través de otra llamada de transmisión.
    • La respuesta del LLM a estos resultados también se procesa de forma continua.
  5. Manejo y limpieza de errores:
    • try/catch proporciona un manejo de errores sólido.
    • El bloque finally garantiza que el estado de la conversación se restablezca correctamente.
    • El mensaje siempre se finaliza, incluso si se producen errores.

Esta implementación crea una experiencia de transmisión responsiva y confiable, a la vez que mantiene el estado de conversación adecuado.

Actualiza la pantalla principal para conectar el estado de la conversación

Modifica tu archivo lib/main.dart para pasar el estado de la conversación a la pantalla principal:

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),
     
),
   
);
 
}
}

El cambio clave aquí es pasar el conversationState al widget MainScreen. MainScreen (proporcionado por el paquete colorist_ui) usará este estado para inhabilitar la entrada de texto mientras se procesa una respuesta.

Esto crea una experiencia del usuario cohesiva en la que la IU refleja el estado actual de la conversación.

Genera código de Riverpod

Ejecuta el comando build runner para generar el código de Riverpod necesario:

dart run build_runner build --delete-conflicting-outputs

Ejecuta y prueba respuestas de transmisión continua

Ejecuta tu aplicación:

flutter run -d DEVICE

Captura de pantalla de la app de Colorist en la que se muestra que el LLM de Gemini responde de forma continua

Ahora, prueba el comportamiento de transmisión con varias descripciones de colores. Prueba descripciones como las siguientes:

  • "Muéstrame el color verde azulado intenso del océano al atardecer"
  • “Me gustaría ver un color coral intenso que me recuerde a las flores tropicales”.
  • "Crea un verde oliva apagado como los uniformes del ejército"

Flujo técnico de transmisión en detalle

Veamos exactamente qué sucede cuando se transmite una respuesta:

Establecimiento de la conexión

Cuando llamas a sendMessageStream(), ocurre lo siguiente:

  1. La app establece una conexión con el servicio de Vertex AI.
  2. La solicitud del usuario se envía al servicio
  3. El servidor comienza a procesar la solicitud.
  4. La conexión de transmisión permanece abierta y lista para transmitir fragmentos.

Transmisión de fragmentos

A medida que Gemini genera contenido, los fragmentos se envían a través de la transmisión:

  1. El servidor envía fragmentos de texto a medida que se generan (por lo general, algunas palabras o oraciones).
  2. Cuando Gemini decide realizar una llamada a función, envía la información de la llamada a función.
  3. Es posible que se incluyan fragmentos de texto adicionales después de las llamadas a función.
  4. La transmisión continúa hasta que se completa la generación.

Procesamiento progresivo

Tu app procesa cada fragmento de forma incremental:

  1. Cada fragmento de texto se agrega a la respuesta existente.
  2. Las llamadas a función se ejecutan en cuanto se detectan.
  3. La IU se actualiza en tiempo real con resultados de texto y función
  4. Se realiza un seguimiento del estado para mostrar que la respuesta aún se está transmitiendo.

Finalización de la transmisión

Cuando se complete la generación, ocurrirá lo siguiente:

  1. El servidor cierra la transmisión
  2. El bucle await for se cierra de forma natural
  3. El mensaje se marca como completo
  4. El estado de la conversación vuelve a ser inactivo.
  5. La IU se actualiza para reflejar el estado completado.

Comparación entre transmisión y no transmisión

Para comprender mejor los beneficios de la transmisión, comparemos los enfoques de transmisión y no transmisión:

Aspecto

Sin transmisión

Transmisión

Latencia percibida

El usuario no ve nada hasta que la respuesta completa está lista

El usuario ve las primeras palabras en milisegundos

Experiencia del usuario

Espera prolongada seguida de la aparición repentina de texto

Apariencia de texto natural y progresiva

Administración de estados

Más simple (los mensajes están pendientes o completos)

Más complejos (los mensajes pueden estar en un estado de transmisión)

Ejecución de funciones

Ocurre solo después de que se completa la respuesta.

Ocurre durante la generación de respuestas.

Complejidad de implementación

Es más fácil de implementar.

Requiere administración de estado adicional

Recuperación de errores

Respuesta del tipo todo o nada

Las respuestas parciales pueden ser útiles

Complejidad del código

Menos compleja

Es más complejo debido al manejo de transmisiones.

En el caso de una aplicación como Colorist, los beneficios de la transmisión de UX superan la complejidad de la implementación, en especial para las interpretaciones de colores que pueden tardar varios segundos en generarse.

Prácticas recomendadas para la UX de transmisión

Cuando implementes la transmisión en tus propias aplicaciones de LLM, ten en cuenta estas prácticas recomendadas:

  1. Indicadores visuales claros: Siempre proporciona indicadores visuales claros que distingan los mensajes en tiempo real de los mensajes completos.
  2. Bloqueo de entradas: Inhabilita la entrada del usuario durante la transmisión para evitar varias solicitudes superpuestas.
  3. Recuperación de errores: Diseña tu IU para que controle la recuperación elegante si se interrumpe la transmisión.
  4. Transiciones de estado: Asegúrate de que las transiciones entre los estados inactivo, de transmisión y completo sean fluidas.
  5. Visualización del progreso: Considera usar animaciones o indicadores sutiles que muestren el procesamiento activo.
  6. Opciones de cancelación: En una app completa, proporciona formas para que los usuarios cancelen generaciones en curso.
  7. Integración de resultados de funciones: Diseña tu IU para controlar los resultados de las funciones que aparecen en medio de la transmisión.
  8. Optimización del rendimiento: Minimiza las recomposiciones de la IU durante las actualizaciones rápidas de transmisiones.

El paquete colorist_ui implementa muchas de estas prácticas recomendadas por ti, pero son consideraciones importantes para cualquier implementación de LLM de transmisión.

Próximos pasos

En el siguiente paso, implementarás la sincronización de LLM notificando a Gemini cuando los usuarios seleccionen colores del historial. Esto creará una experiencia más cohesiva en la que el LLM esté al tanto de los cambios que inicia el usuario en el estado de la aplicación.

Solución de problemas

Problemas de procesamiento de transmisiones

Si tienes problemas con el procesamiento de transmisiones, haz lo siguiente:

  • Síntomas: Respuestas parciales, falta de texto o finalización abrupta de la transmisión
  • Solución: Comprueba la conectividad de red y asegúrate de que los patrones de async/await sean correctos en tu código.
  • Diagnóstico: Examina el panel de registro en busca de mensajes de error o advertencias relacionados con el procesamiento de transmisiones.
  • Solución: Asegúrate de que todo el procesamiento de transmisiones use el manejo de errores adecuado con bloques try/catch.

Llamadas a la función faltantes

Si no se detectan llamadas a funciones en la transmisión, haz lo siguiente:

  • Síntomas: Aparece el texto, pero los colores no se actualizan, o el registro no muestra llamadas a función.
  • Solución: Verifica las instrucciones del mensaje del sistema sobre el uso de llamadas a función.
  • Diagnóstico: Verifica el panel de registro para ver si se reciben llamadas a la función.
  • Solución: Ajusta el mensaje del sistema para indicarle al LLM de forma más explícita que use la herramienta set_color.

Manejo general de errores

Si tienes algún otro problema, haz lo siguiente:

  • Paso 1: Comprueba el panel de registro en busca de mensajes de error
  • Paso 2: Verifica la conectividad de Vertex AI en Firebase
  • Paso 3: Asegúrate de que todo el código generado por Riverpod esté actualizado
  • Paso 4: Revisa la implementación de transmisión para detectar si faltan instrucciones await

Conceptos clave aprendidos

  • Implementa respuestas de transmisión con la API de Gemini para obtener una UX más responsiva
  • Cómo administrar el estado de la conversación para controlar correctamente las interacciones de transmisión
  • Procesa las llamadas de texto y función en tiempo real a medida que llegan
  • Cómo crear IUs responsivas que se actualizan de forma incremental durante la transmisión
  • Cómo controlar transmisiones simultáneas con patrones asíncronos adecuados
  • Proporcionar comentarios visuales adecuados durante las respuestas de transmisión

Cuando implementaste la transmisión, mejoraste significativamente la experiencia del usuario de tu app de Colorist, ya que creaste una interfaz más responsiva y atractiva que se siente realmente conversacional.

8. Sincronización de contexto de LLM

En este paso adicional, implementarás la sincronización de contexto de LLM notificando a Gemini cuando los usuarios seleccionen colores del historial. Esto crea una experiencia más cohesiva en la que el LLM está al tanto de las acciones del usuario en la interfaz, no solo de sus mensajes explícitos.

Contenido de este paso

  • Cómo crear una sincronización de contexto de LLM entre tu IU y el LLM
  • Serializa los eventos de la IU en el contexto que el LLM puede entender.
  • Cómo actualizar el contexto de la conversación según las acciones del usuario
  • Crea una experiencia coherente en diferentes métodos de interacción
  • Mejora la conciencia del contexto del LLM más allá de los mensajes de chat explícitos

Información sobre la sincronización de contexto de LLM

Los chatbots tradicionales solo responden a mensajes explícitos de los usuarios, lo que genera una desconexión cuando los usuarios interactúan con la app a través de otros medios. La sincronización de contexto de LLM aborda esta limitación:

Por qué es importante la sincronización de contexto de LLM

Cuando los usuarios interactúan con tu app a través de elementos de la IU (como seleccionar un color del historial), el LLM no tiene forma de saber qué sucedió, a menos que se lo indiques de forma explícita. Sincronización de contexto de LLM:

  1. Mantiene el contexto: Mantiene al LLM informado sobre todas las acciones relevantes del usuario.
  2. Crea coherencia: Produce una experiencia cohesiva en la que el LLM reconoce las interacciones de la IU.
  3. Mejora la inteligencia: Permite que el LLM responda de manera adecuada a todas las acciones del usuario.
  4. Mejora la experiencia del usuario: Hace que toda la aplicación se sienta más integrada y responsiva.
  5. Reduce el esfuerzo del usuario: Elimina la necesidad de que los usuarios expliquen manualmente sus acciones en la IU.

En tu app de Colorist, cuando un usuario selecciona un color del historial, quieres que Gemini reconozca esta acción y haga comentarios inteligentes sobre el color seleccionado, manteniendo la ilusión de un asistente fluido y consciente.

Actualiza el servicio de chat de Gemini para recibir notificaciones de selección de color

Primero, agregarás un método a GeminiChatService para notificar al LLM cuando un usuario seleccione un color del historial. Actualiza el archivo lib/services/gemini_chat_service.dart:

lib/services/gemini_chat_service.dart

import 'dart:async';
import 'dart:convert';                                               // Add this import

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

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

part 'gemini_chat_service.g.dart';

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

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

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

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

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

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

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

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

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

La incorporación clave es el método notifyColorSelection, que hace lo siguiente:

  1. Toma un objeto ColorData que representa el color seleccionado.
  2. Lo codifica en un formato JSON que se puede incluir en un mensaje.
  3. Envía un mensaje con formato especial al LLM que indica una selección del usuario.
  4. Vuelve a usar el método sendMessage existente para controlar la notificación.

Este enfoque evita la duplicación, ya que utiliza tu infraestructura existente de manejo de mensajes.

Actualiza la app principal para conectar las notificaciones de selección de color

Ahora, modifica tu archivo lib/main.dart para pasar la función de notificación de selección de color a la pantalla principal:

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),
     
),
   
);
 
}
}

El cambio clave es agregar la devolución de llamada notifyColorSelection, que conecta el evento de la IU (seleccionar un color del historial) al sistema de notificaciones de LLM.

Actualiza el mensaje del sistema

Ahora, debes actualizar la instrucción del sistema para indicarle al LLM cómo responder a las notificaciones de selección de color. Modifica tu archivo assets/system_prompt.md:

assets/system_prompt.md

# Colorist System Prompt

You are a color expert assistant integrated into a desktop app called Colorist. Your job is to interpret natural language color descriptions and set the appropriate color values using a specialized tool.

## Your Capabilities

You are knowledgeable about colors, color theory, and how to translate natural language descriptions into specific RGB values. You have access to the following tool:

`set_color` - Sets the RGB values for the color display based on a description

## How to Respond to User Inputs

When users describe a color:

1. First, acknowledge their color description with a brief, friendly response
2. Interpret what RGB values would best represent that color description
3. Use the `set_color` tool to set those values (all values should be between 0.0 and 1.0)
4. After setting the color, provide a brief explanation of your interpretation

Example:
User: "I want a sunset orange"
You: "Sunset orange is a warm, vibrant color that captures the golden-red hues of the setting sun. It combines a strong red component with moderate orange tones."

[Then you would call the set_color tool with approximately: red=1.0, green=0.5, blue=0.25]

After the tool call: "I've set a warm orange with strong red, moderate green, and minimal blue components that is reminiscent of the sun low on the horizon."

## When Descriptions are Unclear

If a color description is ambiguous or unclear, please ask the user clarifying questions, one at a time.

## When Users Select Historical Colors

Sometimes, the user will manually select a color from the history panel. When this happens, you'll receive a notification about this selection that includes details about the color. Acknowledge this selection with a brief response that recognizes what they've done and comments on the selected color.

Example notification:
User: "User selected color from history: {red: 0.2, green: 0.5, blue: 0.8, hexCode: #3380CC}"
You: "I see you've selected an ocean blue from your history. This tranquil blue with a moderate intensity has a calming, professional quality to it. Would you like to explore similar shades or create a contrasting color?"

## Important Guidelines

- Always keep RGB values between 0.0 and 1.0
- Provide thoughtful, knowledgeable responses about colors
- When possible, include color psychology, associations, or interesting facts about colors
- Be conversational and engaging in your responses
- Focus on being helpful and accurate with your color interpretations

La incorporación clave es la sección "Cuando los usuarios seleccionan colores históricos", que hace lo siguiente:

  1. Explica el concepto de notificaciones de selección de historial al LLM
  2. Proporciona un ejemplo de cómo se ven estas notificaciones.
  3. Muestra un ejemplo de una respuesta adecuada.
  4. Establece expectativas para confirmar la selección y comentar sobre el color.

Esto ayuda al LLM a comprender cómo responder de forma adecuada a estos mensajes especiales.

Genera código de Riverpod

Ejecuta el comando build runner para generar el código de Riverpod necesario:

dart run build_runner build --delete-conflicting-outputs

Ejecuta y prueba la sincronización de contexto de LLM

Ejecuta tu aplicación:

flutter run -d DEVICE

Captura de pantalla de la app de Colorist que muestra el LLM de Gemini respondiendo a una selección del historial de colores

Probar la sincronización de contexto de LLM implica lo siguiente:

  1. Primero, genera algunos colores describiéndolos en el chat.
    • "Muéstrame un morado intenso"
    • “Quiero un verde bosque”
    • "Dame un rojo brillante"
  2. Luego, haz clic en una de las miniaturas de color de la barra de historial.

Debes observar lo siguiente:

  1. El color seleccionado aparece en la pantalla principal.
  2. Aparece un mensaje del usuario en el chat que indica la selección de color.
  3. El LLM responde confirmando la selección y comentando sobre el color.
  4. Toda la interacción se siente natural y cohesiva.

Esto crea una experiencia fluida en la que el LLM detecta y responde de forma adecuada tanto a los mensajes directos como a las interacciones de la IU.

Cómo funciona la sincronización de contexto de LLM

Exploremos los detalles técnicos de cómo funciona esta sincronización:

Flujo de datos

  1. Acción del usuario: El usuario hace clic en un color de la barra de historial.
  2. Evento de IU: El widget MainScreen detecta esta selección.
  3. Ejecución de devolución de llamada: Se activa la devolución de llamada notifyColorSelection.
  4. Creación de mensajes: Se crea un mensaje con formato especial con los datos de color.
  5. Proceso de LLM: El mensaje se envía a Gemini, que reconoce el formato.
  6. Respuesta contextual: Gemini responde de forma adecuada según la instrucción del sistema.
  7. Actualización de la IU: La respuesta aparece en el chat, lo que crea una experiencia cohesiva.

Serialización de datos

Un aspecto clave de este enfoque es la forma en que serializas los datos de color:

'User selected color from history: ${json.encode(color.toLLMContextMap())}'

El método toLLMContextMap() (proporcionado por el paquete colorist_ui) convierte un objeto ColorData en un mapa con propiedades clave que el LLM puede entender. Esto suele incluir lo siguiente:

  • Valores RGB (rojo, verde y azul)
  • Representación del código hexadecimal
  • Cualquier nombre o descripción asociados con el color

Si aplicas el formato a estos datos de forma coherente y los incluyes en el mensaje, te aseguras de que el LLM tenga toda la información que necesita para responder de forma adecuada.

Aplicaciones más amplias de la sincronización de contexto de LLM

Este patrón de notificación al LLM sobre eventos de IU tiene varias aplicaciones más allá de la selección de colores:

Otros casos prácticos

  1. Cambios de filtro: Notifica al LLM cuando los usuarios aplican filtros a los datos.
  2. Eventos de navegación: Informan al LLM cuando los usuarios navegan a diferentes secciones.
  3. Cambios en la selección: Actualiza el LLM cuando los usuarios seleccionen elementos de listas o cuadrículas.
  4. Actualizaciones de preferencias: Informa al LLM cuando los usuarios cambien la configuración o las preferencias.
  5. Manipulación de datos: Notifica al LLM cuando los usuarios agreguen, editen o borren datos.

En cada caso, el patrón sigue siendo el mismo:

  1. Detecta el evento de la IU
  2. Serializa los datos relevantes
  3. Envía una notificación con formato especial al LLM
  4. Guía al LLM para que responda de manera apropiada a través de la instrucción del sistema

Prácticas recomendadas para la sincronización de contexto de LLM

En función de tu implementación, estas son algunas prácticas recomendadas para lograr una sincronización de contexto de LLM eficaz:

1. Formato coherente

Usa un formato coherente para las notificaciones para que el LLM pueda identificarlas fácilmente:

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

2. Contexto enriquecido

Incluye suficientes detalles en las notificaciones para que el LLM responda de forma inteligente. En el caso de los colores, esto significa valores RGB, códigos hexadecimales y cualquier otra propiedad relevante.

3. Instrucciones claras

Proporciona instrucciones explícitas en la instrucción del sistema sobre cómo controlar las notificaciones, idealmente con ejemplos.

4. Integración natural

Diseña las notificaciones para que fluyan de forma natural en la conversación, no como interrupciones técnicas.

5. Notificación selectiva

Solo notifica al LLM sobre las acciones que son relevantes para la conversación. No es necesario comunicar todos los eventos de la IU.

Solución de problemas

Problemas con notificaciones

Si el LLM no responde correctamente a las selecciones de color, haz lo siguiente:

  • Verifica que el formato del mensaje de notificación coincida con lo que se describe en el mensaje del sistema
  • Verifica que los datos de color se estén serializando correctamente
  • Asegúrate de que el mensaje del sistema tenga instrucciones claras para controlar las selecciones.
  • Busca errores en el servicio de chat cuando envíes notificaciones

Administración del contexto

Si el LLM parece perder el contexto, haz lo siguiente:

  • Verifica que la sesión de chat se mantenga correctamente
  • Verifica que los estados de conversación se transfieran correctamente
  • Asegúrate de que las notificaciones se envíen a través de la misma sesión de chat.

Problemas generales

Para problemas generales:

  • Examina los registros en busca de errores o advertencias
  • Verifica la conectividad de Vertex AI in Firebase
  • Verifica si hay algún tipo de discrepancia en los parámetros de la función.
  • Asegúrate de que todo el código generado por Riverpod esté actualizado

Conceptos clave aprendidos

  • Cómo crear una sincronización de contexto de LLM entre la IU y el LLM
  • Serialización de eventos de la IU en un contexto compatible con LLM
  • Cómo guiar el comportamiento del LLM para diferentes patrones de interacción
  • Crea una experiencia cohesiva en las interacciones con y sin mensajes
  • Mejora la conciencia del LLM sobre el estado más amplio de la aplicación

Cuando implementas la sincronización de contexto de LLM, creas una experiencia verdaderamente integrada en la que el LLM se siente como un asistente consciente y responsivo, en lugar de solo un generador de texto. Este patrón se puede aplicar a innumerables otras aplicaciones para crear interfaces más naturales e intuitivas potenciadas por IA.

9. ¡Felicitaciones!

Completaste correctamente el codelab de Colorist. 🎉

Qué compilaste

Creaste una aplicación de Flutter completamente funcional que integra la API de Gemini de Google para interpretar descripciones de colores en lenguaje natural. Ahora, tu app puede hacer lo siguiente:

  • Procesa descripciones de lenguaje natural, como "naranja atardecer" o "azul océano profundo".
  • Usa Gemini para traducir de forma inteligente estas descripciones en valores RGB
  • Cómo mostrar los colores interpretados en tiempo real con respuestas de transmisión
  • Controla las interacciones del usuario a través de elementos de chat y de la IU
  • Mantén la información contextual en diferentes métodos de interacción

Lo que vendrá

Ahora que dominaste los conceptos básicos de la integración de Gemini con Flutter, estas son algunas formas de continuar tu recorrido:

Mejora tu app de Colorist

  • Paleta de colores: Agrega funcionalidad para generar esquemas de colores complementarios o a juego.
  • Entrada de voz: Integra el reconocimiento de voz para descripciones de colores verbales.
  • Administración de historial: Agrega opciones para nombrar, organizar y exportar conjuntos de colores.
  • Mensajes personalizados: Crea una interfaz para que los usuarios personalicen los mensajes del sistema.
  • Análisis avanzados: Realiza un seguimiento de qué descripciones funcionan mejor o causan dificultades.

Explora más funciones de Gemini

  • Entradas multimodales: Agrega entradas de imagen para extraer colores de las fotos.
  • Generación de contenido: Usa Gemini para generar contenido relacionado con los colores, como descripciones o historias.
  • Mejoras en las llamadas a función: Crea integraciones de herramientas más complejas con varias funciones.
  • Configuración de seguridad: Explora los diferentes parámetros de configuración de seguridad y su impacto en las respuestas.

Aplica estos patrones a otros dominios

  • Análisis de documentos: Crea apps que puedan comprender y analizar documentos.
  • Asistencia para la escritura creativa: Compila herramientas de escritura con sugerencias potenciadas por LLM
  • Automatización de tareas: Diseña apps que traduzcan el lenguaje natural en tareas automatizadas.
  • Aplicaciones basadas en el conocimiento: Crea sistemas expertos en dominios específicos.

Recursos

Estos son algunos recursos valiosos para continuar tu aprendizaje:

Documentación oficial

Curso y guía de instrucciones

Comunidad

Serie de agentes de Flutter observables

En el episodio 59, Craig Labenz y Andrew Brogden exploran este codelab y destacan partes interesantes de la compilación de la app.

En el episodio 60, vuelve a unirte a Craig y Andrew mientras extienden la app del codelab con nuevas funciones y luchan por hacer que los LLM hagan lo que se les dice.

En el episodio 61, Chris Sells se une a Craig para analizar los títulos de las noticias y generar las imágenes correspondientes.

Comentarios

Nos encantaría conocer tu experiencia con este codelab. Considera enviarnos tus comentarios a través de las siguientes opciones:

Gracias por completar este codelab. Esperamos que sigas explorando las emocionantes posibilidades en la intersección de Flutter y la IA.