見出し画像

OpenAI Assistants APIで請求書データの抽出に挑んでみた

請求書内容を読み取って、会計ツールに登録するような作業はさすがに安定性に欠けるかなと思っていたのだけれども、OpenAIのAssistants APIを利用するとPDFや画像ファイルのOCRを実行した上で必要なデータを抽出してくれるみたい。

ということで、実際にプログラムを作成してみた。

Introduction : PDFの請求書データを読み取る作業

今までのやり方だとOCRツールを使ってPDF文書のテキストを抽出し、抽出されたテキストの中から必要な部分だけを正規表現で抜き出してくる。みたいなことしかできなかった。

この方法にはいくつか問題があって、大前提となるOCRがテキストを完璧に読み取ってこれないということ。なにか余計な文字が追加されてしまっていたり、全然違う文字として取得されてしまったり、請求書の枠線を文字として認識してしまったり、そもそも何も取得できなかったり、、、ということはかなりの高頻度で発生していた。たまーに発生する程度なら許容できるんだけれども、頻度が多く不安定さが否めない。

次に正規表現を用いての文字抽出がとても難しいということ。
OCRで抽出される文章が必ず一定ではないことに加えて、元々の請求書のフォーマットが全て異なることなどもあって、正規表現で一定にパターン化することが非常に難しい。

今回もやってみた業務もGoogle Vision APIとかPDF.jsとかを駆使してテキストを抽出して、それから生成APIにかけようと思っていたんだけれども、どうやらOpen AIからそれらすべてを一括で実施してくれる有能なAPIが提供されていることを知った。

Step 1 : Assistants APIの設定

一括で実施してくれるAPIというのがタイトルに設定したAssistants APIというものなのだけれど、この機能のひとつに「file_search」という機能があり、保持しているファイルの中身を見て解答を生成することができるらしい。すばらしいすぎる!!もちろん最新のAI modelであるGPT-4oも利用可能。

1. API Keyの取得

以下のChatGPTにログイン後、リンクにアクセスして「+ Create new secret key」のボタンからAPI Keyを作成。https://platform.openai.com/settings/profile?tab=api-keys

API Keys管理画面

発行されたSecret Keyをコピーし、プログラム内で利用します。
※コピーする前にポップを消すと2度と確認できなくなるので要注意!!

赤枠内に表示されるのがSecret Key

2. ファイルのアップロード

ここからは実際にコードを書いていく。
まずやらないといけないのはOpen AIの環境にファイルをアップロードする作業。

/**
 * 対象ファイルをOpenAIにアップロード ※利用目的 = assistants
 * @param {file} fileData : アップロードするファイルデータ
 * @return {string} - OpenAIのファイルID
 */
 function uploadFileToOpenAi (fileData) {
    const endpoint = 'https://api.openai.com/v1/files';
    const header = {
      'Authorization' : 'Bearer [API TOKEN]'
    };
    const fileOptions = {
      'headers' : header,
      'method' : 'post',
      'payload' : {
        'purpose' : 'assistants',
        'file' : fileData
      }
    };
    const fileFetch = UrlFetchApp.fetch(endpoint, fileOptions);
    return JSON.parse(fileFetch.getContentText());
}

※このコードだとファイル名が文字化けするけど、今回の用途ではファイル名は使わないので特に対策はしない。対策が必要なときは以下の記事をご参考にどうぞ。

3. URLFetch用のコードを作成

複数のエンドポイントを利用するため、毎回URLFetchのコード書くのも面倒。使いまわせる部品を最初に作っておきましょう。POSTとGETとDELETEができれば十分。基本的にはクラス処理でそれぞれのAPIを実行するつくりにしたため、request部分ではこれを使い回す。
※ファイルアップロード処理には非対応

/**
 * OpenAI Assistants API v2を実行するクラス
 * @param {file} targetFile : 読み取りに利用するファイルデータ
 */
class CHAT_GPT_ASSISTANTS_CLASS {
  constructor () {
    this.api_token = 'Your API Key';
  }

  /**
   * Assistants APIのリクエスト実行用
   * @param {string} endpoint : 実行するAPI
   * @param {object} options : ペイロードに設定する値
   * @return {object} - JSONパースしたレスポンスデータ
   */
  request (method, endpoint, payload) {
    const options = {
      'headers' : {
        'Authorization' : 'Bearer ' + this.api_token,
        'OpenAI-Beta' : 'assistants=v2',
        'Content-Type' : 'application/json'
      },
      'method' : method,
      'muteHttpExceptions' : true
    };
    //GET以外であればペイロードを追加する
    if(payload) options.payload = JSON.stringify(payload);
    //リクエストを実行し、JSONパースして返す
    const fetchData = UrlFetchApp.fetch(endpoint , options);
    return JSON.parse(fetchData.getContentText());
  }
}

ここまでがAPIを実行するための準備段階。

4. ベクターストアの作成

ここからが実際にAssistants APIを利用していく段階。まず最初にベクターストアというものを作成していきます。ベクターストアとは、Open AIのAssistantが参照する情報がまとまっている箱みたいなもので、ここにアップロードしたファイルを追加しておくことでAIが回答を行う際に参照できるようになる。
※ベクターストアを作ったまま放置しておくと課金され続けるらしいので、不要なベクターストアはこまめに削除しておきましょう。

まず、空のベクターストアを作成

/**
 * 空のベクターストアを作成
 * @returns {object} - 作成されたベクターストア情報
 */
createVectorStore () {
    const endpoint = 'https://api.openai.com/v1/vector_stores';
    const method = 'post';
    const param = { 'name' : '請求書' };
    return this.request(method, endpoint, param);
}

続いて、作成した空のベクターストアに、先にアップロードしておいたファイルをアタッチする。

/**
 * ファイルをアタッチする => OpenAI側で処理が終わるまで待機時間がある
 * @return {object} - ベクターストアのファイル情報
 */
attachVectorStore () {
    const endpoint = 'https://api.openai.com/v1/vector_stores/' + this.vector_store_id + '/files';
    const method = 'post';
    const param = { 'file_id': this.file_id };
    return this.request(method, endpoint, param);
}

5. アシスタントの作成

次にアシスタントの新規作成。
ここで利用するChatGPTモデルやアシスタント名、参照するベクターストアの設定などを行う。

/**
 * アシスタントの作成
 * @return {object} - 生成されたアシスタントオブジェクトの情報
 */
createAssistant () {
    const endpoint = 'https://api.openai.com/v1/assistants';
    const method = 'post';
    const param = {
      name: '請求書情報の解読',
      model: 'gpt-4o',
      tools: [{ type: 'file_search' }], // retrieval -> file_search
      tool_resources: {
        file_search: {
          vector_store_ids: [
            this.vector_store_id  // 作成したVector StoreのID
          ]
        }
      }
    };
    return this.request(method, endpoint, param);
}

6. スレッドの作成、メッセージの追加、処理の実行

スレッドの作成
→ メッセージ(プロンプト)の追加
→ 処理の実行

それぞれの処理を分割してひとつひとつAPI実行することもできるが、これらの処理を一括で実行することもできるので、今回は一括処理の方を使っていく。

/**
 * スレッドの作成とアシスタントの実行を一括でやる
 * @param {string} message : GPTに投げるプロンプト文章
 * @return {object} - 実行結果の情報(thread ID込み)
 */
execute () {
    const endpoint = 'https://api.openai.com/v1/threads/runs';
    const method = 'post';
    const param = {
      'assistant_id': this.assistant_id,
      'thread': {
        'messages': [
          {
            'role': 'user', 
            'content': promptMessage
          }
        ]
      }
    };
    return this.request(method, endpoint, param);
}

7. アシスタントの実行状況を取得

UIからChatGPTを使っているときと同じように生成完了までは若干の時間がかかる。生成が完了してから次の処理に進みたいので、実行状況の取得処理も作成しておく。

/**
 * アシスタントの実行状況を取得
 * @return {object} - 実行状況の取得
 */
getRunStatus () {
    const endpoint = 'https://api.openai.com/v1/threads/' + this.thread_id + '/runs/' + this.run_id;
    const method = 'get';
    return this.request(method, endpoint);
}

8. 生成された結果の取得

生成ステータスが完了で返ってきたら、実際に請求書を読み取った結果を取得する。結果の取得にはスレッドに含まれるメッセージをすべて取得するAPIを利用。

/**
 * 結果の出力
 * @return {object} : メッセージの一覧
 */
getGptResponse () {
    const endpoint = 'https://api.openai.com/v1/threads/' + this.thread_id + '/messages';
    const method = 'get';
    const response = this.request(method, endpoint);
    return response;
}

Step 1 のまとめ

Open AI Assistants APIを利用して請求書を読み取る際に最低限必要な処理としては以上の通り。けっこう手順が多くて若干めんどいのだけれども、それぞれのエンドポイント設定は非常にシンプルなので、ひとつひとつ疎通確認をしながら設定を進めていけばそんなに苦戦することもないかなという印象。


Step 2 : 一連のフローを実行するコードを作成

Assistants APIに必要な各エンドポイントのコードを書き終わったら、次はそれらを順番に実行していくための処理を設定していきます。
まぁ、ここまでの情報があれば十二分にプログラムを作成ができると思うので、プログラムの全体像については有料記事として設定しておくこととします。

興味あれば買って参考にしてみてください。

ここから先は

12,409字

¥ 1,000

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