こんにちは、エクスチュアの石原です。
こちらは第2回の記事になっております。前回の記事をご覧になっていない方は、ぜひ前回の記事からお読みください。
前回の記事ではLangChainの最も基礎的な部分であるLLMの応答に関わるPromptTemplate・OutputParser・LLM Chainについてお話しました。これによって生成AIを用いたアプリケーションの開発はできるようになりましたが、より複雑な生成AIアプリケーションの構築を行うためにクイックスタートの続きを解説していきます。
Retrieval Chain
前回の記事でLLMに投げていた質問「Langsmithはテストをどのように支援できますか?」は生成AIにとっては実は答えるのが難しい質問になります。その理由は単純で、ChatGPT-3.5のモデルは2021年9月までの情報しか学習ができていないためです。
Langsmithは2023年に作られたプロダクトなので、2021年までの知識しか持たないLLMは「誤った回答を想像から生成する」もしくは「知識に無いので答えられない」と回答するしかできないのです。そのため、OpenAIがChatGPT-3.5のモデルを更新しない限りは誤った回答を生成する確率が非常に高くなってしまいます。
参考:https://platform.openai.com/docs/models/overview/
この問題を解決する方法として、LLMに知識を追加するという方法があります。これは単純な解決策で、LLMにWeb上で調べた検索結果を入力することで、最新情報を調べた状態にするという方法になります。わからないことを調べるというのは人間もAIも同じというわけですね。
それでは、実際にコードを作成していきましょう。今回のコードはこちら。
WebBaseLoader
まずは、langchianとインターネット上のデータを取得するためのスクレイピングツールのbeautifulsoup4をインストールします。
pip install langchain langchain-openai beautifulsoup4
その後、WebBaseLoader をインポートして使用します。
from langchain_community.document_loaders import WebBaseLoader
loader = WebBaseLoader("https://docs.smith.langchain.com")
docs = loader.load()
これによってLangsmithの公式ドキュメントから情報を取得することができました。
ここでdocsはLangChainで用意されているDocmentクラスのリストになっています。Docmentにはメタデータとコンテンツを持っています。WebBaseLoaderでは以下のようなデータを保持しています。
絵文字🦜️🛠️が少々文字化けしていますが、ページ内の文章が取得できていることがわかります。
[Document(page_content="\n\n\n\n\nLangSmith | \uf8ffü¶úÔ∏è\uf8ffüõ†Ô∏è LangSmith\n\n\n\n\n\nSkip to main content\uf8ffü¶úÔ∏è\uf8ffüõ†Ô∏è LangSmith DocsLangChain Python DocsLangChain JS/TS DocsLangSmith API DocsSearchGo to AppLangSmithUser GuideSetupPricing (Coming Soon)Self-HostingTracingEvaluationMonitoringPrompt HubLangSmithOn this pageLangSmithIntroduction‚ÄãLangSmith is a platform for building production-grade LLM applications.It lets you debug, test, evaluate, and monitor chains and intelligent agents built on any LLM framework and seamlessly integrates with LangChain, the go-to open source framework for building with LLMs.LangSmith is developed by LangChain, the company behind the open source LangChain framework.Quick Start‚ÄãTracing: Get started with the tracing quick start.Evaluation: Get started with the evaluation quick start.Next Steps‚ÄãCheck out the following sections to learn more about LangSmith:User Guide: Learn about the workflows LangSmith supports at each stage of the LLM application lifecycle.Setup: Learn how to create an account, obtain an API key, and configure your environment.Pricing: Learn about the pricing model for LangSmith.Self-Hosting: Learn about self-hosting options for LangSmith.Tracing: Learn about the tracing capabilities of LangSmith.Evaluation: Learn about the evaluation capabilities of LangSmith.Prompt Hub Learn about the Prompt Hub, a prompt management tool built into LangSmith.Additional Resources‚ÄãLangSmith Cookbook: A collection of tutorials and end-to-end walkthroughs using LangSmith.LangChain Python: Docs for the Python LangChain library.LangChain Python API Reference: documentation to review the core APIs of LangChain.LangChain JS: Docs for the TypeScript LangChain libraryDiscord: Join us on our Discord to discuss all things LangChain!Contact SalesIf you're interested in enterprise security and admin features, special deployment options, or access for large teams, reach out to speak with sales.NextUser GuideIntroductionQuick StartNext StepsAdditional ResourcesCommunityDiscordTwitterGitHubDocs CodeLangSmith SDKPythonJS/TSMoreHomepageBlogCopyright ¬© 2024 LangChain, Inc.\n\n\n\n", metadata={'source': 'https://docs.smith.langchain.com', 'title': 'LangSmith | \uf8ffü¶úÔ∏è\uf8ffüõ†Ô∏è LangSmith', 'description': 'Introduction', 'language': 'en'})]
こちらのDocmentをそのままLLMに入力しても良いですが、今回の例では「Retrieval-Augmented Generation」(RAG)と呼ばれる仕組みを踏襲する形でこのDocmentをVectorStoreに保存してプロンプトを作成していきます。
VectorStore
用意したコンテキストからLLMに渡す必要のある内容を検索するデータベースがvectorstoreになります。 また、vectorstoreと深く関連する内容としてEmbeddingsについてもご紹介いたします。
例では検索結果が1件のみなので、追加で以下のようにDocmentを読み込みます。
url_list = ["https://python.langchain.com/docs/langserve","https://python.langchain.com/docs/get_started/introduction"]
for url in url_list:
loader = WebBaseLoader(url)
docs.extend(loader.load())
Embeddings
次にEmbeddings modelを定義します。
Embeddingsは以前作成したこちらの記事でもお話しましたが、文章などベクトル形式に変換する仕組みです。類似する内容が近くに位置するというベクトル形式に変換することで、類似度の検索を行うことが出来ます。
今回はOpenAIの提供しているEmbeddings modelを使用します。(vectorstoreではここで設定したものと同じモデルを使用する必要があります。)
from langchain_openai import OpenAIEmbeddings
embeddings = OpenAIEmbeddings()
お試しに、Docmentをベクトル変換してみましょう。実行すると2100次元のベクトルが生成されます。
embed_doc = embeddings.embed_documents(docs[0].page_content)
print(len(embed_doc))
多次元すぎて人間には把握できないものになっていますが、vectorstoreではこれを使用して類似検索する仕組みが最初から提供されています。
Faiss
次にvectorstoreを定義します。今回はfaissというvectorstoreを使用します。cpuメモリ用のパッケージをインストールします。
pip install faiss-cpu
先ほどのDocmentとEmbeddings modelを入力しますが、Embeddings modelには文字数の制限があるため、text_splitterによって分割して入力します。(なお、from_documents関数内でembed_documentsが自動的に実行されています)
from langchain_community.vectorstores import FAISS
from langchain.text_splitter import RecursiveCharacterTextSplitter
# テキストを分割
text_splitter = RecursiveCharacterTextSplitter()
documents = text_splitter.split_documents(docs)
vector = FAISS.from_documents(documents, embeddings)
これにてvectorstoreを準備することができました。
ここで、類似度の検索を試してみましょう。
similarity_search_with_scoreは類似度のスコアを出力する関数で、検索ワードは「langsmith」にしています。kの値は結果の数になるので、今回は全件を対象にしています。出力結果は元のdocumentsのURLにしています。
res = vector.similarity_search_with_score("langsmith",k=len(documents))
for r,score in res:
print(r.metadata['source'],score)
結果がこちらです。
最も類似するものがlangsmithのドキュメントになっていますね。このように類似している可能性の高い情報だけをプロンプトに追加するものがRAGの仕組みになっています。
https://docs.smith.langchain.com 0.30677193
https://python.langchain.com/docs/langserve 0.37146676
https://python.langchain.com/docs/get_started/introduction 0.38909566
https://python.langchain.com/docs/get_started/introduction 0.41376638
https://python.langchain.com/docs/get_started/introduction 0.43194193
https://python.langchain.com/docs/langserve 0.503536
https://python.langchain.com/docs/langserve 0.50498414
https://python.langchain.com/docs/langserve 0.5049914
https://python.langchain.com/docs/langserve 0.5206187
https://python.langchain.com/docs/langserve 0.5917833
https://python.langchain.com/docs/langserve 0.63852376
それでは、検索結果をLLMに入力します。コンテキストを結合するためのLLMChainとして用意されているcreate_stuff_documents_chainメソッドを使用しましょう。
from langchain_core.prompts import ChatPromptTemplate
from langchain.chains.combine_documents import create_stuff_documents_chain
prompt = ChatPromptTemplate.from_template("""Answer the following question based only on the provided context:
<context>
{context}
</context>
Question: {input}""")
document_chain = create_stuff_documents_chain(llm, prompt)
ここでDocmentを手動で渡す場合は以下のように書けます。
from langchain_core.documents import Document
doc,score = res[0] # 先ほどの検索結果から最も類似するDocmentを取得
document_chain.invoke({
"input": "how can langsmith help with testing?",
"context": [doc]
})
ただし、検索も一度に実行してくれるChainも提供されています。
from langchain.chains import create_retrieval_chain
retriever = vector.as_retriever()
retrieval_chain = create_retrieval_chain(retriever, document_chain)
response = retrieval_chain.invoke({"input": "how can langsmith help with testing?"})
print(response["answer"])
以下が実行結果になります。
知識を追加する前にくらべて具体的な機能や処理が並んでおり、より専門的な回答になっているかと思います。
LangSmithは、LLMフレームワークで構築されたチェーンやインテリジェントエージェントをデバッグ、テスト、評価、監視することができます。LangSmithはLangChainとシームレスに統合されており、LLMで構築するためのオープンソースフレームワークであるLangChainの主要なプラットフォームです。LangSmithにはトレーシング機能と評価機能があり、LLMアプリケーションのライフサイクルの各段階でサポートするワークフローについて学ぶことができます。
会話
ここまでで作成したLLMChainでは、予め用意をしておいたURLの情報に基づいて答えを生成してくれるようになりました。しかし、これでは用意した単一の問題にのみ回答できるようシステムが構築できただけです。
LLMを用いることで構築されるアプリケーションの代表例はチャットボットですので、これまでに話した質問も踏まえたうえでの回答が期待されます。
memoryと呼ばれる会話履歴をLLMに追加することで、過去のやり取りをLLMが把握できるようにします。
create_history_aware_retriever
まずは、チャットの入力だけではなく履歴を踏まえて、vectorstoreで検索する文章を作成するLLMChainを作成します。
from langchain.chains import create_history_aware_retriever
from langchain_core.prompts import MessagesPlaceholder
# First we need a prompt that we can pass into an LLM to generate this search query
prompt = ChatPromptTemplate.from_messages([
MessagesPlaceholder(variable_name="chat_history"),
("user", "{input}"),
("user", "上記の会話を想定して、会話に関連する情報を得るために検索を行います。")
])
retriever_chain = create_history_aware_retriever(llm, retriever, prompt)
次にチャットのやり取りをリストの形式でchat_historyとして作成し、実行してみましょう。
今回、直接入力しているのは教えてくださいという文章のみです。
from langchain_core.messages import HumanMessage, AIMessage
chat_history = [HumanMessage(content="Langsmithはテストをどのように支援できますか?"), AIMessage(content="はい!")]
retriever_chain.invoke({
"chat_history": chat_history,
"input": "教えてください"
})
上記コードを実行すると、Docmentの検索結果が得られます。LangSmithが検索結果の上位に来ていると思います。これで検索を行う新しいLLMChainが作成できました。
それでは質問用のChainを作成します。
prompt = ChatPromptTemplate.from_messages([
("system", "以下の文脈に基づいてユーザーの質問に答えてください:\n\n{context}"),
MessagesPlaceholder(variable_name="chat_history"),
("user", "{input}"),
])
document_chain = create_stuff_documents_chain(llm, prompt)
retrieval_chain = create_retrieval_chain(retriever_chain, document_chain)
chat_history = [HumanMessage(content="Langsmithはテストをどのように支援できますか?"), AIMessage(content="はい!")]
retrieval_chain.invoke({
"chat_history": chat_history,
"input": "教えてください"
})
応答のanswerを確認すると以下のようになっています。
しっかりと過去の履歴を踏まえていることが確認できますね。
LangSmithは、LLMアプリケーションのテストを支援するための機能を提供しています。具体的には、LangSmithを使用して以下のようなことができます:\n\n1. デバッグ:LangSmithを使用して、構築したLLMチェーンやインテリジェントエージェントをデバッグすることができます。これにより、アプリケーションの動作を理解し、問題を特定して修正することができます。\n\n2. テスト:LangSmithを使用して、構築したLLMアプリケーションをテストすることができます。テストを実行してアプリケーションの正確性やパフォーマンスを検証し、品質を確保することができます。\n\n3. 評価:LangSmithを使用して、構築したLLMアプリケーションを評価することができます。アプリケーションの効果や成果を評価し、改善のための洞察を得ることができます。\n\n4. モニタリング:LangSmithを使用して、構築したLLMアプリケーションをモニタリングすることができます。アプリケーションの動作やパフォーマンスをリアルタイムで監視し、問題が発生した際に迅速に対応することができます。\n\nこれらの機能を活用することで、LangSmithを使って効果的なテストを行い、品質の高いLLMアプリケーションを構築することができます。
Agent
最後にご紹介するのが、langcahinの特徴的な機能であるAgentです。
Agnetを用いることでLLMにこちらが準備したツールと呼ばれる処理を実行させることができます。これによってLLM単体よりもより正確な回答が期待できます。
今回は検索エンジンと先ほど作成したretrieverをツールとして登録することで、langsmithに関する質問とそれ以外の質問で使用するツールを使い分けてもらいます。
まず、retrieverをツールとして登録します。この時設定する説明文を元にLLMがツールを使用するかどうかの判断を行うのでここはしっかりと設定する必要があります。
from langchain.tools.retriever import create_retriever_tool
# Toolの設定(retriever)
retriever_tool = create_retriever_tool(
retriever,
"langsmith_search",
"LangSmithに関する情報を検索。LangSmithに関するご質問は、こちらのツールをご利用ください!",
)
その他の内容については、検索エンジンを使用してもらいます。 今回はTavilyというAPIを使用してみます。
無料分で今回の処理は試すことができると思うので、よければ登録してみてください。
- Tavily : https://tavily.com/
環境変数にTavilyのAPIKeyを「TAVILY_API_KEY」という名前で登録します。
from langchain_community.tools.tavily_search import TavilySearchResults
# Toolの設定(Tavily)
search = TavilySearchResults()
langchainで用意されているツールはデフォルトでツールの説明を保持しています。以下のコードで確認できます。
print(search.description)
# A search engine optimized for comprehensive, accurate, and trusted results. Useful for when you need to answer questions about current events. Input should be a search query.
使用するツールをリストでAgentに渡す必要があるので以下のようにtoolsを定義します。
# Agentの使用にはtoolsのリストを登録する必要があります。
tools = [retriever_tool, search]
Agentを使用するためには別途プロンプトが必要になります。
今回はlangchainhubからインストールをしてみます。
pip install langchainhub
from langchain_openai import ChatOpenAI
from langchain import hub
from langchain.agents import create_openai_functions_agent
from langchain.agents import AgentExecutor
# Get the prompt to use - you can modify this!
prompt = hub.pull("hwchase17/openai-functions-agent") # Agent用のプロンプトをダウンロード
llm = ChatOpenAI(model="gpt-3.5-turbo", temperature=0)
agent = create_openai_functions_agent(llm, tools, prompt)
agent_executor = AgentExecutor(agent=agent, tools=tools)
それでは準備が整いましたので、以下のようにコードを実行します。
agent_executor.invoke({"input": "Langsmithはテストをどのように支援できますか?"})
結果は以下の様になりました。
{'input': 'Langsmithはテストをどのように支援できますか?',
'output': 'LangSmithは、本番向けのLLM(Large Language Models)アプリケーションを構築するためのプラットフォームです。LangSmithは、チェーンやインテリジェントエージェントをデバッグ、テスト、評価、モニタリングすることができます。LangSmithはLangChainとシームレスに統合されており、LLMで構築する際のオープンソースフレームワークとして知られています。LangSmithはLangChainの開発元であるLangChain社によって開発されています。LangSmithを使用することで、テストや評価などの機能を活用して、LLMアプリケーションの開発をサポートすることができます。詳細については、LangSmithのユーザーガイドやドキュメントを参照してください。'}
内部でどのようにAgentが動いているかを見るためにverboseオプションをTrueに設定して実行してみます。
agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True)
agent_executor.invoke({"input": "Langsmithはテストをどのように支援できますか?"})
すると、以下のように出力内部でlangsmith_searchを使用しているというログを確認することが出来ます。
Invoking: `langsmith_search` with `{'query': 'Langsmith support for testing'}`
それでは、別の質問をしてみましょう。
agent_executor.invoke({"input": "東京の天気は?"})
{'input': '東京の天気は?',
'output': '東京の天気は、明日は晴れて日向では少し暖かさを感じられる予報です。朝晩は冷えるので、体調を崩さないようにお気を付けください。洗濯など日差しの有効活用がオススメです。最高気温は13℃、最低気温は5℃で、降水確率は午前0%、午後0%です。'}
東京にお住いの方は実際の天気情報が表示されましたでしょうか。
こちらもログを確認すると、以下のようになっていました。
Invoking: `tavily_search_results_json` with `{'query': '東京の天気'}`
これで様々な質問に対して動的にLLMが検索しながら回答してくれるアプリケーションが開発できましたね。お疲れ様でした。
まとめ
本記事ではRAGとAgentを使用したアプリケーションの作成方法を紹介しました。上記の内容をさらに発展させると、社内データ案内用のチャットボットなどを作成することができるようになります。今回ご紹介した内容の詳細については公式ドキュメントをどうぞ。
次回の記事では、LLMへの質問例として使用していたlangsmithを実際に使用してみます。また、RestAPIなどを作成できるlangserveについてご紹介する予定です。
今後もLangChainの機能を継続してご紹介していく予定ですので、どうぞご注目ください。
エクスチュアはマーケティングテクノロジーを実践的に利用することで企業のマーケティング活動を支援しています。
ツールの活用にお困りの方はお気軽にお問い合わせください