【UiPath×OpenAI】開発者向けのプロンプトエンジニアリング④ 応用編 チャットボットの作成

LLM（Large Language Models）は人々の生活を着実に変えており、開発者にとっては非常に強力なツールです。LLMが提供するAPIを利用することで、開発者は素早く便利なアプリケーションを開発することができます。
OpenAIは、LLMを開発者に提供するためのAPIを提供しており、これを使用することで、開発者は自分のアプリケーションやプロジェクトにLLMの能力を組み込むことができます。

UiPathでOpenAIを使用すると、本来ルールベースのRPAでできなかったこと、例えば、自然言語処理やチャットボットなども可能になり、UiPath開発者として、OpenAIを使用する際のプロンプト文の構成原則やテクニックを把握することで、自動化の範囲が更に広がると思います。

# 記事概要
本記事は以下の講座を纏めた内容です。

https://www.deeplearning.ai/short-courses/chatgpt-prompt-engineering-for-developers/

これが開発者向けのプロンプトエンジニアリングガイドです。Andrew Ng先生の「ChatGPT Prompt Engineering for Developers」講座を基に、**初心者向けのLLM開発者向けに、プロンプトの構築方法とOpenAIのAPIを使用して要約、推論、変換などの一般的な機能を実現する方法をわかりやすく紹介しています。**
記事の最後に、UiPath Studioを使用して、講座でのそれぞれのテーマを実現しました。UiPath×OpenAIの開発において、この情報を活用することで、より高度な自動化ワークフローの開発を期待できます。

本記事では、**OpenAIを使用してカスタムできるチャットボット**を解説し、最後に、UiPath×OpenAIの応用も説明します。
本記事で作成したUiPath WorkflowとUiPath Appsファイルは以下の通りです。ご参照ください。
- [【UiPath Workflow】開発者向けのプロンプトエンジニアリング④ 応用編](https://drive.google.com/file/d/1L6mUcyvLGrIoOIs5kgkekRh83gozE2wo/view?usp=drive_link)
- [ビザ大好きロボットくん.uiapp](https://drive.google.com/file/d/1NhX3QusbKVrCVtIdueoonw7eoDMfQ3S_/view?usp=drive_link)

# 環境設定
この記事では、OpenAIが提供するChatGPT APIを使用しています。したがって、最初にChatGPTのAPIキーを取得する必要があります（または公式ウェブサイトでオンラインテストを実施することもできます）。次に、openaiのライブラリをインストールする必要があります。
openai：
```bash
pip install openai
```
ライブラリのインポートと自分のAPIキーを設定します。
```bash
import openai
import os
openai.api_key='自身のOpenAI キー'
```
これから、OpenAIが提供するChatCompletion APIの使用方法を詳しく確認します。ここでは、それを関数にラップしてみます。内部の仕組みを理解する必要はありません。単に、この関数を呼び出すと、プロンプトに対応するCompletionが提供されることを知っていれば十分です。
```python
# 「Prompt」を受け取り、それに対応する結果を返す関数
# gpt-3.5-turbo
def get_completion_from_messages(messages, model="gpt-3.5-turbo", temperature=0):
    response = openai.ChatCompletion.create(
        model=model,
        messages=messages,
        temperature=temperature, # 生成されたテキストの多様性や予測のランダム性を調整するためのパラメータ
    )
#     print(str(response.choices[0].message))
    return response.choices[0].message["content"]
```
# ロールと対話コンテキストの設定

ChatGPTのWeb画面では、質問者のメッセージはユーザーメッセージと呼ばれ、ChatGPTのメッセージはアシスタントメッセージと呼ばれます。ただし、チャットボットを構築する際には、システムメッセージを送信した後、質問者のロールはユーザー(user)のままでも良いし、または、ユーザーとアシスタント(assistant)の間で切り替えることができ、対話のコンテキストを提供できます。

## ロールとは

ロールは、AIの対話モデルでのインタラクションを表すためのもので、対話の各要素がどの種類のアクターから発生しているのかを明確に示します。具体的には、「システム」「ユーザー」「アシスタント」の三つのロールがあります。
これらのロールは、ChatGPTの効果的な動作とユーザとのスムーズなインタラクションをサポートします。
- **システム**：「システム」は、チャットシステム自体を指します。この役割は、**チャットの制御や管理**に関与します。また、**アシスタントに対する指示やコンテキスト**を提供する役割も果たします。
- **ユーザー**：「ユーザー」は、**チャットを使用する人**を指します。ユーザーはチャットにメッセージを入力し、質問や要求を行ったり、アシスタントからの返答を受け取ったりします。
- **アシスタント**：「アシスタント」は、**ユーザーの質問や要求に対して回答や支援を提供**する役割を持ちます。



以下のプレイグラウンドで試してみてください。

https://platform.openai.com/playground


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/177240/23abd15f-f7d4-fa82-1974-f740c799f6bd.png)


Pythonで実施してみると：

```python 
messages = [
    {'role':'system', 'content':'こんにちは、私は経験豊富な小学校の英語の先生です。'},
    {'role':'user', 'content':'子供の語学教育のアドバイスをください。'}]
response = get_completion_from_messages(messages, temperature=1)
print(response)
```

回答：
```
子供の語学教育のアドバイスは以下の通りです：

1. 楽しく学ぶことを重視してください：子供は遊びながら学ぶことが好きですので、教材や活動を通じて楽しく英語を学ばせるようにしてください。ゲームや歌、絵本などを使って、子供が興味を持てるような学習環境を作りましょう。

2. リスニングを重視して育ててください：小さい時期から英語のリスニングに慣れさせることは非常に重要です。子供に英語の音をたくさん聞かせるようにしましょう。英語の歌やアニメ、映画などを通じて、自然にリスニング力を身に付けることができます。

3. コミュニケーションに重点を置いてください：英語を話す機会を積極的に作りましょう。子供が英語でコミュニケーションをとる機会を増やすことで、自信を持って話すことができるようになります。外国人との交流や英会話教室への参加など、英語を使う機会を探すことが重要です。

4. 定期的な復習を行ってください：言語学習は定期的な復習が重要です。子供が学んだ内容を定期的に復習し、定着させるようにしましょう。アウトプットの機会も大切ですので、学んだ言葉やフレーズを使って会話したり、短い文章を書いたりする練習も取り入れましょう。

5. 学習意欲を育てるようにしましょう：子供の学習意欲を高めるために、英語を使って楽しい活動や挑戦を提供してください。クイズやゲーム、チャレンジプロジェクトなど、目標を持って取り組むことで子供のモチベーションを高めることができます。

以上のアドバイスを参考にして、子供の語学教育を楽しく効果的に行ってみてください。
```

## 対話コンテキストの設定

今までは`get_completion`関数を利用して、単一の対話ができています。例えば、以下ような例：

```python 
messages =  [
{'role':'system', 'content':'あなたは小学校の優しい先生です。'},
{'role':'user', 'content':'おはようございます。私の名前は二郎です'}  ]
response = get_completion_from_messages(messages, temperature=1)
print(response)
```

回答：
```python 
おはようございます、二郎くん。私はあなたの優しい先生です。どんなことでお悩みですか？話してみましょう。
```

但し、次は再度名前を聞くと：

```python 
messages =  [
{'role':'system', 'content':'あなたは小学校の優しい先生です。'},
{'role':'user', 'content':'おはようございます。私が認知障害なので、自分の名前が忘れました、私の名前を教えてください。'}  ]
response = get_completion_from_messages(messages, temperature=1)
print(response)
```

前回の名前が覚えられていません：
```python 
おはようございます。あなたのお名前がわからないのですね。心配しないでください、一緒に探しましょう。私たちは一緒に楽しく学ぶことができるように、お互いの名前を知る必要がありますね。まずは少し話してみましょう。好きな食べ物や趣味、特技など、私に教えていただけるととても嬉しいです。それを元に、あなたの名前を思い出せるかもしれませんよ。一緒に頑張りましょうね
```

以上のように、各会話はそれぞれ独立しており、モデルに現在の対話で参照するためにすべての関連メッセージを提供しなければならないです。モデルに対話の履歴を参照または「記憶」させたい場合は、モデルの入力で既に発生した対話履歴を提供する必要があります。これをコンテキスト（context）と呼んでいます。以下の例を試してください。


```python 
messages =  [
{'role':'system', 'content':'あなたは小学校の優しい先生です。'},
{'role':'user', 'content':'おはようございます。私の名前は二郎です'},
{'role':'assistant', 'content': "了解しました。あなたは二郎くんですね"},
{'role':'user', 'content':'私が認知障害なので、自分の名前が忘れました、私の名前を教えてください。'}  ]
response = get_completion_from_messages(messages, temperature=0)
print(response)
```

回答：

```python 
そうですか、二郎さんは認知障害をお持ちなんですね。心配しないでください、私がお手伝いします。あなたの名前は二郎さんですよ。
```

以上のように、モデルには対話履歴が提供されており、それは前の対話で言及された名前であり、それからは同じ質問、すなわち私の名前は何かを尋ねます。モデルは必要な全ての文脈を持っているので、入力されたメッセージリストで見たように、それに対応することができます。

# ピザ注文ロボット

これから"注文ロボット"を構築します。ロボットくんは、お客様からの注文内容を収集してピザ屋からの注文を受け付ける必要があります。

以下の関数は、お客様からの注文内容を収集するために使用されます。この関数は、ウェブ画面のUIからプロンプトを収集し、それを「コンテキスト（context）」という名前のリストに追加します。そして、モデルを呼び出すたびにこのコンテキストを使用します。モデルのレスポンスもコンテキストに追加されるため、ユーザーメッセージとモデルメッセージの両方がコンテキストに追加され、コンテキストは徐々に長くなります。これにより、モデルは次に何をするべきかを判断するための必要な情報を得ることができます。

```python
# お客様からの注文内容の収集
def collect_messages(_):
    prompt = inp.value_input
    inp.value = ''
    # ウェブ画面のUIからプロンプトを収集し、それを「コンテキスト（context）」という名前のリストに追加
    context.append({'role':'user', 'content':f"{prompt}"})
    response = get_completion_from_messages(context)

    # モデルのレスポンスもコンテキストに追加
    context.append({'role':'assistant', 'content':f"{response}"})
    panels.append(
        pn.Row('お客様:', pn.pane.Markdown(prompt, width=600)))
    panels.append(
        pn.Row('ビザ大好きロボくん:', pn.pane.Markdown(response, width=600, styles={'background-color': '#F6F6F6'})))

    return pn.Column(*panels)
```

次はPythonでUI利用のため、ライブラリをインストールします。

```python 
pip install panel
```

## 実行してみる

次は、実際にピザ注文用のプロンプトを設定して実行してみよう。ロールがシステムのコンテストには、ビザの種類やロボットくんの接客流れも定義しています。

```python 
import panel as pn  # GUI
pn.extension()

panels = [] # collect display

context = [{'role':'system', 'content':"""
あなたは注文受け取りロボットで、ピザレストランのために自動で注文情報を収集する。
まず最初に顧客に挨拶をし、その後ユーザーの返信を待ちながら注文情報を収集する。
情報収集が完了したら、顧客に他に追加する内容があるかどうか確認する。
最後に、注文を持ち帰りかデリバリーか尋ね、デリバリーの場合は住所を尋ねる。
最後に、顧客に注文の合計金額を伝え、祝福を送る。

すべての選択肢、追加項目、サイズを明確にして、メニューからその項目のユニークな内容を識別できるようにする。
あなたの応答は短く、非常にカジュアルでフレンドリーなスタイルであることが求められます。

メニュー内容：

料理名：
イタリア風辛味ソーセージピザ（大、中、小） ¥1,424.5、¥1,100、¥770
チーズピザ（大、中、小） ¥1,204.5、¥1,017.5、¥715
ナスのピザ（大、中、小） ¥1,314.5、¥1,072.5、¥742.5
フレンチフライ（大、小） ¥495、¥385
ギリシャサラダ ¥797.5

トッピング：
チーズ 220円
マッシュルーム 165円
ソーセージ 330円
カナダのベーコン 385円
AIソース 165円
チリペッパー 110円

飲料：
コーラ（大、中、小） 330円、220円、110円
スプライト（大、中、小） 330円、220円、110円
ボトルウォーター 550円
"""} ]  # accumulate messages


inp = pn.widgets.TextInput(value="こんにちは、", placeholder='こちらで注文内容を入力してください')
button_conversation = pn.widgets.Button(name="チャット!")

interactive_conversation = pn.bind(collect_messages, button_conversation)

dashboard = pn.Column(
    inp,
    pn.Row(button_conversation),
    pn.panel(interactive_conversation, loading_indicator=True, height=300),
)
```

`dashboard`がGUIのオブジェクトです：

```python 
dashboard
```

実行したら、以下のような画面が表示されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/177240/ae8bc8d4-a09d-c8d1-1af6-159d3d5b5c3b.png)


これがピザ注文用の入力画面とロボットくんからの歓迎メッセージとメニューです。
お客様は入力欄にどんどん注文内容を入力して、結果は以下の通りです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/177240/2b88f773-1af7-2fb5-c2ee-6f5c702fa7fd.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/177240/3a12e11b-6445-0b79-19cf-fea1fb2e574d.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/177240/8d4469fb-a404-7991-f91e-e1cc2729dd89.png)

ものすごく短時間ですが、賢いロボットくんができました。

## 注文システムへの送信のためJSONデータ整形

上記のように、お客様からの注文内容を受付しました。しかし、この注文内容を実際に注文システムへ送信するため、注文内容を構造化する必要があります。

したがって、コンテキストのベースにシステムメッセージを追加する必要があり、これを別の指示（instruction）としています。直前の注文のJSON要約を作成し、各アイテムの価格をリストし、フィールドは`1）ピザ、サイズを含む `、 `2）トッピングリスト `、 `3）飲料リスト `、 `4）サイドディッシュリスト、サイズ `を含む、最後に `合計価格 `を含むべきである、と言っています。ここでは、ユーザーメッセージとして定義することも可能であり、必ずしもシステムメッセージである必要はありません。

一つご注意必要あるのは、ここでは比較的低い温度を使用しています。これは、このタイプのタスクに対しては、出力が比較的予測可能であることを望んでいるからです。

```python 
messages =  context.copy()
messages.append(
{'role':'system', 'content':'前回の食品注文のjsonサマリーを作成する。\
各アイテムの価格を項目ごとにリストアップし、フィールドは 1) ピザ、サイズを含む 2) トッピングリスト 3) ドリンクリスト、サイズを含む 4) サイドディッシュリスト、サイズを含む 5) 合計価格 とする'},
)

response = get_completion_from_messages(messages, temperature=0)
print(response)
```

出力結果：

```python 
注文のJSONサマリーは以下のようになります：

{
  "ピザ": [
    {
      "種類": "チーズピザ",
      "サイズ": "大",
      "価格": 1204.5
    },
    {
      "種類": "ナスのピザ",
      "サイズ": "中",
      "価格": 1072.5
    }
  ],
  "トッピングリスト": [
    {
      "トッピング": "ソーセージ",
      "価格": 330
    }
  ],
  "ドリンクリスト": [],
  "サイドディッシュリスト": [
    {
      "サイドディッシュ": "フレンチフライ",
      "サイズ": "大",
      "価格": 495
    }
  ],
  "合計価格": 3102.0
}

以上が注文のJSONサマリーです。ご確認いただければと思います。
```

注文内容をJSONのオブジェクトに整形しました。JSONデータあウェブ上でのデータ交換のために広く使用されていますので、これで他のシステムへデータ転送が簡単になります。

# 応用:UiPath Studio × UiPath Apps × OpenAIで注文ロボットくんを開発

次は、UiPath製品と連携して、注文ロボットくんのウェブアプリケーションを開発します。UiPath Appsはどんな製品なのか、こちらで詳細をふれないので、以下の記事をご参照ください。

https://qiita.com/utanesuke/items/fe061d77d7244996be6f

UiPath Apps側では、お客様とのやり取りをする画面を作ります。UiPath Studio側では、ChatGPTとの連携などのロジックを作ります。

作ったロボットくんの動作は以下の通りです。
![PizzRobot-short.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/177240/bbb4a83a-798d-c216-595d-b3236e51d73e.gif)

ロボットくんはメニューの内容を覚えているし、最後にお客様に注文を持ち帰りかデリバリーか尋ね、デリバリーの場合は住所を確認しました。

このような注文ロボットくんのウェブアプリケーションは、これほど簡単に開発できます。

開発の細かいところはこちらで説明しないので、以下のソースコードをご参照ください。

- [【UiPath Workflow】開発者向けのプロンプトエンジニアリング④ 応用編](https://drive.google.com/file/d/1L6mUcyvLGrIoOIs5kgkekRh83gozE2wo/view?usp=drive_link)
- [ビザ大好きロボットくん.uiapp](https://drive.google.com/file/d/1NhX3QusbKVrCVtIdueoonw7eoDMfQ3S_/view?usp=drive_link)

何点か実装時のポイントを説明します。
1. **セッション**：ウェブ画面が最初にロードされた際に、新しくセッション番号が作成されます。画面からUiPath ロボットへ指示を出す際に、このセッション番号も渡していますので、ロボットくんはこれが同じ人の注文かどうかを判断できます。
1. **チャット履歴**：前の注文内容を覚えるために、ローカルにキャッシュファイルを保持して、同じセッション内の質問と回答を保存しています。
1. **動的なチャット履歴の表示**：Appsでのカスタムリストを利用して、UiPath ロボットからの出力とバインドして、該当セッション内のすべてのチャット履歴データを表示しています。

また、ピザショップのメニューも外部のエクセルファイルに保存していますので、変更があった際に、Excelで変更していけば、お客様側にもすぐに反映されます。

:::note 
サンプルという位置づけで作成されているため、機能的には完成していますが、まだ不具合や性能面で改善できる点が存在すると考えています。ご理解いただければ幸いです。
:::

# 最後に
UiPath StudioとUiPath Appsを利用して、PythonやDjangoなどのプログラミング言語を知らなくても、簡単にChatGPTを活用したウェブアプリケーションを作りました。

UiPathは最後の一マイルの自動化を実現するための強力なツールです。このツールを使用することで、複数のシステムをシームレスにつなげ、業務プロセスの効率化が可能となります。さらに、OpenAIの自然言語処理技術を組み込むことで、RPAがさらに進化します。