BioErrorLog Tech Blog

試行錯誤の記録

LangChain 入門

LLMによる開発パターンを容易に実装できると噂のLangChainに入門します。

はじめに

LangChainは、LLMを活用した開発を容易に実現するフレームワークです。

We believe that the most powerful and differentiated applications will not only call out to a language model via an API, but will also:
- Be data-aware: connect a language model to other sources of data
- Be agentic: allow a language model to interact with its environment

(ドキュメントより)

LamgChainは単にLLMのAPIをラップするのではなく、

  • 外部データと繋げる
  • エージェントとして相互作用する

など、LLMの活用パターンを容易に実現できます。

今回は下記のLangChain Python公式ガイドを参考にしながら、このLangChainの基本的な使い方を見ていきます。

https://python.langchain.com/en/latest/getting_started/getting_started.html

LangChainに入門する

事前準備

今回はLLMモデルとしてOpenAI APIを使うので、langchainopenaiをインストールします。

pip install langchain openai

OpenAIのAPIキーも環境変数に設定しておきます。

export OPENAI_API_KEY="..."


以降、OpenAI APIを使ったことがある方向けの内容として書いていきます。 まだOpenAI API、特にChat completionを触ったことのない方は、先にOpenAI APIのドキュメントに軽く目を通しておくことをおすすめします:

OpenAI Platform

Chat Model

まずは、LangChainを用いてgpt-3.5-turboのようなチャット形式でやり取りできるモデルを使ってみます。

from langchain.chat_models import ChatOpenAI
from langchain.schema import (
    AIMessage,
    HumanMessage,
    SystemMessage,
)

chat = ChatOpenAI(temperature=0)

response = chat([HumanMessage(content="Translate this sentence from English to French. I love programming.")])
# -> AIMessage(content="J'aime programmer.", additional_kwargs={})

print(response)  # content="J'aime programmer." additional_kwargs={}
print(type(response))  # <class 'langchain.schema.AIMessage'>

OpenAI APIのgpt-3.5-turboではメッセージをsystem/user/assistantの3つのroleとして与えますが、LangChainのChatOpenAI (こちらもデフォルトでgpt-3.5-turboが使われます)ではそれぞれ、

  • HumanMessage (user role)
  • SystemMessage (system role)
  • AIMessage (assistant role)

として与えます。

シンプルに一問一答の形で良ければ、上記のようにHumanMessageとしてテキストを与えます。


Chat ModelにHumanMessage以外の文脈を与えるには、下記のようにします。

from langchain.chat_models import ChatOpenAI
from langchain.schema import (
    AIMessage,
    HumanMessage,
    SystemMessage,
)

chat = ChatOpenAI(temperature=0)

messages = [
    SystemMessage(content="You are a helpful assistant that translates English to French."),
    HumanMessage(content="Translate this sentence from English to French. I love programming.")
]
response = chat(messages)
# -> AIMessage(content="J'aime programmer.", additional_kwargs={})

print(response)  # content="J'aime programmer." additional_kwargs={}
print(type(response))  # <class 'langchain.schema.AIMessage'>

SystemプロンプトはSystemMessageとして格納し、Chat Modelからの返答はAIMessageとして返されます。 もしそのままやり取りを続ける場合は、Chat Modelからの返答AIMessageをそのままメッセージに追記する、という使い方ができます。


ここまでは単純にOpenAI APIをラップしただけの機能と言えます。 以下、LangChainならではの便利な機能を見ていきます。

Prompt Templates

API経由でのLLMモデル利用パターンの一つに、Prompt Templatesという使い方があります。

Prompt Templatesは、あらかじめ用意されたテンプレートにユーザーからの入力を挿入してプロンプトを構築する方法です。 複雑なプロンプトが必要になるような用途でも、ユーザーからの入力をシンプルにしつつ、LLMに想定の振る舞いをさせることができます。

from langchain.chat_models import ChatOpenAI
from langchain.prompts.chat import (
    ChatPromptTemplate,
    SystemMessagePromptTemplate,
    HumanMessagePromptTemplate,
)

chat = ChatOpenAI(temperature=0)

template = "You are a helpful assistant that translates {input_language} to {output_language}."
system_message_prompt = SystemMessagePromptTemplate.from_template(template)
human_template = "{text}"
human_message_prompt = HumanMessagePromptTemplate.from_template(human_template)

chat_prompt = ChatPromptTemplate.from_messages([system_message_prompt, human_message_prompt])

format_prompt = chat_prompt.format_prompt(input_language="English",
                                          output_language="French", text="I love programming.").to_messages()
print(format_prompt)
# -> [SystemMessage(content='You are a helpful assistant that translates English to French.', additional_kwargs={}), HumanMessage(content='I love programming.', additional_kwargs={})]

SystemMessageのプロンプトテンプレートはSystemMessagePromptTemplateで、HumanMessageのプロンプトテンプレートはHumanMessagePromptTemplateで用意します。

後から挿入する部分を鉤括弧{}で用意し、format_prompt関数で挿入箇所を補完することで、最終的なプロンプトが生成されています。

Chains

ここまで挙げたようなModelやPrompt Templatesは、Chainsによってシンプルに繋げることができます。

from langchain.chat_models import ChatOpenAI
from langchain import LLMChain
from langchain.prompts.chat import (
    ChatPromptTemplate,
    SystemMessagePromptTemplate,
    HumanMessagePromptTemplate,
)

chat = ChatOpenAI(temperature=0)

template = "You are a helpful assistant that translates {input_language} to {output_language}."
system_message_prompt = SystemMessagePromptTemplate.from_template(template)
human_template = "{text}"
human_message_prompt = HumanMessagePromptTemplate.from_template(human_template)
chat_prompt = ChatPromptTemplate.from_messages([system_message_prompt, human_message_prompt])

chain = LLMChain(llm=chat, prompt=chat_prompt)
response = chain.run(input_language="English", output_language="French", text="I love programming.")

print(response)
# -> "J'aime programmer."

LLMChainによって、LLMモデルとPrompt Templateを連結しています。

Chainsに連結したら、あとはrun関数の引数にPrompt Templateの補完を渡して、LLMへのリクエストを投げることができます。


ここまでのおさらい:

  • Chat Model: LLMモデルのAPIラッパー
  • Prompt Templates: プロンプトを後から補完できるテンプレート
  • Chains: これらを連結してシンプルなインターフェースを提供

以下、より応用的な使い方を見ていきます。

Agents

ここまでは、LLMに行わせる振る舞いをあくまで人間が指定してきました。 Agentsでは、何のアクションを行うべきなのか、をLLMに決めさせることができます。

Agentsは大きく3つの概念から成り立っています。

  • Tool: 特定タスクを実行するための外部機能
  • LLM: Agentの振る舞いを決定するLLM
  • Agent: Agentのタイプ。今回はChat Modelを使う際の最も標準的なタイプAgentType.CHAT_ZERO_SHOT_REACT_DESCRIPTIONを使います。
from langchain.agents import load_tools
from langchain.agents import initialize_agent
from langchain.agents import AgentType
from langchain.chat_models import ChatOpenAI


chat = ChatOpenAI(temperature=0)
tools = load_tools(["terminal"])

agent = initialize_agent(
    tools,
    chat,
    agent=AgentType.CHAT_ZERO_SHOT_REACT_DESCRIPTION,
    verbose=True
)

agent.run("Who is the current terminal user?I would like to know the list of directories under that user's home directory.")

使い方はシンプルです。 initialize_agentに先ほど挙げた3つの概念Tool/LLM/Agentを渡して初期化し、run関数で実行します。

上の例では、Toolとしてterminalを、LLMとしてChatOpenAIを、AgentとしてAgentType.CHAT_ZERO_SHOT_REACT_DESCRIPTIONを渡しています。

サポートされているToolの一覧はこちら、Agentの一覧はこちらを参照ください。


この例では、Toolとしてterminalを利用し、現在のユーザーとそのホームディレクトリ配下のディレクトリ構成を質問しています。

Who is the current terminal user? I would like to know the list of directories under that user's home directory.

実行してみましょう。

> Entering new AgentExecutor chain...
Thought: We can use the Terminal tool to execute the `whoami` command to get the current user and then use the `ls` command to list the directories under the user's home directory.

Action:
{
  "action": "Terminal",
  "action_input": "whoami"
}


Observation: bioerrorlog

Thought:Now that we have the current user, we can use the `ls` command to list the directories under the user's home directory.

Action:
{
  "action": "Terminal",
  "action_input": "ls /home/bioerrorlog"
}


Observation: Command 'ls /home/bioerrorlog' returned non-zero exit status 1.
Thought:The `ls` command returned a non-zero exit status, which means that there might be an issue with the path we provided. We can try using the `~` symbol to represent the home directory of the current user.

Action:
{
  "action": "Terminal",
  "action_input": "ls ~"
}


Observation: AWSCLIV2.pkg
Applications
Desktop
Documents
Downloads
Dropbox
Library
Movies
Music
Pictures
Public
dev
dotfiles

Thought:We have successfully listed the directories under the current user's home directory using the `ls` command with the `~` symbol. 

Final Answer: The list of directories under the current user's home directory are: AWSCLIV2.pkg, Applications, Desktop, Documents, Downloads, Dropbox, Library, Movies, Music, Pictures, Public, dev, and dotfiles.

> Finished chain.

与えられた質問に回答するためのアクションが何なのかを推察し、最終回答が得られるまで繰り返されます。

Thought: We can use the Terminal tool to execute the whoami command to get the current user and then use the ls command to list the directories under the user's home directory.


あるアクションがエラーになったとしても、またすぐ別の方法を試行してくれるのも面白いところです。

Observation: Command 'ls /home/bioerrorlog' returned non-zero exit status 1.

このようにホームディレクトリをlsしようとしてエラーになった場合、また別の方法で試行してくれます:

Thought:The ls command returned a non-zero exit status, which means that there might be an issue with the path we provided. We can try using the ~ symbol to represent the home directory of the current user.

Toolを利用したアクションが繰り返された後、最終回答が得られます:

Final Answer: The list of directories under the current user's home directory are: AWSCLIV2.pkg, Applications, Desktop, Documents, Downloads, Dropbox, Library, Movies, Music, Pictures, Public, dev, and dotfiles.

Memory

LLMは基本的にステートレスなAPIのため、過去に行ったやりとりの記憶は何か工夫しない限り保持されません。

そこで最後に、ChainやAgentに記憶を保持させることができる、Memoryを紹介します。

from langchain.prompts import (
    ChatPromptTemplate,
    MessagesPlaceholder,
    SystemMessagePromptTemplate,
    HumanMessagePromptTemplate
)
from langchain.chains import ConversationChain
from langchain.chat_models import ChatOpenAI
from langchain.memory import ConversationBufferMemory

prompt = ChatPromptTemplate.from_messages([
    SystemMessagePromptTemplate.from_template("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."),
    MessagesPlaceholder(variable_name="history"),
    HumanMessagePromptTemplate.from_template("{input}")
])

llm = ChatOpenAI(temperature=0)
memory = ConversationBufferMemory(return_messages=True)
conversation = ConversationChain(memory=memory, prompt=prompt, llm=llm)

response = conversation.predict(input="Hi I'm BioErrorLog, living in Tokyo.")
print(response)
# -> "Hello BioErrorLog, it's nice to meet you! I'm an AI language model. How can I assist you today?"

response = conversation.predict(input="Today is a holiday and the weather is nice.")
print(response)
# -> "That sounds lovely! Do you have any plans for the day?"

response = conversation.predict(input="Summarize our conversation so far.")
print(response)
# -> "Sure! You introduced yourself as BioErrorLog and mentioned that you're currently living in Tokyo. You also mentioned that today is a holiday and the weather is nice."

ConversationBufferMemoryをChainに渡すことで、そのChainで行ったやりとりの記憶を簡単に保持させることができます。

上の例では3回のやり取りを行なっていますが、3回目の質問の時点でも1, 2回目のやり取りの記憶が保持されていることがわかります。

Q:
Summarize our conversation so far.
A:
Sure! You introduced yourself as BioErrorLog and mentioned that you're currently living in Tokyo. You also mentioned that today is a holiday and the weather is nice.

この他にも、Vector Storeを使ったMemoryやナレッジグラフをベースにしたMemoryなど、様々なタイプが提供されています。 色々遊んでみると面白そうですね: https://python.langchain.com/en/latest/modules/memory/how_to_guides.html

おわりに

以上、LangChainの基本的な使い方をざっとまとめました。

LLMのフレームワークは、LangChainの他にもMicrosoftのSemantic Kernelなど色々と選択肢があると思います。

現時点ではその中でもLangChainが一番広く使われているように見受けられます。

ぜひ遊び倒していきたいところです。

[関連記事]

www.bioerrorlog.work

www.bioerrorlog.work

www.bioerrorlog.work

参考

GitHub - hwchase17/langchain: ⚡ Building applications with LLMs through composability ⚡

🦜️🔗 Langchain

https://python.langchain.com/en/latest/getting_started/getting_started.html

OpenAI Platform

GitHub - bioerrorlog/langchain-sandbox: LangChain sandbox projects

GitHub - microsoft/semantic-kernel: Integrate cutting-edge LLM technology quickly and easily into your apps