はじめに
言語モデルを使ってアプリケーション開発をしたいと思い立ち、LangChain なるものがあると知りました。
様々な方のサンプルコードを拝見させていただきましたが、理解が進まなかったためチュートリアルをこなすことにしました。
今回は基礎の第一回であるBuild a Simple LLM Application with LCELを実践した結果を記事にしています。
前提
公式チュートリアルの内容をこなすにあたり、自分のリソースに合わせてアレンジしている部分があります。
(例えば、環境変数は dotenv を利用する。モデルは AzureOpenAI リソースを使うなど)
langchain 中心の記事なので Azure のリソース作成といった操作には触れていません。
環境
- OS:macOS Sonoma14.3
- node:v21.6.1
- チャットモデル:AzureOpenAI
モジュール
- @langchain/core: 0.3.7,
- @langchain/openai: 0.3.5,
- dotenv: 16.4.5,
- langchain: 0.3.2,
- tsx: 4.19.1,
- typescript: 5.6.2
準備
LangSmith の登録
チェーン、エージェントで何が起きているのかを把握する手段として LangSmith が紹介されています。
代表的な活用事例として以下が挙げられます。
- ログ記録
- トークン数計測
- チェーンの実行過程の確認
未登録でも開発を行うことは可能ですが、今回は自分の理解のために登録していきます。
不要な方はスキップでも構いません。
LangSmithにアクセスしユーザ登録を行います。
初回にキーを作成するように誘導されるますが、キーの取得は作成した時しかできないようなので注意してください。
画面を閉じてしまった場合、Create API Key から再度作成できます。
langchain をインストール
TypeScript が実行できる環境に langchain をインストールしていきます。
今回は Azure OpenAI と接続したいので以下の組み合わせでインストールします。
npm i langchain @langchain/core @langchain/openai
また、ESM プロジェクトで TypeScript を使用している場合の推奨設定に従い、tsconfig.json に以下の記述を追加しました。
インストールガイド
{
...省略
"compilerOptions": {
...省略
"target": "ES2020",
"module": "nodenext",
}
}
Azure OpenAI キーの取得
AzureOpenAI リソースから以下の接続情報を取得し、環境変数ファイルに記述していきます。
必要な接続情報は以下の通りです。
- AzureOpenAI リソースのキー
- AzureOpenAI リソースのエンドポイント
- チャットモデルのデプロイ名
- チャットモデルの ver
1.2 はリソースのページ"キーとエンドポイント"から取得できます。
3.4 は Azure OpenAI Studio のチャットプレイグラウンドのコードを表示
で表示されるページから取得できます。
環境変数設定
LangSmith と AzureOpenAI の接続情報を環境変数ファイルに記述していきます。
LANGCHAIN_TRACING_V2="true"
LANGCHAIN_API_KEY="xxxxxxxxxxxxxxxxx"
AZURE_OPENAI_API_KEY ="xxxxxxxxxxxxxx"
AZURE_OPENAI_ENDPOINT="https://xxxxx.azure.com/"
AZURE_OPENAI_GPT_DEPLOYMENT_NAME="xxxxxxxx"
AZURE_OPENAI_GPT_API_VERSION="2024-05-01-preview"
- LANGCHAIN_TRACING_V2
- LANGCHAIN_API_KEY
- AZURE_OPENAI_API_KEY:キー
- AZURE_OPENAI_ENDPOINT:エンドポイント
- AZURE_OPENAI_GPT_DEPLOYMENT_NAME:チャットモデルのデプロイ名
- AZURE_OPENAI_GPT_API_VERSION:チャットモデルの ver
コーディング
準備ができたのでコーディングしていきます。
Chat モデルから応答を得る。
ChatOpenAI クラスを使い翻訳可能なインスタンスを作成します。
import "dotenv/config";
import { HumanMessage, SystemMessage } from "@langchain/core/messages";
import { ChatOpenAI } from "@langchain/openai";
const model = new ChatOpenAI({
azureOpenAIApiDeploymentName: process.env.AZURE_OPENAI_GPT_DEPLOYMENT_NAME,
azureOpenAIApiVersion: process.env.AZURE_OPENAI_GPT_API_VERSION,
modelName: "gpt-3.5-turb",
});
const messages = [
new SystemMessage("文章を日本語から英語に翻訳してください。"),
new HumanMessage("こんにちは!"),
];
(async () => {
const output = await model.invoke(messages);
console.log(output);
})();
プロンプトの通り「こんにちは」を英語に変換することができました。
AIMessage {
"id": "chatcmpl-AFDMxZHZ29CC7QNsfzDMNJkg2CS0E",
"content": "Hello!",
"additional_kwargs": {},
"response_metadata": {
"tokenUsage": {
"completionTokens": 2,
"promptTokens": 32,
"totalTokens": 34
},
"finish_reason": "stop"
},
"tool_calls": [],
"invalid_tool_calls": [],
"usage_metadata": {
"input_tokens": 32,
"output_tokens": 2,
"total_tokens": 34
}
}
この時点で LangSmith を確認すると実行時の詳細な情報が記録されていることがわかります。
モデルの実行結果のOutput
はAIMessage
の形式なので、プロンプトに対する文字列応答以外にも、関連するメタデータが含まれています。
プロンプトに対する文字列応答のみを扱いたい場面に使える、出力パーサーを使い出力をシンプルな文字列応答にしてみます。
import "dotenv/config";
import { HumanMessage, SystemMessage } from "@langchain/core/messages";
import { ChatOpenAI } from "@langchain/openai";
import { StringOutputParser } from "@langchain/core/output_parsers"; //追加
const model = new ChatOpenAI({
azureOpenAIApiDeploymentName: process.env.AZURE_OPENAI_GPT_DEPLOYMENT_NAME,
azureOpenAIApiVersion: process.env.AZURE_OPENAI_GPT_API_VERSION,
modelName: "gpt-3.5-turb",
});
const parser = new StringOutputParser(); //追加
const messages = [
new SystemMessage("文章を日本語から英語に翻訳してください。"),
new HumanMessage("こんにちは!"),
];
(async () => {
const output = await model.invoke(messages);
const parserOuput = await parser.invoke(output); //追加
console.log(parserOuput); //変更
})();
パーサーを使い出力を文字列応答のみにすることができました。
Hello!
LCEL を使ってコンポーネントを連結する
LangChain Expression Language (LCEL)はチェーンを簡単に組み合わせるツールです。
LCEL を使うことでストリーミングのサポート、処理の最適化/高速化、中間結果へのアクセスなどが可能になります。
公式ドキュメント
pipe
メソッドを使い、モデルとパーサーを接続していきます。
import "dotenv/config";
import { HumanMessage, SystemMessage } from "@langchain/core/messages";
import { ChatOpenAI } from "@langchain/openai";
import { StringOutputParser } from "@langchain/core/output_parsers";
const model = new ChatOpenAI({
azureOpenAIApiDeploymentName: process.env.AZURE_OPENAI_GPT_DEPLOYMENT_NAME,
azureOpenAIApiVersion: process.env.AZURE_OPENAI_GPT_API_VERSION,
modelName: "gpt-3.5-turb",
});
const parser = new StringOutputParser();
// pipeを使いmodelとparserを接続する
const stringOutputModel = model.pipe(parser);
const messages = [
new SystemMessage("文章を日本語から英語に翻訳してください。"),
new HumanMessage("こんにちは!"),
];
(async () => {
// チェーンしたモデルから出力を得る
const output = await stringOutputModel.invoke(messages);
console.log(output);
})();
チャットモデルとパーサーが接続され、シンプルな文字列応答が得られています。
Hello!
LangSmith で実行結果を確認すると 2 つの要素がチェーンできていることが確認できます。
チェーンを使用する前と比較すると待ち時間も改善されているようです。
PromptTemplate
PromptTemplate を利用することで、ユーザの入力を整形し、言語モデルに渡す準備が整ったデータに変換できます。
先ほどまでのコードではユーザ入力を直接言語モデルに渡していますが、今後は PromptTemplate を利用してユーザの入力を変数として受け取れるようにしてみます。
- lang: 翻訳先言語
- input: 翻訳するテキスト
import { ChatPromptTemplate } from "@langchain/core/prompts";
//システムメッセージのフォーマットを作成する
const promptTemplate = ChatPromptTemplate.fromMessages([
["system", "文章を日本語から{lang}に翻訳してください:"],
["user", "{input}"],
]);
(async () => {
const prompt = await promptTemplate.invoke({
lang: "中国語",
input: "こんにちは!",
});
console.log(prompt.toChatMessages());
})();
実行結果から、ユーザ変数がテンプレートに反映されていることがわかります。
[
SystemMessage {
"content": "文章を日本語から中国語に翻訳してください:",
"additional_kwargs": {},
"response_metadata": {}
},
HumanMessage {
"content": "こんにちは!",
"additional_kwargs": {},
"response_metadata": {}
}
]
さらにプロンプトを pipe で接続してみます。
import "dotenv/config";
import { ChatPromptTemplate } from "@langchain/core/prompts";
import { ChatOpenAI } from "@langchain/openai";
import { StringOutputParser } from "@langchain/core/output_parsers";
const promptTemplate = ChatPromptTemplate.fromMessages([
["system", "文章を日本語から{lang}に翻訳してください:"],
["user", "{input}"],
]);
const model = new ChatOpenAI({
azureOpenAIApiDeploymentName: process.env.AZURE_OPENAI_GPT_DEPLOYMENT_NAME,
azureOpenAIApiVersion: process.env.AZURE_OPENAI_GPT_API_VERSION,
modelName: "gpt-3.5-turb",
});
const parser = new StringOutputParser();
// 各要素を接続する
const translateModel = promptTemplate.pipe(model).pipe(parser);
(async () => {
const output = await translateModel.invoke({
lang: "中国語",
input: "こんにちは!",
});
console.log(output);
})();
入力通り、「こんにちは!」を「中国語」に変換することに成功しました。
你好!
ちなみに、LangSmith の結果は以下のようになります。
まとめ
このチュートリアルを行うことで以下の基本事項がわかりました。
- チャットモデルの使い方
- 出力パーサーの使い方
- LCEL を使ったチェーン化
- LangSmith を使った実行ログの監視
- プロンプトテンプレートの使い方