見出し画像

LangChain v0.1 クイックスタートガイド - Python版

Python版の「LangChain」のクイックスタートガイドをまとめました。

・langchain 0.1.16


1. LangChain

LangChain」は、「大規模言語モデル」 (LLM : Large language models) と連携するアプリの開発を支援するライブラリです。

「LLM」という革新的テクノロジーによって、開発者は今まで不可能だったことが可能になりました。しかし、「LLM」を単独で使用するだけでは、真に強力なアプリケーションを作成するのに不十分です。真の力は、それを他の 計算 や 知識 と組み合わせた時にもたらされます。「LangChain」は、そのようなアプリケーションの開発をサポートします。

主な用途は、次の3つになります。

文書に関する質問応答
チャットボット
エージェント

v0.1 ではlangchainパッケージが次の3つのパッケージに分割されました。すべて下位互換性のある方法で行われました。

・langchain-core : 「Core」と「LangChain Expression Language」を含む
・langchain-community : サードパーティ統合を含む
・langchain : アプリの認知アーキテクチャの中核となる、より高レベルでユースケース固有のチェーン、エージェント、検索アルゴリズムを含む

2. LangChain のモジュール

「LangChain」は、言語モデル アプリケーションの構築に使用できる多くのモジュールを提供します。モジュールを組み合わせて複雑なアプリケーションを作成したり、個別に使用したりできます。

主なモジュールは、次のとおりです。

・Model I/O : モデルの入出力
 ・Language Model : 言語モデルによる推論の実行
  ・LLM : テキスト生成モデル
  ・ChatModel : チャットモデル
 ・Prompt Template : ユーザー入力からのプロンプトの生成
 ・Output Parser : 言語モデルの応答を構造データ化
・Chain : 複数のLLMやプロンプトの入出力を繋げる
 ・LCEL (LangChain Expression Language) : 表記言語によるチェーン実装
 ・Chain Interface : クラスによるチェーン実装 (レガシー)
・Agent : ユーザーの要求に応じてどの機能をどういう順番で実行するかを決定
・Memory : 過去のやりとりに関する情報を保持。
・Retrieval : 検索拡張生成 (RAG)
・Callback : ロギング、モニタリング、ストリーミングなどで利用

モジュールは、単純なアプリケーションではモジュール単体で使用でき、より複雑なユースケースではモジュールをチェーンで繋げて利用することができます。

3. インストール

Google Colabでのインストール手順は、次のとおりです。

(1) パッケージのインストール。

# パッケージのインストール
!pip install langchain==0.1.16
!pip install langchain_openai

(2) 環境変数の準備。
左端の鍵アイコンで「OPENAI_API_KEY」を設定してからセルを実行してください。

import os
from google.colab import userdata

# 環境変数の準備 (左端の鍵アイコンでOPENAI_API_KEYを設定)
os.environ["OPENAI_API_KEY"] = userdata.get("OPENAI_API_KEY")

4. Model I/O

LLMアプリケーションの中核はLLMです。「LangChain」は、様々なLLMを手順で操作できる共通インタフェースを提供します。

主な構成要素は、次の3つです。

・Language Model
・Prompt Template
・Output Parser

4-1. Language Model

「LangChain」の最も基本的な機能は、LLMを呼び出すことです。「Language Model」は、LLMを呼び出すモジュールになります。

次の2種類のインタフェースを提供します。

・LLM
今回は例として、「テキスト生成モデル」でLLMの呼び出しを行います。テキスト→テキストのインタフェースになります。

from langchain.llms import OpenAI

# LLMの準備
llm = OpenAI(temperature=0.9)

# LLMの実行
output = llm.invoke("コンピュータゲームを作る日本語の新会社名をを1つ提案してください。")
print(type(output))
print(output)
<class 'str'>
ツクルゲームス

LLMの入力は文字列、出力は文字列になります。Messageリストで入力することも可能です。

・ChatModel
次に、「チャットモデル」でLLMの呼び出しを行います。メッセージリスト→メッセージのインタフェースになります。

from langchain.chat_models import ChatOpenAI
from langchain.schema import HumanMessage

# ChatModelの準備
chat_model = ChatOpenAI(temperature=0.9)

# ChatModelの実行
messages = [
    HumanMessage(content="コンピュータゲームを作る日本語の新会社名をを1つ提案してください。")
]
output = chat_model.invoke(messages)
print(type(output))
print(output)
<class 'langchain_core.messages.ai.AIMessage'>
content='「夢遊計画」(ゆめあるけいかく)' response_metadata={'token_usage': {'completion_tokens': 18, 'prompt_tokens': 38, 'total_tokens': 56}, 'model_name': 'gpt-3.5-turbo', 'system_fingerprint': 'fp_b28b39ffa8', 'finish_reason': 'stop', 'logprobs': None} id='run-93614b2e-aaba-40c6-ace3-f6abced6610e-0'

ChatModelの入力はMessageリスト、出力はMessageになります。文字列で入力することも可能です。

メッセージは役割 (システム、ユーザー、AIなど) とコンテンツの情報を持ちます。「LangChain」の主なメッセージ型は、次の3種類です。

・SystemMessage : システムからのメッセージ
・HumanMessage : ユーザーからのメッセージ
・AIMessage : AIからのメッセージ

「Language Model」の使い方について詳しくは、「Language Models」を参照してください。

4-2. Prompt Template

Prompt Template」は、ユーザー入力からプロンプトを生成するためのテンプレートです。アプリケーションで LLM を使用する場合、通常、ユーザー入力を直接LLM に渡すことはありません。ユーザー入力を基に「Prompt Template」でプロンプトを作成して、それをLLMに渡します。

今回は例として、ユーザー入力「作るもの」を基に「○○を作る日本語の新会社名をを1つ提案してください」というプロンプトを生成します。

from langchain.prompts import PromptTemplate

# プロンプトテンプレートの準備
prompt_template = PromptTemplate(
    input_variables=["product"],
    template="{product}を作る日本語の新会社名をを1つ提案してください",
)

# プロンプトテンプレートの実行
output = prompt_template.invoke({"product": "家庭用ロボット"})
print(type(output))
print(output)
<class 'langchain_core.prompt_values.StringPromptValue'>
text='家庭用ロボットを作る日本語の新会社名をを1つ提案してください'

「Prompt Template」の入力は辞書、出力は「PromptValue」です。

「PromptValue」は、LLMとChatModelの入力となるクラスです。文字列(LLMの入力)にキャストするto_string()と、Messageリスト(ChatModelの入力)にキャストするto_messages()を持ちます。

「Prompt Template」の使い方について詳しくは、「Prompts」を参照してください。

4-3. Output Parser

「Output Parser」 は、「Language Model」の出力を用途にあわせて変換するモジュールです。

主な用途は次のとおりです。

・Language Modelの出力 → プレーンテキスト
・Language Modelの出力 → 構造化データ (JSONなど)
・Language Modelの出力 → メッセージ以外の情報 (OpenAI Functionsなど)

今回は、「StrOutputParser」でMessageリストをプレーンテキストに変換します。

from langchain_core.output_parsers import StrOutputParser
from langchain.schema import AIMessage

# Output Parserの準備
output_parser = StrOutputParser()

# Output Parserの実行
message = AIMessage(content="AIからのメッセージです")
output = output_parser.invoke(message)
print(type(output))
print(output)
<class 'str'>
AIからのメッセージです

「StrOutputParser」の入力は「Language Model」の出力 (文字列またはMessageリスト)、出力は文字列になります。

提供されている「OutputParser」は、次のとおりです。

・CSV parser
・Datetime parser
・Enum parser
・JSON parser
・OpenAI Functions
・OpenAI Tools
・Output-fixing parser
・Pandas DataFrame Parser
・Pydantic parser
・Retry parser
・Structured output parser
・XML parser
・YAML parser

5. Chain

単純なアプリケーションであれば「LLM」や「ChatModel」の単独使用で問題ありませんが、それらモジュール (Runnable) をチェーンで繋げることで、複雑なアプリケーションを構築することができます。

Chainの実装方法には、次の2種類があります。

・LCEL (LangChain Expression Language) : 表記言語によるチェーン実装
・Chain Interface : クラスによるチェーン実装 (レガシー)

「LangChain」ではこのチェーンを「LCEL」(LangChain Expression Language)と呼ばれる表現言語で記述します。基本的な使い方は、モジュールを「|」で繋げるだけになります。

from langchain.prompts import PromptTemplate
from langchain.chat_models import ChatOpenAI
from langchain_core.output_parsers import StrOutputParser

# プロンプトテンプレートの準備
prompt_template = PromptTemplate(
    input_variables=["product"],
    template="{product}を作る日本語の新会社名を1つ提案してください",
)

# ChatModelの準備
chat_model = ChatOpenAI(temperature=0.9)

# OutputParserの準備
output_parser = StrOutputParser()

# チェーンをつなげて実行
chain = prompt_template | chat_model | output_parser
output = chain.invoke({"product": "家庭用ロボット"})
print(output)
家庭ロボットテクノロジー株式会社

「LCEL」の使い方については「LangChain Expression Language (LCEL)」を参照してください。

6. Agent

6-1. Agentの概要

Agent」は、ユーザーの要求に応じて、どの「Action」をどういう順番で実行するかを決定するモジュールです。「Chain」の機能の実行の順番はあらかじめ決まっていますが、「Agent」はユーザーの要求に応じてLLM自身が決定します。この「Agent」が「Action」で実行する特定の機能のことを「Tool」と呼びます。

・Agent : 実行するアクションと順番を決定
・Action : 次のアクションを実行 or ユーザーに応答
・Tool : アクションで実行される特定の機能

今回は例として、次の2つのToolを使うAgentを作成します。

・Tavily Tool (オンライン検索用)
・Retriever Tool (ローカル検索用)

6-2. Tavility Toolの準備

(1) パッケージのインストール。
「Tavily」のパッケージをインストールします。

# パッケージのインストール
!pip install tavily-python

(2) 環境変数の準備。
左端の鍵アイコンで「TAVILY_API_KEY」を設定してからセルを実行してください。

import os
from google.colab import userdata

# 環境変数の準備 (左端の鍵アイコンでTAVILY_API_KEYを設定)
os.environ["TAVILY_API_KEY"] = userdata.get("TAVILY_API_KEY")

(3) Tavilityツールの準備。

from langchain_community.tools.tavily_search import TavilySearchResults

# Tavilyの準備
tavily_tool = TavilySearchResults()

# 動作確認
tavily_tool.invoke("東京の天気は?")
[{'url': 'https://weather.yahoo.co.jp/weather/jp/13/4410.html',
  'content': '東京(東京)の天気予報。今日・明日の天気と風と波、...'},
 {'url': 'https://weathernews.jp/onebox/35.691667/139.750000/q=東京&v=7568ec9017b1ee619b719b2e27bb2222ff3533c3d047419f40d2980258c0a799&temp=c&lang=ja',
  'content': '午後20%\n日差しに暖かさを感じそう...'},
 {'url': 'https://weather.yahoo.co.jp/weather/jp/13/',
  'content': '日本海には高気圧があって、...'},
 {'url': 'https://tenki.jp/forecast/3/16/',
  'content': '東京都の天気予報です。市区町村別の今日の天気、...'},
 {'url': 'https://weathernews.jp/onebox/tenki/tokyo/',
  'content': '東京の最新天気情報。よく当たる1時間毎のピンポイント天気、...'}]

6-3. Retriever Toolの準備

(1) パッケージのインストール。
ベクトルストア関連のパッケージをインストールします。

# パッケージのインストール
!pip install unstructured faiss-cpu

(2) ドキュメントの準備。
今回は、マンガペディアの「ぼっち・ざ・ろっく!」のドキュメントを用意しました。

・bocchi.txt

(3) Colabにdataフォルダを作成して配置。

(4) Retrieverの準備。

from langchain.document_loaders import DirectoryLoader
from langchain_community.vectorstores import FAISS
from langchain_openai import OpenAIEmbeddings
from langchain_text_splitters import RecursiveCharacterTextSplitter

# ドキュメントの準備
loader = DirectoryLoader('./data/')
docs = loader.load()
documents = RecursiveCharacterTextSplitter(
    chunk_size=1000,
    chunk_overlap=200
).split_documents(docs)

# ベクトルストアの準備
vector = FAISS.from_documents(documents, OpenAIEmbeddings())

# Retrieverの準備
retriever = vector.as_retriever()

# 動作確認
retriever.invoke("ぼっち・ざ・ろっくのぼっちちゃんの本名は?")
[Document(page_content='後藤ひとり(ごとうひとり)\n\n秀華高校に通う女子。...', metadata={'source': 'data/bocchi.txt'}),
 Document(page_content='長谷川あくび(はせがわあくび)\n\nメタルバンド...', metadata={'source': 'data/bocchi.txt'}),
 Document(page_content='2号(にごう)\n\n美術大学の映像学科に在籍する女子。...', metadata={'source': 'data/bocchi.txt'}),
 Document(page_content='山田リョウ(やまだりょう)\n\n下北沢高校に通う女子。...', metadata={'source': 'data/bocchi.txt'})]

(5) Retriever Toolの準備。

from langchain.tools.retriever import create_retriever_tool

# Retriever Toolの準備
retriever_tool = create_retriever_tool(
    retriever,
    "bocchi_search",
    "ぼっち・ざ・ろっくに関する情報を検索します。ぼっち・ざ・ろっくに関する質問がある場合は、このツールを使用する必要があります。",
)

6-4. Agentの準備

(1) Toolsの準備。

# Toolsの準備
tools = [tavily_tool, retriever_tool]

(2) プロンプトテンプレートとモデルの準備。

from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI

# プロンプトテンプレートの準備
prompt = ChatPromptTemplate.from_messages([
    ("system", "you're a helpful assistant. Please respond in Japanese."),
    ("human", "{input}"),
    ("placeholder", "{agent_scratchpad}"),
])

# 言語モデルの準備
model = ChatOpenAI(model="gpt-4-turbo", temperature=0)

(3) Agentの準備。

from langchain.agents import create_tool_calling_agent, AgentExecutor

# エージェントの準備
agent = create_tool_calling_agent(model, tools, prompt)

# AgentExecutorの準備
agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True)

(3) 動作確認1。
オンライン検索が必要な場合は、「Tavility Tool」が呼ばれます。

# 動作確認
output = agent_executor.invoke({"input": "東京の天気は?"})
print(output)
> Entering new AgentExecutor chain...

Invoking: `tavily_search_results_json` with `{'query': '東京の天気'}`

[{'url': 'https://weathernews.jp/onebox/tenki/tokyo/', 'content': '東京の最新天気情報。...'}...]

東京の天気情報をお伝えしますね。現在の東京の天気は冬晴れで、日中は日差しの温もりを感じることができます。気温は最高14℃、最低3℃です。降水確率は午前10%、午後10%となっています。明日の天気予報では、最高気温11℃、最低気温3℃で、午前10%の降水確率が予想されています。冬晴れの日が続く中、体感温度の調節に注意してください。朝晩は冷え込むので、体調管理に気を付けてください。詳細な天気情報は以下のリンクからご確認いただけます:\n[東京の天気情報](https://weathernews.jp/onebox/tenki/tokyo/)

> Finished chain.
{'input': '東京の天気は?', 'output': '東京の天気情報をお伝えしますね。現在の東京の天気は冬晴れで、日中は日差しの温もりを感じることができます。気温は最高14℃、最低3℃です。降水確率は午前10%、午後10%となっています。明日の天気予報では、最高気温11℃、最低気温3℃で、午前10%の降水確率が予想されています。冬晴れの日が続く中、体感温度の調節に注意してください。朝晩は冷え込むので、体調管理に気を付けてください。詳細な天気情報は以下のリンクからご確認いただけます:\n[東京の天気情報](https://weathernews.jp/onebox/tenki/tokyo/)'}

(4) 動作確認2。
ローカル検索が必要な場合は、「Retriever Tool」が呼ばれます。

# 動作確認2
output = agent_executor.invoke({"input": "ぼっち・ざ・ろっくのぼっちちゃんの本名は?"})
print(output)
> Entering new AgentExecutor chain...

Invoking: `bocchi_search` with `{'query': 'ぼっち・ざ・ろっく ぼっちちゃん 本名'}`

後藤ひとり(ごとうひとり)...

ぼっち・ざ・ろっくのぼっちちゃんの本名は後藤ひとり(ごとうひとり)です。

> Finished chain.
{'input': 'ぼっち・ざ・ろっくのぼっちちゃんの本名は?', 'output': 'ぼっち・ざ・ろっくのぼっちちゃんの本名は後藤ひとり(ごとうひとり)です。'}

「Agent」の使い方について詳しくは、「Agents」を参照してください。

7. Memory

これまでの「Chain」や「Agent」はステートレスでしたが、「Memory」を使うことで、「Chain」や「Agent」で過去の会話のやり取りを記憶することができます。過去の会話の記憶を使って、会話することができます。

Agentに「Memory」を追加する手順は、次のとおりです。

(1) AgentExecutorをRunnableWithMessageHistoryでラップ。

from langchain_community.chat_message_histories import ChatMessageHistory
from langchain_core.runnables.history import RunnableWithMessageHistory

# Memoryの準備
message_history = ChatMessageHistory()

# Memory付きAgentの準備
agent_with_chat_history = RunnableWithMessageHistory(
    agent_executor,
    lambda session_id: message_history,
    input_messages_key="input",
    history_messages_key="chat_history",
)

現実世界のほとんどのシナリオではセッション IDが必要になります。今回は、単純なメモリ内の「ChatMessageHistory」を使用しているため、ここでは実際には使用されていません。

(2) 動作確認1。

# 動作確認1
output = agent_with_chat_history.invoke(
    {"input": "うちのネコの名前は白子です。"},
    config={"configurable": {"session_id": "<foo>"}},
)
print(output)
> Entering new AgentExecutor chain...
白子という名前はとてもかわいいですね!白子ちゃんは元気ですか?何か質問やお手伝いがあればお知らせくださいね。

> Finished chain.
{'input': 'うちのネコの名前は白子です。', 'chat_history': [], 'output': '白子という名前はとてもかわいいですね!白子ちゃんは元気ですか?何か質問やお手伝いがあればお知らせくださいね。'}

(3) 動作確認2。
過去の会話内容を覚えていることがわかります。

# 動作確認2
output = agent_with_chat_history.invoke(
    {"input": "うちのネコの名前を呼んでください"},
    config={"configurable": {"session_id": "<foo>"}},
)
print(output)
> Entering new AgentExecutor chain...
白子ちゃん、おはよう!元気にしてるかな?白子ちゃん、白子ちゃん、こっちに来て〜♪

> Finished chain.
{'input': 'うちのネコの名前を呼んでください', 'chat_history': [HumanMessage(content='うちのネコの名前は白子です。'), AIMessage(content='白子という名前はとてもかわいいですね!白子ちゃんは元気ですか?何か質問やお手伝いがあればお知らせくださいね。')], 'output': '白子ちゃん、おはよう!元気にしてるかな?白子ちゃん、白子ちゃん、こっちに来て〜♪'}

「Memory」の使い方について詳しくは、「Memory」を参照してください。

関連

LangChain
LangChain.js
LangChain Templates
Chat LangChain

LangChain Document
LangChain Integrations
LangChain Hub
LangSmith
LangGraph
LangChain Blog



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