サンプルアプリ作成の2回目は、取得したレスポンス(JSON)から必要な値を取得する部分を作成します。
Responses API のレスポンスは #31 で紹介したように複雑な構造になっています。例えば API の返答が含まれる output ノードは配列となっており、要素の個数や順序が固定ではありません。その中から type が message となっているものを探し出す必要がありました。
|
{ "id": "resp_xxxxx", "object": "response", ・・・(省略)・・・ "moderation": null, "output": [ { "id": "rs_xxxxx", "type": "reasoning", "content": [], "summary": [] }, { "id": "msg_xxxxx", "type": "message", "status": "completed", "content": [ { "type": "output_text", "annotations": [], "logprobs": [], "text": "大阪府の県庁所在地は大阪市です。" } ], "role": "assistant" } ], ・・・(省略)・・・ } |
← output は配列 ← 1つ目の要素は回答ではない ← 2つ目の要素が回答 ← 回答 |
実際にコーディングを行うと JSON の構造をたどるコードと Responses API 特有の意味を解釈するコードが混在し、煩雑で理解しがたいコードになります。
そこで、今回のサンプルでは JSON の汎用的な操作をライブラリに定義します。「JSON の構造をたどるコード」をライブラリに外出しにして、メインルーチンは「JSON の意味を解釈するコード」に特化させ、コードの可読性を上げようという算段です。
ライブラリの作成
今回利用する Responses API のレスポンス調査では、次のような JSON 操作が必要となります。
| 関数名 | 機能 | 使用例 |
| GetProperty | 名前を指定して、子ノードを取得します。 | output ノードを取得 |
| GetPropertyValue | 名前を指定して、子ノードの値を取得します。 | text ノードを探してその値を取得 |
| FindInArray | 配列からノードと値が一致した要素を返します。 | output ノードから type = message の子ノードを取得 |
新規でスクリプトライブラリ lsJSONNavi を作成して、以下のコードを記述します。
|
Option Declare %REM 名前を指定して、子ノードを取得します。 ◆ 引数 1. voParent Variant 取得もととなる親ノード NotesJSONElement の場合:値が NotesJSONObject であれば、子ノードを調査 NotesJSONObject、NotesJSONNavigator の場合:その子ノードを調査 2. vsName String 取得するノード名 ◆ データ型(戻り値) NotesJSONElement 一致したノードを返します。 見つからない場合は Nothing を返します。 %END REM Public Function GetProperty(voParent As Variant, ByVal vsName As String) As NotesJSONElement Dim jobj As NotesJSONObject Dim sType As String On Error GoTo Err_General sType = TypeName(voParent) If sType = "NOTESJSONELEMENT" Then Set jobj = voParent.Value Set GetProperty = jobj.GetElementByName(vsName) ElseIf sType = "NOTESJSONNAVIGATOR" Then Set GetProperty = voParent.GetElementByName(vsName) ElseIf sType = "NOTESJSONOBJECT" Then Set GetProperty = voParent.GetElementByName(vsName) End If Exit_Proc: Exit Function Err_General: Set GetProperty = Nothing Resume Exit_Proc End Function %REM 名前を指定して、子ノードの値を取得します。 ◆ 引数 1. voParent Variant 取得もととなる親ノード NotesJSONElement の場合:値が NotesJSONObject であれば、子ノードを調査 NotesJSONObject、NotesJSONNavigator の場合:その子ノードを調査 2. vsName String 取得するノード名 ◆ データ型(戻り値) String 一致したノードの値を返します。 見つからない場合は Null を返します。 %END REM Public Function GetPropertyValue(voParent As Variant, ByVal vsName As String) As String Dim je As NotesJSONElement Dim sType As String On Error GoTo Err_General sType = TypeName(voParent) If sType = "NOTESJSONELEMENT" Then Set je = GetProperty(voParent, vsName) ElseIf sType = "NOTESJSONNAVIGATOR" Then Set je = voParent.GetElementByName(vsName) ElseIf sType = "NOTESJSONOBJECT" Then Set je = voParent.GetElementByName(vsName) End If GetPropertyValue = je.Value Exit_Proc: Exit Function Err_General: GetPropertyValue = "" Resume Exit_Proc End Function %REM 配列の要素からノードと値が一致した要素を返します。 ◆ 引数 1. voArray Variant 調査する配列 NotesJSONAarray の場合:その配列の各要素を調査 NotesJSONElement の場合:値が配列の場合、各要素を調査 2. vsName String ノード名 3. vsValue String ノード名の値 ◆ データ型(戻り値) NotesJSONElement 一致した要素(= ノード)を返します。 見つからない場合は Nothing を返します。 %END REM Public Function FindInArray(voArray As Variant, ByVal vsName As String, ByVal vsValue As String) As NotesJSONElement Dim je As NotesJSONElement Dim ja As NotesJSONArray Dim sType As String Dim sVal As String On Error GoTo Err_General sType = TypeName(voArray) If sType = "NOTESJSONARRAY" Then '配列なのでセット Set ja = voArray ElseIf sType = "NOTESJSONELEMENT" Then 'Elemetの値が配列なら処理 Set ja = voArray.Value End If If Not ja Is Nothing Then Set je = ja.GetFirstElement() Do Until je Is Nothing sVal = GetPropertyValue(je, vsName) If sVal = vsValue Then '発見 Set FindInArray = je Exit Do End If Set je = ja.GetNextElement() Loop End If Exit_Proc: Exit Function Err_General: Set FindInArray = Nothing Resume Exit_Proc End Function |
要素の取得もとは、JSON ツリーのトップレベルである NotesJSONNavigator であっても、サブツリーを表す NotesJSONObject でも動作するようにします。また、NotesJSONElement であっても値が NotesJSONObject であればそこから取得するようにしています。関数の利用者は、JSON オブジェクトの型をあまり気にすることなく利用できるようにしてみました。
今のところ JSON の操作は手探りで、例外処理も不完全と思います。まだまだよい関数というには程遠いですが、いったん掲載しておきます。今後改善や機能追加することもあるので、その点はご了承ください。
Responses API レスポンス取得
作成しているサンプルアプリに戻ります。
まず、取得した回答を記録するフィールドをフォームに作成します。
続いて、前回作成したエージェントにライブラリを組み込み、レスポンス取得の関数を追加します。取得するのは、レスポンスの最初の項目である id と返答です。
|
Use "lsJSONNavi" Function xGetResponce(vnd As NotesDocument, vjnav As NotesJSONNavigator) As Boolean Dim je As NotesJSONElement Set je = vjnav.Getfirstelement() ' id の値を取得 vnd.Result_ID = GetPropertyValue(vjnav, "id") ’ 返答を取得 ' ① output ノードを取得 Set je = GetProperty(vjnav, "output") ' ② "type": "message" のノードを取得 Set je = FindInArray(je, "type", "message") ' ③ content ノードを取得 Set je = GetProperty(je, "content") ' ④ "type": "output_text" Set je = FindInArray(je, "type", "output_text") ' ⑤ 返答を取得してセット vnd.Result_Content = GetPropertyValue(je, "text") xGetResponce = True End Function |
最後に作成した関数をメインルーチンに組み込んで完成です。
|
Sub Initialize ・・・(省略)・・・ '問い合わせ実行 Set jnavResponse = xAskResponses(jnavRequest) Call xSetJSON(nd, "Response", jnavResponse) 'Response の保存 '返答を取得 If xGetResponce(nd, jnavResponse) Then Call nd.Save(True, False) '文書を開きなおす Call nuid.Close() Call nuiw.EditDocument(False, nd) End If End Sub |
実行して、ID と回答が取得できれば OK です。
まとめ
今回はレスポンス JSON から必要な情報を取得する部分を作成しました。
その過程で JSON から必要な情報を効率よく抽出するための関数群作成し、利用しました。xGetResponce 関数を見ていただけるとわかると思いますが、JSON をどのようにたどっているのか、一目瞭然になっています。これだけシンプルになっていれば、処理内容が一瞬で理解できます。
『動けばいい』でとどめず、後々のメンテナンス性まで考えてコーディングしたいですね。
内容的には、本題の Responses API からは少々外れてしまったような気もしますが ...
| 前回 | 連載:つないでみよう | 次回 |































