2024/08/12

つないでみよう:#20)GPT4o は OCR? - レシート読み取り

前回は、GPT4o に画像認識させ、その結果を JSON で受け取ることができました。今回は、その JSON を分析して必要な値を取得できるようにします。


JSON を解析する準備

今回の作業ではレスポンスの JSON を操作することになります。それには NotesJSONNavigator のオブジェクトが必要となります。そこで、これまで使用していた xAskGPT 関数の戻り値を文字列から NotesJSONNavigator のオブジェクトに変更します。

Function xAskGPT(ByVal vsJSON_Post As String) As NotesJSONNavigator
         ・・・
   sURL = "https://api.openai.com/v1/chat/completions" 'エンドポイント
   Set jnav = http.Post(sURL, vsJSON_Post)

   'Responceをセット
   Set xAskGPT = jnav
End Function

この変更に伴い、メインルーチンも調整します。

Sub Initialize
         ・・・
   sJSON_Post = xGetJSON(nd)
   'Call xSetRT(nd, "JSON_Send", sJSON_Post)
'必要な時だけ有効化

   'GPT4o 問い合わせ
   Dim jnavResponce As NotesJSONNavigator
   Set jnavResponce
= xAskGPT(sJSON_Post)

   'レスポンスの JSON を取得
   sJSON_Responce = jnavResponce.Stringify
   Call xSetRT(nd, "JSON_Responce", sJSON_Responce)

   Call nd.Save(True, False)
End Sub

ここまでで機能的な変化はありませんが、この状態を今回のスタート地点とします。


汎用関数の準備

階層化された JSON から必要な値を取得するにはプログラムが長く複雑になりがちです。その対策として、子エレメントを操作する汎用的な関数を 2 つ作成します。


◇ xGetChildElementByName 関数

名前を指定して、子エレメントを取得する関数です。

Function xGetChildElementByName(voParent As Variant, ByVal vsName As String) As NotesJSONElement
   Dim jobj As NotesJSONObject
   Dim sType As String

   On Error Resume Next
   sType = TypeName(voParent)

   If sType = "NOTESJSONELEMENT" Then
      Set jobj = voParent.Value
      Set xGetChildElementByName = jobj.GetElementByName(vsName)
   ElseIf sType = "NOTESJSONNAVIGATOR" Then
      Set xGetChildElementByName = voParent.GetElementByName(vsName)
   ElseIf sType = "NOTESJSONOBJECT" Then
      Set xGetChildElementByName = voParent.GetElementByName(vsName)
   End If
End Function


◇ xGetChildElementByName 関数

名前を指定して、子エレメントの値を取得する関数です。

Function xGetChildValueByName(voParent As Variant, ByVal vsName As String) As String
   Dim je As NotesJSONElement
   Dim sType As String

   On Error Resume Next
   sType = TypeName(voParent)

   If sType = "NOTESJSONELEMENT" Then
      Set je = xGetChildElementByName(voParent, vsName)
   ElseIf sType = "NOTESJSONNAVIGATOR" Then
      Set je = voParent.GetElementByName(vsName)
   ElseIf sType = "NOTESJSONOBJECT" Then
      Set je = voParent.GetElementByName(vsName)
   End If

   xGetChildValueByName = je.Value
End Function


レスポンスの確認と作成する機能

前置きが長くなりましたが、ここからが本題です。

まずは、レスポンスの JSON を再確認します。整形した全体は次の通りとなります。

OCR としての JSON は choices - message - content の順でエレメントを取得すればよいということですね。また、content の値が JSON となっているカスケードされた構造となります。

今回作成する機能は、この content の値を NotesJSONNavigator のオブジェクトとして取得するところまでとします。オブジェクト化しておけば、レシートの項目に柔軟にアクセスできますね。


OCR としての JSON の取得

content の値をオブジェクトで取得する関数は以下の通りです。上記の JSON 構造に従い順に取得しています。

Function xGetJSONObj_OCR(vjnav As NotesJSONNavigator) As NotesJSONNavigator
   Dim je As NotesJSONElement
   Dim ja As NotesJSONArray
   Dim sOCR As String

   'choices 取得
   Set je = xGetChildElementByName(vjnav, "choices")
   If Not (je Is Nothing) Then
      'choices は配列なので NotesJSONArray で受ける
      Set ja = je.Value
      If ja.Size > 0 Then
         '配列の 1 件目を取得
         Set je = ja.GetFirstElement()
         'message 取得
         Set je = xGetChildElementByName(je, "message")
         If Not (je Is Nothing) Then
            'content 取得
            sOCR = xGetChildValueByName(je, "content")
            'オブジェクト化して戻り値にセット
            Set xGetJSONObj_OCR = xns.CreateJSONNavigator(sOCR)
         End If
      End If
   End If
End Function

準備段階で説明した関数を利用しているので、この関数は JSON の操作に特化できています。このように、JSON の煩雑な操作を分離しておくとプログラムの可読性が上がりますね。


メインルーチンの修正とテスト

最後にメインルーチンであるエージェントを修正して、作成した xGetJSONObj_OCR 関数をコールします。また、OCR としての JSON は NotesJSONNavigator  オブジェクトで返されます。これを使用して、JSON をテキストとしてフィールドに保存、汎用関数を経由して、合計金額を抽出して表示しています。

Sub Initialize
         ・・・
   Dim jnavOCR As NotesJSONNavigator
         ・・・
   'GPT4o 問い合わせ
   Set jnavResponce = xAskGPT(sJSON_Post)
   sJSON_Responce = jnavResponce.Stringify
   Call xSetRT(nd, "JSON_Responce", sJSON_Responce)

   'OCR としての返答取得
   Set jnavOCR = xGetJSONObj_OCR(jnavResponce)
   Call xSetRT(nd, "JSON_OCR", jnavOCR.Stringify())

   MsgBox "合計金額 = " & xGetChildValueByName(jnavOCR, "合計金額")


   Call nd.Save(True, False)
End Sub

なお、フォームには取得した JSON を確認するために JSON_OCR フィールドを追加してください。

実行するとこのフィールドに JSON が表示され、合計金額がめっそじボックスで表示されます。


まとめ

GPT4o の画像認識機能を使用して、OCR として利用することに挑戦しました。今回のプログラムで返される JSON は次の通りでした(整形済み)。

{
   "店舗名":"FamilyMart 南堀江店",
   "購入日":"2024/07/08",
   "合計金額":"278"
}

今回はサンプルですのでシンプルな構造としました。

指示の仕方によっては、購入品名や単価など明細を取得できます。明細は複数になるので、カンマなど区切り文字で返させたり、階層化した JSON で返させることも可能なようです。すべては、AI の役割の設定とリクエストの仕方次第ということですね。

ご興味のある方はいろいろと試してみてください。


前回 連載:つないでみよう


0 件のコメント:

コメントを投稿