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が面白い.
— BioErrorLog (@bioerrorlog) April 29, 2023
開発もかなりアクティブ.
昨日ちょっとしたプルリク投げたら、今日もうmergeされていた.
LangChain - Building applications with LLMs through composabilityhttps://t.co/6pjo5Wfb3I
LangChainに入門する
事前準備
今回はLLMモデルとしてOpenAI APIを使うので、langchain
とopenai
をインストールします。
pip install langchain openai
OpenAIのAPIキーも環境変数に設定しておきます。
export OPENAI_API_KEY="..."
以降、OpenAI APIを使ったことがある方向けの内容として書いていきます。 まだOpenAI API、特にChat completionを触ったことのない方は、先にOpenAI APIのドキュメントに軽く目を通しておくことをおすすめします:
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 thels
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が一番広く使われているように見受けられます。
ぜひ遊び倒していきたいところです。
[関連記事]
参考
GitHub - hwchase17/langchain: ⚡ Building applications with LLMs through composability ⚡
https://python.langchain.com/en/latest/getting_started/getting_started.html
GitHub - bioerrorlog/langchain-sandbox: LangChain sandbox projects