🦜

🦜🔗 LangChain v0.2.5 + AzureOpenAI での RAG の実装例

2024/06/28に公開

背景

LangChain は LLM を利用し自分たちがやりたいことを実現することに非常に便利なライブラリですがバージョンアップによってクラス名やサブライブラリ名の変更がやや多く少し古い Web 記事を参考にしてもうまくワークしないことがあります。

この記事は 2024/6/19 現在の LangChain (バージョン 0.2.5) で Azure OpenAI API を動かす例として残しておきます。
同じようなことをしようとして私のように苦戦している方の助けになれば幸いです。

ソフトウェアのバージョンなど

pyproject.toml
python = ">=3.12,<3.13"
python-dotenv = "^1.0.1"
langchain = "0.2.5"
langchain-cli = "0.0.25"
langchain-openai = "0.1.8"
langchain-community = "0.2.5"
langchain-chroma = "0.1.1"
langchainhub = "0.1.20"
streamlit = "1.35.0"

方法

以前 OpenAI API を利用した場合の記事を書いております。
この記事では OpenAI API を利用した場合と同じ部分の説明は省略して話を進めていきたいと思います。

https://zenn.dev/cykinso/articles/e1566fd95641c3

.env

以下のように .env を用意します。

AZURE_OPENAI_ENDPOINT=https://xxxxxxxxxx.openai.azure.com/
AZURE_OPENAI_API_KEY=YYYYYYYYYYYYYYYYYY
DEPLOYMENT_GPT_NAME=ZZZZZZZZZZZZZZZZ-gpt
DEPLOYMENT_EMBEDDINGS_NAME=ZZZZZZZZZZZZZZZZ-embedding

もし AZURE_OPENAI_API_KEY をまだ取得していなかったりモデルのデプロイをまだしていない場合は以下の方法で取得してください。

AZURE_OPENAI_API_KEY

  1. Microsoft Azure | Azure AI services | Azure OpenAI サービスを作成します。

  1. 「リソース管理」 の 「キーとエンドポイント」 を押します。

  1. 表示される 「キー1」 「キー2」 のいずれかと 「エンドポイント」 を .env にメモしておきます。

モデルのデプロイ

  1. 「Azure OpenAI Stuido に移動する」 を押して Azure OpenAI Stuido へ移動します。

  1. 「デプロイ」 を押します。

  1. 「新しいデプロイの作成」 を押します。

  1. モデルを作成します。
    RAG を用いた ChatBot を実装するには
  • Chat 用のモデル
    • gpt-35-turbo, gpt-4 など
  • Embeddings 用のモデル
    • text-embedding-3-large, text-embedding-ada-002 など

の2つのモデルが必要なのでどちらも作成してください。

Chat 用のモデル

Embeddings 用のモデル

2つのデプロイ名を表示される .env にメモしておきます。

.env
AZURE_OPENAI_ENDPOINT=https://xxxxxxxxxx.openai.azure.com/
AZURE_OPENAI_API_KEY=YYYYYYYYYYYYYYYYYY
+ DEPLOYMENT_GPT_NAME=ZZZZZZZZZZZZZZZZ-gpt
+ DEPLOYMENT_EMBEDDINGS_NAME=ZZZZZZZZZZZZZZZZ-embedding

データ

OpenAI の記事 同様に弊社 Cykinso のブログ記事の「会社のビジョンを話しているページ」を今回は用いたいと思います。

https://note.com/cykinso/n/n432d5ea70783

💡 プライベートで実装する場合は、好きなマンガの詳細をテキストにまとめてデータとするとモチベーションも上がると思います。

ざっと文章をコピーして以下のように整形しました。

note.txt
細菌叢からの新たな気付きを通じて、新ビジョンを策定しました!
2023年11月にサイキンソーは10期目に入りました。高齢化社会による社会保障への不安が募る現在、病気を未然に防ぐ0次予防の重要性が高まっています。「細菌叢で人々を健康に」というミッションに向けて、サイキンソーは新たなビジョンを制定しました。
新しいビジョンについて
ービジョン変更で具体的にどの個所が変更したかをまずご紹介します。
こちらがサイキンソーのミッション(MISSION)、ビジョン(VISION)、バリュー(VALUE)になります。
私たちが目指し続ける「細菌叢で人々を健康に」というミッションはそのままに、ビジョンを新しく変更致しました。

<これまでのビジョン>
菌叢データから「次世代のライフスタイル」を提供するプラットフォームになる

<新しいビジョン>
細菌叢からの新たな気付きを通じて
ヒト、社会、地球環境を健康にするエコシステムを実現する
新しいビジョンでは、サイキンソーが影響を与えていきたい範囲もこれまでよりさらに大きくなったことが分かります。

今回のビジョン変更を通して、サイキンソーがどんな価値発揮を目指していくのか、それに伴い事業面ではどんな挑戦をしていくのかを、代表取締役CEOの沢井さんに聞いてきました!

...(以下略)

コード

続けてコードを実装します。
今回は RAG として外部の情報を参照しつつ回答する ChatBot を実装してみます。
インターフェースとして streamlit を用います。

先にコード全体を示すと以下のようになります。
(streamlit のコードのベースとして以下の記事を参考にさせていただきました。ありがとうございます)

https://tech-lab.sios.jp/archives/41574

chatbot.py
import os
from pathlib import Path

import streamlit as st
from dotenv import load_dotenv
from langchain import hub
from langchain.schema import AIMessage, HumanMessage
from langchain_chroma import Chroma
from langchain_community.document_loaders import TextLoader
from langchain_core.runnables import RunnablePassthrough, RunnableSequence
from langchain_core.vectorstores import VectorStoreRetriever
from langchain_openai import AzureChatOpenAI, AzureOpenAIEmbeddings
from langchain_text_splitters import RecursiveCharacterTextSplitter

load_dotenv()


def initialize_vector_store() -> Chroma:
    """VectorStoreの初期化."""
    embeddings = AzureOpenAIEmbeddings(
        azure_deployment=os.getenv("DEPLOYMENT_EMBEDDINGS_NAME"),
        openai_api_version="2023-03-15-preview",
    )

    vector_store_path = "./resources/note.db"
    if Path(vector_store_path).exists():
        vector_store = Chroma(embedding_function=embeddings, persist_directory=vector_store_path)
    else:
        loader = TextLoader("resources/note.txt")
        docs = loader.load()

        text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200)
        splits = text_splitter.split_documents(docs)

        vector_store = Chroma.from_documents(
            documents=splits, embedding=embeddings, persist_directory=vector_store_path
        )

    return vector_store


def initialize_retriever() -> VectorStoreRetriever:
    """Retrieverの初期化."""
    vector_store = initialize_vector_store()
    return vector_store.as_retriever()


def initialize_chain() -> RunnableSequence:
    """Langchainの初期化."""
    prompt = hub.pull("rlm/rag-prompt")
    llm = AzureChatOpenAI(
        azure_endpoint=os.getenv("AZURE_OPENAI_ENDPOINT"),
        openai_api_version="2023-03-15-preview",
        deployment_name=os.getenv("DEPLOYMENT_GPT_NAME"),
        openai_api_key=os.getenv("AZURE_OPENAI_API_KEY"),
        openai_api_type="azure",
    )
    retriever = initialize_retriever()
    chain = (
        {"context": retriever, "question": RunnablePassthrough()} | prompt | llm
    )
    return chain


def main() -> None:
    """ChatGPTを使ったチャットボットのメイン関数."""
    chain = initialize_chain()

    # ページの設定
    st.set_page_config(page_title="RAG ChatGPT")
    st.header("RAG ChatGPT")

    # チャット履歴の初期化
    if "messages" not in st.session_state:
        st.session_state.messages = []

    # ユーザーの入力を監視
    if user_input := st.chat_input("聞きたいことを入力してね!"):
        st.session_state.messages.append(HumanMessage(content=user_input))
        with st.spinner("GPT is typing ..."):
            response = chain.invoke(user_input)
        st.session_state.messages.append(AIMessage(content=response.content))

    # チャット履歴の表示
    messages = st.session_state.get("messages", [])
    for message in messages:
        if isinstance(message, AIMessage):
            with st.chat_message("assistant"):
                st.markdown(message.content)
        elif isinstance(message, HumanMessage):
            with st.chat_message("user"):
                st.markdown(message.content)
        else:
            st.write(f"System message: {message.content}")


if __name__ == "__main__":
    main()

OpenAI API を利用する場合と異なる部分を説明していきます。
各関数の細かい説明や仕組みなどはOpenAI の記事をお読みください。

AzureOpenAIEmbeddings

OpenAI API を直接用いる場合は OpenAIEmbeddings を用いていましたが AzureOpenAI API では AzureOpenAIEmbeddings を用います。

以下に OpenAI API ではなく AzureOpenAI API を用いる場合異なる部分を diff として表示しておきます。

python
def initialize_vector_store() -> Chroma:
    """VectorStoreの初期化."""
-   embeddings = OpenAIEmbeddings()
+   embeddings = AzureOpenAIEmbeddings(
+       azure_deployment=os.getenv("DEPLOYMENT_EMBEDDINGS_NAME"),
+       openai_api_version="2023-03-15-preview",
+   )

AzureOpenAIEmbeddingsazure_deployment に先ほど作成した Embeddings のモデルのデプロイ名 (上の例だと tutorial-embeddings )をアサインしています。

AzureChatOpenAI

OpenAI API を直接用いる場合は ChatOpenAI を用いていましたが AzureOpenAI API では AzureChatOpenAI を用います。

以下に OpenAI API ではなく AzureOpenAI API を用いる場合異なる部分を diff として表示しておきます。

python
def initialize_chain() -> RunnableSequence:
    """Langchainの初期化."""
    prompt = hub.pull("rlm/rag-prompt")
-   llm = ChatOpenAI()
+   llm = AzureChatOpenAI(
+       azure_endpoint=os.getenv("AZURE_OPENAI_ENDPOINT"),
+       openai_api_version="2023-03-15-preview",
+       deployment_name=os.getenv("DEPLOYMENT_GPT_NAME"),
+       openai_api_key=os.getenv("AZURE_OPENAI_API_KEY"),
+       openai_api_type="azure",
+   )
    retriever = initialize_retriever()
    chain = (
        {"context": retriever, "question": RunnablePassthrough()} | prompt | llm
    )
    return chain

AzureOpenAIEmbeddings の以下の各引数に作成したモデルや確認した API_KEY をアサインしています。

  • azure_endpoint : Azure OpenAI のエンドポイント
  • deployment_name : Chat 用のモデルのデプロイ名
  • openai_api_key : Azure OpenAI の API_KEY

💡 AzureChatOpenAI ではなく AzureOpenAI の方を用いてしまうと実行時に以下のエラーが出てしまいますので注意してください

openai.BadRequestError: Error code: 400 - {'error': {'code': 'OperationNotSupported', 'message': 'The completion operation does not work with the specified model, gpt-35-turbo. Please choose different model and try again. You can learn more about which models can be used with each operation here: https://go.microsoft.com/fwlink/?linkid=2197993.'}}

以上の点に注意すれば OpenAI API を直接使った時と同様に実装することができます。

テスト

それでは実装したものを動かしてみましょう。

> streamlit run techblog.py

Collecting usage statistics. To deactivate, set browser.gatherUsageStats to false.


  You can now view your Streamlit app in your browser.

  Local URL: http://localhost:8501
  Network URL: http://10.200.0.4:8501
  External URL: http://13.73.233.61:8501

http://localhost:8501 へブラウザからアクセスし、質問してみます。

現時点での note.txt の先頭の方に書いてあった新しいビジョンをちゃんと答えています。この情報は OpenAI にはないためきちんと RAG が働いていると言えそうです。

また「100年後に発売予定の新商品」という情報として含まないような質問をするとちゃんと「情報は提供されていない」と返答してくれます。こちらも期待通りですね。

💡 まとめ

  • LangChain v0.2.5 時点での RAG を用いた ChatBot の実装を行いました
  • Streamlit を用いてブラウザからユーザがアクセスできるようにしました
  • AzureOpenAI API を利用して実装することができました
Cykinso's Tech Blog

Discussion