Rozwiązanie

Utwórz opartą na agentach aplikację do planowania podróży z generatywną AI.

Poniższy diagram przedstawia ogólny przegląd sposobów łączenia najlepszych w swojej klasie usług Google dla deweloperów w celu tworzenia aplikacji opartych na AI.

Twórz świetne aplikacje oparte na AI na urządzenia mobilne i internet

Za pomocą Flutter i Firebase Genkit możesz tworzyć wieloplatformowe aplikacje, które płynnie integrują się z AI.
Możesz użyć Genkit, aby umożliwić aplikacji bezpieczne wykorzystywanie danych z LLM przez zdefiniowanie schematu, który może zweryfikować dane wyjściowe tego modelu. W Flutter możesz używać Dart do serializowania żądania i deserializacji odpowiedzi, aby była zgodna ze schematem Genkit za pomocą standardowych żądań HTTP.
Dzięki IDX możesz tworzyć aplikacje Flutter bez konieczności instalowania żadnego oprogramowania. Umożliwia to programowanie i testowanie aplikacji na Androida oraz aplikacji internetowych w przeglądarce.
Firebase Data Connect to usługa relacyjnej bazy danych na potrzeby aplikacji mobilnych i internetowych, która umożliwia tworzenie i skalowanie z użyciem w pełni zarządzanej bazy danych PostgreSQL obsługiwanej przez Cloud SQL. Zapewnia bezpieczny schemat oraz zarządzanie zapytaniami i mutacjami za pomocą GraphQL, który dobrze integruje się z Uwierzytelnianiem Firebase. Data Connect obejmuje obsługę pakietów SDK na urządzeniach z Androidem i w przeglądarce.

Tworzenie agenta za pomocą Firebase Genkit

Agent używa platformy administrowania AI do odbierania danych wejściowych użytkownika i generowania odpowiedzi.
---
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}}
W przypadku pracy z generatywną AI ważne jest tworzenie skutecznych promptów, tak aby model zwracał odpowiedzi o wysokiej jakości. Firebase Genkit udostępnia wtyczkę Dotprompt i format tekstowy, które ułatwiają pisanie i porządkowanie promptów generatywnej AI. Ten format zapisuje prompty, schematy wejściowe i wyjściowe, model oraz konfigurację w jednym pliku.

Ten przykładowy kod pokazuje plik Dotprompt używany w aplikacji turystycznej. Schemat jest oparty na informacjach podanych przez użytkownika podczas opisywania wymarzonej podróży.
Dotprompt opiera się na założeniu, że prompty są kodem. Prompty piszesz i przechowujesz w specjalnie sformatowanych plikach nazywanych plikami kropkaprompt, śledzisz w nich zmiany za pomocą tego samego systemu kontroli wersji, którego używasz w przypadku kodu, i wdrażasz je razem z kodem, który wywołuje Twoje modele generatywnej AI.
Przepływy to funkcje, które są ściśle wpisywane, zapisywane strumieniowo, dostępne lokalnie i zdalnie, a także w pełni dostrzegalne. Firebase Genkit udostępnia interfejs wiersza poleceń i interfejs programisty, który umożliwia pracę z przepływami, takimi jak uruchamianie czy debugowanie przepływów.
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;
}
Za pomocą wywołań funkcji w Genkit możesz rozszerzyć możliwości agenta, dzięki czemu będzie on mógł jeszcze bardziej doprecyzować odpowiedzi i wykonać dodatkowe zadania. Aplikacja podróżnicza definiuje narzędzie, które może zwracać informacje o restauracjach z interfejsu Places API na podstawie wybranej przez użytkownika podróży. Kod wykorzystuje znak Zod do zdefiniowania schematu wejściowego i wyjściowego, co umożliwia weryfikację wyników zapytania.
...
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();
});
Aby zapewnić użytkownikom bardziej spersonalizowane wyszukiwanie, po tym, jak użytkownik opisuje wymarzoną podróż, Gemini na podstawie promptów wyświetlanych przez aplikację turystyczną określa, czy potrzebuje więcej informacji, i sygnalizuje aplikacji, jeśli uzna, że potrzebuje więcej informacji. Następnie aplikacja prosi o te informacje i dołącza je do żądania w backendzie.
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());
  }
Aplikacja podróżnicza prosi użytkownika o opracowanie wymarzonej podróży przez wprowadzenie tekstu lub przez kliknięcie przycisku mikrofonu w celu aktywacji zamiany mowy na tekst. Użytkownik może też opcjonalnie przesłać obrazy.

Aplikacja wykorzystuje pakiet Dart z firmy pub.dev, aby zintegrować ją z natywnymi funkcjami zamiany mowy na tekst na każdej platformie, i używa interfejsu Gemini API w Firebase Genkit do obsługi multimodalnych danych wejściowych, takich jak obrazy czy filmy. Interfejs Gemini API korzysta z generowania rozszerzonego przez wyszukiwanie w zapisanych informacjach (RAG), aby zwrócić zestaw sugerowanych podróży za pomocą Firebase Data Connect i wektorów dystrybucyjnych w celu przeprowadzenia wyszukiwania najbliższych sąsiadów.

Skalowanie aplikacji pod kątem środowiska produkcyjnego

Hosting Firebase integruje się z popularnymi nowoczesnymi platformami internetowymi, takimi jak Flutter. Korzystając z Hostingu Firebase i Cloud Functions dla Firebase w połączeniu z tymi platformami, możesz tworzyć aplikacje i mikroserwisy w preferowanym środowisku, a następnie wdrażać je w zarządzanym, bezpiecznym środowisku serwerowym. Zanim przejdziesz na wersję produkcyjną, poznaj zabezpieczenia i wydajność wszystkich usług w aplikacji. Więcej informacji znajdziesz na liście kontrolnej uruchomienia Firebase.
Aplikacja podróżnicza korzysta z AI od Google, aby szybko iterować dane testowe. Jest to dobry wybór w przypadku aplikacji, w których AI nie jest potrzebna w minimalnym stopniu. Vertex AI ma wyższy limit, aby ułatwić skalowanie aplikacji produkcyjnych, oraz silniejszą politykę prywatności zapewniającą ochronę danych użytkowników. Genkit ma wbudowaną funkcję umożliwiającą łatwe przełączanie modeli, więc nie musisz przepisywać promptów ani wywołań interfejsu API.