ローカル環境で動作する！AI検索ツールを作った話　〜質問に対する文書検索から回答生成まで〜


![pySimpleGUI.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/909180/cca15fd6-3fe3-8d60-b1eb-153d886fa587.png)
## はじめに
今回、APIや外部サーバー上で扱いたくないような文書データに対して、質問への文書検索から回答までできたら良いなと思って**KnowledgeExplorer**というツールを作成しました。
探したらどこかに類似ツールはあるかもしれませんが、温かい目で見ていただければと思います。

個人的に想定しているユースケースは以下のようなものです。（他に使えそうな使い方があれば教えていただければ幸いです。）業務で過去の文献を漁る作業をしたくないという強い思いで作成したため、ユースケースがそちらに寄っているのはご了承ください。

- 過去の議事録や会議メモから「xxxはoooとなった」といった文面を探す作業
- 過去の議事録や会議メモから「xxxは△△△である」というのがあっているか確認する作業   etc...

コードはここです。
https://github.com/jumtra/KnowledgeExplorer

## 概要

![ドキュメント説明.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/909180/9851c62b-a0f4-63fa-e426-533a15c1199c.png)

このアプリは、与えられた質問に関連するドキュメントや文章を効果的に取り出して簡潔に説明できます。たとえば、過去の会議の議事録やメモが入っているディレクトリを指定することで、過去の会議の決定事項や経緯について説明することが可能です。とくに注目すべき特徴として、一般的なローカル環境で動作するため、ビジネス上外部APIで扱えないような情報も扱うことができるため外部への情報漏洩リスクの心配が不要です。

対象データ形式：markdown

※`data/template/template.md`に準拠したファイルを推奨


### ドキュメントの記入方法

このツールは、ドキュメントの記入方法を指定することで、文章の検索精度を補っているため
以下の記入方法に従うことを推奨します。

```markdown
# div1
## title1
- body1
## title2
- body2

# div2
## title1
- body1
## title2
- body2
```

- div: TODOやMEMOなど区分の大枠、将来的にdivを指定して検索できるようにすることを想定している。
例えば、文書のTODO部分のみを検索するなど
- title: 議題などが入ることを想定、body部分に検索クエリが含まれていなくてもtitleに検索クエリが含まれている場合は、検索クエリが含まれている文章として扱うため、**他文書でもtitleは統一することを推奨**する。
- body: 議題などtitleの内容が含まれている部分




### 検索クエリ分解
1. 質問文からJanomeで非自立名詞以外の名詞を抽出
2. 1で抽出した名詞を用いて2~4gramを作成
3. 2での生成物を検索クエリとする。

### ドキュメント検索
1. 検索クエリを含むドキュメントを指定フォルダーから抽出
2. 1で抽出したドキュメントの検索クエリを含む数を降順に見たときの上位max_doc個抽出し、ドキュメントの検索結果とする。

### 文章検索
1. 文章をチャンク化する。
2. 検索クエリを含むチャンク化した文章を抽出
3. 2で抽出した文章を文章の検索結果とする。

### 文章前処理
1. 検索結果の文章の不正文字やカッコやUnicodeなどを除去

### 文章と質問をベクトル化
1. Sentence Transformerで検索結果文章と質問をベクトル化

### KNNで類似文章を抽出
1. 質問文と類似度が高い検索結果文章をmax_results個抽出
2. 1の結果を検索結果とする。

### LLMを用いた回答
1. 検索結果と質問文をプロンプトと結合
2. Ctranslate2でLLMを変換
3. プロンプトと変換したLLMで回答生成

使用したプロンプト
```
以下はタスクを説明する指示と文脈のある文章が含まれた入力です。要求を適切に満たす回答を生成しなさい。

###入力:
検索結果を参考に質問の回答をしてください。
###質問：{question}

###検索結果:
{result}

###回答:
```

※文章のチャンク化、ベクトル化〜KNNで検索の実装は[NeMo-Guardrailsの実装](https://github.com/NVIDIA/NeMo-Guardrails)を参考にしました。

## 使い方
### 実行環境
- OS: Windows 11 pro
- CPU: Rythen5 PRO 5650U
- GPU: AMD Radeon(TM) Pro Graphics (CPU内蔵GPU)
- MEMORY: 16GB
### 実行方法

1. 環境構築

前提として、poetryがインストールされている必要があります。

```sh
git clone https://github.com/jumtra/knowledge_explorer.git
poetry install
```

2. 実行

実行方法は、3種類あります。

- cliで実行する場合

```sh
poetry run ke
```
以下CLIで有効な引数の一覧

```
options:
  -h, --help            show this help message and exit
  --data_path DATA_PATH
                        ドキュメントが存在するディレクトリの絶対パスを指定
  --question QUESTION   質問内容
  --max_results MAX_RESULTS
                        検索結果から参照する最大文章数
  --max_doc MAX_DOC     検索結果から参照する最大ドキュメント数
  --generate_num GENERATE_NUM
                        回答の生成数
```

- ローカルサーバとして実行する場合

localhostでgradioに接続する形になります。
ファイアーウォールの設定によっては、ローカルで起動できない可能があります。

```sh
poetry run ke_app
```


実行例
1. 質問を入力
2. フォルダパスに対象フォルダーの絶対パスを入力
3. 実行を押して(実行環境では)30秒程待つと結果が出力される。

![gradio結果.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/909180/cc3f61c0-a73a-6ea8-49bc-3ff4f331900d.png)



- ローカルアプリとして実行する場合

ファイアーウォールの関係でgradioが起動できない場合はこちらをお試しください。
※windows 11では実行確認済み

```sh
poetry run local_app
```
※以下のようなコードを記述したbashファイルを作成してデスクトップ上からGit for windowsで実行すれば
アプリのように起動可能
```sh
cd "/c/Users/xxx/knowledge_explorer"　# knowledge_explorerをクローンしたディレクトリのパス
poetry run local_app
```

実行例
1. 質問を入力
2. フォルダパスに対象フォルダーの絶対パスを入力
3. 実行を押して(実行環境では)30秒程待つと結果が出力される。

![pySimpleGUI.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/909180/cca15fd6-3fe3-8d60-b1eb-153d886fa587.png)


## 使用モデル

|使用用途|モデル名|リンク| 商用利用|
|---|---|---|---|
|文章のベクトル化|all-MiniLM-L6-v2|https://huggingface.co/sentence-transformers/all-MiniLM-L6-v2|可能|
|回答生成|Jumtra/rinna-3.6b-tune-ep5|https://huggingface.co/Jumtra/rinna-3.6b-tune-ep5|可能|


## サンプルデータ
`data/sample`内ダミーのデータは、
gpt3.5-turboで以下のプロンプトを使って生成させた。

```
以下プロンプトで適当な議事録を生成させた。
あなたは、データサイエンティストです。
あなたは、顧客の購買データを用いた分析の方針についてミーティングを行いました。
ミーティングの議事録を以下のフォーマットで記述してください.
出力結果は、必ずmarkdownのコード形式にしてください。
# 決定事項
## hoge1
## hoge2
# TODO
## hoge1
## hoge2
# MEMO
## hoge1
## hoge2
```

続けて生成する場合のプロンプトは以下
```
前回の会議で決まらなかったことや新たな課題点について同じ顧客とミーティングを行いました。
ミーティングの議事録を同様のフォーマットで記述してください
```

## 最後に

まだまだ改善点は多いと感じているツールなので、気の向くままに改善できればと思います。
とりあえずchat機能はなるべく早く実装したい…

コードはここです。
https://github.com/jumtra/KnowledgeExplorer