# by nitta
import os
is_colab = 'google.colab' in str(get_ipython()) # for Google Colab
if is_colab:
from google.colab import drive
drive.mount('/content/drive')
SAVE_PATH='/content/drive/MyDrive/DeepLearning/llm_book2'
else:
SAVE_PATH='.'
# # variable definition in 'account.py' to access HuggingFace Hub
# MyAccount = {
# 'HuggingFace': {
# 'user': 'YourUserName',
# 'token': 'YourTokenWithWritePermission'
# },
# 'OpenAI': {
# 'api-key': 'YourOpenAI_API_Key'
# }
# }
ACCOUNT_FILE = os.path.join(SAVE_PATH, 'account.py')
%run {ACCOUNT_FILE} # set 'MyAccount' variable
LangChain は、さまざまなモジュールを組み合わせることで、複雑なタスクを実行するアプリケーションを作成する。 このモジュールを Chain と呼ぶ。
Agent モジュールを使うことで、ユーザの入力から回答として求められているものを推論し、必要なツールを選択して実行する(= 行動)ことで、最適な回答を導き出す。
Agent は、回答が不十分であると判断した場合は、推論と行動を繰り返して、高い精度で複雑なタスクを実行する。
次の方法を説明する。
! pip install langchain
LangChain を用いて構築されるアプリケーションでは、LLM呼び出しを複数回呼び出す手順を含んでいる。chain や agent 内で何が起きているかを調べる最良の方法は LangSmith を使うことである。
LangSmith を使う場合は、次の環境変数を設定すること。
export LANGCHAIN_TRACING_V2="true" export LANGCHAIN_API_KEY="..."
LangChain を使うと、LLMを外部のデータソースや計算に接続するアプリケーションを構築できる。 このクィックスタートでは、そのためのいくつかの方法を示す。 返答のためのプロンプト・テンプレートのみに依存する単純な LLM chain から始める。 次に、別のデータベースからデータを取得し、それをプロンプト・テンプレートに渡す chain を構築する。 それから、チャット履歴を追加して、会話検索 chain を構築する。 これにより 前の質問を記憶されているので、LLM とチャット形式で対話できる。 最後に、質問に答えるためにデータを取得する必要があるかどうかを決定するために LLM を使う agent を構築する。 大まかに説明するが、それぞれには詳細がある。 関連するリンクを貼っておくので必要ならば参照してほしい。
#
! pip install langchain-openai
import os
os.environ["OPENAI_API_KEY"] = MyAccount["OpenAI"]["api-key"]
from langchain_openai import ChatOpenAI
llm = ChatOpenAI()
# llm = ChatOpenAI(openai_api_key=MyAccount["OpenAI"]["api-key"])
llm.invoke("how can langsmith help with testing?")
Langsmith は、次のようなさまざまな方法でテストを支援できます。
さまざまなプログラミング言語で記述されたコードのテストを迅速かつ効率的に実行できる自動テスト ツールを提供します。
テストを実行する前にコード内の潜在的なバグやエラーを特定するコード分析機能を提供します。
テスト プロセスを合理化し、テストの作成と実行を容易にするテスト フレームワークとライブラリを提供します。
単体テスト、統合テスト、エンドツーエンド テストなど、さまざまなテスト手法のサポートを提供します。
継続的な統合および展開ツールを提供して、テスト プロセスを自動化し、コードの変更が本番環境に展開される前に徹底的にテストされるようにします。
全体として、Langsmith は包括的なテスト機能を通じてソフトウェアの品質と信頼性の向上に役立ちます。
# prompt template を使って回答を誘導することができる
from langchain_core.prompts import ChatPromptTemplate
prompt = ChatPromptTemplate.from_messages([
("system", "You are world class technical documentation writer."),
("user", "{input}")
])
# combine these into a simple LLM chain
# In python, '|' is short hand for calling the object's __or__ method.
chain = prompt | llm
# 上の単純な llm への質問と同じ内容を送ってみる。"techincal writer" として回答してくれるはず。
chain.invoke({"input": "how can langsmith help with testing?"})
Langsmith は、次のようなさまざまな方法でテストを支援できます。
テストの自動化: ラングスミスは、言語仕様に基づいてテスト スクリプトまたはテスト ケースを生成することにより、テスト プロセスを自動化できます。 これは、手作業の労力を軽減し、より一貫性のある包括的なテスト範囲を確保するのに役立ちます。
テスト データの生成: Langsmith は、言語のルールと制約に基づいて、さまざまなシナリオのテスト データの生成を支援します。 これは、さまざまなエッジケースをテストし、システムの堅牢性を確保するのに役立ちます。
テスト カバレッジ分析: ラングスミスは、言語仕様に基づいてテスト カバレッジを分析し、さらにテストが必要な領域についての洞察を提供します。 これは、テスト プロセス全体の品質の向上に役立ちます。
回帰テスト: Langsmith は、コード変更後にテスト ケースを自動的に再実行し、回帰を特定することで、回帰テストの実行を支援します。 これは、新しい変更によって既存の機能が損なわれないようにするのに役立ちます。
パフォーマンス テスト: Langsmith は、言語仕様に基づいて負荷テストを生成することにより、パフォーマンス テストを支援することもできます。 これは、さまざまな負荷条件下でのシステムのパフォーマンスを評価するのに役立ちます。
全体として、Langsmith は言語分析機能を活用することで、テスト プロセスを合理化し、効率を向上させ、テストの品質を向上させることができます。
# ChatModel の出力は message であるが、文字列として扱うと便利な場合が多い。
# 単純な output parser を使って、message を string に変換できる。
from langchain_core.output_parsers import StrOutputParser
chain = prompt | llm | StrOutputParser()
chain.invoke({"input": "how can langsmith help with testing?"})
基本的な LLM chain のせってアップ方法を解説してきた。 プロンプトやモデル、出力パーサの基礎に触れたに過ぎない。 詳しく知りたい場合は Model I/O を参照すること。
元の質問 "how can langsmith help with testing?" に適切に回答するために、LLMに追加の文脈を提供する必要がある。 retrieval を用いてこれを行うことができる。 retreaval は、LLMに直接渡すには多すぎるデータがあるときに、役に立つ。 最も関連した部分だけを取得して渡すには retreaver を用いる。
この手順では、関連文書を Retriever から検索して、プロンプトに渡す。 retriever は SQL テーブル、インターネットなど、なんでも裏で使えるが、 今回はvector storeを設定してretriever として使ってみる。 詳しい説明は Vector stores を参照のこと。
最初に、インデックスしたいデータをロードする必要がある。 そのために WebBaseLoader を使う。 これには BeautifulSoup をインストールする必要がある。
! pip install beautifulsoup4
from langchain_community.document_loaders import WebBaseLoader
loader = WebBaseLoader("https://docs.smith.langchain.com/user_guide")
docs = loader.load()
次に、これを vector store へインデックスする必要がある。 そのためには、いくつかのコンポーネント、すなわち embedding model と vectorstore が必要となる。
# lancchain_openai パッケージをインストールしたうえで、環境変数が設定されている必要がある。
from langchain_openai import OpenAIEmbeddings
embeddings = OpenAIEmbeddings()
# 簡単にするためローカルな vector store である FAISS を使う。
# まず、パッケージをインストールする
! pip install faiss-gpu # faiss-cpu
# 次に、自分の index を構築する
from langchain_community.vectorstores import FAISS
from langchain_text_splitters import RecursiveCharacterTextSplitter
text_splitter = RecursiveCharacterTextSplitter()
documents = text_splitter.split_documents(docs)
vector = FAISS.from_documents(documents, embeddings)
このデータは vectorstore 内で index 付けされたので、retrieval chain を作成できる。 この chain は入力される質問を受け取り、関連文書を検索し、元の質問と共にこれらの文書をLLM に渡し、元の質問に回答するように依頼する。
# 最初に、質問を受け取り、文書を検索し、回答を生成する chain を設定する。
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)
# 必要ならば、文書に直接渡すことでこれを自分で実行することもできる。
from langchain_core.documents import Document
document_chain.invoke({
"input": "how can langsmith help with testing?",
"context": [Document(page_content="langsmith can let you visualize test results")]
})
#しかし、最初に設定した retriever から文書を得ようと思う。 このやり方では、与えられた質問に対して、retriever を用いて最も関連が深い文書を動的に選択して、それを渡すことができる。
from langchain.chains import create_retrieval_chain
retriever = vector.as_retriever()
retrieval_chain = create_retrieval_chain(retriever, document_chain)
# これで、chain を呼び出すことができるようになった。辞書を返すが、LLMからの返答では "answer" key が回答である。
response = retrieval_chain.invoke({
"input": "how can langsmith :help with testing?"
})
print(response["answer"])
これで 基本的な retrieval chain を設定できたことになる。 より詳しい情報は Retrieval を参照すること。
これまで作成してきた chain は、単一の質問に答えるだけであった。 LLM アプリケーションの主要なタイプの一つは、chat bot である。 chain を続く質問に答えられるように調整する方法について述べる。
やはり create_retrieval_chain
関数を使うことができるが、
2種類の変更点がある。
検索を更新するために、新しい chain を作成する。
この chain は、最新の質問を input
に、
会話履歴を chat_history
に受け取り、LLM を使用して
質問クエリを生成する。
from langchain.chains import create_history_aware_retriever
from langchain_core.prompts import MessagesPlaceholder
# 最初に、LLM に渡して検索クエリを生成させるための、プロンプトが必要となる。
prompt = ChatPromptTemplate.from_messages([
MessagesPlaceholder(variable_name="chat_history"),
("user", "{input}"),
("user", "Given the above conversation, generate a search query to look up in order to get information relevant to the conversation")
])
retriever_chain = create_history_aware_retriever(llm, retriever, prompt)
# ユーザーが引き続く質問をしたインスタンスを渡して、テストする。
from langchain_core.messages import HumanMessage, AIMessage
chat_history = [
HumanMessage(content="Can LangSmith help test my LLM applications?"),
AIMessage(content="Yes!")
]
retriever_chain.invoke({
"chat_history": chat_history,
"input": "Tell me how"
})
# 新しい retriever を得て、検索済みの文書を記憶しながら、会話を続けるための新しい chain を生成することができる。
prompt = ChatPromptTemplate.from_messages([
("system", """
Answer the user's questions based on the below context:
{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)
# end-to-end でテストできるようになった。
chat_history = [
HumanMessage(content="Can LangSmith help test my LLM applications?"),
AIMessage(content="Yes!")
]
retrieval_chain.invoke({
"chat_history": chat_history,
"input": "Tell me how"
})
LangSimth でのテストに関する文書を、これが返すことがわかる。 これは、続く質問を会話履歴に結合して、LLM が新しい質問を生成したおかげである。
この新しい retriver を得たので、検索した文書を心にとめつつ、会話を続ける新しい chain を生成できる。
prompt = ChatPromptTemplate.from_messages([
("system", """
Answer the user's questions based on the below context:
{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)
# end-to-end でテストする
chat_history = [
HumanMessage(content="Can LangSmith help test my LLM applications?"),
AIMessage(content="Yes!")
]
retrieval_chain.invoke({
"chat_history": chat_history,
"input": "Tell me how"
})
一貫した回答が得られたことがわかる。retrieval chain (質問チェイン)を chat bot へと調整することができた。
各ステップが事前にわかっている chain 例を作成してきた。 最後に作成するのは agent で、agent では LLM がどのステップを取るかを決定する。
[注意] ローカルなモデルはまだ信頼性がないので、OpenAI モデルを使った agent の作り方のみを説明する。
agent を構築するときにさいしょにすべきなのは、どのツールにアクセスできるかを決定することである。 この例では、agent が2つのツールにアクセスできるものとする。
最初に、たった今作成した retriever 用のツールをセットアップする。
from langchain.tools.retriever import create_retriever_tool
retriever_tool = create_retriever_tool(
retriever,
"langsmith_search",
"""\
Search for information about LangSmith. \
For any questions about LangSmith, \
you must use this tool!\
""",
)
使用する検索ツールは Tavily である。 https://tavily.com/
Tavily API の使用は、限定的ならば無料である。 クレジットカードの入力無しでサインアップして API キーを取得できる。
# 検察ツールは Tavily である。
import os
os.environ["TAVILY_API_KEY"] = MyAccount["Tavily"]["api-key"]
from langchain_community.tools.tavily_search import TavilySearchResults
search = TavilySearchResults()
tools = [retriever_tool, search]
ツールを準備したので、これらを使う agent を生成する。
! 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
prompt = hub.pull("hwchase17/openai-functions-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, verbose=True)
agent_executor.invoke({"input":"how can langsmith help with testing?"})
agent_executor.invoke({"input":"what is the weather in SF?"})
chat_history = [
HumanMessage(
content="Can LangSmith help test my LLM applications?"
),
AIMessage(content="Yes!")
]
agent_executor.invoke({
"chat_history": chat_history,
"input": "Tell me how"
})
基本的な agent を設定することができた。 もっと詳しい情報は Agents を参照してほしい。
アプリケーションを構築したので、それを外部に提供する。 ここで LangServe が必要になる。 LangServe は LangChain chain を REST API として配備するのに役に立つ。
この文書はこれまで Jupyter で実行することを意図していたが、これ以降は Python ファイルを作成してコマンドラインから対話実行する。
! pip install "langserve[all]"
略
!python {SAVE_PATH}/langserve_serv.py
略