DifyとWeaviateを使ったCRAG

# 記事の概要

以前、以下の記事でDify上に、「Weaviate（ベクトルDB）」と「intfloat/multilingual-e5-large（エンベディングモデル）」、「Groqのllama 3（LLM）」を使った、RAG（Retrieval Augmented Generation）のワークフローを構築した。

https://qiita.com/kongo-jun/items/c254a695f719c22b6ebc

今回はこのワークフローをバージョンアップし、CRAGという仕組みを導入することで、以下のようなベクトル検索を使ったRAGの精度向上を試みる。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3619695/2a2f5422-801b-76e0-7db7-bff801968030.png)

動画でも解説してます。

https://youtu.be/Jway7cnaDRI

# CRAG（Corrective Retrieval Augmented Generation）

CRAGはRAGが抱える、ハルシネーションを抑制するための考え方で、導入が容易な割に、期待できる精度向上が高いため、優れたテクニックと考えている。

https://arxiv.org/abs/2401.15884

具体的には、ベクトル検索などで取得した外部情報から、質問と関係のない検索結果を排除するという試みで、実際の回答を作成する前に、別のLLMなどで不要な情報を排除する。

```mermaid
graph TB;
    subgraph Dify[従来のRAG]
        q[質問]
        vector[ベクトル検索]
        RAG[RAG（LLM）]
        output[回答]
    end

    q --> vector
    vector --> RAG
    RAG --> output
```

ベクトル検索の直後に、CRAG用のLLMの処理を追加する。

```mermaid
graph TB;
    subgraph Dify[CRAG]
        q[質問]
        vector[ベクトル検索]
        CRAG[★CRAG（LLM）★]
        RAG[RAG（LLM）]
        output[回答]
    end

    q --> vector
    vector --> CRAG
    CRAG --> RAG
    RAG --> output
```

# 処理フロー


以下のようなフローを構築する。

```mermaid
graph TB;
    subgraph local
        Dify
        Weaviate
        venv
    end

    subgraph groq[Groq Cloud]
        llama370[llama 3 70B]
        llama38[llama 3 8B]
    end

    subgraph Dify[Dify Docker]
        q[質問]
        vector[ベクトル検索]
        tune[後処理]
        CRAG[CRAG]
        RAG[RAG]
        RAG2[RAG]
        IFELSE[IF/ELSE]
        output[回答]
        output2[回答]
    end

    subgraph Weaviate[Weaviate Docker]
        vectorDB
    end

    subgraph venv[python仮想環境]
        flask
    end

    q --> vector
    vector --> tune
    tune --> CRAG
    CRAG --> IFELSE
    IFELSE -->|ベクトル検索結果残有| RAG    
    RAG --> output
    IFELSE -->|ベクトル検索結果残無| RAG2    
    RAG2 --> output2

    vector <-.-> |HTTPリクエスト|flask
    flask <-.-> |ベクトル検索|vectorDB

    CRAG <-.-> |テキスト排除|llama38
    RAG <-.-> |テキスト生成|llama370
    RAG2 <-.-> |テキスト生成|llama370

```

従来のRAGのフローと比較して、CRAG用の処理が追加となり、それ以降の処理が少し煩雑になっている。（分岐が発生している）
これは、CRAGで外部情報の候補が全て排除されたときのための対応として分岐を行っている。
また、CRAG用に追加するLLMは、最終的なテキストを出力するLLMよりも単純な処理を行うため、軽量なLLMを選択することができる。

# 各種設定

Difyのワークフローをベースに各種設定について解説する。

```mermaid
graph TB;
    subgraph Dify[Dify Docker]
        q[1.質問]
        vector[2.ベクトル検索]
        tune[3.後処理]
        CRAG[4.CRAG]
        IFELSE[5.IF/ELSE]
        RAG[6-a.RAG]
        output[7-a.回答]
        RAG2[6-b.RAG]
        output2[7-b.回答]
    end

    q --> vector
    vector --> tune
    tune --> CRAG
    CRAG --> IFELSE
    IFELSE --> RAG
    RAG --> output
    IFELSE --> RAG2
    RAG2 --> output2
```


![CRAG_workflow_edit.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3619695/146a164a-6960-2aac-1896-eb603ef421bc.png)


### 1.質問

Dify上の設定は以下となる。

![CRAG_1_質問.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3619695/36f6e7c4-040d-fc6d-cc0b-a98f8bd28880.png)

### 2.ベクトル検索

Difyからpython仮想環境に対して、HTTPリクエストを送信し、pythonでWeaviateへベクトル検索を実行する。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3619695/b4b9d12f-85dd-ec7c-2143-9a8b763e21b6.png)


詳細な手順については、以下の記事の「2.ベクトル検索」を実施。

https://qiita.com/kongo-jun/items/c254a695f719c22b6ebc#2%E3%83%99%E3%82%AF%E3%83%88%E3%83%AB%E6%A4%9C%E7%B4%A2

### 3.後処理

Dify上の設定は以下となる。
LLMにはstringのみが送信可能だが、json形式で送信したいため、jsonの見た目のstringへと処理する。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3619695/a2c5bf37-0600-0ef5-3224-c327a3ab7855.png)

### 4.CRAG

Dify上の設定は以下となる。
CRAG用のLLMは、最終的なテキスト生成用のLLMよりも簡単な処理を行うため、軽量なモデルである「llama 3 8B」を選定する。
また、ベクトル検索の結果が全て削除された場合を想定して、その場合に「全ての情報が削除されました」と出力するように指示する。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3619695/ccf58a21-611b-6580-9626-925b408512e8.png)

```
「参考情報」の情報はjson形式のテキストです。
回答は同様の形式で出力してください。
「参考情報」のみを参考にし、「ユーザーの質問」：の質問内容と無関係の情報を削除してください。
全ての情報が削除された場合は必ず、「全ての情報が削除されました」という文字列を出力してください。
「ユーザーの質問」：{{#0000000000.input_text#}}
「参考情報」: {{#0000000000.result#}}
```

### 5.IF/ELSE

Dify上の設定は以下となる。
前工程でベクトル検索の結果が全て削除された場合を想定した分岐処理を設定する。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3619695/ec060a1a-af29-e6f6-bc80-dc6054775fa4.png)

### 6-a.LLM（情報なし）

Dify上の設定は以下となる。
CRAGによりベクトル検索結果がなくなる可能性があるため、こちらの分岐ではそれに対応できるように設定している。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3619695/f2e225df-2751-2de7-6986-6926a13700ed.png)

```
# 命題
「原則」を必ず守って、テキストを出力してください。

# 原則
日本語で出力してください。
入力がどのような形式でも日本語のみで出力してください。
「ユーザーの質問」質問について​、質問に答えられないことを端的に回答してください。
回答するときの口調については、「口調」のどれか１つの口調のみを参考にしてください。敬語は使用しないでください。

# インプット
「ユーザーの質問」：{{#000000000000.input_text#}}
「口調」: [知らない][勉強とかできない][特にない][だいたい寝てるかも][アニメ？][みはるがよく使ってるけど、ネムルはよくわからない][覚えてない][アプリって何？][眠い][そうなんだ]
```

CRAGとは関係ないが、今回は口調も調整している。

### 7-a.終了

Dify上の設定は以下となる。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3619695/37f2ee8d-a631-789b-d35e-d9b9d0c2b649.png)

今回のフローでうまくいけば以下のような出力になる。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3619695/08d723b2-3dbc-6781-1c56-e55068212f82.png)


### 6-b.LLM

Dify上の設定は以下となる。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3619695/c4b90dac-bdf8-73ab-241f-6a4df0984637.png)

```
# 命題
「原則」を必ず守って、「ユーザーの質問」質問について回答してください。

# 原則
日本語で出力してください。
入力がどのような形式でも日本語のみで出力してください。
「参考情報」の情報のみを参考にし、「ユーザーの質問」質問について回答してください。
「参考情報」に情報がない場合は、質問に答えられないことを回答してください。
回答するときの口調については、「口調」のどれか１つの口調のみを参考にしてください。敬語は使用しないでください。

# インプット
「ユーザーの質問」：{{#000000000000.input_text#}}
「参考情報」: {{#000000000000.text#}}
「口調」: [知らない][勉強とかできない][特にない][だいたい寝てるかも][アニメ？][みはるがよく使ってるけど、ネムルはよくわからない][覚えてない][アプリって何？][眠い][そうなんだ]
```

CRAGとは関係ないが、今回は口調も調整している。

### 7-b.終了

Dify上の設定は以下となる。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3619695/93d59355-a00b-0ba5-74db-523dc09504c4.png)

# 感想

素直に最後のテキスト生成用LLMは、GPT-4oかClaude-3-Opusを使いたい。
CRAGは軽量なLLMで不要なベクトル検索結果を削除することが重要なので、「llama 3 8B」を使った。
結果として、期待通りの結果が出ているため、こちらのモデルはそれほど代替えする必要性を感じていない。
あと口調の部分は、プロンプトで頑張るよりも軽量なLLMをファインチューニングすることで、語尾を学習させた方がいいと感じている。