Solution Boussole

Créez une application de planification de voyages basée sur des agents avec l'IA générative

Le schéma suivant présente dans les grandes lignes comment combiner les meilleurs services de développement Google pour créer des applications basées sur l'IA.

Créez des applications attrayantes optimisées par l'IA pour le Web et le mobile

Vous pouvez utiliser Flutter et Firebase Genkit pour créer des applications multiplates-formes qui s'intègrent parfaitement à l'IA.
Vous pouvez utiliser Genkit pour permettre à votre application de consommer en toute confiance les données des LLM en définissant un schéma capable de valider la sortie du LLM. Dans Flutter, vous pouvez utiliser Dart pour sérialiser la requête et désérialiser la réponse afin qu'elle corresponde au schéma Genkit à l'aide de requêtes HTTP standards.
Avec IDX, vous pouvez créer une application Flutter sans avoir à installer de logiciel. Cela vous permet de développer et de tester votre application Android et votre application Web dans le navigateur.
Firebase Data Connect est un service de base de données relationnelle pour applications mobiles et Web. Il vous permet de créer des applications et d'effectuer leur scaling à l'aide d'une base de données PostgreSQL entièrement gérée et optimisée par Cloud SQL. Il fournit un schéma sécurisé, ainsi qu'une gestion des requêtes et des mutations à l'aide de GraphQL, qui s'intègre bien à Firebase Authentication. Data Connect est compatible avec le SDK avec Kotlin pour Android et le Web.

Créer un agent avec Firebase Genkit

L'agent utilise un framework d'orchestration d'IA pour recevoir les entrées utilisateur et générer une réponse.
---
model: googleai/gemini-1.5-flash-latest
config:
  temperature: 1.0
  safetySettings:
    - category: HARM_CATEGORY_HATE_SPEECH
      threshold: BLOCK_LOW_AND_ABOVE
    - category: HARM_CATEGORY_DANGEROUS_CONTENT
      threshold: BLOCK_ONLY_HIGH
    - category: HARM_CATEGORY_HARASSMENT
      threshold: BLOCK_LOW_AND_ABOVE
    - category: HARM_CATEGORY_SEXUALLY_EXPLICIT
      threshold: BLOCK_LOW_AND_ABOVE
input:
  schema:
    request: string, The users request for where they want to travel to.
    place: string, The place that closely represents the users request.
    placeDescription: string, A description of that place.
    activities(array, a stringify list of activities that can be found at the specified place): string
    restaurants?(array, a stringify list of all the restaurants found at that location): string
output:
  schema:
    place: string, The place the user is traveling to.
    itineraryName: string, a catchy itinerary name that encapsulates the spirit of the trip and includes the place name
    startDate: string, the start date of the trip
    endDate: string, the end date of the trip
    tags(array, relevant tags for the trip): string
    itinerary(array):
      day: number
      date: string
      planForDay(array):
        activityRef: string, the reference value for the activity - this comes from the available activities JSON. If no value is present use a ref value of restaurant.
        activityTitle: string, a catchy title for the activity
        activityDesc: string, a six word description of the activity
        photoUri?: string, set the photo uri value for restaurants only.
        googleMapsUri?: string, if this is a restaurant include the googleMapsUri
---

Generate an itinerary for a tourist planning on traveling to the location specified based in their request.
If there is something that does not exist within the list of activities, do not include it in your answer.
Feel free to relate the activitiy to the request in a meaningful way.
In the plan for day array, put activities as a travel brouchure might do.
Come up with a catchy name for the itinerary.

Pick three activities per day, minimum of three day trip unless otherwise specified in the request.

Output schema should not include the properties type or object.

Pick a date after 2024-05-14 but before 2024-12-31.

The output date must be in the format year-month-day.

Give each activity a unique title and description.

Limit activity descriptions to 6 words.

If no restaurants are supplied, do not recommend any restaurants to eat at.

{{#if restaurants}}
Find a restaurant to eat at each day.

Include a restaurant to visit in the itinerary for each day from the available restaurants.
The restaurant should be the only activity with a photoUri.
The photoUri for the restaurant should be from the photoUri property from the restaurant array.
If there are no restaurants to pick from, do not include it in the list.

The photoUri from the restaurantFinder should be in the format of places/${placeId}/photos/${photoId}

Each restaurant should be unique to the overall itinerary.
Each restaurant must contain a photoUri in their output JSON schema.
Each restaurant must also include  an activitiyRef, activityTitle, and activityDesc in their output
{{/if}}
Output must be in JSON format.

REQUEST : {{request}}
PLACE : {{place}}
PLACE DESCRIPTION : {{placeDescription}}
AVAILABLE ACTIVITIES : {{activities}}
RESTAURANTS : {{restaurants}}
Lorsque vous travaillez avec l'IA générative, il est important de créer des requêtes efficaces pour que le modèle renvoie des réponses de haute qualité. Firebase Genkit fournit le plug-in Dotprompt et le format de texte pour vous aider à rédiger et à organiser vos requêtes d'IA générative. Le format encapsule la requête, le schéma d'entrée et de sortie, le modèle et la configuration dans un seul fichier.

L'exemple de code suivant présente un fichier Dotprompt utilisé dans l'application de voyage. Le schéma est basé sur les informations fournies par l'utilisateur lorsqu'il décrit le voyage de rêve.
Dotprompt est conçu autour du principe que les requêtes sont du code. Vous écrivez et gérez vos requêtes dans des fichiers à la mise en forme spéciale appelés fichiers "dotprompt", suivez leurs modifications à l'aide du même système de contrôle des versions que vous utilisez pour votre code, et vous les déployez avec le code qui appelle vos modèles d'IA générative.
Les flux sont des fonctions fortement typées, pouvant être diffusées en streaming, appelées en local et à distance, et entièrement observables. Firebase Genkit fournit une interface de ligne de commande et une interface utilisateur pour les développeurs qui permet d'utiliser des flux, comme l'exécution ou le débogage de flux.
import {defineTool} from '@genkit-ai/ai/tool';
...
{
  name: 'restaurantFinder',
  description: `Used when needing to find a restaurant based on a users location.
  The location should be used to find nearby restaurants to a place. You can also
  selectively find restaurants based on the users preferences, but you should default
  to 'Local' if there are no indications of restaurant types in the users request.
  `,
  inputSchema: z.object({
    place: z.string(),
    typeOfRestaurant: z.string().optional() }),
    outputSchema: z.unknown(),
},
...
async (input) => {
  if (input.typeOfRestaurant == undefined) {
    input.typeOfRestaurant = "Local";
  }
  const geocodeEndpoint = "https://places.googleapis.com/v1/places:searchText";
  const textQuery = {textQuery: `${input.typeOfRestaurant} restaurants in ${input.place}`};

  const  response = await axios.post(
    geocodeEndpoint,
    JSON.stringify(textQuery),
    {
      headers: {
        "Content-Type": "application/json",
        "X-Goog-Api-Key": MAPS_API_KEY,
        "X-Goog-FieldMask": "places.displayName,places.formattedAddress,places.priceLevel,places.photos.name,places.editorialSummary,places.googleMapsUri"
      }
    }
  );
  console.log(response.data);
  let data = (response.data as PlaceResponse);
  for(let i = 0; i < data.places.length; i++) {
    if (data.places[i].photos) {
      data.places[i].photos = [data.places[i].photos[0]];
    }
  }
  return data as PlaceResponse;
}
Vous pouvez utiliser l'appel de fonction dans Genkit pour étendre les fonctionnalités de l'agent afin qu'il puisse affiner les réponses et effectuer des tâches supplémentaires. L'application de voyage définit un outil qui peut renvoyer des informations sur les restaurants à partir de l'API Places en fonction du trajet souhaité par l'utilisateur. Le code utilise Zod pour définir le schéma d'entrée et de sortie afin que les résultats de la requête puissent être validés.
...
export const textRefinement = defineFlow(
{
  name: 'textRefinement',
  inputSchema: z.string(),
  outputSchema: z.unknown(),
},
async (userRequest) => {
  const refinementPrompt = await prompt('textRefinement')
  const result = await refinementPrompt.generate({
      input: {
          request: userRequest
      },
  });
  return result.output();
});
Pour offrir aux utilisateurs une expérience de recherche plus personnalisée, une fois que l'utilisateur a décrit le voyage de ses rêves, Gemini détermine si des informations supplémentaires sont nécessaires en fonction des invites fournies par l'application de voyage, puis indique à l'application s'il estime que des informations supplémentaires sont nécessaires. L'application invite ensuite l'utilisateur à fournir ces informations et les ajoute à la requête sur le backend.
import 'package:http:http.dart' as http;
...
Future<List<Trip>> generateTrips(String description, List<Image> images) async {
  final uri = Uri.parse('.../generateTrips');
  final request = http.MultipartRequest('POST', uri);
  request.fields['description'] = description;
  request.files.add(http.MultipartFile.fromData(
      images.name, images.bytes,
      contentType: MediaType('image', 'png'),
  ));
  var response = await request.send();
  if (response.statusCode == 200) {
      final body = await response.body.text();
      final items = jsonDecode(body) as List<dynamic>;
      return items.map(Trip.fromJson).toList();
  }
  ...
  import { imagen2, geminiProVision } from '@genkit-ai/vertexai';
  import { generate } from '@genkit-ai/ai';

  const imageResult = await generate({
    model: imagen2,
    prompt: 'Generate an image of a very specific historical time and place.',
  });
  const generatedImage = imageResult.media();

  const descriptionResult = await generate({
    model: geminiProVision,
    prompt: [
      {
        text: 'What is the historical time and place represented in this picture?',
      },
      { media: generatedImage },
    ],
  });
  console.log(descriptionResult.text());
  }
L'application de voyage demande à l'utilisateur de définir son voyage de rêve en utilisant la saisie de texte ou en appuyant sur le bouton du micro pour activer la reconnaissance vocale. L'utilisateur peut également importer des images.

L'application exploite un package Dart de pub.dev pour s'intégrer aux fonctionnalités de reconnaissance vocale natives de chaque plate-forme. Elle utilise également l'API Gemini dans Firebase Genkit pour gérer les entrées multimodales telles que les images ou les vidéos. L'API Gemini utilise la génération augmentée de récupération (RAG) pour renvoyer un ensemble de suggestions de trajets à l'aide de Firebase Data Connect et de représentations vectorielles continues pour effectuer une recherche des voisins les plus proches.

Faire évoluer votre application pour la production

Firebase Hosting s'intègre aux frameworks Web modernes les plus courants, y compris Flutter. En utilisant Firebase Hosting et Cloud Functions for Firebase avec ces frameworks, vous pouvez développer des applications et des microservices dans l'environnement de framework de votre choix, puis les déployer dans un environnement de serveur géré et sécurisé. Avant de passer en production, vous devez comprendre la sécurité et les performances de tous les services de votre application. Pour en savoir plus, consultez la checklist avant lancement de Firebase.
Cette application de voyage utilise l'IA de Google pour itérer rapidement les données de test. Elle est idéale pour les applications qui utilisent peu l'IA et qui n'ont pas besoin d'évoluer. Vertex AI dispose de quotas plus élevés pour faciliter le scaling des applications de production et de règles de confidentialité renforcées pour protéger les données utilisateur. Genkit dispose d'une fonctionnalité intégrée permettant de changer facilement de modèle. Vous n'avez donc pas besoin de réécrire vos requêtes ou vos appels d'API.