最新動向

Pythonコーディングを簡単に|LangChainで効率化【LLMことはじめ Vol.2】

株式会社調和技研のAI技術開発第一グループにて、AIの開発・導入支援を担当している鈴木 卓麻です。

前回はAzure OpenAI Serviceを用いて、コマンドプロンプト上で大規模言語モデル(LLM)との対話を実現しました。今回は、LLMの機能拡張を効率的に実装するためのライブラリであるLangChainについて紹介します。

LangChainは、AI開発経験者にとって、「TensorFlowにおけるKeras」に相当するものといえます。前回作ったコードの一部をLangChainを用いて書き直すことで、機能拡張の準備を行います。

目次

前提条件

  • Azure OpenAI Serviceにアクセスが可能であること
  • Openai のパッケージバージョンが1.x.xであること
  • Pythonの初歩的なプログラミングが可能であること
  • Windowsのローカル仮想環境で実験を行うこと
  • LangChainのパッケージバージョンが0.2.1であること

本記事の目標

この記事では、コマンドプロンプト上でLLMと対話ができるようにすることを目標にします。前回と表面上は変わりませんが、コードをLangChainに置き換えていきます。


全体の流れ

Step1:LangChainとは

Step2:アクセスキーとエンドポイント

Step3:ライブラリのインストール

Step4:GPTを呼び出すコード

Step5:コードの実行

Step6:ブラッシュアップ

Step1:LangChainとは

LangChain[1]は、大規模言語モデル(LLM)を活用したアプリケーションを開発するためのフレームワークです。MIT ライセンスのオープンソースであり、ライセンスの条件を満たせば商用利用も可能です。現在(2024年7月3日時点)v0.2が最新となっています。

LangChainを使用する際の最大の利点の一つは、LLMのカスタマイズが非常に容易であることです。これにより、ユーザーは特定のニーズや要件に合わせてAIの振る舞いを調整できます。以下では、今回のハンズオンを実施するために特に重要であると私が感じたLangChainの4つの機能を列挙します。

Models

LangChainのModels使えば、OpenAIのGPT-3.5やGoogleのGeminiなど、異なるAIモデルを手軽に切り替え、または同時に活用することが可能です。これにより、特定のタスクに最適なモデルを選んだり、複数のモデルの強みを組み合わせたりして、より良い結果を得ることができます。簡単に言えば、LangChainのModelsは多様なAIモデルを一つのプラットフォームで使いこなすための便利ツールです。

Retrieval

LangChainのRetrieval機能は、言語モデルが学習していない最新の情報や、インターネット上に存在しない企業の内部データを取り込んで、より正確な回答を生成することができます。

例えば、GPT-3.5は2021年9月までの情報に基づいて学習されているため、それ以降のデータについては直接知りません。しかし、Retrievalを活用することで、2024年の最新情報を取得し、それを基に回答を提供することが可能になります。

また「RAG」という用語を最近よく耳にするかもしれませんが、これはRetrieval-Augmented Generationの略で、外部の情報を組み合わせて回答の質を高める技術を指します。LangChainのRetrievalを使えば、このようなシステムを簡単なコードで実装できます。ただし、回答の精度をさらに高めるためには、別の技術が必要になる場合があります。

Chains

LangChainのChains機能は、AIがユーザーのプロンプトに基づいて複数のAIを連携させ、複雑な問題に対して高精度な回答を提供することを可能にします。従来のChatGPTのやりとりでは、ユーザーとAIが以下のように複数回にわたって対話を行う必要がありました。

USER:        「プログラミングで人気の言語を教えて」

ChatGPT:    「Pythonです」

USER:        「Pythonを上達するにはどうしたらよい?」

ChatGPT:    「Pythonスキルを向上させるためには….」

しかし、Chainsを使用すると、このプロセスが単一の流れで行われます。ユーザーが一度のプロンプトで複数の関連する質問ができ、AIはそれらを内部的に「チェーン」して、一連の回答を出力します。

例えば

USER:        「プログラミングで人気の言語を教えて」

ChatGPT:    「{Python}です」

ChatGPT:    「{Python}スキルを上達させるには..」

この方法では、ユーザーが一度の質問で必要な情報を得ることができ、より効率的な対話が可能になります。

Memory

LangChainのMemory機能は、LLMとの会話履歴を簡単かつ効率的に管理できるように設計されています。第一回の記事では、過去の会話履歴を記憶する機能がなかったため、ユーザーは質問の度に詳細な情報を提供する必要がありました。しかし、Memory機能を利用することで、会話の持続性を保つこと、効率的な対話を実現すること、そして、パーソナライズされた回答を得ることが可能となります。

Step2:アクセスキーとエンドポイント

LangChainでAzure OpenAI Serviceを利用するためには、「アクセスキー」と「エンドポイント」の取得が必要です。これらをWindowsの環境変数に登録します。

setx AZURE_OPENAI_API_KEY "***************************"
setx AZURE_OPENAI_ENDPOINT "https://********.openai.azure.com/"

環境変数に登録されているかどうかの確認は、以下のコマンドを実行します。

echo %AZURE_OPENAI_API_KEY%
echo %AZURE_OPENAI_ENDPOINT%

Step3:LangChainのパッケージをインストール

PythonでLangChainを使用するにはlangchain、langchain-openai およびlangchain-communityパッケージをインストールする必要があります。今回は執筆時のバージョン0.2.1を指定します。

 pip install langchain==0.2.1 langchain-openai langchain-community

インストールが完了したら、次のコマンドを実行してバージョンを確認します。 

 pip show langchain

バージョンが0.2.1と表示されれば、インストールは成功です。もしバージョンが0.1.xと表示された場合は、再度インストールコマンドを実行してください。

Step4:GPTを呼び出すコード

パッケージのインストールが完了したので、次に第一回目の記事でのコードをベースにLangChainを使ってAzure OpenAIを呼び出してみましょう。

まずは、以下にコード全文を記載します。

# 1 パッケージのインポート
from langchain_openai import AzureChatOpenAI
from langchain_core.prompts import ChatPromptTemplate


# 2 Azure OpenAI Serviceの設定
model = AzureChatOpenAI(
    openai_api_version="2023-05-15",
    azure_deployment="gpt-35-turbo",
)
# 3 システムの役割を定義する文章
system_prompt = ChatPromptTemplate.from_template("""
    常にシンプルに回答してください。{user_input}
                                                 """)
# Azure OpenAI Serviceにこのプロンプトを送信するためのチェーンを作成
chains = system_prompt | model
# 4 API呼出方法
# 無限ループでユーザーからの入力を受け付け、回答を返す
while True:
        # 入力を受け付ける
        user_input = input("Q:")
        # 4-1 APIの呼び出し方法
        response = chains.invoke(user_input)
        # レスポンスの処理
        print("A:",response.content + "\n")

このコードは大きく4つのブロックに分けられます。それぞれ部分的に見ながら解説していきます。

#1 パッケージのインポート

# 1 パッケージのインポート
from langchain_openai import AzureChatOpenAI
from langchain_core.prompts import ChatPromptTemplate

LangChainのlangchain_openaiパッケージからAzureChatOpenAIクラスをインポートしています。このクラスは、Azure上で動作するOpenAIのチャットボットAPIとの通信を可能にします。

また、langchain_coreパッケージから、ChatPromptTemplateクラスをインポートしています。このクラスを使用すると、変数や関数を組み込んだ柔軟なテンプレートを作成し、言語モデルに入力するプロンプトを簡単に生成できます。これにより、コードの可読性が向上し、開発プロセスがスムーズになります。

#2 APIクライアントの設定

次に、APIクライアントの設定を行います。クライアントインスタンスを作成して、そのインスタンスにAPIバージョン、デプロイ名を設定します。APIキーとエンドポイントは既に環境変数へ登録済みのため、省略されています。

# 2 Azure OpenAI Serviceの設定
model = AzureChatOpenAI(
    openai_api_version="2023-05-15",
    azure_deployment="gpt-35-turbo",
)

openai_api_versionは使用したいAPIのバージョンを指定します。これはモデルのバージョンではなく、REST APIのバージョン[2]ですのでご注意ください。azure_deploymentはデプロイ名を指定します。

#3 システムの役割を定義する文章

# 3 システムの役割を定義する文章
system_prompt = ChatPromptTemplate.from_template("""
    常にシンプルに回答してください。{user_input}
                                                 """)
# Azure OpenAI Serviceにこのプロンプトを送信するためのチェーンを作成
chains = system_prompt | model

システムの役割を定義するプロンプトを設定します。この例では、システムに対して「常にシンプルに回答してください」と指示しています。その後、ユーザーが入力した質問文が続くようにしています。

また、Azure OpenAI Serviceにこのプロンプトを送信するためのチェーンを作成します。

# Azure OpenAI Serviceにこのプロンプトを送信するためのチェーンを作成
chains = system_prompt | model

#4 API呼出方法

 while True:
        # 入力を受け付ける
        user_input = input("Q:")
        # 4-1 APIの呼び出し方法
        response = chains.invoke(user_input)
        # レスポンスの処理
        print("A:",response.content + "\n")

無限ループでユーザーからの入力を受け付け、回答を返します。

ユーザーの入力を受け付け、その入力に基づいてAPIを呼び出し、得られたレスポンスを表示します。

invokeと記入し、user_inputを引数に設定してあげればOKです。

User:「日本で一番高い山を教えて下さい。」

期待する回答は「富士山」です。

回答の表示

response: GPTから返ってきた内容は次のような構造になっています。Azure OpenAI ServiceからのAPIレスポンスの生データはJSON形式ですが、LangChainを介して出力するとパース(解析)されてオブジェクト形式に変換されます。これは、プログラム内で直接操作が可能になり、簡単にデータにアクセスし、必要な情報を抽出したり、特定の操作を行ったりできるようになります。

input_variables=[] 
messages=[
AIMessage(content='富士山です。', 
response_metadata={
'token_usage': {
'completion_tokens': 7, 
'prompt_tokens': 34, 
'total_tokens': 41
}, 
'model_name': 'gpt-35-turbo', 
'system_fingerprint': None, 
'finish_reason': 'stop', 
'logprobs': None, 
'content_filter_results': {}
}, 
id='run-13a1f3ea-165d-40ae-a36c-0a72d78571e8-0'),
HumanMessagePromptTemplate(
prompt=PromptTemplate(input_variables=[], template='\n')
)
]
レスポンスの中身はさまざまな情報が含まれますが、必要な内容はAIからの回答部分であるAIMessageのcontent部分です。他にも、回答が完全に完了しているかどうかを示すfinish_reasonや、トークン数に関するtoken_usageなどの情報も返ってきます。

#4 レスポンスの処理

オブジェクト化されているため、レスポンスの中から回答のcontent部分のみ抜き出すのは以下のように書くと簡単にできます。

 # レスポンスの処理
print("A:",response.content + "\n")

Step5:コードの実行

さて、すごく短いコードですが、実行してみましょう。

非常にシンプルな回答をもらうことが出来ました。Langchain でAzure OpenAI Serviceが実行できるようになりました。やりましたね。

Step6:ブラッシュアップ

  • エラー処理
  • メモリ機能

エラー処理

while文で無限ループを実行している場合、Ctrl+Cで停止できます。しかし、この方法では正しく終了しないためエラーが発生します。そこで、エラー処理を追加し、さらに「exit」と入力することで正常に終了できる処理を追加します。

以下、コード全文です。

# 1 パッケージのインポート 
from langchain_openai import AzureChatOpenAI
from langchain_core.prompts import ChatPromptTemplate


# 2 Azure OpenAI Serviceの設定
model = AzureChatOpenAI(
    openai_api_version="2023-05-15",
    azure_deployment="gpt-35-turbo",
)
# 3 システムの役割を定義する文章
system_prompt = ChatPromptTemplate.from_template("""
    常にシンプルに回答してください。{user_input}
                                                 """)
# Azure OpenAI Serviceにこのプロンプトを送信するためのチェーンを作成
chains = system_prompt | model
# 4 AIP呼出方法
# 無限ループでユーザーからの入力を受け付け、回答を返す
while True:
エラー処理追加:
    try:   
        # 入力を受け付ける
        use_input = input("Q:")
        # エラー処理追加:ユーザーがexitを入力した場合、ループを終了
        if use_input.lower() == "exit":
            break
        # 4-1 APIの呼び出し方法
        response = chains.invoke(use_input)
        # 回答を表示
        print("A:",response.content + "\n")
    # エラー処理追加:キーボード割り込みがあった場合、プログラムを終了
    except KeyboardInterrupt:
        print("\nInterrupted by user. Exiting...")
        break
    # エラー処理追加:例外が発生した場合、エラーメッセージを表示してプログラムを終了
    except Exception as e:
        print(f"\nAn error occurred:{e}")
        break

では、このプログラムを実行してみます。

終了処理や、エラー処理が適切にできました。

メモリ機能

チャットでのやり取りは基本的に自動的に記憶されることはありません。メモリ機能は、チャット内での過去の内容を引き継ぐための機能です。

先ほどのプログラムに基づいて、その確認を行ってみます。

このように、ポチという名前を教えていても、覚えていないという回答が返ってきました。

では、チャット内のやり取りを記憶するために、簡単なメモリ機能をコードに追加します。この機能は、過去の入力を保存するため過去のやりとりの情報を引き出すことが可能となります。

# 1 パッケージのインポート
from langchain_openai import AzureChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_community.chat_message_histories import ChatMessageHistory
from langchain_core.chat_history import BaseChatMessageHistory
from langchain_core.runnables.history import RunnableWithMessageHistory
# 2 Azure OpenAI Serviceの設定
model = AzureChatOpenAI(
    openai_api_version="2023-05-15",
    azure_deployment="gpt-35-turbo",
)
# 3 システムの役割を定義する文章
system_prompt = ChatPromptTemplate.from_template("""
    常にシンプルに回答してください。{user_input}
                                                 """)
# 会話履歴を格納するための準備
store = {}
# 会話履歴をセッションID毎に取得
def get_session_history(session_id: str) -> BaseChatMessageHistory:
    if session_id not in store:
        store[session_id] = ChatMessageHistory()
    return store[session_id]
# RunnableWithMessageHistoryを使って、会話履歴を追加
with_message_history = RunnableWithMessageHistory(model, get_session_history)
# configの設定(セッションIDを指定)
config = {"configurable": {"session_id": "abc2"}}

chains = system_prompt | model | with_message_history
# 4 無限ループでユーザーからの入力を受け付け、回答を返す
while True:
    try:    
        # 入力を受け付ける
        use_input = input("Q:")
        # ユーザーがexitを入力した場合、ループを終了
        if use_input.lower() == "exit":
            break
       # 4-1 APIの呼び出し方法 
        response = chains.invoke(use_input, config=config)
   # 回答を表示
        print("A:",response.content + "\n")
    # キーボード割り込みがあった場合、プログラムを終了
    except KeyboardInterrupt:
        print("\nInterrupted by user. Exiting...")
        break
    # その他の例外が発生した場合、エラーメッセージを表示してプログラムを終了
    except Exception as e:
        print(f"\nAn error occurred:{e}")
        break

パッケージをまず追加で3つインポートしました。

# 1 パッケージのインポート
from langchain_community.chat_message_histories import ChatMessageHistory
from langchain_core.chat_history import BaseChatMessageHistory
from langchain_core.runnables.history import RunnableWithMessageHistory

これらはそれぞれ、会話履歴を記録するために必要です。

会話履歴の格納:store という名前の辞書を使用して、セッションIDごとに会話履歴を格納します。これにより、異なるユーザーセッションのデータを区別し、それぞれの会話履歴を独立して保持することができます。

会話履歴の取得:get_session_history 関数は、指定されたセッションIDに対応する会話履歴を取得します。もし指定されたセッションIDが store 辞書に存在しない場合は、新しい ChatMessageHistory オブジェクトを作成し、それを store 辞書に追加します。これにより、新しいセッションが開始されたときに、新しい会話履歴が自動的に作成されます。

# 会話履歴を格納するための準備
store = {}
# 会話履歴をセッションID毎に取得
def get_session_history(session_id: str) -> BaseChatMessageHistory:
    if session_id not in store:
        store[session_id] = ChatMessageHistory()
    return store[session_id]

ここで会話履歴を追加しています。

# RunnableWithMessageHistoryを使って、会話履歴を追加
with_message_history = RunnableWithMessageHistory(model, get_session_history)
# configの設定(セッションIDを指定)
config = {"configurable": {"session_id": "abc2"}}

Chains機能を用いて、プロンプトとモデルと会話履歴を繋げます。

chains = system_prompt | model | with_message_history

そして呼び出す際、引数にconfigを追加します。

# 4-1 APIの呼び出し方法 
response = chains.invoke(use_input, config=config)

では実行して確認してみます。

以前の会話を覚える機能を追加することで、ChatGPTのようなサービスに近づけるコードが実装できました。これにより、過去の会話内容を記憶し、それに基づいて応答することができます。

おわりに

コマンドプロンプト上でLangChainを利用し、GPTと会話するプログラムを作成することに成功しました。さらに、過去の会話履歴を簡単に保存する設定も実装しました。次回は外部のドキュメントを参照するRAGシステムを作成してみましょう。



【参考文献】

[1]https://python.langchain.com/v0.2/docs/integrations/chat/azure_chat_openai/

[2]https://learn.microsoft.com/ja-jp/azure/ai-services/openai/reference



記事を書いた人
鈴木 卓麻

画像系AIの開発に従事。2002年立命館大学理工学部卒。写真現像機の開発、プリントシール機の開発を経て2022年8月に調和技研に参加。二児の父。子育てのために京都からフルリモートワークが可能な会社を探していたら北海道の調和技研に出会う。最近はPTA活動と競技ドッジボールの審判が忙しい。