0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

LangChain の実行

Posted at

はじめに

LangChain を使用して、次の機能を実行します。

  • プロンプトテンプレート
  • 会話履歴の扱い
  • データベース連携(RAG)
  • チャットボット

cheerio を利用してウェブページを読み込み、情報源としています。
本文では、LangChain の機能に含まれる会話スレッドとエージェントは行っていません。

Ubuntu に JavaScript (Node.js) をインストール

使用した環境: Windows 11、WSL2 の Ubuntu 22.04

sudo apt update
sudo apt install nodejs

バージョンの確認(.exit を入力すると node を終了します)

node
Welcome to Node.js v12.22.9.
Type ".help" for more information.
>.exit

デフォルトでインストールされるバージョンが古いため、nvm を使用して Node.js をバージョンアップします。

nvm インストール(リンク先の Install & Update Script のコマンドを実行します)
https://github.com/nvm-sh/nvm#install--update-script

nvm ls-remote
nvm install 20.18.1

バージョンの再確認

node
Welcome to Node.js v20.18.1.
Type ".help" for more information.
>.exit

 

LangChain のセットアップ

作業用フォルダを作成し、そのフォルダに移動します。

mkdir /home/(username)/20241214_langchain
cd /home/(username)/20241214_langchain

Node.js にライブラリを追加

npm install --save cheerio readline-sync

npm i langchain @langchain/core
npm i @langchain/community
npm i @langchain/openai

OpenAI API と LangChain の API キーを環境変数に登録

export OPENAI_API_KEY="..."

export LANGCHAIN_TRACING_V2="true"
export LANGCHAIN_API_KEY="..."

以下の1番簡単なサンプルプログラムが動作することを確認します。コンソールで「node ファイル名」を入力するとプログラムが実行されます。

node 01_SimpleLLM.mjs

Build a Simple LLM Application with LCEL
https://js.langchain.com/docs/tutorials/llm_chain

01_SimpleLLM.mjs
import { ChatOpenAI } from "@langchain/openai";
import { HumanMessage, SystemMessage } from "@langchain/core/messages";

const model = new ChatOpenAI({ model: "gpt-4o-mini" });

const messages = [
  new SystemMessage("Translate the following from English into Italian"),
  new HumanMessage("hi!"),
];
// SystemMessage:以下を英語からイタリア語に翻訳してください
// HumanMessage:hi!

const response = await model.invoke(messages);

console.log(response.content);
// AIMessage:Ciao!

 

LangChain の実行

下記の「02_LangChain.mjs」が、LangChain を実行するプログラムです。

OpenAI API を使用します。LangChain では Google Gemini、Microsoft Azure、Amazon AWS、Anthropic Claude などに簡単に切り替えることが可能です。

まず、質問への回答元となる情報源(コンテンツ)を、ウェブページから読込みます。gpt-4o-mini は2023年10月時点までの情報しか持っていないため(カットオフ)、最新の情報や優先的に扱いたい自社の情報を追加します。

プログラムは ChatGPT で使用するようなプロンプトを作成します。LangChain を使用しているので、

  • システムメッセージ
  • 情報源
  • 会話履歴
  • ユーザー質問

が OpenAI API に送信されます。ここで、会話履歴は(内部的に)毎回 OpenAI API に送信する必要があります。これを行わないと、引き続きの対話になりません。

RAG(データベース連携)は、ユーザーの質問に対して、事前に準備されたデータベースを検索し、見つかった情報(コンテンツ、記事)をユーザーの質問に追加したうえで ChatGPT に問合せる仕組みです。
「以下の記事を使用して質問に答えてください。答えが分からなければ『分からない』と伝えてください。」+「記事」+「ユーザー質問」
のような形になります。

プログラムは、クローリングまたはスクレイピングを行う cheerio を使用してウェブページから情報を取得します。その後、LangChain の機能を利用して、記事を1000文字ごとに区切りつつ、各区切りで200文字を重複させて分割し、ベクトルデータベースに登録します。英語だと良く動作するのかもしれませんが、日本語では章や節ごとに区切って意味のあるデータを持ったほうが良いかもしれません。

LangChain は、OpenAI API の Embedding 機能を使用して、与えられた文章や記事を1536個の数値パラメータに変換します(1536 次元ベクトル、text-embedding-3-small モデル、埋め込み)。変換されたデータは、PCメモリ上またはどこかのベクトルデータベースに保存します。検索時には、ベクトルの類似性を計算してデータを検索します。
参考:https://cookbook.openai.com/examples/question_answering_using_embeddings

データベース検索を行う際、ユーザーの質問に「それ」や「この」などの表現が含まれていると、クエリの結果として正しい答えが返って来ない可能性があります。LangChain の機能を使用すると、自動的に ChatGPT に問合せて会話履歴の中から、「それ」や「この」を具体的な内容に置き換えて、補完された質問を作成してくれます。
「会話履歴がなくても理解できる、独立した質問を作成してください。質問に答えてはいけません。」
のような形になります。回答はデータベース検索のために使用されるものであり、最終的に ChatGPT に送信される問合せ内容は「会話履歴」+「(元の)ユーザー質問」となります。

プログラムは while 文を使用して繰り返し処理を行い、ユーザーからの質問を受け付けて回答します。「exit」を入力すると終了します。
 

node 02_LangChain.mjs
02_LangChain.mjs
import { ChatOpenAI, OpenAIEmbeddings } from "@langchain/openai";
import { CheerioWebBaseLoader } from "@langchain/community/document_loaders/web/cheerio";
import { RecursiveCharacterTextSplitter } from "langchain/text_splitter";
import { MemoryVectorStore } from "langchain/vectorstores/memory";
import { ChatPromptTemplate, MessagesPlaceholder } from "@langchain/core/prompts";
import { createRetrievalChain } from "langchain/chains/retrieval";
import { createStuffDocumentsChain } from "langchain/chains/combine_documents";
import { createHistoryAwareRetriever } from "langchain/chains/history_aware_retriever";
import { HumanMessage, AIMessage } from "@langchain/core/messages";
import readlineSync from "readline-sync";


// OpenAI API を使用
const llm = new ChatOpenAI( { model: "gpt-4o-mini", temperature: 0 } ); // temperature=0 は質問が同じならばいつも同じ回答を返す


// コンテンツの読込み
const loader = new CheerioWebBaseLoader( "https://lilianweng.github.io/posts/2023-06-23-agent/" );
const docs = await loader.load();
const textSplitter = new RecursiveCharacterTextSplitter({
  chunkSize: 1000,
  chunkOverlap: 200
});
const splits = await textSplitter.splitDocuments(docs);
const vectorstore = await MemoryVectorStore.fromDocuments( splits, new OpenAIEmbeddings() );
const retriever = vectorstore.asRetriever();


// システムテンプレート
const systemPrompt =
  "You are an assistant for question-answering tasks. " +
  "Use the following pieces of retrieved context to answer " +
  "the question. If you don't know the answer, say that you " +
  "don't know. Use three sentences maximum and keep the " +
  "answer concise." +
  "\n\n" +
  "{context}";
// あなたは質問応答タスクのためのアシスタントです。以下の提供された
// コンテンツを使用して質問に答えてください。答えが分からない場合は、
// 分からないと伝えてください。最大で3文以内に簡潔に答えてください。


// RAG で使用するために、会話履歴を利用して最新の質問を補完するためのプロンプト
const contextualizeQSystemPrompt =
  "Given a chat history and the latest user question " +
  "which might reference context in the chat history, " +
  "formulate a standalone question which can be understood " +
  "without the chat history. Do NOT answer the question, " +
  "just reformulate it if needed and otherwise return it as is.";
// チャット履歴と、チャット履歴の文脈を参照している可能性がある最新の
// ユーザーの質問をもとに、チャット履歴なしでも理解できる独立した質問
// を作成してください。質問に答えてはいけません。必要に応じて再構成し、
// それ以外の場合はそのまま返してください。


const contextualizeQPrompt = ChatPromptTemplate.fromMessages([
  ["system", contextualizeQSystemPrompt],
  new MessagesPlaceholder("chat_history"),
  ["human", "{input}"],
]);

const historyAwareRetriever = await createHistoryAwareRetriever( { llm, retriever, rephrasePrompt: contextualizeQPrompt } );

const qaPrompt = ChatPromptTemplate.fromMessages([
  ["system", systemPrompt],
  new MessagesPlaceholder("chat_history"),
  ["human", "{input}"],
]);

const questionAnswerChain = await createStuffDocumentsChain( { llm, prompt: qaPrompt } );
const ragChain = await createRetrievalChain( { retriever: historyAwareRetriever, combineDocsChain: questionAnswerChain } );

let chatHistory = [];


// メインループ
(async () => {
  console.log("Welcome to the chatbot! Type 'exit' to quit.");

  while (true) {
    const userInput = readlineSync.question("\nYour question: ");

    if (userInput.toLowerCase() === "exit") {
      console.log("Thank you for using the chatbot. Have a great day!");
      break;
    }
  
    const output = await ragChain.invoke({
      input: userInput,
      chat_history: chatHistory,
    });
    
    chatHistory = chatHistory.concat([
      new HumanMessage(userInput),
      new AIMessage(output.answer),
    ]);

    console.log(output.answer);
  }
})();

// 入力例1:What is Task Decomposition?
// 入力例2:What are common ways of doing it?

 

実行結果

1215_04.png

 
実行後、何が行われたのかを LangChain のウェブページで確認できます。

https://www.langchain.com/
Home > Tracing projects > default > Runs

1215_11.png

 
1回目の問合せでは、コンテンツの内容を追加したシステムメッセージとユーザーの質問を使用して、OpenAI に問合せ(ChatOpenAI)を行います。コンテンツの検索(VectorStoreRetriever)では、ユーザーが入力した文章がそのまま使用されています。
 

1215_15.png

1215_16.png

1215_12.png

1215_13.png

1215_14.png

 
2回目の問合せでは、実際には OpenAI への問合せ(ChatOpenAI)が2回行われています。最初に、会話履歴を利用して最新の問合せを補完します。 そして、補完された文章を使用してコンテンツを検索(VectorStoreRetriever)します。
 

補完前:
What are common ways of doing it?

補完後:
What are some common methods for breaking down complex tasks into smaller, manageable steps?
 

1215_20.png

1215_21.png

1215_25.png

 
次に、コンテンツの内容を追加したシステムメッセージ(会話履歴を含む)とユーザーの質問を使用して、OpenAI に問合せを行います。前述の補完した文章は RAG のために使用されるものであり、OpenAI への問合せには補完前の文章がそのまま使用されています。また、会話履歴も送信されています。
 

1215_22.png

1215_23.png

1215_24.png

 

参考ページ:

Build a Chatbot
https://js.langchain.com/docs/tutorials/chatbot

Conversational RAG
https://js.langchain.com/docs/tutorials/qa_chat_history

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?