jiku log

データサイエンスの核心を掴む : 学びと発見の記録

「RAG・AIエージェント[実践]入門」を読む ~第4章 LangChainの基礎③LCEL/LangChainのRAGに関するコンポーネント~

はじめに

業務でLLMを活用していくためには,検索拡張生成(RAG)やAIエージェントといった技術が必要になってくると考えられる。これらの技術の基礎知識と実践経験を積むために,西見公宏・吉田真吾・大嶋勇樹 著「LangChainとLangGraphによるRAG・AIエージェント[実践]入門」を読むことにした。

本記事は,第4章 「LangChainの基礎」におけるLangChain Expression Language (LCEL),およびLangChainのRAGに関するコンポーネントに関する読書メモである。

  • 本書の紹介ページ

gihyo.jp

4.5 Chain ― LangChain Expression Language (LCEL)の概要

LLMアプリケーションでは,LLMへの入力・出力を含む処理を連鎖的につなげることが多い。このような処理の連鎖を実現するのがLangChainのChainである。

本節ではChainの記述方法であるLangChain Expression Language (LCEL) について説明している。

promptとmodelの連鎖

LCELの最もシンプルな例は,promptとmodelをつなぐことである。
はじめにprompt (ChatPromptTemplate)とmodel (ChatOpenAI)を準備する。

  • サンプルコード
from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI

prompt = ChatPromptTemplate.from_messages(
    [
        ("system", "ユーザーが入力した料理のレシピを考えてください。"),
        ("human", "{dish}"),
    ]
)

model = ChatOpenAI(model_name="gpt-4o-mini", temperature=0)

そして,これらを"|" (縦線)でつないだchainを作成する。

  • サンプルコード
chain = prompt | model

そして,このchainを実行することでLLMからの出力を得る。

  • サンプルコード
ai_message = chain.invoke({"dish": "カレー"})
print(ai_message.content)
  • 出力
カレーのレシピをご紹介します。シンプルで美味しい基本のカレーを作りましょう。

### 材料(4人分)
- 鶏肉(もも肉または胸肉): 400g
- 玉ねぎ: 2個
- にんじん: 1本
(以下略)

上記のように定義したchainから,関数invokeを呼び出すことで処理を実行している。
この変数chainの変数型を確認した。

  • サンプルコード
print(type(chain))
  • 出力
<class 'langchain_core.runnables.base.RunnableSequence'>

このように,演算子"|"でつないだ変数が,langchain_core.runnables.base.RunnableSequenceクラスの変数になっていることが確認できた。

また,出力であるai_messageの変数型も確認した。

  • サンプルコード
print(type(ai_message))
  • 出力
<class 'langchain_core.messages.ai.AIMessage'>

StrOutputParserを連鎖に追加

chainにStrOutputParserを追加すると,出力を文字列に変換することができる。

  • サンプルコード
from langchain_core.output_parsers import StrOutputParser

chain = prompt | model | StrOutputParser()
output = chain.invoke({"dish": "カレー"})
print(output)
  • 出力
カレーのレシピをご紹介します。シンプルで美味しい基本のカレーを作りましょう。

### 材料(4人分)
- 鶏肉(もも肉または胸肉): 400g
- 玉ねぎ: 2個
- にんじん: 1本
(以下略)
  • サンプルコード
print(type(output))
  • 出力
<class 'str'>

上記のように,print関数で出力した結果の見た目が同じであっても,変数型がlangchain_core.messages.ai.AIMessageからstrに変換されていることが確認できた。

4.6 LangChainのRAGに関するコンポーネント

本節では,LLMに含まれない情報を検索結果に含めるための手法であるRAG (Retrieval-Augmented Generation)について説明している。

RAG (Retrieval-Augmented Generation)

GPT-4oやGPT-4o miniなどのLLMでは,学習に使われているデータがある時点までのデータである,という制限がある。そのため,より新しい情報はLLMの解答には含まれない。また,プライベートな情報も含まれない。
解決策として,文脈(context)中にこれら含まれない情報を加えて回答を得るという方法があるが,トークン数の制限に抵触する可能性がある。

RAG (Retrieval-Augmented Generation)はこの解決策の1つである。外付けのベクターデータベースに,数値ベクトル化した文書を保管しておき,このデータベースから入力に近い文書を検索し,その検索結果をcontextに含める,という方法である。

LangChainのRAGに関するコンポーネントの概要

LangChainでRAGを使うための主要なコンポーネントには,以下の5つが挙げられる。

  • Document loader : データソースからドキュメントを読み込む。
    • テキストファイル用,ディレクトリ内のファイル用,Amazon S3バケット用など,データの種類に応じた,150種類のDocument loaderが提供されている。
  • Document transformer : ドキュメントになんらかの変換をかける。
    • ドキュメントをある程度の長さでチャンクに分割する際などに用いる。
  • Embedding model : ドキュメントをベクトル化する。
    • OpenAIのEmbedding APIなどを用いて,テキストをベクトル化する。
    • たとえば,text-embedding-3-smallモデルを用いると,1536次元のベクトルに変換される。
  • Vector store : ベクトル化したドキュメントを保管する。
    • Chromaなどのローカルで使用可能なVector storeが存在する。
  • Retriever : 入力のテキストと関連するドキュメントを検索する

LCELを使ったRAGのChainの実装

LCELを用いたRAGのChainを実装するには,まずpromptとmodelを準備する。

  • サンプルコード
from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI

prompt = ChatPromptTemplate.from_template('''\
以下の文脈だけを踏まえて質問に回答してください。

文脈: """
{context}
"""

質問: {question}
''')

model = ChatOpenAI(model_name="gpt-4o-mini", temperature=0)

文脈(context)と質問(question)は,あとから変数を代入できるように準備しておく。

その後,Chainを実装する。

  • サンプルコード
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnablePassthrough

chain = (
    {"context": retriever, "question": RunnablePassthrough()}
    | prompt
    | model
    | StrOutputParser()
)

output = chain.invoke(query)
print(output)

ここで,変数retriverは,ベクトル化された文書データの中から,質問文に内容が近しいものを選択したものである。この内容を文脈としてLLMに渡す。

  • 出力
はい、AWSのS3からデータを読み込むためのDocument loaderとして、`S3DirectoryLoader`と`S3FileLoader`があります。これらを使用することで、S3のディレクトリやファイルからデータを読み込むことができます。

まとめと感想

LangChainにおける,処理の流れを表現するための記法であるLCELと,利用シーンが多いRAGの実装方法について学んだ。

処理の流れを記述する場合,通常では「ある処理結果の出力を,次の処理の入力にする」といったプログラムを書くことになるが,LCELによってかなり簡素化できそうな印象があった。

またRAGについても,実装した際に精度が出なかった場合に,原因(関連文書をベクトルDBから取ってくる部分や,回答生成の部分,など)を究明していく必要がある。RAGの処理の流れを理解しておけば,RAGの精度が出なかった場合におけるトラブルシューティングが容易になると考えている。


本記事を最後まで読んでくださり,どうもありがとうございました。