2026/07/19

つないでみよう:#33) Responses API - サンプルアプリ作成 ②

サンプルアプリ作成の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 からは少々外れてしまったような気もしますが ...


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


0 件のコメント:

コメントを投稿