見出し画像

ChatGPT勉強日記(#11) LangChainの総復習-第2回

今日もGreg Kamradt氏のYoutube Videoで出てくる、The LangChain Cookbook 7 Coreconceptの残りのレシピを、最新のLangChainで動かしていきたいと思います。


(勉強日記ですが、ところどころチュートリアル形式なので「です・ます」調になります。)

Text Embedding Model

これも下のオリジナルコードでインポートしているlangchain.embeddingsモジュールはDeprecatedになっているので、

from langchain.embeddings import OpenAIEmbeddings

下のようにlangchain_openaiモジュールを使ったものに書き換えました。

from langchain_openai import OpenAIEmbeddings

embeddings = OpenAIEmbeddings(openai_api_key=openai_api_key)

これで下のコードも問題なく動きます。

text = "Hi! It's time for the beach"
text_embedding = embeddings.embed_query(text)
print (f"Here's a sample: {text_embedding[:5]}...")
print (f"Your embedding is length {len(text_embedding)}")

このような出力が返ってきてテキストがベクトルデータに変換されたことがわかります。
Here's a sample: [-0.0001933947418589633, -0.0030791846713453044, -0.00105463973203673, -0.019258900286671155, -0.015191653342505319]... Your embedding is length 1536

Embeddingsについては、私の以下の2つのブログを参考にしてみてください。

Prompts - Text generally used as instructions to your model

LangChainを使う上で絶対にヘビーユースすることが確定しているPromptTemplate。
これはもともとのコードのOpen AIのimport先をlangchain.llmsモジュールではなくて、langchain_openaiにすることでgpt-3.5-turbo-instructのモデルまでは動作しましたが、それ以上のモデルとは互換性がなくなっていました。

# from langchain.llms import OpenAI
from langchain_openai import OpenAI
from langchain import PromptTemplate

v1/chat/completionsとの互換性がないため、
いろいろ調べた結果、OpenAIクラスと PromptTemplateクラスを使うことを諦め、ChatOpenAIクラスと、ChatPrompTemplateをつかって以下のようにして同様のことができました。

from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate

chat = ChatOpenAI(model_name="gpt-4o", openai_api_key=openai_api_key)

# Notice "location" below, that is a placeholder for another value later
human_template = """
I really want to travel to {location}. What should I do there?

Respond in one short sentence
"""

prompt = ChatPromptTemplate.from_messages([("human", human_template)])
chain = prompt | chat

result = chain.invoke(
    {
        "location": "Rome"
    }
)
print(result.content)

実行すると、下のようにローマのお勧めスポットを教えてくれます。

Explore the ancient Colosseum, the Vatican Museums, and enjoy authentic Italian cuisine in local trattorias.

ChatPromptTemplateの良いところは下のように、テンプレートの"location"の値を変えることで、

result = chain.invoke(
    {
        "location": "Tokyo"
    }
)
print(result.content)

下のように簡単に別の場所のお勧めをOpenAIに答えさせることができることです。

Explore the historic temples, bustling markets, and cutting-edge technology districts in Tokyo for a truly unique experience.

システムを作るうえで同じテンプレートの中のキーワードだけ入れ替えて使い回すというシーンは多いので、ChatPromptTemplateを使うケースは非常に多くなるのではないかと思います。

Example Selectors

こちらのサンプルはOpenAIクラスや、FewShotPromptTemplateが最新のモデルと互換性がないので抜本的に変えざるをえませんでした。

FewShotPromptTemplateの代替となるものを探していると、
↓のFewShotChatMessagePromptTemplateがみつかり、

なんとここにExample Selectorsのサンプルが2つあったので、それを使って、同じものをつくってみました。このサンプルでも一部動かない箇所が合ったので、それを直して動かしてみました。

Similarity Searchを使わない実装

Example Selectorを勉強をするうえで、よりシンプルな実装がこちらの類似検索を使わずに例文を参考にして答えを出させる、というものです。

from langchain_core.prompts import (
    FewShotChatMessagePromptTemplate,
    ChatPromptTemplate
)

examples = [
    {"input": "pirate", "output": "ship"},
    {"input": "pilot", "output": "plane"},
    {"input": "driver", "output": "car"},
    {"input": "tree", "output": "ground"},
    {"input": "bird", "output": "nest"},
]

example_prompt = ChatPromptTemplate.from_messages(
    [('human', '{input}'), ('ai', '{output}')]
)

few_shot_prompt = FewShotChatMessagePromptTemplate(
    examples=examples,
    # This is a prompt template used to format each individual example.
    example_prompt=example_prompt,
)

final_prompt = ChatPromptTemplate.from_messages(
    [
        ('system', 'You are a helpful AI Assistant'),
        few_shot_prompt,
        ('human', '{input}'),
    ]
)
print('==== check final prompt with input data ====')
print(final_prompt.format(input="fish"))
print('============================================')
from langchain_openai import ChatOpenAI
chat = ChatOpenAI(model_name="gpt-4o", openai_api_key=openai_api_key)

chain = final_prompt | chat
result = chain.invoke({"input": "fish"})
print(result.content)

examplesにある、pirateとship、pilotとplane、driverとcar、treeとground、birdとnestとういペアをAIに”例”として与えて、それと”似たルール”で、ユーザーが言ったものに対する答えを答えさせると言うものです。
examplesとexample_templateをFewShotChatMessagePromptTemplateに与えて、それを元に、final_promptを生成します。
これをChatOpenAIと組み合わせてchainを作り、このChainに{"input": "fish"}という入力を与えると、

water

という結果が返ってきました。これは、pirate->ship, pilot->plane, driver->car, tree->ground, bird->nestという組み合わせからAIがパターンを理解し、fishにもっとも適した単語としてwaterを導き出したということです。
Example Selectorは私がlangchainを最初に勉強したときに一番動作イメージが理解できないものでした。
上のコード中の↓の行で、final_promptをformat関数を呼び出してログに出力しています。

print(final_prompt.format(input="fish"))

このformat関数を使うと、実際にfinal_promptがどういうものになるのかを見ることができます。出力の結果は以下のようになります。

System: You are a helpful AI Assistant
Human: pirate
AI: ship
Human: pilot
AI: plane
Human: driver
AI: car
Human: tree
AI: ground
Human: bird
AI: nest
Human: fish

これをみると、例文と最後のユーザーからの入力をlangchainがどのようにOpen AIのモデルに与えているかよく分かり、動作イメージがはっきりするのではないでしょうか。

Similarity Searchを使った実装

こんどは、SemanticSimilarityExampleSelectorクラスをつかって、先ほどと同じ例文をつかって同じことをやってみました。
これも先ほどと同じFewShotChatMessagePromptTemplateの仕様に2つ目のサンプルとして載っているものをベースにつくりました。



from langchain_core.example_selectors import SemanticSimilarityExampleSelector
from langchain_openai import OpenAIEmbeddings
from langchain_chroma import Chroma


examples = [
    {"input": "pirate", "output": "ship"},
    {"input": "pilot", "output": "plane"},
    {"input": "driver", "output": "car"},
    {"input": "tree", "output": "ground"},
    {"input": "bird", "output": "nest"},
]

to_vectorize = [
    " ".join(example.values())
    for example in examples
]
# embeddings = OpenAIEmbeddings()
embeddings = OpenAIEmbeddings(openai_api_key=openai_api_key)

vectorstore = Chroma.from_texts(
    to_vectorize, embeddings, metadatas=examples
)
example_selector = SemanticSimilarityExampleSelector(
    vectorstore=vectorstore
)

#from langchain_core import SystemMessage
from langchain.schema import HumanMessage, SystemMessage, AIMessage
# from langchain_core.prompts import HumanMessagePromptTemplate
from langchain_core.prompts import HumanMessagePromptTemplate, AIMessagePromptTemplate, SystemMessagePromptTemplate
from langchain_core.prompts.few_shot import FewShotChatMessagePromptTemplate

few_shot_prompt = FewShotChatMessagePromptTemplate(
    # Which variable(s) will be passed to the example selector.
    input_variables=["input"],
    example_selector=example_selector,
    # Define how each example will be formatted.
    # In this case, each example will become 2 messages:
    # 1 human, and 1 AI
    example_prompt=(
        HumanMessagePromptTemplate.from_template("{input}")
        + AIMessagePromptTemplate.from_template("{output}")
    ),
)
# Define the overall prompt.
final_prompt = (
    SystemMessagePromptTemplate.from_template(
        "You are a helpful AI Assistant"
    )
    + few_shot_prompt
    + HumanMessagePromptTemplate.from_template("{input}")
)
# Show the prompt
print(final_prompt.format_messages(input="fish"))

# Use within an LLM
# from langchain_core.chat_models import ChatAnthropic
# chain = final_prompt | ChatAnthropic()
from langchain_openai import ChatOpenAI
chat = ChatOpenAI(model_name="gpt-4o", openai_api_key=openai_api_key)
result = chain.invoke({"input": "fish"})
print(result.content)

サンプルコードからの変更点は、したのクラスのモジュールが変わっていたのでインポート先を変えたこと、

# from langchain_core.prompts import SemanticSimilarityExampleSelector
# from langchain_core.embeddings import OpenAIEmbeddings
# from langchain_core.vectorstores import Chroma
from langchain_core.example_selectors import SemanticSimilarityExampleSelector
from langchain_openai import OpenAIEmbeddings
from langchain_chroma import Chroma

また、下のクラスもモジュールを変更したりそもそもインポート文がないものがあったので変えました。

#from langchain_core import SystemMessage
from langchain.schema import HumanMessage, SystemMessage, AIMessage
# from langchain_core.prompts import HumanMessagePromptTemplate
from langchain_core.prompts import HumanMessagePromptTemplate, AIMessagePromptTemplate, SystemMessagePromptTemplate

OpenAIEmbeddingsクラス初期化時にKeyを渡すようにしました。

# embeddings = OpenAIEmbeddings()
embeddings = OpenAIEmbeddings(openai_api_key=openai_api_key)

最後に使用するモデルをAnthropic->OpenAIに以下のように変えました。

# from langchain_core.chat_models import ChatAnthropic
# chain = final_prompt | ChatAnthropic()
from langchain_openai import ChatOpenAI
chat = ChatOpenAI(model_name="gpt-4o", openai_api_key=openai_api_key)

そして、例となるexamplesのデータを先ほどと同じ

examples = [
    {"input": "pirate", "output": "ship"},
    {"input": "pilot", "output": "plane"},
    {"input": "driver", "output": "car"},
    {"input": "tree", "output": "ground"},
    {"input": "bird", "output": "nest"},
]

にして

result = chain.invoke({"input": "fish"})

のように、fishをinputに与えて実行してみると

water

という同様の答えが返ってきました。
今回も、例のデータからAIがパターンを理解し、fishにもっとも適した単語としてwaterを導き出したということは同じですが、やり方が若干違っています。

print(final_prompt.format_messages(input="fish"))

途中で、final_promptをformat_messages()関数を使って出力してみると、このような内容となっているのがわかります。

[SystemMessage(content='You are a helpful AI Assistant'), HumanMessage(content='bird'), AIMessage(content='nest'), HumanMessage(content='bird'), AIMessage(content='nest'), HumanMessage(content='bird'), AIMessage(content='nest'), HumanMessage(content='bird'), AIMessage(content='nest'), HumanMessage(content='fish')]

今回は、final_promptをつくる段階で、example_selectorが、例のデータの中から、inputの'fish'に一番意味の近いものを探し出し、その例だけを使っているのがわかるでしょう。
この場合、SemanticSimilarityExampleSelectorがfishに一番近いbirdだけをデータから抜き出し、bird -> nestだけを例としてAIに与え(しかも4回も与えています)、最後にfishだったら何が来る?とAIに聞いています。

SemanticSimilarityExampleSelectorを使うメリットはこのようにExample(例)のデータセットがたくさんあって、そのなかにばらつきが多い場合、ユーザーから聞かれた内容に意味的に近い例だけを参考にしながらAIに考えさせることができる点です。

続く。



このブログに関する質問や、弊社(Goldrush Computing)へのOpenAI API、LLM、LangChain関連の開発案件の依頼は↓↓↓からお願いします。

mizutori@goldrushcomputing.com

次回は↓↓↓





この記事が気に入ったらサポートをしてみませんか?