Firebase Genkit 如何帮助我们在 Compass 应用中添加 AI

五月 15, 2024
Alexander Nohe Developer Relations Engineer
Arthur Thompson Developer Relations Engineer

将生成式 AI 集成到应用中,可以帮助您的企业脱颖而出并让用户满意,但将基于 AI 的功能从原型开发阶段推进并进行优化,仍然具有挑战性。在与刚踏上 AI 开发之旅的应用开发者交谈后,我们了解到许多人在面对大量需要学习的新概念,以及使功能在生产环境中可扩展、安全且可靠的繁重任务时,会感到不知所措。

正因如此,我们构建了 Firebase Genkit,这是一个开源框架,旨在通过开发者友好的模式和范式,帮助您在应用中构建复杂的 AI 功能。该框架提供库、工具和插件,以协助开发者构建、测试、部署及监控 AI 负载。目前,Firebase Genkit 支持 JavaScript/TypeScript,很快亦将支持 Go 语言

在本帖子中,我们将介绍 Genkit 的一些关键功能,以及我们如何利用这些功能在旅行规划应用“Compass”中添加生成式 AI。


强大的开发者工具

由于生成式 AI 的独特性和非确定性特性,您需要专门的工具来帮助您高效地探索和评估各种解决方案,以最终实现稳定、符合生产标准的结果。

Genkit 通过其专用的 CLI 和基于浏览器的本地开发者界面,提供强大的工具使用体验。借助 Genkit CLI,您可以在几秒钟内初始化 AI 流;随后,您可以启动开发者界面来本地运行该流程。通过开发者界面,您可以与 Genkit 组件互动,例如流(端到端逻辑)、模型、提示、索引器、检索器、工具等。根据您的代码和配置的插件,Genkit 将为您提供可运行的组件。这使得您可以轻松地使用多种提示和查询来针对组件进行测试,并通过热重载迅速迭代结果。

Welcome to Firebase Genkit

通过流实现端到端的可观测性

所有 Genkit 组件都集成了 OpenTelemetry 及自定义元数据,以实现下行的可观测性和监控。Genkit 提供“流”(flow) 基本构造块,用于将多个步骤和 AI 组件串联起来,形成一个连贯的端到端工作流程。流是特殊的函数,具有强类型、可流式传输、本地和远程可调用以及完全可观察的特点。

得益于出色的插桩功能,当您在开发者界面中运行流时,您可以对其进行“检查”,以查看每个步骤和组件内部的跟踪信息和指标。这些跟踪记录包含了每个步骤的输入和输出,从而使调试 AI 逻辑或发现可优化的瓶颈变得更加容易。您甚至能够查看在生产环境中执行的已部署流的跟踪记录。

AI flow in Genkit

使用 dotprompt 进行提示管理

提示工程不仅仅是调整文本那么简单。您所使用的模型、提供的参数以及请求的格式,都会影响输出的质量。

Genkit 引入了 dotprompt 这一文件格式,该格式使您能够将所有这些要素整合到一个文件中,与代码并行保存,从而实现更简便的测试和组织管理。这意味着您可以像管理常规代码一样管理提示,使用相同的版本控制系统来跟踪它们,并将两者一起部署。dotprompt 文件可让您指定模型及其配置、提供基于 handlebars 的灵活模板,并定义输入和输出架构。这样一来,Genkit 就能在开发过程中帮助您验证模型交互的有效性。

---
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"}
---
 
给定此位置:{{place}},想出一个虚构的酒店名称和a
一个适合 {{place}} 的虚构酒店描述。

插件生态系统:支持 Google Cloud、Firebase、Vertex AI 等众多平台!

Genkit 通过其由 Google 和社区构建的开放式插件生态系统,提供对模型、矢量存储、工具、评估器、可观测性等多方面的预构建组件和集成的访问。如要查看 Google 和社区现有插件的列表,请在 npm 上使用关键词 #genkit-plugin 进行探索。

在 Compass 应用中,我们使用 Google Cloud 插件将遥测数据导出到 Google Cloud Logging 和 Monitoring、使用 Firebase 插件将跟踪记录导出到 Cloud Firestore,并借助 Vertex AI 插件获取 Google 最新的 Gemini 模型的访问权限。


我们如何使用 Genkit

为了让您亲身体验 Genkit 的强大功能,我们创建了旅行规划应用 Compass,旨在展示一个贴近实际的用例。

Genkit-Inline-2 (1)

Compass 的初始版本提供基于标准表单的旅行规划体验,但我们想知道:使用 Genkit 添加 AI 驱动的行程规划体验,会是什么样子?


为位置属性生成嵌入

由于我们已有现成的内容数据库,我们借助 Postgres 的 pgvector 扩展和来自 Vertex AI 的 textembedding-gecko API(在 Go 语言中使用),为我们的内容添加了带外嵌入。我们的目标是让用户能够根据每个地点的“知名特点”或大致描述来进行搜索。为了实现这一目标,我们为每个位置提取了“knownFor”属性,为之生成嵌入,并将其与数据一起插入到我们现有的表中,以便进行高效查询。

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

对相关位置进行语义搜索

接着,我们创建了一个检索器,根据用户的查询来搜索语义相关的数据,重点关注位置数据中的“knownFor”字段。为了实现这一目的,我们使用了 Genkit 的嵌入函数来生成用户查询的嵌入。这个嵌入随后被传递给检索器,检索器会高效地查询数据库,并依据查询与“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);
      }),
    };
  },
);

优化提示

我们把提示整理成 dotprompt 文件,并存放在 Genkit 项目根目录下的一个专门的 /prompts 目录中。对于提示的迭代,我们采用了两种方式进行测试:

  1. 流内测试:将提示加载到一个流中,这个流会从检索器获取数据并将其传给提示,模拟最终应用中的实际工作方式。

2. 开发者界面测试:将提示加载到开发者界面中。通过这种方式,我们能够在 prompt 文件中更新提示内容,并立即在界面上测试这些更改对输出质量的影响,从而实现快速迭代和调整。

一旦我们对某个提示感到满意,接下来就会使用评估器插件来进一步检验。评估器利用另一个 LLM 来评判响应,针对诸如忠实度、相关性和恶意性等常见 LLM 指标进行评估。


部署到 Cloud Run

Genkit 的设计核心中包含了部署能力。它天生就能与 Cloud Functions for Firebase、Firebase Authentication 及 App Check 等服务无缝集成。但在这个项目中,我们选择使用 Cloud Run。因为我们要部署到 Cloud Run,所以我们采用了 defineFlow,该功能在部署时会自动为每个声明的流生成 HTTPS 端点。

Genkit-Inline-3 (1)

亲自试用 Genkit

​​Genkit 简化了我们从开发到生产的 AI 开发流程。其直观的开发者界面变革了我们的工作方式,使得对于我们添加 AI 旅行规划功能至关重要的提示迭代过程变得极为简便。插件则实现了与各种 AI 产品和服务的无缝性能监控和集成。通过 Genkit 流和对提示的清晰版本控制,我们可以大胆地进行改动,因为我们知道如有需要,便可轻松回退到之前的版本。探索 Firebase Genkit 文档,了解 Genkit 如何帮助您为应用添加 AI 功能,并尝试此 Firebase Genkit Codelab,亲自实现一个类似的解决方案!