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バイト文字を使用したグループはそもそもサポート外です。ご注意ください。


2024/11/15

Notes - Excel 連携:#48)オブジェクトの回転と配置

#41)グラフの調整』から開始した ”使える” グラフにするための調整作業です。今回は Y 軸ラベルの設定を行います。

Y 軸ラベルは通常のテキストボックスを 90° 左に回転させ PlotArea の右端に配置します。オブジェクトのサイズは、プロットエリアの Y 軸に合わせ、文字を中央揃えにします。


オブジェクトの回転

テキストボックスをはじめ Shape オブジェクトには Rotation というプロパティがあり、オブジェクトの回転を表します。正の値を設定すると右(時計)回り、負の値で左回りに回転します。

まずはこのプロパティをテストします。100 × 15 ポイントのテキストボックスを座標 (0, 0) に作成し左に 90° 回転してみます。

   Set oText = xCreateTextBox(voShape)
   '配置の設定
   oText.Left = 0
   oText.Top = 0
   oText.Width = 100
   oText.Height = 15

   '左に 90° 回転
   oText.Rotation = -90

結果を確認すると回転処理はできていたのですが、オブジェクトサイズが小さくなったような気がします。そこで、サイズをメッセージボックスで表示させてみました。すると、幅が 61.5 と小さくなっているうえ、座標(Left と Top)が 19.25 と動いています。

まず、回転はオブジェクトの中心の座標を中心に実行されます。今回で言うと (50, 7.5) となります。左に 90° 回転するとオブジェクトがプロットエリアからはみ出ます(紫部分)。この部分が Excel の機能で自動的に調整され、残った部分が 61.5 ということですね。

オブジェクトの座標(Left と Top)はというと、調整されたオブジェクトの回転をもとに戻すとわかります。オブジェクトの長さは 61.5 ポイントだったので中心はその半分、そして、上部に 4 ポイントのマージンがあるので、中心座標は (50, 26.75) となります。回転を戻したオブジェクトの左上が Left と Top になり、Left は幅の半分 30.75、Top は高さの半分 7.5 を中心座標から差し引くと (19.25, 19.25) となります。

このように、はみ出た分の自動調整により中心位置がずれ、それに引きずられてオブジェクトの配置(Left と Top)が動いたということになります。


回転操作の注意点

かなり詳細な説明をしましたが、ここまで座標系や挙動を理解しておくと、今後様々なシーンで迷うことなく操作できるはずです。

回転操作の注意点をまとめると次の通りです。

  • 回転はオブジェクトの中心が起点
  • 回転した結果、はみ出た分は自動調整される
  • 自動調整が発生すると中心位置が変わり、オブジェクトの座標も変わる
  • オブジェクトの位置(Left、Top)やサイズ(Width、Height)は回転しない状態の値

Y 軸ラベルの作成と配置

オブジェクトの回転が理解できたので、グラフに Y 軸ラベルを配置する関数を作成します。

Function xSetLabelY(voShape As Variant, ByVal vsLabel As String)
   Dim oText As Variant
   Dim dW As Double
   Dim dH As Double
   Dim d As Double

   'Y 軸用テキストボックス
   Set oText = xCreateTextBox(voShape)

   With oText.TextFrame2
      'フォーマットの設定
      .TextRange.Font.Size = xcdFontSize 'フォントサイズ
      .TextRange.ParagraphFormat.Alignment = msoAlignCenter '水平方向に中央揃え
      'テキスト
      .TextRange.Text = vsLabel
   End With

   'サイズのセット
   dW = voShape.Chart.PlotArea.InsideHeight 'プロットエリアの高さ
   dH = xcdFontSize * 1.5 'フォントサイズの1.5倍
   oText.Width = dW
   oText.Height = dH

   '回転してもPlotAreaからはみ出ないよう位置を調整
   d = dW / 2
   d = d - dH / 2
'オブジェクトの高さの半分 上へ
   d = d + voShape.Chart.PlotArea.InsideTop '上のマージン分下げる
   oText.Top = d

   '回転
   oText.Rotation = -90

   '左端に移動
   d = dW / 2 - dH / 2 '左への移動量
   d = d + 4 'マージンも詰める
   Call oText.IncrementLeft(-d)
End Function

この関数のポイントは 2 点です。

まず、Y 軸ラベルの幅は、プロットエリアの高さに合わせています。プロットエリアにそろえて配置して、文字を中央揃えするだけなので、計算が比較的簡単となります。

もう一点は、回転後 PlotArea からはみ出ないよう、事前にに位置を調整している点です。また、この操作と同時に、回転後の高さがプロットエリアにぴったり合うよう調整しています。プロットエリアの高さ(= テキストラベルの幅)とマージン(InsideTop)を利用、回転の中心は Top 座標よりオブジェクトの高さ(Height)の半分下にずれることも考慮してオブジェクトを配置しています。

回転後、オブジェクトを左端に寄せています。移動量は、オブジェクトの幅や高さ、マージンの 4 ポイントを使って厳密に計算しています。Excel の自動調整を活用するのであれば、厳密な計算をせず、大きめの値をセットするだけでもかまいません(意図が見えなくなるので推奨しませんが...)。


最後にメインルーチンから作成した関数をコールすれば Y 軸ラベルの作業は完了です。

Sub Initialize
         ・・・
   'タイトルエリアの生成
   Call xSetTitle(oShape, "ユーザ数の推移", "Server01/Domino")

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

   oXls.Visible = True
End Sub


前回 Notes - Excel 連携 次回


2024/11/12

編集権限を持つ文書の編集を止める方法

先日、既存ノーツ DB のメンテナンスをしていてバグを発見しました。LotusScript を覚えたての方がよくやるミスだったので、ご紹介します。

問題の DB は『漢字アドレス帳』です。懐かしいですね(笑)

Notes 4.6 時代に Lotus 社が提供した First Step Kit 内に同梱されていた製品(?)の一部です。ノーツユーザを漢字表記するための DB です(Notes R5 以降は DJX が同等の機能を担当)。非常に短命だったのですが、当時ノーツを新規導入した企業が多かったことから爆発的に利用されました。もしかしたらお使いのサーバ内にも残骸があるかもしれませんね。


問題の機能

今回漢字アドレス帳の文書を修正しようと管理者権限のユーザで文書を編集しようとしました。すると、『この文書は編集できません』とエラーが表示されました。

漢字アドレス帳への登録は『申請データベース』経由で行う仕様だから、当然です。そこで、フォームの設計を確認すると次のようになっていました。

QueryModeChange のイベントは、文書のモードが変化(読込⇔編集)する前に発生します。上記コードでは EditMode が True でないとき(= 読込モード)エラーを表示しています。そして、引数の Continue に False を設定することで、モードの変更をキャンセルさせています。

なるほど、これで編集を抑制(= 編集権限があっても文書を編集させなく)しているんですね。今回『この文書は編集できません』とエラーが表示されたので、うまく機能しています。


バグとは?

さて、この機能、何が問題か気がつきましたか?


バグは QueryModeChange イベントではありません。QueryOpen イベントにコードがないことが問題なんです。例えば、ビューで文書を選択し、Ctrl + E を押して編集モードで文書を開くと、難なく編集できてしまいます。

これでは、編集を抑制したい機能は満足できていませんね。


対策

QueryOpen は文書を開く前に発生するイベントです。イベントの引数は 4 つです。


Source 開く文書
Mode 編集モードで開く場合 1
読込モードで開く場合 0
IsNewDoc 開く文書が新規文書の場合 True
Continue 文書を開かせない場合 False をセット


この引数を利用して、いきなり編集モードで開く操作に制限を掛けます。

Sub QueryOpen(Source As Notesuidocument, Mode As Integer, IsNewDoc As Variant, Continue As Variant)
   If IsNewDoc = False Then
      If Mode = 1 Then
         Msgbox "この文書は編集できません",16,"漢字メール"
         Continue = False
      End If
   End If
End Sub

Mode = 1 の場合、エラーメッセージを表示して、開く動作をキャンセルしています。

また、このフォームで文書を新規作成する機能があるかは知りませんが、万が一、新規作成をした場合に備えて、開くようにしています。


まとめ

今回はフォームの編集を抑制する機能について紹介しました。

編集を止めるのは QueryModeChange イベントだけでなく QueryOpen イベントにも配慮が必要というお話でした。LotusScript に不慣れであったり、急いで作るとついつい忘れがちです。

約 30 年前とはいえ、ノーツのメーカが提供した DB で発見しました。プロでもやっちゃうありがちなバグということですね。注意しましょう。


2024/11/07

Notes - Excel 連携:#47)グラフタイトルの作成と配置

前回はグラフオブジェクト内の座標系についてまとめました。仕様が明確になったので、いよいよタイトルを表示するコーディングを始めます。

#45)グラフタイトルとテキストボックスの違い』で記載したように、標準のグラフタイトルは使用せず、”テキスト ボックス” でタイトルとサブタイトルを作成します。


テキストボックスの配置

今回、テキストボックスの幅はグラフエリアいっぱいにしておきます。文字を右寄せにするのでできるだけ大きくしておけば、文字の折り返しなど、細かなことは気にしなくて済むという判断です。基準のフォントを 10 ポイントとし、目立たせたいタイトルだけ 1.5 倍のフォントサイズで太字とします。

テキストボックスの高さはフォントサイズの 1.5 倍とし、文字の垂直揃えを真ん中に設定します。

これらの設定を行う関数は次の通りとなります。関数の引数は、グラフ全体を表す Shape オブジェクトとタイトル / サブタイトルに設定する文字列です。

Function xSetTitle(voShape As Variant, ByVal vsTitle As String, ByVal vsSubTitle As String)
   Dim oText As Variant

   'タイトル
   Set oText = xCreateTextBox(voShape)
   'オブジェクトの配置
   oText.Width = voShape.Width
   oText.Height = xcdFontSize * 1.5 * 1.5 ' フォントサイズ×マージンの 1.5
   Call oText.IncrementTop(-4) '上マージンをなくす
   With oText.TextFrame2
      'フォーマットの設定
      .TextRange.Font.Size = xcdFontSize * 1.5 'フォントサイズ(標準の1.5)
      .TextRange.ParagraphFormat.Alignment = msoAlignRight '水平方向に右揃え
      'テキスト
      .TextRange.Text = vsTitle
   End With

   'サブタイトル
   Set oText = xCreateTextBox(voShape)
   'オブジェクトの配置
   oText.Width = voShape.Width
   oText.Top = xcdFontSize * 1.5 * 1.5  'タイトルの高さ分下に下げる
   oText.Height = xcdFontSize * 1.5
   With oText.TextFrame2
      'フォーマットの設定
      .TextRange.Font.Size = xcdFontSize 'フォントサイズ
      .TextRange.ParagraphFormat.Alignment = msoAlignRight '水平方向に右揃え
      'テキスト
      .TextRange.Text = vsSubTitle
   End With
End Function

テキストボックスを作成する処理はサブ関数  xCreateTextBox は #45 で紹介しています。

なお、上記プログラムで未定義の定数を 2 つ使用しています。1 つ目は文字のフォントサイズである  です。このエージェントの (Declarations) に記載します。

Private Const xcdFontSize = 10  'ベースフォントサイズ

もう一つは文字列の水平揃えに関する定数です。今回は右寄せだけを使用していますが、ついでに左寄せも記述しておきます。なお、こちらの記述は lsXls ライブラリへの追加となるので注意してください。

'MsoParagraphAlignment 列挙 (Office)
Public Const msoAlignLeft = 1  '左寄せ
Public Const msoAlignCenter = 2  '中央揃え
Public Const msoAlignRight = 3  '右寄せ


メインプログラムの修正

関数ができあがったら、メインルーチンからコールします。

Sub Initialize
         ・・・
   'プロット領域の設定
   oChart.PlotArea.InsideWidth = 350
   oChart.PlotArea.InsideLeft = 50
   oChart.PlotArea.InsideTop = 7
   oChart.PlotArea.InsideHeight = 170

   'タイトルエリアの生成
   Call xSetTitle(oShape, "ユーザ数の推移", "Server01/Domino")

   oXls.Visible = True
End Sub


実行結果とまとめ

実行すると次のようにタイトル、サブタイトルが表示されます。また、グラフエリアの上から順に隙間なく配置されています。


ところで、テキストボックスの横幅にはグラフエリアの幅をセットしています。

   oText.Width = voShape.Width

以前まとめたように Excel は範囲外の値をセットしてもエラーを出さずうまく調整してくれます。今回はこの機能を利用して、以下の記述を省略しています。

    Call oText.IncrementLeft(-4)


前回 Notes - Excel 連携 次回


2024/11/06

Notes - Excel 連携:#46)テキストボックスの配置

前回紹介したように、グラフ上にテキストボックスを追加すると、座標 (0, 0) で作成したにも関わらず、左と上に少し隙間が空いてしまいます。

Set oText = voShape.Chart.Shapes.AddLabel(msoTextOrientationHorizontal, 0, 0, 50, 10)

今後、より詳細にオブジェクトの位置決めをするときに混乱しないよう挙動を確認します。


隙間のサイズ

まず、Excel のマクロの記録しながらテキストボックスに左上に移動してみました。すると、次のスクリプトが出力されました。

Sub Macro1()
   Selection.ShapeRange.IncrementLeft -4
   Selection.ShapeRange.IncrementTop -4
End Sub

IncrementLeft は、図形を指定したポイント数だけ水平方向に移動するメソッドです。マイナスの値を指定すると左、プラスの値で右に移動します。IncrementTop も同様の機能で、こちらは垂直方向に移動するメソッドです。Left や Top プロパティで位置を絶対的に設定するだけでなく、現在位置から相対的に移動できるんですね。

この結果より、左と上の隙間の幅は 4 ポイントであることがわかりました。


左上に配置するには

隙間の大きさがわかったので、左上ぎりぎりに配置する方法を確認します。

まず、テキストボックス作成時にマイナスの座標をセットしてみます。結果は、変わらず隙間が空いた状態となりました。

・・・   .Shapes.AddLabel(msoTextOrientationHorizontal, -4, -4, 50, 10)


次は、Left と Top プロパティに負の値を入れてみます。実行はしているようですが、結果の位置は変わりませんでした。


では先ほどの Increment* メソッドをテストします。座標 (0, 0) で作成したオブジェクトをそれぞれ -4 してみます。これで無事、左隅に移動することができました。

   'タイトルエリアの生成
   Dim oText As Variant
   Set oText = xCreateTextBox(oShape)
   oText.TextFrame2.TextRange.Text = "Test"

   Call oText.IncrementLeft(-4)
   Call oText.IncrementTop(-4)


座標系

上記のように、右上に寄せた状態で、オブジェクトの位置を確認すると -4 となっています。

   Call oText.IncrementLeft(-4)
   Call oText.IncrementTop(-4)

   Dim s As String
   s = "Left = " & CStr(oText.Left)
   s = s & Chr(10) & "Top = " & CStr(oText.Top)
   MsgBox s

グラフオブジェクトの幅は 500 ポイントでした。試しに座標 (0, 0) で作成し、幅を 492 ポイントに設定してみます。

   'タイトルエリアの生成
   Dim oText As Variant
   Set oText = xCreateTextBox(oShape)
   oText.TextFrame2.TextRange.Text = "Test"

   ' Call oText.IncrementLeft(-4)
   ' Call oText.IncrementTop(-4)


   oText.Width = 492

結果は以下のように、左右の隙間が同じになりました。


上記の結果より、Shape オブジェクトとその上に配置するテキストオブジェクトの関係は次の通りとなります。


まとめ

今回は、グラフオブジェクトの座標系に関して調査しました。Excel としてはオブジェクトのマージンとして 4 ポイント分確保し、そこを座標の起点としていることがわかりました。

Notes - Excel 連携:#42)PlotArea とサイズ』の「位置合わせの謎」のセクションで、PlotArea を左から 50 ポイント(約 66 ピクセル)にしても、54 ポイント(72 ポイント)になる原因が不明だと書きました。原因がわかりましたね。今回の検証で理解した 4 ポイントのマージン(約 5 ピクセル)ということだったのです。

上記は PlotArea の操作で、今回の検証はテキストボックスでしたが、位置の指定は同じです。Shape オブジェクト内の座標系はこれで統一されているようですね。

オブジェクトの配置やサイズの操作のポイントは 2 つです。

まず、4 ポイント分のマージンは利用できないわけではなく、 Increment* メソッドを使えば利用できます。ただし、Left や Top プロパティで、直接的にマイナスの値は指定できないようなので、注意が必要です。

もう一点は、今回の例でいうと Left と Top に -4 を指定しても、0 となったように、範囲外の値を指定してもエラーが発生しないことです。こちらは、 #42 でも触れましたが、プログラムを実行しても希望した通りの配置とならない場合、バグなのか、Excel が調整したのか、それがいつ(どの操作で)発生したのか判定しずらい点に注意が必要です。


前回 Notes - Excel 連携 次回


2024/11/05

Notes - Excel 連携:#45)グラフタイトルとテキストボックスの違い

#41)グラフの調整』から開始した ”使える” グラフにするための調整作業です。今回はグラフ右上に配置するグラフタイトルエリアです。通常のタイトルのほかに補足情報を表示できるサブタイトル欄を用意します。

両タイトルとも右寄せで配置します。タイトルが長い場合、グラフエリアにはみ出して表示し、文字の折り返しはない前提とします。


グラフタイトルは特殊?

まずは Excel でグラフタイトルの設定を確認します。フォントの設定や右寄せなど一般的な操作は可能です。ただ、[グラフ タイトルの書式設定]-[タイトルのオプション]-[サイズとプロパティ]を表示すると、オブジェクトのサイズ設定がありません。

ところが、図形メニューから追加した ”テキスト ボックス” では、次の通り、サイズの設定が可能です。このようにグラフタイトルでは、設定できないプロパティが部分的に存在するようです。


グラフタイトルの削除

将来細かな操作が必要となった場合に備え、今回はグラフタイトルは使用せず、”テキスト ボックス” でタイトルとサブタイトルを表現することとします。

標準のグラフタイトルの削除は、HasTitle プロパティに False を設定するだけです。

Sub Initialize
         ・・・
   'タイトルのセット
   'oChart.ChartTitle.Text = "ユーザ数の推移"

   oChart.HasTitle = False
         ・・・


テキストボックスの配置

グラフ内にテキストボックスを配置する方法は、『#39)名前アイコン生成 ③』で紹介しました。Shapes オブジェクトの AddLabel メソッドを使用するんでしたね。

今回は、タイトル、ダブタイトル、Y 軸ラベルでテキストボックスを使用します。そこで、オブジェクトを作成する汎用的な関数 xCreateTextBox を作成します。

引数はグラフを表す Shape オブジェクトです。テキストボックスの作成以外に共通の設定である垂直方向の中央揃えと文字色を設定しています。

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

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

   Set xCreateTextBox = oText
End Function

なお、オブジェクトの配置や文字サイズなどはテキストボックス毎にバラバラになります。そのため、この関数の外側で設定する前提としています。ですので、オブジェクト作成時に初期設定しているオブジェクトの位置とサイズは適当な値で、意味はありません。


次回の予告

上記関数のテストを行います。次の通り、テキストボックスの作成して、文字をセットします。

Sub Initialize
          ・・・
   'プロット領域の設定
   oChart.PlotArea.InsideWidth = 350
   oChart.PlotArea.InsideLeft = 50
   oChart.PlotArea.InsideTop = 7
   oChart.PlotArea.InsideHeight = 170

   '関数テスト(テキストボックスの作成)
   Dim oText As Variant
   Set oText = xCreateTextBox(oShape)
   oText.TextFrame2.TextRange.Text = "Test"


   oXls.Visible = True
End Sub

※ 上記コードはテスト目的であり、以降の作業では不要です。


実行すると左と上に隙間が空きます。次回はこの挙動について整理したいと思います。


前回 Notes - Excel 連携 次回


2024/10/31

ノーツ・しこく・フェスタ 2024:AIとNotes/Domino 禁断のコラボ ~ 生成 AI 実装事例集

2024年 10月 25日にうどん(香川)県 高松市で開催された『ノーツ・しこく・フェスタ 2024』のレポートです。イベント本体はもちろん有意義なものでしたが、前後のエクストラツアーも充実しており、全方位で魅力的なイベントでした(結果的に 4 日間もうどん県に滞在...)。

イベントの全体像については、以下のブログを参照ください。一緒に参加した仲間がすでに公開してくれています。

ノーツコンソーシアムブログ:ノーツ・しこく・フェスタ2024 開催

Domino Lab:NSF 2024 エクストラツアー


私はこのイベントで、ミニセッションを担当しました。『AIとNotes/Domino 禁断のコラボ ~ 生成 AI 実装事例集』というタイトルで ”一席” 設けさせていただきました。

生成 AI 実装事例ということで、OpenAI の gpt-4o の API と連動したノーツアプリを 4 本を一気に紹介させていただきました。

どんなアプリなのか、ざっくり紹介させていただきます。


1. AI じゃダメなんですか? - AI 文書仕分

文書作成者がタイトル、カテゴリ、要約を記入していると、その人の知識や経験、好みや文章力で統一感がなくなるという、文書管理のお悩みあるあるを AI で解決しようというサンプルです。

Notes/Domino に関する HCLSoftware Blog をコピペしてノーツ文書として保存し、勝手に事例にさせていただきました。もとの文書を AI に送信し、タイトル、カテゴリ、要約を生成させています。

ビューで比較すると効果は一目瞭然です。AI ってすごいですね。

カテゴリは、ドキュメントタイプ、テーマ/トピック、キーワード(タグ的なもの)の 3 種類生成しています。これらカテゴリを複数の人間が的確に行うのは不可能に近いと思います。

ビューのカテゴリ分けはノーツの特徴的な機能で大変便利です。それを活かすためにもカテゴリの適切な設定、平準化は重要ですよね。


2. AI が導く、次世代の学習体験 - AI Learning

eLearning のコンテンツって作成時間がかかる割に利用が一瞬だったり、さまざまなレベルの利用者を前提したコンテンツが難しかったりと、作成作業は非常に非効率ですよね。これを AI に助けてもらおうというサンプルです。

次のように学習のテーマと目標を入力し、学習をスタートするだけで、コンテンツは AI が生成してくれるというアプリケーションです。

生成されたコンテンツは、次の通り、体裁を整えたノーツ文書として提供されます。キーワードはリンクになっていて、クリックするその単語をより詳しく教えてくれます。

これを繰り返し、どんどん調査(学習)を繰り返すという仕組みです。生成されたコンテンツはノーツの返答文書として階層管理されます。返答文書のツリーをビューで確認すると学習の進捗がわかります。クリックしたキーワード別のビューでは用語集になり、全ユーザ分をキーワードでカテゴライズすると同じ悩み(学習課題)を持つ仲間を探したりできます。

当日のセッションでは解説しなかった技術的な補足を少しだけします。

コンテンツはすべて AI に生成させています。各セクションのタイトルや文章以外にリンクにすべきキーワードも AI 任せです。ノーツの文章として表現する(プログラムで利用する)ためにこれらが的確に判定できる必要があります。これを実現するために、gpt-4o で使用できる Structured Outputs という機能を利用しています。この機能は、AI の返答をあらかじめ指定したスキーマに従わせることができます。要は返答がこちらが指定する JSON フォーマットになるので、どの部分がタイトルで、キーワードは何かが後続のプログラムで簡単に判別できます。

ちなみに、コンテンツの生成は DXL を使用しています。フォントサイズや行間の設定はもちろん、リンクも生成しています。LotusScript で記述されたホットスポットのアクションで、その段落内の文章とともにクリックしたキーワードについて AI に問い合わせしており、連続性のあるコンテンツが生成されるようにしています。


3. 添付画像の説明を生成 - PicInsight

古いノーツで添付した文書はサムネイルが表示されません。また、XPages や LotusScript で添付した文書も同様です。こういった画像ファイルを AI に食べさせ画像に何が写っているか説明させようというものです。

この結果をビューに表示すると、文書や画像を開くことなく、どのような画像か判別できます。より詳細に確認したい画像だけ開けばいいので、便利ですね。


4. AI はとびきりの愛なんだよ! - 忖度メール AI(アイ)

こちらは AI Learning と同様、文章生成系のアプリです。メールの宛先となる方の性格や趣味・趣向と要件を入力すると、メールの文面を考えてくれるアプリです。

メールの作成条件を入力してから、文面作成を依頼します。

すると、次のように素晴らしい文章が生成されます。新しいバイクが欲しいという欲求をダイレクトに伝えることなく、買ってもいいかな?買ったほうがいいかな?と思わる文章になっていますね。私には書けない文章です...

文面の作成は何度も実行できますので、気に入った文書が出るまで繰り返すことができます。実用においては、相手との関係性などを考慮して、言葉尻や事実関係などの調整が必要かもしれませんが、話の流れや構成はそのまま利用できると思います。


まとめ

これまで、AI との連携はつながるかどうかの検証など、技術的な側面からだけ見ていました。今回のセッションはサンプルアプリの紹介がテーマだったので、利用シーンなどを考えながらアプリ開発を行い、AI 利用の可能性を大きく感じました。

AI は膨大な知識を持っています。API を利用するとその知識や情報量を利用したアプリが作成できます。知らないことはもちろんですが、自分にない発想で回答してくれます。新たな気づきが得られることに感動しました。

AI への問い合わせは、標準化された方法で問い合わせします(連載『つないでみよう』参照)。パターンが決まっているということは、応用が簡単ということです。今回紹介した 4 アプリとボツとなった 2 本を合わせて、6 DBを作成しました。9月 19日 の DominoHub Osaka が終わって本格的に作成したので、期間は約 1 ヶ月間でした。Notes/Domino が備える基本機能の高さもありますが、開発効率が高いと感じました。スピード感をもって対応できますね。

AI × Notes/Domino。これまでとは違う新しい世界、そしてその可能性を感じました。今後の展開が楽しみです!


最後に、今回のセッションはノーツコンソーシアム 大阪研でともに活動している 林哲司さん と共同で行いました。前半は私の事例紹介で、後半は林さんの『Notesアプリと生成AIを融合 生成AI実装のポイントと課題』でした。

AI 連係の実装については林さんのブログ『Notes開発者のためのXPagesデザインレシピ』も参考にしてください。

AI 関連の記事はこちら