Cómo Firebase Genkit ayudó a incorporar la IA en nuestra aplicación Compass

MAY 15, 2024
Alexander Nohe Developer Relations Engineer
Arthur Thompson Developer Relations Engineer

Integrar la IA generativa en tus aplicaciones puede ayudarte a diferenciar tu negocio y complacer a los usuarios, pero desarrollar y refinar funciones impulsadas por IA más allá de un prototipo sigue siendo un desafío. Luego de hablar con desarrolladores de aplicaciones que recién están comenzando su exploración de desarrollo de IA, aprendimos que muchos se sienten abrumados con la cantidad de conceptos nuevos que aprender y la tarea de hacer que estas funciones sean escalables, seguras y confiables en la producción.

Es por eso que compilamos Firebase Genkit, un marco de trabajo de código abierto para compilar funciones sofisticadas de IA en tus aplicaciones con patrones y paradigmas fáciles de usar para el desarrollador. Proporciona bibliotecas, herramientas y complementos a fin de ayudar a los desarrolladores a compilar, probar, implementar y supervisar las cargas de trabajo de IA. En este momento, está disponible para JavaScript/TypeScript y, próximamente, será compatible con Go.

En esta publicación, aprenderás sobre algunas de las capacidades clave de Genkit y cómo las usamos para implementar la IA generativa en Compass, nuestra aplicación de planificación de viajes.


Herramientas sólidas para desarrolladores

La naturaleza única y no determinista de la IA generativa requiere de herramientas especializadas a fin de ayudarte a explorar y evaluar de manera eficiente las posibles soluciones a medida que trabajas para obtener resultados coherentes y de calidad de producción.

Genkit ofrece una experiencia sólida de herramientas a través de su CLI dedicada y su IU de desarrollador local con base en navegador. Con la CLI de Genkit, puedes inicializar un flujo de IA en segundos; luego, puedes iniciar la IU del desarrollador para ejecutarla de forma local. La IU del desarrollador es una superficie que te permite interactuar con componentes de Genkit, como flujos (tu lógica de extremo a extremo), modelos, indicaciones, indexadores, recuperadores, herramientas y mucho más. Los componentes estarán disponibles para que los ejecutes en función de tu código y de los complementos configurados. Esto te permite probar fácilmente tus componentes con varias indicaciones y consultas, e iterar con rapidez sobre los resultados con la recarga en caliente.

Welcome to Firebase Genkit

Observabilidad de extremo a extremo con flujos

Todos los componentes de Genkit están instrumentados con Telemetría Abierta y metadatos personalizados para habilitar la observabilidad y supervisión downstream. Genkit proporciona la primitiva de "flujo" como una forma de unir múltiples pasos y componentes de IA en un flujo de trabajo cohesivo de extremo a extremo. Los flujos son funciones especiales que están tipificadas de manera rigurosa, son transmisibles, llamables de forma local y remota, y son completamente observables.

Gracias a esta instrumentación increíble, cuando ejecutas un flujo en la IU del desarrollador, puedes "inspeccionarlo" para ver los registros y las métricas de cada paso y de cada componente. Estos registros incluyen las entradas y salidas de cada paso, lo que facilita la depuración de la lógica de tu IA o el hallazgo de cuellos de botella que puedas mejorar. Incluso puedes ver los registros de los flujos que se implementaron y ejecutaron en la producción.

AI flow in Genkit

Gestión de indicaciones con dotprompt

La ingeniería de indicaciones es más que solo ajustar un texto. El modelo que utilizas, los parámetros que proporcionas y el formato que solicitas afectan a la calidad de tus resultados.

Genkit ofrece dotprompt, un formato de archivo que te permite ponerlo todo en un solo archivo que se guarda junto con tu código a fin de facilitar las pruebas y la organización. Esto significa que puedes administrar las indicaciones junto con tu código regular, rastrearlos en el mismo sistema de control de versiones e implementarlos juntos. Los archivos dotprompt te permiten especificar el modelo y sus configuraciones, proporcionan plantillas flexibles con base en handlebars y definen esquemas de entrada y salida para que Genkit pueda ayudar a validar las interacciones de tu modelo a medida que lo desarrollas.

---
model: vertexai/gemini-1.0-pro
config:
  temperature: 1.0
input:
  schema:
    properties:
      place: {type: string}
    required: [place]
  default:
    place: New York City
output:
  schema:
    type: object
    properties:
      hotelName: {type: string, description: "hotelName"}
      description: {type: string, description: "description"}
---
 
Given this location: {{place}} come up with a fictional hotel name and a
fictional description of the hotel that suites the {{place}}.

Ecosistema de complementos: ¡Google Cloud, Firebase, Vertex AI y mucho más!

Genkit proporciona acceso a componentes e integraciones prediseñados para modelos, tiendas de vectores, herramientas, evaluadores, observabilidad y mucho más a través de su ecosistema abierto de complementos creados por Google y la comunidad. Para obtener una lista de los complementos existentes de Google y de la comunidad, explora la palabra clave #genkit-plugin en npm.

En nuestra aplicación, Compass, utilizamos el complemento Google Cloud a fin de exportar datos de telemetría al registro y supervisión de Google Cloud, el complemento Firebase para exportar registros a Cloud Firestore y el complemento Vertex AI para obtener acceso a los últimos modelos de Gemini de Google.


Cómo usamos Genkit

Para ofrecerte una visión práctica de las capacidades de Genkit, creamos Compass, una aplicación de planificación de viajes diseñada con el fin de mostrar un caso práctico familiar.

Genkit-Inline-2 (1)

Las versiones iniciales de Compass ofrecían una experiencia de planificación de viajes estándar con base en formularios, pero nos preguntamos: ¿cómo sería usar Genkit para agregar una experiencia de planificación de viajes con inteligencia artificial?


Generación de elementos insertados para atributos de ubicación

Dado que ya teníamos una base de datos de contenido, agregamos elementos insertados fuera de banda para nuestro contenido mediante el uso de la extensión pgvector para Postgres y la API textembedding-gecko de Vertex AI en Go. Nuestro objetivo era permitir a los usuarios hacer una búsqueda en función de "por qué es conocido" cada lugar o de una descripción general. Para lograr esto, extrajimos el atributo "knownFor" para cada ubicación, generamos elementos insertados para este y los insertamos junto con los datos en nuestra tabla existente a fin de lograr una búsqueda eficiente.

// generateEmbeddings creates embeddings from text provided.
func GenerateEmbeddings(
	contentToEmbed,
	project,
	location,
	publisher,
	model,
	titleOfContent string) ([]float64, error) {
	ctx := context.Background()
 
	apiEndpoint := fmt.Sprintf(
		"%s-aiplatform.googleapis.com:443", location)
 
	client, err := aiplatform.NewPredictionClient(
		ctx, option.WithEndpoint(apiEndpoint))
	handleError(err)
	defer client.Close()
 
	base := fmt.Sprintf(
		"projects/%s/locations/%s/publishers/%s/models",
		project,
		location,
		publisher)
 
	url := fmt.Sprintf("%s/%s", base, model)
 
	promptValue, err := structpb.NewValue(
		map[string]interface{}{
			"content":   contentToEmbed,
			"task_type": "RETRIEVAL_DOCUMENT",
			"title":     titleOfContent,
		})
	handleError(err)
 
	// PredictRequest: create the model prediction request
	req := &aiplatformpb.PredictRequest{
		Endpoint:  url,
		Instances: []*structpb.Value{promptValue},
	}
 
	// PredictResponse: receive the response from the model
	resp, err := client.Predict(ctx, req)
	handleError(err)
	pred := resp.Predictions[0]
 
	embeddings := pred.GetStructValue().AsMap()["embeddings"]
	embedInt, ok := embeddings.(map[string]interface{})
	if !ok {
		fmt.Printf("Cannot convert")
	}
	predSlice := embedInt["values"]
	outSlice := make([]float64, 0)
	for _, v := range predSlice.([]any) {
		outSlice = append(outSlice, v.(float64))
	}
 
	return outSlice, nil
}

Búsqueda semántica de ubicaciones relevantes

Luego, creamos un recuperador para buscar datos semánticamente relevantes en función de la búsqueda del usuario, centrándonos en el campo "knownFor" de nuestras ubicaciones. Para lograr esto, usamos la función de incorporación de Genkit a fin de incorporar la búsqueda del usuario. Esta incorporación se pasa a nuestro recuperador, que consulta de manera eficiente nuestra base de datos y devuelve los resultados de ubicación más relevantes en función de la similitud semántica entre la consulta y los atributos "knownFor".

export const placeRetriever = defineRetriever(
  {
    name: "postgres/placeRetriever",
    configSchema: QueryOptions,
  },
  async (input, options) => {
    const inputEmbedding = await embed({
      embedder: textEmbeddingGecko,
      content: input,
    });
    const results = await sql`
      SELECT ref, name, country, continent, "knownFor", tags, "imageUrl"
        FROM public.places
        ORDER BY embedding <#> ${toSql(inputEmbedding)} LIMIT ${options.k ?? 3};
    `;
    return {
      documents: results.map((row) => {
        const { knownFor, ...metadata } = row;
        return Document.fromText(knownFor, metadata);
      }),
    };
  },
);

Refinar las indicaciones

Organizamos nuestras indicaciones como archivos dotprompt dentro de un directorio dedicado "/prompts" en la raíz de nuestro proyecto Genkit. Teníamos dos formas de lograr una iteración de indicaciones:

  1. Pruebas en el flujo: Cargar indicaciones en un flujo que obtiene datos del recuperador y los manda a la indicación, al igual que funcionará en la aplicación final.

2. Pruebas en la IU del desarrollador: Cargar la indicación en la IU del desarrollador. De esta manera, podemos actualizar nuestras indicaciones en el archivo de indicaciones y probar al instante los cambios en la indicación para medir el impacto en la calidad del resultado.

Cuando estuvimos satisfechos con la indicación, utilizamos el complemento evaluator para evaluar las métricas comunes de modelos de lenguaje grande (LLM), como la fidelidad, la relevancia y la malicia, utilizando otro LLM para juzgar las respuestas.


Implementado en Cloud Run

La implementación está integrada en el ADN de Genkit. Si bien, naturalmente, se integra con Cloud Functions para Firebase (junto con Firebase Authentication y Verificación de aplicaciones), optamos por usar Cloud Run para este proyecto. Desde que implementamos Cloud Run, usamos defineFlow, que genera de forma automática un punto final HTTPS para cada flujo declarado cuando se implementa.

Genkit-Inline-3 (1)

Prueba Genkit por ti mismo

​​Genkit optimizó nuestro proceso de desarrollo de IA, desde el desarrollo hasta la producción. La IU intuitiva para desarrolladores fue un cambio radical, ya que facilitó mucho la iteración de indicaciones (una parte crucial de agregar nuestra función de planificación de viajes con IA). Los complementos permitieron una supervisión e integración sin problemas del rendimiento con varios productos y servicios de IA. Mediante nuestros flujos e indicaciones de Genkit con cuidadosos controles por versión, realizamos cambios con confianza sabiendo que podríamos revertirlos fácilmente si fuera necesario. Explora los documentos de Firebase Genkit para descubrir cómo Genkit puede ayudarte a agregar capacidades de IA en tus aplicaciones y prueba este codelab de Firebase Genkit a fin de implementar una solución similar por ti mismo.