見出し画像

Firebase Genkitを利用してRAG実装を試す

はじめに

Firebaseは、Googleが提供するモバイル・ウェブアプリ開発プラットフォームで、認証、データベース、ストレージ、ホスティングなどの機能をクラウドサービスとして提供し、開発者がバックエンドを気にせずフロントエンド開発に集中できるようにします。

Firebase Genkitは、本番環境に対応したAI搭載アプリの構築、デプロイ、監視に役立つオープンソースフレームワークです。Firebaseの延長線上にあり、Firebaseの各種サービスとシームレスに連携できるよう設計されています。

Genkitを使うと、カスタムコンテンツ生成、セマンティック検索、非構造化入力処理、ビジネスデータでの質問応答、自律的意思決定、ツール呼び出し調整などのAI機能を備えたアプリが作成できます。


Genkitのインストールと環境設定

Genkit CLIをインストールします。

npm i -g genkit

新しいNodeプロジェクトを作成します。

mkdir genkit-rag && cd genkit-rag npm init -y

Genkitプロジェクトを初期化します。

genkit init

Gemini (Google AI)を選択します。
Google AI Studioを使用してGemini APIのAPIキーを生成します。
その後、GOOGLE_GENAI_API_KEY環境変数にキーを設定します。

export GOOGLE_GENAI_API_KEY=<your API key>

Genkitをスタートします。

genkit start

RAGの概要と利点

Retrieval-Augmented Generation (RAG)は、外部データソースの情報を言語モデル(LLM)のレスポンスに組み込む手法です。RAGを使用することで、LLMに特定のドメイン知識を提供し、より正確で関連性の高いレスポンスを生成できます。また、データソースを継続的に更新できるため、LLMは最新の情報をすぐに活用できます。

 FirestoreをRAGのデータソースとして使用する

この例では、Firestoreをデータソースとして使用します。defineFirestoreRetriever関数を使って、Firestoreのコレクションからデータを取得するためのRetrieverを定義します。

実装例

firebaseのプロジェクトの作成

以下のチュートリアルを参考にされるのがよいかと思います。

Firestoreの設定 

<your project id>を作成したプロジェクトIDに置き換えて、コマンドを実行します。これにより、merchコレクションのembeddingフィールド用のベクターインデックスが作成されます。

gcloud alpha firestore indexes composite create --project=<your project id> --collection-group=merch --query-scope=COLLECTION --field-config=vector-config='{"dimension":"768","flat": "{}"}',field-path=embedding

Retrieverの作成

このコードは、Firebase GenkitとFirestoreを組み合わせて、RAGベースの顧客サービスAIを実装するための基本的な構成を示しています。

  1. 必要なモジュールのインポートとFirebase SDKの初期化

  2. Genkitの設定(プラグイン、ログレベル、トレースストアなど)

  3. Firestoreをデータソースとして使用するためのRetrieverの定義

  4. 質問と検索結果のスキーマ定義

  5. 顧客サービスAIのためのDotPromptの定義

  6. Firebase FunctionであるmerchFlowの定義

    • 認証ポリシーの設定(メール確認済みユーザーのみ)

    • ユーザーの質問に基づいて関連商品をFirestoreから検索

    • 検索結果とDotPromptを使用してAIレスポンスを生成

    • AIレスポンスをテキストで返す

  7. 別のモジュール(merch_embed.ts)からインデックス作成用のフローindexFlowをエクスポート

genkit-rag/src/index.ts

// Import necessary modules
import { retrieve } from "@genkit-ai/ai";
import { configureGenkit } from "@genkit-ai/core";
import { firebaseAuth } from "@genkit-ai/firebase/auth";
import { onFlow } from "@genkit-ai/firebase/functions";
import { gemini15Pro, textEmbeddingGecko001 } from "@genkit-ai/googleai";
import * as z from "zod";
import { defineFirestoreRetriever, firebase } from "@genkit-ai/firebase";
import { googleAI } from "@genkit-ai/googleai";
import { getFirestore } from "firebase-admin/firestore";
import { defineDotprompt } from '@genkit-ai/dotprompt';
import { dotprompt } from '@genkit-ai/dotprompt';
import * as admin from 'firebase-admin';
import * as functions from 'firebase-functions';

// **Firebase configuration**
// **NOTE:** Replace these example values with your actual Firebase project credentials.
// You can find these values in your Firebase project settings.
const firebaseConfig = {
  apiKey: "YOUR_API_KEY",
  authDomain: "YOUR_PROJECT_ID.firebaseapp.com",
  projectId: "YOUR_PROJECT_ID",
  storageBucket: "YOUR_PROJECT_ID.appspot.com",
  messagingSenderId: "YOUR_MESSAGING_SENDER_ID",
  appId: "YOUR_APP_ID",
  measurementId: "YOUR_MEASUREMENT_ID"
};

// Initialize Firebase Admin SDK
const app = admin.initializeApp(firebaseConfig);

// Get Firestore instance
const firestore = getFirestore(app);

// Configure Genkit with plugins and settings
configureGenkit({
  plugins: [
    dotprompt(),
    firebase(),
    googleAI({ apiVersion: ['v1', 'v1beta'] }),
  ],
  flowStateStore: 'firebase',
  logLevel: 'debug',
  traceStore: 'firebase',
  enableTracingAndMetrics: true,
});

// Define Firestore retriever for merchandise data
const retrieverRef = defineFirestoreRetriever({
  name: "merchRetriever",
  firestore,
  collection: "merch",  // Collection containing merchandise data
  contentField: "text",  // Field for product descriptions
  vectorField: "embedding", // Field for embeddings
  embedder: textEmbeddingGecko001, // Embedding model
  distanceMeasure: "COSINE", // Similarity metric
});

// Input schema for the question and retrieved data
const MerchQuestionInputSchema = z.object({
  data: z.array(z.string()), // Retrieved product descriptions
  question: z.string(), // User's question
});

// Define the DotPrompt for the customer service AI
const merchPrompt = defineDotprompt(
  {
    name: 'merchPrompt',
    model: gemini15Pro,
    input: {
      schema: merchQuestionInputSchema
    },
    output: {
      format: 'text'
    },
    config: {
      temperature: 0.3 // Control randomness of responses
    },
  },
  `
  You are a customer service AI for an online store. 
  Given a customer's question and product information from the database, 
  recommend the most suitable products.

  Product Database:
  {{#each data~}}
  - {{this}}
  {{~/each}}

  Customer's Question:
  {{question}}
  `
);

// Firebase Function to handle customer questions
export const merchFlow = onFlow(
  {
    name: "merchFlow",
    inputSchema: z.string(), // Input is the customer's question
    outputSchema: z.string(), // Output is the AI's response
    authPolicy: firebaseAuth((user) => {
      if (!user.email_verified) {
        throw new Error("Verified email required to run flow");
      }
    }),
  },
  async (question) => {
    // Retrieve relevant products from Firestore
    const docs = await retrieve({
      retriever: retrieverRef,
      query: question,
      options: { limit: 5 }, // Get top 5 matches
    });

    // Generate a response using the DotPrompt and retrieved data
    const llmResponse = await merchPrompt.generate({
      input: {
        data: docs.map(doc => doc.content[0].text || ''),
        question,
      },
    });

    return llmResponse.text(); // Return the AI's response
  }
);

// Export the index flow from another module (merch_embed.ts)
export { embedFlow } from './merch_embed';

商品データをEmbeddingしてFirestoreへ格納

このコードは、商品データのインデックス作成プロセスを自動化するための一連の処理を定義しています。具体的には、テキストファイルから商品データを読み込み、それをチャンクに分割し、各チャンクをエンベディングしてFirestoreに保存します。このフローを実行することで、商品データがRetrieverから利用できるようになります。

  1. 必要なモジュールのインポートとGenkitの設定(Google AIプラグインの使用)

  2. Firestoreインスタンスの初期化

  3. データ処理フローembedFlowの定義

    • 入力と出力のスキーマ定義(ここでは入力も出力も期待されない)

    • 以下の処理を順番に実行

      1. テキストファイルからデータを読み込む

      2. テキストを'---'を区切り文字としてチャンクに分割

      3. チャンクをFirestoreにインデックス化

  4. Firestoreにチャンクをインデックス化するための関数indexToFirestoreの定義

    • 各チャンクに対して以下の処理を実行

      1. チャンクのテキストをtextEmbeddingGecko001モデルを使用してエンベディング

      2. テキストとエンベディングをFirestoreに追加

  5. ファイルからテキストコンテンツを読み込むための関数extractTextの定義

genkit-rag/src/merch_embed.ts

import { configureGenkit } from "@genkit-ai/core";
import { embed } from "@genkit-ai/ai/embedder";
import { defineFlow, run } from "@genkit-ai/flow";
import { textEmbeddingGecko001, googleAI } from "@genkit-ai/googleai";
import { FieldValue, getFirestore } from "firebase-admin/firestore";
import { chunk } from "llm-chunk";
import * as z from "zod";
import { readFile } from "fs/promises";
import path from "path";

// Configuration for indexing process
const indexConfig = {
  collection: "merch",  // Firestore collection to store the data
  contentField: "text", // Field name for the text content
  vectorField: "embedding", // Field name for the embedding vector
  embedder: textEmbeddingGecko001, // Embedder model to use
};

// Configure Genkit with Google AI plugin
configureGenkit({
  plugins: [googleAI({ apiVersion: ['v1', 'v1beta'] })],
  enableTracingAndMetrics: false,
});

// Initialize Firestore instance
const firestore = getFirestore();

// Define the data processing flow
export const embedFlow = defineFlow(
  {
    name: "embedFlow", // Name of the flow
    inputSchema: z.void(), // No input is expected
    outputSchema: z.void(), // No output is returned
  },
  async () => {
    // 1. Read text data from file
    const filePath = path.resolve('./shop-merch-google.txt');
    const textData = await run("extract-text", () => extractText(filePath));

    // 2. Split text into chunks using '---' as delimiter
    const chunks = await run("chunk-it", async () => chunk(textData, { delimiters: '---' }));

    // 3. Index chunks into Firestore
    await run("index-chunks", async () => indexToFirestore(chunks));
  }
);

// Function to index chunks into Firestore
async function indexToFirestore(data: string[]) {
  for (const text of data) {
    // Generate embedding for the text chunk
    const embedding = await embed({
      embedder: indexConfig.embedder,
      content: text,
    });

    // Add the text and embedding to Firestore
    await firestore.collection(indexConfig.collection).add({
      [indexConfig.vectorField]: FieldValue.vector(embedding),
      [indexConfig.contentField]: text,
    });
  }
}

// Function to read text content from a file
async function extractText(filePath: string) {
  const f = path.resolve(filePath);
  return await readFile(f, 'utf-8');
}

genkit-rag/shop-merch-google.txt

Geminiに作成してもらった仮の商品データになります。

Here is the English version:

**Google Pixel Watch - Wi-Fi/Bluetooth - Matte Black Stainless Steel Case with Obsidian Active Band - Google Store**

https://store.google.com/product/google_pixel_watch

The Pixel Watch is Google's first smartwatch. It integrates Fitbit's health management features with Wear OS by Google's smart functions, providing a seamless experience on your wrist, from health management to daily support.

* **Price:** 39,800 yen
* **Reviews:** 4.3 stars (Google Store reviews)

---

**Google Pixel Buds Pro - Coral - Google Store**

https://store.google.com/product/pixel_buds_pro

Google Pixel Buds Pro are wireless earbuds that offer active noise cancellation and clear, high-quality sound. They are also comfortable to wear, making them suitable for long hours of use without fatigue.

* **Price:** 23,800 yen
* **Reviews:** 4.2 stars (Google Store reviews)

---

**Nest Hub (2nd gen) - Chalk - Google Store**

https://store.google.com/product/nest_hub_2nd_gen

The Nest Hub (2nd gen) is a smart display with Google Assistant. It allows you to play music and videos, control smart home devices, make video calls, and more. It also features sleep tracking capabilities to support your sleep.

* **Price:** 11,000 yen
* **Reviews:** 4.5 stars (Google Store reviews)

---

**Google Pixel 7a - Charcoal - Google Store**

https://store.google.com/product/pixel_7a

The Google Pixel 7a is a high-performance smartphone equipped with the Google Tensor G2 chip. It excels in camera performance, allowing you to take beautiful photos and videos. Its long-lasting battery is also one of its attractive features.

* **Price:** 53,900 yen
* **Reviews:** 4.6 stars (Google Store reviews)

実行結果

Auth JSONに{"email_verified": true}を追加し、探している商品についてAIに尋ねてみます。

参考


この記事が気に入ったらサポートをしてみませんか?