LangChain徹底解説 読み込まれました

About

Service

Works

Outline

Philosophy

Blog

Recruit

Contact

  • All

  • ChatGPT

  • 機械学習

2023/11/20 04:33

LangChain徹底解説

みなさん、大規模言語モデルの重要性をご存知ですか?近年、これらのモデルを活用したアプリケーション開発が注目されています。

特にLangChainは、大規模言語モデルを効率的に扱うための強力なライブラリです。LangChainを用いることで大規模言語モデルの威力が爆上がりします。

大規模言語モデルと聞いてまず思い浮かぶのは、ChatGPTではないでしょうか。そこで、まずはChatGPTに簡単に解説していきます。

目次

ChatGPTとは?

ChatGPTは、OpenAIによって開発された自然言語処理(NLP)モデルの一種です。ChatGPTは、大規模なテキストデータセットを使用してトレーニングされ、テキストに基づいた質問応答や会話生成などのタスクに利用できる人工知能エージェントです。ChatGPTはGPT(Generative Pre-trained Transformer)アーキテクチャに基づいており、テキストデータのパターンや構造を学習し、人間のような自然な会話を生成できるように設計されています。

ChatGPTは、一般のユーザーが質問をしたり、情報を取得したり、対話を行ったりするために使用できるツールとして、ウェブサイトやアプリケーションに統合されることがあります。ユーザーがテキストで質問を投げかけると、ChatGPTは文脈を理解し、適切な応答を生成します。

ただし、ChatGPTはコンピュータープログラムであるため、生成された情報は入力データに基づいており、正確性や信頼性に関して確認が必要な場合があります。また、適切な利用ガイドラインや倫理的な観点からの注意が必要です。

ChatGPTは、カスタマーサポート、情報検索、教育支援、クリエイティブライティング、プログラミングサポートなど、さまざまな用途に活用されています。開発者やビジネスは、ChatGPTをカスタマイズして特定のタスクに適応させることも可能です。

上に書いてある文章を読んで、何か違和感を感じましたか?おそらく、多くの方は違和感を感じなかったでしょう。

実は、上の説明文はChatGPTによって生成されたものです。このことからも分かるように、ChatGPTは人間同士の会話のように自然な対話を行う能力を持っています。

LangChainとは?

次にLangChainの説明に移りましょう。

LangChainは、大規模言語モデルに様々な拡張機能を付加できる有用なライブラリです。

ライブラリとは「特定の言語でプログラムを作成する際に、呼び出すだけで便利な機能を使えるようにしてくれる道具の集まり」です。

例えば、GPT-3モデルを利用してチャットボットアプリを開発する際に、GPT-3だけでは最新情報をもとに回答を生成することはできません。しかしLangChainを用いることで最新情報をもとに回答を生成することができるようになります。

現時点でLangChainは、Python、JavaScript、TypeScriptというプログラミング言語に対応しています。 Python版はLangChainの全機能に対応しているため、他の言語よりも高度な操作が可能です。

そのため、LangChainを使用する際はpythonでプログラムすることをおすすめします。

LangChainの主な機能

LangChainには主に6つの機能によって構成されています。これらの機能を組み合わせることで、大規模言語モデル(LLM)に様々な機能を追加することができます。

以下でそれぞれの機能について説明していきます。

Model I/O (LLMモデルの切り替え)

ModelsはopenAIなどの様々なサービスが提供している大規模言語モデルの切り替え、組み合わせを行ってくれる機能を持っています。

現在のModel I/Oは大きく分けて以下の機能があります。

  1. Prompts(プロンプトの最適化)

  2. Language models(言語モデル)

  3. Output parsers(出力データ形式の指定)

Prompts(プロンプトの最適化)

まずプロンプトが何か理解しましょう。

大規模言語モデルを利用するためには、大規模言語モデルにプロンプトと呼ばれる指示文のようなものを渡す必要があります。大規模言語モデルはプロンプトに沿って回答の生成を行いますが、同様の趣旨のプロンプトでも、プロンプトの書き方によって回答の精度が変わってきます。

LangChainのPromptsは、プロンプトの管理や最適化をおこなうための機能です。

大規模言語モデル(LLM)を使用したアプリ開発には、プロンプトに関する機能を実装しなくてはなりません。LangChainのPromptsはプロンプトの管理や最適化を行うことが可能です。また、簡単に実装することができるため、実装コストの削減が期待できます。

Promptsを用いることで、チームで開発する際に統一された記述方法でのコーディングが可能です。これにより効率的な開発が期待できます。

そしてPromptsはPrompt templatesexample selectorsに分かれます

Prompt templates

Prompt templatesはプロンプトをテンプレート化し、プログラミングによりプロンプトを生成することができる機能です。

Prompt templatesにはPromptTemplateやChatPromptTemplateなどの機能が用意されています。
PromptTemplateはプロンプトのテンプレートを作成する場合に使用します。
ChatPromptTemplateは大規模言語モデルを会話形式で使用する場合に用いることができます。

それでは実際にPromptTemplateを使用してプロンプトテンプレートを作成してみましょう。

from langchain.prompts import PromptTemplate

prompt_template = PromptTemplate.from_template(
    "{target}について{number}文字で説明してください。."
)
prompt_template.format(target="AI", number="50")

上のコードではtargetとnumberを変数とすることで、「〇〇について△△文字で説明してください。」というテンプレートを作成しています。
今回はtargetに"AI"を、numberに"50"を代入しているので、以下のようなプロンプトを作成したことになります。

'AIについて50文字で説明してください。.'

Example selectors

Example selectorsは大量の教師データからプロンプトに入力するデータを選択するための機能です。

例えば、100件の教師データがあった時にその中から30個をランダムに抽出するような機能を作りたいときに便利です。

またExample selectorsではランダムに選択するだけでなく、教師データの類似度などに基づいてデータを選択することも可能です。

Example selectorsを利用することで教師データに偏りが生じにくくなり、精度向上にも繋がります。

Language models(言語モデル)

Language modelsでは、LLMsChat modelsを提供しています。

両者の特徴は以下の点です。

  • LLMs:入力を文字列を受け取り文字列を補完(出力)する

  • Chat models:チャットメッセージ形式で受け取り、AIが生成したチャットメッセージを補完(出力)する

以下でそれぞれについて説明していきます。

LLMs

LLMsとは大規模言語モデルのことであり、オープンAIのGPT-3.5やtext-davinci-003やGoogleのPlan T5、Plan T5XLなどのモデルが含まれています。

LLMsを利用することでモデルを変更してもコードの書き方に変更が少なく、様々なモデルを利用しやすいことが最大のメリットです。

from langchain.llms import OpenAI

llm = OpenAI(model_name="text-davinci-003")
llm("AIについて30文字で教えて。")

上のコードを実行すると、以下のような返答が返ってきます。

AIは、人間の行動や思考を再現しようとするコンピューター技術です。

Chat models

Chat modelsは大規模言語モデル(LLM)をチャット形式で用いたい場合に使用することができます。

LLMsは自然言語処理においてテキストを生成・補完するための基礎的なモデルです。これに対し、LangChainの「Chat models」は、これらの大規模言語モデルを基にした応用的な機能を提供し、特に対話型アプリケーションの開発に焦点を当てています。

つまり、LLMsは一般的な言語モデルの基盤を提供し、LangChainの「Chat models」は対話型に適したツールを提供するという違いがあります。

簡単にChat modelsの実装を行っていきましょう。

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

chat = ChatOpenAI(model_name="gpt-3.5-turbo")
messages = [
	SystemMessage(content="You're a helpful assistant"),
	HumanMessage(content="AIエンジニアについて30文字で教えて。"),
]

chat.invoke(messages)

chat.invoke(messages)では対話型のデータmessagesを受け取り、与えられたメッセージ(システムメッセージとヒューマンメッセージ)に基づいてAIの応答を生成します。この場合、AIは「AIエンジニアについて30文字で教えて。」という要求に応じて回答を生成することになります。

上のコードを実行すると次のような結果が返ってきます。HumanMessageに対する回答が生成されていることが確認できました。

content='AIエンジニアは、AIを開発する専門家。'

Output parsers(出力データの形式指定)

Output parsersは出力データの形式を指定するための機能です。

プロンプトで出力データの形式の指定をすることもできますが、Output parsersを利用することで簡単にデータ形式の指定をすることができます。

例えば、出力をカンマ区切りの形式や指定したクラスの形式で出力することができます。また、指定したフォーマットにならなかった時にそのフォーマットになるまで修正を繰り返してくれる機能もあります。

以下では出力データをリストで出力させる実装をしていきます。

リスト形式で出力するために、CommaSeparatedListOutputParserを使います。

from langchain.output_parsers import CommaSeparatedListOutputParser
from langchain.prompts import PromptTemplate
from langchain.llms import OpenAI

output_parser = CommaSeparatedListOutputParser()
format_instructions = output_parser.get_format_instructions()
prompt = PromptTemplate(
    template="List five {subject}.\n{format_instructions}",
    input_variables=["subject"],
    partial_variables={"format_instructions": format_instructions}
)

llm = OpenAI(model_name="text-davinci-003")
_input = prompt.format(subject="kind of fruits")
output = llm(_input)
output_parser.parse(output)

実行結果は以下のようになります。リスト形式でデータを出力してくれているのが確認できました。

['Apple', 'Orange', 'Banana', 'Mango', 'Pineapple']

Retrieval(外部データ利用)

Retrievalには外部データを用いて回答を生成するための機能が豊富に存在します。

外部データを使用することで大規模言語モデル(LLM)に専門的な内容を回答させることや、トークン制限の制御が可能になります。

例えば、自社の顧客の情報をもとに回答を生成したい場合などにRetrievalを利用することで、自社のシステム上に簡単に実装することができます。

現在、Retrievalでは主に以下の機能が用意されています。

  1. Document loaders(データの読み込み)

  2. Document transformers(データ形式の変換)

  3. Text embedding models(自然言語のベクトル化)

  4. Vector stores(ベクトルデータの管理)

  5. Retrievers(ドキュメント検索)

  6. Indexing(データの整理・構造化)

Document loaders

Document loadersを使用することで、簡単に外部のドキュメントを読み込むことが可能になります。

読み込み可能な形式としてtxtやpdf、csvなど様々な形式に対応しています。
また、Youtube、Notion、AWS、Azureなどの様々なサービスとも連携が可能です。

例えば自社の顧客情報が書かれたcsvファイルを読み込むことで、大規模言語モデルに自社の顧客情報を与える、顧客情報をもとに回答を生成することが可能になります。

Document transformers

Document transformersは読み込まれたデータを大規模言語モデルが扱いやすいようなデータに変換する機能です。

Document transformersにはText splittersとPost retrievalが用意されています。

Text splitters

Text splittersにはデータを分割するための機能が用意されています。

また分割の仕方にも様々な種類が用意されており、用途にあった分割方法を用いることで大規模言語モデル(LLM)の性能向上が期待できます。

単純な文字列データでないpythonコードやMarkdownで書かれたものでも分割が可能です。また、トークン数で分割をすることが可能であり、プロンプトのトークン数を制限するのにも便利です。

大規模言語モデルにはトークン数制限というのがあり、一度に送ることのできるプロンプトと回答文の文字数に制限があります。そのため文字数が多く、一度に送れない場合は分割して送ることで解決することが可能です。

Post retrieval

どの言語モデルかに関わらず、10個以上のドキュメントを言語モデルに取得させると性能が大幅に低下することがわかっています。

具体的には、言語モデルが回答作成する際の重要な事柄がドキュメントの途中に書いてある場合に性能が低下します。そのためPost retrievalではドキュメントの並べ替えを行うことで、言語モデルの性能低下を防止することが可能です。

それでは、具体例を確認して理解を深めましょう。与えられたドキュメントが以下のtextsであり、言語モデルに「"What can you tell me about the Celtics?"」と渡したとします。

言語モデルはここにある10個のドキュメントからCelticsに関する情報を探さなくてはなりません。今回はCelticsに関する内容がドキュメントの3番目と4番目にあります。

このような状態では言語モデルの性能が低下してしまいます。

texts = [
    "Basquetball is a great sport.",
    "Fly me to the moon is one of my favourite songs.",
    "The Celtics are my favourite team.",
    "This is a document about the Boston Celtics",
    "I simply love going to the movies",
    "The Boston Celtics won the game by 20 points",
    "This is just a random text.",
    "Elden Ring is one of the best games in the last 15 years.",
    "L. Kornet is one of the best Celtics players.",
    "Larry Bird was an iconic NBA player.",
]

Post retrievalを用いることで以下のようにCelticsに関するドキュメントを最初に持ってくることが可能です。

[Document(page_content='This is a document about the Boston Celtics', metadata={}),
 Document(page_content='The Celtics are my favourite team.', metadata={}),
 Document(page_content='L. Kornet is one of the best Celtics players.', metadata={}),
 Document(page_content='The Boston Celtics won the game by 20 points', metadata={}),
 Document(page_content='Larry Bird was an iconic NBA player.', metadata={}),
 Document(page_content='Elden Ring is one of the best games in the last 15 years.', metadata={}),
 Document(page_content='Basquetball is a great sport.', metadata={}),
 Document(page_content='I simply love going to the movies', metadata={}),
 Document(page_content='Fly me to the moon is one of my favourite songs.', metadata={}),
 Document(page_content='This is just a random text.', metadata={})]

実際に確認してみると、1番目と2番目にCelticsに関するドキュメントが配置されていることがわかります。これにより、大規模言語モデルの性能低下を防ぐことが可能になります。

Text embedding Models

Text embedding Modelsは文章や単語などの自然言語をベクトル化する機能です。ベクトル化を簡単に説明すると、文章や単語を数値化するということです。

ではベクトル化することで何の役に立つのでしょうか?
以下ではベクトル化することの意味について説明していきます。

例えば、「りんご」という単語は「サッカー」と「果物」のどちらのほうが関連度が高いでしょうか。おそらく、ほとんどの人が「りんご」と関連度が高いのは「果物」と答えるでしょう。しかし、機械は人間のように考えることが出来ません。そこで「りんご」、「サッカー」、「果物」という単語をベクトル化することで単語間の距離を測定し、関連度を測ることが可能となります。「距離が近い」というのはベクトル化した後の数値が、近い値であり、単語間の関連度が高いということです。

Vector stores

先ほど自然言語のベクトル化について説明しましたが、Vector storesはベクトル化したデータを保存するための機能を提供してくれます。

Text embedding ModelsとVector storesを用いることで、自然言語のベクトルデータを簡単に管理、利用することが可能になります。

ベクトル型のストアとしてはchromaDBFAISS・Redis・LanceDBなどがあります。

Retrievers

Retrieversはドキュメントを検索するための機能を提供しています。

検索とは、長い文章を複数のドキュメントに分割し、その中でプロンプトに関連のあるドキュメントを抽出することです。

これにより長い文章が与えられても、回答精度を保ったまま回答することが可能になります。

具体例として短い文章を用いて説明をします。「今日サッカーをしました。その後に公園でリンゴを食べました。」という文章があり、プロンプトとして「何を食べましたか。」という文章が与えられたとします。このときに元の文章を「今日はサッカーをしました。」「その後に公園で、」「リンゴを食べました。」というドキュメントに分割し、プロンプトに関係のあるドキュメントを抽出します。今回の例では「リンゴを食べました。」というドキュメントがプロンプトの内容と関係があるので、「リンゴを食べました。」というドキュメントが抽出されます。

現在、RetrieversではMultiQueryRetrieverなど、複数の機能が提供されています。

Indexing

Indexingは大量の文章や文書を整理する機能です。

具体的には以下のような操作をしたい場合に利用することができます。

  • 重複したコンテンツをベクター ストアに書き込まないようにする

  • 変更されていないコンテンツの再書き込みを避ける

  • 変更されていないコンテンツに対する埋め込みの再計算を避ける

Indexingを用いてデータを整理することでRetrieversなどの検索機能の精度を向上させ、結果として大規模言語モデルの回答精度も向上させることが可能となります。

例えば、顧客情報として、すでに登録されている情報と重複しているものを再び保存するのはメモリの無駄使いになってしまいます。また他社のサービスを利用している場合は、容量を多く使用してしまうことにより金銭的な負担が増えてしまうというデメリットもあります。そこでIndexingを用いることで重複コンテンツの書き込みを避けることが可能となります。

Chains(他の機能との連携)

LLMは単体で使用しても非常に便利です。しかし複数のLLMを組み合わせたり、特定の機能に特化したモジュールと合わせて利用することで、さらに複雑で強力なアプリケーションを作成することも可能になります。

Chainsは他の機能と連携するための機能を提供しています。Chainsを用いることでLLMの利用や他の機能の利用をまとまりとして利用することが出来ます。

Chainsを用いることで、LLMを自由自在に操ることが可能になります!

以下では3つのChainを紹介していきます。

Simple Chain

Simple ChainはChainの最小単位です。Simple Chainを複数繋げていくことで強力な機能を実現することが可能になります。
以下ではSimple Chainの簡単な具体例としてLLM Chainを実装していきます。

LLM Chainは「LLMに渡すプロンプトテンプレートと、そのプロンプトをLLMに渡して回答を生成する」という一連の流まとまりとして扱うことが出来ます。

それでは実装していきましょう!

今回は特定の場所のおすすめ観光スポットを聞くためのプロンプトテンプレートを作成し、そのプロンプトをLLMに渡し、回答してもらうという一連の流れをChainとしています。

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

llm = OpenAI(model_name="text-davinci-003")
prompt = PromptTemplate(
    input_variables=["place"],
    template="{place}で最もおすすめの観光スポットは?"
)
chain = LLMChain(llm=llm, prompt=prompt)
print(chain("恵比寿"))

上のコードを実行すると以下のような結果となります。プロンプトテンプレートの変数に入っている値がplaceに格納されていて、LLMからの回答がtextに格納されています。

{'place': '恵比寿', 'text': '\n\n恵比寿で最もおすすめの観光スポットとして、恵比寿駅前広場や恵比寿ガーデンプレイス、恵比寿神社、恵比寿映画館などが挙げられます。また、恵比寿スターバックス、恵比寿スタープレイス、恵比寿マルイなど、人気のショッピングモールもあります。近くには、東京タワーや六本木ヒルズ、渋谷109など、観光名所があるので、昼と夜に観光スポットを楽しむことができま'}

Sequential Chain

Sequential Chainは複数のChainを組み合わせて実行することが可能になります。

具体的には、一つ目のChainの回答を元に二つ目のChainで回答を生成するということも可能になります。

実際に実装を見た方が理解できると思うので、早速実装していきます!

chain_1では、おすすめの観光スポットを聞くためのプロンプトテンプレートの作成と、そのプロンプトを元に回答を作成。
chain_2は特定の場所について説明させるプロンプトテンプレートの作成と、そのプロンプトを元に回答を作成。

これらのchainを組み合わせることでchain_1でLLMが回答した観光スポットをchain_2のbest_placeに格納し、おすすめの観光スポットについての説明をします。
以下は実際のコードです。

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

llm = OpenAI(model_name="text-davinci-003")
prompt_1 = PromptTemplate(
		input_variables=["place"],
		template="{place}で最もおすすめの観光スポットを一つ教えてください。"
)
chain_1 = LLMChain(llm=llm, prompt=prompt_1)

prompt_2 = PromptTemplate(
    input_variables=["best_place"],
    template="{best_place}について100字以内で教えてください。",
)
chain_2 = LLMChain(llm=llm, prompt=prompt_2)

two_chain = SimpleSequentialChain(chains=[chain_1, chain_2], verbose=False)
print(two_chain("恵比寿"))

上のコードを実行すると以下のような結果となりました。しっかりと観光スポット(今回は恵比寿ガーデンプレイス)について説明できていることがわかります。

{'input': '恵比寿', 'output': '\n\n恵比寿ガーデンプレイスは、日本初の大規模な屋外ショッピングモールの一角にあり、豊かな緑の中にスタイリッシュな店舗、レストラン、映画館が並び、芝生広場やグラウンドなどで屋外で楽しめる施設が充実。また、屋内にはホテルやギャラリーなどもあり、恵比寿のオシャレな街を満喫できる場所となっている。'}

Custom Chain

Custom Chainは自由にChainを繋げることが可能です。Custom Chainを用いることで、複雑な質問への対応力を向上させることが出来ます。

以下では具体例として、一つの変数から二つの質問に対する回答を生成するCustom Chainを紹介します。Chainクラスを継承して簡単に実装することができます。

今回はAIエンジニアという職種についての説明と、平均年収を回答させています。

from typing import Dict, List
from langchain.chains import LLMChain
from langchain.chains.base import Chain
from langchain.prompts import PromptTemplate
from langchain.llms import OpenAI

class ConcatenateChain(Chain):
    # もととなる2つの LLMChain をコンストラクタで受け取る
    chain_1: LLMChain
    chain_2: LLMChain

    @property
    def input_keys(self) -> List[str]:
        # 2つのチェーンの入力キーの和集合
        all_input_vars = set(self.chain_1.input_keys).union(set(self.chain_2.input_keys))
        return list(all_input_vars)

    @property
    def output_keys(self) -> List[str]:
        # このチェーンの出力キーは、concat_output のみ
        return ['concat_output']

    def _call(self, inputs: Dict[str, str]) -> Dict[str, str]:
        # `output_keys` で定義したキーをもつ Dict を返す関数を定義する
        # ここでは、2つのチェーンを独立に実行した得られた出力を連結して返す
        output_1 = self.chain_1.run(inputs)
        output_2 = self.chain_2.run(inputs)
        return {'concat_output': output_1 + "\n" +output_2}
    
llm = OpenAI(model_name="text-davinci-003")
prompt_1 = PromptTemplate(
    input_variables=["job"],
    template="{job}の仕事内容について100字で教えて。",
)
chain_1 = LLMChain(llm=llm, prompt=prompt_1)

prompt_2 = PromptTemplate(
    input_variables=["job"],
    template="{job}の平均年収は?",
)
chain_2 = LLMChain(llm=llm, prompt=prompt_2)

concat_chain = ConcatenateChain(chain_1=chain_1, chain_2=chain_2, verbose=False)
print(concat_chain.run("AIエンジニア"))

上のコードを実行すると以下のような結果となりました。

AIエンジニアの仕事内容は、機械学習アルゴリズムを用いて、コンピューターシステムを開発していることです。その他にも、データサイエンスアルゴリズムを活用して、データを分析し、人工知能を実装することなどもあります。


AIエンジニアの平均年収は、2020年において、約900万円と言われています。

Memory(履歴の保持)

Memoryは大規模言語モデルの回答やプロンプトを保存する機能を提供しています。

ChatGPTなどのチャットモデルでChainsやAgents(後ほど紹介します)を用いる際に、チャット上でのやり取りは勝手に保存されません。そこで、Memoryを使うことで過去の会話も記憶し、それをもとに回答生成が可能です。

今回はChatMessageHistoryという多くのメモリモジュールを支えるユーティリティクラスについて簡単な解説と実装をしていきます。

ChatMessageHistory

ChatMessageHistoryはチャット履歴を記録するための機能です。
ChatMessageHistoryはHumanMessagesとAIMessages を保存し、それらすべてを取得するための機能を提供しています。

以下では実際にChatMessageHistoryを用いた実装を行なっていきます。

from langchain.memory import ChatMessageHistory
from langchain.schema import messages_to_dict
from langchain.schema import messages_from_dict

history = ChatMessageHistory()

history.add_user_message("システムエンジニアとは?")
history.add_ai_message("システムの開発、コーディングを行う職業。")

message_list = history.messages
message_dict = messages_to_dict(message_list)
print(message_list)
print(message_dict)

message_list_from_dict = messages_from_dict(message_dict)
print(message_list_from_dict)

add_user_messageとadd_ai_messageを用いて、質問内容と回答内容を保存することができます。
また、messages_to_dictとmessages_from_dictを用いると簡単にリスト型と辞書型に変換することができます。

上のコードの実行結果は以下のようになります。

[HumanMessage(content='システムエンジニアとは?'), AIMessage(content='システムの開発、コーディングを行う職業。')]
[{'type': 'human', 'data': {'content': 'システムエンジニアとは?', 'additional_kwargs': {}, 'type': 'human', 'example': False}}, {'type': 'ai', 'data': {'content': 'システムの開発、コーディングを行う職業。', 'additional_kwargs': {}, 'type': 'ai', 'example': False}}]
[HumanMessage(content='システムエンジニアとは?'), AIMessage(content='システムの開発、コーディングを行う職業。')]

質問内容と回答内容がしっかり保存されていることが確認できました。

Agents(機能の追加・実行)

Agentsを用いるとLLMに独自の機能を持たせることが可能となります。

Agentsはさらに以下の4つの機能に分割されており、以下の機能を合わせて用いることで独自の機能を追加することができるようになります。

  • Tools(道具)

  • Agents(実行の判断)

  • Toolkits(Toolを搭載したAgent)

  • Agent Executor(Agentの実行)

以下ではそれぞれの説明と簡単な実装を行っていきます。

Tools

ToolsはLLMが外界とやり取りをするための機能のことを指します。そして、Toolsの各機能のことをToolと言います。すなわち、ToolはAgentが使うための道具です。

LangChainのToolsは元から用意されている機能も豊富であるため、簡単に外界とやり取りをすることが可能です。さらに、Toolsで提供されていないToolを自作することも可能です。

Toolを自作する方法として以下の3つの方法があります

  • Toolクラスの利用

  • BaseToolクラスからサブクラスを作成する方法

  • @toolデコレータを使用する方法

今回はToolsで用意されているSerpAPIWrapperを用いてToolを定義していきます。
実際にToolを定義したコードは以下になります。

search = SerpAPIWrapper()

tools = [
    Tool(
        func=search.run,
        name="Search",
        description="useful for when you need to answer questions about current events"
    )
]

Agents

紛らわしいですが、LangChainのAgentsの中にAgentsという機能があります。AgentsはToolの中からどの機能を用いて問題を解決するのかをプロンプトに沿って考え、決定してくれるものです。

実際にAgentsを実装すると以下のようになります。

llm = OpenAI()

agent = initialize_agent(
    tools, 
    llm, 
    agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION, 
    verbose=True
)

Toolkits

Toolkitsは、特定のユースケースに応じてツールを初期搭載したAgentsを指します。

Toolkitsはたくさんあり、ここでは一部を紹介します。

例えばcreate_python_agentはPythonを実行することに特化したツールであり、ニューラルネットワークを作成させることなども可能です。

Agent Executor

Agent ExecutorはAgentの実行を実際に行うための機能です。

難しい話ではないので、実際にコードを見てみましょう。

具体的なコードは以下になります。

agent.run(
    "日本のエンジニアの平均年収"
)

Agentsの全体実装

それでは、これまでに定義したTool、Agentを用いて実際に実行していきましょう!
全体のコードは以下のようになります。

YOUR_API_KEYの部分は、自身で取得したSerpAPIKeyを設定してください。

from langchain.llms import OpenAI
from langchain.tools import Tool
from langchain.agents import initialize_agent, AgentType
from langchain.utilities import SerpAPIWrapper
from langchain.agents import load_tools
import os

os.environ["SERPAPI_API_KEY"]= "YOUR_API_KEY"

search = SerpAPIWrapper()

tools = [
    Tool(
        func=search.run,
        name="Search",
        description="useful for when you need to answer questions about current events"
    )
]

llm = OpenAI()

agent = initialize_agent(
    tools, 
    llm, 
    agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION, 
    verbose=True
)

agent.run(
    "日本のエンジニアの平均年収"
)

以上のコードを実行すると以下のような結果となります。

色々書いてあってわかりにくいですが、大半はAgentがToolを使用するかどうかの判断と使用した際のログが出力されています。
最終的な結果は最後の方に記載されているFinal Answer:の後に続く部分です。

> Entering new AgentExecutor chain...
 I should do some research to find the answer
Action: Search
Action Input: "Average salary for engineers in Japan"
Observation: ['The average salary for an Engineer is $6054233 per year in Tokyo, Japan. Click here to see the total pay, recent salaries shared and more!', 'If you are an office staff (with no heavy responsibilities and all), the average Japanese earns 150,000 to 180,000 yen. That is a fair amount of income if your ...', "bachelor's degrees or higher in traditional engineering disciplines can expect a starting pay range of around ¥6 million to ¥10 million in pre- ...", 'I was looking it up online, and it said the average pay for an electrical engineer 3400000 yen, or $25000 a year.', 'How much does an Entry Engineer make in Japan? The average Entry Engineer salary in Japan is ¥4,320,688 as of March 26, 2021, but the range typically falls ...', 'indicated that the average annual salary of IT engineers in Japan is 4.53 million JPY.', 'A person working in Engineering in Japan typically earns around 436,000 JPY. Salaries range from 138,000 JPY (lowest average) to 919,000 JPY (highest average, ...', 'A person working as Engineer in Japan typically earns around 489,000 JPY. Salaries range from 225,000 JPY (lowest) to 777,000 JPY (highest).', 'The average salary for a Mechanical Engineer in Japan is ¥3388067 in 2023. Visit PayScale to research mechanical engineer salaries by city, ...', 'The average mechanical engineer gross salary in Japan is ¥9,315,419 or an equivalent hourly rate of ¥4,479. In addition, they earn an average bonus of ¥325,108.']
Thought: It appears that the salary for engineers in Japan varies depending on the type of engineer and the city. 
Action: Search
Action Input: "Average salary for engineers in Tokyo, Japan"
Observation: ['The average salary for an Engineer is $6054233 per year in Tokyo, Japan. Click here to see the total pay, recent salaries shared and more!', 'The average salary for Engineer is JP¥72,41,853 per year in the Tokyo, Japan. The average additional cash compensation for a Engineer in the Tokyo, Japan is JP¥ ...', '**Entry-Level Engineer**: For engineers with 0-3 years of experience, the salary can range from ¥3,000,000 to ¥5,000,000 per year. This can vary depending on ...', 'While there is no minimum salary for a Software Engineer in Greater Tokyo Area, JP, the average total compensation is ¥8,642,252. What company pays the most for ...', 'Salary rankings by profession ; Mechanical Engineer, $46,164 ; Software Engineer, $45,466 ; Business Analyst, $45,175 ; Chemical Engineer, $42,585.', 'A good and competitive compensation would range anywhere between 392,000 JPY and 485,000 JPY. This is a very rough estimate. Experience and education play a ...', 'indicated that the average annual salary of IT engineers in Japan is 4.53 million JPY.', 'The average systems engineer gross salary in Tokyo, Japan is ¥13,376,076 or an equivalent hourly rate of ¥6,431. This is 38% higher (+¥3,690,289) than the ...', 'How much does an Entry Engineer make in Japan? The average Entry Engineer salary in Japan is ¥4,320,688 as of March 26, 2021, but the range typically falls ...', "4.5 million yen is about average for the specs you described. The reality is that software engineer is a poor paying job in Japan. There's a ..."]
Thought: It appears that the average salary for engineers in Tokyo, Japan is around 4.5 million yen. 
Final Answer: The average salary for engineers in Tokyo, Japan is around 4.5 million yen.

> Finished chain.

Agentsについてさらに詳しく知りたい方、自由自在にLLMを操りたい方は以下の記事もご覧ください。

LangChain Agentsの解説と活用方法

Callbacks

Callbacksは大規模言語モデルアプリケーションのログ記録、モニタリング、ストリーミングを効率よく行うための機能を提供しています。

コールバックについて知らない方のために、一般的なコールバックの概念について説明していきます。一般的に、コールバックとは「あるイベントが発生した場合や、特定の条件を満たした場合に自動的に呼び出される関数やメソッド」のことを指します。

ログ記録やモニタリングを行うことはアプリケーション開発の際のデバッグやシステムの挙動を理解する上で大切になります。

似たような機能としてLangSmithというものがありますがLangSmithはアプリケーション全体やシステム全体を対象としているのに対し、LangChainのCallbacksは1つのイベント(オブジェクトなど)で使用されます。

Callbacksのイベントハンドラは以下のように提供されています。

    イベント名

      用途

on_llm_start

LLMの動作開始時

on_chat_model_start

チャットモデルの動作開始時

on_llm_new_token

新しいLLMトークン時(ストリーミングが有効な場合のみ)

on_llm_end

LLMの動作終了時

on_llm_error

LLMでのエラー発生時

on_chain_start

チェーンの動作開始時

on_chain_end

チェーンの動作終了時

on_chain_error

チェーンでのエラーが発生時

on_tool_start

ツールの動作開始時

on_tool_end

ツールの動作終了時

on_tool_error

ツールでのエラーが発生時

on_text

テキストが出力された時

on_agent_action

エージェントアクション時

on_agent_finish

エージェントの終了時

では、実際にLangChainのCallbacksの機能を実装していきましょう!
今回はChainの一連の流れを実行していく中での途中経過を出力するようにしていきます。

今回使用する「StdOutCallbackHandler」は、「BaseCallbackHandler」を継承していて、様々なイベントが発生した際に、コンソールへログを出力することが可能です。Chains、Tools、Agentsが何らかのアクションを実行した際に、呼び出されるメソッド用意されています。

以下はStdOutCallbackHandlerを使用した実際のコードになります。

from langchain.callbacks import StdOutCallbackHandler
from langchain.chains import LLMChain
from langchain.llms import OpenAI
from langchain.prompts import PromptTemplate
import os

OPENAI_API_KEY=os.environ["OPENAI_API_KEY"]
llm = OpenAI(model_name="text-davinci-003",openai_api_key=OPENAI_API_KEY)
prompt = PromptTemplate.from_template("5 × {number} = ")

handler = StdOutCallbackHandler()
chain = LLMChain(llm=llm, prompt=prompt, callbacks=[handler])

print(chain.run(number=2))

上のコードを実行すると以下のようになります。

> Entering new LLMChain chain...
Prompt after formatting:
5 × 2 = 

> Finished chain.


10

しっかりと途中経過を表示することが出来ています。

Chainsでは以下のようにverboseをTrueにすることで途中経過を表示させることも可能です。しかし、さらに複雑なログ出力をしたい場合はCallbackを使用する必要があります。

chain = LLMChain(llm=llm, prompt=prompt, verbose=True)

まとめ

いかがだったでしょうか。LangChainについての理解を深めることはできましたか?

大規模言語モデルが日に日に進化し、大規模言語モデル単体でも凄まじい能力を発揮しています。しかし専門的なタスクや複雑なタスクを実行するには使い方を工夫し、大規模言語モデルを自由自在に操作する必要があります。今回の記事で学んだことを活かし、LangChainを用いて大規模言語モデルを自由自在に操りましょう!

弊社Nucoでは、AIサービスに特化しているため、技術力を持つ社員を多数在籍しており、お客様の満足のいくサービスを提供できると考えております。「AIを自社のサービスに取り入れたい!」と考えている方は、ぜひこちらのお問い合わせフォームから気軽にご相談ください!

Share

FOLLOW US

更新情報や関連ニュースをチェック

最新のブログ

      Contact

      Nucoに関するご質問、案件ご相談、お見積り依頼など、
      以下のフォームよりお気軽にご連絡ください。

      Nucoに関する
      ご質問、案件ご相談、お見積り依頼など、
      以下のフォームよりお気軽にご連絡ください。

      Nucoに関する
      ご質問、案件ご相談、
      お見積り依頼など、
      以下のフォームより
      お気軽にご連絡ください。

      お問い合わせはこちら

      株式会社Nuco

      〒150-0011 東京都渋谷区東1-26-20 東京建物東渋谷ビル6F

      〒150-0011
      東京都渋谷区東1-26-20
      東京建物東渋谷ビル6F

      E-mail : info@nuco.co.jp

      © 2023 Nuco Inc.