langchianlogo

LangChain入門 – 2)プロンプト

LangChain入門の2回目です。プロンプトとは生成AIへの入力です。普通に話すような感じ(自然言語)で指示を出すのが一般的ですが、パラメタを使用して、よりフレキシブルにする方法が用意されています。ここでは、PromptTemplate, ChatTemplateといったテンプレートの使い方を見てゆきましょう。ちなみに、生成AIからの出力はCompletionなどと呼ばれます。

PromptとCompletion
LangChain入門 - 2)プロンプト 8

この記事はFuture Coders独自教材からの抜粋です。この分野は頻繁に更新されるので、この記事を見ていただいているころには状況が変わっている可能性があることご了承ください

PromptTemplate

プロンプトは生成AIへの問いかけです。繰り返して似たような質問をすることも少なくないでしょう。

  • 東京の名所は?
  • 大阪の名所は?
  • 横浜の名所は?

LangChainではプロンプトの使いまわしを容易にするためテンプレートが導入されています。

Pythonではf文字列を使用すると文字列中に値を埋め込むことができます。

for place in ["東京", "大阪", "横浜"]:
    print(f"{place}の名所は?")

Pythonのf文字列を知っている人なら、LangChainでのテンプレートはf文字列とよく似たイメージだと考えていただくとよいでしょう。

LangChainはPythonだけでなく、いろいろな言語から利用できるようになっています。それらの言語で同じような使い勝手を提供するため、Pythonのf文字列ではなく、PromptTemplateクラスを使用します。まず、PromptTemplateのオブジェクトを作成し、それにパラメタを与えてプロンプトを作成します。以下のようなイメージです。

Templateとプロンプトの関係
LangChain入門 - 2)プロンプト 9

このPromptTemplateの作り方にはいくつかありますが、以下のような方法があります。

  1. PromptTemplate(template=”文字列”, input_variables=[パラメタのリスト])
  2. PromptTemplate.from_template(“文字列”)

文字列中にパラメタを埋め込むには ”{パラメタ}” のように文字列の中に波括弧を書き、その内に変数を記述します。

どちらの方法でも同じPromptTemplate型のオブジェクトが返されます。PromptTemplateができたら、formatメソッドでパラメタ名と値を指定して、実際のプロンプトを作成します。具体例をみてみましょう。

from langchain.prompts import PromptTemplate

template1 = PromptTemplate(template="{place}の名所は?", input_variables=["place"])
template2 = PromptTemplate.from_template("{place}の名所は?")

p1 = template1.format(place="東京")
p2 = template2.format(place="京都")

print(p1)
print(p2)

出力は以下のようになります。

東京の名所は?
京都の名所は?

template1はPromptTemplateから、template2はfrom_templateメソッドを使って、テンプレートを作成しています。どちらもformatメソッドを使って実際のパラメタを埋め込んでいます。

このようにテンプレートを使ってプロンプトを作成する場合は、以下の手順となります。

  1. テンプレートの作り方
  2. テンプレートからプロンプトの作り方

ここまで説明したことを以下の図にまとめてみました。

TemplateからPromptの生成
LangChain入門 - 2)プロンプト 10

このようにテンプレートを作っておくとプロンプトの使いまわしが容易になります。以下はテンプレートからプロンプトを作成するサンプルです。

from langchain.prompts import PromptTemplate

template = PromptTemplate(template="{place}の名所は?", input_variables=["place"])
for p in ["東京","京都","横浜"]:
    prompt = template.format(place=p)
    print(prompt)

出力は以下のようになります。

東京の名所は?
京都の名所は?
横浜の名所は?

テンプレート中には複数のパラメタを使用することもできます。

from langchain.prompts import PromptTemplate

cooking = PromptTemplate.from_template(
    "{ingredient}を使った{cuisine}の作り方は?"
)
p0 = cooking.format(ingredient="じゃがいも", cuisine="中華")
p1 = cooking.format(ingredient="人参", cuisine="フレンチ")
print(p0)
print(p1)

出力は以下の通りです。

じゃがいもを使った中華の作り方は?
人参を使ったフレンチの作り方は?

では、実際にテンプレートから作成したプロンプトをOpenAIに入力して結果を取得してみましょう。

from langchain_openai import OpenAI
from langchain.prompts import PromptTemplate

llm = OpenAI(temperature=0.7)
cooking = PromptTemplate.from_template(
    "{ingredient}を使った{cuisine}の作り方は?"
)
prompt = cooking.format(ingredient="じゃがいも", cuisine="中華")
prediction = llm.invoke(prompt)
print(prediction.strip())

OpenAIオブジェクトを作成して変数llmに格納しています。引数でtemperatureを指定しています。あとは、llm(prompt)と実行するだけで結果を取得できます。

出力は以下のようになりました。

1. 中華風ジャガイモの作り方

①ジャガイモを皮をむいて、薄切りにする。

②鍋に油を熱し、ジャガイモを炒める。

③醤油、酒、砂糖、醤油、お好みの調味料を加えて、ジャガイモを炒める。

④鍋からジャガイモを取り出し、皿に盛り付ける。

⑤お好みの野菜を加えて、一緒に炒める。

⑥最後に、ジャガイモがしっかりと炒められたら、火を止める。  

内容や手順に首をかしげたくなるような部分もありますが、出力は得られています。

ChatPromptTemplate

生成AIでは会話の形でコンテキスト(文脈)を提供して、より適切な結果を得ることがよく行われます。

以下はOpenAIのWebAPIを直接呼び出すときのイメージです。

注意)最近のopenaiモジュールの更新で、記述方法が変更されました。詳しくは以下の記事を参照してください。OpenAI入門 – Pythonでの利用 – Future Coders (future-coders.net)

from openai import OpenAI
client = OpenAI()

response = client.chat.completions.create(
    model="gpt-3.5-turbo",
    messages=[
        {"role": "system", "content": "あなたは有能なアシスタントです"},
        {"role": "user", "content": "2020のワールドシリーズで勝ったのは?"},
        {"role": "assistant", "content": "ロサンジェルスドジャーズが優勝しました。"},
        {"role": "user", "content": "開催地はどこでしたか?"}
    ]
)

OpenAIが提供するChatCompletionというAPIでは上記のように会話の情報をオブジェクトの配列として与えます。システム、ユーザー、AIの発言は”role”という属性で区別しています。

LangChainでは、以下のように記述します。

from langchain_openai import ChatOpenAI
from langchain.schema import AIMessage, HumanMessage,SystemMessage

chat = ChatOpenAI()
r = chat.invoke([
    SystemMessage(content="あなたは有能なアシスタントです"),
    HumanMessage(content="2020のワールドシリーズで勝ったのは?"),
    AIMessage(content="ロサンジェルスドジャーズが優勝しました。"),
    HumanMessage(content="開催地はどこでしたか?"),
])
print(r.content)

LangChainとOpenAI、書き方は違いますが、ともに会話の役割に応じたメッセージを引数で与えていることがわかります。

LangChainでは、役割に応じたオブジェクトを使って会話を表現します。

  • SystemMessage = システム(前提)の発言
  • HumanMessage = 人(あなた)の発言
  • AIMessage = AIの発言

ここで一旦、OpenAIとChatOpenAIの関係性を整理しておきましょう。

  • OpenAIクラス = 一般的な生成AIクラス、引数には文字列を入力します。
  • ChatOpenAIクラス = 会話に特化したクラス、役割に応じたオブジェクトの配列を入力します。
ChatとCompletionの違い
LangChain入門 - 2)プロンプト 11

生成AIのクラスによって入力が異なります。この区別をしっかりとしておくことが大切です。

役割に応じた発言は上記のようにHumanMessage, AIMessageなどから直接作成することもできますが、HumanMessagePromptTemplateやAIMessagePromptTemplateといったテンプレートクラスを使って作成することも可能です。

以下はHumanMessageを直接作成する場合と、テンプレートから作成する場合のサンプルです。

from langchain.prompts import HumanMessagePromptTemplate
from langchain.schema import HumanMessage

msg1 = HumanMessage(content="こんにちは、私は太郎です")
template = HumanMessagePromptTemplate.from_template("こんにちは、私は{name}です")
msg2 = template.format(name="次郎")

print(msg1)
print(msg2)

出力は以下のようになります。

content='こんにちは、私は太郎です' additional_kwargs={} example=False
content='こんにちは、私は次郎です' additional_kwargs={} example=False

このようにテンプレートを使う場合は、以下のように役割に応じたクラスを使用します。

  • SystemMessageのメッセージ = SystemMessagePromptTemplateテンプレート
  • HumanMessageのメッセージ = HumanMessagePromptTemplateテンプレート
  • AIMessageのメッセージ = AIMessagePromptTemplateテンプレート

これら個々の役割に応じたテンプレートクラスが用意されていますが、会話全体として管理する場合、まとめてパラメタを変換できたほうが便利です。そのようなときには、ChatPromptTemplateクラスを使用します。以下サンプルです。

from langchain.prompts import ChatPromptTemplate
from langchain.prompts.chat import SystemMessagePromptTemplate, \
    AIMessagePromptTemplate, HumanMessagePromptTemplate

template = ChatPromptTemplate.from_messages([
    SystemMessagePromptTemplate.from_template("あなたは優秀なアシスタントです。名前は{name}です。"),
    HumanMessagePromptTemplate.from_template("{greet}"),
    AIMessagePromptTemplate.from_template("{greet}、何の御用でしょう?"),
    HumanMessagePromptTemplate.from_template("{user_input}"),
])

messages = template.format_messages(
    name="AI太郎",
    greet="こんにちは",
    user_input="あなたの名前を教えてください"
)

print(messages)

出力は以下のようになります。会話に必要な役割に応じたオブジェクト(プロンプト)がまとめて作成されていることがわかります。

[
  SystemMessage(content='あなたは優秀なアシスタントです。名前はAI太郎です。', additional_kwargs={}), 
  HumanMessage(content='こんにちは', additional_kwargs={}, example=False), 
  AIMessage(content='こんにちは、何の御用でしょう?', additional_kwargs={}, example=False), 
  HumanMessage(content='あなたの名前を教えてくださ い', additional_kwargs={}, example=False)
]

このように会話のプロンプトを作成する方法をみてきました。これをChatOpenAIオブジェクトに渡してみましょう。

from langchain_openai import ChatOpenAI

chat = ChatOpenAI()

from langchain.prompts import ChatPromptTemplate
from langchain.prompts.chat import SystemMessagePromptTemplate, \
    AIMessagePromptTemplate, HumanMessagePromptTemplate

template = ChatPromptTemplate.from_messages([
    SystemMessagePromptTemplate.from_template("あなたは優秀なアシスタントです。名前は{name}です。"),
    HumanMessagePromptTemplate.from_template("{greet}"),
    AIMessagePromptTemplate.from_template("{greet}、何の御用でしょう?"),
    HumanMessagePromptTemplate.from_template("{user_input}"),
])

messages = template.format_messages(
    name="AI太郎",
    greet="こんにちは",
    user_input="あなたの名前を教えてください"
)

r = chat.invoke(messages)
print(r.content)

出力は以下のようになりました。

私の名前はAI太郎です。どのようにお手伝いできますか?

ChatOpenAIオブジェクトを作成して変数chatに代入しています。あとはchat(会話形式のプロンプト)と実行することで結果が得られます。ChatOpenAIを実行したときの戻り値はオブジェクト形式で、contentプロパティに得られたテキストが格納されています。

ここでテンプレートからプロンプトを作成するときに、

  • ChatPromptTemplateからプロンプトを作成するときはformat_messagesメソッド
  • PromptTemplateからプロンプトを作成するときはformatメソッド

を使用しました。ここも混乱しやすいので整理しておきましょう。以下のサンプルを見てください。

from langchain.prompts import ChatPromptTemplate
from langchain.prompts.chat import SystemMessagePromptTemplate, \
    AIMessagePromptTemplate, HumanMessagePromptTemplate

template = ChatPromptTemplate.from_messages([
    SystemMessagePromptTemplate.from_template("あなたは優秀なアシスタントです。名前は{name}です。"),
    HumanMessagePromptTemplate.from_template("{greet}"),
    AIMessagePromptTemplate.from_template("{greet}、何の御用でしょう?"),
    HumanMessagePromptTemplate.from_template("{user_input}"),
])

prompt1 = template.format_messages(
    name="AI太郎",
    greet="こんにちは",
    user_input="あなたの名前を教えてください"
)

prompt2 = template.format(
    name="AI太郎",
    greet="こんにちは",
    user_input="あなたの名前を教えてください"
)

print(f"prompt1:{prompt1}")
print("---")
print(f"prompt2:{prompt2}")

出力は以下の通りです。

prompt1:[
    SystemMessage(content='あなたは優秀なアシスタントです。名前はAI太郎です。', additional_kwargs={}), 
    HumanMessage(content='こんにちは', additional_kwargs={}, example=False), 
    AIMessage(content='こんにちは、何の御用でしょう?', additional_kwargs={}, example=False), 
    HumanMessage(content='あなたの名前を教え てください', additional_kwargs={}, example=False)
]
---
prompt2:System: あなたは優秀なアシスタントです。名前はAI太郎です。
Human: こんにちは
AI: こんにちは、何の御用でしょう?
Human: あなたの名前を教えてください

前者のformat_messagesを使用すると、役割に応じたオブジェクトの配列になっています。これはChatOpenAIに渡すことができます。一方後者は単なる1つの文字列となっています。こちらはOpenAIには渡すことができますが、ChatOpenAIに渡すことはできません。

用途によってプロンプトの形式を変えているのですが、混乱しやすい点でもあるので注意してください。

Promptを使ったアプリ

ここまでの内容を踏まえて、レシピを作成するWebアプリを作成してみましょう。

Streamlitというライブラリを使用します。モジュールをインストールしていない人は以下のコマンドを実行してインストールしてください。

pip install streamlit

Streamlitに馴染みのない方は違和感を覚える箇所があるかもしれません。その場合は、LangChainがどのように使われているかだけ確認してもらえれば十分です。

以下がプログラムです。

from langchain_openai import OpenAI
from langchain.prompts import PromptTemplate
import streamlit as st

recipe = PromptTemplate(
    input_variables=["menu", "num"],
    template="{menu}を{num}人分作るレシピを教えてください。マークダウン形式で簡潔にお願いします",
)

llm = OpenAI(max_tokens=300)
st.title("簡単レシピ")
menu = st.text_input("メニュー", value="カレーライス")
num = st.slider("人数", min_value=1, max_value=4)
if st.button("レシピを見る"):
    prompt = recipe.format(num=num, menu=menu)
    steps = llm(prompt)
    print(steps)
    st.markdown(steps.strip())

以下の内容をコマンドプロンプトやターミナルから入力することで実行します。

streamlit run ファイル名

実行するとサーバが起動します。メニューに作りたい料理を記入して、人数を指定して、レシピを見るボタンを押してください。以下のように材料と手順が表示されました。

screen1
LangChain入門 - 2)プロンプト 12

streamlitはウェブアプリです。ブラウザを終了してもサーバは稼働し続けています。中断する場合は、コマンドプロンプトやターミナルでCtrl+Cキーを押下します。反応しない場合は、ターミナルそのものを終了させても大丈夫です。

HuggingFace

生成AIのサービスを提供する会社としてはOpenAIが人気ですが、OpenAIのWebAPIは従量課金となるため大量のデータを処理することに躊躇するかもしれません。生成AIの分野にはいろいろな会社が新規参入しており、OpenAI以外にもいろいろな選択肢があります。その中でもHuggingFaceは無料のLLMインスタンスをやAPIを公開しており、人気があります。

https://huggingface.co/

OpenAIとHuggingFaceはまったく別の会社で、WebAPIの仕様も異なります。しかしながら、LangChainを使っていれば、その差異をほとんど意識せずに使うことが可能です。

huggingfaceのサービスを利用するためには、サインアップして、APIキー(AccessToken)を取得する必要があります。

https://huggingface.co/settings/tokens

screen2
LangChain入門 - 2)プロンプト 13

また、Pythonのモジュールが必要になるので以下のコマンドでインストールしておきます。

pip install huggingface_hub

以下はHuggingFaceをLLMに使用した場合のサンプルです。

from huggingface_hub import InferenceClient
client = InferenceClient()
inputs = "What is the currency of India?"
response = client.post(json={"inputs": inputs}, model="databricks/dolly-v2-3b")
print(response)

HuggingFaceの生成AIを使用するには、まずHuggingFaceHubオブジェクトを作成します。引数のrepo_idでいろいろなモデルを指定できます。

llm(プロンプト)と実行することで応答が得られます。これはOpenAIのときと同じです。つまり、いったんオブジェクトを作成してしまえば、OpenAIもHuggingFaceも同じような方法で呼び出すことが可能となります。

出力は以下のようになりました。

b'[{"generated_text":"What is the currency of India?\\n\\nThe Indian rupee is the currency.\\n\\nHow"}]'

いろいろな用途に対応するようさまざまなmodelが利用可能です。

APIキーは環境変数 HUGGINGFACEHUB_API_TOKEN に設定してもかまいません。その場合はhuggingfacehub_api_token引数は不要です。

https://huggingface.co/google/flan-t5-xl

演習

prompt-ex1.py

PromptTemplateを使って、東京、神奈川、北海道の人口を出力してください。
以下のような出力が得られました。

2020年4月1日現在、東京都の人口は約13,942万人です。
神奈川県の人口は2020年4月1日現在で9,092,845人です。
2020年4月1日現在、北海道の人口は約564万人です。

prompt-ex2.py

ChatPromptTemplateを使って、以下のような会話から応答を導き出してください。
{}の部分はパラメタを使って指定するものとします。

  • System : あなたは優秀なシェフです。
  • Human : ダイエット中ですがおなかがすきました
  • AI : 何かレシピをご案内します。冷蔵庫に何がありますか?
  • Human : 冷蔵庫には{玉ねぎ}があります

以下のような出力が得られました。

素敵です!玉ねぎを使ったヘルシーなレシピをご紹介します。

【玉ねぎと鶏胸肉の炒め物】
材料:
- 鶏胸肉 150g
- 玉ねぎ 1個
- にんにく 2片
- オリーブオイル 大さじ1
- 醤油 大さじ2
- ごま油 小さじ1
- 塩 少々
- こしょう 少々

作り方:
1. 鶏胸肉を薄切りにし、塩とこしょうで下味をつけます。
2. 玉ねぎとにんにくをみじん切りにします。
3. フライパンにオリーブオイルを熱し、鶏肉を炒めます。鶏肉が白くなったら、玉ねぎとにんにくを加えてさらに炒めます。
4. 玉ねぎが透明になるまで炒めたら、醤油とごま油を加えて全体を混ぜ合わせます。
5. 全体が絡んだら完成です。ご飯やサラダと一緒にお召し上がりください。

このレシピは低カロリーでありながら、満足感のある一品です。ぜひお試しください!

解答例

prompt-ex1.py

from langchain_openai import OpenAI
from langchain.prompts import PromptTemplate

llm = OpenAI(temperature=0)
capital = PromptTemplate.from_template(
    "{pref}の人口は?"
)
for p in ["東京","神奈川","北海道"]:
    prompt = capital.format(pref=p)
    prediction = llm.invoke(prompt)
    print(prediction.strip())

prompt-ex2.py

from langchain_openai import ChatOpenAI
chat = ChatOpenAI()

from langchain.prompts import ChatPromptTemplate, HumanMessagePromptTemplate
from langchain.schema import AIMessage, HumanMessage,SystemMessage

template = ChatPromptTemplate.from_messages([
    SystemMessage(content="あなたは優秀なシェフです。"),
    HumanMessage(content="ダイエット中ですがおなかがすきました"),
    AIMessage(content="何かレシピをご案内します。冷蔵庫に何がありますか?"),
    HumanMessagePromptTemplate.from_template("冷蔵庫には{ingredient}があります"),
])
messages = template.format_messages(ingredient="玉ねぎ")
r = chat.invoke(messages)
print(r.content)