2026/07/26

つないでみよう:#35) Responses API - 画像生成

前回までで、一番単純な会話ができるようになったので、次は画像生成をさせてみましょう。

Responses API は、画像生成にも対応しているので、/images/generations など別の API をコールする必要はありません。例えば、次のような JSON を送信することで、Responses API に画像生成させることができます。

{
   "model": "gpt-5-nano",
   "input": "ビーチリゾートに行きたくなるような、白い砂浜とヤシの木の画像",
   "tools": [
      {
         "type": "image_generation",
         "model": "gpt-image-2",
         "size": "1024x1024",
         "output_format": "png",
         "quality": "low"
      }
   ]
}

重要なのは tools 配列を指定し、その中で type に image_generation を設定している点です。設定値からもわかるように、Responses API では画像生成も「ツール」の一つとして扱われます。次の model が画像生成を担当するモデルです。現在の推奨モデルは gpt-image-2 で、size、output_format、quality は画像生成時のオプションです。指定できる項目や設定値はモデルによって異なるため、必要に応じて公式ドキュメントを確認してください。

この例では、プロンプトの解釈を gpt-5-nano が担当し、実際の画像生成を gpt-image-2 が担当します。このように Responses API では、それぞれの役割に応じてモデルを組み合わせて利用できるのが特徴です。


画像生成機能の作成方針

前回までに紹介した会話のサンプルでは、新規と継続で別々のフォーム、エージェントで作成しました。今回の画像生成では、1つにまとめたいと思います。

新規か継続の判断は、文書に実行結果が入っているかで判断するものとします。具体的には、Result_ID に値が入っていれば、継続として処理します。


フォームの作成

画像生成用のフォームをサンプルアプリに追加します。

前回 #34 で作成した fGPT_Res02 フォーム(継続元ID フィールドがある)をコピペして、画像生成用フォーム fGPT_Res03 を作成します。

フィールドは基本的にはそのままなのですが、今回の返答が画像となります。そこで、実行結果のフィールドをリッチテキスト Result_Image に変更します。


エージェントの作成

フォームと同様にエージェントも前回作成のエージェントをコピペして作業を開始します。


◇ Initialize

基本的な処理の流れに変更はありません。ただ、新規と継続をまとめたことで中盤の太字の部分が大きく違います。また、この変更に関連して変数名や処理順を一部変更していますので、Initialize 全体を掲載しています。

Sub Initialize
   Dim nuiw As New NotesUIWorkspace
   Dim nuid As NotesUIDocument
   Dim ndb As NotesDatabase
   Dim ndCur As NotesDocument    'UI で開いている文書
   Dim ndReq As NotesDocument   '問い合わせに利用する文書

   Dim jnavRequest As NotesJSONNavigator
   Dim jnavResponse As NotesJSONNavigator

   Dim s As String

   '初期化
   Set xns = New NotesSession
   Set ndb = xns.CurrentDatabase

   '現在の文書(= 継続元の文書)
   Set nuid = nuiw.Currentdocument
   Set ndCur = nuid.Document    ' UI の文書は読み込みモードの前提

   ' 新規 or 継続の判定
   If ndCur.Result_ID(0) = "" Then
      '新規リクエスト(現在の文書で問い合わせ)
      Set ndReq = ndCur
   Else
      '継続したリクエスト(現在の文書を参照させる設定で新規作成)
      s = nuiw.Prompt(PROMPT_OKCANCELEDIT, "継続", "次のプロンプトを入力してください。")
      If s = "" Then Exit Sub

      '新規文書作成
      Set ndReq = ndb.CreateDocument()
      ndReq.Form = "fGPT_Res03"
      ndReq.Model = ndCur.Model
      ndReq.PrevID = ndCur.Result_ID    '現在の文書を参照
      ndReq.Prompt = s
   End If


   'Request Body(JSON) の作成
   Set jnavRequest = xGetRequest_JSON(ndReq)
   Call xSetJSON(ndReq, "Request", jnavRequest)    'Request Body の保存

   '問い合わせ実行
   Set jnavResponse = xAskResponses(jnavRequest)
   Call xSetJSON(ndReq, "Response", jnavResponse)    'Response の保存

   '返答を取得
   If xGetResponce(ndReq, jnavResponse) Then
      Call ndReq.Save(True, False)

      '文書を開きなおす
      Call nuid.Close()
      Call nuiw.EditDocument(False, ndReq)
   End If
End Sub


◇ xGetRequest_JSON

Responses API に送信する関数で、画像生成を指定する tools 配列を追加しています。今回はサンプルですので、model や size などには固定値を追加しています。

Sub Function xGetRequest_JSON(vnd As NotesDocument) As NotesJSONNavigator
   Dim jnav As NotesJSONNavigator
   Dim jaTol As NotesJSONArray
   Dim joTol As NotesJSONObject

   'RequestBody(JSON) の準備
   Set jnav = xns.CreateJSONNavigator("")

   '1.model
   Call jnav.AppendElement(vnd.Model(0),"model")

   '1.previous_response_id  継続の場合のみ設定
   If vnd.PrevID(0) <> "" Then
      Call jnav.AppendElement(vnd.PrevID(0),"previous_response_id")
   End If

   '1.input
   Call jnav.AppendElement(vnd.Prompt(0),"input")

   '1.tools 配列
   Set jaTol = jnav.AppendArray("tools")

   '2.image_generation
   Set joTol = jaTol.AppendObject()
   Call joTol.AppendElement("image_generation", "type")
   Call joTol.AppendElement("gpt-image-2", "model")
   Call joTol.AppendElement("1024x1024", "size")
   Call joTol.AppendElement("png", "output_format")
   Call joTol.AppendElement("low", "quality")


   Set xGetRequest_JSON = jnav
End Function


◇ xAskResponses

画像生成は会話より応答時間がかかります。そこで、タイムアウトを 450 秒に設定します(デフォルトは 30 秒)。ちなみに私が試した限り上記生成条件で、300 秒以内で返答が返ってきました。

Sub Function xAskResponses(vjnavRequest As NotesJSONNavigator) As NotesJSONNavigator
         ・・・(省略)・・・
   'HTTP ヘッダーの設定
   Call http.SetHeaderField("Content-Type", "application/json")
   Call http.SetHeaderField("Authorization", "Bearer " & xcsBearer)


   'API 実行
   http.TimeOutSec = 450    '画像生成は時間がかかるので
   http.PreferJSONNavigator = True
         ・・・(省略)・・・
End Function


◇ xGetResponce

API のレスポンスから返答を取得する処理も変化します。生成された画像は output 配列内の type が image_generation_call にあります。これを取り出し、result の値が画像データとなります。

Function xGetResponce(vnd As NotesDocument, vjnav As NotesJSONNavigator) As Boolean
   Dim je As NotesJSONElement
   Dim sBase64 As String

   Set je = vjnav.Getfirstelement()

   'id 取得
   vnd.Result_ID = GetPropertyValue(vjnav, "id")

   'output 取得してセット
   Set je = GetProperty(vjnav, "output")

   '"type": "image_generation_call" の要素を取得
   Set je = FindInArray(je, "type", "image_generation_call")

   '生成画像(result)を取得してセット
   sBase64 = GetPropertyValue(je, "result")
   Call xSetImageFile(vnd, "Result_Image", sBase64)


   xGetResponce = True
End Function

画像データは Base64 でエンコードされているので、以下の3つの関数を通して、デコードし、画像ファイルに変換します。


◇ xSetImageFile

Base64 の画像データをデコード、画像ファイル化し、文書に添付する関数です。

Private Function xSetImageFile(vnd As NotesDocument, ByVal vsFld As String, vsBase64 As String)
   Dim nsImg As NotesStream
   Dim nrti As NotesRichTextItem
   Dim sFP As String

   sFP = xns.GetEnvironmentString("Directory", True)
   sFP = sFP & "\" & Format(Now, "yyyymmddhhnnss") & ".png"

   On Error Resume Next
   Kill sFP    '存在してたら削除

   On Error GoTo Err_General
   Set nsImg = xns.CreateStream()
   Call Base64ToBinary(vsBase64, nsImg)   'デコード
   Call StreamToImageFile(nsImg, sFP)    '画像ファイルとして保存

   '画像ファイルを文書に添付
   Call vnd.RemoveItem(vsFld)
   Set nrti = vnd.CreateRichTextItem(vsFld)
   Call nrti.EmbedObject(EMBED_ATTACHMENT, "", sFP)

Exit_Proc:
   Exit Function

Err_General:
   MsgBox Error$
   Resume Exit_Proc
End Function


◇ Base64ToBinary / StreamToImageFile

これらの関数は  OpenNTFLotusScript Gold Collection プロジェクトから拝借した関数です。詳しくは #28 で紹介しているので必要に応じて参照ください。関数自体は下記に再掲載します。

%REM
   Sub Base64ToBinary
   Description: Given a string of base64-encoded data, write into a binary stream we are passed.
      This is done rather than creating the stream here and returning it, so that you can
   stream directly into a file if you choose.
%END REM

Sub Base64ToBinary(strBase64$, streamOut As NotesStream)
   ' Given a string of base64 encoded data, this routine decodes and writes the original binary data into a NotesStream
   Dim doc As NotesDocument
   Dim mime As NotesMIMEEntity
   Dim streamIn As NotesStream
   Dim db As NotesDatabase
   Dim session As New NotesSession

   Set db = session.CurrentDatabase
   Set doc = db.CreateDocument
   Set mime = doc.CreateMIMEEntity("Body") ' the mime classes already know how to do this conversion,
   Set streamIn = session.CreateStream
   Call streamIn.WriteText(strBase64)
   streamIn.Position = 0
   Call mime.SetContentFromText(streamIn, "binary", ENC_BASE64)
   Call mime.GetContentAsBytes(streamOut, True) ' decode as you stream out the data.
End Sub

Sub StreamToImageFile(vnstImage As NotesStream, ByVal vsFP As String)
   Dim nstOut As NotesStream

   On Error Resume Next
   Kill vsFP '存在してたら削除

   Set nstOut = xns.CreateStream()
   Call nstOut.Open(vsFP)

   vnstImage.Position = 0
   Do Until vnstImage.Position >= vnstImage.Bytes
      Call nstOut.Write(vnstImage.Read(16000))
   Loop
End Sub


想定外の改造

画像生成のサンプルとしては、上記までで完了のつもりでした。ただ、実行するとエラーが発生しました。

原因は、API のレスポンスが大きく、リッチテキストに書き込む処理で  リッチテキストの制限 に引っ掛かっていたためだと思われます(どちらも 64 KB まで)。

  • リッチテキストフィールドの単一段落の最大サイズ
  • 文書当たりのテキスト (要約) データの最大量

そこで、JSON のサイズが大きい場合、添付ファイルとして保存するように変更します。


◇ xSetJSON

JSON を文書に保存する関数で、サイズが大きい場合(10000 バイト)、後述する JSONToFile を通して、ファイルに保存します。

Private Function xSetJSON(vnd As NotesDocument, ByVal vsFld As String, vjnav As NotesJSONNavigator)
            ・・・(省略)・・・

   '日時の記録
   Call vnd.ReplaceItemValue(vsFld & "_DT", Now)

   'JSON の記録
   'いったん削除してからセット

   Dim sFP As String
   Call vnd.RemoveItem(vsFld & "_JSON")
   Set nrti = vnd.CreateRichTextItem(vsFld & "_JSON")

   If Len(sText) > 10000 Then
      '大きいのでファイルで保存
      sFP = JSONToFile(vjnav, "")    'ファイル名は指定なし
      Call nrti.EmbedObject(EMBED_ATTACHMENT, "", sFP)

      Kill sFP  
  '添付したのでファイルは削除
   Else
      'そのままテキストで保存
      Call nrti.AppendText(sText)
   End If
End Function


◇ JSONToFile

JSON をファイルに保存する関数です。この関数はほかでも使いそうなので、lsJSONNavi ライブラリに作成します。

Public Function JSONToFile(vjnav As NotesJSONNavigator, ByVal vsFileName As String) As String
   Dim ns As New NotesSession
   Dim nstOut As NotesStream
   Dim sFP As String

   sFP = ns.Getenvironmentstring("Directory", True)
   If vsFileName = "" Then
      sFP = sFP & "\" & Format(Now, "yyyymmddhhnnss") & ".json"
   Else
      sFP = sFP & "\" & vsFileName
   End If

   On Error Resume Next
   Kill sFP   '存在してたら削除

   On Error GoTo Err_General

   Set nstOut = ns.CreateStream()
   Call nstOut.Open(sFP)

   Call nstOut.WriteText(vjnav.Stringify())
   Call nstOut.Close()

   JSONToFile = sFP
Exit_Proc:
   Exit Function

Err_General:
   MsgBox Error$
   Resume Exit_Proc
End Function


動作検証

出来上がったら動作検証します。

最初のリクエストを書いた文書を開き、エージェントを実行します。しばらくすると文書が開きなおし、画像が添付された状態となります。API からの返答は大きくなるので、JSON は返答だけが添付ファイルになっているはずです。この状態から再度エージェントを実行すると作成された画像に対するリクエストが送信できます。

今日も朝から暑い中 Notes 三昧なので、『ビーチリゾートに行きたくなるような、白い砂浜とヤシの木の画像』ネタだけは休日仕様にしてみました。

結果は次のような感じでした。

青い海に真っ白な砂浜、デッキチェアにパラソル、イメージ通りの画像ですね。次のリクエストでは『この画像を夕景に変更してください。』と送信しました。構図は少々変わってしまってますが、見事な夕焼けですね。

あぁ、リゾートしたい...


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


0 件のコメント:

コメントを投稿