Lösung

Agent-gestützte Reiseplan-App mit generativer KI erstellen

Das folgende Diagramm bietet einen allgemeinen Überblick darüber, wie Sie erstklassige Google-Entwicklerdienste kombinieren können, um KI-gestützte Anwendungen zu erstellen.

Ansprechende, KI-gestützte Apps für Mobilgeräte und das Web erstellen

Mit Flutter und Firebase Genkit können Sie plattformübergreifende Apps erstellen, die sich nahtlos in KI einbinden lassen.
Mit Genkit können Sie Ihrer Anwendung die zuverlässige Nutzung von Daten aus LLMs ermöglichen. Dazu definieren Sie ein Schema, mit dem sich die Ausgabe des LLM validieren lässt. In Flutter können Sie Dart verwenden, um die Anfrage zu serialisieren und die Antwort zu deserialisieren, damit sie mit dem Genkit-Schema mit standardmäßigen HTTP-Anfragen abgeglichen wird.
Mit IDX können Sie eine Flutter-App erstellen, ohne eine Software installieren zu müssen. So können Sie Ihre Android-App und Ihre Web-App im Browser entwickeln und testen.
Firebase Data Connect ist ein relationaler Datenbankdienst für mobile Apps und Webanwendungen, mit dem Sie eine vollständig verwaltete PostgreSQL-Datenbank auf Basis von Cloud SQL erstellen und skalieren können. Es bietet eine sichere Schema-, Abfrage- und Mutationsverwaltung mithilfe von GraphQL, die sich gut in Firebase Authentication einbinden lässt. Data Connect umfasst SDK-Unterstützung für Kotlin Android und das Web.

Agent mit Firebase Genkit erstellen

Der Agent verwendet ein KI-Orchestrierungs-Framework, um Nutzereingaben zu empfangen und eine Antwort zu generieren.
---
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}}
Bei der Arbeit mit generativer KI ist es wichtig, effektive Prompts zu erstellen, damit das Modell qualitativ hochwertige Antworten zurückgibt. Firebase Genkit bietet das Dotprompt-Plug-in und das Textformat, damit Sie Generative AI-Prompts schreiben und organisieren können. Das Format kapselt den Prompt, das Ein- und Ausgabeschema, das Modell und die Konfiguration in einer einzigen Datei.

Das folgende Codebeispiel zeigt eine Dotprompt-Datei, die in der Reise-App verwendet wird. Das Schema basiert auf den Informationen, die der Nutzer bei der Beschreibung seiner Traumreise bereitstellt.
Bei Dotprompt geht es darum, dass Eingabeaufforderungen Code sind. Sie schreiben und verwalten Ihre Prompts in speziell formatierten Dateien, sogenannte Punktprompt-Dateien, verfolgen Änderungen an ihnen mit demselben Versionsverwaltungssystem, das Sie für Ihren Code verwenden, und stellen sie zusammen mit dem Code bereit, der Ihre generativen KI-Modelle aufruft.
Datenflüsse sind Funktionen, die stark typisiert, streambar, lokal und remote aufrufbar sowie vollständig beobachtbar sind. Firebase Genkit bietet eine Befehlszeilen- und Entwickler-UI zum Arbeiten mit Abläufen, z. B. zum Ausführen oder Debuggen von Abläufen.
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;
}
Sie können Funktionsaufrufe in Genkit verwenden, um die Funktionalität des Agents zu erweitern, sodass der Agent Antworten weiter verfeinern und zusätzliche Aufgaben ausführen kann. Die Reise-App definiert ein Tool, das Restaurantinformationen aus der Places API basierend auf der gewünschten Fahrt des Nutzers zurückgeben kann. Der Code verwendet Zod, um das Ein- und Ausgabeschema zu definieren, damit die Abfrageergebnisse validiert werden können.
...
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();
});
Um Nutzern eine individuellere Suche zu ermöglichen, ermittelt Gemini anhand der Prompts der Reise-App, ob mehr Informationen erforderlich sind, nachdem der Nutzer seine Traumreise beschrieben hat, und signalisiert der App, wenn weitere Informationen benötigt werden. Die App fordert den Nutzer dann zur Eingabe dieser Informationen auf und hängt sie an die Anfrage im Back-End an.
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());
  }
Die Reise-App bittet den Nutzer, seine Traumreise über die Texteingabe oder durch Tippen auf die Mikrofonschaltfläche zur Aktivierung der Spracherkennung zu definieren. Optional kann der Nutzer auch Bilder hochladen.

Die App nutzt ein Dart-Paket von pub.dev zur Einbindung nativer Spracherkennungsfunktionen für jede Plattform und die Gemini API in Firebase Genkit, um die multimodalen Eingaben wie Bilder oder Videos zu verarbeiten. Die Gemini API verwendet Retrieval-augmented Generation (RAG), um mithilfe von Firebase Data Connect eine Reihe von vorgeschlagenen Fahrten zurückzugeben. Dabei werden mithilfe von Einbettungen eine Suche nach dem nächsten Nachbarn durchgeführt.

App für die Produktion skalieren

Firebase Hosting lässt sich in gängige moderne Web-Frameworks wie Flutter einbinden. Wenn Sie Firebase Hosting und Cloud Functions for Firebase mit diesen Frameworks verwenden, können Sie Apps und Mikrodienste in Ihrer bevorzugten Framework-Umgebung entwickeln und dann in einer verwalteten, sicheren Serverumgebung bereitstellen. Bevor Sie mit der Produktion beginnen, sollten Sie sich mit der Sicherheit und Leistung aller Dienste in Ihrer App vertraut machen. Weitere Informationen finden Sie in der Firebase-Start-Checkliste.
Die Reise-App nutzt Google AI, um Testdaten schnell zu iterieren. Sie eignet sich gut für Apps mit minimalen KI-Anwendungsfällen, die nicht skaliert werden müssen. Vertex AI hat ein höheres Kontingent, um Produktionsanwendungen zu skalieren, und strengere Datenschutzrichtlinien zum Schutz von Nutzerdaten. Genkit bietet integrierte Funktionen für einen einfachen Modellwechsel, sodass Sie Ihre Prompts oder API-Aufrufe nicht neu schreiben müssen.