将生成式 AI 集成到应用中,可以帮助您的企业脱颖而出并让用户满意,但将基于 AI 的功能从原型开发阶段推进并进行优化,仍然具有挑战性。在与刚踏上 AI 开发之旅的应用开发者交谈后,我们了解到许多人在面对大量需要学习的新概念,以及使功能在生产环境中可扩展、安全且可靠的繁重任务时,会感到不知所措。
正因如此,我们构建了 Firebase Genkit,这是一个开源框架,旨在通过开发者友好的模式和范式,帮助您在应用中构建复杂的 AI 功能。该框架提供库、工具和插件,以协助开发者构建、测试、部署及监控 AI 负载。目前,Firebase Genkit 支持 JavaScript/TypeScript,很快亦将支持 Go 语言。
在本帖子中,我们将介绍 Genkit 的一些关键功能,以及我们如何利用这些功能在旅行规划应用“Compass”中添加生成式 AI。
由于生成式 AI 的独特性和非确定性特性,您需要专门的工具来帮助您高效地探索和评估各种解决方案,以最终实现稳定、符合生产标准的结果。
Genkit 通过其专用的 CLI 和基于浏览器的本地开发者界面,提供强大的工具使用体验。借助 Genkit CLI,您可以在几秒钟内初始化 AI 流;随后,您可以启动开发者界面来本地运行该流程。通过开发者界面,您可以与 Genkit 组件互动,例如流(端到端逻辑)、模型、提示、索引器、检索器、工具等。根据您的代码和配置的插件,Genkit 将为您提供可运行的组件。这使得您可以轻松地使用多种提示和查询来针对组件进行测试,并通过热重载迅速迭代结果。
所有 Genkit 组件都集成了 OpenTelemetry 及自定义元数据,以实现下行的可观测性和监控。Genkit 提供“流”(flow) 基本构造块,用于将多个步骤和 AI 组件串联起来,形成一个连贯的端到端工作流程。流是特殊的函数,具有强类型、可流式传输、本地和远程可调用以及完全可观察的特点。
得益于出色的插桩功能,当您在开发者界面中运行流时,您可以对其进行“检查”,以查看每个步骤和组件内部的跟踪信息和指标。这些跟踪记录包含了每个步骤的输入和输出,从而使调试 AI 逻辑或发现可优化的瓶颈变得更加容易。您甚至能够查看在生产环境中执行的已部署流的跟踪记录。
提示工程不仅仅是调整文本那么简单。您所使用的模型、提供的参数以及请求的格式,都会影响输出的质量。
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}} 的虚构酒店描述。
Genkit 通过其由 Google 和社区构建的开放式插件生态系统,提供对模型、矢量存储、工具、评估器、可观测性等多方面的预构建组件和集成的访问。如要查看 Google 和社区现有插件的列表,请在 npm 上使用关键词 #genkit-plugin 进行探索。
在 Compass 应用中,我们使用 Google Cloud 插件将遥测数据导出到 Google Cloud Logging 和 Monitoring、使用 Firebase 插件将跟踪记录导出到 Cloud Firestore,并借助 Vertex AI 插件获取 Google 最新的 Gemini 模型的访问权限。
为了让您亲身体验 Genkit 的强大功能,我们创建了旅行规划应用 Compass,旨在展示一个贴近实际的用例。
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 目录中。对于提示的迭代,我们采用了两种方式进行测试:
2. 开发者界面测试:将提示加载到开发者界面中。通过这种方式,我们能够在 prompt 文件中更新提示内容,并立即在界面上测试这些更改对输出质量的影响,从而实现快速迭代和调整。
一旦我们对某个提示感到满意,接下来就会使用评估器插件来进一步检验。评估器利用另一个 LLM 来评判响应,针对诸如忠实度、相关性和恶意性等常见 LLM 指标进行评估。
Genkit 的设计核心中包含了部署能力。它天生就能与 Cloud Functions for Firebase、Firebase Authentication 及 App Check 等服务无缝集成。但在这个项目中,我们选择使用 Cloud Run。因为我们要部署到 Cloud Run,所以我们采用了 defineFlow,该功能在部署时会自动为每个声明的流生成 HTTPS 端点。
Genkit 简化了我们从开发到生产的 AI 开发流程。其直观的开发者界面变革了我们的工作方式,使得对于我们添加 AI 旅行规划功能至关重要的提示迭代过程变得极为简便。插件则实现了与各种 AI 产品和服务的无缝性能监控和集成。通过 Genkit 流和对提示的清晰版本控制,我们可以大胆地进行改动,因为我们知道如有需要,便可轻松回退到之前的版本。探索 Firebase Genkit 文档,了解 Genkit 如何帮助您为应用添加 AI 功能,并尝试此 Firebase Genkit Codelab,亲自实现一个类似的解决方案!