langchianlogo

LangChain入門 – 1)はじめに

このLangChain入門では、ステップバイステップでLangChainの使い方を紹介してゆきます。LangChainは生成AIを使ったシステムを構築するためのフレームワークです。生成AIではOpenAIの提供するChatGPTが有名ですが、それ以外にもたくさんの生成AIが存在します。LangChainを使用すれば、そのような生成AIを効果的に活用し、効率よくアプリケーションを作成することが可能になります。

この記事はFuture Coders独自教材からの抜粋です。この分野は頻繁に更新されるので、この記事を見ていただいているころには状況が変わっている可能性があることご了承ください

LangChainとは

ChatGPTなどの生成AIが注目されています。ChatGPTはOpenAIが提供するサービスですが、Webのインタフェースだけでなく、WebAPIを利用することで、Python, Java, C# などのプログラムからそのサービスを利用することが可能です。

OpenAI from Web and Program
LangChain入門 - 1)はじめに 5

製品のカスタマサポートなどを実装する場合は、その製品に対する詳しい情報を入力して学習させる必要があります。情報はExcel、PDF、Word、データベースなどいろいろなフォーマットで格納されている可能性がありますが、これらを組み合わせるのも大変です。

python connects openai, search and DB
LangChain入門 - 1)はじめに 6

プログラムを駆使すればこれらの課題は解決できます。最新情報や製品紹介などいろいろな生成AIアプリケーションを作成できるでしょう。しかし、内容が複雑になってくるとプログラム作成の負担も増えてきます。また、OpenAI以外の生成AIやクラウドサービスなどと組み合わせたりすると、さらに複雑さは増してしまいます。

LangChainはこのような状況に対応すべく開発されたフレームワークです。つまり、生成AIを組み合わせて、いろいろなアプリを簡単に作成できるようになります。

LangChain position
LangChain入門 - 1)はじめに 7

ちなみにMicrosoft社のguidanceもLangChainと似たような位置づけです。

最初のサンプル

詳しい説明は後で行います。まずLangChainでどのようなことができるか見てみましょう。

各種サンプルを実行する前に以下のコマンドを実行して、必要なモジュールをインストールします。

pip install openai --upgrade
pip install langchain --upgrade

OpenAIの公式ページからAPIキーを取得してください。

https://openai.com/

以下のサンプルにあるAPIキー無効にしてあります。実行する際は公式サイトから自分で取得した値に置き換えて実行してください

APIキーの取得

プログラムからOpenAIのサービスを利用する場合には、APIキーを取得する必要があります。

  • アカウントのない人は https://platform.openai.com/signup から Sing upをしてください。アカウント作成時には本人を確認するための2段階認証の手続きが必要です。また、クレジットカードの登録など支払い方法の登録も必要となります。
  • アカウントがある人は https://platform.openai.com/login から Log inしてください。
from openai import OpenAI
client = OpenAI()

def ask_ai(prompt):
    response = client.chat.completions.create(
        model="gpt-3.5-turbo",
        messages=[
            {"role": "user", "content": prompt},
        ],
    )
    return response.choices[0].message.content

s = ask_ai("メジャーリーグで今一番活躍している日本人選手の名前を一人だけ教えてください。")
print(s)
s = ask_ai("その人の性別はなんですか?")
print(s)

出力は以下のようになりました。

大谷翔平(Shohei Ohtani)
私はその人の性別を知りません。

ask_aiという関数を定義しています。その関数ではOpenAIのAPI(openai.ChatCompletion.create)を使って、プロンプトを送信し、応答を受信しています。この関数を使って質問を2つしています。それぞれの質問はまったく独立して扱われるために、2個目の質問で最初のやり取りの内容を把握することはできません。生成AIなら大谷翔平の性別は自明のはずです。会話の文脈(コンテキスト)が保存されていないことがわかります。

以下は、LangChainを使って書き直した例です。

from langchain.chains import ConversationChain
from langchain.memory import ConversationBufferMemory
from langchain_openai import ChatOpenAI


llm = ChatOpenAI(model_name="gpt-3.5-turbo")
chain = ConversationChain(llm=llm, memory=ConversationBufferMemory(return_messages=True))

r = chain.invoke("メジャーリーグで今一番活躍している日本人選手の名前を一人だけ教えてください。")
print(r)

r = chain.invoke("その人の性別はなんですか?")
print(r)

応答は以下の通りでした。

{'input': 'メジャーリーグで今一番活躍している日本人選手の名前を一人だけ教えてください。', 'history': [HumanMessage(content='メジャーリーグで今一番活躍している日本人選手の名前を一人だけ教えてください。'), AIMessage(content='メジャーリーグで現在最も活躍している日本人選手は、ダルビッシュ有選手 です。彼はヒューストン・アストロズで先発投手として活躍しています。ダルビ ッシュ選手は、素晴らしい制球力と多彩なピッチングで知られており、メジャー リーグで数々の賞も受賞しています。彼は日本人選手の中でも非常に注目されて おり、ファンからも大きな支持を受けています。')], 'response': 'メジャーリ ーグで現在最も活躍している日本人選手は、ダルビッシュ有選手です。彼はヒュ ーストン・アストロズで先発投手として活躍しています。ダルビッシュ選手は、 素晴らしい制球力と多彩なピッチングで知られており、メジャーリーグで数々の 賞も受賞しています。彼は日本人選手の中でも非常に注目されており、ファンか らも大きな支持を受けています。'}
{'input': 'その人の性別はなんですか?', 'history': [HumanMessage(content='メジャーリーグで今一番活躍している日本人選手の名前を一人だけ教えてください。'), AIMessage(content='メジャーリーグで現在最も活躍している日本人選手は、ダルビッシュ有選手です。彼はヒューストン・アストロズで先発投手として 活躍しています。ダルビッシュ選手は、素晴らしい制球力と多彩なピッチングで 知られており、メジャーリーグで数々の賞も受賞しています。彼は日本人選手の 中でも非常に注目されており、ファンからも大きな支持を受けています。'), HumanMessage(content='その人の性別はなんですか?'), AIMessage(content='ダル ビッシュ有選手は男性です。')], 'response': 'ダルビッシュ有選手は男性です 。'}

どちらのサンプルもOpenAIの生成AI (gpt-3.5-turbo)を使用していることに注目してください。OpenAIを直接呼び出したときは大谷翔平・性別不明となりました。LangChainを使った時にはダルビッシュとなりましたが、性別は答えられています。選手の名前は違いますが、LangChainを使用した例では、2つ目の質問に「男性」と答えられていることがわかります。よく見ると二回目の質問では、1回目の質問が含まれていることがわかります。

from openai import OpenAI
client = OpenAI()

def ask_ai(prompt):
    response = client.chat.completions.create(
        model="gpt-3.5-turbo",
        messages=[
            {"role": "user", "content": prompt},
        ],
    )
    return response.choices[0].message.content.strip()

s = ask_ai("今の日本の総理大臣を日本語で教えてください。")
print(s)

出力は以下のようになりました。

今の日本の総理大臣は菅義偉(すが・よしひで)です。

ChatGPTでは最近の情報を学習していないので、このような回答になってしまいます。

LangChainを使って、検索サービスと組み合わせると上記のような問いにも答えられるようになります。検索サービスはGoogle, Bingなどいろいろなものが利用可能ですが、今回は無料で利用できるduckduckgo-searchを使用してみます。

まずpipコマンドでモジュールをインストールします。

pip install duckduckgo-search

以下がプログラムです。

from langchain_openai import ChatOpenAI
from langchain.agents import Tool
from langchain.agents import load_tools

from langchain_community.tools import DuckDuckGoSearchRun
from langchain.agents import AgentExecutor, create_react_agent
from langchain import hub

llm = ChatOpenAI(model_name="gpt-3.5-turbo")

search = DuckDuckGoSearchRun()
tool_names = []
tools = load_tools(tool_names)
tools.append(
    Tool(
        name="duckduckgo-search",
        func=search.run,
        description="最新情報をWebから取得します"
    )
)

prompt = hub.pull("hwchase17/react")
agent = create_react_agent(llm, tools, prompt)
agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True)
r = agent_executor.invoke({"input": "今の日本の総理大臣はだれですか?日本語で答えてください。"})
print(r)

出力は以下のようになりました。Final Answerが最終的な出力です。「岸田総理大臣」と出力されていることが確認できます。

> Entering new  chain...
I need to find out the current Prime Minister of Japan in Japanese.
Action: duckduckgo-search
Action Input: "今の日本の総理大臣"
Observation: 岸田総理大臣は、来週にも内閣改造と自民党の役員人事を行う方向で人選を進めています。. このうち党役員人事では、麻生副総裁と茂木幹事長 ... 日本国憲法下の内閣総理大臣は、閣内に意見の不一致が起こった場合は、罷免して自らの意見を通すことができる。また何らかの理由で大臣が突然辞職しても、内閣総理大臣はその後任を任意 に任命することができる。この顕著な例が衆議院解散権である。 永田町・霞が関のサラめし ... 岸田総理大臣動静.  総理 、 あの日 何してた? 2023 09 10 日. 前日. 00:00 0時15分 (以下、すべて日本時間)社交夕食会 ... 菅 義偉 (すが よしひで、 1948年 〈 昭和 23年〉 12月6日 - )は、 日本 の 政治家 。 自由民主党 所属の 衆議院議員 (9期)、 日韓議員連盟 会長。 ウィキペディア 内閣総理大臣の一覧 出典: フリー百科事典『ウィキペディア(Wikipedia)』 (2023/08/12 07:20 UTC 版) 内閣総理大臣の一覧 (ないかくそうりだいじんのいちらん)は、 日本 の 行政府 の長 である 内閣総理大臣 を務めた人物の一覧である。 脚注 [ 続きの解説] 「内閣総理大臣の一覧」の続きの解説一覧 1 内閣総理大臣の一覧とは 2 内閣総理大臣の一覧の概要 3 歴代内閣総理大臣 4 脚注 急上昇のことば チューリッ プ (バンド) 外す 慮る 踏襲 ピリオド >> 「内閣総理大臣の一覧」を含む用語の索引 内閣総理大臣の一覧のページへ のリンク
Thought:I now know the final answer
Final Answer: 岸田総理大臣

> Finished chain.
岸田総理大臣

verboseとは冗長なという意味です。verbose=Trueと指定しているので、途中の処理内容も出力されています。LLMがどのような手順で調べているかがわかります。
最初に「I need to find out the current Prime Minister of Japan in Japanese.」と出力されていることから、「日本の今の総理大臣をみつける」というタスクをAIが理解していることがわかります。そのためにActionとしてduckduckgo-searchを実行します。そのときの検索キーワードは「今の日本の総理大臣」とし、その応答から正しく「岸田総理大臣」という最終的な答えを導き出していることがわかります。

LangChainの実力が垣間見えたのではないでしょうか。以降、その使い方を詳しく見てゆきましょう。

OpenAI

LangChainはOpenAIに特化したものではなく、いろいろな生成AIをサポートします。ただ、本ドキュメントではOpenAIを中心に説明してゆきます。そのときに中心的な働きをするのがOpenAIクラスです。

from langchain_openai import OpenAI
llm = OpenAI()
r = llm.invoke("いい天気ですね?")
print(r)

出力は以下のようになりました。

はい、今日はとても快晴のようですね。

OpenAI()でオブジェクトを作成し、それに質問を渡すだけで回答が得られます。OpenAIが提供するWebAPIよりも簡単です。OpenAIオブジェクトを作成するときには、引数を使っていろいろな指定が可能です。主な引数に以下のようなものがあります。

from langchain_openai import OpenAI
llm = OpenAI(temperature=0.8, 
    model="gpt-3.5-turbo-instruct",
    max_tokens=200)
r = llm.invoke("いい天気ですね?")
print(r)

出力は以下のようになりました。

はい、いい天気ですね。暖かくて、お散歩にも最適な日ですよ!

temperatureを設定したので、多少自由度が増えたような気もします。

ちなみに、OPENAIのWebAPIサービスをプログラムから利用する場合にはAPIキーを指定する必要があります。しかし、最初のサンプルはOPENAI_API_KEYを指定していませんでした。省略した場合は、プログラムはAPIキーを環境変数OPENAI_API_KEYから取得しようと試みます。

環境変数に値が設定されているかは以下のプログラムで確認できます。

import os
key = os.environ["OPENAI_API_KEY"]
print(f"key=[{key}]")

環境変数を使用しない場合は、以下のようにOpenAIオブジェクトを作成するときに引数でAPIキーを指定してください。

llm = OpenAI(temperature=0.7, 
    openai_api_key="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")

もしくは、以下のようにプログラムの中から環境変数に値を設定することも可能です。そうすれば、OpenAIオブジェクト作成時にopenai_api_key引数を指定する必要はなくなります。

import os
os.environ["OPENAI_API_KEY"] ="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"
llm = OpenAI(temperature=0.7)

以下のようにopenaiモジュールのapi_keyプロパティに値を設定する方法でも大丈夫です。この場合には環境変数の名前は必ずしもOPENAI_API_KEYでなくても構いません。

import os
import openai
openai.api_key = os.environ["OPENAI_API_KEY"]

いずれの方法でもよいので、OpenAIのサービスを利用するには、APIキーが必要になることに注意してください。ちなみに、APIキーをプログラム中に直接埋め込むのは良くないプラクティスとされています。プログラムを他の人に共有したときにAPIキーも見えてしまうためです。そのため、環境変数に値を設定し、プログラムからは環境変数を参照するという手法が取られることが一般的です。

ChatOpenAI

生成AIを使用するときは、会話形式でコンテキスト(文脈)を与えることが少なくありません。そのようなときにはChatOpenAIクラスを使用します。OpenAIとChatOpenAI、これらはクラス名が似ているので混同しないよう注意してください。

以下会話形式でのサンプルです。

from langchain_openai import ChatOpenAI
from langchain.schema import AIMessage, HumanMessage,SystemMessage

chat = ChatOpenAI()
r = chat.invoke([
    SystemMessage(content="あなたは優秀なシェフです"),
    HumanMessage(content="ダイエット中ですがおなかがすきました"),
    AIMessage(content="冷蔵庫には何がありますか?"),
    HumanMessage(content="キャベツと玉ねぎ、ベーコンがあります"),
])
print(r.content)

出力は以下のようになりました。

素晴らしい選択肢です!それらの材料で、ヘルシーで満足感のある食事を作ることができます。

まず、キャベツと玉ねぎを細かく切ります。ベーコンも細切りにします。

フライパンを熱し、ベーコンを炒めます。ベーコンから出た油でキャベツと玉ねぎを炒めます。

キャベツと玉ねぎがしんなりするまで炒めたら、塩や胡椒で味を調えます。

お好みで、レモンの絞り汁やハーブを加えても美味しくなります。

これで、ヘルシーで満足感のあるキャベツと玉ねぎの炒め物が完成です。ダイエット中でもおなかを満たすことができます。お召し上がりください!

会話は以下のオブジェクトをリストに格納したものを引数として渡します。

  • SystemMessage = システムの前提条件
  • HumanMessage = あなたの発言
  • AIMessage = AIの発言(AIならこんな風に応答するという体)

ChatOpenAIクラスでも、temperature, max_tokens, modelなどの引数を指定できます。

この章では、LangChainに触って動かしてみることが目的でした。OpenAIのWebAPIを直接呼び出す方法と、OpenAIクラス、ChatOpenAIクラスを使用する方法について見てきました。次章以降でより詳しく使い方を見てゆきます。

演習

intro-ex1.py

OpenAIクラスを使って、何か質問をしてみてください。

  • “Silver bullet”の意味は?
  • 面白い日本映画は?
  • プログラミングの勉強方法は?

intro-ex2.py

ChatOpenAIクラスを使って、レストランでの会話をシミュレーションしてみてください。

  • 前提:あなたは一流中華レストランのウェイターです
  • あなた:
  • AI:
  • あなた:

解答例

intro-ex1.py

from langchain_openai import OpenAI
llm = OpenAI()
r = llm.invoke("`Silver bullet`の意味は?")
print(r)

intro-ex2.py

from langchain_openai import ChatOpenAI
from langchain.schema import AIMessage, HumanMessage,SystemMessage

chat = ChatOpenAI()
r = chat.invoke([
    SystemMessage(content="あなたは一流中華レストランのウェイターです"),
    HumanMessage(content="こんばんは、予約していた田中です"),
    AIMessage(content="お待ちしておりました。こちらの席へどうぞ"),
    HumanMessage(content="今日のおすすめ料理は何ですか?"),
])
print(r.content)