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

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

Notesクライアント 生成AI

NotesクライアントからChatGPTに質問してみよう(後編)

改行を受け付けてくれない問題の対応

 前編のLotusScriptでは質問に改行が入るとエラーが出るという問題がありました。利用者が改行なしに入力してくれれば良いですが、そうはいかないので改行を別の文字に置換する必要がありそうです。

案1:nullに置換する
→途中改行の場合は問題ない。2つの文章を区点(。)を入力せず、改行区切りで入力された場合、文章が繋がっていまい文意が伝わらない可能性がある。
案2:区点(。)に置換する
→区点の代わりに改行している場合、正しい文章になる。区点が入っている場合、区点が2つ付く。
案3:\n(改行コード)に置換する
→HTML+JavaScriptで作ってみたところ、改行は\nでPOSTしているようです。

 今回は案3を採用し、POSTするJSONを作る前に改行を\nに変換します。

【LotusScriptコード-改行置換部分のみ】

	sRequest		= Replace(sRequest, Chr(13) & Chr(10), "\n")		'改行を\nに変換

ソースコードを送信したい-追記しました

 JavaScriptやPythonなど何らかのプログラムソースをPOSTしたい場合、改行コード問題と同じエラーが出ることがわかりました。エラーの原因は特殊文字にあるようです。
 ひとまず問題がありそうなダブルコーテーションのエスケープ、TABの置換を行います。

【LotusScriptコード-置換部分のみ】

	'ソースコード送信対策
	sRequest = Replace(sRequest, """", "\""")						'ダブルコートをエスケープ
	sRequest = Replace(sRequest, Chr(9), " ")							'TABを半角スペースに変換	

残すボタンの実装

 気に入った回答は残すボタンを使って履歴フォームの文書として保存します。履歴フォームは入力フォームと似た感じで作成し、「質問」フィールドなど変更させたくないフィールドは「作成時の計算結果」として作成して下さい。

【履歴フォーム-frmChatHistory】

 前回の質問を引き継いで質問するために、入力フォームには「質問履歴」フィールドと「継続質問」フィールドを追加します。「質問履歴」フィールドは「複数値可能」で作成し、「残す」ボタンクリック時に現在の質問を追加して残していきます。「継続質問」フィールドはチェックボックスで作成し、選択肢に「継続質問 | 1」と設定し、チェックをつけた際に「1」が入力されるように設定します。

【入力フォーム-frmChatInput】

【LotusScriptコード-残すボタン】

Sub Click(Source As Button)
	'---------- ---------- ---------- ---------- ---------- 
	'入力フォームの内容を履歴フォームにコピーして保存
	'
	'---------- ---------- ---------- ---------- ----------
	'クラス・変数宣言	
	Dim session	As New NotesSession
	Dim db			As NotesDatabase
	Dim newdoc	As NotesDocument
	
	Dim ws			As New NotesUIWorkspace	
	Dim uidoc		As NotesUIDocument
	Dim doc		As NotesDocument
	Dim item		As NotesItem
	
	Dim sRequest	As String					'入力した質問
	
	'クラス・変数セット	
	Set db			= session.CurrentDatabase
	Set uidoc		= ws.CurrentDocument
	Set doc		= uidoc.Document
	Set item		= doc.GetFirstItem("RequestList")
	
	'入力チェック
	If doc.Response(0) = "" Then
		Msgbox "回答が登録されていません。",16,"エラー"
		Exit Sub
	End If
	If doc.Category(0) = "" Then
		Msgbox "カテゴリを入力してください。",16,"エラー"
		Exit Sub
	End If
	
	'回答履歴文書を作成し、回答内容をコピー、文書を保存
	Set newdoc		= db.CreateDocument
	newdoc.Form		= "frmChatHistory"
	newdoc.Request	= doc.Request
	newdoc.Response	= doc.Response
	newdoc.FactCheck	= doc.FactCheck
	newdoc.Category	= doc.Category
	newdoc.Quality		= doc.Quality
	
	Call newdoc.ComputeWithForm(True, True)
	Call newdoc.Save(True, True)
	
	'質問を質問履歴に書き込み
	sRequest		= doc.Request(0)
	sRequest		= Replace(sRequest, Chr(13) & Chr(10), "\n")
	'ソースコード送信対策
	sRequest = Replace(sRequest, """", "\""")						'ダブルコートをエスケープ
	sRequest = Replace(sRequest, Chr(9), " ")							'TABを半角スペースに変換	
	Call item.AppendToTextList(sRequest)	
	
	'質問フォームの書き込み内容をクリア
	doc.Request	= ""
	doc.Response	= ""
	doc.FactCheck	= ""
	doc.Category	= ""
	doc.Quality		= "3"
	
	'ビューを更新
	Call ws.Viewrefresh()
	Msgbox "done"
End Sub

前回の質問を引き継いで質問したい

 前回の質問を引き継いで質問したい場合があります。例えば「ChatGPTに問合せを行うサンプルコードをJavaScriptで書いて下さい。」の後に「VBAに書き換えて下さい。」と質問したい場合です。過去の質問に関連して現在の質問を行いたい場合は、過去の質問と現在の質問を「\n」で連結してPOSTします。

【問合せJSON例】

{
    "model": "gpt-3.5-turbo",
    "messages": [
        {
            "role": "user",
            "content": "OpenAIのAPIを呼び出すサンプルコードをJavaScriptで書いてください。\n先ほどのコードをVBAに書き換えてください。"
        }
    ]
}

 LotusScriptでは、複数値可能に設定した「質問履歴」フィールドから取得したリストを「\n」区切りの文字列に変換後、新規の「質問」を連結し、POSTします。

【LotusScriptコード-送信ボタン】

Sub Click(Source As Button)
	'---------- ---------- ---------- ---------- ---------- 
	'OpenAI APIから取得したレスポンスを書込み
	'
	'---------- ---------- ---------- ---------- ---------- 	
	Const APIURL = "https://api.openai.com/v1/chat/completions"
	Const APIKEY = "(取得したAPIKEY)"
	Const MODEL = "gpt-3.5-turbo"
	
	'クラス・変数宣言	
	Dim ws				As New NotesUIWorkspace
	Dim uidoc			As NotesUIDocument
	Dim doc			As NotesDocument
	Dim session		As New NotesSession
	Dim jsonNav		As NotesJSONNavigator
	
	Dim vXml			As Variant					'XMLオブジェクト
	Dim sUrl			As String					'送信するURL	
	Dim sRequest		As String					'入力した質問
	Dim sBodyJson		As String					'送信するJSON文字列
	Dim sContent		As String					'回答本文
	Dim sCarryOver	As String					'前回の質問引き継ぎフラグ
	Dim vRequestList	As Variant					'質問履歴(リスト)
	
	'クラス・変数セット
	Set uidoc		= ws.CurrentDocument
	Set doc		= uidoc.Document
	
	'入力チェック
	sRequest		= doc.Request(0)
	If sRequest = "" Then
		Msgbox "質問を入力して下さい。",64,"確認"
		Exit Sub
	End If	
	
	'継続質問フラグ確認と送信JSONの作成
	sCarryOver		= doc.CarryOver(0)					'質問引き継ぎフラグ取得
	sRequest		= Replace(sRequest, Chr(13) & Chr(10), "\n")	'質問の改行を「\n」に変換(改行はエラーになる)
	'ソースコード送信対策
	sRequest = Replace(sRequest, """", "\""")						'ダブルコートをエスケープ
	sRequest = Replace(sRequest, Chr(9), " ")							'TABを半角スペースに変換	
		
	If sCarryOver = "1" Then
		vRequestList	= doc.RequestList
		sBodyJson		= |{"model":"| & MODEL & |", "messages":[ {"role":"user", "content":"| & Join(vRequestList,"\n") & "\n" & sRequest & |"} ] }|
	Else
		sBodyJson		= |{"model":"| & MODEL & |", "messages":[ {"role":"user", "content":"| & sRequest & |"} ] }|			
	End If
	'Msgbox sBodyJson		'< for debug>
	
	
	'Getリクエストを実行し、結果を取得
	Set vXml = CreateObject("MSXML2.XMLHTTP")	
	vXml.Open "POST", APIURL, False
	vXml.setRequestHeader "Content-Type", "application/json"
	vXml.setRequestHeader "Authorization", "Bearer " & APIKEY
	
	vXml.send sBodyJson
	
	'エラーレスポンスチェック
	If vXml.Status <> 200 Then
		Msgbox vXml.responseText, 16, "Error : " & Cstr(vXml.Status)
		Exit Sub
	End If
	
	'レスポンスから回答を抽出
	Set jsonNav	= session.CreateJSONNavigator(vXml.responseText)
	sContent		= jsonNav.GetElementByPointer("/choices/0/message/content").Value
	
	'回答をフォームに書込み
	doc.HttpStatus	= vXml.Status
	doc.Response	= sContent
	
	
End Sub

回答待ち状態への対応

ChatGPTのAPIはレスポンスまでに時間がかかります(Web版ChatGPTより遅い)。処理中であることを表示するため、WindowsAPIを使ってマウスカーソルを下記のように変化させます。

  • 処理中-砂時計アイコン(コントロールパネルの設定でアイコンは変更できます)
  • 処理終了-通常アイコン

【LotusScriptコード-送信ボタン(WindowsAPI宣言と関数)】

Declare Function LoadCursor Lib "user32" Alias "LoadCursorA" (Byval hInstance As Long, Byval lpCursorName As Long) As Long
Declare Function SetCursor Lib "user32" (Byval hCursor As Long) As Long

Sub ChangeCursorToHourglass()
	'---------- ---------- ---------- ---------- ---------- 
	' Windows API : 砂時計マークのカーソルをロード
	'
	'---------- ---------- ---------- ---------- ---------- 
	Dim hCursor As Long
	hCursor = LoadCursor(0, 32514) ' 32514 は砂時計マークのリソースID
	
	SetCursor hCursor				' カーソルを設定
End Sub

Sub RestoreCursor()
	'---------- ---------- ---------- ---------- ---------- 
	' WindowsAPI : デフォルトのカーソルに戻す
	'
	'---------- ---------- ---------- ---------- ---------- 
	
	SetCursor 0			' カーソルを設定
End Sub

【LotusScriptコード-送信ボタン】

Sub Click(Source As Button)
	'---------- ---------- ---------- ---------- ---------- 
	'OpenAI APIから取得したレスポンスを書込み
	'
	'---------- ---------- ---------- ---------- ---------- 	
	Const APIURL = "https://api.openai.com/v1/chat/completions"
	Const APIKEY = "(取得したAPIKEY)"
	Const MODEL = "gpt-3.5-turbo"
	
	'クラス・変数宣言	
	Dim ws				As New NotesUIWorkspace
	Dim uidoc			As NotesUIDocument
	Dim doc			As NotesDocument
	Dim session		As New NotesSession
	Dim jsonNav		As NotesJSONNavigator
	
	Dim vXml			As Variant					'XMLオブジェクト
	Dim sUrl			As String					'送信するURL	
	Dim sRequest		As String					'入力した質問
	Dim sBodyJson		As String					'送信するJSON文字列
	Dim sContent		As String					'回答本文
	Dim sCarryOver	As String					'前回の質問引き継ぎフラグ
	Dim vRequestList	As Variant					'質問履歴(リスト)
	
	'クラス・変数セット
	Set uidoc		= ws.CurrentDocument
	Set doc		= uidoc.Document
	
	'入力チェック
	sRequest		= doc.Request(0)
	If sRequest = "" Then
		Msgbox "質問を入力して下さい。",64,"確認"
		Exit Sub
	End If	
	
	'継続質問フラグ確認と送信JSONの作成
	sCarryOver		= doc.CarryOver(0)					'質問引き継ぎフラグ取得
	sRequest		= Replace(sRequest, Chr(13) & Chr(10), "\n")	'質問の改行を「\n」に変換(改行はエラーになる)
	'ソースコード送信対策
	sRequest = Replace(sRequest, """", "\""")						'ダブルコートをエスケープ
	sRequest = Replace(sRequest, Chr(9), " ")							'TABを半角スペースに変換	
	
	If sCarryOver = "1" Then
		vRequestList	= doc.RequestList				'質問履歴フィールド取得
		sBodyJson		= |{"model":"| & MODEL & |", "messages":[ {"role":"user", "content":"| & Join(vRequestList,"\n") & "\n" & sRequest & |"} ] }|
	Else
		sBodyJson		= |{"model":"| & MODEL & |", "messages":[ {"role":"user", "content":"| & sRequest & |"} ] }|			
	End If
	'Msgbox sBodyJson		'< for debug>
	
	
	'Getリクエストを実行し、結果を取得
	Call ChangeCursorToHourglass()		'マウスカーソルを砂時計に変更
	
	Set vXml = CreateObject("MSXML2.XMLHTTP")	
	vXml.Open "POST", APIURL, False
	vXml.setRequestHeader "Content-Type", "application/json"
	vXml.setRequestHeader "Authorization", "Bearer " & APIKEY
	
	vXml.send sBodyJson
	
	'エラーレスポンスチェック
	If vXml.Status <> 200 Then
		Msgbox vXml.responseText, 16, "Error : " & Cstr(vXml.Status)
		Exit Sub
	End If
	
	'レスポンスから回答を抽出
	Set jsonNav	= session.CreateJSONNavigator(vXml.responseText)
	sContent		= jsonNav.GetElementByPointer("/choices/0/message/content").Value
	
	'回答をフォームに書込み
	doc.HttpStatus	= vXml.Status
	doc.Response	= sContent
	
	Call RestoreCursor()				'マウスカーソルを元に戻す
	
End Sub

【完成イメージ】

設計公開NTFのダウンロード

 今回のブログで紹介したコードを含む設計公開NTFをアップロードしましたので、必要に応じてご活用下さい。OpenAI APIのAPIKEYは含んでおりませんので、各自で取得し、設定文書に登録して頂く必要があります。

【ダウンロードリンク】

chatgpt.zip

最後に

 ChatGPTの回答は必ずしも正しいとは限りませんが、下記のようなシーンでは活用できる可能性があります。

  1. 知らないことを学習するために興味のあることをどんどん質問する。
  2. 提示した文章を要約する。
  3. 他言語に翻訳する。
  4. プログラムのリファクタリング提案。
  5. 文章校正。などなど

 生成AIのメリットは、人間と違って、疲れない、怒らないという点にあるのではないかと思います。ですので、どんどん言い回し、表現方法を変更してみたり、英語で質問してみたりすれば有効に使いこなせるはずです。今回のサンプルコードをベースに自社にあったソリューションを生み出して貰えればと思います。