20260519_DominoIQを使わない生成AIによるHCL Domino自然言語検索(サンプルコード付き)

112 Views

May 21, 26

スライド概要

HCL Dominoの全文検索と生成AIを組み合わせ、自然言語から高精度な検索条件を生成する実装例を紹介します。
MCPやRAGを使わず、既存のDomino資産を活用しながら、LotusScriptとOpenAI APIで今すぐ実装可能なアプローチを解説。
JSONスキーマ、developerロール、FTSearch連携など、PoCから実装・運用までの試行錯誤を共有します。
「AI時代にDominoをどう活かすか?」を、現場視点で一緒に考える研究会資料です。

profile-image

Slideshare有料化に伴い過去の資料をこちらのアップします。

シェア

またはPlayer版

埋め込む »CMSなどでJSが使えない場合

ダウンロード

関連スライド

各ページのテキスト
1.

V10.0.1以降で動くはず DominoIQを使わない 生成AIによるHCL Domino自然言語検索 生成AIで精度の高い検索条件を生成 MCPもRAGも使いません 2026/5/19(火) Tetsuji Hayashi1

2.

DominoDBに貯まったデータ資産活用出来てますか? • ビューを作る • カテゴリ分け、集計など • エクスポートしてExcelで分析 最初にトリガーとなる ユーザーの発想・意図が必要 • BIと繋げてグラフ化 特にアイディアはないけど 何となく検索したら 新しい発想が生まれるかも? 部活もせずに、家に帰ってアニメの再放送を見 ているようなオレだけど・・・ ひょっとしたら、 オレもニュータイプかもしれない (という期待値、希望が日本には必要) 機動戦士ガンダム アムロレイの画像 (ニュータイプの閃き) 2

3.

何がやりたいか? • 生成AI時代、いずれ人間は、キーワード検索できなくなる • 生成AIを介した自然言語検索が主流になるはず • 全文検索はキーワードに一致する文書がすべてヒットするので、ひとつひとつ精査が必要 • 自分が欲しいもの以外の文書が大量にヒットするか?単語違いでヒットしないか? • 細かく条件を指定するにはフィールド名を知り、かつ不等号を付けた条件式を作る必要がある • 一般ユーザーはフィールド名なんて知らないし、本来意識する必要もない • 自然言語検索が出来れば、機能ごとにビューを作らなくても「すべて」ビュー(実際はフォルダ) のみでユーザーがナレッジの抽出を行える • ビューの作りすぎで重くなるということもない(という期待値) Dominoデータベース(NSF)が情報資産になる時代がやっと来た!!! 3

4.

何がやりたいか? Teamsアダプタがあるなら Dominoアダプタがあっても よくね? 4

5.

Teamsコネクタの構造 社内LAN OpenAI(ChatGPT) ②意味理解&検索条件の推定& GraphAPIに合わせたクエリ構築 ⑤意味理解&文章作成 ChatGPT(LLM)+Teams コネクタ (Microsoft Graph API Client) Internet ④複数回答 Microsoft ユーザー権限に応じた セキュリティがかかる ③認証&検索クエリ (複数問合せ) MCPっぽ い何か Microsoft GraphAPI 生成AIの「検索しています」というWeb検索も基本的に同じ Teams DB ShareP ointDB 5

6.

今回の構成 社内LAN OpenAI(ChatGPT) ②意味理解&検索条件の推定&全文 検索クエリ構築 OpenAI API ⑥文書作成 Internet Domino DB ④全文検索 実行 ユーザー権限に応じた セキュリティがかかる 6

7.

基礎研究 実現可能性調査、PoC 7

8.

動作を実現するために必要な技術要素 【要素1】自然言語を検索条件にするには? • 人間が入力する文章(自然言語)に含まれる単語を、どうやってフィールドに結びつけるか? • DominoDBはフィールド名を英単語で付けている場合が多い • 英語圏ならフィールド名≒単語って場合もあるが、日本はそうならない • 数値、日付で大小判定するならフィールドの型定義も必要 項目名(日本語)、フィールド名、データ型を定義したJSONを作る 【要素2】生成AIによるレスポンスが文章だと「どこが検索条件か?」判定する必要がある • JSONスキーマを定義して、回答フォーマットを指定する • JSONスキーマを指定すると構造化テキスト(JSON、XML、YAMLなど)で回答を得ることができる JSONスキーマを学ぼう PoC実装の前に脳内で実現可能性を検証する 8

9.

要素1:項目定義JSONで単語とフィールド名を紐付ける • 必要なのは日本語項目名、フィールド名、データ型 • label • field • type 日本語項目名 フィールド名 データ型(text、number、date) definitions = [ {"label": "顧客ID", "field": "CustomerId", "type": "text"}, {"label": "注文日", "field": "OrderDate", "type": "date"}, {"label": "注文No", "field": "OrderNo", "type": "text"}, {"label": "商品価格", "field": "ItemPrice", "type": "number"}, {"label": "商品コード", "field": "OrderItem", "type": "text"}, {"label": "商品カテゴリ", "field": "OrderItemCate", "type": "text"}, {"label": "注文数", "field": "OrderNum", "type": "number"}, {"label": "合計金額", "field": "OrderPrice", "type": "number"} ] 9

10.

要素2:JSONスキーマで返信内容を定義する • JSONスキーマの基本フォーマット • type • property • required • additionalProperties { } 型の指定 返信する項目名とデータ型 必ず返す項目(property) 余計なことはしゃべるな指定 "type": "object", // 1. 型の指定 "properties": { // 2. 項目の定義 "notes_query": { レスポンスして欲しい項目を並べて定義(今回は全文検索条件) "type": "string" } }, "required": [ これだけは絶対に返信しろ "notes_query" ], // 3. 必須項目の宣言 "additionalProperties": false // 4. 余計な出力を禁止 AIの間違いを考慮して、人間が確認するためのデータも欲しい 10

11.

要素2:JSONスキーマで返信内容を定義する • 今回利用したJSONスキーマ(2層構造) "response_format": { "type": "json_schema", "json_schema": { "name": "notes_query_result", "strict": True, "schema": { "type": "object", "properties": { "notes_query": { "type": "string" }, "matched_fields": { "type": "array", "items": { "type": "object", "properties": { "label": { "type": "string" }, "field": { "type": "string" }, "type": { "type": "string" } }, "required": [ "label", "required": [ "label", "field", "type" ], "additionalProperties": False ここも必要 } }, "assumptions": { "type": "array", "items": { "type": "string" } } ①全文索引クエリ }, "required": [ "notes_query", "matched_fields", "assumptions" ], "additionalProperties": False ②検索クエリに使用した フィールド ③生成AIの判断理由 ④3つの項目を回答させる } } }, 11

12.

要素2:JSONスキーマで返信内容を定義する • 「3つ以上の注文で3万円以上の売上」と指定した場合のレスポンス (/choices/0/message/contentの中身) { } "notes_query": "OrderNum >= 3 AND OrderPrice >= 30000", "matched_fields": [ { "label": "注文数", "field": "OrderNum", "type": "number" }, { "label": "合計金額", "field": "OrderPrice", "type": "number" } ], "assumptions": [ "ユーザーは注文数が3以上で合計金額が3万円以上の条件を指定している。", "注文数と合計金額はそれぞれ数値型のフィールドである。" ] ①全文索引クエリ ②検索クエリに使用したフィールド ③生成AIの判断理由 生成AIはUPよりDOWNの金がかかるので JSONスキーマを定義すれば非常に低コストで運用出来る 12

13.
[beta]
まずは動く物が欲しい(生成AIにPythonで作らせる)
• Pythonならコピペで動く物が生成できる(実行も生成AI内でできる)
import json
import requests

【systemロール】
・以前からある生成AIに対する指示、役割設定

def get_notes_query():
# OpenAIのAPIキーを設定
api_key = “(APIKEYを指定する)"
# 1. 項目定義リストを組み立てる
definitions = [
{"label": "顧客ID", "field": "CustomerId", "type": "text"},
{"label": "注文日", "field": "OrderDate", "type": "date"},
{"label": "注文No", "field": "OrderNo", "type": "text"},
{"label": "商品価格", "field": "ItemPrice", "type": "number"},
{"label": "商品コード", "field": "OrderItem", "type": "text"},
{"label": "商品カテゴリ", "field": "OrderItemCate", "type": "text"},
{"label": "注文数", "field": "OrderNum", "type": "number"},
{"label": "合計金額", "field": "OrderPrice", "type": "number"}
]
# 2. ユーザーの入力を取得
user_query = "注文が3件以上で、3000円以上の取引"

【developerロール】
・推論や指示遵守に特化した役割
・開発者からの絶対的指示

コードギアス
ルルーシュの画像
(ギアスをかける
シーン)

①ユーザーが入力する質問(フィールドから入力させる)

# 3. ペイロードの組み立て
# Pythonの辞書(dict)として作成することで、json.dumpsが適切にシリアライズします
payload = {
"model": "gpt-4o-mini",
"messages": [
{
②下に書くのは開発者からの強い指示ですよという指定
③開発者の指示(全文検索条件を作れ!) +項目定義JSON
"role": "developer",
"content": f"以下は検索可能な項目定義です。labelはユーザー表現、fieldはNotesのフィールド名、typeはデータ型です。HCL Notes用の全文検索条件を作りなさい。
¥n{json.dumps(definitions, ensure_ascii=False)}"
},
{

13

14.

まずは動く物が欲しい(生成AIにPythonで作らせる) { "role": "user", "content": user_query } ], "response_format": { "type": "json_schema", "json_schema": { "name": "notes_query_result", "strict": True, "schema": { "type": "object", "properties": { "notes_query": {"type": "string"}, "matched_fields": { "type": "array", "items": { "type": "object", "properties": { "label": {"type": "string"}, "field": {"type": "string"}, "type": {"type": "string"} }, "required": ["label", "field", "type"], "additionalProperties": False } }, "assumptions": { "type": "array", "items": {"type": "string"} } }, "required": ["notes_query", "matched_fields", "assumptions"], "additionalProperties": False } ④JSONスキーマ 14

15.
[beta]
まずは動く物が欲しい(生成AIにPythonで作らせる)
}
},
"temperature": 0.0
}
# 4. APIへのリクエスト送信
headers = {
"Content-Type": "application/json",
"Authorization": f"Bearer {api_key}"
}
try:
response = requests.post(
"https://api.openai.com/v1/chat/completions",
headers=headers,
data=json.dumps(payload)
)
print(payload)
response.raise_for_status() # エラーがあれば例外を発生させる

⑤リクエストJSONをPOST

# レスポンスの解析
result = response.json()
content = result['choices'][0]['message']['content']
# 文字列として返ってくるJSONをパースして表示
structured_data = json.loads(content)
print("--- 検索クエリ ---")
print(structured_data.get("notes_query"))
print("¥n--- マッチしたフィールド ---")
for field in structured_data.get("matched_fields", []):
print(f"- {field['label']} ({field['field']}): {field['type']}")
print("¥n--- 前提条件 ---")
for assumption in structured_data.get("assumptions", []):
print(f"- {assumption}")

PythonならNotesJSONクラスみたいな
ややこしいのを使わなくてもforループで
簡単に分解できる

⑥レスポンスを分解してコンソールに表示

15

16.

まずは動く物が欲しい(生成AIにPythonで作らせる) except Exception as e: print(f"エラーが発生しました: {e}") if __name__ == "__main__": get_notes_query() ⑦コマンドラインから実行 • 実行結果(VS Code) 16

17.

先ほどの検索クエリには問題があります • 全文検索条件はフィールド名を[]で括る必要がある • 修正方法は簡単!ギアス(developer属性)を追加するだけ { "role": "developer", "content": f"以下は検索可能な項目定義です。labelはユーザー表現、fieldはNotesのフィールド名、typeはデータ型です。HCL Notes用 の全文検索条件を作りなさい。全文検索条件はフィールド名を[]で括る必要があります。¥n{json.dumps(definitions, ensure_ascii=False)}" }, 17

18.

まだ完全ではありません • 「2020年の注文が知りたい」→文字列として出力される • ギアス(developer属性)を追加 { "role": "developer", "content": f"以下は検索可能な項目定義です。labelはユーザー表現、fieldはNotesのフィールド名、typeはデータ型です。HCL Notes用 の全文検索条件を作りなさい。全文検索条件はフィールド名を[]で括る必要があります。日付の比較を行う場合、日付はダブルコーテーショ ンで括りません。¥n{json.dumps(definitions, ensure_ascii=False)}" }, 18

19.

今までのコーディング概念では理解不能な点 • developerロールの指定 { "role": "developer", "content": f"以下は検索可能な項目定義です。labelはユーザー表現、fieldはNotesのフィールド名、typeはデータ型で す。HCL Notes用の全文検索条件を作りなさい。¥n{json.dumps(definitions, ensure_ascii=False)}" }, • レスポンス JSONスキーマ(notes_query、matched_fields、assumptions) の説明は一切定義していない 19

20.

ここまでが下ごしらえ • ゴール(正解)が見えないと無駄に遠回りするのでPythonでゴールを見極めておく • VBAでもJavaでも生成AIの正答率が高い言語なら何でも良い 20

21.

Dominoでの実装 21

22.

初期設定文書 • APIKey、developer属性、フィールド定義JSONを入力するフィールドを配置する • フィールド定義JSONはシングルコーテーションで囲って定義する • ダブルコーテーションで作るとJSON作成時にエスケープ(ダブルコートが並ぶ)する必要があり面倒 ひ・み・つ developer属性をフィールド化し、 チューニングを簡単にする (適格な指示が必要) 入力変換式でダブルコーテーションを シングルコーテーションに変換 22

23.

項目定義JSONをシングルコーテーションにする理由 • LotusScriptで変数をダブルコートの中に入れたい sDeveloperParam = |{"role":"developer","content":"| & sDeveloper & |¥n| & sFieldMappingJson & |"}| Pythonの文字列はダブル、シングル どっちでもOK 23

24.

検索データの準備 • 「サンプルデータ フリー CSV」でググって適当なデータを用意する • 実運用DBでも可 ←日付 ←数値 ←数値 ←数値 24

25.

LotusScriptへの移植時のキモ • 基本はNotesHTTPClientクラスで書き換えればOK • 面倒なのはレスポンスをNotesJSON****クラスで分解するところ { } "notes_query": "OrderNum >= 3 AND OrderPrice >= 30000", "matched_fields": [ { "label": "注文数", "field": "OrderNum", "type": "number" }, { "label": "合計金額", "field": "OrderPrice", "type": "number" } ], "assumptions": [ "ユーザーは注文数が3以上で合計金額が3万円以上の条件を指定している。", "注文数と合計金額はそれぞれ数値型のフィールドである。" ] ①項目からそのままテキストを取得 ②JSONの配列内 ③文字列の配列 25

26.

LotusScriptへの移植時のキモ ① 項目からそのままテキストを取得 Set jsonNav = http.post(APIURL, sRequestJson) '正常終了の場合にレスポンスから回答を抽出し、フィールドに書き込み vResCode = Split(http.ResponseCode, " ") 'HTTP1.1 200 OKを分解 If vResCode(1) = "200" Then sContent = jsonNav.GetElementByPointer("/choices/0/message/content").Value Set jsonNavContent = session.CreateJSONNavigator(sContent) 'notes_query の取得 (単純な文字列) sNotesQuery = jsonNavContent.GetElementByName("notes_query").Value doc.NotesQuery = sNotesQuery レスポンスのJSON部分をJSONNavigatorへ JSONNavigatorから項目を指定するだけ "notes_query": "OrderNum >= 3 AND OrderPrice >= 30000", 26

27.
[beta]
LotusScriptへの移植時のキモ
② JSONの配列内
'matched_fields の取得 (配列内のオブジェクト)
Set jsonArray = jsonNavContent.GetElementByName("matched_fields").Value
JSONArrayをループで分解
Set jsonElem = jsonArray.GetFirstElement()
JSONElementに取得する
Do Until jsonElem Is Nothing
Set jsonObj = jsonElem.Value
JSONElementのvalueをJSONObjectに取得
sLabel = jsonObj.GetElementByName("label").Value
JSONオブジェクトからキー(属性名)で取得
sField = jsonObj.GetElementByName("field").Value
sType = jsonObj.GetElementByName("type").Value
doc.MatchedFields = doc.MatchedFields(0) & sLabel & "(" & sField & ") : " & sType & Chr(13)
Set jsonElem = jsonArray.GetNextElement()
Loop

"matched_fields": [
{
"label": "注文数",
"field": "OrderNum",
"type": "number"
},
{
"label": "合計金額",
"field": "OrderPrice",
"type": "number"
}
],

あのDesignerHelpでわかるかボケ!みたいな形になる

27

28.

LotusScriptへの移植時のキモ ③ 文字列の配列 'assumptions の取得 (文字列の配列) Set jsonArray = jsonNavContent.GetElementByName("assumptions").Value JSONArrayをループで分解 Set jsonElem = jsonArray.GetFirstElement() JSONElementに取得する Do Until jsonElem Is Nothing sAssumptions = jsonElem.Value JSONElementのvalueが欲しい値 doc.Assumptions = doc.Assumptions(0) & sAssumptions & Chr(13) Set jsonElem = jsonArray.GetNextElement() Loop "assumptions": [ "ユーザーは注文数が3以上で合計金額が3万円以上の条件を指定している。", "注文数と合計金額はそれぞれ数値型のフィールドである。" ] GPT5.4よりGeminiのほうがNotesJSONクラスの扱いは得意そう 28

29.

生成された「notes_query」を使って検索すれば完成 29

30.

例) 家電で3つ以上売れていて、八万円以上の売上 30

31.

例) 2021年5月の取引で、三万円以上の売上 31

32.

制限事項:普通の制約 • 全文検索条件で表現出来ない物は検索出来ない • 例) 一番金額が高い取引 • FTSearchには取得出来る文書数に上限がある • デフォルトは5,000件(PentiumPro 200MHz時代くらいの制約) • notes.iniのFT_MAX_SEARCH_RESULTSで指定(新規追加) • FT_MAX_SEARCH_RESULTSの上限値は2,147,483,647(21億4,700万) • FTSearchの第二引数に指定出来る最大値は32,767だが、notes.ini以下の値のみ有効 • Nomad系の動作 • アクションボタンにNotesHTTPClientを実装しても動きません!!! • NotesクライアントはOSレベルで通信するので問題なし • Webブラウザの場合は、別ドメイン(openai.com)への通信となり、CORS制限に抵触する • エージェント化して実行する必要がある • エージェントの実行時セキュリティレベルには「2.制限された操作を許可する」以上の権限が必要 • 検索条件は本来保存しなくても良い物だが、エージェントにすると保存する必要がある, 32

33.

クロスオリジン制限:Webブラウザ • 通常の通信 • 外部API呼出(Webサービス参照) www.domino.com www.domino.com www.openai.com 同じドメイン内の機能、リソースを呼び 出すのは問題なし ドメインが違うのでダメです www.domino.comから取得し たHTMLから呼び出しますけど OKですか?(Originヘッダー) Webブラウザ OK www.domino.comから 取得したHTML Webブラウザ OK www.domino.comから 取得したHTML 33

34.

クロスオリジン制限:Notes&Domino • Notesクライアント www.domino.com • Dominoエージェント+Webブラウザ www.openai.com OriginないのでOK www.domino.com OSアプリとしての Dominoサーバー www.openai.com Originヘッダー (自己申告)なし OriginないのでOK Originヘッダー (自己申告)なし Webブラウザ OK OSアプリとしてのNotes クライアント www.domino.comから 取得したHTML 34

35.

制限事項:生成AIモデルのバージョンアップ • ここまでの実装は「gpt-4o-mini」を使っていました • このままの状態でモデルだけ変えるとどうなるか?の検証 • gpt-4o • gpt-5.4-mini • gpt-5.4 35

36.

参考情報:モデルと価格 • 自分が求める結果、性能にあった価格のモデルを選択する モデル 入力 キャッシュ入力 出力 GPT-5.4 $2.50 $1.25 $15.00 GPT-5.4 mini $0.75 $0.075 $4.50 GPT-5.4 nano $0.20 $0.02 $1.25 GPT-4o $2.50 $1.25 $10.00 GPT-4o mini $0.15 $0.075 $0.60 2倍 7.5倍 依存を作ってからハシゴを落としてくるビジネスモデルなので賢く使う 36

37.

参考情報:PythonのEOL(end-of-life) • Python時代、クラウド時代は走り続ける必要がある • Python本体もライブラリも見直す必要がある 20年間無改修で同じアプリを使い続けるということが出来ない時代 37

38.

参考情報:ローディングアイコンの実装 • POSTからレスポンスの間は画面が止まって見える • ユーザーは壊れた?動いてない?という心理になる • WindowsAPIの砂時計アイコンはNomad系では動かない 38

39.

参考情報:ローディングアイコンの実装 ◼ 準備する物 • ローディング用アニメーションgif ◼ 作成手順 1. 表示・非表示切替フラグ用フィールドを作成する(IsLoading) 2. 上記フィールドの下にレイヤーを配置し、非表示式を設定する 1. フラグフィールドが「1」の場合、非表示 3. レイヤーにローディング用アニメーションgifを配置し、レイヤーを出したい位置に移動する 4. ボタンを作成し、@式やLotusScriptでフラグ用フィールドに「1」「0」を書き込む 39

40.

参考情報:ローディングアイコンの実装 ◼ 作成手順続き • STARTボタン ・ENDボタン ◼ 制限事項 • LotusScriptはシングルスレッドなので、NotesHTTPRequest実行時はグルグルしない • ユーザーにはやってるだろうという意図は伝わるのでまぁOK • Javaなら別スレッドだと思うのでグルグルするかも。。。。 40

41.

今後の拡張と課題 41

42.

Not条件をどうする? • 「雑貨以外の商品」→全文検索ではNot条件が特殊 • ギアス(developer属性)を追加 { "role": "developer", "content": f"以下は検索可能な項目定義です。labelは項目名、fieldはNotesのフィールド名、typeはデータ型、aliasesは項目名の別の言 い回しです。空の場合は項目名のみを参照しなさい。HCL Notes用の全文検索条件を作りなさい。全文検索条件はフィールド名を[]で括る必 要があります。日付の比較を行う場合、日付はダブルコーテーションで括りません。否定条件の場合は「NOT [field] CONTAINS 条件」の ように記述します。¥n{json.dumps(definitions, ensure_ascii=False)}" }, NotesDatabase.FTSearchではフォーム名を限定する必要性が発生する 42

43.

Not条件をどうする? • 項目定義をフォームごとに拡張 { } "form": "frmOrder", "label": "注文マスタ", "definitions": [ { "label": "顧客ID", "field": "CustomerId", "type": "text", "aliases": ["得意先コード"] }, { "label": "注文日", "field": "OrderDate", "type": "date", "aliases": [] }, { "label": "商品カテゴリ", "field": "OrderItemCate", "type": "text", "aliases": [] }, { "label": "合計金額", "field": "OrderPrice", "type": "number", "aliases": ["しのぎ"]} ] • ギアスを追加 { "role": "developer", "content": f"以下は検索可能な項目定義です。labelは項目名、fieldはNotesのフィールド名、typeはデータ型、aliasesは項目名の別の言い回しです。空の場合は 項目名のみを参照しなさい。HCL Notes用の全文検索条件を作りなさい。全文検索条件はフィールド名を[]で括る必要があります。日付の比較を行う場合、日付はダ ブルコーテーションで括りません。否定条件の場合は「NOT [field] CONTAINS 条件」のように記述します。検索クエリには必ずフォーム名の指定をAND条件で含 めます。¥n{json.dumps(forms, ensure_ascii=False)}" }, これでドメイン検索(FTDomainSearch)にも対応出来るかも 43

44.

Not条件をどうする? • 設定文書のフィールド定義JSONを書き換えれば動く フォームなし フォームあり definitions = [ {"label": "顧客ID", "field": "CustomerId", "type": "text"}, {"label": "注文日", "field": "OrderDate", "type": "date"}, {"label": "注文No", "field": "OrderNo", "type": "text"}, {"label": "商品価格", "field": "ItemPrice", "type": "number"}, {"label": "商品コード", "field": "OrderItem", "type": "text"}, {"label": "商品カテゴリ", "field": "OrderItemCate", "type": "text"}, {"label": "注文数", "field": "OrderNum", "type": "number"}, {"label": "合計金額", "field": "OrderPrice", "type": "number"} ] forms = [ { "form": "frmOrder", "label": "注文マスタ", "definitions": [ {"label": "顧客ID", "field": "CustomerId", "type": "text", "aliases": ["得意先コード"]}, {"label": "注文日", "field": "OrderDate", "type": "date", "aliases": []}, {"label": "商品カテゴリ", "field": "OrderItemCate", "type": "text", "aliases": []}, {"label": "合計金額", "field": "OrderPrice", "type": "number", "aliases": ["しのぎ"]} ] } 44 ]

45.

同義語、社内用語、省略語に対応する • 項目定義JSONにエイリアスを追加し、同義語、社内用語を追加し、検索精度を上げる definitions = [ {"label": "顧客ID", "field": "CustomerId", "type": "text", "aliases": ["得意先コード", "クライアントID", "Customer ID"]}, {"label": "注文日", "field": "OrderDate", "type": "date", "aliases": []}, {"label": "注文No", "field": "OrderNo", "type": "text", "aliases": []}, {"label": "商品価格", "field": "ItemPrice", "type": "number", "aliases": []}, {"label": "商品コード", "field": "OrderItem", "type": "text", "aliases": []}, {"label": "商品カテゴリ", "field": "OrderItemCate", "type": "text", "aliases": []}, {"label": "注文数", "field": "OrderNum", "type": "number", "aliases": []}, {"label": "合計金額", "field": "OrderPrice", "type": "number", "aliases": ["しのぎ"]} ] { "role": "developer", "content": f"以下は検索可能な項目定義です。labelは項目名、fieldはNotesのフィールド名、typeはデータ型、aliasesは項目名の別の言い回しです。空の場合は 項目名のみを参照しなさい。HCL Notes用の全文検索条件を作りなさい。全文検索条件はフィールド名を[]で括る必要があります。日付の比較を行う場合、日付はダ ブルコーテーションで括りません。¥n{json.dumps(definitions, ensure_ascii=False)}" }, • 実行するクエリ user_query = "しのぎが三万円以上" 45

46.

その他の可能性 • 一度の質問で集計や要約までやらせたい STEP1 ユーザの入力した質問を検索条件部、要約指示部に分ける 生成AI① STEP2 検索条件部から全文検索条件を生成 生成AI② 得られた検索条件から検索を実行 NotesClient 検索結果と要約指示を生成AIにPOSTし、文章を生成させる 生成AI③ STEP3 • FTDomainSearchメソッドを利用し、ドメイン検索に利用する • 「データベースは忘れたけど、こういう情報があったはず・・・」にたどり着きやすくなる • リッチテキスト内の文章、添付ファイル内の文章を拡張検索 • 「遠方への出張に関する規定を教えて」と聞いた時にキーワードを補完 • • • • 移動: 交通費、特急料金、新幹線、航空機、タクシー 宿泊: 宿泊費、日当、滞在費、宿泊上限額 手続き: 出張申請書、仮払申請、旅費精算、事後報告 遠方の定義: 100km以上、宿泊を伴う、県外 (交通費 OR 宿泊 OR 精算 OR 日当) AND 出張 46

47.
[beta]
その他の可能性
• 全文検索じゃなくてDQLの方が良いんじゃね?
• できます
• 検索数の5,000件上限はありません
• @dtで日付型にデータを合わせたり、@flで複雑な検索条件を指定することができますが、今のところ条
件を教育していく必要があります
• gpt-4o-miniとgpt-5.4-miniでは後者の方が読解力が高い
{
"role": "developer",
"content": f"以下は検索可能な項目定義です。labelは項目名、fieldはNotesのフィールド名、typeはデータ型、aliasesは項目名の別の言い回しで
す。空の場合は項目名のみを参照しなさい。HCL Notes用のDomino Query Languageを使用した検索条件を作りなさい。値はシングルコーテーショ
ンで括ります。論理演算子にはand、or、notがあります。比較演算子には=、>、=>、<、=<、inがあります。複数の文字列をリストにして比較す
るには()で括り、「in ('field1', 'field2')」のように記述します。否定条件は「not (field = 'value')」のように一致条件を先に作り、先頭にnotを付
けます。日付の比較は@dt('yyyy-mm-dd')とし、@dt関数でデータ型を日付に変更します。¥n{json.dumps(forms, ensure_ascii=False)}"
},

gpt-4o-mini

-

動かない

gpt-5.4-mini

(OrderItemCate not in ('雑貨', '家電'))

-

動く

not (OrderItemCate = '雑貨' or OrderItemCate = '家電')

ギアス(developer属性)の定義が難易度高め

47

48.

まとめ • 小規模だが自分が理解できる実装をやってみることで、自分の能力も生成AIの能力も見えてくる • 生成AIはすべてのインターフェースに組み込まれることになるのは変わらない • 自分で実装すればチューニング(精度向上)も自分で出来る • 生成AIの能力が見えてくると、より有効な活用シーンが具体化できる • 人間も同じ、向いてないところにアサインしても使えない • どこが補完できて、どこを人間がやるべきか? • 生成AIとDominoDBを繋げれば、誰でも最適な答えが得られるわけではない • DominoIQやRAGさえあれば、売上が10億アップで、あなたの年収は10倍とはならない 生成AI時代に必要なのは背景、本質理解と、新しい構造の定義ではないか? 48

49.

キーワード検索の終わりと自然言語検索の世界 • 条件マッチングの終わり • パートナー、就職・転職、コミュニティなどのマッチング • 学歴、過去の年収、職歴、容姿etc • MBTI診断、DSKB診断のさらに先へ • 一番実装しやすいのはゲーム内→トモダチコレクションは一部実装済み • 自然言語からはキーワードではない多くの情報が得られる • 語彙の多さ、言葉の使い方、言い回し • 過去の検索と組み合わせれば、精度の高い人物像が得られる • GoogleやAppleは行動履歴も持っている • 一時的にハックできても、ハックも学習されるから逃げられない • 世界の上部構造にいる人は「人類って個性とか言ってもこの程度の多様性しかないのか?じゃぁほとん どいらなくね?」となるのもよくわかる 文脈・コンテキストマッチングへ 必要なのは個性、差別化ではなく、何を自覚して生きるか? 49

50.

最大価値の変化:ポストマネー時代へ 神 自然 お金 X 重力に魂を引かれたものたち 過去の価値観で生きる人 現時点でアホか!と思っても来るものは来る 50

51.

付録 • LotusScriptサンプルコード • サンプルなのでエラートラップなどは御自身で追加して下さい OpenAI_FTSearch.lss 51

52.
[beta]
~\OneDrive\MyDocument\40 ノーツコンソ\大阪研究会2026\OpenAI_FTSearch.vb
1
2

Sub Click(Source As Button)
'---------- ---------- ---------- ---------- ----------

3
4
5

'OpenAI APIから取得したレスポンスを書込み
'
'---------- ---------- ---------- ---------- ----------

6
7
8

Const APIURL = "https://api.openai.com/v1/chat/completions"

9
10
11

Dim ws
Dim uidoc
Dim doc

As New NotesUIWorkspace
As NotesUIDocument
As NotesDocument

12
13
14

Dim settingdoc
Dim session

As NotesDocument
As New NotesSession

15
16
17

Dim http
Dim jsonNav
Dim jsonNavContent

As NotesHTTPRequest
As NotesJSONNavigator
As NotesJSONNavigator

18
19
20

Dim jsonElem
Dim jsonArray

As NotesJSONElement
As NotesJSONArray

'JSON配列

21
22
23

Dim jsonObj

As NotesJSONObject

'JSON配列内の1項目

Dim sRequest

As String

24
25
26

Dim sRequestJson
Dim sContent
Dim sNotesQuery

27
28
29

Dim sLabel,sField,sType As String
Dim sAssumptions
As String

30
31
32
33

Dim sApiKey
Dim sModel
Dim sDeveloper
Dim dTemperature

34
35
36

Dim sFieldMappingJson
As String
Dim sDeveloperParam As String
Dim sJsonSchema
As String

'項目名、フィールド名対応関係JSON(API設定文書から取得)
'指示パラメータ文字列
'戻り値のSchemaを定義

37
38
39

Dim vResCode

'HTTPレスポンスコード配列

40
41
42

Set uidoc
Set doc

43
44
45

'フィールドの値クリア
doc.NotesQuery
= ""
doc.MatchedFields
= ""

46
47
48

doc.Assumptions

49
50
51

Set settingdoc
sApiKey
sModel

= GetSettingDocument()
= settingdoc.ApiKey(0)
= settingdoc.Model(0)

52
53
54

sDeveloper
sDeveloper

= settingdoc.Developer(0)
= Replace(sDeveloper, Chr(13) & Chr(10), "\n")

55
56
57

sFieldMappingJson
= settingdoc.FieldMappingJson(0)
sFieldMappingJson
= Replace(sFieldMappingJson, Chr(13) & Chr(10), "\n")
dTemperature
= settingdoc.Temperature(0)

58
59
60

sDeveloperParam = |{"role":"developer","content":"| & sDeveloper & |\n| & sFieldMappingJson & |"}|
sJsonSchema = |"response_format": {| & _

61
62
63

|"type": "json_schema",| & _
|"json_schema": {| & _
|"name": "notes_query_result",| & _

64
65
66

|"strict": true,| & _
|"schema": {| & _
|"type": "object",| & _

67
68
69

|"properties": {| & _
|"notes_query": { "type": "string" },| & _
|"matched_fields": {| & _

70
71
72

|"type": "array",| & _
|"items": {| & _
|"type": "object",| & _

73
74
75

|"properties": {| & _
|"label": { "type": "string" },| & _
|"field": { "type": "string" },| & _

76
77
78

|"type": { "type": "string" }| & _
|},| & _
|"required": ["label", "field", "type"],| & _

79
80
81

|"additionalProperties­
": false| & _
|}| & _
|},| & _

'クラス・変数宣言

'入力した質問

As String
As String
As String

'送信するJSON文字列
'回答本文全体
'検索クエリ
'マッチフィールド
'前提条件

As String
As String
As String
As Double

'API KEY(API設定文書から取得)
'API MODEL(API設定文書から取得)
'指示(API設定文書から取得)
'精度(API設定文書から取得)

As Variant

'クラス・変数セット
= ws.CurrentDocument
= uidoc.Document

= ""

'設定文書からAPIKey、モデル、指示などを読み込み
'関数を使って設定文書取得

53.
[beta]
82
83

|"assumptions": {| & _
|"type": "array",| & _

84
85
86

|"items": { "type": "string" }| & _
|}| & _
|},| & _

87
88
89

|"required": ["notes_query", "matched_fields", "assumptions"],| & _
|"additionalProperties­
": false| & _
|}| & _

90
91
92

|}| & _
|},| & _
|"temperature":| & Format$(dTemperature, "0.0#") & _

93
94
95

|}|

96
97
98

If settingdoc Is Nothing Then
Msgbox "設定文書を作成して下さい。",16,"エラー"
Exit Sub

99
100
101

End If

102
103
104

sRequest
= doc.Request(0)
If sRequest = "" Then
Msgbox "質問を入力して下さい。",64,"確認"

105
106
107

Exit Sub
End If

108
109
110

'入力文字から禁止文字(改行など)を削除
sRequest
= Replace(sRequest, Chr(13) & Chr(10), "\n")
'ソースコード送信対策

111
112
113

sRequest
sRequest

114
115
116
117

'リクエストJSON(送信文字列)の作成
sRequestJson
= |{"model":"| & sModel &_
|", "messages":[ | &_
sDeveloperParam & |,| &_

118
119
120

|{"role":"user", "content":"| & sRequest & |"} ],| &_
sJsonSchema

121
122
123

'Msgbox sRequestJson

124
125
126

doc.IsLoading
= 1
Call uidoc.RefreshHideFormulas

127
128
129

'Postリクエストを実行し、結果を取得
Set http
= session.CreateHTTPRequest()
'クラス・変数セット
Call http.SetHeaderField("Content-Type", "application/json")

130
131
132

Call http.SetHeaderField("Authorization", "Bearer " & sApiKey)
http.PreferJSONNavigator
= True

133
134
135

Set jsonNav = http.post(APIURL, sRequestJson)

136
137
138

vResCode
= Split(http.ResponseCode, " ")
'HTTP1.1 200 OKを分解
If vResCode(1) = "200" Then
sContent
= jsonNav.GetElementByPointer("/choices/0/message/content").Value

'設定文書存在チェック

'入力チェック

= Replace(sRequest, """", "\""")
= Replace(sRequest, Chr(9), " ")

'質問の改行を「\n」に変換(改行はエラーになる)

'ダブルコートをエスケープ
'TABを半角スペースに変換

'< for debug>

'Loading ON

'正常終了の場合にレスポンスから回答を抽出し、フィールドに書き込み

139
140
141

Set jsonNavContent

= session.CreateJSONNavigator(sContent)

142
143
144

sNotesQuery
doc.NotesQuery

145
146
147

'matched_fields の取得 (配列内のオブジェクト)
Set jsonArray
= jsonNavContent.GetElementByName("matched_fields").Value
Set jsonElem
= jsonArray.GetFirstElement()

148
149
150

Do Until jsonElem Is Nothing
Set jsonObj = jsonElem.Value

'notes_query の取得 (単純な文字列)
= jsonNavContent.GetElementByName("notes_query").Value
= sNotesQuery

151
152
153

sLabel
sField
sType

= jsonObj.GetElementByName("label").Value
= jsonObj.GetElementByName("field").Value
= jsonObj.GetElementByName("type").Value

154
155
156

doc.MatchedFields
Set jsonElem

= doc.MatchedFields(0) & sLabel & "(" & sField & ") : " & sType & Chr(13)

= jsonArray.GetNextElement()

157
158
159

Loop

160
161
162

Set jsonArray
= jsonNavContent.GetElementByName("assumptions").Value
Set jsonElem
= jsonArray.GetFirstElement()
Do Until jsonElem Is Nothing

163
164

'assumptions の取得 (文字列の配列)

sAssumptions
doc.Assumptions

= jsonElem.Value
= doc.Assumptions(0) & sAssumptions & Chr(13)

54.
[beta]
165
166

Set jsonElem

= jsonArray.GetNextElement()

167
168
169

Loop
End If

170
171
172

doc.IsLoading
= 0
Call uidoc.RefreshHideFormulas

173
174
175

'全文検索を実行

'Loading OFF

176
177
178

FTSearchToFolder(sNotesQuery)

179
180
181

'Msgbox sContent , 64 , "完了"
End Sub

182
183
184

Function GetSettingDocument() As NotesDocument
'---------- ---------- ---------- ---------- ----------

185
186
187

' API設定文書を取得する
' 戻り値:設定文書(NotesDocument)
'---------- ---------- ---------- ---------- ----------

188
189
190

'クラス・変数宣言
Dim session
As New NotesSession
Dim db
As NotesDatabase

191
192
193

Dim settingvw
Dim settingdoc
Dim sSearchKey

194
195
196

'クラス・変数セット
Set db
= session.CurrentDatabase

'回答をフォームに書込み

As NotesView
As NotesDocument
As String

197
198
199
200

Set settingvw

= db.GetView("SysSettingByFormVw")

201
202
203

Set settingdoc

204
205
206

Set GetSettingDocument = settingdoc
End Function

207
208
209

Function FTSearchToFolder(sSearchQuery As String) As Long
'---------- ---------- ---------- ---------- ---------- ----------

'設定文書検索
sSearchKey = "frmApiSetting"
= settingvw.GetDocumentByKey(sSearchKey,True)

'設定文書を返す

210
211
212

'
全文検索を実行し、フォルダに文書を書き込む(書き込む前にフォルダはクリア)
'
'---------- ---------- ---------- ---------- ---------- ----------

213
214
215

'定数宣言
Const FOLDERNAME

216
217
218

'クラス、変数宣言
Dim ws
As New NotesUIWorkspace
Dim session
As New NotesSession

219
220
221

Dim db
Dim vw
Dim dc

As NotesDatabase
As NotesView
As NotesDocumentCollect­
ion

222
223
224

Dim doc

As NotesDocument

225
226
227

Set db
Set vw

228
229
230

' 1. 全文検索インデックスが作成されているか確認
If Not db.IsFTIndexed Then
' インデックスがない場合、必要に応じて作成(サーバー負荷に注意)

231
232
233

= "FTSearchFolder"

'クラス、変数セット
= session.CurrentDatabase
= db.GetView(FOLDERNAME)

' db.CreateFTIndex(0, False)
Msgbox "全文検索インデックスが作成されていないため、検索できません。" , 16 , "エラー"
Exit Function

234
235
236

End If

237
238
239

' 第1引数: クエリ文字列、第2引数: 最大取得件数(0は全件)
Set dc = db.FTSearch(sSearchQuery, 32767)

240
241
242

' 3. 結果の処理
If dc.Count > 0 Then
Call vw.AllEntries.RemoveAllFromFolder(FOLDERNAME)

' 2. FTSearchの実行

'フォルダクリア

243
244
245

Msgbox dc.Count & " 件の文書が見つかりました。" , 64 , "確認"
Print dc.Count & " 件の文書が見つかりました。" ,

246
247

Call dc.

PutAllInFolder(FOLDERNAME)

'フォルダに書込

55.

248 249 Else Msgbox "該当する文書はありませんでした。" 250 251 252 End If 253 254 Call ws.ViewRefresh End Function , 64 , "確認"