Open14

LangChainを触って動かしたときのメモ

zerebomzerebom

LangChain何もわからないので、ChatGPTに解説してもらいつつ、さらっていきたい。
※ ⚠️スピード重視でキャッチアップ & ちゃんとドキュメント精読してるわけではないので注意

まずはドキュメント読む

下記のような主要なモジュールがあるらしい。

  • Models(モデル):LangChainがサポートする様々なモデルタイプとモデル統合
  • Prompts(プロンプト):プロンプトの管理、プロンプトの最適化、プロンプトのシリアル化
  • Memory(メモリ):チェーン/エージェントの呼び出しの間に状態を永続化する概念。LangChainは、メモリのための標準インターフェース、メモリの実装のコレクション、およびメモリを使用するチェーン/エージェントの例を提供する。
  • Indexes(インデックス):言語モデルは、独自のテキストデータと組み合わせるとより強力になることがある。このモジュールでは、そのためのベストプラクティスをカバーしている。
  • Chains(チェーン):単一のLLM呼び出しを超えて、呼び出しのシーケンス。LangChainは、チェーンのための標準インターフェース、他のツールとの多数の統合、および一般的なアプリケーションのためのエンドツーエンドのチェーンを提供する。
  • Agents(エージェント):LLMがどのアクションを取るかを決定し、そのアクションを取り、観察を見てそれを繰り返すことを含む。LangChainは、エージェントのための標準インターフェース、選択可能なエージェントのコレクション、およびエンドツーエンドのエージェントの例を提供する。
zerebomzerebom

LangChainのほうが生openaiより、インターフェースが簡単だ。
これだけでも、とりあえず嬉しいかもしれない

zerebomzerebom

Quickstart Guide :Agent

複数のミニ機能を自然言語でつなぎ合わせられるやつ。例えばgoogle検索 → LLMで抽出 なんかができる。

from langchain.agents import AgentType, initialize_agent, load_tools
from langchain.chains import LLMChain
from langchain.llms import OpenAI
from langchain.prompts import PromptTemplate

llm = OpenAI(temperature=0)

tools = load_tools(["serpapi", "llm-math"], llm=llm)
agent = initialize_agent(tools, llm, agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION, verbose=True)
agent.run("Wantedlyの樋口ってだれですか")

ANS: '樋口はWantedlyでデータサイエンティストをしている新卒です。'

ChatGPT先生のわかりやすい解説

LangChainにおけるAgentは、ユーザーの入力に応じて特定の処理を行い、必要に応じて他の外部ツールやチェーンと連携して追加の処理を行うための仕組みです。

具体的には、エージェントはLLM(言語モデル)を使用して、どのようなアクションを実行するか、どのような順序で実行するかを決定します。アクションには、外部ツールやチェーンを使用して出力を観察するもの、ユーザーに戻るものなどがあります。

エージェントの構築には、以下の3つの要素が必要です。

  1. ツール: 特定の処理を実行するための関数。たとえば、Google検索、データベース検索、Python REPL、他のチェーンなどがあります。ツールのインターフェースは、現在は文字列を入力として、文字列を出力する関数であることが期待されています。

  2. LLM: エージェントを動かすための言語モデル。

  3. Agent: エージェントの種類。サポートされている標準エージェントを使用する場合は、文字列で参照する必要があります。カスタムエージェントを実装する場合は、カスタムエージェントのドキュメントを参照してください。

エージェントは、ユーザーの入力に対する特定の処理を行うことができるため、非常に強力な機能です。LangChainでは、ユーザー入力を受け取ってエージェントを初期化し、必要なツールとLLMを読み込んでから、エージェントを実行して追加の処理を行うことができます。

短い説明

Agentは、LLMを使って何をすべきか、何の順序で行うかを決定するために使用される概念です。ツールや他のチェーンなど、様々なプリミティブを使用して構成され、正しく使用されれば非常に強力です。Agentを使用するには、ツールやLLM、Agentの種類などの概念を理解する必要があります。さらに、事前に定義されたAgentの種類やツールの仕様が提供されています。例として、SerpAPIパッケージをインストールして環境変数を設定し、サポートされるAgentクラスを参照してAgentを初期化する方法を示しました。

プリミティブとは?

プリミティブとは、LangChainで使用される基本的な機能や処理を行うコンポーネントのことです。例えば、LLM(言語モデル)やPromptTemplate(プロンプトのテンプレート)がプリミティブの一例です。これらのプリミティブを組み合わせて、より複雑なアプリケーションを構築することができます。

zerebomzerebom

Quickstart Guide :Memory

会話の記憶を保持するための機能。conversationインスタンスに.predict()メソッドでinputを与えることで、前後の文脈を保持する感じ。多分openaiapiでいうmessagesに当たるやつ。

from langchain import OpenAI, ConversationChain

llm = OpenAI(temperature=0)
conversation = ConversationChain(llm=llm, verbose=True)

conversation.predict(input="Hi there!")
conversation.predict(input="I'm doing well! Just having a conversation with an AI.")

ans: " That's great! It's always nice to have a conversation with someone new. What would you like to talk about?"

ChatGPT先生の解説

Memory(メモリー)とは、LangChainにおいて過去のやりとりを記憶し、それを後のやりとりに活用する機能です。例えば、チャットボットを作る場合、前回の会話内容を記憶して、それを元に次回の会話をより良いものにするために使われます。また、長期的なメモリーを持つチェーン/エージェントも存在し、重要な情報を記憶することでより複雑なタスクを実行することができます。LangChainには、このようなメモリーを持つための特別なチェーンが用意されています。

zerebomzerebom

Quickstart Guide :Chat Models

ChatModelsはチャットの新しい抽象化モデル?らしい。正しい抽象化を模索中とのこと。

その公開するインターフェースは「テキストを入力してテキストを出力する」APIではなく、「チャットメッセージ」が入出力となるインターフェースとなっています。

下のコードを読む感じ、stringじゃなくて、Message classをchatインスタンスに渡すようなインターフェースみたい。
きっとopenaiとかgoogleとか複数のLLM APIがでてきたとしても、うまく統合できるように抽象化するために作ってるんじゃないかと想像した。

あとMessageはstring以上の情報(ex. 会話履歴, やtemprature等のパラメータ)を持つだろうし、これらにアクセス可能にするためにいるのかな、と思った。

from langchain.chat_models import ChatOpenAI
from langchain.schema import (
    AIMessage,
    HumanMessage,
    SystemMessage
)

chat = ChatOpenAI(temperature=0)

# 単一のメッセージを渡して完了を取得することもできます
chat([HumanMessage(content="Translate this sentence from English to French. I love programming.")])
# -> AIMessage(content="J'aime programmer.", additional_kwargs={})

# gpt-3.5-turboとgpt-4モデルの場合、複数のメッセージを渡すこともできます
messages = [
    SystemMessage(content="You are a helpful assistant that translates English to French."),
    HumanMessage(content="Translate this sentence from English to French. I love programming.")
]
chat(messages)
# -> AIMessage(content="J'aime programmer.", additional_kwargs={})

# 複数のセットのメッセージの完了を生成することもできます
batch_messages = [
    [
        SystemMessage(content="You are a helpful assistant that translates English to French."),
        HumanMessage(content="Translate this sentence from English to French. I love programming.")
    ],
    [
        SystemMessage(content="You are a helpful assistant that translates English to French."),
        HumanMessage(content="Translate this sentence from English to French. I love artificial intelligence.")
    ],
]
result = chat.generate(batch_messages)
result
# -> LLMResult(generations=[[ChatGeneration(text="J'aime programmer.", generation_info=None, message=AIMessage(content="J'aime programmer.", additional_kwargs={}))], [ChatGeneration(text="J'aime l'intelligence artificielle.", generation_info=None, message=AIMessage(content="J'aime l'intelligence artificielle.", additional_kwargs={}))]], llm_output={'token_usage': {'prompt_tokens': 71, 'completion_tokens': 18, 'total_tokens': 89}})

# LLMResultからトークン使用量などを取得できます
result.llm_output['token_usage']
# -> {'prompt_tokens': 71, 'completion_tokens': 18, 'total_tokens': 89}
zerebomzerebom

とくに自然言語でアプリが連携する、Agents, tools辺りに可能性を感じたのでより深ぼっていく。

Agents

エージェントはLLMを使用して、どのアクションをどのような順序で行うかを決定します。

Agent Types

Agentには幾つかタイプがあるらしい.

(ChatGPTの解説)

  • zero-shot-react-description: このエージェントは、ツールの説明に基づいてどのツールを使用するかを判断するために、ReActフレームワークを使用します。任意の数のツールを提供できますが、各ツールには説明が必要です。
  • react-docstore: このエージェントは、docstoreとやり取りするために、ReActフレームワークを使用します。2つのツールが必要で、それらは「Search tool」と「Lookup tool」として正確に命名する必要があります。Search toolはドキュメントを検索し、Lookup toolは最近見つかったドキュメントで用語を検索する必要があります。このエージェントは、元のReAct論文(具体的には、Wikipediaの例)と同等です。
  • self-ask-with-search: このエージェントは、Intermediate Answerという名前の単一のツールを使用します。このツールは、事実ベースの質問に答えることができる必要があります。このエージェントは、元のself ask with search論文に相当し、Googleの検索APIがツールとして提供されたものです。
  • conversational-react-description: このエージェントは、会話の設定で使用するように設計されています。プロンプトは、エージェントが役立ち、会話ができるように設計されています。ReActフレームワークを使用して、どのツールを使用するかを決定し、以前の会話のやり取りを覚えるためにメモリを使用します。

ReActとかいろいろな専門用語がでてきた...

各用語の解説

  • ReAct framework:言語モデルを用いて、特定のタスクを自動化するためのフレームワークです。ReActは「React to Action」の略称で、与えられた状態に応じて最適なアクションを選択するという目的を持ちます。
  • docstore:文書データベースのことで、検索や参照などの操作を行うことができます。
  • Search tool:docstore内での文書の検索を行うためのツールです。
  • Lookup tool:検索された文書内での単語の参照を行うためのツールです。
  • Self ask with search:ユーザーの質問に対し、事実に基づいた回答を返すためのエージェントです。単一のツールのみを使用し、Google検索APIを利用した先行研究に基づいています。
  • Memory:エージェントが過去の会話内容を記憶するための機能で、前回の対話内容などの文脈を利用して応答を生成することができます。

各コンポーネントの関係性

この図は以下のような概念を表しています:

  1. ユーザーが入力を提供します。
  2. エージェントは入力を受け取り、LLMを利用して処理します。
  3. LLMはReActフレームワークを用いて適切なアクションを決定します。
  4. ツール選択のプロセスを経て、SearchツールとLookupツールが選択されます。
  5. これらのツールを利用して、適切な情報が得られるまで処理が行われます。
  6. Memoryコンポーネントを用いて、過去の対話履歴がエージェントに利用されます。
  7. 最終的な結果がユーザーに出力されます。

この図は、LangChain内で提供されるエージェントのうちのいくつかのコンポーネントとその関連性を示しています。具体的なエージェントタイプに応じて、これらの概念的なコンポーネントの使用方法や関係性が異なります。

zerebomzerebom

Agent TypesのうちのReActというのが重要そうだが、理解できてないので、ふかぼる。

ReAct

論文はこれ: https://arxiv.org/abs/2210.03629

Reactとは

ReAct(Recursive Attention Model)とは、LangChain内のエージェントの一つであり、与えられた入力に対して、複数の「ツール」と呼ばれる機能を実行することで、最終的な出力を生成する方法を学習するモデルのことを指します。このツールは、文字列から情報を検索する、Wikipediaの記事を検索するなど、様々なタスクに適応できます。ReActは、特定の状況において、どのツールを使用するべきかを決定するために、注意メカニズムを使用します。ReActモデルは、LangChain内のさまざまなタスクに使用されます。

コード例

このコードでは、LangChainのAgentを使用して、ReActロジックを実装する方法を紹介しています。

具体的には、Wikipediaを用いたDocstoreExplorerを用意し、ReAct_DOCSTOREタイプのAgentを初期化します。このタイプのAgentでは、提供されたツールのうち、SearchツールとLookupツールを使用して、質問に対する回答を生成します。

上の例では、著者のDavid Chanoffは、どの大統領の下でイギリスの大使を務めたアメリカ海軍の提督と共同作業を行いましたか?という質問に対して回答を生成しています。

具体的には、Searchツールを使用してDavid Chanoffに関する情報を取得し、William J. Croweが彼が協力した海軍提督であることを判断します。そして、Lookupツールを使用して、彼が大使を務めた大統領はBill Clintonであることが分かります。そして、その結果が"Bill Clinton"として返されます。

# 必要なライブラリをimport
from langchain import OpenAI, Wikipedia
from langchain.agents import initialize_agent, Tool
from langchain.agents import AgentType
from langchain.agents.react.base import DocstoreExplorer

# Wikipediaを用いてDocstoreExplorerを初期化
docstore = DocstoreExplorer(Wikipedia())

# 使用するツールのリストを作成
tools = [
    # 検索用のツール
    Tool(
        name="Search",
        func=docstore.search,
        description="useful for when you need to ask with search"
    ),
    # ルックアップ用のツール
    Tool(
        name="Lookup",
        func=docstore.lookup,
        description="useful for when you need to ask with lookup"
    )
]

# OpenAIのLLMを初期化
llm = OpenAI(temperature=0, model_name="text-davinci-002")

# Agentの初期化
# react-docstoreタイプのAgentを初期化する
react = initialize_agent(tools, llm, agent=AgentType.REACT_DOCSTORE, verbose=True)

# 質問を指定
question = "Author David Chanoff has collaborated with a U.S. Navy admiral who served as the ambassador to the United Kingdom under which President?"

# Agentを実行して回答を得る
react.run(question)

ReActとSelf-Ask-With Search Agentの違いは?

ReActとSelf-Ask-With-Search Agentは、どちらもLangChainにおけるAgentの一種で、質問に応じて適切なツールを使用することで答えを見つけるためのロジックを提供します。しかし、そのアプローチに違いがあります。

ReActは、ツールの説明を基にどのツールを使用するかを決定するためにReActフレームワークを使用します。これに対して、Self-Ask-With-Search Agentは、単一のツールを使用し、このツールを使って単純な質問に回答します。

Self-Ask-With-Search Agentは、特に質問応答のコンテキストで使用されることが多く、通常、単純な事実に関する質問に回答するために使用されます。ReActは、より高度なタスクに適しており、多くのツールの中から適切なものを選択することができます。また、ReActは、選択されたツールに応じて、追加の質問を生成することができます。

→ 一旦自分は、下記のような理解をした。

  • ReAct: 実装者が検索してほしいドキュメントを指定する
  • Self-Ask-With-Search Agent: LMMが検索エンジン等を使って、勝手に文献を探してくる

ReActとMRKLの違いは?

ReActとMRKLは、両方ともエージェントのアクションの決定に対してLLMを使用することに基づいているため、類似したコンセプトを持っています。しかし、ReActは特定の問題を解決するために設計された汎用的なエージェントのフレームワークであるのに対して、MRKLは特定のチェーンのフローを再現することに特化したエージェントです。MRKLは、複数のデータベースと数学の計算を組み合わせて複雑な質問に答えることができ、ReActはテキスト検索や質問応答などの一般的なタスクに対応しています。また、ReActは自然言語処理と外部のAPIを使用して質問に回答するための最適なアクションを選択するのに対して、MRKLはチェーンの進行に基づいて、次に適切なアクションを選択します。そのため、ReActはより一般的なタスクに対応していますが、MRKLはより複雑な質問に対応するために特化しています。

zerebomzerebom

これまでのAgentTypeごとの向いている用途と実用例

(ChatGPT先生の回答)
以下に、各AgentTypeの用途や実用例を示します。

  1. ZERO_SHOT_REACT_DESCRIPTION
  • 用途:あらかじめ準備した複数のツールから、それぞれのツールの説明文だけを元に、自動的にツールを選択するために使用される。
  • 実用例:異なるデータセットに対して対話型ツールを作成する場合、事前に用意されたツールの説明文だけを使用して、ツールを選択できる。
  1. REACT_DOCSTORE
  • 用途:ユーザーが入力した検索クエリに基づいて、文書の検索や文書内でのキーワード検索を行うために使用される。
  • 実用例:データベース内の文書を検索する、ウェブ上で情報を検索するなど。
  1. SELF_ASK_WITH_SEARCH
  • 用途:特定のドメインに関する質問に答えるために使用される。特定のツールに依存せず、単一の検索APIだけで質問に答える。
  • 実用例:自社製品に関する質問に答える、ウェブ上の質問に答えるなど。
  1. CONVERSATIONAL_REACT_DESCRIPTION
  • 用途:対話形式のユーザーインタフェースを使用して、検索、推論、計算などのタスクを実行するために使用される。
  • 実用例:チャットボット、バーチャルアシスタント、対話型のアプリケーションなど。
  1. MRKL
  • 用途:複数のドメインをカバーするタスクに対応するために使用される。
  • 実用例:知識問答、検索エンジン、FAQシステムなど。
zerebomzerebom

Indexes

自分のドキュメントをインデックスして引くためのモジュール

サブコンポーネント

  • Document Loaders: 様々なソースからドキュメントをロードする
  • Text Splitters: テキストを分割するための抽象化と実装について
  • VectorStores: VectorStoresとLangChainが提供する多数のインテグレーション
  • Retrievers: Retrievers
zerebomzerebom

Indexesのサブコンポーネントを深ぼってく

Document Loaders

非構造データをLangChainで扱える"documents"という形式に変換するサブコンポーネント。
HTML, Notion DB, PDF, Google Driveとか50種類くらいのソースに対応しているみたい。

ex. PDF

PyPDFとかのWrapperがあって、それを使って読み込めるみたい。vectorstoreなるものに渡して近似近傍探索ができるよう

from langchain.document_loaders import PyPDFLoader

loader = PyPDFLoader("example_data/layout-parser-paper.pdf")
pages = loader.load_and_split()
from langchain.vectorstores import FAISS
from langchain.embeddings.openai import OpenAIEmbeddings

faiss_index = FAISS.from_documents(pages, OpenAIEmbeddings())
docs = faiss_index.similarity_search("How will the community be engaged?", k=2)
for doc in docs:
    print(str(doc.metadata["page"]) + ":", doc.page_content)

Text Splitters

Textを意味のある単位に分けつつ、Splitしてくれるサブコンポーネント。確かにChatGPTも4000tokenとか上限あるので、こういうのは地味に有用そう。

tokenカウンターを渡してあげると、そのカウンターに基づいて、tokenを計算して、generatorとして返してくれる様子。
前後の文脈を保持するために、少しoverlapしてあげると良いみたい(chunk_overlapでその幅を指定できる)

# This is a long document we can split up.
with open('../../state_of_the_union.txt') as f:
    state_of_the_union = f.read()
from langchain.text_splitter import RecursiveCharacterTextSplitter
text_splitter = RecursiveCharacterTextSplitter(
    # Set a really small chunk size, just to show.
    chunk_size = 100,
    chunk_overlap  = 20,
    length_function = len,
)

ex. Python Code Text Splitter

例えばPython Code Text SplitterでPythonコードを分割すると、意味の単位で分割しつつ小さいDocmentにしてくれるみたい。

python_text = """
class Foo:

    def bar():
    
    
def foo():

def testing_func():

def bar():
"""
python_splitter = PythonCodeTextSplitter(chunk_size=30, chunk_overlap=0)
docs = python_splitter.create_documents([python_text])
[Document(page_content='Foo:\n\n    def bar():', lookup_str='', metadata={}, lookup_index=0),
 Document(page_content='foo():\n\ndef testing_func():', lookup_str='', metadata={}, lookup_index=0),
 Document(page_content='bar():', lookup_str='', metadata={}, lookup_index=0)]

Vectorstores

Embeddingを保存するやつ。保存先はFaissとかElasticSearchとか色々選べる。

raw_text → TextSplitter → Embedding → Vectorstoreという感じに処理が流れている。

from langchain.embeddings.openai import OpenAIEmbeddings
from langchain.text_splitter import CharacterTextSplitter
from langchain.vectorstores import Chroma
with open('../../state_of_the_union.txt') as f:
    state_of_the_union = f.read()
text_splitter = CharacterTextSplitter(chunk_size=1000, chunk_overlap=0)
texts = text_splitter.split_text(state_of_the_union)

embeddings = OpenAIEmbeddings()
docsearch = Chroma.from_texts(texts, embeddings)

query = "What did the president say about Ketanji Brown Jackson"
docs = docsearch.similarity_search(query)

文字列を直接vector storeに保存することもできる

docsearch.add_texts(["Ankush went to Princeton"])

vectorsearchを使うことでクエリに対する類似docをひっぱてきたり、類似度を計算することが可能
vectorsearch同士をmergeすることもできる。

Retrievers

言語モデルとdocmentを組み合わせるのを簡単にする汎用的なインターフェース。

ex. VectorStore Retriever

作ったVectorStoreを Retrieversとして扱うことも簡単にできる

from langchain.text_splitter import CharacterTextSplitter
from langchain.vectorstores import FAISS
from langchain.embeddings import OpenAIEmbeddings

documents = loader.load()
text_splitter = CharacterTextSplitter(chunk_size=1000, chunk_overlap=0)
texts = text_splitter.split_documents(documents)
embeddings = OpenAIEmbeddings()
db = FAISS.from_documents(texts, embeddings)
retriever = db.as_retriever()

ex. ChatGPT Plugin Retriever

ChatGPT Pluginに必要なjson形式を csv → LangChainのDocment → jsonとパースできる

# STEP 1: Load

# Load documents using LangChain's DocumentLoaders
# This is from https://langchain.readthedocs.io/en/latest/modules/document_loaders/examples/csv.html

from langchain.document_loaders.csv_loader import CSVLoader
loader = CSVLoader(file_path='../../document_loaders/examples/example_data/mlb_teams_2012.csv')
data = loader.load()


# STEP 2: Convert

# Convert Document to format expected by https://github.com/openai/chatgpt-retrieval-plugin
from typing import List
from langchain.docstore.document import Document
import json

def write_json(path: str, documents: List[Document])-> None:
    results = [{"text": doc.page_content} for doc in documents]
    with open(path, "w") as f:
        json.dump(results, f, indent=2)

write_json("foo.json", data)

# STEP 3: Use

# Ingest this as you would any other json file in https://github.com/openai/chatgpt-retrieval-plugin/tree/main/scripts/process_json
zerebomzerebom

Memory

会話の履歴を保存する。

LangChainでは、Memoryを、ユーザと言語モデルの相互作用を通じて、ユーザの状態を保持するものと考えている。
Memoryには幾つかタイプが有る。シーケンスから情報を抽出するやつが2つと、Chainに使うもの。

ChatMessageHistory

AIと人間のメッセージを保存、Featchできる軽量のwrapper.

from langchain.memory import ChatMessageHistory

history = ChatMessageHistory()
history.add_user_message("hi!")
history.add_ai_message("whats up?")

ConversationBufferMemory

ChatMessageHistoryのラッパー。文字列でメモリの履歴を抽出できたり、dictに書き出したりできる。

from langchain.llms import OpenAI
from langchain.chains import ConversationChain


llm = OpenAI(temperature=0)
conversation = ConversationChain(
    llm=llm, 
    verbose=True, 
    memory=ConversationBufferMemory()
)
conversation.predict(input="Hi there!")
conversation.predict(input="Tell me about yourself.")
dicts = messages_to_dict(history.messages)

いっぱい種類があって、難しい。。。幾つかピックアップ

ConversationBufferMemory:

  • メッセージを保管し、そのメッセージを変数として抽出することができる。
  • チャット履歴を返すことができる。

ConversationBufferWindowMemory:

  • 会話のインタラクションのリストを保管し、直近のK個のみを使用する。
  • 直近のインタラクションのスライディングウィンドウを保持するために役立つ。

EntityMemory:

  • 特定のエンティティに関する情報を覚えるために使用される。
  • LLMを使用してエンティティに関する情報を抽出し、時間とともにそれを構築する。

ConversationSummaryMemory:

  • 会話全体についてサマリーを作成するために使用される。
  • 会話から情報を縮約するのに役立つ。

ConversationSummaryBufferMemory:

  • 直近のインタラクションをメモリに保管するが、古いインタラクションを完全にフラッシュせず、サマリーにまとめることで、両方を使用する。
  • インタラクションをフラッシュするタイミングは、直近のトークン数で決定する。

ConversationTokenBufferMemory:

  • 直近のインタラクションをトークン数でフラッシュするタイプのメモリ。
zerebomzerebom

Chain

LangChainのコンポーネントをつなぎ合わせて実行できるようにする。

Chainが必要な理由

チェーンは複数のコンポーネントを組み合わせて1つのアプリケーションを作成するために必要です。例えば、ユーザー入力を取得し、PromptTemplateでフォーマットし、それをLLMに渡すチェーンを作成することができます。複数のチェーンを組み合わせるか、チェーンを他のコンポーネントと組み合わせてより複雑なチェーンを構築することができます。

LLMChainを使って、Template→llmを実行

from langchain.prompts import PromptTemplate
from langchain.llms import OpenAI
from langchain.chains import LLMChain

llm = OpenAI(temperature=0.9)
prompt = PromptTemplate(
    input_variables=["product"],
    template="What is a good name for a company that makes {product}?",
)



chain = LLMChain(llm=llm, prompt=prompt)

# チェーンを実行して、input_variableだけを指定して実行します。
response = chain.run("colorful socks")

chatmodelを使ってもできる

SequentialChain

SequentialChainでは複数のChainを接続できる。下の実装は、商品名と会社のキャッチフレーズを生成するためのチェーンを作成している。最初に、商品名を生成するためのLLMChainが作成され、次に、キャッチフレーズを生成するための別のLLMChainが作成されます。それらの2つのチェーンは、SimpleSequentialChainによって組み合わせられます。そして、入力変数として"colorful socks"が渡され、その結果として、会社のキャッチフレーズ "Spread smiles from your toes!"が生成されます。

from langchain.chains import SimpleSequentialChain
second_prompt = PromptTemplate(
    input_variables=["company_name"],
    template="Write a catchphrase for the following company: {company_name}",
)
chain_two = LLMChain(llm=llm, prompt=second_prompt)


overall_chain = SimpleSequentialChain(chains=[chain, chain_two], verbose=True)

# Run the chain specifying only the input variable for the first chain.
catchphrase = overall_chain.run("colorful socks")
print(catchphrase)
zerebomzerebom

Question Answering over Docs

ざっくりと全体感はつかめてきたので、実際にQ/A over Docsを実装してみようと思う。
手始めに自分のNotionに対してQ/Aできるようにしてみる。

検索のフローは下記の通りのよう


コード上これだけでdocment searchができるみたい。

from langchain.llms import OpenAI
from langchain.chains.question_answering import load_qa_chain
from langchain.embeddings import OpenAIEmbeddings
from langchain.document_loaders import TextLoader
from langchain.text_splitter import CharacterTextSplitter
from langchain.vectorstores import Chroma

with open("../neco.txt") as f:
    texts = f.read()

# Load text from file and split into documents
text_splitter = CharacterTextSplitter(separator="。", chunk_size=1000, chunk_overlap=0)
source_documents = text_splitter.create_documents([texts])

# Embded documents and create a retriever
db = Chroma.from_documents(source_documents, OpenAIEmbeddings())
retriever = db.as_retriever()

# Question Answering with Relavant Document
query = "物語に出てくる登場人物をそれぞれ詳細に箇条書きで説明して"
relvant_docs = retriever.get_relevant_documents(query)
chain = load_qa_chain(OpenAI(temperature=0), chain_type="map_reduce", verbose=True)
answer = chain.run(input_documents=relvant_docs, question=query)
)

やっていることは下記の通り

  • 文章をloadする
  • TextSplitterで文章を区切ったあとList[Docment]に変換
  • List[Docment]をListList[float]に変換し、VectorDB(Chroma)に保存する
  • queryを与えて関連Docmentを取得する
  • 関連Docmentを利用して、Q/Aに回答する

ここで使われているchain_type=map_reduceは関連文書が長すぎたときに、token数をoverするのを防ぐために、関連情報の抽出 → QAの二段階で質問応答をしている。

Source: https://github.com/hwchase17/langchain/blob/ec59e9d886904e27e6436a50a2cb755a621382d6/langchain/chains/qa_with_sources/map_reduce_prompt.py#L1

map_reduceのプロンプト
question_prompt_template = """Use the following portion of a long document to see if any of the text is relevant to answer the question. 
Return any relevant text verbatim.
{context}
Question: {question}
Relevant text, if any:"""
QUESTION_PROMPT = PromptTemplate(
    template=question_prompt_template, input_variables=["context", "question"]
)
combine_prompt_template = """Given the following extracted parts of a long document and a question, create a final answer with references ("SOURCES"). 
If you don't know the answer, just say that you don't know. Don't try to make up an answer.
ALWAYS return a "SOURCES" part in your answer.

QUESTION: Which state/country's law governs the interpretation of the contract?
=========
Content: This Agreement is governed by English law and the parties submit to the exclusive jurisdiction of the English courts in  relation to any dispute (contractual or non-contractual) concerning this Agreement save that either party may apply to any court for an  injunction or other relief to protect its Intellectual Property Rights.
Source: 28-pl
Content: No Waiver. Failure or delay in exercising any right or remedy under this Agreement shall not constitute a waiver of such (or any other)  right or remedy.\n\n11.7 Severability. The invalidity, illegality or unenforceability of any term (or part of a term) of this Agreement shall not affect the continuation  in force of the remainder of the term (if any) and this Agreement.\n\n11.8 No Agency. Except as expressly stated otherwise, nothing in this Agreement shall create an agency, partnership or joint venture of any  kind between the parties.\n\n11.9 No Third-Party Beneficiaries.
Source: 30-pl
Content: (b) if Google believes, in good faith, that the Distributor has violated or caused Google to violate any Anti-Bribery Laws (as  defined in Clause 8.5) or that such a violation is reasonably likely to occur,
Source: 4-pl
=========
FINAL ANSWER: This Agreement is governed by English law.
SOURCES: 28-pl

QUESTION: What did the president say about Michael Jackson?
=========
Content: Madam Speaker, Madam Vice President, our First Lady and Second Gentleman. Members of Congress and the Cabinet. Justices of the Supreme Court. My fellow Americans.  \n\nLast year COVID-19 kept us apart. This year we are finally together again. \n\nTonight, we meet as Democrats Republicans and Independents. But most importantly as Americans. \n\nWith a duty to one another to the American people to the Constitution. \n\nAnd with an unwavering resolve that freedom will always triumph over tyranny. \n\nSix days ago, Russia’s Vladimir Putin sought to shake the foundations of the free world thinking he could make it bend to his menacing ways. But he badly miscalculated. \n\nHe thought he could roll into Ukraine and the world would roll over. Instead he met a wall of strength he never imagined. \n\nHe met the Ukrainian people. \n\nFrom President Zelenskyy to every Ukrainian, their fearlessness, their courage, their determination, inspires the world. \n\nGroups of citizens blocking tanks with their bodies. Everyone from students to retirees teachers turned soldiers defending their homeland.
Source: 0-pl
Content: And we won’t stop. \n\nWe have lost so much to COVID-19. Time with one another. And worst of all, so much loss of life. \n\nLet’s use this moment to reset. Let’s stop looking at COVID-19 as a partisan dividing line and see it for what it is: A God-awful disease.  \n\nLet’s stop seeing each other as enemies, and start seeing each other for who we really are: Fellow Americans.  \n\nWe can’t change how divided we’ve been. But we can change how we move forward—on COVID-19 and other issues we must face together. \n\nI recently visited the New York City Police Department days after the funerals of Officer Wilbert Mora and his partner, Officer Jason Rivera. \n\nThey were responding to a 9-1-1 call when a man shot and killed them with a stolen gun. \n\nOfficer Mora was 27 years old. \n\nOfficer Rivera was 22. \n\nBoth Dominican Americans who’d grown up on the same streets they later chose to patrol as police officers. \n\nI spoke with their families and told them that we are forever in debt for their sacrifice, and we will carry on their mission to restore the trust and safety every community deserves.
Source: 24-pl
Content: And a proud Ukrainian people, who have known 30 years  of independence, have repeatedly shown that they will not tolerate anyone who tries to take their country backwards.  \n\nTo all Americans, I will be honest with you, as I’ve always promised. A Russian dictator, invading a foreign country, has costs around the world. \n\nAnd I’m taking robust action to make sure the pain of our sanctions  is targeted at Russia’s economy. And I will use every tool at our disposal to protect American businesses and consumers. \n\nTonight, I can announce that the United States has worked with 30 other countries to release 60 Million barrels of oil from reserves around the world.  \n\nAmerica will lead that effort, releasing 30 Million barrels from our own Strategic Petroleum Reserve. And we stand ready to do more if necessary, unified with our allies.  \n\nThese steps will help blunt gas prices here at home. And I know the news about what’s happening can seem alarming. \n\nBut I want you to know that we are going to be okay.
Source: 5-pl
Content: More support for patients and families. \n\nTo get there, I call on Congress to fund ARPA-H, the Advanced Research Projects Agency for Health. \n\nIt’s based on DARPA—the Defense Department project that led to the Internet, GPS, and so much more.  \n\nARPA-H will have a singular purpose—to drive breakthroughs in cancer, Alzheimer’s, diabetes, and more. \n\nA unity agenda for the nation. \n\nWe can do this. \n\nMy fellow Americans—tonight , we have gathered in a sacred space—the citadel of our democracy. \n\nIn this Capitol, generation after generation, Americans have debated great questions amid great strife, and have done great things. \n\nWe have fought for freedom, expanded liberty, defeated totalitarianism and terror. \n\nAnd built the strongest, freest, and most prosperous nation the world has ever known. \n\nNow is the hour. \n\nOur moment of responsibility. \n\nOur test of resolve and conscience, of history itself. \n\nIt is in this moment that our character is formed. Our purpose is found. Our future is forged. \n\nWell I know this nation.
Source: 34-pl
=========
FINAL ANSWER: The president did not mention Michael Jackson.
SOURCES:

QUESTION: {question}
=========
{summaries}
=========
FINAL ANSWER:"""