Notes開発者のためのXPagesデザインレシピ

簡単でCoolなXPagesアプリケーションを作るための情報を発信していきます

Notesクライアント 生成AI

コイツ…動くぞ!Nomad Mobileでも動くGPT-4oを使ったテキストファイル、画像問合せ(後編)

画像と問合せをPOSTする

 前回に引き続き、いよいよ画像のPOSTまでたどり着きました。前回画像をPOSTしたClaude3では公式サンプルがわかりやすいんですが、GPTのAPIリファレンスには画像をPOSTできるサンプルが見当たりませんでした。ネットに上がっていたPythonを実装してみて、LotusScriptに移植してみます。
 前回の記事を見ていない方は合わせてご参照下さい。
コイツ…動くぞ!Nomad Mobileでも動くGPT-4oを使ったテキストファイル、画像問合せ(前編) – Notes開発者のためのXPagesデザインレシピ (enjoyxpages.sakura.ne.jp)

 前回と同じくフォームを作ります。基本的にテキストファイルの選択と同じ感じです。
【フォーム】

 画像をPOSTするには、Claude3でやったように画像をバイナリで読み込み、Base64でエンコードしてテキストとして扱うので、JSONの属性が変わるだけで難しいことはありません。下記JSONの{base64_image}をエンコードした画像のテキストに置き換えます。
【JSON】

{
    "model": "gpt-4o-2024-05-13",
    "messages": [
        {
            "role": "user",
            "content": [
                {
                    "type": "text",
                    "text": "何が描かれていますか?詳しく日本語で教えて下さい。"
                },
                {
                    "type": "image_url",
                    "image_url": {
                        "url": "data:image/jpeg;base64,{base64_image}"
                    }
                }
            ]
        }
    ]
}

 LotusScriptのコードもテキストファイルの読み込み部分とそんなに変わりません。NotesStreamクラスでバイナリを読み込み、浜さんに教えて貰った裏コードBase64にエンコードします。
 浜さんのブログには@式関係、LotusScript関係など多彩なテクニックが掲載されていますので、Notes系ブログがどんどんなくなっていく中、貴重な存在です。
出直し!! ドミノ塾 (denaoshidomino.com)
【LotusScript】

Sub Click(Source As Button)
	'---------- ---------- ---------- ---------- ---------- 
	'OpenAI APIから取得したレスポンスを書込み
	'
	'---------- ---------- ---------- ---------- ---------- 	
	Const APIURL = "https://api.openai.com/v1/chat/completions"
	Const APIKEY = "(取得したAPIKEY)"
	Const MODEL = "gpt-4o-2024-05-13"
	
	'クラス・変数宣言	
	Dim ws				As New NotesUIWorkspace
	Dim uidoc			As NotesUIDocument
	Dim doc			As NotesDocument
	Dim session		As New NotesSession
	Dim http			As NotesHTTPRequest
	Dim jsonNav		As NotesJSONNavigator
	Dim stream			As NotesStream			'画像ファイル読み込み	
	
	Dim sRequest		As String					'入力した質問
	Dim sBodyJson		As String					'送信するJSON文字列
	Dim sContent		As String					'回答本文
	Dim vResHeader	As Variant					'httpレスポンスヘッダー全体
	Dim vResCode		As Variant					'httpレスポンスヘッダー0行目リスト
	Dim iResCode		As Integer					'httpレスポンスコード
	Dim sImagePath	As String					'画像ファイルパス
	Dim sBase64Str	As String					'Base64変換後ファイル
	Dim sMediaType	As String					'メディアタイプ(POSTで使用)
	
	'クラス・変数セット
	Set http		= session.CreateHTTPRequest()	
	Set uidoc		= ws.CurrentDocument
	Set doc		= uidoc.Document
	
	'入力チェック
	sRequest		= doc.Request(0)
	If sRequest = "" Then
		Msgbox "質問を入力して下さい。"
		Exit Sub
	End If
	sImagePath		= doc.ImagePath(0)
	If sImagePath = "" Then
		Msgbox "画像ファイルパスを入力して下さい。"
		Exit Sub
	End If	
	
	
	'画像ファイルの読み込みとBase64エンコード
	Set stream			= session.CreateStream()
	Call stream.Open(sImagePath, "binary")
	sBase64Str = Replace(stream.ReadEncoded(ENC_BASE64, 76), Chr(13) & Chr(10) , "")		'裏コード
	
	'画像ファイルのMediaType決定
	sMediaType		= GetMediaType(sImagePath)		
	
	'Getリクエストを実行し、結果を取得
	Call http.SetHeaderField("Content-Type", "application/json") 
	Call http.SetHeaderField("Authorization", "Bearer " & APIKEY) 
	http.PreferJSONNavigator	= True
	
	'特殊文字対策
	sRequest = ReplaceSpecialCharacters(sRequest)
	
	'リクエスト送信
	sBodyJson		= |{"model":"| & MODEL & |", "messages":[| _
	& |{"role":"user", "content":[| _
	& |{"type":"text","text":"| & sRequest & |"},| _
	& |{"type":"image_url","image_url": {| _
	& |"url": "data:| & sMediaType & |;base64,|& sBase64Str & |"| _
	& |} }| _
	& |] }| _
	& |] }|
	Set jsonNav	= http.post(APIURL, sBodyJson)
	
	'エラーレスポンスチェック
	vResHeader	= http.GetResponseHeaders()
	vResCode		= Split(vResHeader(0)," ")
	iResCode		= Cint(vResCode(1))	
	If iResCode <> 200 Then
		Msgbox  sContent , 16 , "Error : " & Cstr(iResCode)
		Exit Sub
	End If
	
	'レスポンスから回答を抽出
	sContent		= jsonNav.GetElementByPointer("/choices/0/message/content").Value
	
	'回答をフォームに書込み
	doc.HttpStatus	= iResCode
	doc.Response	= sContent
	
	Exit Sub
	
ErrRtn:              'エラールーチン
	Msgbox Error() & Chr(13) & "Line: " & Erl(), 16, "Error"
	Print Error(), "ErrorCode: " & Err(), "Line: " & Erl()
	Print "モジュール: 送信でエラーが発生しました。"
	End
	
End Sub


Function GetMediaType(sFilePath As String) As String
	'---------- ---------- ---------- ---------- ---------- 
	' ファイルの拡張子からMediaTypeを決定する
	' 引数:
	' 戻り値:MediaType(image/jpeg、image/png、image/gif、image/webp
	'---------- ---------- ---------- ---------- ---------- 
	Dim sExt			As String		'拡張子
	Dim sMediaType	As String		'メディアタイプ
	
	sExt	= Lcase(Strrightback(sFilePath , "."))
	
	Select Case sExt
	Case "jpg","jpeg":
		sMediaType		= "image/jpeg"
	Case "png":
		sMediaType		= "image/png"
	Case "gif":
		sMediaType		= "image/gif"
	Case "webp":
		sMediaType		= "image/webp"
	Case Else
		sMediaType		= "Unknown"
	End Select
	
	GetMediaType		= sMediaType
	
End Function



'<<<<<<<<<< ReplaceSpecialCharacters関数は省略 >>>>>>>>>>

実行結果と活用シーン

 実行結果は下記のようになります。あまり大きな画像をPOSTすると、一気にチャージ金額を使い切るのでご注意下さい。(スマホ画像を直接POSTするなど)

【質問とPOST画像1】
・添付画像に記載された文字をすべて読み取って下さい。回答は読み取った文字だけにして下さい。

【質問とPOST画像2】
・添付の画像を見て、あなたは何を感じますか?できるだけ詳しく教えて下さい。

最後に

 簡易OCRとしても使えるかもしれませんし、JSONに「system」属性を追加すれば、あなたの気持ちを唯一わかってくれるチャットAIになるかもしれません。
 e-mailやLINE、messengerでのやりとりが当たり前になっている現代、そのアイコンの向こうにいるのは本当にその人ですか?あなたが嬉しくなったり、悲しくなったりするのにその人は必要ですか?もしかしたら生成AIの方が、あなたの意見を否定せず、優しく寄り添ってくれる存在かもしれませんよ。