Databricksで学ぶプロンプトの基礎

こちらのノートブックをウォークスルーしたら勉強になりました。

https://github.com/yasmk/apj-workshops-2024/blob/main/Lab%2005%20-%20GenAI/1.%20Prompt%20Engineering.py

日本語に対応させながらウォークスルーしていきます。

# プロンプトの基礎

プロンプトの基礎を探索しましょう。

詳細はこちらをご覧ください: https://www.promptingguide.ai/

## ライブラリのセットアップ

```py
%pip install mlflow==2.11.1 llama_index==0.10.17 langchain==0.1.10
dbutils.library.restartPython()
```

```py
# 単なるREST呼び出しではありますが、Langchainラッパーを使います
from langchain_community.chat_models import ChatDatabricks
from langchain_core.messages import HumanMessage, AIMessage, SystemMessage

pipe = ChatDatabricks(
    target_uri = 'databricks',
    endpoint = 'databricks-mixtral-8x7b-instruct',
    temperature = 0.1
)
```

# 基本的なプロンプト

始めるのは簡単です。テキストを送信できます。異なるモデルは異なる反応を示すことを覚えておいてください！同じモデルでも、プロンプトを再実行すると異なる反応を示すことがあります。（ただし、基本的な1行のプロンプトでこれを見ることはほとんどないでしょう）

```py
prompt = "空は"
output = pipe([HumanMessage(content=prompt)], max_tokens=100)
str_output = print(output.content)
```
```
「空」は日本語で「そら」や「 heaven 」という意味で使います。空は大気圏や宇宙などがあり、星球の上にある場所を指します。空は人類が生きる場所でもあり、航空機や衛星などが運動する場所でもあります。空気
```

```py
prompt = "こんにちは、あなたはどなた？"
output = pipe([HumanMessage(content=prompt)], max_tokens=100)
str_output = print(output.content)
```
```
こんにちは！私はAIです。人工知能の言語モデルで、日本語と英語の両方を話すことができます。あなたのお手伝いをすることができますか？
```

# ゼロショットプロンプティング(Zero Shot Prompting)

ゼロショットは、モデルに何かを尋ねる最も基本的な方法です。タスクを定義して、ただ尋ねるだけです！

```py
prompt = """
    Textを中立、ネガティブ、ポジティブに分類して日本語で回答してください。
    Text: 休暇はまあまあだったと思う。
    Sentiment:
"""

output = pipe([HumanMessage(content=prompt)], max_tokens=100)
str_output = print(output.content)
```
```
中立 (Neutral)

The given text, "休暇はまあまあだったと思う。" ( "*I think the holiday was so-so.* "), is considered neutral in sentiment. It does not express strong positive or negative emotions, but rather a moderate or passing opinion.
```

最初の試行では、何かゴミが出てしまったかもしれません（モデルは確率的ですので）。 それは、私たちのプロンプトが問題を抱えているためです。 異なるモデルには、それぞれ異なる「プロンプトテンプレート」があります。 Llama 2 の公式のものを使用してみましょう。

```py
prompt = """


Textを中立、ネガティブ、ポジティブに分類して日本語で回答してください。


Text: 休暇はまあまあだったと思う。
Sentiment: 

"""

output = pipe([HumanMessage(content=prompt)], max_tokens=100)
str_output = print(output.content)
```

```
Sentiment: 中立

休暇はまあまあだったと思う」は、ややネガティブな意味合いを含む表現ですが、まったくのネガティブではありません。そのため、この文章のセンティメントを中立とします。
```

llama 2は、`
`タグを使用して指示全体を強調します。`
`はシステムプロンプトであり、モデルに対して応答方法を指示するガイドです。サンプルでは、ユーザーの質問は`Text:`フィールドの後に来ます。この形式を採用すると、より良い応答が得られるはずです。

```py
prompt = """


以下に基づいて日本語で質問に回答してください:


Fedの6月13日から14日の会合の議事録によると、ほとんどの役員が金利を5%から5.25%の目標範囲内で変更しないことを「適切または受け入れ可能」と見なしていましたが、一部の役員は四半期ポイントの増加を支持したでしょう。

User Question: 以下の段落での金利はいくつですか？
Answer: 

"""

output = pipe([HumanMessage(content=prompt)], max_tokens=100)
str_output = print(output.content)
```
```
金利は5%から5.25%の目標範囲内です。
```

# フューショットプロンプティング(Few Shot Prompting)

モデルがロジックをより良く理解するのを支援する方法の一つは、サンプルを提供することです。

```py
prompt = """




お客様に適したアカウントタイプを提案してください。

    

以下はいくつかの例です：
- 消費者は貯蓄口座を希望しています
- ビジネスはビジネス口座を希望しています
- テックのユニコーンは特別なVC口座を受けるべきです

Question:
小規模ビジネスにはどのアカウントをお勧めしますか？

"""

output = pipe([HumanMessage(content=prompt)], max_tokens=100)
str_output = print(output.content)
```
```
小規模ビジネスにはビジネス口座 (Business Account) をお勧めします。このタイプのアカウントは、会社の預金や支出を管理するのに適しています。さらに、このタイプのアカウントでは、通常、より多くの取引口座と、
```

# チェーンオブソートプロンプティング(Chain of Thought Prompting)

チェーンオブソートプロンプティングでは、モデルにどのように理由づけをするのかを示します。これにより、タスクを適切に実行する方法を推論するのに役立ちます。

## 標準的なプロンプト - 直接お願いをします

```py
user_question = """カフェテリアには23個のリンゴがありました。昼食に20個使用し、さらに6個購入した場合、リンゴの数はいくつになりますか？"""

prompt = f"""




お客様に役立つ回答を日本語で提供し、お客様をガイドしてください。

    

以下は回答の例です：
質問：
私は市場に行って10個のリンゴを買いました。
2個のリンゴを隣人に、2個のリンゴを修理工にあげました。
その後、さらに5個のリンゴを買って1個食べました。

回答：
答えは10です。

上記を参考に、次の質問に回答してください。
質問:
{user_question}

"""


output = pipe([HumanMessage(content=prompt)], max_tokens=100)
str_output = print(output.content)
```

```
回答: 9 個です。

原因: カフェテリアに初期に 23 個のリンゴがあり、昼食に 20 個使用され、その後 6 個を購入したため、残りのリンゴの数は 23 - 20 + 6 = 9 個となります
```

## チェーンオブソートのプロンプト

```py
user_question = """カフェテリアには23個のリンゴがありました。昼食に20個使用し、さらに6個購入した場合、リンゴの数はいくつになりますか？"""

prompt = f"""




お客様に役立つ回答を日本語で提供し、お客様をガイドしてください。

    

以下は回答の例です：
質問：
私は市場に行って10個のリンゴを買いました。
2個のリンゴを隣人に、2個のリンゴを修理工にあげました。
その後、さらに5個のリンゴを買って1個食べました。

回答：
私たちは10個のリンゴを持っていました。隣人と修理工にそれぞれ2個ずつあげました。
10 - 2 - 2 = 6です。5個買って1個食べました。6 + 5 - 1 = 10です。答えは10です。

上記を参考に、次の質問に回答してください。
質問：
{user_question}

"""

output = pipe([HumanMessage(content=prompt)], max_tokens=100)
str_output = print(output.content)
```

```
カフェテリアにあるリンゴの数は、23 個です。昼食に 20 個使用し、その後 6 個購入します。
23 - 20 + 6 = 9 個のリンゴがカフェテリアに残ります。
```

# システムプロンプト

システムプロンプトは、モデルに指示を出すためだけでなく、応答を調整するためにも使用できます。我々はこれを既に見ています。それは`
`タグの内部の部分です。これらはレスポンス大きな影響を与えることがあります！

```py
system_prompt = '日本語でお客様に役立つ回答を提供し、銀行口座の選択における重要な要素のいくつかを丁寧に説明して、お客様に口座の種類を提案してください。'

user_question = '私は一人のホームレスの男です。どのような口座を開設すべきですか？'

prompt = f"""




{system_prompt}

    

以下はいくつかの例です：
消費者は普通預金口座を希望しています
ビジネスは法人口座を希望しています
テックのユニコーンは特別なベンチャーキャピタル口座を値するでしょう

Question:
{user_question}

"""

output = pipe([HumanMessage(content=prompt)], max_tokens=100)
str_output = print(output.content)
```
```
日本語でお客様に役立つ回答を提供するために、まずは、お客様のニーズと要件について理解する必要があります。ホームレスの男として、お客様は、どのようなサービスや機能を求めているのか
```

# プロンプトのフォーマット

プロンプトのフォーマットは、様々なLLMに対するプロンプトの構造化の助けとなります。それぞれのLLMには異なる標準がある場合があります。

**Stanford Alpaca structure**

```
Below is an instruction that describes a task.
Write a response that appropriately completes the request.
### Instruction:
{user question}
### Response:
```

**llama v2 format**
```


 

You are a friendly assistant. Be Polite and concise.



Answer the following question:
{user question}


```

# Retrieval Augmented Generation

もし私がボットに何か突飛なことを尋ねたらおそらく答えられないでしょう。トレーニングは高価です。では、文脈を与えたらどうでしょうか？

**文脈なし**

```py
system_prompt = '親切な日本人の司書として、提供された質問に簡潔かつ雄弁に日本語で答えてください。'

user_question = '5歳の私に、LK-99を説明してください'

prompt = f"""




{system_prompt}

    

Question:
{user_question}

"""

output = pipe([HumanMessage(content=prompt)], max_tokens=100)
str_output = print(output.content)
```
```
Ohayou gozaimasu! (おはようございます！)

LK-99は、すごいロボットですね！ (LK-99 is a really cool robot, isn't it!)

このロボットは、人間のようにwalkすることができます。 (This robot can walk like a human, can't it!)
```

**文脈あり**

```py
system_prompt = '親切な日本人の司書として、提供された質問に簡潔かつ雄弁に日本語で答えてください。'

user_question = '5歳の私に、LK-99を説明してください'

prompt = f"""


{system_prompt}


以下の文脈をベースにしてください:

LK-99は、灰色‒黒色の外観を持つ潜在的な室温超伝導体です。六角形の構造を持ち、鉛‒アパタイトからわずかに変更されており、少量の銅が導入されています。室温超伝導体とは、0°C（273K; 32°F）以上の動作温度で超伝導性を示すことができる材料のことで、日常環境で到達および容易に維持できる温度です。

以下の質問に回答してください:
{user_question}

"""

output = pipe([HumanMessage(content=prompt)], max_tokens=100)
str_output = print(output.content)
```
```
「LK-99は、えんじのような外観をした材料です。六角形の形をしていて、ちょっとした銅が入っています。この材料は、室温超伝導体です。それは、0度 Celsius（273K; 32°F
```

# MLflowによるプロンプトの管理

ここまで見てきたように、プロンプトの記録は大変なことになりえます！\
すでに、プロンプトと回答を記載したスプレッドシートを使う羽目になっているかもしれません！\
MLflowではLLMをサポートしていますが、依然として改善を要する領域であり、既に素晴らしい進歩を遂げています。

- [MLflow 2.3の紹介：LLMのネイティブサポートと新機能による強化 | Databricks Blog](https://www.databricks.com/jp/blog/2023/04/18/introducing-mlflow-23-enhanced-native-llm-support-and-new-features.html)
- [MLflow 2.4を発表：ロバストなモデル評価のためのLLMOpsツール | Databricks Blog](https://www.databricks.com/jp/blog/announcing-mlflow-24-llmops-tools-robust-model-evaluation)

MLflow 2.3で追加された、LLMトラッキングAPIをクイックに見てみましょう。完全な説明はこちらをご覧ください: https://mlflow.org/docs/latest/llm-tracking.html


```py
import mlflow
import pandas as pd

username = spark.sql("SELECT current_user()").first()['current_user()']
mlflow_dir = f'/Users/{username}/mlflow_log_hf'
mlflow.set_experiment(mlflow_dir)
```

## プロンプトの評価

```py
common_test_prompts = [
    "オーストラリアのパースは何で有名？",
    "パースのバーガーのトップ10を教えて",
    "なぜ鉄鉱石が良いのか、そのインフォマーシャルスクリプトを書いてください。",
    "オムレツを作る最良の方法は何ですか？",
    "もし100万ドルを持っていたら、あなたは何をしますか？"
]

testing_pandas_frame = pd.DataFrame(
    common_test_prompts, columns = ['prompt']
)
```
```py
# MLflow 2.8以降ではevaluateを使う必要はなく、pyfuncの関数でOKです
def eval_pipe(inputs):
    answers = []
    for index, row in inputs.iterrows():
        # pipe([HumanMessage(content=prompt)], max_tokens=100)
        result = pipe( [HumanMessage(content=row.item())], max_tokens=100)
        answer = result.content
        answers.append(answer)
    
    return answers
```
```py
model = 'databricks-mixtral-8x7b-instruct'
with mlflow.start_run(run_name=model):
    pipe = ChatDatabricks(
            target_uri = 'databricks',
            endpoint = model,
            temperature = 0.1
        )
    
    results = mlflow.evaluate(eval_pipe, 
                          data=testing_pandas_frame, 
                          model_type='text')
    

model = 'databricks-llama-2-70b-chat'
with mlflow.start_run(run_name=model):
    pipe = ChatDatabricks(
            target_uri = 'databricks',
            endpoint = model,
            temperature = 0.1
        )
    
    results = mlflow.evaluate(eval_pipe, 
                          data=testing_pandas_frame, 
                          model_type='text')
```

上を実行すると二つのMLflowランが記録されます。
![Screenshot 2024-09-12 at 17.40.32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1168882/83e7c92f-9c1e-18f4-bbfc-dc0ac2237661.png)

エクスペリメントの画面で**評価**タブをクリックすると、記録されたプロンプトとモデルごとのレスポンスを容易に比較することができます。
![Screenshot 2024-09-12 at 17.41.17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1168882/ed73e672-efe2-0b44-3696-ffda24b986df.png)

### はじめてのDatabricks

[はじめてのDatabricks](https://qiita.com/taka_yayoi/items/8dc72d083edb879a5e5d)

### Databricks無料トライアル

[Databricks無料トライアル](https://databricks.com/jp/try-databricks)