difyでお手軽、社内文書検索


株式会社カオナビで、開発に役立ちそうなツールの検証・導入を行っている iamapen です。

[dify](https://dify.ai) というツールを使うと、ノーコード/ローコード でAIを組み込んだワークフローを作ることができます。

簡単にRAGが実現できるので、これを使って社内文書検索を始めました。
その際に工夫したことを紹介します。

検証は `v0.13.1` 、セルフホスト版で行っています。


# dify導入の背景

- 社内にはドキュメントがたくさんある
- とくにエンジニア組織のドキュメントはコンフルに本当にたくさんある
- しかしコンフル標準の検索機能ではなかなか目的の文書にたどり着けないという悩みがある😖
- AIを使ったRAGという検索の流行は知っていて、試してみたい
- Amazon Kendraを試そうとしたが検証費用が足りなくて凍結
- difyをセルフホストすればより安く早く実現できそうだったので試してみた
- けっこういい感じなので検索範囲を拡大中  ←今ここ


# 工夫したこと

## 例1: チャットフローで引用元URLを表示

2024/12現在の最新版である `v0.13.1` では、ナレッジのドキュメントに対してURLを設定するメタデータが機能しておらず、標準機能では引用元URLを出力することができません。

> 現在のバージョンでは、メタデータのフィルタリングおよび引用元の機能はサポートされていません。
https://docs.dify.ai/ja-jp/guides/knowledge-base/knowledge-and-documents-maintenance#metadta

※要望は出ている模様（https://github.com/langgenius/dify/discussions/3781）


社内文書検索システムとしては「目的の文書にたどり着ける」ことが重要なのでこれでは困ります。

そこで、「ナレッジドキュメントのタイトルに引用元URL埋め込む」という対応をして実現しています。

大まかな流れは以下です。

![スタジオ.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/29639/1de16d59-a7a6-c73d-e52e-2a7841198e2e.png)

1. ドキュメントタイトルに引用元URLを設定
2. retrieval結果のデータ構造にURL情報を付加
3. (2) を社内文書としてテキスト形式で再フォーマット
4. (3) を質問とともにユーザプロンプトとしてLLMに入力
5. LLMからのGenerationの結果に、引用元URLが含まれる

### 具体的な実装

#### 1. ドキュメントタイトルに引用元URLを設定

ナレッジのドキュメントタイトルには引用元URLを設定します。
タイトルはretrieval結果に含まれるので、アクセス可能な情報です。

retrieval結果の構造は以下の通り。

```json
{
  "result": [
    {
      "metadata": {
        "_source": "knowledge",
        "dataset_id": "002b1390-10ad-41ca-a0a2-dd93c7c067a0",
        "dataset_name": "docs.dify.net",
        "document_id": "b3fc447a-0c75-4c8c-8ed1-39a16790a72c",
        "document_name": "https://docs.dify.ai/guides/workflow/orchestrate-node",
        "document_data_source_type": "website_crawl",
        "segment_id": "436c6284-1962-47b6-aa55-b5ca09900f47",
        "retriever_from": "workflow",
        "score": 0.4241129,
        "segment_hit_count": 1,
        "segment_word_count": 1571,
        "segment_position": 13,
        "segment_index_node_hash": "3c797b9d1862472160665f7f9f0e69238d53193635b4e9932c0ca8923bb6764c",
        "position": 1
      },
      "title": "https://docs.dify.ai/guides/workflow/orchestrate-node",
      "content": "All parallel structures will run simultaneously; nodes within the parallel structure output results after completing their tasks, with no order relationship in output. The simpler the parallel structure, the faster the output of results.\n\n![](https://docs.dify.ai/~gitbook/image?url=https%3A%2F%2F3866086014-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FRncMhlfeYTrpujwzDIqw%252Fuploads%252Fgit-blob-0ceb0311c9f69671bc1a5f0256dc32808b94e3ad%252Forchestrate-node-chatflow-multi-answer.png%3Falt%3Dmedia&width=768&dpr=4&quality=100&sign=99a8dbb2&sv=2)\n\n### [Direct link to heading](\\#designing-parallel-structure-patterns) Designing Parallel Structure Patterns\n\nThe following four patterns demonstrate common parallel structure designs:\n\n#### [Direct link to heading](\\#id-1.-normal-parallel) 1\\. Normal Parallel\n\nNormal parallel refers to the `Start | Parallel Nodes | End three-layer` relationship, which is also the smallest unit of parallel structure. This structure is intuitive, allowing the workflow to execute multiple tasks simultaneously after user input.\n\nThe upper limit for parallel branches is 10.\n\n![](https://docs.dify.ai/~gitbook/image?url=https%3A%2F%2F3866086014-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252FRncMhlfeYTrpujwzDIqw%252Fuploads%252Fgit-blob-6208ef2403ee301e15029ef431fa6d8d313c98f8%252Forchestrate-node-simple-parallel.png%3Falt%3Dmedia&width=768&dpr=4&quality=100&sign=39314b3d&sv=2)\n\n#### [Direct link to heading](\\#id-2.-nested-parallel) 2\\. Nested Parallel"
    }
  ]
}
```

#### 2. retrieval結果にURL情報を付加
スタジオ上の コードブロック「整形・URL付加」 部分です。

タイトルに引用元URLが設定してあるので、これをURL属性として追加します。

```js
/**
 * 文書情報の加工
 * urlを付加する
 * @param retrievedResults {Object[]}
 * @return {Object[]}
 */
function main({ retrievedResults }) {
    return {
        results: retrievedResults.map(result => {
            const url = result.title;
            return { ...result, url };
        })
    };
}
```

#### 3. (2) を社内文書としてテキスト形式で再フォーマット
スタジオ上の テンプレートブロック「文書情報テキスト」 部分です。

LLMへの入力用に、1行目に引用元URL、2行目以降に本文、という書式にフォーマットします。

```js
{% for result in results %}
> {{ result.url }}
{% for line in result.content.split('\n') %}> {{ line }}
{% endfor %}
{% endfor %}
```

#### 4. (3) を質問とともにユーザプロンプトとしてLLMに入力
スタジオ上の LLMブロック 部分です。

- (3)の社内文書のフォーマットを説明する
- URLを表示してと指示する

システムプロンプト：
````
# 指示内容
あなたは社内文書検索システムのアシスタントです。「ユーザーの質問」に対して、「社内文書」の情報のみを使用して回答してください。以下の指示に厳密に従ってください：

- 「社内文書」のうち、回答のために参照した部分だけを抽出して、「回答作成に使用した文書」セクションにURLと参照箇所の抜粋を示してください。参照していない箇所は「…」で省略してください。（「 回答例」参照）
- 提供された文書情報のみを使用し、外部の知識や情報を追加しないでください。
- 文書に記載されていない情報については、「その情報は提供された社内文書には含まれていません」と回答してください。
- 社内文書から情報を取得したという言及は避けてください。
- 曖昧な点や不明確な点がある場合は、その旨を明確に伝え、追加の情報や明確化を求めてください。
- 回答は簡潔かつ正確に行い、可能な限り元の文書の表現を使用してください。
- 機密情報や個人情報が含まれている場合は、それらを開示しないよう注意してください。

# 例
以下のコードブロック内に、質問と回答の例を示す。

## 質問例
```
# ユーザーの質問
AAAのインストール方法は？

# 社内文書
> https://doc.example.com/wiki/foo/git
> AAAの開発環境構築手順です。Macの方はターミナル、Windowsの方はGit for Windows付属のGit Bashでの操作を想定しています。1から8までの手順をやれば構築できます。※9以降はおまけです。1. 必要なアプリケーションのインストール Macを使用している方Docker Desktopインストール下記ページを参考に、Docker Desktopをインストールしてください。Docker Desktopインストール Intel MacやWindowsを使用している方Docker Desktopのインストール下記ページを参考に、Docker Desktopをインストールしてください。 Docker Desktopのインストール Dockerコンテナが利用可能なメモリサイズの変更(Macのみ)Dockerコンテナが利用可能な最大メモリサイズは、デフォルトでは2GBに設定されており、このままでは起動に不十分です。Dockerの設定画面を表示し、Resources -> Advancedを選択してメモリサイズを 8GB 程度に調整してください。Node.js, npmのインストール下記ページを参考にインストールしてください

（後略）
```

## 回答例
```
以下は、AAAの構築方法の概要です：

1. **必要なアプリケーションのインストール**：
    - **Macを使用している方**:
        Docker Desktopをインストールします。詳細なインストール手順は指定されたページを参照してください。
（他手順 略）

# 回答作成に使用した文書
> https://doc.example.com/wiki/foo/git
> …1. 必要なアプリケーションのインストールMacを使用している方…
（他引用は略）
```
````

ユーザプロンプト：
````
# ユーザーの質問
{{#sys.query#}}

# 社内文書
{{#12345.output#}}
````

（参考）画面のハードコピー：
![LLMブロック.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/29639/940d661b-a18d-61c1-4d3e-45c68428297a.png)
![LLMブロック_user.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/29639/6afb63d8-842d-97d9-27e9-004cdb871c71.jpeg)



#### 5. LLMからのGenerationの結果に、引用元URLが含まれる

構造の説明と出力指示をしたことで、引用元URLを含めた結果が生成されている。

![テスト実行.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/29639/7a5c6bc6-ab75-933e-4f86-308d942175d7.png)


### 注意点

上記の例は概念を説明するために簡素化してあります。
実際はもう少し複雑で、加えて以下のような工夫を行っています。

- タイトルは拡張子を付け、urlencodeしたものにする
  - ナレッジ登録APIの制限＆ドキュメントの種類を伝えるため
    - バッチプログラムから登録している
- 引用文を加工
  - タイトルをurldecodeして拡張子を取り除いたものを引用元URLとする
  - 本文は先頭n文字に切り詰める
  - 引用元URLで重複排除
  - 検索スコアを追加
- retrieval結果を「もしかして」として引用文とスコア、URLを並べる
  - retrieval結果があるときのみ表示する


実際の検索結果の画面：
![完成版実行結果.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/29639/13054319-4bd1-eb8b-8e6c-89726f598b6f.png)


## 2. 検索ドメイン

ノイズを減らして検索体験を良くするために、コンフルのスペース、slackのチャネル... という単位でナレッジを作り、それをユースケースごとにまとめる検索ドメインを定義、選択できるようにしています。

コンフル、slack、エンジニア向け、フロント向け...

![検索ドメイン_UI.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/29639/c96e3348-da18-603e-a5a5-002660bdfc3a.png)
![検索ドメイン_スタジオ.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/29639/869e967c-3769-c440-b803-05f8f10c3a85.png)


# 所感
本当に簡単にRAGが実現できました。

ただし細かな権限制御ができないため、社内オープンな情報を対象とするまでに留めておいたほうがよさそうです。
権限まで加味した検索システムを作るなら、より本格的なプロダクトを利用したいところ。

他人の作ったアプリを簡単に変更・削除ができてしまう点も注意です。
各自でDSLにバックアップしておく運用が必要そうです。

現在は検索範囲の拡大・検索品質の向上を行いながら、Difyの活用方法を模索しています。


# 採用情報
株式会社カオナビでは一緒に働く仲間を募集しています。
カジュアル面談も行っていますので、ご興味がある方は気軽にお声がけください。
https://corp.kaonavi.jp/recruit/recruitment/