スクエニ ITエンジニア ブログ

Firebase Genkit で RAG app をつくってみる

RAG (Retrieval-Augmented Generation, 検索拡張生成) を使用した application を開発できる framework として、Firebase Genkit なるものがあると知りました。

Firebase は、認証や database など web app やモバイルゲームなどの開発に便利な様々な機能を提供している platform です。Firebase を利用することによってこういった機能の組み込みの敷居が下がり、また Google Cloud などの外部サービスとの連携による拡張も可能なので、小さくはじめて徐々にスケールしていく開発に威力を発揮します。

今回は、この Firebase Genkit を使った RAG application の開発体験がどのようなものなのか、公式 document Retrieval-augmented generation (RAG) に沿って見ていきましょう。

License

Genkit の LICENSE は、Apache License 2.0 です。また、上記した document にあるコードもページ末尾の記述から同様に Apache License 2.0 と明記されています。

開発環境をつくる

devcontainer の準備

いつもながら Visual Studio CodeDev Container 機能を使用します。他の環境をご利用の方はお手数ですが、以下の構築手順を参考にして環境構築してください。

このブログでは他にも Dev Container に関する記事 があります。Dev Container って何? と思われたかたにもご覧いただけたら幸いです。

今回は Firebase で Node.js を使うのと、開発言語として Go を使用します。これらは devcontainer feature を使用して追加します。

{
	"name": "genkit-dev",
	"image": "mcr.microsoft.com/devcontainers/base:bullseye",
	"mounts": [
		"source=${localEnv:HOME}/.config/gcloud/application_default_credentials.json,target=/home/vscode/.gcloud/application_default_credentials.json,type=bind,consistency=cached"
	],
	"containerEnv": {
		"GOOGLE_APPLICATION_CREDENTIALS": "/home/vscode/.gcloud/application_default_credentials.json"
	},
	"features": {
		"ghcr.io/devcontainers/features/go:1": {},
		"ghcr.io/devcontainers/features/node:1": {}
	},
	"remoteEnv": {
		"GCLOUD_PROJECT": "<gcloud-project>",
		"GCLOUD_LOCATION": "us-central1"
	}
}

Google Cloud の project ID と location の部分は適宜変更してください。

今回は Vertex AI を使用するため、web console などから API を有効にしておきます。また、host 側で Google Cloud SDK を install し、ADC (Application Default Credential) が使用できるよう セットアップ しておきます。

devcontainer.json では ADC の credential file を devcontainer 内に mount し、Vertex AI への認証が通るようにしています。

Genkit の install と init

devcontainer を起動したら、Get started with Genkit using Go に従って genkit の setup を行います。

$ npm i -g genkit

added 250 packages in 9s
...

続けて genkit CLI を使用して workspace を初期化します。

$ genkit init
Genkit CLI and Developer UI use cookies and similar technologies from Google
to deliver and enhance the quality of its services and to analyze usage.
Learn more at https://policies.google.com/technologies/cookies
Press "Enter" to continue

? Select a model provider: 
  Google AI 
? Select a model provider: Google Cloud Vertex AI
✔ Successfully installed Go packages
? Would you like to generate a sample flow? (Y/n) 

? Would you like to generate a sample flow? Yes
✔ Successfully generated sample file (genkit.go)
Run the following command to enable Vertex AI in your Google Cloud project:

  gcloud services enable aiplatform.googleapis.com

Genkit successfully initialized.

model は Google AI と Google Cloud Vertex AI の選択肢がありますが、この記事では Google Cloud Vertex AI を使います。続けて sample flow をつくる選択をすると main.go が生成されます。( main.go がすでにあると genkit.go などに避けてくれるようです)

Go で使用する package の install

必要なものを get しておきます。

go mod init genkit_rag
go get github.com/tmc/langchaingo/textsplitter
go get github.com/firebase/genkit/go/plugins/localvec
go get github.com/firebase/genkit/go/plugins/vertexai

Genkit を実行してみる

環境のセットアップが完了したので、Genkit を起動してみます。これにより、Genkit Developer UI (web UI) が起動します。

genkit start

localhost:4000 でブラウザから接続してみましょう。

Genkit web UI

かなり立派な web UI が利用できます。sample flow を生成しただけの初期状態では Flows のところに menuSuggestionFlow のみ存在しています。上の画像は、それを実行したときのものです。
一部の機能は Vertex AI 経由で費用がかかりますので、注意しながらメニューを見て回ってみましょう。model の利用を試したり、実行の履歴を確認したりと開発にすぐに使える便利な機能が入っています。縮小版 Vertex AI のような感じですね。

RAG 実装サンプルを試す

Genkit の準備が整いましたので、今回の目的である RAG application の実装を試してみます。

document Retrieval-augmented generation (RAG) に沿って、生成されたサンプルに実装を足していきます。
結果としてできたもの main.go を置いておきます。

このコード追加により、 indexMenumenuQA という flow が追加されます。この flow というのは Genkit の処理単位のようなもので、Go で実装に追加すると web UI の Flows のリストに追加され、そこから実行できるようになります。(後で見ますが、CLI からも実行できます)

indexMenu flow を実行する

indexMenu flow は指定された input に従って pdf を開き、そこから text を取得して適当に分割したものから embedding と index を生成、local の vector store に保存します。コードで指定された storage name menuQA に従い、実際の file は /tmp/__db_menuQA.json として保存されます。( /tmpos.tmpDir() による)
※ 本番環境では local storage の利用は推奨されません。

まずは、indexMenu flow に対して menu.pdf を与えて vector index を生成しておきます。この pdf は、Microsoft Copilot に頼んで生成してもらった和食のメニューを pdf 化したものです。container 内にこの menu.pdf を置き、その path を input(JSON) に書いて Run で実行してください。

menuQA flow では、input (question) によってまず保存された vector store を検索し、その結果を context として取得、それを用いて input に基づいた回答を生成しています。コード内の prompt では、与えられた menu 情報のみを改変せず使用して返答を生成するように指示しています。

先の flow でデータができたので、menuQA flow に対して質問をしてみます。

Do you have any fish dishes?

以下のように結果が得られました。

Yes, there are several fish dishes on the menu:

* **Sushi Platter:**  Assorted sushi with fresh fish and vegetables.
* **Tempura:** Lightly battered and deep-fried shrimp and vegetables (shrimp is a type of fish). 
* **Chawanmushi:** Steamed egg custard with seafood and vegetables (likely includes fish). 
* **Seaweed Salad:**  Fresh seaweed with a light sesame dressing (seaweed is technically a type of algae). 

Let me know if you have any other questions! 

menu.pdf に書かれた内容から fish 関係のものを正しく (学術的には正確ではないものが含まれていますが 😆) 抽出し、それを提案できています。なぜそれを選択したかの補足まで括弧書きで追加されていますね。最後に生成 AI chat でよく見る口癖 (他にも質問があったら言ってね) が出ているのがちょっとおもしろいです。

pdf から情報を取得してそれを保存しておき、必要に応じてそこからデータを取得してそれに基づいて回答を生成するという RAG application の実装を確認できました。

サンプルの構成概要を図に表してみます。

サンプルの構成概要図

以下のようにして CLI から flow を実行することもできるようです。

genkit flow:run menuQA '"Do you have any fish dishes?"'

(元ページではこの後 indexer / retriever を自分で実装する例も書かれていますがここでは扱いません)

Deploy

開発したものを Chatbot のような application として使用するには、これを API server として deploy すればいいようです。こちら に Cloud Run へ deploy する方法が書かれています。これにより、 https:///menuSuggestionFlow のように flow 単位で endpoint として serve することができるようになります。

application として利用するには、deploy したものを API として利用する frontend や server を別で実装する必要があります。これがまたちょっと手間そうですね。Slack bot とかにするほうが楽かもしれません。

費用を確認する

Firebase Genkit 自体には費用がかからない開発 framework という立ち位置のようです。今回は index store も local を使うので費用はかかっていませんが、実サービスで利用する際にはデータ量などの要件に耐えるものを選択する必要があります。

テキスト生成と embedding 生成で Vertex AI を使うのでその費用がかかります。今回でいうと gemini-1.5-flashtext-embedding-004 の model を使用しており、Vertex AI pricing の以下の部分にあたります。(と思います)

  • Gemini > Text Input / Output
  • Embedding (のところでいいんだろうか)

いずれも大量にならなければそこまで高くはなさそうです。embedding は pdf から read した text を split したものを処理するようなので、pdf が含む text 量に気をつけるとよさそうです。

Genkit の印象

コードと web UI のようなものを結びつけてよい開発体験をつくる、Firebase らしい product と感じました。起動中に無駄に CPU を使っているようなこともなく、しかし hot reloading のような気が効いたところもあります。web UI も直感的で使いやすく、また必要な設定も少なくすぐに立ち上げることができます。

Traces (flow の実行履歴)

個人的には Vertex AI で Chatbot などをノーコードでつくる Agent Builder であるとか Dialogflow CX といったあたりは一見したとっつきやすさはあるものの実際にやってみると直感的な理解が難しかったり、一度作ったもののその後のメンテはコードのほうがしやすいのではないかと感じたり、そもそも思ったように動かなかったりという経験があるのでむしろ Genkit のようなアプローチのほうが好感が持てます。また Dialogflow CX などは Genkit の flow をつなぐ一段階上の機能も持つところからしても、単純な比較対象として合っていないかもしれません。

まとめ

実装は少々たいへんですが、様々なことに応用できそうです。よく事例としてあがるのは社内規定 Q&A bot とかですね。特定組織内などで秘密にしておきたいデータを AI に知識として食わせて応答をするような、カスタム bot をつくることができます。

また、メンテナンスもすごく楽とはいかないかもしれません。元になるデータが更新される場合は取り込みをどれくらいの頻度で行うかといった考慮も必要になりますし、serverless な構成も可能ですが、長期的に安定運用するにはある程度の知識を持った人が面倒を見れるようにしておく必要があるでしょう。

上にもすこし書きましたが Agent Builder のような選択肢もあるなかこういったものをわざわざ採用するか悩ましいところではあります。要件に応じて適切に選択・利用できるようにしておけるとよいですね。

Troubleshooting

genkit start に失敗する

$ genkit start
Starting app at `.`...
Genkit Tools API: http://localhost:4000/api
  ...
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0xa8 pc=0x20e1f8]

goroutine 1 [running]:
github.com/invopop/jsonschema.(*Reflector).ReflectFromType(0x4000575cb8, {0x0, 0x0})
        /go/pkg/mod/github.com/invopop/jsonschema@v0.12.0/reflect.go:170 +0x28
github.com/invopop/jsonschema.(*Reflector).Reflect(0x40003880f0?, {0x0?, 0x0?})
        /go/pkg/mod/github.com/invopop/jsonschema@v0.12.0/reflect.go:165 +0x44
github.com/firebase/genkit/go/internal/base.InferJSONSchema(...)
        /go/pkg/mod/github.com/firebase/genkit/go@v0.1.1/internal/base/json.go:76
github.com/firebase/genkit/go/genkit.defineFlow[...](0x4000237cc0, {0xc8e43a, 0x9}, 0x400038e090, {0x0, 0x0, 0x4000398500})
        /go/pkg/mod/github.com/firebase/genkit/go@v0.1.1/genkit/flow.go:207 +0xb8
github.com/firebase/genkit/go/genkit.DefineFlow[...]({0xc8e43a, 0x9}, 0x4000388140, {0x0, 0x0, 0x0})
        /go/pkg/mod/github.com/firebase/genkit/go@v0.1.1/genkit/flow.go:177 +0xac
main.main()
        /workspaces/genkit-rag/main.go:77 +0x240
exit status 2
App process exited with code 1, signal null
Error: Timed out while waiting for code to load.

document Retrieval-augmented generation (RAG) の例では以下のようになっています。

genkit.DefineFlow(
  "indexMenu",
  func(ctx context.Context, path string) (any, error) {
    ...
    return nil, err
  },
)

が、ひとつめの return の型が any だとこのエラーが発生するようです。型を string に、return のひとつめも nil から "" に変更すると動作するようになります。変更後のコードは main.go を参照してください。(サンプルコードの interface 仕様が古いのでしょうか?ちゃんと調べていません)

この記事を書いた人

記事一覧
SQUARE ENIXでは一緒に働く仲間を募集しています!
興味をお持ちいただけたら、ぜひ採用情報ページもご覧下さい!