出直し!! ヘルプ

連載中

連載 終了

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


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 連携 次回