Como o Firebase Genkit ajudou a integrar a IA ao app Compass

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

Integrar a IA generativa a um app pode ajudar você a diferenciar sua empresa e encantar os usuários. No entanto, desenvolver e refinar recursos baseados em IA além de um protótipo continua sendo um desafio. Depois de conversar com desenvolvedores de apps que estão apenas começando a jornada de desenvolvimento de IA, vimos que muitos estão sobrecarregados com a quantidade de novos conceitos a serem aprendidos e com a tarefa de tornar esses recursos escalonáveis, seguros e confiáveis na produção.

É por isso que criamos o Firebase Genkit, um framework de código aberto para incorporar recursos de IA sofisticados aos apps com padrões e paradigmas que os desenvolvedores podem usar facilmente. Ele fornece bibliotecas, ferramentas e plug-ins para ajudar os desenvolvedores a criar, testar, implantar e monitorar cargas de trabalho de IA. Atualmente, ele está disponível para JavaScript/TypeScript, e o suporte ao Go chegará em breve.

Nesta postagem, você conhecerá alguns dos principais recursos do Genkit e saberá como nós os utilizamos para adicionar a IA generativa ao Compass, nosso app de planejamento de viagens.


Ferramentas robustas para desenvolvedores

A natureza única e não determinística da IA generativa exige ferramentas especializadas para ajudar a explorar e avaliar com eficiência as possíveis soluções à medida que você trabalha para obter resultados consistentes e com qualidade no nível de produção.

O Genkit proporciona uma experiência robusta com as ferramentas por meio de sua CLI dedicada e da IU de desenvolvimento local baseada em navegador. Com a CLI do Genkit, você pode inicializar um fluxo de IA em segundos e, depois, iniciar a IU de desenvolvimento para executá-lo localmente. A IU de desenvolvimento é uma plataforma que permite interagir com componentes do Genkit, como fluxos (a lógica de ponta a ponta), modelos, prompts, indexadores, recuperadores, ferramentas e muito mais. Os componentes ficam disponíveis para você executar com base no código e nos plug-ins configurados. Isso permite que você teste facilmente os componentes com vários prompts e consultas e faça iterações rapidamente dos resultados com a recarga automática.

Welcome to Firebase Genkit

Observabilidade de ponta a ponta com fluxos

Todos os componentes do Genkit são instrumentados com o Open Telemetry e com metadados personalizados para possibilitar a observabilidade e o monitoramento de downstream. O Genkit fornece o primitivo "flow" como uma forma de amarrar várias etapas e vários componentes de IA em um fluxo de trabalho coeso de ponta a ponta. Os fluxos são funções especiais fortemente tipadas, transmissíveis, local e remotamente chamáveis e totalmente observáveis.

Graças a essa instrumentação incrível, ao executar um fluxo na IU de desenvolvimento, você pode "inspecioná-lo" para visualizar traces e métricas de cada etapa e componente. Esses traces incluem as entradas e saídas de cada etapa, facilitando a depuração da lógica de IA ou a identificação de gargalos para fazer melhorias. É possível até mesmo visualizar traces de fluxos implantados e executados em produção.

AI flow in Genkit

Gerenciamento de prompts com o dotprompt

A engenharia de prompts é mais do que apenas o ajuste do texto. O modelo usado, os parâmetros fornecidos e o formato solicitado afetam a qualidade da saída.

O Genkit oferece o dotprompt, um formato de arquivo que permite colocar tudo em um único arquivo que você mantém junto com o código para facilitar os testes e a organização. Isso significa que você pode gerenciar os prompts juntamente com o código regular, rastreá-los no mesmo sistema de controle de versões e implantá-los juntos. Os arquivos dotprompt permitem especificar o modelo e suas configurações, fornecer modelos flexíveis com base em handlebars e definir esquemas de entrada e saída para que o Genkit possa ajudar a validar as interações dos modelos durante o desenvolvimento.

---
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}}.

Ecossistema de plug-ins: Google Cloud, Firebase, Vertex AI e muito mais!

O Genkit fornece acesso a integrações e componentes prontos para modelos, armazenamentos vetoriais, ferramentas, avaliadores, observabilidade e muito mais por meio de seu ecossistema aberto de plug-ins criados pela Google e pela comunidade. Para obter uma lista de plug-ins existentes da Google e da comunidade, explore a palavra-chave #genkit-plugin no npm.

No app Compass, usamos o plug-in do Google Cloud para exportar dados de telemetria para o Google Cloud Logging e o Google Cloud Monitoring, o plug-in do Firebase para exportar traces para o Cloud Firestore e o plug-in do Vertex AI para obter acesso aos modelos do Gemini mais recentes do Google.


Como usamos o Genkit

Para que você tenha uma visão prática dos recursos do Genkit, criamos o Compass, um app de planejamento de viagens projetado para demonstrar um caso de uso familiar.

Genkit-Inline-2 (1)

As versões iniciais do Compass ofereciam uma experiência padrão de planejamento de viagens baseada em formulários, mas nós nos perguntamos como seria adicionar uma experiência de planejamento de viagens baseada em IA com o Genkit.


Geração de incorporações para atributos de locais

Como já tínhamos um banco de dados de conteúdo, adicionamos incorporações fora de banda do conteúdo usando a extensão pgvector para Postgres e a API textembedding-gecko do Vertex AI no Go. Nossa meta era permitir que os usuários pesquisassem com base nas características pelas quais cada lugar "é conhecido" ou com base em uma descrição geral. Para conseguir isso, extraímos o atributo "knownFor" de cada local, geramos incorporações para ele e as inserimos em conjunto com os dados em nossa tabela existente para garantir eficiência nas consultas.

// 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
}

Pesquisa semântica de locais relevantes

Em seguida, criamos um recuperador para pesquisar dados semanticamente relevantes com base na consulta do usuário, com foco no campo "knownFor" dos locais. Para isso, usamos a função embed do Genkit para gerar uma incorporação da consulta do usuário. Depois, essa incorporação é transmitida para o recuperador, que consulta o banco de dados com eficiência e retorna os resultados de locais mais relevantes com base na semelhança semântica entre a consulta e os 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);
      }),
    };
  },
);

Refinamento dos prompts

Organizamos os prompts como arquivos dotprompt dentro de um diretório /prompts dedicado na raiz do projeto Genkit. Para a iteração dos prompts, tínhamos dois caminhos:

  1. Teste no fluxo: carregar os prompts em um fluxo que busca dados do recuperador e os alimenta no prompt, assim como ocorrerá no aplicativo final.

2. Teste na IU de desenvolvimento: carregar o prompt na IU de desenvolvimento. Dessa forma, é possível atualizar os prompts no arquivo de prompts e testar instantaneamente as mudanças do prompt para medir o impacto na qualidade da saída.

Quando ficamos satisfeitos com o prompt, usamos o plug-in de avaliação para avaliar métricas comuns de LLM, como fidelidade, relevância e intenção maliciosa, usando outro LLM para julgar as respostas.


Implantação no Cloud Run

A implantação está integrada ao DNA do Genkit. Embora ele se integre naturalmente ao Cloud Functions para Firebase (juntamente com o Firebase Authentication e o App Check), optamos por usar o Cloud Run neste projeto. Como estávamos implantando no Cloud Run, usamos o defineFlow, que gera automaticamente um endpoint HTTPS para cada fluxo declarado durante a implantação.

Genkit-Inline-3 (1)

Experimente o Genkit

​​O Genkit otimizou o processo de desenvolvimento de IA, desde o desenvolvimento até a produção. A IU de desenvolvimento intuitiva foi um divisor de águas, facilitando muito a iteração de prompts, que é uma parte crucial da adição de nosso recurso de planejamento de viagens por IA. Os plug-ins possibilitaram o monitoramento otimizado do desempenho e a integração a vários produtos e serviços de IA. Com o controle de versões dos fluxos e prompts do Genkit, fizemos as mudanças com segurança, sabendo que poderíamos revertê-las facilmente, se necessário. Explore os documentos do Firebase Genkit para saber como o Genkit pode ajudar a integrar recursos de IA aos apps e experimente este codelab do Firebase Genkit para implementar uma solução similar!