GeminiAPIで手書きのアンケート画像を読み込んで分析し、kintoneに保存しよう

目次

caution
警告

2020年8月改訂のセキュアコーディングガイドライン に抵触する内容が含まれています。認証情報が漏洩した場合の影響を考慮して慎重に検討してください。
該当箇所:JavaScriptプログラムの12行目。

はじめに

お客様からの手書きアンケートをkintoneに取り込んで分析したいことはありませんか。
この記事では、手書きのアンケートをGeminiAPIを使って読み込んで分析し、kintoneに保存するカスタマイズを紹介します。

完成イメージ

アプリの新規入力フォームで手書きアンケートの画像ファイルを添付して保存すると、Geminiが画像を解析してテキストを抽出します。
さらに、その内容を基に「良い点」「悪い点」「改善点」を自動的に分析し、結果をkintoneに保存します。

画像データを解析する流れ

  1. 「ファイルを選択」ボタンで、解析したい画像ファイルを読み込みます。
  2. 「保存する」ボタンを押すと、GeminiAPIで手書きのアンケート画像を読み込んで分析し、結果をkintoneに保存します。

実際の操作の流れは、下記GIF画像を確認してください。

アプリの準備

「はじめから作成」でアプリを新規作成し、フィールドを次のように設定します。

フィールド名 フィールドの種類 フィールドコード 備考
添付ファイル 添付ファイル image 解析したい画像ファイルをkintoneに格納するためのファイルフィールドです。
読み込んだ日本語テキスト 文字列(複数行) extractedText 解析した画像の日本語テキストを格納するためのフィールドです。
良い点 文字列(複数行) goodPoints 読み込んだ日本語テキストから良い点を抽出して格納するためのフィールドです。
悪い点 文字列(複数行) badPoints 読み込んだ日本語テキストから悪い点を抽出して格納するためのフィールドです。
改善点 文字列(複数行) improvements 読み込んだ日本語テキストから改善点を抽出して格納するためのフィールドです。

GeminiAPIの準備

APIキーの取得

Google AI Studio (External link) にアクセスし、APIキーを作成ボタンをクリックします。
コピーボタンが表示されたらクリックしてAPIキーをコピーし、控えておきます。

サンプルコード

kintone-geminiAPI.js

本サンプルのプログラムです。
12行目の{APIキー}を、先ほど取得したAPIキーで置き換えてください。

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
/*
 * kintone-GeminiAPI sample program
 * Copyright (c) 2024 Cybozu
 *
 * Licensed under the MIT License
 * https://opensource.org/license/mit/
 */

(() => {
  'use strict';
  // APIキー
  const APIkey = '{APIキー}';
  // エンドポイント
  const geminiUrl = `https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash-latest:generateContent?key=${APIkey}`;

  // BlobをBase64エンコード
  const blobToBase64 = (blob) => {
    return new Promise((resolve, reject) => {
      const reader = new FileReader();
      reader.onloadend = () => resolve(reader.result.split(',')[1]);
      reader.onerror = reject;
      reader.readAsDataURL(blob);
    });
  };

  // 解析結果からJSON形式のブロックを抽出
  const extractJsonBlocks = (text) => {
    const match = text.match(/```json([\s\S]*?)```/);
    return match[1].trim();
  };

  // gemini AIを使って画像を解析した結果をJSON形式で取得
  const analyzeImageWithGeminiAI = async (imageBlob) => {
    // BlobをBase64エンコード
    const base64Content = await blobToBase64(imageBlob);

    // 解析のためのプロンプト
    const prompt = `画像は製品の使い勝手についてのアンケートです。
      このアンケートの日本語の文章を読み取り、"読み込んだ日本語テキスト"データから「良い点」「悪い点」「改善点」をJSON形式で出力してください。
      出力形式は以下の通り、JSON.parse()メソッドでjson化できる形式で出力してください。
      {
        "読み込んだ日本語テキスト":"読み込んだ日本語テキスト",
        "良い点": ["良い点1", "良い点2"],
        "悪い点": ["悪い点1", "悪い点2"],
        "改善点": ["改善点1", "改善点2"]
      }`;

    // リクエストボディを作成
    const requestBody = {
      contents: {
        role: 'user',
        parts: [
          {
            inlineData: {
              mimeType: imageBlob.type,
              data: base64Content,
            },
          },
          {
            text: prompt,
          },
        ],
      },
    };

    // APIリクエスト
    const response = await fetch(geminiUrl, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify(requestBody)
    });

    // 解析結果を取得し、JSON形式に変換
    const result = await response.json();
    const analysis = JSON.parse(extractJsonBlocks(result.candidates[0].content.parts[0].text));

    return analysis;
  };

  // レコード追加画面で保存に成功した後のイベント
  kintone.events.on('app.record.create.submit.success', async (event) => {
    const record = event.record;

    // 画像ファイルの取得
    const fileKey = record.image.value[0].fileKey;
    const fileResponse = await fetch(`/k/v1/file.json?fileKey=${fileKey}`, {
      method: 'GET',
      headers: {'X-Requested-With': 'XMLHttpRequest'},
    });
    const analysisBlob = await fileResponse.blob();

    // 画像解析
    const analysis = await analyzeImageWithGeminiAI(analysisBlob);

    // 解析結果をレコードに保存
    const body = {
      app: kintone.app.getId(),
      id: record.$id.value,
      record: {
        extractedText: {value: analysis['読み込んだ日本語テキスト']},
        goodPoints: {value: analysis['良い点'].join('\n')},
        badPoints: {value: analysis['悪い点'].join('\n')},
        improvements: {value: analysis['改善点'].join('\n')}
      }
    };

    // 保存後に上書き
    await kintone.api(kintone.api.url('/k/v1/record.json', true), 'PUT', body);

    return event;
  });
})();

サンプルコードの解説

保存に成功した後のイベントで画像データを取得する

保存に成功したあとでなければ、レコードの情報からfileKeyを取得できません。
そのため、app.record.create.submit.successイベントで画像データを取得します。

82
83
84
85
86
87
88
89
90
91
92
93
// レコード追加画面で保存に成功した後のイベント
kintone.events.on('app.record.create.submit.success', async (event) => {
  const record = event.record;

  // 画像ファイルの取得
  const fileKey = record.image.value[0].fileKey;
  const fileResponse = await fetch(`/k/v1/file.json?fileKey=${fileKey}`, {
    method: 'GET',
    headers: {'X-Requested-With': 'XMLHttpRequest'},
  });
  const analysisBlob = await fileResponse.blob();
  
解析結果を上書き保存する

保存したレコードから画像データを取得し、GeminiAPIを使って画像を解析します。
解析結果をレコードに保存します。

 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
// 画像解析
const analysis = await analyzeImageWithGeminiAI(analysisBlob);

// 解析結果をレコードに保存
const body = {
  app: kintone.app.getId(),
  id: record.$id.value,
  record: {
    extractedText: {value: analysis['読み込んだ日本語テキスト']},
    goodPoints: {value: analysis['良い点'].join('\n')},
    badPoints: {value: analysis['悪い点'].join('\n')},
    improvements: {value: analysis['改善点'].join('\n')}
  }
};

// 保存後に上書き
await kintone.api(kintone.api.url('/k/v1/record.json', true), 'PUT', body);
画像の解析

Blob形式に変換した画像データを解析するための関数に渡します。

94
95
// 画像解析
const analysis = await analyzeImageWithGeminiAI(analysisBlob);

GeminiAPIに画像データを送信し、解析結果を取得します。
取得した解析結果をJSON形式に変換し、JSON形式のブロックを抽出し、解析結果を返します。

32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
// gemini AIを使って画像を解析した結果をJSON形式で取得
const analyzeImageWithGeminiAI = async (imageBlob) => {
  // BlobをBase64エンコード
  const base64Content = await blobToBase64(imageBlob);

  // 解析のためのプロンプト
  const prompt = `画像は製品の使い勝手についてのアンケートです。
    このアンケートの日本語の文章を読み取り、"読み込んだ日本語テキスト"データから「良い点」「悪い点」「改善点」をJSON形式で出力してください。
    出力形式は以下の通り、JSON.parse()メソッドでjson化できる形式で出力してください。
    {
      "読み込んだ日本語テキスト":"読み込んだ日本語テキスト",
      "良い点": ["良い点1", "良い点2"],
      "悪い点": ["悪い点1", "悪い点2"],
      "改善点": ["改善点1", "改善点2"]
    }`;

  // リクエストボディを作成
  const requestBody = {
    contents: {
      role: 'user',
      parts: [
        {
          inlineData: {
            mimeType: imageBlob.type,
            data: base64Content,
          },
        },
        {
          text: prompt,
        },
      ],
    },
  };

  // APIリクエスト
  const response = await fetch(geminiUrl, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
    },
    body: JSON.stringify(requestBody)
  });

  // 解析結果を取得し、JSON形式に変換
  const result = await response.json();
  const analysis = JSON.parse(extractJsonBlocks(result.candidates[0].content.parts[0].text));

  return analysis;
};

動作の確認

画像を解析する

以下の手書きアンケートをダウンロードし、カスタマイズしたアプリ上に保存し、動作を確認してください。

おわりに

今回の記事は、Gemini APIを利用して画像から日本語テキストを抽出、分析し、kintoneに保存するカスタマイズを紹介しました。
手書きアンケートの画像を読み込んで分析することで、アンケートの集計作業を効率化できます。
なお、Google Cloudの利用には課金が発生する可能性がありますので、ご利用前に料金プランを確認してください。

information

このTipsは、2024年11月版kintoneで動作を確認しています。