Firebase Genkit で Compass アプリに AI を追加する

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

生成 AI をアプリに組み込めば、ビジネスを差別化し、ユーザーを喜ばせることができます。しかし、プロトタイプ以上の AI 機能を開発したり改善したりすることは、今もまだ困難です。AI 開発を始めたばかりのアプリ デベロッパーに話を聞いたところ、多くの人が、学ぶべき新しい概念の数と、信頼性が高くスケーラブルで安全な機能を実現するためのタスクに圧倒されていることがわかりました。

そこで開発したのが、Firebase Genkit です。このオープンソース フレームワークは、デベロッパーに優しいパターンとパラダイムを使って、アプリで洗練された AI 機能を開発できるようにするものです。そこで提供されるライブラリ、ツール、プラグインは、開発やテスト、デプロイに加え、AI ワークロードの監視に役立ちます。現在、JavaScript/TypeScript で利用でき、近日中に Go もサポートします

この投稿では、Genkit の一部の主要機能について紹介します。また、Genkit を使って、旅行計画アプリ Compass にどのように生成 AI を追加したかについても説明します。


堅牢なデベロッパー ツール

生成 AI には、ほかにはない非決定的な性質があります。そのため、一貫した本番環境向けの品質を実現するには、候補となるソリューションを効率的に調査、評価できる特別なツールが必要になります。

Genkit は、専用の CLI とブラウザベースのローカル デベロッパー UI を通じて、堅牢なツール エクスペリエンスを提供します。Genkit CLI を使うと、AI フローを数秒で初期化できます。その後、デベロッパー UI を起動し、ローカルで実行できます。デベロッパー UI は、フロー(エンドツーエンド ロジック)、モデル、プロンプト、インデックス登録、検索、ツールなどの Genkit コンポーネントを操作できる層です。コンポーネントは、コードと設定したプラグインに基づいて実行できるようになります。そのため、さまざまなプロンプトやクエリでコンポーネントを簡単にテストし、ホットリロードですばやく試行を繰り返すことができます。

Welcome to Firebase Genkit

フローによるエンドツーエンド オブザーバビリティ

Genkit のすべてのコンポーネントには、Open Telemetry とカスタム メタデータが含まれているので、ダウンストリームで観測したり監視したりできます。Genkit では、複数のステップと AI コンポーネントをひとつのエンドツーエンドのワークフローに結びつける「フロー」プリミティブが提供されます。フローは特別な関数で、強く型付けされ、ストリームに対応しており、ローカルでもリモートでも呼び出すことができます。また、完全に観測可能です。

このすばらしい仕組みのおかげで、デベロッパー UI でフローを実行して「検査」すると、各ステップや内部コンポーネントのトレースや指標を確認できます。このトレースには、すべてのステップの入力と出力が含まれているため、簡単に AI ロジックをデバッグしたり、ボトルネックを見つけて改善したりできます。本番環境にデプロイしたフローを実行し、トレースを表示することもできます。

AI flow in Genkit

dotprompt によるプロンプト管理

プロンプト エンジニアリングとは、単にテキストを微調整することだけを指しているのではありません。使うモデル、指定するパラメータ、リクエストする形式は、すべて出力品質に影響します。

Genkit が提供する dotprompt は、そのすべてを 1 つのファイルにまとめることができるファイル形式です。このファイルをコードと一緒に保管しておけば、テストや整理が簡単になります。つまり、プロンプトを通常のコードと一緒に管理でき、同じバージョン管理システムで追跡でき、一緒にデプロイできるようになります。dotprompt ファイルを使うと、モデルやその構成を指定したり、ハンドルバーに基づく柔軟なテンプレート化を行ったり、入力スキーマと出力スキーマを定義したりできるので、開発時にモデルの入出力を検証したい場合に役立ちます。

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

プラグイン エコシステム: Google Cloud、Firebase、Vertex AI など!

Genkit を使うと、Google やコミュニティが作成したプラグインのオープンなエコシステムから、モデル、ベクトルストア、ツール、エバリュエータ、オブザーバビリティといった開発済みのコンポーネントを組み込むことができます。Google とコミュニティによる既存プラグインのリストは、npm の #genkit-plugin キーワードで確認できます。

Compass アプリでは、Google Cloud プラグインを使ってテレメトリ データを Google Cloud Logging and Monitoring にエクスポートし、Firebase プラグインを使ってトレースを Cloud Firestore にエクスポートしてから、Vertex AI プラグインを使って Google の最新 Gemini モデルにアクセスしました。


Genkit の活用方法

Genkit の機能を実際に確認していただけるように、一般的なユースケースに対応した旅行計画アプリ Compass を作成しました。

Genkit-Inline-2 (1)

Compass の初期バージョンでは、標準的なフォームベースの旅行計画エクスペリエンスを提供していました。では、Genkit を使って AI 旅行計画エクスペリエンスを追加するには、どうすればよいでしょうか?


場所属性からエンベディングを生成する

コンテンツのデータベースはすでに手元にあります。そこで、Postgres の pgvector 拡張機能 を使い、Go で Vertex AI の textembedding-gecko API を呼び出して、コンテンツのアウトオブバンド エンベディングを追加しました。ここでの目標は、それぞれの場所が何で知られているかや、場所の一般的な説明から検索できるようにすることです。これを実現するために、各場所の「knownFor」属性を抽出し、そのエンベディングを生成し、データと合わせて既存のテーブルに挿入することで、効率的に検索できるようにしました。

// generateEmbeddings は、渡されたテキストからエンベディングを作成する。
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: モデル予測リクエストを生成する
	req := &aiplatformpb.PredictRequest{
		Endpoint:  url,
		Instances: []*structpb.Value{promptValue},
	}
 
	// PredictResponse: モデルからの応答を受け取る
	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 の embed 関数を使って、ユーザーのクエリのエンベディングを生成します。このエンベディングを検索機能に渡すと、効率的にデータベースを検索して、クエリと「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 つの方法を利用しました。

  1. フロー内テスト: フローにプロンプトを読み込みます。このフローは、検索機能からデータを取得し、プロンプトに渡すものです。これは、最終的なアプリケーションの動作と同じ形です。

2. デベロッパー UI テスト: デベロッパー UI にプロンプトを読み込みます。こうすることで、プロンプト ファイルのプロンプトを更新して即座に変更をテストし、出力品質への影響を測定できます。

納得できるプロンプトができたら、評価プラグインを使って、忠実度、関連性、悪意などの一般的な LLM 指標を評価しました。回答の判断には、別の LLM を使いました。


Cloud Run にデプロイする

デプロイは Genkit の DNA に組み込まれています。Genkit は、Cloud Functions for Firebase と自然な形で連携できますが(Firebase Authentication と App Check も同様)、このプロジェクトでは Cloud Run を使いました。Cloud Run にデプロイするので、defineFlow を使って、デプロイ時に宣言したすべてのフローの HTTPS エンドポイントを自動生成しました。

Genkit-Inline-3 (1)

ぜひご自分で Genkit をお試しください

​​Genkit によって、開発から本番までの AI 開発プロセスを効率化できました。画期的で直感的なデベロッパー UI によって、AI 旅行計画機能を追加するために欠かせない反復作業が容易になりました。また、プラグインによって、シームレスなパフォーマンス監視ができるだけでなく、さまざまな AI プロダクトやサービスとの連携も実現できました。Genkit のフローとプロンプトはバージョン管理でき、必要に応じて簡単に元に戻せるので、安心して変更することもできました。Firebase Genkit ドキュメントを確認すると、アプリに AI 機能を追加するうえで Genkit がどのように役立つかを理解できます。こちらの Firebase Genkit Codelab を試し、今度は皆さんが同じようなソリューションを実装してみてください!