langchianlogo

LangChain入門 – 4)Chains チェイン

本記事はLangChain入門の4回目です。Chainについて説明します。Chainとは鎖という意味です。LangChainという名前から予想できるように、言語モデルを使った処理を鎖のようにつなげて実行する機能です。

Chainの連鎖
LangChain入門 – 4)Chains チェイン 6

それぞれの処理に入力と出力があり、ある処理の出力が次の処理の入力になってゆく、そんなイメージです。

本記事はFuture Coders独自教材からの抜粋です。更新の多い分野なので現状の内容と異なる場合があることご了承ください。

OpenAIを直接呼び出す場合、Chainを単体で実行する場合、それらを比較する例を以下に示します。

from langchain import OpenAI
from langchain.prompts import PromptTemplate
from langchain.chains import LLMChain

llm = OpenAI()

## 1) LLM単体1
r = llm("日本の首都は?")
print(r)

## 2) LLM単体2
template = PromptTemplate.from_template("{place}の首都は?")
r = llm(template.format(place="日本"))
print(r)

## 3) Chain単体
chain = LLMChain(llm=llm, prompt=template, verbose=True)
r = chain.run("アメリカ")
print(r)

上記サンプルでは生成AIを3回実行しています。

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

東京です。


東京です。


> Entering new  chain...
Prompt after formatting:
アメリカの首都は?

> Finished chain.
ワシントンD.C.

最初は直接”日本の首都は?”と聞きました。OpenAIに直接プロンプトを渡しています。

2つ目はTemplateを使っていますが、template.formatを使って文字列に直しているだけで、1つ目と同じように文字列をOpenAIに渡しています。どちらも”東京です。”という答えが返ってきています。

3つ目がChainを使ったサンプルです。注目してほしいのは、LLMChainの引数にOpenAIのオブジェクト、プロンプトのテンプレートを渡していることです。つまり、LLMと”{place}の首都は?”というテンプレートのみをChainに渡しているのです。最後にchain.run(“アメリカ”)と実行していますが、明示的に「placeというパラメタに”アメリカ”をあてはめる」という旨は記述していません。

chainオブジェクトを作成したときにverbose=Trueと指定しているので、途中経過が出力されています。以下の範囲に含まれている箇所がChainの中の処理になります。

> Entering new  chain...
Prompt after formatting:
アメリカの首都は?

> Finished chain.

このChainでは、プロンプトに{place}というパラメタがあり、chain.run(“アメリカ”)と実行することで、placeというパラメタには”アメリカ”という値を当てはめるものと判断して処理を進めていることがわかります。

直接OpenAIを呼び出したときと、Chainを使った時の違いのイメージを以下に示します。

chain3
LangChain入門 – 4)Chains チェイン 7

Chainは単体で使ってもありがたみはありません。では鎖をつなげてみるとどんなことが可能になるか見てゆきましょう。

SimpleSequentialChain

SimpleSequentialChainは処理を順番につなげてゆくときに使用するクラスです。

たとえば、以下のような2ステップからなる作業を考えます。

  1. 会社の名前を考える
  2. その名前をベースにキャッチフレーズのアイディアをだす

最初の処理を実行し、その結果を次の処理へ入力する、処理の結果をつなげて実行するイメージです。
例を見てみましょう。

chain1
LangChain入門 – 4)Chains チェイン 8
from langchain import OpenAI
from langchain.prompts import PromptTemplate
from langchain.chains import LLMChain, SimpleSequentialChain
llm = OpenAI()

prompt_1st = PromptTemplate(
    input_variables=["product"],
    template="{product}を作る会社の社名の案を1つ教えてください。",
)
print(prompt_1st)

chain_1st = LLMChain(llm=llm, prompt=prompt_1st)

prompt_2nd = PromptTemplate(
    input_variables=["company_name"],
    template="{company_name}という会社名のキャッチコピーを考えてください。",
)

chain_2nd = LLMChain(llm=llm, prompt=prompt_2nd)

sequential_chain = SimpleSequentialChain(chains=[chain_1st, chain_2nd], verbose=True)
r = sequential_chain.run("風鈴")
print(r)

タスク1つをLLMChainオブジェクトで表現して、SimpleSequentialChainでそれらをつなげています。
SimpleSequentialChainの引数において、verbose=Trueとすることで、途中経過も出力されます。

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

> Entering new  chain...


「風鈴職人」


「風鈴で楽しい時間を! 風鈴職人であなたの夢を叶える!」

> Finished chain.


「風鈴で楽しい時間を! 風鈴職人であなたの夢を叶える!」

社名案が表示され、そのキャッチコピーが出力されていることがわかります。プログラムを作成している段階では、どのような社名が候補として列挙されるかわかりません。前段の処理結果を受け取って処理するために、パラメタ化されたテンプレートを使用する必要があることに注意してください。

ちなみに、テンプレートを作成するコードはPromptTempalate.from_templateを使ってもかまいません。同じ結果が得られます。

prompt_1st = PromptTemplate.from_template(
    "{product}を作る会社の社名の案を3つ教えてください。"
)
chain_1st = LLMChain(llm=llm, prompt=prompt_1st)

prompt_2nd = PromptTemplate.from_template(
    "{company_name}という会社名のキャッチコピーを考えてください。",
)
chain_2nd = LLMChain(llm=llm, prompt=prompt_2nd)

連鎖は2つ以上でも大丈夫です。以下は3つのタスクを連鎖させている例です。

from langchain import OpenAI
from langchain.prompts import PromptTemplate
from langchain.chains import LLMChain, SimpleSequentialChain
llm = OpenAI(temperature=0.1)

prompt_1st = PromptTemplate.from_template(
    "{city}近郊の日帰りに最適な場所を1つ教えてください。"
)
chain_1st = LLMChain(llm=llm, prompt=prompt_1st)

prompt_2nd = PromptTemplate.from_template(
    "{place}で人気のグルメを1つ教えてください。",
)
chain_2nd = LLMChain(llm=llm, prompt=prompt_2nd)

prompt_3rd = PromptTemplate.from_template(
    "{food}の値段の相場を教えてください",
)
chain_3rd = LLMChain(llm=llm, prompt=prompt_3rd)

sequential_chain = SimpleSequentialChain(chains=[chain_1st, chain_2nd, chain_3rd], verbose=True)
r = sequential_chain.run("東京")
print(r)

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

> Entering new  chain...


湯河原温泉です。湯河原温泉は、東京から車で1時間半ほどの距離にあり、温泉街を中心に様々な観光スポットがあります。湯河原温泉は、温泉街を中 心に、湯河原温泉公園、湯河原温泉神社、湯河原温泉自然公園など、自然豊かな環境の中で様々な観光スポットを楽しむことができます。また、湯河原温泉には、温泉


湯河原温泉で人気のグルメは、「湯河原温泉牛タン」です。湯河原温泉牛タンは、湯河原温泉の自然な温泉水を使用して、牛肉を柔らかく仕上げた特製のタンです。湯河原温泉牛タンは、湯河原温泉の観光スポットを訪れた際には、是非お試しください!
。

湯河原温泉牛タンの値段は、1個あたり約400円~500円程度です。

> Finished chain.
。

ConversationChain

ConversationChainは会話処理を行うためのモデルです。会話を扱うのでOpenAIではなく、ChatOpenAIを使用している点に注意してください。

シンプルな例をみてみましょう。

from langchain.chains import ConversationChain
from langchain.chat_models import ChatOpenAI
chain = ConversationChain(llm=ChatOpenAI())

s = input("> ")
r = chain.run(s)
print(r)

s = input("> ")
r = chain.run(s)
print(r)

以下実行結果です。

> こんにちは
こんにちは!お元気ですか?何かお手伝いできることはありますか?
> 東京の夏は暑いですね
はい、東京の夏は非常に暑くなります。平均気温は30度以上になることも多く、湿度も高いです。特に7月から8月にかけては、蒸し暑さが厳しく感じ
られます。外出する際は、涼しい服装や帽子、日焼け止めなどの対策が必要です。また、エアコンや扇風機を利用することも一般的です。夏の暑さに
は十分に気を付けてくださいね。

for文を使って繰り返し会話するように修正してみましょう。以下サンプルです。ENTERが入力されるまで会話を繰り返します。

from langchain.chains import ConversationChain
from langchain.chat_models import ChatOpenAI
chain = ConversationChain(llm=ChatOpenAI(), verbose=True)

s = input("> ")
while s != "":
    r = chain.run(s)
    print(r)
    s = input("> ")

以下出力例です。verbose=Trueを指定しているので、Chainの中で何が行われているかも出力されています。

> こんにちは、私は太郎、8歳です。


> Entering new ConversationChain chain...
Prompt after formatting:
The following is a friendly conversation between a human and an AI. The AI is talkative and provides lots of specific details from its context. If the AI does not know the answer to a question, it truthfully says it does not know.

Current conversation:

Human: こんにちは、私は太郎、8歳です。
AI:

> Finished chain.
こんにちは太郎さん!私はAIです。どのようにお手伝いできますか?
> 私は来年何歳になるでしょうか?


> Entering new ConversationChain chain...
Prompt after formatting:
The following is a friendly conversation between a human and an AI. The AI is talkative and provides lots of specific details from its context. If the AI does not know the answer to a question, it truthfully says it does not know.

Current conversation:
Human: こんにちは、私は太郎、8歳です。
AI: こんにちは太郎さん!私はAIです。どのようにお手伝いできますか?
Human: 私は来年何歳になるでしょうか?
AI:

> Finished chain.
太郎さん、あなたが現在8歳であると言われましたので、来年は9歳になります。

ここで注目してほしいのは、最初に名前と年齢を伝えたら、そのことを覚えていた(来年は9歳と答えた)ということです。その理由はChainの中で何がおきていたか見るとよく分かります。人の入力、AIからの応答、すべてメッセージの中に含まれていることが分かります。これによって、会話のコンテキストが保存されていたのです。

以下はStreamlitを使ってアプリ化したサンプルです。

import streamlit as st
from langchain.chains import ConversationChain
from langchain.chat_models import ChatOpenAI

if "chat" not in st.session_state:
    st.session_state["chat"] = ConversationChain(llm=ChatOpenAI(), verbose=True)

if "msg" not in st.session_state:
    st.session_state["msg"] = []

chain = st.session_state["chat"]

st.title("Chatbot by LangChain")

for msg in st.session_state["msg"]:
    with st.chat_message(msg["role"]):
        st.markdown(msg["content"])

if prompt := st.chat_input(""):
    with st.chat_message("user"):
        st.markdown(prompt)

    response = chain.run(prompt)

    with st.chat_message("assistant"):
        st.markdown(response)

    st.session_state["msg"].append({"role":"user", "content":prompt})
    st.session_state["msg"].append({"role":"assistant", "content":response})
screen chain1
LangChain入門 – 4)Chains チェイン 9

このように簡単に会話をするアプリを作成できるようになりました。コンソールをみると途中経過が出力されています。入力した会話が全て保存されていることが分かります。

> Entering new ConversationChain chain...
Prompt after formatting:
The following is a friendly conversation between a human and an AI. The AI is talkative and provides lots of specific details from its context. If the AI does not know the answer to a question, it truthfully says it does not know.

Current conversation:
Human: こんにちは
AI: こんにちは!元気ですか?私はAIです。何かお手伝いできますか?
Human: 東京から名古屋まで新幹線でどのくらいかかりますか?
AI:

> Finished chain.

このことから気づいた方もいるかもしれません。会話が長く続くほど、処理するテキストが長くなってゆきます。そのうちトークンの上限に到達すると会話が途切れてしまうでしょう。会話が長引くほど処理コストがかかってしまうことも問題です。

人間の会話においても、一字一句間違えずに覚えてることはないでしょう。「なんとなくこんなことを話した」とその内容を要約しながら覚えているはずです。LangChainにはそのような機能を実装するMemoryモジュールが用意されています。Memoryモジュールに関して詳しくは後述します。

Chainには他にも

  • 入力を加工して出力するTransformChain
  • 独自で繋がりを指定できるカスタムChain
  • 動的に次の処理を選択するRouterChain

などが用意されています。詳しくは以下のURLを参照してください。

https://python.langchain.com/docs/modules/chains/

演習

chain-ex1.py

以下のChainを作成して生成AIで回答を求めてください。

  1. 東京から6時間以内で行けるおすすめのビーチを1つ
  2. その国名と人口を調べて
  3. その国のおすすめ料理を表示する

以下出力例です。

> Entering new  chain...
サイパン島(Saipan Island)

マリアナ諸島(Commonwealth of the Northern Mariana Islands)、人口52,344人(2020年)


代表的な料理として、「チキンケータレット」があります。チキンケータレットは、マリアナ諸島の伝統的な料理で、チキン、ライス、野菜などを炒めたものです。

> Finished chain.


代表的な料理として、「チキンケータレット」があります。チキンケータレットは、マリアナ諸島の伝統的な料理で、チキン、ライス、野菜などを炒めたものです。

chain-ex2.py

日本とアメリカそれぞれで

  1. おすすめの都市
  2. その理由

を出力してください。以下出力例です。

日本では東京は、多様な文化を体験できる多国籍な都市です。
アメリカではニューヨークは、文化、美術、食文化、娯楽など、多くの要素が揃っているため、住むのに最適な都市です。

解答例

chain-ex1.py

from langchain import OpenAI
from langchain.prompts import PromptTemplate
from langchain.chains import LLMChain, SimpleSequentialChain
llm = OpenAI(temperature=0.3)

prompt_1st = PromptTemplate.from_template(
    "{city}から6時間以内のフライトで行けるおすすめの国外のビーチを1つ教えてください。\nビーチ名:"
)
chain_1st = LLMChain(llm=llm, prompt=prompt_1st)

prompt_2nd = PromptTemplate.from_template(
    "{beach}がある国とその国の人口を教えてください。\n国名と人口:",
)
chain_2nd = LLMChain(llm=llm, prompt=prompt_2nd)

prompt_3rd = PromptTemplate.from_template(
    "その国:{country}のおすすめ料理はなんですか?代表的な料理を1つ教えてください。",
)
chain_3rd = LLMChain(llm=llm, prompt=prompt_3rd)

sequential_chain = SimpleSequentialChain(chains=[chain_1st, chain_2nd, chain_3rd], verbose=True)
r = sequential_chain.run("東京")
print(r)

chain-ex2.py

from langchain import OpenAI
from langchain.prompts import PromptTemplate
from langchain.chains import LLMChain, SimpleSequentialChain
llm = OpenAI(temperature=0.3)

prompt_1st = PromptTemplate.from_template(
    "{country}で一番住むのにおすすめなのはどの都市ですか?"
)
chain_1st = LLMChain(llm=llm, prompt=prompt_1st)

prompt_2nd = PromptTemplate.from_template(
    "{city}に住むのをおすすめする理由を1つ簡潔に教えてください。",
)
chain_2nd = LLMChain(llm=llm, prompt=prompt_2nd)

for place in ["日本", "アメリカ"]:
    sequential_chain = SimpleSequentialChain(chains=[chain_1st, chain_2nd])
    r = sequential_chain.run(place)
    print(f"{place}では{r.strip()}")