LangChain入門
2024/08/01
AILangChainとは
LangChainは大規模言語モデル(LLM)を組み込んだアプリを簡単に実装できるようにしたライブラリである。LangChainを用いることで、LLMのみでは実現できない、複雑な計算をさせたり、Web検索した結果を参照して回答を生成させたりといった機能を簡単に実装できる。OpenAIを始め、AWSやGoogle Cloudなどが提供する様々なLLMのモデルを切り替えて利用可能。
LangChainの6つのモジュール
LangChainには6つのモジュールが用意されていて、これらのモジュールを組み合わせて複雑な機能を実現できる。
LangChain(Python)で基本的な機能を実装する
0. 準備
LangChainを利用するための準備を実施する。(今回はOpenAIのAPIキーを取得して検証を実施)
import os
os.environ['OPENAI_API_KEY'] = "<各自取得したOPENAIのAPIキー>"
pip install langchain
pip install langchain-openai
pip install langchain-community
pip install wikipedia
pip install grandalf
pip install chromadb
pip install langchain-chroma
pip install duckduckgo-search
pip install langchain-experimental
1. Model I/O
Model I/Oでは、「プロンプトの準備」、「言語モデルの呼び出し」、「結果の受け取り」という3ステップを簡単に実装するための機能を提供する。これら、「プロンプトの準備」、「言語モデルの呼び出し」、「結果の受け取り」のために用意されている様々なコンポーネントの中で代表的なものを紹介する。
from langchain.prompts import PromptTemplate
prompt_template = PromptTemplate(
template = "次の食材からレシピを提案してください。「{input}」",
input_variables=["input"],
)
response = prompt_template.invoke(input="じゃがいも 玉ねぎ 人参 りんご").text
print(response)
# 次の食材からレシピを提案してください。「じゃがいも 玉ねぎ 人参 りんご」
from langchain_openai import ChatOpenAI
chat = ChatOpenAI(model_name="gpt-4o-mini", temperature=0)
response = chat.invoke("こんにちは").content
print(response)
# こんにちは!どういったことをお手伝いできますか?
from langchain_openai import OpenAI
llm = OpenAI(model='gpt-3.5-turbo-instruct', temperature=0)
response = llm.invoke("机の上に積み木が")
print(response)
# 積まれている状態で、積み木を一つずつ取り除いていきます。...
from langchain_core.output_parsers import StrOutputParser, JsonOutputParser, CommaSeparatedListOutputParser
# StrOutputParser
output_parser = StrOutputParser()
response = output_parser.invoke(1234)
print(response)
# JsonOutputParser
output_parser = JsonOutputParser()
response = output_parser.invoke('{"a":1, "b":2}')
print(response)
# CommaSeparatedListOutputParser
output_parser = CommaSeparatedListOutputParser()
response = output_parser.invoke('a,b,c,d')
print(response)
# 1234
# {'a': 1, 'b': 2}
# ['a', 'b', 'c', 'd']
from langchain.prompts import PromptTemplate
from langchain_openai import ChatOpenAI
from langchain_core.output_parsers import StrOutputParser
prompt_template = PromptTemplate(
template = "次の食材からレシピを提案してください。「{input}」",
input_variables=["input"],
)
chat = ChatOpenAI(model_name="gpt-4o-mini", temperature=0)
output_parser = StrOutputParser()
response = chat.invoke(prompt_template.format(input="じゃがいも 玉ねぎ 人参 りんご"))
response = output_parser.parse(response.content)
print(response)
# もちろんです!「じゃがいも」「玉ねぎ」「人参」「りんご」を使ったレシピとして、「りんごと野菜のスープ」を提案します。...
from langchain.prompts import PromptTemplate
from langchain_openai import ChatOpenAI
from langchain_core.output_parsers import StrOutputParser
prompt_template = PromptTemplate(
template = "次の食材からレシピを提案してください。「{input}」",
input_variables=["input"],
)
chat = ChatOpenAI(model_name="gpt-4o-mini", temperature=0)
output_parser = StrOutputParser()
chain = prompt_template | chat | output_parser
response = chain.invoke(input="じゃがいも 玉ねぎ 人参 りんご")
print(response)
# じゃがいも、玉ねぎ、人参、りんごを使ったレシピとして、「りんごと野菜のスープ」を提案します。以下に作り方を示します。...
2. Retrieval
Retrievalでは、言語モデルが学習していない固有のデータを参照して回答させるための機能を提供する。ここでは、手元のテキストファイル、指定のWebページ、Wikipediaの情報を参照して回答させる方法を紹介する。
# sample.txt
スイル
スイルはボードゲームとボールゲームを組み合わせた新感覚のスポーツです。基本ルールは以下のとおりです。
1. チーム構成:
- 各チームは4人のプレイヤーで構成されます。
2. フィールド:
- フィールドは長方形で、中央にネットが張られています。
-「ドット」と呼ばれる特定のエリアが、フィールド上を常に移動しています。
3. ボールゲーム要素:
- ボールは軽くて弾力のある素材で作られています。
- ボールを相手チームの「ドット」に運ぶことが目的です。
4. ボードゲーム要素:
- フィールドの中央に大きなボードがあり、プレイヤーはボード上で駒を動かします。
- 駒を動かすためには、ボールを相手チームの「ドット」に運ぶ必要があります。
5. ゲームの進行:
- ゲームは2つのハーフに分かれており、各ハーフは20分です。
- ハーフタイムは10分間です。
6. 得点方法:
- ボールを相手チームの「ドット」に入れることで得点します。
- ボード上で特定のマスに駒を進めることで追加得点が得られます。
7. 反則:
- 手でボールを扱う、相手プレイヤーを押す、引っ張るなどの行為は反則とされます。
- 反則があった場合、相手チームにフリースローが与えられます。
8. 勝利条件:
- 試合終了時に得点が多いチームが勝利します。
- 同点の場合は延長戦を行い、それでも決着がつかない場合はペナルティシュートで勝敗を決めま
す。
from langchain_community.vectorstores import Chroma
from langchain_community.document_loaders import TextLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_core.runnables import RunnablePassthrough
from langchain.prompts import PromptTemplate
from langchain_openai import ChatOpenAI, OpenAIEmbeddings
from langchain_core.output_parsers import StrOutputParser
embedding = OpenAIEmbeddings(model="text-embedding-3-large")
if os.path.exists("vectorstore"): # vectorstore作成
vectorstore = Chroma(persist_directory="./vectorstore", embedding_function=embedding)
else: # vectorstore読み込み
document = TextLoader('./sample.txt', encoding="utf-8").load()
text_splitter = RecursiveCharacterTextSplitter(chunk_size=100, chunk_overlap=0)
documents = text_splitter.split_documents(document)
vectorstore = Chroma.from_documents(persist_directory="./vectorstore", documents=documents, embedding=embedding)
retriever = vectorstore.as_retriever(search_kwargs={"k": 4})
prompt = PromptTemplate(
input_variables=["context","question"],
template="""以下のContextを参照して、Questionに回答してください。Contextの中に回答に役立つ情報が含まれていなければ、分からないと答えてください。
Context: {context}
Question: {question}
"""
)
chat = ChatOpenAI(model_name="gpt-4o-mini", temperature=0)
rag_chain = (
{
"question": RunnablePassthrough(),#invokeで指定したtextが入る。
"context": retriever
}
|prompt
|chat
|StrOutputParser()
)
response = rag_chain.invoke("スイルでボード上の駒を動かすための条件は?")
print(response)
# スイルでボード上の駒を動かすための条件は、ボールを相手チームの「ドット」に運ぶ必要があることです。
from langchain_community.document_loaders import WebBaseLoader
from langchain_community.vectorstores import Chroma
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_core.runnables import RunnablePassthrough
from langchain.prompts import PromptTemplate
from langchain_openai import ChatOpenAI, OpenAIEmbeddings
from langchain_core.output_parsers import StrOutputParser
embedding = OpenAIEmbeddings(model="text-embedding-3-large")
document = WebBaseLoader("https://minkabu.jp/financial_item_ranking/sales").load()
text_splitter = RecursiveCharacterTextSplitter(chunk_size=100, chunk_overlap=0)
documents = text_splitter.split_documents(document)
vectorstore = Chroma.from_documents(documents=documents, embedding=embedding)
retriever = vectorstore.as_retriever(search_kwargs={"k": 4})
prompt = PromptTemplate(
input_variables=["context","question"],
template="""以下のContextを参照して、Questionに回答してください。Contextの中に回答に役立つ情報が含まれていなければ、分からないと答えてください。
Context: {context}
Question: {question}
"""
)
chat = ChatOpenAI(model_name="gpt-4o-mini", temperature=0)
rag_chain = (
{
"question": RunnablePassthrough(),#invokeで指定したtextが入る。
"context": retriever
}
|prompt
|chat
|StrOutputParser()
)
response = rag_chain.invoke("売上1位は?")
print(response)
# 売上1位はトヨタ(7203)です。
from langchain_community.retrievers import WikipediaRetriever
from langchain_core.runnables import RunnablePassthrough
from langchain.prompts import PromptTemplate
from langchain_openai import ChatOpenAI
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import chain as chain_decorator
prompt = PromptTemplate(
input_variables=["context","question"],
template="""以下のContextを参照して、Questionに回答してください。Contextの中に回答に役立つ情報が含まれていなければ、分からないと答えてください。
Context: {context}
Question: {question}
"""
)
chat = ChatOpenAI(model_name="gpt-4o-mini", temperature=0)
retriever = WikipediaRetriever(lang='ja', top_k_results=3, doc_content_chars_max=300)
output_parser = StrOutputParser()
@chain_decorator
def join(xs):
return ' '.join([x.page_content for x in xs])
chain = (
{
"question":RunnablePassthrough(),#invokeで指定したtextが入る。
"context":retriever | join
}
| prompt
| chat
| StrOutputParser()
)
response = chain.invoke("違法素数とは?")
print(response)
# 違法素数とは、素数のうち、違法となるような情報やコンピュータプログラムを含む数字のことです。...
3. Memory
Memoryでは、前の文脈を踏まえて回答できるように、過去の会話履歴の保存、読み込みを簡単に実装するための機能を提供する。ここでは、ChatGPTのWebアプリケーションのように会話履歴を保持するして、APIを呼び出す方法を紹介する。
from langchain.prompts import ChatPromptTemplate, SystemMessagePromptTemplate, HumanMessagePromptTemplate
from langchain_openai import ChatOpenAI
prompt = ChatPromptTemplate.from_messages([
SystemMessagePromptTemplate.from_template("あなたは優秀な医者です。診断をしてほしいと指示があるまで「うん」「そうですか」など短く相槌を打ち、診断をしてほしいと指示があれば診断を下してください。"),
HumanMessagePromptTemplate.from_template("{input}")
])
chat = ChatOpenAI(model_name="gpt-4o-mini", temperature=0)
chain = prompt | chat
response1 = chain.invoke({"input":"頭が痛いです"})
response2 = chain.invoke({"input":"手足がしびれます"}) # 「頭が痛いです」と伝えたことはもう覚えていない!
response3 = chain.invoke({"input":"熱もあるようです"}) # 「手足がしびれます」と伝えたことはもう覚えていない!
response4 = chain.invoke({"input":"診断してください"}) # 「熱もあるようです」と伝えたことはもう覚えていない!
print(f"""
回答1:{response1.content},
回答2:{response2.content},
回答3:{response3.content},
回答4:{response4.content},
""")
# 回答1:うん。,
# 回答2:うん。,
# 回答3:うん。,
# 回答4:症状や状況について詳しく教えていただけますか?,
from langchain.prompts import ChatPromptTemplate, SystemMessagePromptTemplate, MessagesPlaceholder, HumanMessagePromptTemplate
from langchain_openai import ChatOpenAI
from langchain_core.runnables.history import RunnableWithMessageHistory
from langchain_community.chat_message_histories import ChatMessageHistory
from langchain_core.chat_history import BaseChatMessageHistory
prompt = ChatPromptTemplate.from_messages([
SystemMessagePromptTemplate.from_template("あなたは優秀な医者です。診断をしてほしいと指示があるまで「うん」「そうですか」など短く相槌を打ち、診断をしてほしいと指示があれば診断を下してください。"),
MessagesPlaceholder(variable_name="history"),
HumanMessagePromptTemplate.from_template("{input}")
])
chat = ChatOpenAI(model_name="gpt-4o-mini", temperature=0)
chain = prompt | chat
# セッションIDごとの会話履歴の取得
store = {}
def get_session_history(session_id: str) -> BaseChatMessageHistory:
if session_id not in store:
store[session_id] = ChatMessageHistory()
return store[session_id]
# RunnableWithMessageHistoryでチェーンをラップする
chain_with_history = RunnableWithMessageHistory(
chain,
get_session_history,
input_messages_key="input",
history_messages_key="history",
)
response1 = chain_with_history.invoke({"input":"頭が痛いです"}, config={"configurable": {"session_id": "123"}})
response2 = chain_with_history.invoke({"input":"手足がしびれます"}, config={"configurable": {"session_id": "123"}})
response3 = chain_with_history.invoke({"input":"熱もあるようです"}, config={"configurable": {"session_id": "123"}})
response4 = chain_with_history.invoke({"input":"診断してください"}, config={"configurable": {"session_id": "123"}})
print(f"""
回答1:{response1.content},
回答2:{response2.content},
回答3:{response3.content},
回答4:{response4.content},
""")
# 回答1:うん。,
# 回答2:そうですか。,
# 回答3:うん。,
# 回答4:頭痛、手足のしびれ、発熱の症状から考えられる可能性はいくつかあります。...
4. Chains
Chainsでは、個別の機能を組み合わせて、より複雑な機能を簡単に実装するための機能を提供する。Chainsを簡単に構築するために、LCELという記法が推奨されている。LCELでは|
でつなげてチェーンを表現する。
|
で並べて表現する。from langchain_core.runnables import chain as chain_decorator
from langchain_core.runnables import RunnablePassthrough
@chain_decorator
def double(x):
return x * 2
@chain_decorator
def triple(x):
return x * 3
@chain_decorator
def increment(x):
return x + 1
chain = double | triple | increment
response = chain.invoke(2)
print(response)
# チェーンの可視化
chain.get_graph().print_ascii()
# 13
# +--------------+
# | double_input |
# +--------------+
# *
# *
# *
# +--------+
# | double |
# +--------+
# *
# *
# *
# +--------+
# | triple |
# +--------+
# *
# *
# *
# +-----------+
# | increment |
# +-----------+
# *
# *
# *
# +------------------+
# | increment_output |
# +------------------+
from langchain_core.runnables import chain as chain_decorator
from langchain_core.runnables import RunnablePassthrough
@chain_decorator
def double(x):
return x * 2
@chain_decorator
def triple(x):
return x * 3
@chain_decorator
def increment(x):
return x + 1
chain = {
"double":double,
"triple":triple,
} | RunnablePassthrough()
response = chain.invoke(2)
print(response)
# チェーンの可視化
chain.get_graph().print_ascii()
# {'double': 4, 'triple': 6}
# +------------------------------+
# | Parallel<double,triple>Input |
# +------------------------------+
# ** **
# ** **
# * *
# +--------+ +--------+
# | double | | triple |
# +--------+ +--------+
# ** **
# ** **
# * *
# +-------------------------------+
# | Parallel<double,triple>Output |
# +-------------------------------+
# *
# *
# *
# +-------------+
# | Passthrough |
# +-------------+
# *
# *
# *
# +-------------------+
# | PassthroughOutput |
# +-------------------+
from langchain.prompts import PromptTemplate
from langchain_openai import ChatOpenAI
from langchain_core.output_parsers import StrOutputParser
prompt_template = PromptTemplate(
template = "次の食材からレシピを提案してください。「{input}」",
input_variables=["input"],
)
chat = ChatOpenAI(model_name="gpt-4o-mini", temperature=0)
output_parser = StrOutputParser()
chain = prompt_template | chat | output_parser
response = chain.invoke(input="じゃがいも 玉ねぎ 人参 りんご")
print(response)
# じゃがいも、玉ねぎ、人参、りんごを使ったレシピとして、「りんごと野菜のスープ」を提案します。以下に作り方を示します。...
5. Agents
Agentsでは、言語モデルの呼び出しだけでは対応できないタスクを実行できるように、Web検索やファイル入出力といった機能を実装するための機能を提供する。
from langchain_community.tools import DuckDuckGoSearchRun, WriteFileTool
from langchain.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI
from langchain.agents import create_tool_calling_agent, AgentExecutor
search = DuckDuckGoSearchRun()
writefile = WriteFileTool()
prompt = ChatPromptTemplate.from_messages([
("system", "you're a helpful assistant"),
("human", "{input}"),
("placeholder", "{agent_scratchpad}"),
])
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
tools = [search, writefile]
agent = create_tool_calling_agent(llm, tools, prompt)
agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True)
agent_executor.invoke({"input":"違法素数の説明をresult.txtというファイルに書き込んで保存してください"})
# > Entering new AgentExecutor chain...
# Invoking: `write_file` with `{'file_path': 'result.txt', 'text': '違法素数(いほうそすう、英: illegal prime)とは、特定の条件や規則に従って定義された素数のことを指します。一般的に、素数は1とその数自身以外の約数を持たない自然数ですが、違法素数は通常の素数の定義から外れる特異な性質を持つことがあります。これらの素数は、数学的な研究や暗号理論などの分野で特に興味深いとされています。違法素数の具体的な定義や性質は、文脈によって異なる場合があります。', 'append': False}`
# File written successfully to result.txt.違法素数の説明を `result.txt` というファイルに書き込みました。ファイルは正常に保存されています。
# > Finished chain.
from langchain.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI
from langchain.agents import create_tool_calling_agent, AgentExecutor
from langchain_community.tools import tool as tool_decorator
@tool_decorator
def multiply(x: float, y: float) -> float:
"""Multiply 'x' times 'y'."""
return x * y
@tool_decorator
def exponentiate(x: float, y: float) -> float:
"""Raise 'x' to the 'y'."""
return x**y
@tool_decorator
def add(x: float, y: float) -> float:
"""Add 'x' and 'y'."""
return x + y
prompt = ChatPromptTemplate.from_messages([
("system", "you're a helpful assistant"),
("human", "{input}"),
("placeholder", "{agent_scratchpad}"),
])
tools = [multiply, exponentiate, add]
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
agent = create_tool_calling_agent(llm, tools, prompt)
agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True)
agent_executor.invoke({"input": "3+5を計算してその結果の2乗を計算してください"})
# > Entering new AgentExecutor chain...
# Invoking: `add` with `{'x': 3, 'y': 5}`
# 8.0
# Invoking: `exponentiate` with `{'x': 8, 'y': 2}`
# 64.0 3 + 5 の計算結果は 8 で、その 2 乗は 64 です。
# > Finished chain.
6. Callbacks
Callbacksでは、イベント発生時にある特定の処理を実行させる機能を提供する。
from langchain.callbacks.base import BaseCallbackHandler
from langchain_openai import ChatOpenAI
class LogCallbackHandler(BaseCallbackHandler):
def on_chat_model_start(self, serialized, messages, **kwargs):
print("ChatModelの実行を開始")
print(f"入力: {messages}")
chat = ChatOpenAI(model="gpt-4o-mini", temperature=0, streaming=True, callbacks=[LogCallbackHandler()])
response = chat.invoke("こんにちは").content
print(response)
# ChatModelの実行を開始
# 入力: [[HumanMessage(content='こんにちは')]]
# こんにちは!どういったことをお手伝いできますか?