Firebase Genkit を試してみた
Firebase Genkitとは
Firebase Genkitは、Firebaseのプロダクトに属するAIアプリケーションを作成するための統合開発環境です。LangChainをはじめとするLLM統合環境と同等の機能を提供しています。
※ 2025/01時点ではB版の位置づけになっています。
インストール
npm install genkit @genkit-ai/googleai genkit
または、Firebase CLIを使って、直接Firebase Functionsにデプロイするための環境構築をすることも可能です。
firebase init genkit
バージョン
この記事時点のバージョンは次の通りです。
- @genkit-ai/googleai: 0.9.12
- genkit: 0.9.12
LLMの対応モデル
Googleサービスのため、Geminiを標準でサポートしています。コミュニティサポートのプラグインを利用すると、「OpenAI」「Anthropic」など各ベンダーのモデルや、「Ollama」によるローカルモデルにも対応できます。
統合環境としてできること
- 各種LLMの実行
- プロンプト管理
- 評価
- AIワークフローのトレース
- etc
これらの機能も、各社が提供している統合環境と同等のものを備えています。
LLMの実行機能
LLMの実行機能は、機能ごとにAPIが分かれています。
- シンプルなモデル呼び出し
- チャット
- ワークフロー(エージェント)の構築
- retrieve(RAG)
LLMモデルの実行
シンプルなLLMモデルの実行コードは次のようになります。
import { gemini15Flash, googleAI } from "@genkit-ai/googleai";
import { genkit } from "genkit";
const ai = genkit({
plugins: [googleAI()],
model: gemini15Flash,
});
(async () => {
const { text } = await ai.generate("カレーの材料はなんですか?");
console.log(text);
})();
構造化アウトプット
genkitは、構造化アウトプット(LLMの結果をあらかじめ指定した型で受け取る機能)に対応していて、zodを使った型指定が可能です。
import { gemini15Flash, googleAI } from "@genkit-ai/googleai";
import { genkit, z } from "genkit";
const ai = genkit({
plugins: [googleAI()],
model: gemini15Flash,
});
const FoodSchema = z.object({
name: z.string(),
ingredients: z.array(z.string()),
description: z.string(),
});
(async () => {
const { output } = await ai.generate({
prompt: "カレーの材料を日本語で教えて下さい.",
output: { schema: FoodSchema },
});
console.log(output);
})();
上記を実行すると、型付けされた形式でoutputが出力されます。
pnpm tsx generate.ts
{
description: '簡単で美味しいカレーのレシピです。',
ingredients: [
'玉ねぎ 1個',
'にんにく 1かけ',
'生姜 1かけ',
'鶏むね肉 200g',
'カレールー 2かけ',
'水 500ml',
'油 大さじ1',
'塩コショウ 少々'
],
name: '簡単鶏むねカレー'
}
フローの実行
LLMアプリケーションの開発では、単純にLLMを実行するだけでなく、複数の処理をつなげるケースがほとんどです。genkitでは、それを実現するためのワークフロー機能を提供します。
LangChain系統でいうとLangGraphのような立ち位置の機能だと思われます。
import { gemini15Flash, googleAI } from "@genkit-ai/googleai";
import { genkit, z } from "genkit";
const ai = genkit({
plugins: [googleAI()],
model: gemini15Flash,
});
const FoodSchema = z.object({
name: z.string(),
ingredients: z.array(z.string()),
description: z.string(),
});
export const askForIngredientsFlow = ai.defineFlow(
{
name: "askForIngredientsFlow",
// フローにも型付けのInput/Outputを指定できる
inputSchema: z.string(),
outputSchema: FoodSchema,
},
async (food: string) => {
// フロー内でLLMの実行やその他ワークフロー処理を行う
const { output } = await ai.generate({
model: gemini15Flash,
prompt: `${food} の材料を日本語で教えて下さい.`,
output: { schema: FoodSchema },
});
if (!output) {
throw new Error("Failed to generate ingredients");
}
return output;
}
);
(async () => {
const result = await askForIngredientsFlow("肉じゃが");
console.log(result);
})();
pnpm tsx flow.ts
{
description: '定番の家庭料理、肉じゃがのレシピです。',
ingredients: [
'牛肉(300g)',
'じゃがいも(3個)',
'玉ねぎ(1個)',
'にんじん(1/2本)',
'だし汁(400ml)',
'砂糖(大さじ2)',
'醤油(大さじ3)',
'みりん(大さじ2)'
],
name: '肉じゃが'
}
プロンプト管理
Firebase Genkitでは、プロンプトを Dotprompt という形式で管理します。特徴としては、---
で囲まれたYAMLセクションと、Handlebarsというテンプレートエンジンを利用可能なプロンプトセクションから構成されています。
YAMLセクションにはmodel情報やLLMの設定、input/outputのスキーマなどを定義できます。Handlebarsはさまざまな制御構文が使えますが、シンプルな使い方としては {{}}
を使った埋め込み変数などがあります。
以下はサンプルです。
---
model: googleai/gemini-1.5-flash
---
{{food}}の材料を教えて下さい
プロンプトの実行方法
プロンプトファイルを使ってLLMを実行するためには、プロジェクト配下の prompts ディレクトリへ配置する必要があります。
prompts
└── recipe.prompt
そして、コード上では次のように呼び出します。
import { genkit } from 'genkit';
import { googleAI } from '@genkit-ai/googleai';
const ai = genkit({
plugins: [
googleAI(),
],
});
// ai.promptでプロンプトのインスタンスを生成
// 引数の名前は promptsディレクトリ配下のプロンプトファイル名
const helloPrompt = ai.prompt('recipe');
(async () => {
// プロンプトを使ったLLMの実行
const {text} = await helloPrompt({
food: 'カレー'
});
console.log(text);
})();
genkitのインスタンスから prompt
メソッドを使うことで、プロンプトのインスタンスを生成できます。このときに指定するのは、prompts ディレクトリ配下のプロンプトファイル名
です。
開発環境での実行
Genkitにはローカルでアプリケーションを開発・テストするためのCLI Toolが用意されています。
genkit CLI
npm install -g genkit-cli
genkit CLIをインストールすると、コマンドラインからGenkit関連の操作を行えるようになります。これにより、フロー(ワークフロー)を実行したり、開発用のUIを起動してフローを一覧・管理することができます。
genkit -h
Usage: genkit [options] [command]
Firebase Genkit CLI
Options:
-V, --version output the version number
-h, --help display help for command
Commands:
ui:start [options] start the Developer UI which connects to runtimes in the same directory
ui:stop stops any running Genkit Developer UI in this directory
flow:run [options] <flowName> [data] run a flow using provided data as input
flow:batchRun [options] <flowName> <inputFileName> batch run a flow using provided set of data from a file as input
eval:extractData [options] <flowName> extract evaludation data for a given flow from the trace store
eval:run [options] <dataset> evaluate provided dataset against configured evaluators
eval:flow [options] <flowName> [data] evaluate a flow against configured evaluators using provided data as input
config set development environment configuration
start [options] runs a command in Genkit dev mode
help
gekitコマンドを使ったフローの実行
サンプルとして利用するフローを genkit flow:run
コマンドで実行してみます。
読み込むフローのコード
export GOOGLE_GENAI_API_KEY=<api key>
- API KEYは任意のものを読み込む
{
"name": "genkit-sample",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"@genkit-ai/googleai": "^0.9.12",
"genkit": "^0.9.12"
},
"devDependencies": {
"tsx": "^4.19.2",
"typescript": "^5.7.3"
}
}
import { gemini15Flash, googleAI } from "@genkit-ai/googleai";
import { genkit, z } from "genkit";
const ai = genkit({
plugins: [googleAI()],
model: gemini15Flash,
});
export const askForIngredientsFlow = ai.defineFlow(
{
name: "askForIngredientsFlow",
inputSchema: z.string(),
outputSchema: z.string(),
},
async (food: string) => {
const { text } = await ai.generate({
model: gemini15Flash,
prompt: `${food} の材料を教えて下さい.`,
});
return text;
}
);
フローを実行するには、まず Genkit Developer UI
を起動して、必要なフローのコードを読み込む必要があります。
genkit start -- pnpm tsx --watch index.ts
genkit start
コマンドを実行すると Genkit Developer UI
が起動し、http://localhost:4000/flows
にアクセスすると、先ほど定義したフローが登録されていることを確認できます。
この状態で、別のコンソールを立ち上げ、以下のコマンドを実行します。
genkit flow:run askForIngredientsFlow '"カレー"'
出力結果
genkit flow:run askForIngredientsFlow '"カレー"'
[Telemetry Server] initialized local file trace store at root: /Users/mor/repos/my-work/github.com/morooka-akira/genkit-sample2/.genkit/traces
Telemetry API running on http://localhost:4034
Running '/flow/askForIngredientsFlow' (stream=false)...
Result:
"カレーの材料は、使用するカレーの種類(例えば、日本のカレー、インドカレー、タイカレーなど )によって大きく異なります。 ここでは、一般的な日本の家庭風カレーの材料を基本として、いくつかのバリエーションも紹介します。\n\n\n**基本材料(4人分):**\n\n* **野菜:** 玉ねぎ 1個 、にんじん 1/2本、じゃがいも 2個\n* **肉:** 豚肉(こま切れ、バラ肉など)200g or 鶏むね肉 200g or 牛肉(薄切りなど)200g (お好みで)\n* **カレー粉:** 大さじ3〜4 (辛さはお好みで調整)\n* **カレールー:** 4〜5かけ (ルーの種類によって適量を調整)\n* **水:** 800ml〜1000ml (野 菜の量やルーによって調整)\n* **油:** 大さじ1\n* **塩:** 少々\n* **砂糖:** 小さじ1 (お好み で)\n* **ウスターソース:** 大さじ1 (お好みで)\n\n\n**その他、入れると美味しい材料:**\n\n* **リンゴ:** 1/4個 (煮込むと甘みが増す)\n* **トマト:** 1個 (酸味とコクが加わる)\n* **バター:** 10g (コクが増す)\n* **生姜:** 1かけ (すりおろして入れると風味がアップ)\n* **ニンニク:** 1かけ (すりおろして入れると風味がアップ)\n* **セロリ:** 1/2本 (みじん切りにして入れると 風味が増す)\n* **ホールスパイス:** クミンシード、コリアンダーシード、クローブなど (本格的 な風味を出す)\n\n\n**作り方のバリエーション:**\n\n* **キーマカレー:** ひき肉を使用し、野菜を細かく刻むか、ミキサーにかける。\n* **チキンカレー:** 鶏肉を使用し、ココナッツミルクを加えるとマイルドな味わいになる。\n* **ビーフカレー:** 牛肉を使用し、赤ワインを加えるとコクが深まる。\n* **野菜カレー:** 肉を使わず、様々な野菜をたっぷり入れる。\n\n\n**注意:**\n\n* 材料の分量はあくまで目安です。お好みで調整してください。\n* ルーの種類によって、水の量や煮込み時間が異なりますので、ルーのパッケージの表示をよく確認してください。\n\n\n上記を参考に、自分好みのカレーを作ってみてください!\n"
上記のように、ターミナル上で生成結果を確認できます。
また、この記事では触れていませんが、Genkit Developer UI
から直接フローを実行することも可能です。
触ってみた所感
私は普段、LangChain(Python)を使ってLLMを扱うことが多いのですが、Firebase Genkitは統合環境としての機能が非常に充実していると感じました。LangChainエコシステム(LangChain, LangGraph, LangSmith)と同等の機能が用意されていて、アプリケーション構築の観点でも十分に検討できるツールだと思います。
特に「すでにFirebaseエコシステムに乗っていて、ある機能をLLM化したい」というユースケースであれば、優先的に選択肢に入れてもいいのではないでしょうか。
ただし、まだB版ということもあり、ドキュメントがリンク切れしているなど荒い部分も見られます。インタフェースも頻繁変わることが予想されるため、プロダクトへの導入は慎重に行うほうが良いかと思います。
良さそうな点
-
ローカルの開発環境(Genkit Developer UI)が充実している
ローカルで実行をトレースできるのは非常に便利そうです。 -
Google CloudやFirebaseとの連携が簡単
各種環境(Cloud Function, Cloud Run)へのデプロイが簡単にできるほか、Firebase Authによる認証やモニタリング・ロギングの連携がある点はメリットです。Google Cloudが基本環境のチームは特に良いかと思います。 -
他ベンダーモデルの対応や他ツールのプラグインも一通り存在
OpenAIやAnthropicなどのLLMにもプラグインで対応できる点は良いと思います。
微妙かなと感じた点
-
プロンプト管理が独自フォーマット(Dotprompt)であるところ
Genkitで行き切る場合は良いですが、将来的に他のフレームワークへ移植したい場合にはコストがかかりそうだと感じました。 -
基本がTypeScript(Goもあるが)のSDKであるところ
かなり個人的な感想ではありますが、複雑なパラメータ構成などは、Pythonのほうが記述しやすいと感じることがあります。また、現時点では、LangChainのほうがSDKとしての抽象レベルなどが高く使いやすいかなと感じました。
最後に
1記事では全機能を網羅しきれませんでしたが、今後は各機能を個別に試してみて、使い勝手を探ってみたいと思います。特にRAGなどはサクッと作れそうな気がするので、詳細触ってみたいと思います。
Discussion