🌊

LangChainのContextual Compressionがどのようにコンテキストを圧縮しているのか

2023/04/23に公開

LangChainにContextual Compressionという抽象化が追加されました。概要は以下にあります。

https://blog.langchain.dev/improving-document-retrieval-with-contextual-compression/

Contextual Compressionは「インデックスするドキュメントのテキスト」と「プロンプトに含めるコンテキストとしてのテキスト」の性質が異る点に注目して、ドキュメント検索の後処理としてプロンプトに含めるテキストの内容に変換処理をかけて改善します。

前提知識

「LLMに質問の答えを生成してもらうためにコンテキストとして事前に検索したテキストをプロンプトに挿入する」という大枠の仕組みさえ知っていればokです。

最近読んだ以下のスライドが分かりやすかったです。

https://speakerdeck.com/ryoheiigushi/chatgpt-apinoembedding-kasutamaisuru-men

使うRetriever

ドキュメント取得のRetrieverにはChatGPT Retriever PluginsをLangChainでデバッグするで作ったRetriverを利用します。

「最近話題になった英語の勉強方法は?」で検索するとPineconeから以下3件のドキュメントを取得しました。

base_retriever = ChatGPTPluginRetriever(url="http://localhost:3333", bearer_token="XXX")
docs = base_retriever.get_relevant_documents("最近話題になった英語の勉強方法は?")
for doc in docs:
    print(f"""[{doc.metadata['metadata']['title'] or 'NO TITLE'}]({doc.metadata['metadata']['url']}) 
{doc.page_content}\n""")
[ChatGPTを使って英語の勉強をする - Qiita](https://qiita.com/s7353109/items/a5a2ed5f5dd0b41d0768) 
ChatGPTを使って英語の勉強をする - Qiita はじめに 最近話題のChatGPTですが、色々触っているうちに、 これ勉強に使えるんじゃね? と思ったので、まず英語について試してみたところ、意外とうまくいったのでここに書きたいと思います。 追:週間トレンドに乗りました!!ありがとうございます!! 英訳問題 出題 まず、 日本語を英語に訳す問題を、答えを表示させずに出題して下さい。 と入力すると、 上の写真のように問�

[話しやすい雰囲気を作るために意識していること - Qiita](https://qiita.com/hisamura333/items/befacfe24a43a059b038) 
ることを今回は言語化

[‎スピーク(Speak)- 英会話アプリ](https://apps.apple.com/jp/app/%E3%82%B9%E3%83%94%E3%83%BC%E3%82%AF-speak-%E8%8B%B1%E4%BC%9A%E8%A9%B1%E3%82%A2%E3%83%97%E3%83%AA/id1286609883) 
‎スピーク(Speak)- 英会話アプリ コンテンツが増えました! これまでは超初級と初級、それぞれだいたい1ヶ月分ずつでしたが、6日間コースで超初級、初級、中級が増え、AI講師機能も増えてました! オンライン英会話では反復がためらうんですが、これなら反復しやすいです! また、挨拶からその日のコンテンツの内容まで流れがあっていいです。シチュエーション別の暗記、というよりは会話をしている感じがあり、よくできた、

ContextualCompressionRetriever

ContextualCompressionRetrieverはCompressorを1つ指定して圧縮します。

EmbeddingsFilterをCompressorとして使う場合

  1. 検索で見付かった各ドキュメントの本文のEmbeddingsを作成
  2. 入力テキストと類似を比較してフィルタリングする

を追加で行う。

コードでは以下のようにEmbeddingsFilterにsimilarity_threshold=0.86を設定してContextualCompressionRetrieverでラップする。

これによって閾値を下回るドキュメントはフィルタされて結果に含まれなくなる。

base_compressor = EmbeddingsFilter(embeddings=embeddings, similarity_threshold=0.86)
retriever = ContextualCompressionRetriever(
    base_compressor=base_compressor, base_retriever=base_retriever
)

docs = retriever.get_relevant_documents("最近話題になった英語の勉強方法は?")
for doc in docs:
    print(f"""[{doc.metadata['metadata']['title'] or 'NO TITLE'}]({doc.metadata['metadata']['url']}) 
{doc.page_content}\n""")

検索結果が1件になりました。

> python contextual_compression.py

[ChatGPTを使って英語の勉強をする - Qiita](https://qiita.com/s7353109/items/a5a2ed5f5dd0b41d0768) 
ChatGPTを使って英語の勉強をする - Qiita はじめに 最近話題のChatGPTですが、色々触っているうちに、 これ勉強に使えるんじゃね? と思ったので、まず英語について試してみたところ、意外とうまくいったのでここに書きたいと思います。 追:週間トレンドに乗りました!!ありがとうございます!! 英訳問題 出題 まず、 日本語を英語に訳す問題を、答えを表示させずに出題して下さい。 と入力すると、 上の写真のように問

EmbeddingsFilter実装

EmbeddingsFilterの実装はnumpyを使ったシンプルなembeddingsの比較です。

https://github.com/hwchase17/langchain/blob/6200a2a00e5d54cece5b754d20f6c3bd423ef71f/langchain/retrievers/document_compressors/embeddings_filter.py#L46-L64

DocumentCompressorPipeline

DocumentCompressorPipelineは任意の個数のドキュメント変換処理(transformers)を重ねがけするためのクラスです。

以下では

  1. CharacterTextSplitterでドキュメントを分割して増やし(デフォルトは改行コード)
  2. EmbeddingsRedundantFilterでクエリとの一致が高すぎる冗長な(Redundant)ドキュメントをフィルタして減らし
  3. 前項で使ったEmbeddingsFilterでさらに減らして絞り込みます

CharacterTextSplitterの分割が入ることで、ドキュメントの内部も変化します。

embeddings = OpenAIEmbeddings()
base_retriever = ChatGPTPluginRetriever(url="http://localhost:3333", bearer_token="XXX")

splitter = CharacterTextSplitter(chunk_size=20, chunk_overlap=0, separator="。")
redundant_filter = EmbeddingsRedundantFilter(embeddings=embeddings, similarity_threshold=0.85)
relevant_filter = EmbeddingsFilter(embeddings=embeddings, similarity_threshold=0.8)
pipeline_filter = DocumentCompressorPipeline(
    transformers=[splitter, redundant_filter, relevant_filter]
)

input = "最近話題になった英語の勉強方法は?"
docs = base_retriever.get_relevant_documents(input)

query = '英語' # 検索クエリと圧縮クエリは分離できるという例
filtered_docs = pipeline_filter.compress_documents(docs, query)
for doc in filtered_docs:
    print(f"""[{doc.metadata['metadata']['title'] or 'NO TITLE'}]({doc.metadata['metadata']['url']}) 
{doc.page_content}\n""")

ドキュメントが1つになりました。くわえて本文の内容が短かく切り詰められていることが分かります。

> python contextual_compression_pipeline.py

[ChatGPTを使って英語の勉強をする - Qiita](https://qiita.com/s7353109/items/a5a2ed5f5dd0b41d0768) 
ChatGPTを使って英語の勉強をする - Qiita はじめに 最近話題のChatGPTですが、色々触っているうちに、 これ勉強に使えるんじゃね? と思ったので、まず英語について試してみたところ、意外とうまくいったのでここに書きたいと思います

EmbeddingsRedundantFilter実装

EmbeddingsRedundantFilterは実装はEmbeddingsFilterと同じくnumpyを逆方向に使ったものです。

https://github.com/hwchase17/langchain/blob/a6664be79ca51031edf970aecf1f02bf99c6c140/langchain/document_transformers.py#L36-L49

Discussion