🤖

Gemini 1.5のJSON Modeを使用してダミーデータを用意する

2024/11/08に公開

モチベーション

  • テストの際に、入力を適当に埋めてくれるダミーデータがほしい
    • ある入力をテストしたい(テスト対象以外は適当な文字列で埋めたい)
    • テストのために新しいユーザーを作成したい(テスト対象はその先なので入力は何でも良い)
  • ただし、あまりにも適当な値を使用すると実際のプロダクトと乖離してしまう。これは避けたい
    • テスト環境を第三者(営業先等)が閲覧する可能性がある
    • 適当な値が固定値だと不具合に気付きにくい
  • ダミーデータ作成はFakerが有名だが、日本語対応していない出力も多い

本題

GeminiのJSON Modeを使用して、日本語ダミーデータの作成を試みます。
https://ai.google.dev/gemini-api/docs/structured-output?lang=node

JSON Modeとは

スキーマを提供することでGeminiの応答を提供したJSONフォーマットに制御できるモードです。

スキーマの提供方法には

  • プロンプト内のテキストとして提供する方法
  • モデル構成時にスキーマを提供する方法

の2通りがあり、2024年11月現在どちらの方法もGemini 1.5 FlashGemini 1.5 Proの両方で使用できます。

プロンプト内のテキストとして提供する方法

npm install @google/generative-ai
import { GoogleGenerativeAI } from '@google/generative-ai'

export const generateDummyUsersByInPrompt = async () => {
  const geminiApiKey = process.env.NEXT_PUBLIC_GEMINI_API_KEY
  if (!geminiApiKey) {
    return
  }

  const genAI = new GoogleGenerativeAI(geminiApiKey)
  const model = genAI.getGenerativeModel({
    model: 'gemini-1.5-flash',
    generationConfig: { responseMimeType: 'application/json' },
  })

  const prompt = `
・以下のJSONスキーマに基づいて、テスト用のダミーデータを作成してください。
・各値は日本語で出力してください。

{
  type: 'object',
  description: 'ユーザーアカウントのデータスキーマ',
  properties: {
    response: {
      type: 'array',
      description: 'ユーザーアカウント作成のレスポンスデータの配列',
      items: {
        type: 'object',
        properties: {
          username: {
            type: 'string',
            description: 'ユーザー名',
          },
          userId: {
            type: 'string',
            description: 'ユーザーID',
          },
          email: {
            type: 'string',
            description: 'メールアドレス',
          },
          password: {
            type: 'string',
            description: 'パスワード',
          },
          biography: {
            type: 'string',
            description: '自己紹介',
          },
          isPublic: {
            type: 'boolean',
            description: 'アカウントの公開設定',
          },
        },
        required: ['username', 'userId', 'email', 'password', 'isPublic'],
      },
    },
    required: ['response'],
  },
}
`

  const result = await model.generateContent(prompt)
  const text = result.response.text()

  return text
}

上記のプロンプトに対する応答は下記。

{
  "response": [
    {
      "username": "山田太郎",
      "userId": "yamada_taro",
      "email": "yamada@example.com",
      "password": "password123",
      "biography": "日本の東京に住んでいます。趣味は読書と旅行です。",
      "isPublic": true
    },
    {
      "username": "鈴木花子",
      "userId": "suzuki_hanako",
      "email": "suzuki@example.com",
      "password": "hanako123",
      "biography": "海外ドラマが好きです。最近は韓国ドラマにもハマっています。",
      "isPublic": false
    }
  ]
}

モデル構成時にスキーマを提供する方法

上記の方法はresponseMimeType: 'application/json'によりJSON形式で返却されることは保証されていますが、スキーマの形式までは保証されていません。

一方で、responseSchemaにスキーマを指定する方法であればスキーマの形式まで保証することができます。
以前はGemini 1.5 Proでのみ使用可能でしたが、2024年8月からはGemini 1.5 Flashでも使用できるようになりました。

https://ai.google.dev/gemini-api/docs/changelog

https://x.com/OfficialLoganK/status/1829678117054792160

import { GoogleGenerativeAI } from '@google/generative-ai'

export const generateDummyUsersByThroughConfiguration = async () => {
  const geminiApiKey = process.env.NEXT_PUBLIC_GEMINI_API_KEY
  if (!geminiApiKey) {
    return
  }
  const genAI = new GoogleGenerativeAI(geminiApiKey)

  const schema = {
    type: SchemaType.OBJECT,
    description: 'ユーザーアカウントのデータスキーマ',
    properties: {
      response: {
        type: SchemaType.ARRAY,
        description: 'ユーザーアカウント作成のレスポンスデータの配列',
        items: {
          type: SchemaType.OBJECT,
          properties: {
            username: {
              type: SchemaType.STRING,
              description: 'ユーザー名',
            },
            userId: {
              type: SchemaType.STRING,
              description: 'ユーザーID',
            },
            email: {
              type: SchemaType.STRING,
              description: 'メールアドレス',
            },
            password: {
              type: SchemaType.STRING,
              description: 'パスワード',
            },
            biography: {
              type: SchemaType.STRING,
              description: '自己紹介',
            },
            isPublic: {
              type: SchemaType.BOOLEAN,
              description: 'アカウントの公開設定',
            },
          },
          required: ['username', 'userId', 'email', 'password', 'isPublic'],
        },
      },
    },
  }

  const model = genAI.getGenerativeModel({
    model: 'gemini-1.5-flash',
    generationConfig: {
      responseMimeType: 'application/json',
      responseSchema: schema,
    },
  })

  const prompt = `
・JSONスキーマに基づいて、テスト用のダミーデータを作成してください。
・各値は日本語で出力してください。
`

  const result = await model.generateContent(prompt)
  const text = result.response.text()

  return text
}

上記のプロンプトに対する応答は下記。

{
  "response": [
    {
      "email": "test@example.com",
      "password": "password123",
      "username": "山田太郎",
      "biography": "こんにちは!山田太郎です。よろしくお願いします。",
      "isPublic": true,
      "userId": "user123"
    }
  ]
}

有効なフィールド

便利なJSON Modeですが、注意点としてスキーマの下記以外のフィールドは無視されます

  • enum
  • items
  • maxItems
  • nullable
  • properties
  • required

そのため、上記以外の制御を行いたい場合や期待した応答が得られない場合はプロンプトを調整してあげる必要があります。
githubにも同様の内容が記載されています。

If you aren't seeing the results you expect, add more context to your input prompts or revise your response schema. For example, review the model's response without controlled generation to see how the model responds. You can then update your response schema that better fits the model's output.

また、上述の応答だとまだまだダミーデータ感があるので、下記のようにプロンプトを修正してみます。

  const prompt = `
・responseは配列長5で返却してください。
・usernameは日本語で返却してください。
・userIdは^[a-zA-Z0-9_]{5,20}の正規表現を満たす、英単語2語以上を組み合わせた値で返却してください。
・passwordはランダムな文字列を返却してください。
・biographyは日本語で100文字以上160文字以内で返却してください。
`
{
  "response": [
    {
      "email": "haruka.sato@example.com",
      "isPublic": true,
      "password": "YQ3l!s0m",
      "userId": "haruka_sato",
      "username": "佐藤 春香",
      "biography": "新しいことに挑戦するのが大好きです。最近はヨガにハマっています。美味しいものを食べるのも大好きで、よく食べログでお店を探しています。"
    },
    {
      "email": "kenji.suzuki@example.com",
      "isPublic": false,
      "password": "F7n%b2U+",
      "userId": "kenji_suzuki",
      "username": "鈴木 健二",
      "biography": "読書と映画鑑賞が趣味です。最近はSF小説にはまっています。最近はまっている映画は、ブラックパンサーです。"
    },
    {
      "email": "ayaka.tanaka@example.com",
      "isPublic": true,
      "password": "8V5$@p%",
      "userId": "ayaka_tanaka",
      "username": "田中 彩佳",
      "biography": "旅行が好きで、色々な場所に行ってみたいと思っています。最近は韓国ドラマにはまっています。美味しいコーヒーを飲むのが大好きです。"
    },
    {
      "email": "tomohiro.ito@example.com",
      "isPublic": false,
      "password": "W9#rK?q",
      "userId": "tomohiro_ito",
      "username": "伊藤 智弘",
      "biography": "音楽を聴くのが好きです。最近はジャズにはまっています。最近はまっているアーティストは、マイルス・デイビスです。"
    },
    {
      "email": "akari.nakano@example.com",
      "isPublic": true,
      "password": "K2Z$c7!",
      "userId": "akari_nakano",
      "username": "中野 明里",
      "biography": "最近はまっていることは、ランニングです。毎日少しずつ走っています。目標はフルマラソンを完走することです。美味しいパン屋を見つけるのも大好きです。"
    }
  ]
}

まだまだ調整が必要ですが、多少はマシになりました。
文字数指定が上手くいかないのはなぜなのでしょうか…

あとはこのメソッドをテストの最初に呼んであげれば良さそうです。

Discussion