2024/12/07

つないでみよう:#番外)GPT4o Structured Outputs 活用事例 と dxlSuite

前回 #24)GPT4o Structured Outputs - 活用事例 ② で紹介したサンプルアプリ『AI が導く、次世代の学習体験 - AI Learning』で、AI が作成したコンテンツをどのようにノーツの文書に展開したかを紹介します。今回の内容は WebAPI の結果活用であり、"つながる" 部分ではないので "番外" としています。


フォームの構造

まずは、コンテンツフォームの設計です。

上部のグレーの部分は管理用の隠しフィールドで、ユーザ向けのフィールドはタイトル (Title) と本文 (Body) だけとなっています。

実際の文書は次のようになっています(画像は管理フィールドを表示して取得)。AI のレスポンスをリッチテキストに体裁よく表示させています。


本文の構造

通常の文字はフォントや文字サイズ、行間などを設定しつつ見栄えよく表示しています。キーワードとなるリンクは、ホットスポットのアクションで構成されています。

アクションは LotusScript で記述されており、クリックしたキーワードとそのキーワードが含まれる文章を引数にした関数が指定されています。

Sub Click(Source As Button)
   Call ClickKeyword("ピザ生地", "ピザの作り方は、まず材料を揃えることから始まります。基本の材料にはピザ生地、トマトソース、チーズ、そしてお好みのトッピングが挙げられます。")
End Sub

コールしている関数は、スクリプトライブラリに定義されています。

この関数は、引数を利用して過去のクリック履歴を確認、初回であれば AI に問い合わせ、履歴があればその時の結果の文書を開く機能になっています。

問い合わせする際には、クリックしたキーワードだけでなく、キーワードを含む文章を含めて送信しています。AI に送信しているリクエストだけを抜粋すると次のようになります。

『ピザ屋の運営』について学習しています。
学習のゴールは次の通りです。
ピザの作り方、飲食店でのコスト構造、経営上の課題を理解する
その中で『ピザの作り方は、まず材料を揃えることから始まります。基本の材料にはピザ生地、トマトソース、チーズ、そしてお好みのトッピングが挙げられます。』という文書があり、『ピザ生地』についてさらに詳しく教えてください。

これにより、AI が的外れな回答をしないようにしています。また、同じキーワードであっても段落が違えば別のコンテンツが生成されるようになります。


コンテンツのリッチテキスト化

このリクエストの返信は Structured Outputs の機能で、次のような JSON となります。

このレスポンスをリッチテキストに体裁よく表示したり、リンクを生成しているのが『DominoHub 2024 Osaka 大盛況 & DXL 拡張ライブラリリリース!』で紹介した dxlSuite for LotusScript です。

リンクを作成する部分を事例に dxlSuite の機能を紹介します。

リンクは、先ほど紹介した LotusScript のコードがセットされていること以外に、ホットスポットには境界線の指定がなく太字となっています。また、リンクが目立つように左右にスペースが入っています。

このリンクを作成する関数だけを抜粋すると次の通りです。

Function xDrawKeyword(voPar As DXL_Paragraph, ByVal vsKeyword As String, ByVal vsText As String)
   Dim oHS As DXL_HotSpot_Action

   'キーワードが段落の先頭でなければ、スペースを追加
   If voPar.GetText() <> "" Then
      Call voPar.AppendText_F(" ", xoFont_Body)
   End If

   'ホットスポットの作成(式は後で入れ替えるのでダミー)
   Set oHS = voPar.AppendHotSpot_Action(vsKeyword, "dummy")

   'ホットスポットのデザイン調整
   Call oHS.SetFont(xoFont_Keyword)
   oHS.ShowBorder = False

   'LotusScript のプログラムを準備
   Dim oCode As New DXL_Code(DXL.Code_LotusScript)
   Dim oLS As DXL_LotusScript

   Set oLS = New DXL_LotusScript()
   Call oLS.AppendScript(|Sub Click(Source As Button)|)
   Call oLS.AppendScript(|Call ClickKeyword("| & vsKeyword & |", | & xConvStr_LSString(vsText) & |)|)
   Call oLS.AppendScript(|End Sub|)
   Call oCode.ReplaceEvent("click", oLS)

   'ホットスポットのプログラムを準備した LotusScript に差し替え
   Call oHS.ReplaceCode(oCode)

   'キーワードの後ろにスペースを追加
   Call voPar.AppendText_F(" ", xoFont_Body)
End Function

引数の voPar が dxlSuite のオブジェクト DXL_Paragraph でリッチテキストの段落を表します。このオブジェクトにリンク手前の文章までがセットされた状態でこの関数に渡されるということになります。残りの引数は作成するリンクの情報です。

voPar 現在処理中のリッチテキストの段落を表すオブジェクト
vsKeyword 作成するリンクのテキスト(赤字
vsText リンクが含まれる文章(青字


リンクの追加

dxlSuite でリンクを作成する部分を順に解説します。

まず、AppendHotSpot_Action メソッドでリンクを作成します。戻り値はリンクであるホットスポットのアクションを表す DXL_HotSpot_Action のオブジェクトです。

   'ホットスポットの作成(式は後で入れ替えるのでダミー)
   Set oHS = voPar.AppendHotSpot_Action(vsKeyword, "dummy")

このメソッドは式言語のアクションを作成する機能がデフォルトです。今回は LotusScript のプログラムに後で入れ替えるため、適当な文字列 ”dummy” を式として指定しています。


リンクができあがったら、リンクの見た目を整えます。ホットスポットのオブジェクト oHS に対してフォントと境界線をなしにセットしています。

   'ホットスポットのデザイン調整
   Call oHS.SetFont(xoFont_Keyword)
   oHS.ShowBorder = False

なお、引数の xoFont_Keyword は、この関数外で事前に準備したリンク用のフォント情報で、メイリオ / 12 ポイント / 太字 / 青 となっています。

   With xoFont_Keyword
      .FontName = "メイリオ"
      .FontSize = 12
      .Bold = True
      .Color = DXL.Color_Blue
   End With


LotusScript のプログラムは DXL_LotusScript オブジェクトの AppendScript メソッドで 1 行ずつ追加して作成します。

   'LotusScript のプログラムを準備
   Dim oCode As New DXL_Code(DXL.Code_LotusScript)
   Dim oLS As DXL_LotusScript

   Set oLS = New DXL_LotusScript()
   Call oLS.AppendScript(|Sub Click(Source As Button)|)
   Call oLS.AppendScript(|Call ClickKeyword("| & vsKeyword & |", | & xConvStr_LSString(vsText) & |)|)
   Call oLS.AppendScript(|End Sub|)
   Call oCode.ReplaceEvent("click", oLS)

プログラムが完成したら DXL_Code オブジェクトの ReplaceEvent メソッドで Click イベント用のプログラムとしてセットします。


最後に ReplaceCode メソッドを使用して、ホットスポットのプログラムを作成したLotusScript に差し替えています。

   'ホットスポットのプログラムを準備した LotusScript に差し替え
   Call oHS.ReplaceCode(oCode)


まとめ

今回は GPT4o Structured Outputs 活用事例で利用した dxlSuite for LotusScript の機能について紹介させていただきました。

dxlSuite では、ホットスポットや段落のようにリッチテキスト内の構成要素、フォントやコード類などの管理単位でオブジェクトを用意しています。抜粋すると次の通りです。

dxlSuite クラス名 役割
DXL_RichTextItem リッチテキスト全体。
DXL_Table 表。タブやタイマー切替の形式、罫線や枠線、行や列の間隔など。
行列を指定してセルを取得。
DXL_Cell セル。罫線の幅や背景色やグラデーション、背景画像など。
カスケードの表に対応。
DXL_Paragraph 段落。文字や添付ファイル、ホットスポットなどを配置。
DXL_HotSpot_Action ホットスポットのアクション。
DXL_HotSpot_Button ホットスポットのボタン。
DXL_InlineImage インライン(見える状態)の画像。
DXL_LotusScript LotusScript のプログラム。
DXL_Formula 式言語のプログラム。
DXL_Style_Font 文字のフォントやサイズ、装飾。
DXL_Style_Hide 非表示設定や式。

各オブジェクトにはさまざまなプロパティやメソッドが用意されており、ノーツクライアントで操作できるほとんどの設定ができます。コーディング中にポップアップヘルプが表示されますので、簡単にコーディングできます。


今回はリンク(ホットスポットアクション)を例に dxlSuite の機能を紹介しました。

このライブラリはリッチテキストを縦横無尽、自由自在に操作することを目標に開発しました。今回の事例のように、標準のノーツクラスだけでは実現できない機能が作成できるようになります。ご興味のある方は以下のリンクを参照ください。


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


2024/12/04

Notes - Excel 連携:#51)グラフ調整のまとめ

#41#50 までの間、”使える”グラフにするための設定項目について紹介しました。このシリーズの最後に、紹介した機能をまとめます。


作画エリアの構成

まずはグラフの作画エリアである PlotArea オブジェクトです。このオブジェクトの位置(Top と Left)とサイズ(Height と Width)は 2 セット存在しました。"Inside" で始まるプロパティがグラフを表示するエリア(下図の赤枠)で、何もつかないプロパティが軸ラベルを含むエリアでした (#42)。


オブジェクトの配置と自動調整

グラフ内にテキストボックスを配置して位置を設定する方法について紹介しました(#46)。グラフもテキストボックスも一番外側のオブジェクトは Shape となります。そして、Shape の中に Shape オブジェクトを配置でき、親子関係のような管理となります。子のオブジェクトは親の範囲外にはみ出すと自動で調整されるため、結果が指定によるものなのか、調整されたものか判定しづらくなるので注意が必要でしたね。

また、Shape の左上の座標は (0, 0) ではなく (-4, -4) であり、4 ポイントのマージンが確保されていました。

この自動調整の対応とマージンのエリアを利用するために、親からはみ出ない位置でサイズを調整してから IncrementTop や IncrementLeft メソッドで希望の位置に配置する方法を紹介しました。

オブジェクトの回転は Rotation プロパティで行いました (#48)。回転はオブジェクトの中心を起点とすること、位置やサイズの設定は回転していない状態を基準に行うことがポイントです。また、回転後、親の範囲からはみ出た分は、自動調整されることにも注意が必要でした(紫部分)。


自動調整が行われるとサイズが変わるだけでなく、中心が変わることで位置 (Top と Left) が調整される点にも混乱のもととなるので注意してください。


利用したオブジェクト

今回のシリーズでは PlotArea を含め、次のグラフ構成オブジェクトを利用しました。

PlotAreaグラフ作画エリア
Axesグラフ内の軸(Axis オブジェクト)のコレクション
Axisグラフ内の 1 つの軸 を表します (#44)
ChartTitle標準のグラフタイトル(このシリーズでは非表示にしただけ)
Legendグラフ内の凡例 (#49)


オブジェクトの関係

登場したオブジェクトやプロパティ、メソッドを図式化すると次のようになります。今回のシリーズで解説した部分を赤字で表示しています。

ところで、グラフを追加する AddChart2 メソッドや TextFrame2 のように 末尾に ”2” が付いたものがあります。これらは主に Office 2013 以降に登場しており、旧来のオブジェクトと区別し互換性を維持するためだそうです。

上図ではフォントに関連するオブジェクトとして Font と Font2 が登場します。”2” のほうでは、光彩を設定する Glow や色を詳細に設定する Fill プロパティが存在し、機能が充実していることがわかります。

LotusScript から操作する場合 Excel のようにオブジェクトブラウザが利用できず、オブジェクト名称を確認できません。また、Microsoft Learn の記載ではこのあたりがあいまいな場合があり、混乱することがあります。オブジェクト名の確認が必要な場合には、プロパティやメソッドにアクセスし、存在チェックをすることで判別可能です。面倒ですが...


前回 Notes - Excel 連携


2024/12/01

@PickList の使い方

今回は @PickList についてまとめます。この関数を利用するとビューを使った選択画面が作成できます。カテゴリや列のタイトル、色遣いなどビューのデザインがそのまま選択画面として利用できますので、見た目がよく、操作性が高い選択画面が構築できます。


機能の違い

@PickList は、@DbColmun、@DbLookup に並んでよく使用する関数です。どの関数も主にビューを利用した機能なのですが、それぞれの特徴を理解して使い分けましょう。

@DbColmun ビューから指定した列の値を取得します。
@DbLookup ビューを検索し、合致した文書の値を取得します。
値は、ビューの列、または、フィールド値を取得。
@PickList ポップアップ画面にビューを表示し、ユーザに選択させます。

@DbColmun、@DbLookup はビューから値を取得するだけで、ユーザの操作を伴いません。ダイアログリストのフィールドでは選択画面としてが利用しますが、@関数の機能で表示しているのではなく、フィールドの機能が選択画面を表示しています。しかし、@PickList は UI にビューを利用した選択画面を表示する機能を提供することが特徴です。


なお、@DbColmun、@DbLookup については過去の記事でまとめていますので参照ください。

また、今回の内容は上記の記事で作成したフォームやビューと文書を利用しますのでご了承ください。


@PickList の仕様

デザイナーヘルプによると @PickList の構文は次のように記載されています。

@PickList([CUSTOM]:[SINGLE]; server:file; view; title; prompt; column; categoryname) 

※ 最初の引数には、[CUSTOM]、[SINGLE] 以外の機能がありますが、今回は割愛

引数の指定方法は次の通りです。

1 [CUSTOM] キーワード ビューを表示する場合
[SINGLE] 単一選択とする場合
2 server 文字列 サーバとファイル名を指定
"" (Null 文字列) で自分自身の DB
file
3 view 文字列 表示するビューの名称
4 title 文字列 ポップアップ画面のウィンドウタイトル
5 prompt 文字列 ポップアップ画面のメッセージ
6 column 数値 値を取得する列番号
7 categoryname 文字列 省略可能
指定したカテゴリだけをビューに表示


フォームの設計

@PickList を使用した機能の動作イメージです。ボタンをクリックすると選択画面が表示され、課を選択すると、部と課を一括設定します。

ボタンに記述した式は次の通りです。

xReturn := @PickList([Custom]:[Single]; ""; "vPicFunc"; "課"; "課を選択してください。"; 3);

@SetField("Dept"; @Left(xReturn; "/"));
@SetField("Func"; @Right(xReturn; "/"))


@PickList の動作

まず、選択画面を表示するまでの動作を整理します。

3 番目の引数で指定したビューがポップアップ画面にそのまま表示されています。そして、ウィンドウタイトルとメッセージは引数の指定通りとなっていることがわかります。


続いて、文書を選択して[OK]ボタンをクリックした後の動作です。ポップアップ画面のビューで選択した文書の引数で指定した列の値を返すという動作になります。

この結果、”情報システム部/サポート課” という文字列が取得できますので、"/" の左を部、右を課として、各フィールドにセットしているということですね。


まとめ

今回は @PickList の動作についてまとめました。ビューのデザインがそのまま選択画面として表示されることがポイントです。

例えば、顧客マスタを選択する場合を考えてみましょう。

列には会社名のほかに電話番号、住所、業種などの複数の項目が必要です。カテゴリは、都道府県別や 50 音別、業種別などが考えられ、複数階層になることもあります。

こういった画面ではユーザの要望により、運用途中で項目の追加が必要になることがあります。単純に作るとビューの列追加と @PickList の修正が必要となります。この対策として、@PickList 用のビューには、空のダミー列をあらかじめ用意しておくといいでしょう。軽微なデザインの変更であれば、ビューの変更だけで解決できます。


2024/11/28

つないでみよう:#24)GPT4o Structured Outputs - 活用事例 ②

今回も Structured Outputs を使用した事例の紹介です。

前回に続いて『ノーツ・しこく・フェスタ 2024:AIとNotes/Domino 禁断のコラボ ~ 生成 AI 実装事例集』で紹介したサンプルで、『AI が導く、次世代の学習体験 - AI Learning』です。


サンプルアプリと Structured Outputs の役割

利用者が学習のテーマと目標を入力し、学習をスタートさせます。すると、AI がテーマに沿ったコンテンツを生成するアプリで、e-Learning コンテンツ生成の非効率改善にチャレンジするサンプルです。

生成されるコンテンツは次のような構成でした。

まず、全体のタイトルとコンテンツに分かれます。コンテンツは複数のセクションが存在し、それぞれのセクションはタイトルと本文で構成されます。そして、本文内にはキーワードがリンクで表されています。


Structured Outputs の設定

上記のような構造を要求する Structured Outputs として、次のように定義しました。

AI Learning)リクエスト(response_format のみ)
  "response_format": {
    "type": "json_schema",
    "json_schema": {
      "name": "document",
      "strict": true,
      "schema": {
        "type": "object",
        "properties": {
          "capter": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "title": {"type": "string"},
                "contents": {
                  "type": "array", "items": {"type": "string"},
                  “description”: “1段落分の文章を出力します。次の段落がある場合次の配列要素に出力します。"
                }
              },
              "required": ["title", "contents"],
              "additionalProperties": false
            }
          },
          "keywords": {
            "type": "array", "items": {"type": "string"},
            “description”: “keywords には contents 内に存在するキーワードを列挙します。"
          },
          "subject": {
            "type": "string",
            “description”: “subject には capter 内を要約して20文字以内の簡潔なタイトルをセットします。"
          }
        },
        "required": [
          "capter",
          "keywords",
          "subject"
        ],
        "additionalProperties": false
      }
    }
  }

トップレベルのノードとして capter、keywords、subject の 3 つを定義しており、capter ノードに title、contents のサブノードが存在する階層構造になっています。

図式化すると、次のようになります。

capter ノードの型(type)は object となっており、それが配列となるようにしています。セクションとタイトルのセットが AI が必要と考える回数分繰り返される(= 配列)ようにしています。


レスポンス

上述のリクエストの結果は次の通りでした(コンテンツ部分は一部省略)。

AI Learning)レスポンス
{
  "capter": [
    {
      "title": "ピザの作り方",
      "contents": [
        "ピザの作り方は以下のステップで行います。…"
      ]
    },
    {
      "title": "飲食店でのコスト構造",
      "contents": [
        "飲食店のコスト構造は主に以下のように分類されます。…"
      ]
    },
    {
      "title": "経営上の課題",
      "contents": [
        "飲食業を成功させるために直面する経営上の課題は多岐に渡り…"
      ]
    }
  ],
  "keywords": [
    "ピザ",
    "飲食店",
    "コスト構造",
    "経営課題",
    "HCL Domino"
  ],
  "subject": "ピザ屋運営の基礎知識"
}

少し複雑な JSON となっていますが、リクエストとレスポンスを対比するとよくわかります。capter ノード配下に title と contents のセット(= オブジェクト)が 3 つ分、配列で出力されているということですね。


まとめ

今回は、オブジェクトを配列として返し、階層構造となる JSON を返す Structured Outputs の例を紹介しました。応用次第で AI と複雑で密な連携ができそうです。


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


2024/11/26

つないでみよう:#23)GPT4o Structured Outputs - 活用事例 ①

前回までは、 Structured Outputs の仕組みついて整理しました。今回はこの機能を利用したサンプルアプリを使って、より具体的に紹介します。


サンプルアプリと Structured Outputs の役割

活用事例 ① は『ノーツ・しこく・フェスタ 2024:AIとNotes/Domino 禁断のコラボ ~ 生成 AI 実装事例集』の1つ目に挙げた『1. AI じゃダメなんですか? - AI 文書仕分』です。アプリの機能の詳細はリンク記事を確認いただくとして、このアプリでどのように Structured Outputs を活用したかまとめます。

アプリの機能は、タイトルと本文で構成されるブログ記事を AI に食べさせて、タイトル、カテゴリ 3 種(ドキュメントタイプ、テーマ/トピック、キーワード)、要約を生成させます。記事を書いた人物がこれらを記入すると、その人の知識や経験、好みや文章力で統一感がなくなるという問題を AI を使って平準化させることを狙っています。

よって、AI からの返答はタイトルやカテゴリなど、複数の項目を確実に取得したいため、Structured Outputs を使用したということですね。


Structured Outputs の設定とレスポンス

このアプリでの Structured Outputs の設定を紹介します。

定義した返信の項目(ノード)は 5 つです(赤字)。それぞれの項目に description を設定し、こちらの希望を伝えています。

AI 文書仕分)リクエスト(response_format のみ)
  "response_format": {
    "type": "json_schema",
    "json_schema": {
      "name": "docshiwake",
      "strict": true,
      "schema": {
        "type": "object",
        "properties": {
          "doctitle": {
            "type": "string",
            "description": "この文書にタイトルをつけてください。"
          },
          "doctypes": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "ドキュメントタイプを3つまで答えてください。"
          },
          "docthemes": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "テーマ/トピックを3つまで答えてください。"
          },
          "keywords": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "キーワードを列挙してください。"
          },
          "summary": {
            "type": "string",
            "description": "要約"
          }
        },
        "required": [
          "doctypes",
          "docthemes",
          "keywords",
          "summary",
          "doctitle"
        ],
        "additionalProperties": false
      }
    }
  }

レスポンスの例は、次の通りです。指定したノード名で出力されています。

AI 文書仕分)レスポンス
  {
    "doctitle": "HCL Domino ライセンス簡素化に関する発表",
    "doctypes": [
      "通知/連絡",
      "製品情報"
    ],
    "docthemes": [
      "ライセンス変更",
      "製品終了",
      "顧客サポート"
    ],
    "keywords": [
      "HCL Domino",
      "ライセンス",
      "EOM",
      "EOS",
      "CCB",
      "メッセージング"
    ],
    "summary": "この記事は、HCL Dominoの既存の … 省略…"
  }


リスト値の生成と取得

今回のサンプルでは、ドキュメントタイプとテーマ/トピックは3つまで、キーワードは列挙するように指示しています。複数のカテゴリとそれぞれがリスト値(複数値)となるのでさまざまな切り口で文書が探せます。これはノーツの得意技ですね。

このリスト値を返す方法が、Structured Outputs の array というデータ型です。例えば、ドキュメントタイプでは次のように指定していました。

          "doctypes": {
            "type": "array",
            "items": {
              "type": "string"
            }
,
            "description": "ドキュメントタイプを3つまで答えてください。"
          },

型が array となる場合、配列の詳細を指定する items というオブジェクトノードが必要となります(青字)。オブジェクトの中身は配列の型を指定します。今回の例では string となっています。数値型の配列としたい場合はここが number となるということですね。


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


2024/11/23

つないでみよう:#22)GPT4o Structured Outputs - 指定方法

前回から紹介している GPT-4o で利用できる Structured Outputs の続きです。今回は Structured Outputs の指定方法について理解できた範囲でまとめます。


Structured Outputs vs JSON Mode

API のドキュメントには Structured Outputs のセクションが存在します。

Structured Outputs - Ensure responses follow JSON Schema for Structured Outputs.

この中に JSON Mode との比較があります。

どちらの機能も出力が JSON にはなりますが、スキーマに準拠するのは Structured Outputs だけとなります。JSON Mode では JSON のスキーマは AI が生成するということですね。

対応モデルは Structured Outputs が GTP-4o 以降の対応になるのですが、JSON Mode は GPT-3.5 でも利用できるのがポイントですね。


スキーマ設定

前回例に挙げた Structured Outputs のスキーマ設定部分を分解すると次のようになります。


スキーマの名前 名称は自由に設定できるようです。
出力される JSON に影響しないようです。
スキーマ定義 このオブジェクト配下に出力したいスキーマを定義します。
仕様に従い記述すれば自由に定義できます。   
データ型 作成するノードのデータ型です(後述)。
出力ノード名称 作成するノードの名称です。
出力される JSON のノード名に利用されます。
ノード毎の出力指示 そのノードにどのような値を含めるのか AI に指示します。
項目ごとに指定できるので便利ですね。
必須項目 データ型がオブジェクトの場合に指定します。
出力が必須であるノードを指定する機能のようですが、現在のところ全ノードを指定する必要があるようです。


利用できるデータ型

ノードに指定できる型は以下のような種類があります。

string 文字列
number 数値
boolean True / False
integer 整数
object オブジェクト
array 配列
enum 選択肢のうちどれか?
anyOf ???

object を指定すると JSON を階層化できます。array を利用するとノーツで言う複数値を返答させることができます。これらを利用すると複雑な構造の JSON を定義できますね。

なお、enum と anyOf についてはまだ検証していないので詳細は不明です...。


制限事項

API のドキュメントを読み進めるといろいろと制限事項があるようです。ざっくりまとめると次のような項目です。

  • スキーマ内のキーの順序で生成される
  • 最大5レベルのネスト
  • 100 個のオブジェクト
  • スキーマにないキー/値は追加できない
  • 必須には全てのオブジェクトを指定

AI の進化速度を考えるとこれら制限事項はどんどん改善されていくのだと思います。日々チェックが必要ですね。


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


2024/11/22

つないでみよう:#21)GPT4o Structured Outputs とは?

本日 2024 年 11 月 22 日は ノーツコンソーシアム 大阪地区の研究会の開催日です。この研究会で発表するプレゼンテーションをダイジェスト版でお伝えいたします。


2024 年 10 月 1 日の GPT4o のモデルが更新されるとのアナウンスの中に『Structured Outputs がサポートされる』という情報がありました。早速試してみたので、体験レポートとしてまとめます。


Structured Outputs とは?

案内メールのリンク『Introducing Structured Outputs in the API』はこちらでした。ざっくりまとめると、次の通りです。

  • 出力の構造をスキーマとして定義
  • スキーマ通りの出力で強制する機能


アプリ開発とレスポンス制御の種類

まずは、GPT4o の API 連係アプリの一般的な作成方法です。手順は、問い合わせ内容をリクエストの JSON にセットし、API をコールします。そして、API のレスポンスも JSON 形式で返されるという仕組みでした(細かな手順はこの連載の #1#2 をご確認ください)。このレスポンスの JSON から AI の返答部分を取得して後続の処理で利用すれは、連携アプリが構築できるということですね。

GPT4o にはこのレスポンスを制御する機能があります。

  1. 通常の問い合わせ(制御なし)
  2. JSON Mode を指定
  3. Structured Outputs の利用

Structured Outputs 実践に入る前に、これら機能の必要性について確認しましょう。


1. 通常の問い合わせ

AI にメールを作成させる処理を事例に記載します。

まずは、通常の問い合わせのパターンでは次のようなリクエストとレスポンスになります。

1. 通常)リクエスト
{
  "model": "gpt-4o-2024-08-06",
  "messages": [
    {
      "role": "system",
      "content": ""
    },
    {
      "role": "user",
      "content": [
        {
          "type": "text",
          "text": "kintone ユーザにHCL Notesの有効性を説明して、購入を促すメールを作成してください。"
        }
      ]
    }
  ]
}

1. 通常)レスポンス
{
  "id": "chatcmpl-ARVrw9GojS5cAhUHswYkLD8VXY6Tl",
  "object": "chat.completion",
  "created": 1731120724,
  "model": "gpt-4o-2024-08-06",
  "choices": [
    {
      "index": 0,
      "message": {
        "role": "assistant",
        "content": "件名: HCL Notesで業務効率化 … 省略 … \n[あなたの連絡先詳細]",
        "refusal": null
      },
      "logprobs": null,
      "finish_reason": "stop"
    }
  ],
  "usage": {
    "prompt_tokens": 40,
    "completion_tokens": 553,
    "total_tokens": 593,
    "prompt_tokens_details": {
      "cached_tokens": 0,
      "audio_tokens": 0
    },
    "completion_tokens_details": {
      "reasoning_tokens": 0,
      "audio_tokens": 0,
      "accepted_prediction_tokens": 0,
      "rejected_prediction_tokens": 0
    }
  },
  "system_fingerprint": "fp_45cf54deae"
}

赤字の部分がリクエストに対する AI の返答の本体です。ここを取得して、アプリケーションで利用しることになります。この構造は、後述の JSON Mode や Structured Outputs でも同じとなります。

返答の本体部分だけを抽出すると次のようになります。

件名: HCL Notesで業務効率化を実現しましょう!

[お客様名] 様

平素より大変お世話になっております。[あなたの会社名]の[あなたの名前]です。

この度は、業務効率化を実現するための最適なソリューション、HCL Notesをご紹介させていただきます。HCL Notesは、ビジネスコミュニケーションに革新をもたらす総合的なプラットフォームで、多くの企業様にご活用いただいております。

### HCL Notesの特長

1. **シームレスなコラボレーション**
- メール、カレンダー、インスタントメッセージングが一体化され、チームメンバーとの連携がスムーズに行えます。

2. **高いセキュリティ**
- データの暗号化とアクセス管理により ・・・ 後略

人間が見れば件名と本文がどこか判定できます。ですが、プログラムでの処理を考えると少し厄介です。1 行目は必ず件名なのか? ”件名:” で始まるのか? 毎回そのフォーマットで返答してくれるのか? わからないからです。

この問題を回避するための手段がレスポンス制御です。


2. JSON Mode を指定

この手法からはレスポンスを構造化したテキストデータ(JSON)で取得することを目的とした機能です。リクエスト時に返答は JSON で返してとお願いすることに加え、返答は JSON でと指定する方法で JSON Mode と呼ぶようです。この手法は『#19)GPT4o は OCR? - 返答を JSON で返す方法』で紹介しています。

具体的には下記の通りです。response_format を指定することがポイントでした。

2. JSON Mode)リクエスト
{
  "model": "gpt-4o-2024-08-06",
  "messages": [
    {
      "role": "system",
      "content": ""
    },
    {
      "role": "user",
      "content": [
        {
          "type": "text",
          "text": "kintone ユーザにHCL Notesの有効性を説明して、購入を促すメールを作成してください。\r\n返答は、”件名”と”本文” のJSON 形式としてください。"
        }
      ]
    }
  ],
  "response_format": {
    "type": "json_object"
  }

}

これで、結果の content は JSON 形式の文字列になります。その部分を整形すると次のようになります。

{
  "件名": "HCL Notesの導入で業務効率を大幅に向上させましょう!",
  "本文": "お世話になっております。日頃よりkintoneをご利用いただき、誠にありがとうございます。今回は、業務効率のさらなる向上を図るため、HCL Notesをご紹介させていただきます。HCL Notesは、企業のコミュニケーションを変革するととも ・・・ 後略

ここで問題となるのが ”件名” や "本文" のノード名です。この名称はリクエストで指示しているとは言え、その文言で必ず生成される保証はありません。また、JSON のフォーマットについてはどこにも記載されていません。AI の判断にゆだねられているということですね。


3. Structured Outputs の利用

いよいよ本題の Structured Outputs の指定です。

これまで事例にしていたメールの作成を指示するリクエストを Structured Outputs で表現すると次のようになります。response_format の type に json_schema を指定し、以降に出力として返してほしい JSON の構造(スキーマ)を記述します。

3. Structured Outputs)リクエスト
{
  "model": "gpt-4o-2024-08-06",
  "messages": [
    {
      "role": "user",
      "content": [
        {
          "type": "text",
          "text": "kintone ユーザにHCL Notesの有効性を説明して、購入を促すメールを作成してください。"
        }
      ]
    }
  ],
  "response_format": {
    "type": "json_schema",
    "json_schema": {
      "name": "recommendnotes",
      "strict": true,
      "schema": {
        "type": "object",
        "properties": {
          "
subject": {
            "type": "string",
            "description": "メールの件名"
          },
          "
body": {
            "type": "string",
            "description": "メールの本文"
          }
        },
        "required": [
          "subject",
          "body"
        ],
        "additionalProperties": false
      }
    }
  }
}

結果の JSON を整形すると次のようになります。

{
  "subject": "ビジネス効率化への新たな一歩:HCL Notes導入のご提案",
  "body": "拝啓 [顧客名] 様平素よりお世話になっております。私たちは、お客様のビジネスがさらなる成長を遂げるために、現代のデジタルツールを駆使して業務効率とコミュニケーションの質を向上させるソリューションをご提供したいと考えております。本日はその一環として、HCL Notesをご紹介する機会をいただきたくご連絡 ・・・ 後略

スキーマで定義したノード名で JSON が出力されていることがわかります。これで後続のプログラムで、例外処理などを省くことができ連携アプリが容易に構築できるようになりますね。


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


2024/11/21

Notes - Excel 連携:#50)グラフ調整の仕上げ

#41)グラフの調整』から開始した ”使える” グラフにするための調整作業のシリーズです。今回はいよいよ最後の仕上げ作業です。

今回設定したタイトルや軸ラベルの配置では、文字がグラフと重なって表示される可能性があります。そのような状況になっても文字が読み取りやすくなるよう、文字に縁取りを設定します。下図のタイトル(紫枠部分)を確認してください。上が対策を行った状態です。文字の周りの罫線が消えており文字が読み取りやすくなっています(かなり細かい話ですが...)。

この設定は Excel の ”光彩” という機能で実現します。


光彩の操作

光彩の設定は Font2.Glow プロパティから取得できる GlowFormat オブジェクトを使用して行います。今回は、このオブジェクトの 3 つのプロパティを使用して設定します。

Color 光彩(縁取り)の色
Transparency 縁取りの透明度を 0 ~ 1 の実数で指定(0:不透明)
Radius 光彩(縁取り)の大きさを半径で指定(ポイント)


光彩の設定

光彩の設定を行うオブジェクトは、タイトル、サブタイトル、Y 軸ラベルです。これらのテキストボックスを作成するタイミング、xCreateTextBox 関数で設定を実施します。

Function xCreateTextBox(voShape As Variant) As Variant
   Dim oText As Variant

   Set oText = voShape.Chart.Shapes.AddLabel(msoTextOrientationHorizontal, 0, 0, 1, 1)
   With oText.TextFrame2
      '垂直方向に中央揃え
      .VerticalAnchor = msoAnchorMiddle
      '色の設定
      .TextRange.Font.Fill.ForeColor.RGB = RGB(32, 32, 32)
   End With

   '光彩の設定
   With oText.TextFrame2.TextRange.Font.Glow
      .Color.RGB = RGB(255, 255, 255)
      .Transparency = 0
      .Radius = 18
   End With


   Set xCreateTextBox = oText
End Function

光彩の色は白で、透明度は 0(不透明)に設定しています。光彩は文字から離れるに従い透過するので、不透明にしておくとしっかり縁取りできます。

光彩のサイズ Radius は、フォントサイズにかかわらず 18 ポイントで統一しています。


画像化とリッチテキストでの活用

完成したグラフは『#37)名前アイコン生成 ①』で紹介した xSaveAsPicture 関数を利用すると画像として保存できます。

画像ファイルは DXL を利用するとリッチテキストに見える状態(インラインイメージ)で貼りつけることができます(詳しくは別の連載『DXL Step-by-Step』の『#41)インラインイメージの貼り付け』をご確認ください)。


これらの機能を組み合わせると、これまでノーツでは諦めていた視覚に訴えかけるレポートの作成が可能となります。ノーツの表現力が一気に増しますね。


前回 Notes - Excel 連携 次回


2024/11/20

Notes - Excel 連携:#49)凡例の配置

#41)グラフの調整』から開始した ”使える” グラフにするための調整作業のシリーズです。今回は凡例の操作を行います。

前回までで習得した座標系の知識を利用して、凡例の配置を行います。設定の仕様は次の通りです。

  • フォントサイズは基準のフォントサイズとする
  • 幅はプロットエリア右側の空白に合わせる
  • 高さはサブタイトルと同じ(基準フォントサイズ × 1.5)
  • プロットエリアと凡例の底辺をそろえる


凡例の操作

VBA で凡例を操作するには Chart オブジェクトLegend プロパティ を使用して Legend オブジェクト を取得します。

また、グラフ内に凡例があるか判定するには HasLegend プロパティ を使用します。

Legend オブジェクトには、配置を指定する TopLeft、サイズを表す HeightWidth プロパティが存在します。Format プロパティで ChartFormat オブジェクト が取得でき、このオブジェクトに TextFrame2 プロパティがあります。TextFrame2 オブジェクトが取得できれば、テキストボックスと同様の操作でフォントの指定ができますね。


凡例を配置する関数

前回までオブジェクトの配置の方法や座標系について紹介しました。そして、凡例のオブジェクトと操作は上記の通りです。これらを活用して、どのようなコードを書くべきか想像してみましょう。

いかがですか?

仕様通りに凡例を配置する関数 xSetLegend を紹介します。

Function xSetLegend(voShape As Variant)
   Dim oLegend As Variant
   Dim oPlot As Variant

   If voShape.Chart.HasLegend = True Then
      '凡例が存在
      Set oLegend = voShape.Chart.Legend '凡例を取得
      Set oPlot = voShape.Chart.PlotArea 'プロットエリアを取得

      'フォントサイズの設定
      oLegend.Format.TextFrame2.TextRange.Font.Size = xcdFontSize

      '調整前にいったん左上に移動
      oLegend.Left = 0
      oLegend.Top = 0

      '位置とサイズの指定
      oLegend.Left = oPlot.InsideLeft + oPlot.InsideWidth
      oLegend.Width = voShape.Width - oLegend.Left - 4
      oLegend.Height = xcdFontSize * 1.5
      oLegend.Top = oPlot.InsideTop + oPlot.InsideHeight - oLegend.Height
   End If
End Function

凡例の配置を調整する前に位置を左上に移動しています。これは、凡例の現在位置にかかわらず希望のサイズを確実にセットするためです。以前お伝えした通り、グラフのエリアからはみ出ると自動で調整され、希望通り配置されないことがあります。これを抑制するためです。こうしておけば、設定の順序をいちいち意識する必要はないですね(回転はしない前提)。

位置とサイズの指定について補足します。

まず、Left はプロットエリアの右端と同じ位置ですので、プロットエリアの開始位置(InsideLeft)と幅(InsideWidth) を足しているだけです。

凡例の幅は、グラフエリアの幅(voShape.Width)からプロットエリアの幅(=凡例の Left)を引いています。これだけでは左マージンの 4 ポイントが考慮できていないので、それを差し引いています。

凡例の高さ(Top)は、プロットエリアの高さ(oPlot.InsideTop + oPlot.InsideHeight)から凡例エリアの高さを差し引いて決定しています。


まとめ

メインルーチンで、作成した関数をコールすれば今回の作業は完了です。

Sub Initialize
         ・・・
   'Y軸ラベルの生成
   Call xSetLabelY(oShape, "ユーザ数")

   '凡例の設定
   Call xSetLegend(oShape)

   oXls.Visible = True
End Sub

修正後に作成されるグラフは次のようになります。なかなかいい感じになりましたね。

次回は最後の仕上げを行い、グラフを完成させます。


前回 Notes - Excel 連携 次回


2024/11/19

ビューの検索で全角半角が区別できない !?

先日、ノーツアプリでトラブルに遭遇しました。現象がつかめたのでレポートします。なお今回の調査結果はメーカサポートに連絡・確認していません。私の検証結果だけをまとめてております。あくまで私の主観に基づくレポートですので、ご了承ください。


背景のシステム

ノーツとは別の人事システムから部門マスタと社員マスタを連携しており、これら情報を使用してノーツのグループを自動生成しています。連携するデータはその日の全件で、ノーツ内にあるデータ(前日のマスタ)と比較しながら、差分を算出し、新規/変更/削除の処理をしています。

連係インターフェース仕様としては部門名は ”全角” 文字と決まっていたのですが、誤って半角で入力してしまったそうです(パッケージに仕様上禁止できなかった模様)。半角部門名の存在に気づき連絡、全角文字に修正したタイミングで問題が発生しました。


トラブルの内容

発生した問題は、グループ名とメンバーで全角/半角が不一致となったのです。

例えば下表のような感じです。グループ名で使用している部門名(赤字)は半角文字のままで、メンバーとして使用している部門名(青字)は全角となっていました。

グループ名 メンバー
営業部 営業部/営業
営業部/営業
営業部/営業1 A さん
B さん

要は、階層構造が正しくなく、グループとして正しく機能しなくなりました。結果、アクセス障害が発生し大トラブルになったというものでした。


エラーの原因

グループの更新処理において、グループ名は GetDocumentByKey で既存グループを検索していました。この時、全角で検索したにも関わらず、半角のグループがヒットし、半角のグループが削除されず、全角にならずメンバーだけ更新されたため発生していました。

今回はこの問題を単純なプログラムを作成して、検証します。

なお、グループのメンバーに関しては文字列の比較で判定しており、こちらは正常に判断されていたことを補足いたします。


検証プログラム

まず、フィールドが 1 つだけの単純なフォームを作成します。

フォームができたら、プリビューでテストデータを 1 件作成し、”営業部/営業1課” と入力します(数字は半角)。

次にこの項目を表示する 1 列だけのビューを作成します。検索で使用するのでソートを設定しておきます。

最後にテストエージェントを作成します。テストデータを検索するのですが、検索前に StrConv を使用して全角文字列に変換しています。正しく動作すると検索にはヒットしないという算段です。

検索した結果はメッセージで表示します。ヒットした場合は、検索文字列とヒットした文書内のフィールド値を表示しています。

Option Declare

Sub Initialize
   Dim ns As New NotesSession
   Dim ndb As NotesDatabase
   Dim nv As NotesView
   Dim nd As NotesDocument
   Dim sKey As String

   sKey = "営業部/営業1課"
   sKey = StrConv(sKey, 4)

   Set ndb = ns.CurrentDatabase
   Set nv = ndb.Getview("GetDocumentByKey")

   Set nd = nv.GetDocumentByKey(sKey, True)

   If nd Is Nothing Then
      MsgBox "ヒットしませんでした。"
   Else
      MsgBox sKey & Chr(10) & nd.Keyword(0)
   End If
End Sub


検証結果

手持ちの環境を利用して検証します。サーバは Domino 11 と Domino 14 があったので利用します(クライアントは Notes 11)。その結果、Domino 11 ではヒットし、Domino 14 ではヒットしませんでした。

Domino 11 Domino 14

LotusScript に関連するバージョン依存の障害では、一般にクライアントのバージョンに依存することが多いのですが、今回はサーバのバージョンにより現象が違います。サーバ内のビュー索引に起因するからなのでしょうか?

参考までに、この検証プログラムを Notes 11 と Notes 14 のローカル環境でもテストしてみました。結果はヒットしました(Domino 11 と同じ症状)。同じバージョンでもサーバ実行とクライアント実行で症状が違うことがあるんですね。驚きました。

Domino 14 にすれば問題が起こらないようなので、バージョンアップしましょうというメッセージでこの話は収束できそうです。Domino 12 については検証できていませんが、新しいバージョンの方がいいと言えそうです。


今回は、ノーツのグループを事例にしてご紹介しました。グループに限らず発生しうる問題なので紹介させていただきましたが、『日本語のグループ名はサポート外と再認識した話』で記載しましたが、2バイト文字を使用したグループはそもそもサポート外です。ご注意ください。