NeMo GuardrailsによるTopical Rails


# 背景

[ChatGPT](https://chat.openai.com/)をはじめとして生成AIが日常的に使用されるようになりました。ChatGPTを実際に活用していると下記のような問題があると思います。

- 回答が事実ではない
- 有害な情報が含まれる
- Jailbreakによる機密情報の漏れ

上記のような問題に対して [NeMo-Guardrails](https://github.com/NVIDIA/NeMo-Guardrails) が役に立ちます。

本記事では、回答がなるべく事実から離れないようにするために、特定のトピックにフォーカスした回答をするTopical Railsについて紹介します。

Topical Railsによって特定の回答は知識ベースに基づいた回答をし、自信のない内容には回答しない挙動ができるため、特定のトピックに対応するchatとして使用する場合に有効に働きます。

下記の情報がNeMo-Guardrailsについて参考になります。NeMo-Guardrailsのアーキテクチャや作法などを把握できます。

https://qiita.com/mana-murakami/items/336711f7d761a0b2368d

https://qiita.com/mana-murakami/items/28e9eea654b74a540a67

# 環境構築

Python のバージョンは 3.10.0で試しました。3.8だとライブラリインストールの際にエラーが出たので、なるべく新しいバージョンのPythonを使用される方が良いと思います。

下記のドキュメントに沿ってインストールしました。

https://github.com/NVIDIA/NeMo-Guardrails/blob/main/docs/getting_started/installation-guide.md

# Topical Rails

質問に答えるときは、自分が精通しているトピックにフォーカスする方が良いと思われます。
下記の手順でBotを作成します。

- ボットを作る
- 会話を試す
- Topical Railsを使ったボットのテスト

今回、参考にしたサンプルコードは[こちら](https://github.com/NVIDIA/NeMo-Guardrails/tree/39eef9a45dbc94fc4a66fac3b859f75b001d89e8/examples/topical_rail)です。

# ボットを作る

トピックを選びます。毎月、米国労働統計局は雇用統計を発表します。このチュートリアルでは、[雇用統計](https://www.bls.gov/news.release/empsit.toc.htm)に関連する質問には答えますが、それ以外の話題については答えない基本的なボットを作成します。

ボットが与える回答は、知識ベースとして提供するファイル（この場合はreport.md）に基づいています。
ボットの回答は2つのドメインに分かれています。
- "jobs"と "off-topic-questions "の2つのドメインに分かれるので、簡単にするために、"jobs.co"と "off-topic.co"の2つの "colang"ファイルを作成します。
- 設定ファイル:config.ymlで一般的な指示とモデルの詳細を提供します。

# 一般的な構成

[config.yml](https://github.com/NVIDIA/NeMo-Guardrails/blob/39eef9a45dbc94fc4a66fac3b859f75b001d89e8/examples/topical_rail/config.yml)には、高いレベルで3つの重要な詳細が含まれています。

- [一般的な命令](https://github.com/NVIDIA/NeMo-Guardrails/blob/main/docs/user_guide/configuration-guide.md#general-instruction)：ユーザーは、ボットに対する一般的なシステムレベルの指示を指定することができます。この例では、ボットが職務報告書に関する質問に回答することを指定しています。また、ボットの行動特性のような詳細も指定します。例えば、ボットは簡潔であり、質問に正直に答えるだけにしています。

```
instructions:
- type: general
  content: |
    Below is a conversation between a bot and a user about the recent job reports.
    The bot is factual and concise. If the bot does not know the answer to a
    question, it truthfully says it does not know.
```

- [使用するモデルの指定](https://github.com/NVIDIA/NeMo-Guardrails/blob/main/docs/user_guide/configuration-guide.md#the-llm-model):ユーザーは、ボットのバックボーンとして機能する大規模な言語モデルを幅広い範囲から選択することができます。今回はOpenAIのdavinciを選択しています。

```
models:
- type: main
  engine: openai
  model: text-davinci-003
```

- [会話のサンプルを提供する](https://github.com/NVIDIA/NeMo-Guardrails/blob/main/docs/user_guide/configuration-guide.md#sample-conversation)：サンプル会話は、ユーザーとボット間の会話がどのように進むべきかのトーンを設定します。LLMが会話の形式やトーン、冗長な応答についてよりよく学ぶのに役立ちます。このサンプル会話はすべてのプロンプトに追加されるため、短くて適切なものにすることをお勧めします。

ここでは[Colang](https://github.com/NVIDIA/NeMo-Guardrails/blob/main/docs/user_guide/colang-syntax-reference.md)をという独自言語を使用して記述されています。

Colangを使う理由としてはフローチャート、ステートマシン、フレームベースのシステムなど、既存のダイアログ管理技術は、ChatGPTのようなLLMベースのシステムとの対話のような、柔軟性の高い会話フローのモデル化には適していないため、Colangが使用されています。

Colangは自然言語とpythonのミックスとして設計されました。pythonに慣れているなら、説明がなくても、いくつかの例を見れば使用できるように設計されています。

[参考](https://github.com/NVIDIA/NeMo-Guardrails/blob/main/docs/user_guide/colang-language-syntax-guide.md#why-a-new-language)

```
sample_conversation: |
user "Hello there!"
    express greeting
bot express greeting
    "Hello! How can I assist you today?"
user "What can you do for me?"
    ask about capabilities
...
```

# 知識ベースの使用

ユーザーの質問に答えるためにナレッジベースを使います。フォルダ`kb`を作成し、関連するファイルをすべてそのフォルダに保存すると使用できます。
ボットが読み込まれると、ファイルはチャンク化され、インデックスが付けられ、ローカルのベクターデータベースに保存されます。ユーザーが質問すると、最も関連性の高いチャンクが取得され、大規模言語モデルに送信されるコンテキストに追加されます。

```
topical_rail
├── kb
│   └── report.md
├── config.yml
├── jobs.co
└── off-topic.co
```

# Topical Rails

コンテキストと知識ベースが整ったところで、「Topical Railsの設定」に進みます。colangの2つの重要な側面、ユーザー／ボットのメッセージと フローを設定します。
メッセージとフローの[正規形](https://github.com/NVIDIA/NeMo-Guardrails/blob/39eef9a45dbc94fc4a66fac3b859f75b001d89e8/docs/getting_started/hello-world.md#hello-world-example)を書くことでレールを書きます。

正規形とはユーザーとボットのメッセージの省略記法でLLMがそれらを会話の一部として理解し、処理することを容易にします。

## ユーザーとボットのメッセージ

まず、基本的なユーザークエリから始めます。ユーザーメッセージは能力を尋ねると定義し、次に、簡単な自然言語でボットの能力について尋ねるユーザーとして、どのようなユーザーのクエリの種類を参照することができるか、いくつかの例を提供します。

```
define user ask capabilities
  "What can you do?"
  "What can you help me with?"
  "tell me what you can do"
  "tell me about you"
```

ボットはユーザーが何について質問しているのかを認識できるようになりました。次のステップは、ボットがその質問に答える方法を理解していることを確認することです。

```
define bot inform capabilities
  "I am an AI assistant which helps answer questions based on a given knowledge base. For this interaction, I can answer question based on the job report published by US Bureau of Labor Statistics."
```

## フローの使用

[フロー](https://github.com/NVIDIA/NeMo-Guardrails/blob/main/docs/user_guide/colang-language-syntax-guide.md#concepts)とは会話をどのように展開させたいかを表します。ユーザーメッセージ、ボットメッセージ、その他のイベントのシーケンスが含まれます。

以下は最も単純なフローです。

```
define flow
  user ask capabilities
  bot inform capabilities
```

フローとメッセージは両方とも[jobs.co](https://github.com/NVIDIA/NeMo-Guardrails/blob/39eef9a45dbc94fc4a66fac3b859f75b001d89e8/examples/topical_rail/jobs.co)で定義されています。

## 知識ベースからの質問に答える

1. ボットは関連情報を検索する必要があります。
2. ボットはその情報を使って返答を練る必要があります。

ユーザーが雇用統計の家計調査データについて質問した場合に知識ベースから回答します。

```
define flow
  user ask about household survey data
  bot response about household survey data

define user ask about household survey data
  "How many long term unemployment individuals were reported?"
  "What's the number of part-time employed number?"
```

## 関連性のない会話から遠ざかる

ボットに関連する質問に答えさせることができます。次の問題は、トピックから外れた質問を扱う方法です。

トピックから外れた質問については、2つの異なる方法で対処することができます。

`off-topic`を書くことです。

flowの部分でbotの動作を定義しています。Userが`user ask off topic`の内容を訪ねた場合にbotは`bot explain cant help with off topic`の動作をします。
`define`で`user ask off topic`と`bot explain cant help with off topic`が定義されています。


```
define user ask off topic
    "Who is the president?"
    "Can you recommend the best stocks to buy?"
    "Can you write an email?"
    "Can you tell me a joke?"
    ...

define bot explain cant help with off topic
    "I cannot comment on anything which is not relevant to the job report"

define flow
    user ask off topic
    bot explain cant help with off topic
```

# ボットを起動

API、サーバとのコマンドラインインターフェイス、またはUIを使ってボットと対話することができます。

## API

API経由でボットを使用する方法は下記です。

- すべてのコンフィギュレーション・ファイルとレールにパスを設定する必要があります。
- チャットAPIのために、ほとんどの場合ユーザーである役割と、ボットによって使用される質問またはコンテキストを提供する必要があります。

```py
from nemoguardrails import LLMRails, RailsConfig

# Give the path to the folder containing the rails
config = RailsConfig.from_path(".")
rails = LLMRails(config)

# Define role and question to be asked
new_message = rails.generate(messages=[{
    "role": "user",
    "content": "How can you help me?"
}])
print(new_message)
```

[詳細](https://github.com/NVIDIA/NeMo-Guardrails/blob/39eef9a45dbc94fc4a66fac3b859f75b001d89e8/docs/user_guide/interface-guide.md#python-api)

下記のようなログを確認できます。必要なファイルがダウンロードされ、動作結果を確認できます。

```
Downloading (…)e9125/.gitattributes: 100%|██████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 1.18k/1.18k [00:00<00:00, 2.06MB/s]
Downloading (…)_Pooling/config.json: 100%|███████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 190/190 [00:00<00:00, 209kB/s]
Downloading (…)7e55de9125/README.md: 100%|██████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 10.6k/10.6k [00:00<00:00, 13.6MB/s]
Downloading (…)55de9125/config.json: 100%|██████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 612/612 [00:00<00:00, 1.01MB/s]
Downloading (…)ce_transformers.json: 100%|███████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 116/116 [00:00<00:00, 204kB/s]
Downloading (…)125/data_config.json: 100%|███████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 39.3k/39.3k [00:00<00:00, 240kB/s]
Downloading pytorch_model.bin: 100%|████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 90.9M/90.9M [00:08<00:00, 11.3MB/s]
Downloading (…)nce_bert_config.json: 100%|█████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 53.0/53.0 [00:00<00:00, 489kB/s]
Downloading (…)cial_tokens_map.json: 100%|███████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 112/112 [00:00<00:00, 207kB/s]
Downloading (…)e9125/tokenizer.json: 100%|████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 466k/466k [00:00<00:00, 2.65MB/s]
Downloading (…)okenizer_config.json: 100%|███████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 350/350 [00:00<00:00, 654kB/s]
Downloading (…)9125/train_script.py: 100%|██████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 13.2k/13.2k [00:00<00:00, 20.7MB/s]
Downloading (…)7e55de9125/vocab.txt: 100%|█████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 232k/232k [00:00<00:00, 692kB/s]
Downloading (…)5de9125/modules.json: 100%|███████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 349/349 [00:00<00:00, 699kB/s]
{'role': 'assistant', 'content': 'I am an AI assistant which helps answer questions based on a given knowledge base. For this interaction, I can answer question based on the job report published by US Bureau of Labor Statistics.'}
```

## UI

Colangを使うと、ユーザーは純正のUIを使ってサーバーとやりとりすることができます。サーバを起動し、UIにアクセスしてこの例と対話するには、以下の手順を推奨します：

`nemoguardrails server`コマンドでサーバを起動します。サーバーが起動したら、`http://localhost:8000`からUIにアクセスします。

アクセスすると下記のような画面になります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/10496/51619467-ec09-5ffb-de09-86070aa9d673.png)

Choose a guardrails configuration:からtopical_railを選択を選択します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/10496/bb14d3a7-c6cd-0668-2def-0d14d78dd8da.png)

ボットの能力を確認してみます。

[ユーザーとボットのメッセージ](#ユーザーとボットのメッセージ)で定義した処理が行われます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/10496/b160abd1-b296-46de-c0d7-a9d7e0664345.png)

次に知識ベースから回答をさせてみます。

[知識ベースからの質問に答える](#知識ベースからの質問に答える)で定義した処理が行われます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/10496/4ef78e2d-12bf-9ddd-6ad2-2e76b238ee51.png)

関連性のない会話をやってみます。

[関連性のない会話から遠ざかる](#関連性のない会話から遠ざかる)で定義した処理が行われます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/10496/72d8ff6c-a142-562a-46da-1a09296ad1a5.png)


より詳細を確認したい方は[Guardrails Server Documentation](https://github.com/NVIDIA/NeMo-Guardrails/blob/39eef9a45dbc94fc4a66fac3b859f75b001d89e8/docs/user_guide/interface-guide.md#guardrails-server)を参照してください。

## コマンドライン・チャット

下記でコマンドラインインターフェイスを使用してボットとチャットできます。

```
nemoguardrails chat --config=.
```

下記のような対話例になります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/10496/1a9f783c-21a3-9cd7-59e5-547fc8bfd140.png)


# 日本語で試す

NeMo-Guardrailsのアーキテクチャは下記のようになっています。
入力、flow、出力に対してK-NN vector searchで適切な処理を検索しているため、日本語に変えた方が適切なベクトルが生成され、精度が上がると思われます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/10496/9fc6ccb2-3de7-4f1c-7fa8-4fc4d3831fdb.png)


ここから必要な部分を日本語にして日本語で試してみます。

`config.yml`を下記のように修正します。

```
instructions:
  - type: general
    content: |
      以下は、ボットとユーザーによる最近の求人情報に関する会話です。
      ボットは事実に基づいて簡潔に説明します。ボットが質問の答えを知らない場合
      を知らない場合は、正直に「知らない」と答えます。

sample_conversation: |
  user "こんにちは！"
    express greeting
  bot express greeting
    "こんにちは！本日はどのようなご用件でしょうか？"
  user "何かご用ですか？"
    ask about capabilities
  bot respond about capabilities
    "私は、与えられた知識ベースに基づいて質問に答えるのを助けるAIアシスタントです。今回の対話では、米国労働統計局が発表した求人情報に基づいて質問に答えることができます。"
  user "米国労働統計局について教えてください。"
    ask question about publisher
  bot response for question about publisher
    "労働統計局は、労働経済と統計の広い分野で連邦政府のための主要な事実調査機関です。"
  user "ありがとう"
    express appreciation
  bot express appreciation and offer additional help
    "どういたしまして。また何かご質問やお手伝いできることがありましたら、遠慮なくお尋ねください。"

models:
  - type: main
    engine: openai
    model: text-davinci-003
```

`jobs.co`を修正します。

```
define user ask capabilities
  "何ができますか？"
  "何を手伝ってくれますか？"
  "何ができるか教えてください"
  "あなたのことを教えてください"
  "あなたの助けをどのように利用できますか？"

define flow
  user ask capabilities
  bot inform capabilities

define bot inform capabilities
  "私は、与えられた知識ベースに基づいて質問に答えるのを助けるAIアシスタントです。このインタラクションでは、米国労働統計局が発表した求人情報に基づいて質問に答えることができます。"

define flow
  user ask about headline numbers
  bot response about headline numbers

define user ask about headline numbers
  "非農業部門雇用者数はどのくらい増加しましたか？"
  "非農業部門雇用者数の動きは？"
  "今月の失業率は？"

define flow
  user ask about household survey data
  bot response about household survey data

define user ask about household survey data
  "長期失業者数は？"
  "パートタイム雇用者数は？"

define flow
  user ask about establishment survey data
  bot response about establishment survey data

define user ask about establishment survey data
  "運輸・倉庫業の雇用状況は？"
  "運輸・倉庫業はどうでしたか？"

```

`off-topic.co`を修正します。

```
define user ask off topic
  "どんな株を買えばいいですか？"
  "おすすめの銘柄を教えてください"
  "おすすめのレストランを教えてください"
  "レストランをご存知ですか？"
  "名前を教えてもらえますか？"
  "あなたの名前は？"
  "絵が描けますか？"
  "ジョークを言ってくれますか？"
  "世界で一番大きな都市は？"
  "メールを書けますか？"
  "メールを書いてほしい"
  "大統領は誰？"
  "選挙ではどの政党が勝つ？"
  "誰に投票すればいいですか？"

define flow
  user ask off topic
  bot explain cant off topic

define bot explain cant off topic
  "申し訳ありませんが、雇用統計に関係のないことにはコメントできません。"

define flow
  user ask general question
  bot respond cant answer off topic

```

`kb/report.md`も同様に日本語に翻訳しましたが割愛します。

ディレクトリ構成は下記のようになり、日本語と英語用のディレクトリができています。

```
.
├── config.yml
├── simple_api_test.py
├── topical_rail
│   ├── api_client.py
│   ├── api_client_test.py
│   ├── config_open_ai.yml
│   ├── config.yml
│   ├── jobs.co
│   ├── kb
│   │   └── report.md
│   ├── off-topic.co
│   └── README.md
└── topical_rail_ja
    ├── api_client.py
    ├── api_client_test.py
    ├── config_open_ai.yml
    ├── config.yml
    ├── jobs.co
    ├── kb
    │   └── report.md
    ├── off-topic.co
    └── README.md

```

このディレクトリで下記コマンドでサーバーを起動します。verboseをつけているのでログを確認できます。

```
nemoguardrails server --config . --verbose
```

サーバーが起動したら、`http://localhost:8000`からUIにアクセスします。

作成した`topical_rail_ja`を選択します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/10496/7ca07c55-b80f-eb11-fce7-62e726707672.png)

下記のように日本語で対応してくれます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/10496/dc9070b3-a5f7-12bd-9cec-b9cb6f76df34.png)

ログを見ると`express greeting`に分類されています。

```
INFO:nemoguardrails.rails.llm.llmrails:Conversation history so far: 
user "こんにちは"
  express greeting
bot express greeting
  "こんにちは！本日はどのようなご用件でしょうか？"

```

定義されている内容は"何ができますか？"でしたが異なる言い方で質問しましたが`ask capabilities`に分類して対応しています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/10496/ecc2d630-ce9d-aeaf-729f-dca6c4dcf96b.png)

```
INFO:nemoguardrails.rails.llm.llmrails:Conversation history so far: 
user "こんにちは"
  express greeting
bot express greeting
  "こんにちは！本日はどのようなご用件でしょうか？"
user "何ができるの？"
  ask capabilities
bot inform capabilities
  "私は、与えられた知識ベースに基づいて質問に答えるのを助けるAIアシスタントです。このインタラクションでは、米国労働統計局が発表した求人情報に基づいて質問に答えることができます。"

```

"3月の平均労働時間はいくつですか"という質問をしてこの質問がナレッジベースから回答されるか確認します。
ナレッジベースの下記部分を引用しており、適切な回答をしています。

```
3月の平均労働時間は34.4時間で、0.1時間減少した。製造業の平均労働時間は横ばいの40.3時間であった。
残業時間は3.0時間にとどまった。製造業と非管理職の平均週
民間非農業部門における生産・非管理職の平均労働時間は33.9時間で横ばい。
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/10496/fc265473-39be-1a44-2dd9-d3cab4a02890.png)


```
bot response about headline numbers
  "3月の非農業部門雇用者数は23万6千人増、失業率は3％でした。レジャー・接客業、政府機関、専門職・ビジネス・サービス業、医療サービス業は引き続き増加傾向にあります。"
user "3月の平均労働時間はいくつですか"
  ask about headline numbers
bot response about headline numbers
  "3月の平均労働時間は34.4時間で、0.1時間減少した。製造業の平均労働時間は横ばいの40.3時間であった。残業時間は3.0時間にとどまった。製造業と非管理職の平均週民間非農業部門における生産・非管理職の平均労働時間は33.9時間で横ばい。

```

関連性のない会話をやってみます。`ask off topic`に分類され、適切に動作しています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/10496/1d51c3f1-ff4a-8845-429d-5bb5ddf59453.png)


```
user "株について教えて"
  ask off topic
bot explain cant off topic
  "申し訳ありませんが、雇用統計に関係のないことにはコメントできません。"

```

# まとめ

本記事ではNeMo-GuardrailsによるTopical Railsを試してみました。必要な内容はナレッジベースから回答することで正確な内容を担保しつつ、専門外の内容は回答しないように振る舞えるため、特化型のChat機能を開発するには有用な機能だと思います。また日本語でも動作したため、英語限定の動作ではないことが良いと思います。