Solução

Crie um app de planejamento de viagens com tecnologia de agente usando a IA generativa

O diagrama a seguir mostra uma visão geral de como você pode combinar os melhores serviços do Google para desenvolvedores para criar aplicativos baseados em IA.

Crie apps bonitos com tecnologia de IA para dispositivos móveis e Web

É possível usar o Flutter e o Firebase Genkit para criar apps multiplataforma com integração total à IA.
É possível usar o Genkit para permitir que seu app consuma dados de LLMs com confiança, definindo um esquema que pode validar a saída do LLM. No Flutter, é possível usar o Dart para serializar a solicitação e desserializar a resposta para corresponder ao esquema Genkit usando solicitações HTTP padrão.
Com o IDX, você pode criar um app Flutter sem precisar instalar nenhum software. Isso possibilita desenvolver e testar seu app Android e da Web no navegador.
O Firebase Data Connect é um serviço de banco de dados relacional para apps da Web e de dispositivos móveis que permite criar e escalonar usando um banco de dados PostgreSQL totalmente gerenciado com tecnologia do Cloud SQL. Ele fornece um esquema seguro e gerenciamento de consultas e mutações usando o GraphQL, que tem uma boa integração com o Firebase Authentication. O Data Connect inclui suporte ao SDK para Kotlin para Android e Web.

Criar um agente com o Firebase Genkit

O agente usa um framework de orquestração de IA para receber entradas do usuário e gerar uma resposta.
---
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}}
Ao trabalhar com IA generativa, é importante criar comandos eficazes para que o modelo retorne respostas de alta qualidade. O Firebase Genkit fornece o plug-in Dotprompt e o formato de texto para ajudar você a escrever e organizar seus comandos de IA generativa. O formato encapsula o comando, o esquema de entrada e saída, o modelo e a configuração em um único arquivo.

O exemplo de código a seguir mostra um arquivo Dotprompt usado no app de viagens. O esquema é baseado nas informações que o usuário fornece ao descrever a viagem dos sonhos.
O Dotprompt foi desenvolvido com base na premissa de que comandos são código. Você escreve e mantém seus comandos em arquivos com formatação especial, chamados arquivos .prompt, monitora as alterações usando o mesmo sistema de controle de versões que usa para o código e os implanta junto com o código que chama seus modelos de IA generativa.
Os fluxos são funções fortemente tipadas, transmissíveis, totalmente observáveis e que podem ser chamadas de forma local ou remota. O Firebase Genkit fornece uma interface de linha de comando e uma interface de desenvolvedor para trabalhar com fluxos como execução ou depuração.
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;
}
É possível usar a chamada de função no Genkit para ampliar a funcionalidade do agente para que ele possa refinar ainda mais as respostas e concluir outras tarefas. O app de viagens define uma ferramenta que pode retornar informações sobre restaurantes da API Places com base na viagem que o usuário quer. O código usa o Zod para definir o esquema de entrada e saída de modo que os resultados da consulta possam ser validados.
...
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();
});
Para ajudar a oferecer uma experiência de pesquisa mais personalizada, depois que o usuário descreve a viagem dos seus sonhos, o Gemini determina se são necessárias mais informações com base nos comandos fornecidos pelo app de viagens e avisa ao app se achar que precisa de mais informações. Em seguida, o app solicita essas informações ao usuário e as anexa à solicitação no back-end.
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());
  }
O app pede que o usuário defina a viagem dos seus sonhos usando entrada de texto ou tocando no botão de microfone para ativar a conversão de voz em texto. Como opção, o usuário também pode fazer upload de imagens.

O app usa um pacote Dart do pub.dev para se integrar aos recursos nativos de conversão de voz em texto de cada plataforma e usa a API Gemini no Firebase Genkit para lidar com entradas multimodais, como imagens ou vídeos. A API Gemini usa a geração aumentada de recuperação (RAG) para retornar um conjunto de viagens sugeridas usando o Firebase Data Connect e embeddings para realizar uma pesquisa de vizinho mais próximo.

Escalonar seu app para produção

O Firebase Hosting se integra a frameworks da Web modernos e conhecidos, incluindo o Flutter. Ao usar o Firebase Hosting e o Cloud Functions para Firebase com esses frameworks, é possível desenvolver apps e microsserviços no seu ambiente de framework preferido e implantá-los em um ambiente de servidor gerenciado e seguro. Antes de entrar em produção, entenda a segurança e o desempenho de todos os serviços no seu app. Consulte a lista de verificação de lançamento do Firebase para mais informações.
O app de viagens usa a IA do Google para iterar rapidamente os dados de teste e é uma boa opção para apps com casos de uso mínimos de IA que não precisam ser escalonados. A Vertex AI tem uma cota maior para ajudar a escalonar aplicativos de produção e políticas de privacidade mais fortes para proteger os dados dos usuários. O Genkit tem uma funcionalidade integrada para trocar de modelo com facilidade. Assim, você não precisa reescrever seus comandos ou chamadas de API.