問い合わせに似た文書が検索できても、その文書だけで回答してよいとは限りません。旧版の手順や、質問の条件に合わない説明を根拠にすると、引用付きの誤回答ができます。
DifyのナレッジベースでRAGを組むエンジニアに向けて、検索結果を集める処理と、回答の根拠として使えるかを判定する処理を分ける設計を考えます。
2026年10月5日時点の公式ドキュメントを参照した設計案です。掲載するPython関数は合成データでローカル検証しています。Dify上の接続、検索精度、LLMによる回答評価、本番運用、工数削減は未検証です。
検索スコアは何を判定できるか
ナレッジ検索ノードにはTop K、Score Threshold、メタデータフィルタがあります。検索設定はナレッジベース側とノード側にあり、前者で集めた候補を後者で絞り込み・再順位付けする構造です。公式:Knowledge Retrieval
ここから先は設計上の判断です。関連度の高いチャンクにも、次の問題は残ります。
| 検索後に残る問題 | 検索スコアだけでは決められないこと |
|---|---|
| 旧版の説明がよく一致する | 現在有効な文書か |
| 見出しは合うが例外条件が欠ける | 質問に必要な条件がそろうか |
| 複数文書で説明が食い違う | どちらを採用するか |
| 文書にない補足をLLMが生成する | 回答の各主張を根拠で裏付けられるか |
Score Thresholdは検索候補を制御する値として使い、「この値以上なら正答」という回答許可には使いません。検索方式やモデルを変えたら、同じ閾値の挙動も評価し直します。
選択肢を比べる:プロンプト、検索設定、コード
| 方法 | 得意な処理 | 残る制約 | この設計での役割 |
|---|---|---|---|
| プロンプトで「根拠がなければ答えない」 | 回答方針の指示 | 遵守を機械的に保証できない | 生成時の補助 |
| 検索設定・メタデータフィルタ | 候補の関連度・対象版の絞り込み | データ欠損や設定漏れ、回答の裏付けまでは扱えない | 検索前後の候補制御 |
| Code+If-Else | 空配列、必須項目、版の一致の判定 | 意味的な正しさは判定できない | 生成前の品質ゲート |
| 回答後の評価とHuman Review | 主張と根拠の対応、矛盾、業務上の妥当性 | 評価誤り、待ち時間、人の確認負荷 | 回答を返す前の判断 |
公式には、Python/JavaScriptを実行するCodeノードと、変数の条件で経路を分けるIf-Elseノードがあります。以下では、その組み合わせを使う設計にします。
採用する境界:生成に進めることと、回答を返せることを分ける
generateは「回答案を生成してよい」という意味です。利用者への回答許可ではありません。初期段階では回答案を人が確認し、評価データが蓄積してから低リスクな質問だけ自動回答の対象にする方針です。
検索対象の権限は、認証済みの利用者情報を受け取るサーバー側で決めます。質問文やLLMの推測を権限の根拠にしません。Difyのメタデータフィルタは検索を絞る機能として使い、直接APIを呼ばれた場合も含めて別途認可を確認します。
実装の要点:生成前の小さなゲート
ナレッジ検索のresultは、本文・メタデータなどを持つチャンクの配列です。ただし、次のevidence_idとcorpus_versionはこの記事で定義する共通形式であり、Difyの標準出力フィールド名ではありません。公式:検索ノードの出力
前段の変換処理で、実際の検索出力から本文とチャンク識別子を取り出し、文書メタデータや管理台帳から版を対応付けます。識別子は検索対象全体で一意にし、欠損をLLMに補わせません。Difyではカスタムメタデータを作成できますが、文書への値の設定も必要です。公式:Manage Document Metadata
入力例は合成データです。corpus_versionは文書の更新日時ではなく、レビュー済み文書群のリリース版とします。現行版は管理側が指定し、利用者入力から受け取りません。
[
{
"evidence_id": "manual-a:chunk-1",
"content": "申請には担当者の確認が必要です。",
"corpus_version": "release-a"
}
]
Codeノードへの入力はchunks(Array[Object])とcurrent_version(String)、出力はroute、reason(ともにString)、evidence(Array[Object])を宣言する設計です。
def main(chunks: list, current_version: str) -> dict:
if not isinstance(current_version, str) or not current_version.strip():
return {"route": "stop", "reason": "invalid_version", "evidence": []}
if not isinstance(chunks, list):
return {"route": "stop", "reason": "invalid_input", "evidence": []}
if not chunks:
return {"route": "hold", "reason": "no_evidence", "evidence": []}
fields = ("evidence_id", "content", "corpus_version")
for chunk in chunks:
if not isinstance(chunk, dict) or any(
not isinstance(chunk.get(key), str) or not chunk[key].strip()
for key in fields
):
return {"route": "stop", "reason": "invalid_chunk", "evidence": []}
if any(c["corpus_version"] != current_version for c in chunks):
return {"route": "hold", "reason": "version_mismatch", "evidence": []}
unique = {}
for chunk in chunks:
key = chunk["evidence_id"]
if key in unique and unique[key]["content"] != chunk["content"]:
return {"route": "hold", "reason": "duplicate_conflict", "evidence": []}
unique[key] = {field: chunk[field] for field in fields}
return {"route": "generate", "reason": "ready", "evidence": list(unique.values())}
判定順序は、版指定・入力形式 → 全チャンクの形式 → 版の一致 → 同じ識別子の内容衝突です。旧版だけを黙って除外すると、新旧の混在を見落とすため、ここでは検索結果全体を保留します。同じ内容の重複はまとめますが、別識別子の文書間の矛盾はこの関数では検出しません。
holdとstopでは根拠を後段に渡しません。If-ElseのELSEも停止側に接続し、未知のrouteで生成へ進まないようにします。正常経路でも、LLMへ渡すのは検査後の根拠だけです。標準のresultを直接Contextへ接続する経路を残すと、このゲートを迂回します。変換後の根拠をプロンプトへ渡す実装では、標準の引用表示との対応も別途確認が必要です。
品質管理:検索と回答を別々に評価する
DifyのRetrieval Testingで変更した検索設定は、そのテストセッションだけに適用されます。テスト画面で良い結果が出ても、アプリ側の設定に反映されたとは限りません。公式:Test Knowledge Retrieval
固定した質問セットで、検索テストとアプリの実行結果をそれぞれ確認します。
| 評価対象 | 確認する結果 |
|---|---|
| 検索 | 必要な根拠が候補に含まれるか、対象外の文書が混ざらないか |
| 生成前ゲート | 空・欠損・旧版・重複衝突で生成に進まないか |
| 回答案 | 各主張が渡した根拠で裏付けられるか、条件や例外を落としていないか |
| 保留 | 根拠がない質問や矛盾する質問で、断定せず確認へ進むか |
| 認可 | 別の利用範囲の文書を検索・回答に使わないか |
引用IDが存在することは、主張の裏付けを意味しません。回答後の評価では、主張と引用箇所を対にして確認します。評価用LLMを使う場合も判定は誤り得るため、不明・不一致はHuman Reviewへ送ります。検索文書中の命令文は資料として扱い、システム指示やツール実行権限に昇格させません。
掲載関数のローカル検証ログは次のとおりです。合成入力に対する分岐検証であり、検索や回答の品質測定ではありません。
PASS valid_evidence_routes_to_generation
PASS empty_results_hold_without_generation
PASS non_list_input_stops
PASS blank_current_version_stops
PASS non_object_chunk_stops
PASS missing_content_stops
PASS whitespace_content_stops
PASS stale_version_holds
PASS mixed_versions_hold_all_evidence
PASS identical_duplicates_are_collapsed
PASS conflicting_duplicate_id_holds
PASS invalid_chunk_precedes_version_mismatch
PASS extra_fields_are_not_forwarded
13 cases passed
失敗・トレードオフと運用の決め方
候補を増やせば安全になるとは限らない。 Top Kを上げると、不要な条件や旧版も入り得ます。検索方式はVector/Full-Text/Hybridを比較し、質問セットに必要な根拠が含まれるかで選びます。High Qualityで利用できる方式や、Rerank利用時のトークン消費は公式の検索設定を確認します。生成・評価・再試行・人の確認を含めた総コストを測り、未測定の料金や削減率は断定しません。
保留を増やすと人の処理能力が必要になる。 no_evidenceは質問の追加確認、version_mismatchは文書群の更新確認、duplicate_conflictは識別子と取り込み処理の確認へ分けます。確認待ちは完了扱いにせず、担当者と期限を持たせます。
データ不備を再試行で直そうとしない。 形式不正や版不一致は、同じ入力で再実行しても解消しません。検索の通信障害は根拠ゼロと区別し、エラー経路へ送ります。一時障害の再試行には上限を設け、後段で作る確認依頼は処理IDで重複を防ぎます。タイムアウト後は作成済みかを照合してから再送します。
中断後に古い回答を返さない。 認可・文書版を確認した後でも、利用者が取り消した場合は新たな回答送出を止めます。既に作成した確認依頼は取り消されたとは見なさず、確定済みの処理と未処理を記録します。中断伝播と送出前チェックは、この関数の外側で実装・検証します。
運用ログには処理ID、検索・文書群・プロンプトの版、判定理由、各段階の所要時間を残します。質問全文や文書本文を一般ログへ出す前提にはせず、再現用データはアクセスと保存期間を制限します。
次に検証することと、業務への効果
- Difyの実出力から共通形式への変換と、引用の対応を確認する。
- 検索設定の二段階とアプリ実行を、同じ質問セットで比較する。
- 根拠なし・旧版・条件不足・文書間矛盾・権限外の質問を回帰評価に入れる。
- Human Reviewの修正理由を分類し、文書・検索・生成・評価のどこを直すか決める。
- 自動回答率だけでなく、誤回答の訂正負荷、保留の滞留、総コストを測る。
期待する効果は、根拠が不十分な回答を早めに保留し、確認が必要な理由を担当者へ渡せることです。実測の工数削減はまだ示せません。導入判断では、削減できた回答作業の時間から、確認・訂正・保守に増えた時間を差し引いて評価します。
参考リンク
- Dify:Knowledge Retrieval
- Dify:Code
- Dify:If-Else
- Dify:Manage Document Metadata
- Dify:Test Knowledge Retrieval
- Dify:Specify the Index Method and Retrieval Settings
同様の仕組みの設計・構築・運用については相談可能。
この記事を書いた人✏️@YushiYamamoto
ITPRODX.com代表 / AIアーキテクト
Next.js / TypeScript / n8nを活用した自律型アーキテクチャ設計を専門としています。
日々の自動化の検証結果や、ビジネス側の視点(ROI等)に関するより深い考察は、以下の公式サイトおよびnoteで発信しています。
