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

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

Notesクライアント 生成AI

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

はじめに

 最近、毎日生成AIの話題ばっかりですね。今一番ホットなのは、2024/5/13に公開されたGPT-4oです。かなりまともな回答ができるようになってきて、さらにGPT-4 turboの半額というスペシャルプライスです。
 今回はNotesクライアントのLotusScriptを使って下記の実装を行ってみます。
・テキスト問合せ
・テキストファイル+テキスト問合せ
・画像ファイル+テキスト問合せ
※NomadWebでも動くのかと思いましたが、私のテストした環境では動きませんでした。今後検証が進めばソースコードを差し替えます。Nomad MobileのAndroid版では動作を確認しています。

 APIキーの取得方法などは、こちらの記事を参照して下さい。
NotesクライアントからChatGPTに質問してみよう(前編) – Notes開発者のためのXPagesデザインレシピ (enjoyxpages.sakura.ne.jp)

GPT-4oを使ったテキスト問合せ

 質問を入力するためのフィールド、回答を書き込むフィールド、問合せを送信するためのボタンを配置します。
【フォーム】

 問合せに必要なのは下記のようなJSONです。
【JSON】

{
    "model": "gpt-4o-2024-05-13",
    "messages": [
        {
            "role": "user",
            "content": [
                {
                    "type": "text",
                    "text": "Hello!"
                }
            ]
        }
    ]
}

 LotusScriptを使って、先ほど記載したJSONフォーマットを作り、送信ボタンからPOSTするとGPT-4oのレスポンスを書き込むことができます。LotusScriptは|(縦棒)でも文字列を定義できるので、”ダブルコーテーションだらけにならずに良いですね。
【LotusScript】

Sub Click(Source As Button)
	
	On Error Goto ErrRtn
	
	'---------- ---------- ---------- ---------- ---------- 
	'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 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レスポンスコード
	
	'クラス・変数セット
	Set http		= session.CreateHTTPRequest()	
	Set uidoc		= ws.CurrentDocument
	Set doc		= uidoc.Document
	
	'入力チェック
	sRequest		= doc.Request(0)
	If sRequest = "" Then
		Msgbox "質問を入力して下さい。"
		Exit Sub
	End If
	
	'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":"| & sRequest & |"}| _
	& |] }|	
	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 ReplaceSpecialCharacters(sTargetStr As String) As String
	'---------- ---------- ---------- ---------- ----------
	' 生成AIでエラーになる特殊文字を置換した文字列を返す(改行コード、タブ、
	' 引数	:sTarget-置換対象文字列
	' 戻り値	:置換済み文字列
	'---------- ---------- ---------- ---------- ----------
	'改行コード、特殊文字対応
	sTargetStr	= Replace(sTargetStr, Chr(13) & Chr(10), "\n")		'改行を句点に変換
	sTargetStr	= Replace(sTargetStr, """", "\""")					'ダブルコートをエスケープ
	sTargetStr	= Replace(sTargetStr, Chr(9), " ")					'TABを半角スペースに変換		
	
	ReplaceSpecialCharacters = sTargetStr	
	
End Function

テキストファイルと問合せをPOSTする

 次にテキストファイルと問合せを一緒に送ってみましょう。どんな活用シーンがあるかというと
・人事規定をアップし、受けられるサポートを質問する
・契約書をアップし、注意すべきポイントを確認する
・誤字脱字、語尾の修正など、ドキュメントの統一
などテキストを扱っているものであればいろいろな活用シーンが考えられます。
 先ほどのフォームにテキストファイルを選択するフィールドと、ファイル選択ボタンを追加します。
【フォーム】

 JSONは入力した質問に加えて、テキストファイルから読み込んだ内容を加えて送信します。
【JSON】

{
    "model": "gpt-4o-2024-05-13",
    "messages": [
        {
            "role": "user",
            "content": [
                {
                    "type": "text",
                    "text": "うつ病になりました。下記の人事規定から受けられるサポートを教えて下さい。"
                },
                {
                    "type": "text",
                    "text": text_content
                }
            ]
        }
    ]
}

 テキスト問合せと同様にLotusScriptでJSONを作り、GPT-4oにPOSTします。
【LotusScript】

Sub Click(Source As Button)
	
	On Error Goto ErrRtn
	
	'---------- ---------- ---------- ---------- ---------- 
	'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 sTextPath		As String					'テキストファイルパス
	Dim sTextContent	As String					'テキストファイル本文
	
	'クラス・変数セット
	Set http		= session.CreateHTTPRequest()	
	Set uidoc		= ws.CurrentDocument
	Set doc		= uidoc.Document
	
	'入力チェック
	sRequest		= doc.Request(0)
	If sRequest = "" Then
		Msgbox "質問を入力して下さい。",16,"エラー"
		Exit Sub
	End If
	sTextPath		= doc.TextPath(0)
	If sTextPath = "" Then
		Msgbox "分析対象のテキストファイルを指定してください。",16,"エラー"
		Exit Sub
	End If	
	
	'テキストファイルの読み込み
	Set stream	= session.CreateStream
	Call stream.Open(sTextPath,"UTF-8")
	sTextContent = stream.ReadText
	sTextContent = ReplaceSpecialCharacters(sTextContent)		
	
	'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":"text","text":"| & sTextContent & |"}| _
	& |] }| _
	& |] }|
	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


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

 実行結果イメージは下記のようになります。人間を否定しないところが生成AIの良いところですね。めんどくさがったりもしません。
【実行結果イメージ】

次回予告

 このまま画像のPOSTまで書こうかと思いましたが、長くなるので前後編に分けます。次回はお待ちかねの画像POSTです。