2026/11/15

AI 時代だからこそ、プログラミングを楽しむ

AI 時代にプログラマは必要?

最近、AIの進化には本当に驚かされます。

ChatGPT 4o が登場したころ、LotusScript について質問しても、VBA のコードが混ざっているなど、そのままでは実行できないコードが返ってきたりすることがありました。実際に動かせるようになるまで、何度もやり取りを繰り返す必要がありました。

ところが最近では、かなり複雑な処理でも、ほぼ一発で実行できるコードが返ってくるようになりました。

こういう経験を繰り返すうちに、ふと考えてしまいます。

『これからの時代、プログラマの存在意義って何だろう?』

プログラムを書くこと自体が、特別な技術ではなくなっていくのかもしれません。

自動車の普及によって馬車が主要な移動手段ではなくなり、AT の普及によって MT 車が少数派になったように、プログラマという仕事も、いつか今とは違ったものになってしまうのでしょうか。

そんなことを考えると、少し寂しい気もします。そんなことを考えながら、今回はちょっとしたプログラムを作ってみました。

題材は『ナンプレ』です。

そもそも、ナンプレの問題を解くだけなら、今の AI に聞けば一瞬で答えが返ってきます。わざわざプログラムを作る必要などありません。

それでも、あえて LotusScript でナンプレを解くプログラムを作ってみることにしました。


ナンプレソルバーを作ってみた

過去記事『DXL 拡張ライブラリをリリースします!』で紹介した dxlSuite for LotusScript で作成したサンプルアプリに『ナンプレ』があります。

業務に行き詰まったり一息つきたいときに、気分転換で利用しています。ノーツでできているので、その文書を閉じて、別タブに移動すれば仕事をしているように見える優れものです。学生時代、MS-DOS のゲームにあった”ボスが来た機能”(MIFES でコーディングしている画面に瞬時に切り替える機能)のようですね(笑)。

先日、プレイしていたら解けないのではないかと思うステージに出くわしました。問題に入力ミスがあったのかもしれません。アプリストアに公開している手前、”やばい” と思い、ソルバーを制作して全ステージをチェックすることにしました。

動作イメージはこんな感じです。一瞬で解答を得ることができます。


ナンプレを解くための考え方

ナンプレには、3 つのルールがあります。

  • 1 行には、1 ~ 9 の数字が一つずつ入る
  • 1 列には、1 ~ 9 の数字が一つずつ入る
  • 3 × 3 に分割された各ブロックにも、1 ~ 9 の数字が一つずつ入る

人間が解く場合、このルールに沿って、埋められるマスから順に数字を入れていく方法が一般的だと思います。今回はそのような論理的な解法を再現するのではなく、候補を仮置きしながら解答を探す、シンプルな手順で進めたいと思います。

具体的には、次のような手順となります。

このアルゴリズムはバックトラッキングと呼ばれる手法だそうです。実際のナンプレの問題を例に動作を図式化すると次のような感じになります。

このように、候補を仮置きして先に進み、行き詰まったらひとつ前のマスに戻って別の候補を試します。これを繰り返すことで、最終的に解答を見つけます。再帰呼び出しを使うと、この「進む」「戻る」という処理をシンプルに記述できます。


再帰呼び出しを使った実装

再帰呼び出しというのは、必要に応じて自分自身の関数を呼び出すことを言います。

単純な事例として n までの階乗を求める関数は次のようになります。関数内で自分自身を呼び出していることがわかります。

Function xFactorial(n As Integer) As Long
   If n <= 1 Then
      xFactorial = 1
   Else
      xFactorial = n * xFactorial(n - 1)
   End If
End Function

例えば、n = 3 の場合、

  • xFactorial(3) が xFactorial(2) を呼び出す
  • xFactorial(2) が xFactorial(1) を呼び出す
  • xFactorial(1) が1を返す
  • xFactorial(2) が2を返す
  • xFactorial(3) が6を返す

の順で実行されます。呼び出した順とは逆順に答えが返されます。スタックのように後入れ先出し(LIFO:Last In, First Out)で処理が実行されることになります。

重要なのは次の 2 点です。

  •  n <= 1 が終了条件
  • 再帰呼び出しは n - 1 で実行

n は呼び出されるたびに減るので、無限に再帰呼び出しされることはありません。再帰呼び出しを作る際には、永久ループにならないよう注意する必要があります。


ナンプレソルバー

今回のナンプレソルバーはエージェントとして作成しました。Initialize は次の通りです。重要なのは次の 2 点です。

  • ナンプレソルバーのメインルーチンは xNumberPlace_Solver 関数
  • ナンプレの盤面は、9 x 9 の 2 次元配列 aby(行, 列) に格納

Sub Initialize
   Dim nuiw As New NotesUIWorkspace
   Dim nuid As NotesUIDocument
   Dim nd As NotesDocument

   Dim aby(8,8) As Byte     'ナンプレの盤

   Dim v As Variant
   Dim iRow As Integer
   Dim iCol As Integer

   Dim lStep As Long     '実行回数

   '初期化
   Set nuid = nuiw.CurrentDocument
   Set nd = nuid.Document

   '問題を取得して、9 x 9 の配列に展開(初期盤面の整合性チェックは省略)
   v = nd.CaseInput     '問題取得
   For iRow = 0 To 8
      For iCol = 0 To 8
         aby(iRow, iCol) = CByte(Mid(v(iRow), iCol+1, 1))
      Next
   Next

   'ナンプレソルバーの実行
   if Not xNumberPlace_Solver(aby, lStep) Then
      nd.IsError = "1"     '解けない場合
      End If

   '結果出力
   nd.Step = lStep
   nd.Result = xSetResult(aby)
   Call nuid.refresh()
End Sub

なお、今回は初期盤面の整合性チェックや、解が複数存在するかどうかの判定は省略しています。


メインルーチン

xNumberPlace_Solver 関数がナンプレソルバーのメインルーチンです。

引数の rlStep を使って関数の呼び出し回数をカウントするため、関数の先頭でカウンタを加算しています。これにより、ナンプレを解くために発生した関数呼び出し回数(最初の呼び出しを含む)を確認できます。

Private Function xNumberPlace_Solver(raby() As Byte, rlStep As Long) As Boolean
   Dim vCandidate As Variant     '入れられる数字の候補(配列)
   Dim iRow As Integer
   Dim iCol As Integer
   Dim iCan As Integer
   Dim bFound As Boolean

   bFound = True     'すべて埋まっている時用
   rlStep = rlStep + 1

   '行、列の順に空きマスを検索
   For iRow = 0 To 8
      For iCol = 0 To 8
         If raby(iRow, iCol) = 0 Then
            '空いているマスを発見
            bFound = False     '解が見つかったかどうか

            '入れられる数字を配列で取得
            vCandidate = xGetCandidate(raby, iRow, iCol)

            If vCandidate(0) <> 0 Then
               '候補があったので順に調査
               For iCan = 0 To UBound(vCandidate)
                  '候補をいったんセット
                  raby(iRow, iCol) = vCandidate(iCan)

                  '候補をセットした状態で続きを調査
                  bFound = xNumberPlace_Solver(raby, rlStep)
                  If bFound = True Then
                     'マスが埋まった
                     Exit For
                  End If
               Next
            End If

            If bFound = False Then
               '入る数字がなかったので空きマス(0)に戻す
               raby(iRow, iCol) = 0
               GoTo ExitSolver
            End If
         End If
      Next
   Next

ExitSolver:
   xNumberPlace_Solver = bFound
End Function

各マスを行、列の順でループさせ、空きマスを探索します。発見したら、そのマスに入る数字を配列で取得する xGetCandidate 関数をコールします(後述)。

候補の数字があれば、いったんセットして次のマスを埋める処理に入ります。この部分が再帰呼び出しで実現されているということですね(赤字)。

ループ内で bFound が False となるのは、空きマスに埋める数値が見つからなかった場合で、次のマスを検査するために仮にセットした候補を削除する処理となります。この部分がバックトラッキングで、ひとつ前の呼び出しに戻り、次の候補を試す役割を持ちます。


この呼び出し元との関係は LotusScript の実行環境が管理しており、プログラムで考慮する必要がありません。1 回のエージェント実行で、同じ関数がネストして実行されることになりますが、ローカル変数はそれぞれで管理され、値が混ざることはありません(連携したいものは引数と戻り値を利用)。


その他必要な関数

今回の主題の再帰呼び出しとは直接的に関係がないのですが、ナンプレソルバーの実行に必要な関数を紹介します。


◇ xGetCandidate

盤面を調査して、引数で指定したマスに入れられる数字を探し出し、配列で返す関数です。

%REM
入れられる数字を配列で返す。
ない場合は要素 0 が 0.
%END REM

Private Function xGetCandidate(raby() As Byte, ByVal viRow As Integer, ByVal viCol As Integer) As Variant
   Dim abyCandidate() As Byte
   Dim i As Integer
   Dim iRow As Integer
   Dim iCol As Integer
   Dim iNum As Integer
   Dim bFound As Boolean

   ReDim abyCandidate(i)

   For iNum = 1 To 9
      bFound = False

      '同じ列で同じ番号があるか?
      For iRow = 0 To 8
         If raby(iRow, viCol) = iNum Then
            bFound = True
            Exit For
         End If
      Next

      '同じ行で同じ番号があるか?
      If bFound = False Then
         For iCol = 0 To 8
            If raby(viRow, iCol) = iNum Then
               bFound = True
               Exit For
            End If
         Next
      End If

      '3 x 3 に同じ番号があるか?
      If bFound = False Then
         For iRow = Int(viRow / 3) * 3 To Int(viRow / 3) * 3 + 2
            For iCol = Int(viCol / 3) * 3 To Int(viCol / 3) * 3 + 2
               If raby(iRow, iCol) = iNum Then
                  bFound = True
                  Exit For
               End If
            Next
            If bFound = True Then Exit For
         Next
      End If

      '判定
      If bFound = False Then
         '見つからなかったので戻り値に追加
         ReDim Preserve abyCandidate(i)
         abyCandidate(i) = iNum
         i = i + 1
      End If
   Next

   xGetCandidate = abyCandidate
End Function


◇ xSetResult

この関数は処理が終わったナンプレの配列をフィールドにセットするための文字列配列に変換する関数です。

Function xSetResult(vaby() As Byte) As Variant
   Dim iRow As Integer
   Dim iCol As Integer
   Dim asLine(8) As String

   For iRow = 0 To 8
      For iCol = 0 To 8
         asLine(iRow) = asLine(iRow) & CStr(vaby(iRow, iCol))
      Next
   Next

   xSetResult = asLine
End Function


再帰呼び出しのメリットと注意点

今回は再帰呼び出しを紹介しました。最後に再帰呼び出しの特徴を整理します。

メリット 注意点
プログラムをシンプルにできる 呼び出し回数が増える
階層的な処理を表現しやすい スタックを消費する
戻る処理を実行環境に任せてシンプルに記述できる 終了条件がないと無限に再帰呼び出しされる
処理の考え方が直感的になる 深すぎる再帰はスタックオーバーフローの原因になる


AI 時代だからこそ、プログラミングを楽しむ

AI がコードを書いてくれる時代になりました。これからは、プログラムを作ること自体の価値も変わっていくのかもしれません。

今回作成したナンプレソルバーも、AI に聞けば一瞬で答えが出ます。わざわざプログラムを作って解く必要はありません。効率だけを考えれば、まったく意味のないことかもしれません。

それでも、自分でアルゴリズムを考え、それをプログラムとして形にして、思い通りに動いたときの楽しさは変わらないと思います。今回も、ナンプレを解く仕組みを考えたり、再帰呼び出しを使ってシンプルに実装できたりしたことが、何より面白いところでした。

乗馬や MT 車の運転も、移動手段としての効率だけを考えれば、もっと便利な選択肢はいくらでもあります。それでも、そこには効率とは別の楽しさや価値があります。

プログラミングも、いつかそんな存在になっていくのかもしれません。


……などと、少し格好よくまとめてみましたが、そもそもナンプレをプログラムで解こうとすること自体、邪道なのかもしれませんね(笑)。


2026/10/24

Domino IQ: ACL や文書のアクセス権、文書の削除と RAG

Domino IQ の RAG を実際に運用するにあたって、気になる点を順に整理します。


アクセス権と RAG

Domino IQ の RAG サポートでは、対象の文書を事前にベクトル化し、Qdrant に保存します。ベクトル化は Domino サーバ上で行われるため、サーバ側でアクセス可能な文書がベクトル化の対象となります。

では、RAG の対象 DB にアクセス権のないユーザが Domino IQ を利用した場合、どのように動作するのでしょうか?

今回も、これまでと同じ評価環境を利用して検証を進めます。

RAG には dxlSuite のヘルプをベクトル化させてますが、この DB に対する ACL を一時的に ”なし” に変更します。

この状態で RAG を有効にして質問すると ”I don't know.” となりました。DB にアクセス権限がないため、RAG から有効な情報が取得できず、このような回答になったと考えられます。


続いて、ACL を ”投稿者” に変更して同じ質問をします。

結果は、こちらも ”I don't know.” でした。


ただし、詳細ログを比較すると、両者の処理には違いがあることがわかりました。RAG サポートの詳細ログは、処理ごとに次の3種類に分かれています。

処理 ACL なし ACL 投稿者 詳細ログファイル名
質問のベクトル化 詳細ログなし あり domiqembthr・・・.log
RAG 問い合わせ 詳細ログなし あり domiqvecthr・・・.log
LLM 回答を生成 あり あり domiqllmthr・・・.log

DominoIQ を利用するには、LLM Command 文書を指定します。この文書内には RAG の対象となる DB が指定されています

ACL が ”なし” の場合、ユーザは対象 DB にアクセスできません。そのため質問のベクトル化や RAG の問い合わせを行わず、LLM による回答生成だけが行われた可能性があります。

実際、domiqllmthr の詳細ログを確認すると、LLM に送信されたプロンプトには、RAG のコンテキストが 空 の状態で含まれていました。

DATA-OUT: {
   "model": "Qwen/Qwen3-8B-GGUF",
   "messages": [
      {
         "role": "system",
         "content": "あなたは LotusScript と dxlSuiteの専門家です。
            初心者にも分かりやすく説明してください。
            回答は日本語で行ってください。"
      },
      {
         "role": "user",
         "content": "Use the following context to answer the user query:
            <context>

             </context>

            dxlSuite でイメージを使用する機能について教えてください。
            If the context does not help answer the question, respond with 'I don't know.'
            Do not make up an answer."
      }
   ],
   "max_tokens": 512,
   "temperature": 0.7
}


参照権限と RAG

次の ACL が ”投稿者” の場合を詳しく見ていきましょう。

”投稿者” では、DB にはアクセスできても、文書が参照できません。このとき、RAG はどのように動作するのでしょうか? 順を追って確認しましょう。

まず、Qdrant への問い合わせを記録した domiqvecthr 詳細ログを確認します。レスポンスの DATA-IN には検索に合致したことが記録されていました。

DATA-IN: {
   "result": {
      "points": [
         {
            "id": "5e4e53ba-94a2-4eb6-9b7d-05edef9563f5",
            "version": 516,
            "score": 0.80133533,
            "payload": {
               "type": 0,
               "noteId": 10294,
               "itemName": "Title",
               "dupItemID": 0,
               "itemSequence": 1,
               "itemOrder": 0,
               "itemOffsets": [
                  {
                     "offset": 0,
                     "length": 30
                  }
               ],
               "chunkLength": 32
            }
         },
         {
            "id": "b15176cf-466c-4c7c-99bd-32102d1c2568",
            "version": 537,
            "score": 0.77039313,
            "payload": {
               "type": 0,
               "noteId": 10310,
               "itemName": "Title",
               "dupItemID": 0,
               "itemSequence": 1,

このように、Qdrant は検索結果を返しています。つまり、少なくとも今回の検証では、参照権限のない文書に対応するベクトルも検索結果に含まれていました。


ところが、LLM 回答を生成を記録した domiqllmthr ログを確認すると、ACL が ”なし” の場合と同様に、<context> ~ </context> の間は 空 になっていました。文書が参照できないため、コンテキストに内容が含まれなかったと考えられます。

その理由は詳細ログだけではわかりませんでしたが、Domino サーバのコンソールログに手掛かりがありました。

[5ABC:0095-4910] 2026/10/24 14:55:44.24 GenerateContext> Warning: No access to note 28DE while generating context. Skipping.
[5ABC:0095-4910] 2026/10/24 14:55:44.24 GenerateContext> Warning: No access to note 2856 while generating context. Skipping.
[5ABC:0095-4910] 2026/10/24 14:55:44.24 GenerateContext> Warning: No access to note 28A6 while generating context. Skipping.
[5ABC:0095-4910] 2026/10/24 14:55:44.24 GenerateContext> Warning: No access to note 2846 while generating context. Skipping.
[5ABC:0095-4910] 2026/10/24 14:55:44.24 GenerateContext> Warning: No access to note 2836 while generating context. Skipping.
[5ABC:0095-4910] 2026/10/24 14:55:44.24 GenerateContext> Warning: No access to note 284A while generating context. Skipping.
[5ABC:0095-4910] 2026/10/24 14:55:44.24 GenerateContext> Warning: No access to note 285A while generating context. Skipping.
[5ABC:0095-4910] 2026/10/24 14:55:44.24 GenerateContext> Warning: No access to note 28D2 while generating context. Skipping.
[5ABC:0095-4910] 2026/10/24 14:55:44.24 GenerateContext> Warning: No access to note 28B2 while generating context. Skipping.

この警告は、コンテキストを生成する際に対象文書へのアクセス権がないことを検出し、その文書をスキップしたことを示しています。

Note ID は詳細ログでは 10 進数、コンソールログでは 16 進数で表示されているため、そのままでは比較しづらいのですが、Qdrant の検索結果に含まれていた文書と一致しました。


この検証結果から、Domino IQ は次のように動作していると考えられます。

  • Qdrantは、参照権限のない文書に対応するベクトルも検索結果として返す
  • Domino IQは、検索結果をもとにコンテキストを生成する段階で、元の Notes 文書へのアクセスを試みる
  • アクセスできない文書はスキップされる
  • 今回は取得できる文書がなく、LLM に渡された CONTEXT が空になった

つまり、今回の検証では、Qdrant の検索結果をそのまま LLM に渡すのではなく、コンテキストを生成する段階で文書へのアクセス権が確認されていることがわかりました。


文書の削除と RAG

続いて、文書が削除された場合の挙動に関して確認します。

RAG の対象 DB からすべての文書を削除し、同じ質問を DominoIQ に投げてみました。結果は ACL が ”投稿者” の場合と同じく ”I don't know.” でした。しかし、コンソールログには、先ほどと異なる警告が記録されていました。

[5ABC:0094-3394] 2026/10/24 15:18:13.61 GenerateContext> Warning: Note 28DE was deleted since the search was run. Skipping.

先ほどの警告は ”No access to note” と記録されていましたが、今回は ”was deleted” となっています。削除されたことを検出し、スキップしたことがわかります。

また、警告メッセージには ”since the search was run” と表現されています。対象の文書をベクトル化してコンテキストの作成までの間に削除されたこと示しています。


この結果から、Qdrant の検索結果に含まれた文書が、コンテキスト生成時にはすでに削除されていた場合でも、Domino IQ がその文書をスキップすることを確認できました。


まとめ

今回は、Domino IQ の RAG サポートについて、データベースや文書のアクセス権、そして文書が削除された場合の挙動を検証しました。

今回の実験では、次のことが確認できました。

  • Domino IQ はコンテキストを生成する段階で文書へのアクセス権を確認し、アクセスできない文書をスキップする
  • 文書が削除された場合も、コンテキスト生成時に削除を検出してスキップする
  • アクセス権のない DB には質問のベクトル化や Qdrant の検索など不要な処理処理はスキップしていそう

少し話はそれますが、Domino のドメイン検索では、索引の更新タイミングによって、削除した文書が検索結果に現れることがあります。

今回の Domino IQ の検証では、少なくともコンテキスト生成時に、文書へのアクセス権や削除の状態が確認されていることがわかりました。この仕組みは、RAG を運用するうえで重要なポイントといえそうです。


2026/10/19

Domino IQ: 回答が最後まで生成されないときの原因と設定調整

DominoIQ のセットアップが完了し、使い始めたときに最初に体験すると思われるトラブルについて紹介します。その現象は、DominoIQ の返答が最後まで生成されず、途切れるというものです。

なお、この現象は LLM が回答を生成する際に発生するため、RAG サポートの有無にかかわらず起こり得ます。


LotusScript と DominoIQ

以前紹介した評価用サンプル から DominoIQ に問い合わせを行う部分を抜粋すると次の通りです。NotesLLMRequest クラスが問い合わせを担当し、Completion メソッドで問い合わせを実行します。その戻り値は NotesLLMResponse のオブジェクトで Content プロパティから返答(テキスト)を取得できます。

Sub Click(Source As Button)
          ・・・
   Dim llmreq As NotesLLMRequest
   Dim llmres As NotesLLMResponse
          ・・・
   sText = nd.Prompt(0)    '質問文の取得
   sCmd = nd.IQCommand(0)    'コマンドの取得

   'DominoIQ へ問い合わせ
   Set llmreq = ns.CreateLLMRequest()
   Set llmres = llmreq.Completion(ns.CurrentDatabase.Server, sCmd, sText)

   'DominoIQ の返答を記録
   nd.Results = llmres.Content
   nd.FinishReason = llmres.FinishReason
End Sub

今回紹介したいのは FinishReason プロパティです。

ヘルプによるとプロパティの値は『The reason the language model stopped generating a response.』となっています。翻訳すると「言語モデルが応答の生成を停止した理由」となります。エラーの原因を示すプロパティのようにも読めますが、実際には、正常終了した場合や、生成トークン数の上限に達した場合など、生成が終了した理由を表します。

このプロパティが取り得る値は次のとおりです。1 が返された場合は、LLM がテキストの生成を完了(正常終了)したことを示します。

値 定数 説明
0 LLM_FINISH_REASON_NONE エラーが発生したことを示します
1 LLM_FINISH_REASON_STOP 言語モデルがテキストの生成を完了したことを示します
2 LLM_FINISH_REASON_LENGTH トークンの上限に達したことを示します
3 LLM_FINISH_REASON_FUNCTION_CALL 現在サポートされていません

今回取り上げている「回答が最後まで生成されない」現象では、戻り値が 2 になるということですね。


トークン数の確認

上記プロパティからわかるのは、トークン数の上限に達したことまでです。これだけでは原因を詳しく調べるには情報が足りず判断しかねますよね。

こんな時に頼りになるのが LLM 問い合わせの詳細ログ domiqllmthr・・・.log となります。

DATA-IN の値からは、DominoIQ からの返答を詳しく確認できます。content の部分が回答になりますが、最後が「3.開発支援」「Lotus」で終わっていることが確認できます(青字)。冒頭に掲載した画像の状況と一致しています。

{
  "choices": [
    {
      "finish_reason": "length",
      "index": 0,
      "message": {
        "role": "assistant",
        "content": "はい、**dxlSuite** は IBM Domino(以前はLotus Domino)の開発者向けツールで、データベース(NSFファイル)を DXL(Domino XML)形式に変換したり、逆にDXLからNSFに復元したりするのに便利です。初心者にもわかりやすく説明します。\n\n---\n\n### **dxlSuite とは?**\ndxlSuite は、IBM Domino データベース(NSFファイル)と DXL(Domino eXtensible Markup Language)の間でデータをやりとりするためのツールです。 \nDXLは、XML形式のデータ表現で、NSFファイルを「テキストとして扱える」形式です。\n\n---\n\n### **主な用途**\n1. **データのバックアップや復元** \n - NSFファイルをDXLに変換して、他の場所に保存したり、復元したりできます。\n - データの移行や、開発環境と本番環境の同期に役立ちます。\n\n2. **データの抽出・編集** \n - DXLでデータを編集(例:レコードの追加・削除)後、NSFに戻して利用できます。\n - データの検証や、特定の項目の抽出にも便利です。\n\n3. **開発支援** \n - Lotus",
        "reasoning_content": "Okay, the user is asking if I know about dxlSuite. Let me start by confirming that I do.
                        ・・・
 Alright, that should cover the basics without overwhelming the user.\n"
      }
    }
  ],
  "created": 1792030923,
  "model": "Qwen3-8B-Q5_K_M.gguf",
  "system_fingerprint": "b8691-0e950cfc9",
  "object": "chat.completion",
  "usage": {
    "completion_tokens": 512,
    "prompt_tokens": 55,
    "total_tokens": 567,
    "prompt_tokens_details": {
      "cached_tokens": 45
    }
  },
            ・・・

このログで確認すべき項目は、finish_reason と completion_tokens です(赤字)。

finish_reason が "length" の場合、生成トークン数の上限に達して回答の生成が終了したことを示します。今回の completion_tokens は 512 となっており、この値で生成されたトークン数が上限に達していることがわかります。

ちなみに、正常終了したときの finish_reason は stop となります。

      "finish_reason":"stop",

先ほど紹介した NotesLLMResponse クラスの FinishReason プロパティで正常終了の定数名が LLM_FINISH_REASON_STOP と命名されている理由はこのためですね。


トークン数の上限

では、なぜ 512 で上限を超えたと判断されたのでしょうか?

その理由も domiqllmthr ログを見ればわかります。今度は、LLM に対するリクエスト部分である DATA-OUT を確認します。すると、すぐにわかりますね。max_tokens が 512 に設定されているためです。

{
   "model": "Qwen/Qwen3-8B-GGUF",
   "messages": [
      {
         "role": "system",
         "content":
             "あなたは LotusScript と dxlSuiteの専門家です。
             初心者にも分かりやすく説明してください。
             回答は日本語で行ってください。"
      },
      {
         "role": "user",
         "content":"dxlSuite を知っていますか?"
       }
   ],
   "max_tokens": 512,
   "temperature": 0.7
}

この 512 という値は、Domino IQ Configuration DB の LLM Command 文書に設定されています。DominoIQ を設定した作業を思い出した方は気が付いたかもしれませんね。


上限の変更とまとめ

トークン数の上限を変更するには、LLM Command 文書の Maximum tokens の値を変更します。

試しに 1024 や 2048 に設定してみましたが、いずれも生成トークン数の上限に達してしまいました。4096 に設定したところ、finish_reason が "stop" となり、回答の文章が最後まで出力されました。

今回の質問があいまいだったことも影響しているのかもしれませんが、どうやら今回利用している LLM は、なかなかのおしゃべり好きのようですね...。

もちろん、生成するトークン数が増えれば、回答の生成に時間がかかる可能性があります。長い回答が必要な場合には上限を大きくする必要がありますが、ユーザがいつまでも回答を待つことになっては、システムとして使い勝手がよくありません。

DominoIQ を導入したマシンのスペックやユーザの利用頻度、想定する回答の長さなどを考慮して、適切な値を設定する必要がありそうです。

また、LotusScript から DominoIQ に問い合わせる場合は、NotesLLMResponse クラスの FinishReason プロパティを確認することで、回答の生成が正常に終了したかどうかを判定できます。LLM_FINISH_REASON_LENGTH が返された場合に、生成途中の回答をそのまま表示するのか、質問を具体的にするようユーザに促すのかなど、アプリケーション側でも仕様を決めておくとよさそうですね。


2026/10/17

Domino IQ: RAG サポートの挙動確認 ②

前回は RAG を有効にした場合の動作をコンソールログと処理フェーズごとの詳細ログを使って、挙動を確認しました。今回は、RAG を使用しない場合のログを確認し、RAG の有効/無効の違いを整理します。

今回は、私が作成したライブラリ『dxlSuite』のヘルプを RAG で検索に使用しない状態にして、『dxlSuite を知っていますか?』と、前回の RAG 有効時と同じ質問します。無効の状態では、一般的な DXL の説明が、あたかも dxlSuite の機能のように返答されました。

この操作を行った際のログを確認し、RAG 有効時のログと比較してみましょう。


RAG 無効時のログ

まずは、RAG 無効時に出力されるログを確認します。

コンソールログには、以下の通り、リクエストの開始時間から返答までが、出力されていました。前回紹介した LLM の回答を生成するログと同様ですね。

[5B00:0094-3D4C] 2026/10/17 09:39:41 DominoIQ: PerformRequest() LLM Request Start time: 2026/10/17 09:39:40
2026/10/17 09:39:53.73 InvokeLLM> PerformRequest() returned (200): NotesError : , Error = , ErrorOperation =
[5B00:0094-3D4C] 2026/10/17 09:39:53 DominoIQ: PerformRequest() LLM Request End time: 2026/10/17 09:39:53
[5B00:0094-3D4C] 2026/10/17 09:39:53 DominoIQ: PerformRequest() LLM Request Duration: 12 secs
[5B00:0094-3D4C] 2026/10/17 09:39:53 DominoIQ: PerformRequest() returned Headers : HTTP/1.1 200 OK
Server: llama.cpp
Access-Control-Allow-Origin:
Content-Type: application/json; charset=utf-8
Content-Length: 2886
Keep-Alive: timeout=5, max=100
[5B00:0094-3D4C] 2026/10/17 09:39:53 DominoIQ: PerformRequest() returned Data : {"choices":[{"finish_reason":"length","index":0,"message":{"role":"assistant","content":"はい、dxlSuiteはIBM Domino(旧Lotus Notes)データベースとXML(DXL)データをやり取りするためのツールです。初心者でも理解しやすいように説明します。\n\n---\n\n### **dxlSuiteとは?**\ndxlSuiteは、**Dominoデータベース(ノートベース)**と**XML形式(DXL)**のデータを相互に


◇ 詳細ログ

RAG 無効時のリクエストでは、詳細ログとして、domiqllmthr で始まるログファイルが 1 つだけ出力されていました。コンソールログとも合致するので、RAG 無効時は DominoIQ へのリクエストは 1 回だけとなるようです。

詳細ログファイルの構成は RAG 有効時と同じです。ログの内容については、RAG 有効時との比較を通じて確認するので、ここでは割愛します。


ところで RAG 無効時のコンソールログで気になったことがあります。上記ログの直前に次のように出力されていました。

[5B00:0094-3D4C] 2026/10/17 09:39:40.77 loadDWORDItem> Unable to get value for item command_rag_max_rsp
[5B00:0094-3D4C] 2026/10/17 09:39:40.77 loadNUMBERItem> Unable to get value for item command_rag_score_threshold
[5B00:0094-3D4C] 2026/10/17 09:39:40.77 ValidateCache> Reload View. Clearing cache.
[5B00:0094-3D4C] 2026/10/17 09:39:40.79 ValidateCache> View has not changed. Using cached data.
[5B00:0094-3D4C] CacheList entry key Qwen/Qwen3-8B-GGUF
[5B00:0094-3D4C] CacheList entry key bge-m3-Q6_K
[5B00:0094-3D4C] 2026/10/17 09:39:40.79 getModel> Returning cached model bge-m3-Q6_K
[5B00:0094-3D4C] 2026/10/17 09:39:40.79 ValidateCache> View has not changed. Using cached data.
[5B00:0094-3D4C] CacheList entry key bge-m3-Q6_K
[5B00:0094-3D4C] CacheList entry key Qwen/Qwen3-8B-GGUF
[5B00:0094-3D4C] 2026/10/17 09:39:40.79 getModel> Returning cached model Qwen/Qwen3-8B-GGUF
[5B00:0094-3D4C] 2026/10/17 09:39:40.79 InvokeLLM> account URL: http://localhost:8080/v1/chat/completions
[5B00:0094-3D4C] 2026/10/17 09:39:40.79 InvokeLLM> User input paraphrasing disabled.

まず、RAG 無効時にもかかわらず、RAG に関連するメッセージやモデルの情報が出力されていました(紫字)。

DominoIQ 実行時のコマンドは "dxlSuiteHelp_NoRAG" です(下図)。ログの最初の2行は、RAG 関連の設定値を取得できなかったことを示しているようです。今回のコマンドでは、これらの項目を設定していないため、その影響かもしれません。

また、Embedding Model の名前である bge-m3-Q6_K も出力されています。RAG の有効・無効にかかわらず同じ LLM Command 文書を利用しているため、RAG に関連するモデル情報の参照処理が行われているのかもしれません。ただし、質問のベクトル化を行ったときに出力される domiqembthr・・・.log は出力されていないことからも、質問のベクトル化が実行されたとは判断できません。


また、前々回 に記載した通り、今回の評価環境ではパラフレーズを無効化しています。そのメッセージ『User input paraphrasing disabled.』も出力されていますね(青字)。


ログの比較

RAG が無効の場合、質問のベクトル化と RAG への問い合わせは不要になります。今回のログでも、これらの処理に対応する詳細ログは確認できませんでした。

一方、LLM による回答生成のログは、RAG の有効・無効にかかわらず出力されています。

処理とログの出力を整理すると、次の表のようになります。

処理 RAG 有効 RAG 無効 詳細ログファイル名
質問のベクトル化 あり なし domiqembthr・・・.log
RAG 問い合わせ あり なし domiqvecthr・・・.log
LLM 回答を生成 あり あり domiqllmthr・・・.log

ログ比較のポイントは、LLM に回答を生成させる際の詳細ログにあります。それぞれの DATA-OUT に出力されている JSON を整形して左右に並べると、違いが一目瞭然です。

今回比較したリクエストでは、system の内容やその他のパラメータは同じで、user の content に設定された内容が異なっています。

RAG 有効 RAG 無効
{
   "model": "Qwen/Qwen3-8B-GGUF",
   "messages": [
      {
         "role": "system",
         "content":
             "あなたは LotusScript と dxlSuiteの専門家です。
             初心者にも分かりやすく説明してください。
             回答は日本語で行ってください。"

      },
      {
         "role": "user",
         "content":"Use the following context
                                           to answer the user query:

         <CONTEXT>
            dxlSuite を使ってみよう
            dxlSuite for LotusScript とは?
            dxlSuite の構成と利用条件
            dxlSuite は Script 操作に対応
            既存の文書を dxlSuite でアクセスします。
            dxlSuite とは? dxlSuite for ・・・
            既存の文書を dxlSuite で更新します。
            dxlSuite と NotesDocument の連携
            dxlSuite の構成や使い方、利用条件や・・・
          </CONTEXT>

          dxlSuite を知っていますか?
          If the context does not help answer the question,
                      respond with 'I don't know.'
                      Do not make up an answer."

      }
   ],
   "max_tokens": 512,
   "temperature": 0.7
}
{
   "model": "Qwen/Qwen3-8B-GGUF",
   "messages": [
      {
         "role": "system",
         "content":
             "あなたは LotusScript と dxlSuiteの専門家です。
             初心者にも分かりやすく説明してください。
             回答は日本語で行ってください。"
      },
      {
         "role": "user",
         "content":












            "dxlSuite を知っていますか?"



       }
   ],
   "max_tokens": 512,
   "temperature": 0.7
}

最初に、DominoIQ は『Use the following context to answer the user query:』という固定の文字列を挿入しています(青字)。これは「次のコンテキストを使用して、ユーザからの質問に答えてください。」という指示となります。

続いて、RAG の検索結果が <CONTEXT> と </CONTEXT> で囲まれた範囲に列挙されています(黄字)。このように、質問への回答に利用する検索結果を、質問文とは区別して LLM に渡していることがわかります。

その後に、LotusScript から渡された質問文が続きます。

この構成により、LLM は質問文だけでなく、RAG の検索結果も参照して回答を生成するよう指示されているわけですね。


最後の 紫字 の部分は、次の2点を指示しています。

  • コンテキストが質問への回答に役立たない場合、「I don't know.」と回答
  • 回答を捏造しない

この指示は、DominoIQ が LLM へのプロンプトに付加しているものです。RAG の検索結果が回答の根拠にならない場合に、LLM が推測で回答を生成することを抑制する意図があると考えられます。


まとめ

今回は、RAG の有効/無効によって変化するログから DominoIQ の挙動を確認しました。

RAG を無効にした状態では、LLM による回答生成は行われる一方、今回のログでは質問のベクトル化や RAG への問い合わせに対応する処理は確認できませんでした。

また、LLM に送信するリクエストを比較すると、RAG 有効時には検索結果や追加の指示が user の content に含まれ、RAG 無効時には質問文だけが含まれていました。この違いから、既存の LLM による回答生成の仕組みに、RAG の検索結果を利用するための指示やコンテキストを追加していることがわかります。

今回のログを見る限り、RAG サポートは、Domino 14.5 で対応した DominoIQ の回答生成の仕組み(RAG 無効の機能)を活かしながら、RAG の検索結果や追加の指示をプロンプトに加える形で実現されているようです。既存機能を活用しながら機能を強化するという、Notes/Domino らしい実現方法だと感じました。

ところで、RAG を有効にして、ちんぷんかんぷんな質問をすると、突然 'I don't know.' と英語で回答することがあります。その動作に違和感を感じていたのですが、今回確認したログから、DominoIQ がこの指示を自動的に付加していることがわかりました。

DominoIQ を簡単に利用できるようにとの配慮だとは思うのですが、もう少し細かな制御できるようにしてほしいところですね。

”何でここだけ英語やねん!”っとツッコミを入れたくなります...


2026/10/16

Domino IQ: RAG サポートの挙動確認

今回は DominoIQ の RAG サポートがどのように動作しているのか、ログベースで確認します。前回 紹介した下図の問い合わせを例に使って、確認を進めます。

なお、評価に使用した環境は、前回 紹介した通り、デバッグパラメータを有効にしております。ご了承ください。


RAG サポートの挙動とログの種類

まず、RAG を有効にした場合の DominoIQ の挙動は次の通りとなります。

  1. 質問のベクトル化
  2. RAG 問い合わせ
  3. LLM 回答を生成

この流れに合わせて、コンソールログに処理状況が記録されます。

さらに、詳細なログが別ファイルにも出力されます。ログの最初に接続情報が記録されています。そのポート番号は、Domino IQ Configuration DB で設定したポートと合致しますので、どの処理に対応するログか判断できます。

TEXT: Connected to localhost (127.0.0.1) port 8080

各処理と詳細ログファイル名は次のような関係になっています。

port 処理 詳細ログファイル名
8082 質問のベクトル化 domiqembthr・・・.log
6333 RAG 問い合わせ domiqvecthr・・・.log
8080 LLM 回答を生成 domiqllmthr・・・.log

それでは、各フェーズごとのログを確認していきましょう。


1. 質問のベクトル化

ベクトル化は Embedding Model が担当します。質問を送信すると、下記のようなベクトル化を行っているログが出力されています。このログには、ベクトル化したモデル名称やベクトル化した結果が含まれていることがわかります。

[4BE4:0094-47E4] 2026/10/15 11:20:38   DominoIQ: PerformRequest() LLM Embedding Request Start time: 2026/10/15 11:20:37  
[4BE4:0094-47E4] 2026/10/15 11:20:38 DominoIQ: PerformRequest() LLM Embedding Request End time: 2026/10/15 11:20:37
[4BE4:0094-47E4] 2026/10/15 11:20:38 DominoIQ: PerformRequest() LLM Embedding Request Duration: 0 secs
[4BE4:0094-47E4] 2026/10/15 11:20:38 DominoIQ: PerformRequest() returned Headers : HTTP/1.1 200 OK
Server: llama.cpp
Access-Control-Allow-Origin:
Content-Type: application/json; charset=utf-8
Content-Length: 21873
Keep-Alive: timeout=5, max=100
[4BE4:0094-47E4] 2026/10/15 11:20:38 DominoIQ: PerformRequest() returned Data : {"model":"bge-m3-Q6_K",
"object":"list","usage":{"prompt_tokens":11,"total_tokens":11},"data":[
{"embedding":[-0.07655056565999985,0.00689056096598506,-0.0036124647594988346,-0.040055856108665466,・・・


◇ 詳細ログ

上記コンソールログだけでは、どのような質問文をベクトル化したか、詳細は確認できませんが、詳細ログ domiqembthr を参照すればより詳しく理解できます。

まず DATA-OUT にはベクトル化する質問文が表示されています。

DATA-OUT:{"input":"dxlSuite を知っていますか?","model":"bge-m3-Q6_K","encoding_format":"float"}

DATA-IN では、ベクトル化の結果を確認できます。embedding には、質問文を数値の配列に変換したベクトルが格納されています。

DATA-IN: {
   "model": "bge-m3-Q6_K",
   "object": "list",
   "usage": {
      "prompt_tokens": 11,
      "total_tokens": 11
   },
   "data": [{
      "embedding": [-0.07655056565999985,0.00689056096598506,-0.0036124647594988346, ・・・],
      "index": 0,
      "object": "embedding"
   }]
}

今回使用した Embedding Model「bge-m3-Q6_K」のベクトルは 1024 次元です。そのため、embedding には 1024 個の数値が並んでいると考えられます(実際に数えたわけではありません)。

数値を眺めるだけでは、質問文の意味を読み取ることはできませんね...。


2. RAG 問い合わせ

質問がベクトル化できたので、それを使って事前にベクトル化した DB を検索します。Qdrant に対してベクトル検索するということですね。

コンソールログでは、次の部分が該当します。returned Data に検索結果が出力されていますが、ログが途中で途切れており、内容をすべて確認することはできません。

[4BE4:0094-47E4] 2026/10/15 11:20:38 DominoIQ: PerformRequest() LLM Embedding Request Start time: 2026/10/15 11:20:37
[4BE4:0094-47E4] 2026/10/15 11:20:38 DominoIQ: PerformRequest() LLM Embedding Request End time: 2026/10/15 11:20:37
[4BE4:0094-47E4] 2026/10/15 11:20:38 DominoIQ: PerformRequest() LLM Embedding Request Duration: 0 secs
[4BE4:0094-47E4] 2026/10/15 11:20:38 DominoIQ: PerformRequest() returned Headers : HTTP/1.1 200 OK
content-length: 2554
content-type: application/json
vary: Origin, Access-Control-Request-Method, Access-Control-Request-Headers
date: Thu, 15 Oct 2026 02:20:37 GMT
[4BE4:0094-47E4] 2026/10/15 11:20:38 DominoIQ: PerformRequest() returned Data : {"result":{"points":[{"id":"b3ac9561-78f3-4a8d-a380-47babdf791c6","version":3,"score":0.8422835,"payload":{"type":0,"noteId":6586,"itemName":"Title","dupItemID":0,"itemSequence":1,"itemOrder":0,"itemOffsets":[{"offset":0,"length":30}],"chunkLength":32}},{"id":"5941da2d-8667-4dd2-a156-5c8c19485180","version":242,"score":0.8189756,"payload":{"type":0,"noteId":6778,"itemName":"Note","dupItemID":0,"itemSequence":1,"itemOrder":0,"itemOffsets"


◇ 詳細ログ

このタイミングで出力される詳細ログ(domiqvecthr で始まるログ)には処理結果がすべて含まれています。コンソールログで途切れていた returned Data のJSON は DATA-IN という項目に出力されています。

points 配列に、Qdrant の検索結果が格納されています。今回の検証では、配列に 9 件の検索結果が含まれていました。

各要素を見ると、score があり、類似度を表すと想定できます。さらに詳細を確認すると、文書ID(noteid)やフィールド名(itemName)が発見できます。

{
   "result": {
      "points": [
         {
            "id": "b3ac9561-78f3-4a8d-a380-47babdf791c6",
            "version": 3,
            "score": 0.8422835,
            "payload": {
               "type": 0,
               "noteId": 6586,
               "itemName": "Title",
               "dupItemID": 0,
               "itemSequence": 1,
               "itemOrder": 0,
               "itemOffsets": [
                  {
                     "offset": 0,
                     "length": 30
                  }
               ],
               "chunkLength": 32
            }
         },
         {
            "id": "5941da2d-8667-4dd2-a156-5c8c19485180",
            "version": 242,
                     ・・・

特に興味深いのは、検索結果に noteId や itemName だけでなく、itemOffsets という位置情報も含まれていることです。

例えば、noteId: 6778、itemName: "Body" のデータでは、itemOffsets に複数の offset と length の組み合わせが記録されています。

つまり、Qdrant の検索結果には、類似度の高いデータを示す情報だけでなく、元の Notes 文書のどのフィールドに関連するデータなのか、さらにそのフィールド内の位置に関する情報も保持されているようです。

                     ・・・
          {
            "id": "6109e49c-6792-4965-9002-2838ca46de65",
            "version": 240,
            "score": 0.74068475,
            "payload": {
               "type": 0,
               "noteId": 6778,
               "itemName": "Body",
               "dupItemID": 0,
               "itemSequence": 1,
               "itemOrder": 0,
               "itemOffsets": [
                  {
                     "offset": 102,
                     "length": 18
                  },
                  {
                     "offset": 134,
                     "length": 196
                  },

                                    ・・・
                  {
                     "offset": 1798,
                     "length": 36
                  },
                  {
                     "offset": 1848,
                     "length": 117
                  }
               ],

               "chunkLength": 1292
            }
         },
                     ・・・


3. LLM 回答を生成

最後に、Qdrant の検索結果を利用して LLM に回答を生成させます。コンソールログでは以下の部分が該当します。

[4BE4:0094-47E4] 2026/10/15 11:20:38 DominoIQ: PerformRequest() LLM Request Start time: 2026/10/15 11:20:37
2026/10/15 11:20:49.39 InvokeLLM> PerformRequest() returned (200): NotesError : , Error = , ErrorOperation =
[4BE4:0094-47E4] 2026/10/15 11:20:49 DominoIQ: PerformRequest() LLM Request End time: 2026/10/15 11:20:49
[4BE4:0094-47E4] 2026/10/15 11:20:49 DominoIQ: PerformRequest() LLM Request Duration: 11 secs
[4BE4:0094-47E4] 2026/10/15 11:20:49 DominoIQ: PerformRequest() returned Headers : HTTP/1.1 200 OK
Server: llama.cpp
Access-Control-Allow-Origin:
Content-Type: application/json; charset=utf-8
Content-Length: 2737
Keep-Alive: timeout=5, max=100
[4BE4:0094-47E4] 2026/10/15 11:20:49 DominoIQ: PerformRequest() returned Data : {"choices":[{"finish_reason":"stop","index":0,"message":{"role":"assistant","content":"はい、dxlSuiteについて知っています。 \ndxlSuite for LotusScriptは、DXL(Domino XML)を使用して、ノーツのオブジェクト(特に文書)にアクセスするためのLotusScript用のクラスライブラリです。主な特徴は以下の通りです: \n\n1. **使いやすい** \n Domino Designerの機能を活か

コンソールログの returned Data を見る限り、DominoIQ の最終的な返答が表示されているようですね。


◇ 詳細ログ

domiqllmthr で始まるログを確認すると、LLM に渡された情報や、生成された回答を確認できます。DATA-OUT には LLM に渡す JSON が記録されています。整形して色付けしてみました。

  • 青字 LLM System Prompt 文書作成に設定した LLM の役割
  • 黄字 RAG に問い合わせ結果
  • 紫字 ユーザが入力した質問

{
   "model": "Qwen/Qwen3-8B-GGUF",
   "messages": [
      {
         "role": "system",
         "content": "あなたは LotusScript と dxlSuiteの専門家です。
               初心者にも分かりやすく説明してください。
               回答は日本語で行ってください。"

      },
      {
         "role": "user",
         "content": "Use the following context to answer the user query:
            <CONTEXT>
               dxlSuite を使ってみよう
               dxlSuite for LotusScript とは?
               dxlSuite の構成と利用条件
               dxlSuite は Script 操作に対応
               既存の文書を dxlSuite でアクセスします。
               dxlSuite とは? dxlSuite for LotusScript は DXL(Domino XML) を使用して、ノーツのオブジェクト(主に文書)にアクセスするための、LotusScript 用クラスライブラリです。 dxlSuite には次のような特徴があります。 使いやすい! 簡単 標準クラスを超える機能 1. 使いやすい Domino Designer の特徴を生かし、開発者が開発しやすいよう工夫しています。 ライブラリはクラスで提供されますので、タイプアヘッド機能を利用でき、同時にポップアップヘルプに必要な情報が表示されます。 また、開発支援機能を搭載していますので、効率的に開発できます。 2. 簡単 ノーツの構造に沿ったクラス構造となってます。また、ノーツにはさまざまなオブジェクトがありますが、それに応じたクラスを用意しています。 リッチテキスト内には大量のオブジェクトが順不同に存在しますが、それぞれに効率よくアクセスできるよう検索機能を用意しています。 3. 標準クラスを超える機能 リッチテキスト関連の標準クラスは、ノーツクライアントと同等の操作ができません。
               既存の文書を dxlSuite で更新します。
               dxlSuite と NotesDocument の連携
               dxlSuite の構成や使い方、利用条件や動作環境などをまとめます。
             </CONTEXT>

            dxlSuite を知っていますか?
            If the context does not help answer the question, respond with 'I don't know.' Do not make up an answer."
      }
   ],
   "max_tokens": 512,
   "temperature": 0.7
}

<CONTEXT> と </CONTEXT> で囲まれた部分に、検索結果に関連するテキストがまとめられています。このテキストは全部で 9 件分あり、RAG の検索結果である points 配列の要素数と合致します。

その後にユーザの質問が続きます。

このように、検索で得られた情報と質問をまとめて LLM に渡すことで、Notes 文書の内容を踏まえた回答を生成する仕組みになっていることがわかります。


まとめ

今回は DominoIQ の RAG サポートの挙動をログから詳細に確認しました。この流れを理解しておけば、思い通りの結果が得られない場合も、各処理のログを順に確認することで、問題の切り分けに役立ちそうですね。

ところで、今回のログを調べていて、もう一つ気になる点がありました。検索結果には itemName が含まれており、フィールドごとに関連付けられたデータが検索されているように見えます。

では、製品名、できること、できないことが別々のフィールドに格納されている場合、DominoIQ はそれらの情報をどのように検索し、回答に利用するのでしょうか?各フィールドの関連性は考慮してくれるのでしょうか?

このあたりは、実際のデータを使って別途検証してみたいと思います。


2026/10/15

Domino IQ: RAG サポートの動作検証

前回までで RAG のセットアップが完了したので、今回は動作確認をします。以前 DominoIQ の検証で作成したサンプルアプリ を RAG 用に修正して検証します。


コマンド文書の追加

RAG の効果を確認することを目的に RAG にアクセスしないコマンド文書を追加します。コマンド名を dxlSuiteHelp_NoRAG とし、RAG 以外の設定を同じにします。


テストアプリの作成

まず、問い合わせ内容を記入する Prompt と DominoIQ の返答を表示する Responce フィールドを作成します。

RAG を使用するかのラジオボタンを作成します。選択肢には次の通り設定します。

  • RAG 有効|dxlSuiteHelp
  • RAG 無効|dxlSuiteHelp_NoRAG 

このラジオボタンの選択で、DominoIQ のコマンドを切り替えるというとこですね。

最後に DominoIQ に問い合わせを行う[IQ]ボタンを作成します。今回はラジオボタンの選択で実行するコマンドを切り替えられるようにしています。

Sub Click(Source As Button)
   Dim ns As New NotesSession
   Dim nuiw As New NotesUIWorkspace
   Dim nuid As NotesUIDocument
   Dim nd As NotesDocument
   Dim llmreq As NotesLLMRequest
   Dim llmres As NotesLLMResponse
   Dim sText As String
   Dim sCmd As String

   Set nuid = nuiw.CurrentDocument
   Set nd = nuid.Document
   sText = nd.Prompt(0)
   sCmd = nd.IQCommand(0)    'コマンドの取得

   'DominoIQ へ問い合わせ
   Set llmreq = ns.CreateLLMRequest()
   Set llmres = llmreq.Completion(ns.CurrentDatabase.Server, sCmd, sText)

   'DominoIQ の返答を記録
   nd.Results = llmres.Content
   nd.FinishReason = llmres.FinishReason
End Sub

今回は、DominoIQ のレスポンスの戻り値である FinishReason もフィールドに書き込み結果を確認できるようにしてみました。


パラメータの設定

今回は RAG の動作検証が目的ですので、デバッグパラメータを Domino サーバの notes.ini に設定します。設定するのは以下の 3 つです。

  • DEBUG_DOMIQ=1
  • DEBUG_DOMINOIQ_LLMREQUEST=1
  • DEBUG_DOMINOIQ_LLMPAYLOAD=1

各パラメータの挙動については過去記事「Domino IQ とデバッグパラメータ」を参照ください。

また、今回の検証ではパラフレーズを無効化して行います。パラフレーズについても以前記事を書いていますので、「Domino IQ: パラフレーズ機能の動作」を参照ください。

  • DOMIQ_DISABLE_PROMPT_PARAPHRASE=1 のエントリを削除


動作検証

検証準備ができたので、RAG の有効/無効を切り替えながら検証します。問い合わせ内容は、『dxlSuite を知っていますか?』と RAG に食べさせた dxlSuite に関して聞いてみます。

結果は下図のようになりました。

RAG を有効にすると dxlSuite の特徴を的確に返していますが、無効の場合 DXL の一般的な説明をしたり、dxlSuite が Domino の開発者向けツールであると知ったかぶりのウソを吐いていることがわかりますね。

具体的は、ヘルプ内の「はじめに」文書に記載されている内容をうまくまとめてくれていることがわかります。


2026/09/27

Domino IQ: RAG の対象 DB の設定

Domino IQ の RAG サポート機能で DB を対象にするには、あらかじめ以下の設定・操作を行って、DB のデータをベクトル化しておく必要があります。

  1. 全文索引の作成
  2. Domino IQ オプションで有効化(管理クライアント)
  3. LLM System Prompt 文書作成
  4. LLM Command 文書作成(対象の DB を指定し、フィールドを列挙)
  5. ベクトル化(updall -w 〇〇〇.nsf)


全文索引の作成

Domino IQ では、RAG の対象 DB に全文索引を作成しておくことが前提条件となっているようです。なぜ RAG なのに全文索引が必要なのかは少し不思議なところです。HCL の中の人に技術的な説明をしてほしいですね...。

前提条件なので、索引を作成しておきましょう。データベースのプロパティで[索引の作成]をクリックします。

今回は、私が作成した DXL のライブラリ dxlSuite のマニュアル(dxlSuite.nsf)を対象に操作します。ノーツの一般的な操作なので説明不要だとは思いますが、全文索引が作成されると .ft フォルダが作成されます。


Domino IQ オプションで有効化

全文索引ができたら、管理クライアントを開きます。対象の DB を右クリックすると、一番下に[Domino IQ...]というメニューが表示されるので選択します。

設定画面は単純で、有効 / 無効 の切り替えしかありません。「有効」を選択して[OK]をクリックします。もう一度この画面を開くとステータスが「有効」に変更されていることが確認できます。

ちなみに、全文索引を設定していない DB に対してこの作業を行うと『データベースの全文索引が作成されていません』とエラーが出ます。わかりやすくメッセージを出してくれるので、設定が漏れることはありませんね。


LLM System Prompt 文書作成

後続の LLM Command 文書作成で、LLM System Prompt 文書が必要となるので作成しておきます。AI に対して「この会話では、どのようなルール・方針・役割で振る舞うべきか」を指示するものになるので、とりあえず以下のように設定しました。


LLM Command 文書作成

LLM Command 文書を新規で作成し、必要な情報を入力します。System prompt には前段で作成した LLM System Prompt を指定します。

14.5.1 の LLM Command 文書には RAG 用のフィールドが2つ追加されています。ここに RAG 化したい DB とフィールドを指定します。

14.5.1 では、ビューやフォームを指定する機能がなく、フィールドのみの指定となります。この DB にはヘルプ以外にも文書が登録されているのですが、とりあえず、ヘルプに関連するフィールドだけを指定しておきましょう。

ノーツの DB は、このようにさまざまなフォームの文書が混在していることが珍しくありません。このあたりをどのように扱うのかは、今後の機能面・設定面で気になるところです。


ベクトル化

ここまで、準備ができたらベクトル化するコマンドをサーバコンソールに投入します。

   load updall -w Home\Lib\dxlSuite.nsf

updall タスクが完了すると全文索引と同様にその DB 専用のフォルダが作成されます。


ところで、データフォルダの直下に llm_vector というフォルダが作成されているのを発見しました。名称からベクトルデータ用と思われます。先の updall で作成されたフォルダは DB 単位、llm_vector フォルダは、ベクトルデータ全体を管理するためといったところでしょうか?

最近の Domino は、データフォルダ内にフォルダを作りすぎですね...


次回の予定

ここまでで RAG 化したい DB のベクトル化までが完了しました。次回は、簡単なアプリを作成して、RAG のテストをしたいと思います。


2026/09/26

Domino IQ: Embedding Model の設定

前回は Embedding Model について調査した過程をまとめつつ、採用するモデルは技術情報に倣って bge-m3-Q6_K.gguf としました。今回はダウンロードしたこのモデルを Domino IQ に設定する作業を行います。


LLM の切り替え

Embedding Model の設定の前に LLM を変更します。

というのも、14.5 で DominoIQ がリリースされた時のサポート情報(KB0121957)を改めて確認すると採用しているLLM が「Llama-3-ELYZA-JP-8B-q4_k_m.gguf」から「Qwen3-8B-Q5_K_M.gguf」に変更されていました。理由は『低スペックの環境でも比較的日本語処理性能が高い Qwen3 8B を使用します』と記載されていました。

そこで、この2つのモデルを Chat-GPT 先生に比較してもらいました。

Qwen3-8B Llama-3-ELYZA-JP-8B
ベース Qwen3 Meta Llama 3
パラメータ 8.2B 約8B
特徴 多言語・推論・コード・指示追従を重視 日本語を強化したモデル
日本語 強い かなり強い
Reasoning Thinking / Non-thinkingを切替可能 通常のLLM
ライセンス Apache 2.0 Llama 3系
コンテキスト 32K(YaRNで131Kまで検証) Llama 3系
GGUF Q5_K_M Q4_K_M

どちらも 8B クラスのモデルであるため、必要となる VRAM 容量に大きな違いはないといえます。ただし、ELYZA は Q4_K_M、Qwen3 は Q5_K_M という異なる量子化方式が採用されており、モデル本体のメモリ使用量は Qwen3 のほうがやや大きくなると想定できます。そのため、今回の変更を単純な VRAM 削減を目的としたものと考えるのは適切ではなさそうです。

Qwen3 は日本語処理性能に加えて、多言語対応や推論能力、指示への追従性などを重視したモデルです。今回は、Embedding モデルによる文書検索と LLM による回答生成を組み合わせた RAG を利用するため、単に日本語が得意であるだけでなく、検索された情報を理解して適切に回答する総合的な能力が重要になります。その点で、Qwen3 は今回の用途に適したモデルと考えられます。

また、Embedding モデルとして多言語対応の bge-m3 を利用します。「bge-m3 で多言語の文書を検索し、Qwen3 でその内容を理解して回答する」という構成との相性も良いと考えられます。限られた GPU リソースの中で、必要な日本語性能と汎用的な処理能力のバランスを取ったモデル選択と考えることができそうです。


ということで、Qwen3-8B-Q5_K_M.gguf をダウンロードして、 llm_models フォルダに配置します。そして、Domino IQ Configuration DB で設定し、Domino IQ に組み込みます。

正しく設定できると、サーバ起動時に LLM がロードされます。

[4094:0025-4594] 2026/08/14 20:40:11 DominoIQTask: AI エンジン実行中。読み込まれたモデル = Qwen3-8B-Q5_K_M.gguf


Embedding Model の登録

RAG サポートのセットアップの2つ目、「LLM Model 文書作成(Model Type = Embedding model)」の作業です。ここでは、Embedding Model を Domino IQ に登録します。

Domino IQ Configuration DB を開いて[Embedding Models]ビューを開き、[Add Model]をクリック、新しい文書を作成します。


Embedding Model の組み込み

Domino IQ Configuration DB の Configurations ビューを開き、既存の Domino IQ の設定文書を開き、編集します。

[Embedding Model]タブを開き、先ほど登録した Embedding Model を選択します。その他項目については、デフォルト値のままとします。

なお、Embedding model availability: の項目は、正しく設定完了すると『Embedding model copy succeeded; embedding model is available』に更新されます。

[Vector Database]タブはデフォルト値のままとします。

[Advanced]タブはサポート情報(KB0129110)に倣って入力します。

『例えば、「-c 327680」と「Number of Concurrent requests: 20」に設定すると、AI 推論エンジンにリクエストごとに 32768 トークンのコンテキストサイズが提供されます。』と記載があります。これだけでは何のことかよく分かりませんがが、チューニング時に必要となる項目のようですね。


トランザクションログの有効化

RAG サポートでは、Embedding Model を使ってベクトル化を行います(詳細は次回)。この操作では、トランザクションログの有効化が条件となります。

[352C:0002-287C] 2026/09/16 10:02:00 Index update process shutdown: Function only supported for transaction logged databases

DominoIQ の直接的な設定ではありませんが、ベクトル化を行う前に有効にしておきましょう。設定は、サーバ文書を編集し、[トランザクションログ]タブで行います。