GPTのAPIとGoogle Sheetを連携させて、生成AIで分類作業を自動化する

Google spreadsheetとOpenAI GPTのAPIを利用して、テキスト分類の業務を自動化しました。生成AIとスプレッドシートを組み合わせることで、大きな業務効率化が可能になります。実際の検証プロセスを意識したうえで記事にまとめました。
2023.12.19

はじめに

普段の業務の中で、ExcelやGoogle Sheetなどの表計算ソフトウェアを利用する方は多いのではないでしょうか?私も普段の業務でよく利用します。特にたくさんある情報を整理したいときやデータの管理に利用することが多いです。

GPTなどの生成AIツールを業務利用する際には、これらの表計算系と組み合わせると便利なシーンが多いと思います。今回はGoogle SpreadsheetとOpenAI GPTのAPIを利用して、業務効率化が可能であるかの検証を行います。

この記事のゴール

今回はダミーデータとしてGPTに作成してもらったダミーレビューデータを基に、Google spreadsheetに記載されたデータを読み取ってポジティブ、ネガティブ、ニュートラル、いずれかの分類化を実施することを目標とします。

前提

利用する言語はPythonを利用します。Google App Scriptを利用することも検討しましたが、Google App Scriptには特定のレートリミットが存在しており、拡張性をもたせるための一定のワークアラウンドが必要になりそうなので、より汎用性が高く個人的に利用に慣れているPythonを利用することにしました。

Version情報

Python 3.11.6
openai 1.1.1

それでは検証していきましょう!

0. まずはダミーデータをGPTに作ってもらう。

今回の検証では、まず分類を行うためのダミーデータの作成を行います。実際に利用可能なデータがある場合、それを使用するのが最適ですが、今回はあくまで検証+記事化するために公開できる情報がなかったため、ダミーデータを作成することにしました。

分類を行う対象のダミーレビューデータとして180件(ポジティブ60件、ネガティブ60件、ニュートラル60件)のデータを作成します。

データの作成は、以下のようにGPTのAPIを利用して1件ずつ作成を行いました。

ちなみにプロンプト内のfew shot(例示)として利用しているデータもGPTに作成してもらったものを利用しています。

import json
from openai import OpenAI
import time
import os
from dotenv import load_dotenv
import pandas as pd

load_dotenv()

client = OpenAI(
    api_key = os.environ.get('OPENAI_API_KEY')
)

def query_gpt_chat(model, sys_prompt, user_prompt):
    try:
        response = client.chat.completions.create(
            model=model,
            messages=[
                {"role": "system", "content": sys_prompt},
                {"role": "user", "content": user_prompt}
            ],
        )
        result = response.choices[0].message.content
        print(result)
    except AttributeError as e:
        error_message = f"Error: {e}"
        print(error_message)
        result = error_message
        
    return result

sys_prompt="""

# 役割
- あなたは最高のデータアナリストとしてダミーレビューデータの作成を行ったください。
- 必要なデータは映画のレビューデータです。 
- 例示の内容を参考にデータのレビューデータの出力を行ってください

# 制約条件
- レビューデータを1件出力すること
- レビューの文字数は100文字以上であること
- レビューの文字数は150文字以下であること
- 出力するデータは1件のデータを出力してください。
- ニュートラルなレビューであること // 出力したい分類によって変更する

# 例示

## ネガティブなレビュー
期待していたほどの興奮や感動は得られず、退屈なストーリーと平凡な演出にがっかりしました。キャラクターにも深みがなく、全体的に時間を無駄にしたような感覚に陥りました。ストーリーテリングに欠ける要素が多く、観客を引き込むことができなかったのが残念です。

## ポジティブなレビュー
映像の鮮やかさが際立ち、緻密に練られたストーリーに深く引き込まれる経験をしました。キャラクターたちはそれぞれに魅力的で、彼らの成長と葛藤に心から感情移入しました。画面から目を離すことができず、最後のシーンまで夢中で観てしまいました。映画を観ることの喜びを改めて実感した作品です。

## ニュートラルなレビュー
この映画はまさに平均的な作品でした。視覚的にも物語的にも楽しめる要素はありましたが、長期的に記憶に残るような印象深い瞬間はほとんどありませんでした。もう少し斬新な展開や深いメッセージ性があれば、より心に響く作品になったかもしれません。全体的には満足できるものの、何か物足りなさを感じる映画でした。

"""
user_prompt = """

"""

def gpt_queries_and_save_to_csv(model, sys_prompt, user_prompt, file_path, num_queries=60):
    data = []
    for i in range(num_queries):
        result = query_gpt_chat(model, sys_prompt, user_prompt)
        data.append({"Query Number": i+1, "Response": result})
        time.sleep(1) 

    df = pd.DataFrame(data)
    df.to_csv(file_path, index=False, encoding='utf-8')

file_path = "output.csv"

gpt_queries_and_save_to_csv("gpt-4-1106-preview", sys_prompt, user_prompt, file_path)

アウトプットのデータ(抜粋):

少し日本語に違和感はありますが、今回は目的から逸れるため良しとします。以下はポジティブなレビューデータです。同様のデータをポジティブ、ネガティブ、ニュートラルで出力しました。

Query Number,Response
1,映画の爽快なアクションと鋭いウィットに満ちた脚本が見事にマッチし、スリリングな体験を提供しました。キャストの化学反応は素晴らしく、それぞれの役どころが完璧に演じられていて、笑いあり涙ありの感動的なシーンが際立っていました。まさに心温まる映画の鑑賞でした。
2,この映画は息をのむような美しいシーンと気の利いた台詞で、画面に釘付けにされました。すべてのキャラクターが繊細で、ストーリーに完璧にフィットしていました。感動的な結末には涙がこぼれるほどで、演出の巧みさに心から拍手を送りたい。
3,映画「星空の旅」は、心温まる物語と息をのむような美しい宇宙の映像で私の心を掴みました。豪華なキャストが魅力を倍増させ、笑いと涙を誘います。真実の愛と友情の重要性を描いたこの作品は、見る者の心に希望を与え、暖かい気持ちで帰宅させてくれます。

(余談)ダミーデータを作成するときに少しハマった話し

ダミーデータの作成を始めた際、最初に100件のデータを一度に生成しようとしましたが、その場合出力が省略されてしまい、期待通りの件数が出力されませんでした。

プロンプトに工夫を行ってもデータが省略されてしまう動作が改善されず、結局APIを使って1件ずつ繰り返し処理を行いました。

もし、その他にいい対策をご存知の方がいれば教えてください。

やりたいことと作成する手順をざっくり決める

ダミーデータの準備ができたので、実際に作る前に今回のやりたいことをより具体化して、作成の手順をざっくり決めていきたいと思います。

やりたいこと:

  • Google Sheetsの情報を1行ずつ読み取って、ポジネガ分類の結果を追記する。
    • データレイアウトはざっくり(No | インプットのレビュー情報 | 分類結果)のイメージ。

作成する手順:

  1. やりたいこと(ポジネガ分類の結果)が実現可能でありそうかをプロンプトベースで検証する。
  2. Pythonを使ってGoogle Spreadsheetが操作できるようにする。
  3. 対象の情報を読み取って、GPTで行った処理の結果をスプレッドシートに書き込む処理を作る。

気にしたいこと:

  • スプレッドシートとの連携の処理は今後も使い回せるように汎用性が高い形で実装したい

1. やりたいこと(ポジネガ分類の結果)が実現可能かをプロンプトベースで検証

普段私が生成AI関連のPoCや検証を行う際には、連携部分やシステム部分を作り込んで やっぱ無理だったとなると辛いので、そもそも実現できるかどうかを確認してから実装を行っています。うまくいかないときにできるだけ小さく失敗ができるように、まずは連携部分などを実装する前にやりたいことが実際にできそうかをプロンプトのみで試してみたいと思います。

先ほど出力したダミーデータを抜粋してベタ書きで検証を行います。

プロンプトは以下の内容を利用しました。

# 指示
- あなたは #入力 として与えられる文章に対して、分類を行う必要があります。
- 分類は[ポジティブ, ネガティブ, ニュートラル]いずれかに分類を行ってください。
- 分類を行う際には #例示 の内容を参考にして分類を行ってください。

# 例示

## 例示 1

### 入力
映画「光の彼方へ」は視覚的にも物語的にも息をのむほどの美しさでした。繊細な感情の描写と温かなメッセージが心に響く素晴らしい作品。俳優たちの熱演により、感情移入することなくはいられませんでした。再鑑賞必至の秀逸な映画です。

### 出力
ポジティブ

## 例示 2

### 入力
残念ながら、この映画は一貫性を欠いたプロットと陳腐な対話で期待はるかに下回りました。登場人物の行動は説得力がなく、どのシーンも感情移入することができませんでした。展開も予測可能で、特に新鮮味やサプライズがなく、全体的に見て時間の浪費のように感じました。

### 出力
ネガティブ

## 例示 3

### 入力
この映画は観る人によって評価が分かれるタイプだと感じました。映像や音楽は平均的で特に際立つものはありませんが、演技はまずまずでした。ストーリーは予測可能な部分が多くありつつも、所々に興味深い展開が散りばめられています。

### 出力
ニュートラル

---
# 入力
この映画は技術的な側面では実に標準的な出来でした。演技は適切でありながらも特筆すべき点は少なく、ストーリーは追いやすいものの予測可能な展開でした。改善の余地はあるものの、映画としての基礎はしっかりしている作品です。 // *実際の評価対象のデータを記載。まずは検証のため、いくつかのデータをベタ書きで試す。

# 出力

実際にWebの画面(社内の生成環境)でいくつかのデータで試してみたところ、期待通りの出力を得ることができています。

出力が安定しない(文章で返答されるなど)場合はFunction Callingの利用やjson modeを利用して実装が必要になるかと思っていましたが、少し試してみたところ特に問題なさそうなのでそれらの利用は行わずに実装することにします。

2. Pythonを使って、Google Spreadsheetが操作できるようにする

今回の検証ではGoogle Spreadsheet上の情報を利用するため、スプレッドシートの情報にアクセスできるように準備を行います。

Google Sheetの情報を利用するためにGoogle Sheets APIを利用します。セットアップ手順は以下です。GPTさんの指示に従って、問題なくCSVの内容を保存できました。

利用したプロンプト

あなたは最高のシニアレベルのプログラマーであり、エンジニアです。これまでGoogleやAmazon、Facebookなどで最高のエンジニアとして活躍してきました。これから聞く質問に、最も助けになる回答を教えてください。回答には重要なポイントや考えるべき観点などを丁寧に記載してください。回答にはパフォーマンス、セキュリティ、コードの保守性などの観点から最も良い回答を生成してください。追加で必要な情報がある場合は質問してください。

Google spreadsheetの表をPythonで操作したいです。以下のURLにある表を読み取って、ローカルでCSVとして出力したいです

---

https://docs.google.com/spreadsheets/d/xxxxxxxxxx

*上記URLは一部マスクしています。

(参考)GPTからアウトプットとして表示されたセットアップ手順 *GPT出力のコピー

ステップ 1: Google Sheets API の設定

  1. Google Developers Console (https://console.developers.google.com/) にアクセスし、プロジェクトを作成します。
  2. 「APIとサービス」ダッシュボードで「認証情報」を選択し、「認証情報を作成」から「サービスアカウント」を選択します。
  3. サービスアカウントを作成し、キーを JSON 形式でダウンロードします。このファイルは後で Python スクリプトから認証に使用します。
  4. 「APIとサービス」の「ライブラリ」から「Google Sheets API」を検索し、有効にします。

ステップ 2: 共有設定の変更

  1. Python スクリプトからアクセスするためには、スプレッドシートの共有設定を変更して、先ほど作成したサービスアカウントのメールアドレスに対して編集権限を付与する必要があります。
  2. スプレッドシートを開き、「共有」ボタンをクリックして、サービスアカウントのメールアドレスを追加します。

ステップ 3: Python スクリプトの作成

以下は、Google スプレッドシートを読み取り、ローカルに CSV として出力する Python スクリプトの例です。

import gspread
from oauth2client.service_account import ServiceAccountCredentials
import csv

# 認証情報を設定
scope = ['https://spreadsheets.google.com/feeds','https://www.googleapis.com/auth/drive']
creds = ServiceAccountCredentials.from_json_keyfile_name('path_to_your_credentials.json', scope)
client = gspread.authorize(creds)

# スプレッドシートを開く
spreadsheet_url = 'https://docs.google.com/spreadsheets/d/xxxxxxxxxxxxxxx'
spreadsheet = client.open_by_url(spreadsheet_url)
worksheet = spreadsheet.get_worksheet(0)  # 0 は最初のワークシートを指します

# 全データを取得
list_of_hashes = worksheet.get_all_records()

# CSV ファイルとして保存
with open('output.csv', 'w', newline='') as file:
    writer = csv.DictWriter(file, fieldnames=list_of_hashes[0].keys())
    writer.writeheader()
    for row in list_of_hashes:
        writer.writerow(row)

print('CSVファイルが出力されました。')

このスクリプトでは、gspread と oauth2client パッケージを使用しています。これらは、Google Sheets API とやり取りするために必要です。もしこれらのパッケージがまだインストールされていない場合は、以下のコマンドでインストールできます。

pip install gspread oauth2client

スクリプトを実行する前に、path_to_your_credentials.json をダウンロードした認証情報ファイルのパスに置き換える必要があります。また、output.csv は出力される CSV ファイルの名前で、必要に応じて変更することができます。

注意点

  • API の利用制限やクォータを超えないように注意してください。
  • セキュリティの観点から、認証情報ファイル(JSON)は安全な場所に保管し、公開されないようにしてください。
  • スプレッドシートの URL が公開されている場合でも、サービスアカウントに適切な権限がなければアクセスできません。
  • スプレッドシートのデータ構造が変わると、スクリプトを更新する必要があるかもしれません。

ココまでがGPTの出力コピーです。以下は人間に戻ります。

3. 対象の情報を読み取って、GPTで行った処理の結果をGoogle Spreadsheetに書き込む処理を作る。

「1. やりたいこと(ポジネガ分類の結果)が実現可能か」という点は既にプロンプトベースでの検証を通して確認済みであるため、実際の実装を行っていきたいと思います。

実装したコードの全文は以下です。「気にしたいこと」として、スプレッドシートの操作は汎用性を持たせたかったため、できるだけ関数に切り出して使い回しやすいことを意識しました。

import gspread
import pandas as pd
from oauth2client.service_account import ServiceAccountCredentials
from gspread_dataframe import set_with_dataframe
from openai import OpenAI
import os
from dotenv import load_dotenv

load_dotenv()

client = OpenAI(
    api_key = os.environ.get('OPENAI_API_KEY')
)
def authenticate_gspread(json_credentials_path):
    """Google Sheets APIへの認証を行い、gspreadクライアントオブジェクトを返す"""
    scope = ['https://spreadsheets.google.com/feeds', 'https://www.googleapis.com/auth/drive']
    creds = ServiceAccountCredentials.from_json_keyfile_name(json_credentials_path, scope)
    gs_client = gspread.authorize(creds)
    return gs_client

def get_worksheet_data(client, spreadsheet_url, sheet_index=0):
    """指定されたスプレッドシートのデータをpandas DataFrameとして読み取る"""
    spreadsheet = client.open_by_url(spreadsheet_url)
    worksheet = spreadsheet.get_worksheet(sheet_index)
    data = worksheet.get_all_values()
    headers = data.pop(0)
    return pd.DataFrame(data, columns=headers), worksheet

def update_worksheet_with_dataframe(worksheet, dataframe):
    """更新したDataFrameをスプレッドシートに書き戻す"""
    set_with_dataframe(worksheet, dataframe)
    print('スプレッドシートの更新が完了しました。')

def query_gpt_chat(model, sys_prompt, user_prompt):
    try:
        response = client.chat.completions.create(
            model=model,
            temperature=0,
            messages=[
                {"role": "system", "content": sys_prompt},
                {"role": "user", "content": user_prompt}
            ],
        )
        result = response.choices[0].message.content
        print(result)
    except AttributeError as e:
        error_message = f"Error: {e}"
        print(error_message)
        result = error_message 

    return result

def create_dynamic_sys_prompt(target):
    sys_prompt=f"""
        
    # 指示
    - あなたは #入力 として与えられる文章に対して、分類を行う必要があります。
    - 分類は[ポジティブ, ネガティブ, ニュートラル]いずれかに分類を行ってください。
    - 分類を行う際には #例示 の内容を参考にして分類を行ってください。

    # 例示

    ## 例示 1

    ### 入力
    映画「光の彼方へ」は視覚的にも物語的にも息をのむほどの美しさでした。繊細な感情の描写と温かなメッセージが心に響く素晴らしい作品。俳優たちの熱演により、感情移入することなくはいられませんでした。再鑑賞必至の秀逸な映画です。

    ### 出力
    ポジティブ

    ## 例示 2

    ### 入力
    残念ながら、この映画は一貫性を欠いたプロットと陳腐な対話で期待はるかに下回りました。登場人物の行動は説得力がなく、どのシーンも感情移入することができませんでした。展開も予測可能で、特に新鮮味やサプライズがなく、全体的に見て時間の浪費のように感じました。

    ### 出力
    ネガティブ

    ## 例示 3

    ### 入力
    この映画は観る人によって評価が分かれるタイプだと感じました。映像や音楽は平均的で特に際立つものはありませんが、演技はまずまずでした。ストーリーは予測可能な部分が多くありつつも、所々に興味深い展開が散りばめられています。

    ### 出力
    ニュートラル

    ---
    # 入力
    {target}

    # 出力
    
    """
    return sys_prompt
    

def main():
    json_credentials_path = './PATHTOYOURCREDNTIAL.json'
    spreadsheet_url = 'https://docs.google.com/spreadsheets/d/xxxxxxxxxx'    
    user_prompt = """
    """

    # 認証
    client = authenticate_gspread(json_credentials_path)

    # スプレッドシートのデータを読み取り
    df, worksheet = get_worksheet_data(client, spreadsheet_url)
    
    # 'Result' 列が存在しない場合は追加
    if '分類結果' not in df.columns:
        df['分類結果'] = ''
    
    for index, row in df.iterrows():
        sys_prompt = create_dynamic_sys_prompt(row['Target'])
        res = query_gpt_chat("gpt-4-1106-preview", sys_prompt, user_prompt)
        df.at[index, '分類結果'] = res  # 結果を '分類結果' 列に格納

    # スプレッドシートを更新
    update_worksheet_with_dataframe(worksheet, df)

if __name__ == '__main__':
    main()

実行結果は以下のようになり、期待通り分類が行われていました。自動化成功ですね。精度は詳しくみていませんが、軽くみたところ人間が行う分類と同等の分類はできていそうです。

利用したプロンプト(sys_prompt)はWeb画面で検証したものをそのまま利用しました。

生成AI系のAPIとスプレッドシートを組み合わせるとできることはかなり広がったり、日常の業務に取り入れやすかったりとメリットが大きいと思います、是非試してみてください。

おわりに

今回の記事ではスプレッドシートとGPTのAPIを利用して分類作業を自動化しましたが、記事を書く際には「どのように進めるか?」を意識して実際の構築を行いました。

生成AIを利用した実証実験や業務改善を行う場合、検証をできるだけ頻繁に行うことが大変重要であると感じています。そもそも新しい技術分野であり、なにができるか?できないか?といった線引きがまだ難しいことが多く、さらに技術進歩が非常に速いためです。まずは一番簡単に試せる方法で「コアとなる価値(今回は簡単な分類)」が提供できるかどうかを検証してブラッシュアップしながら構築する方法がやはり大切だと感じています。

まずはサクッとやってみる」を価値観として、業務への適用性を探っていきたいと思います。