VOOZH about

URL: https://zenn.dev/hayato94087/articles/688f78c6b3ead6

⇱ LangChain で Fallbacks(Node.js)


JavaScript
TypeScript
OpenAI
LangChain
tech

はじめに

LLM を利用する場合、レートリミット、API のダウンタイムなど API の利用にまつわる問題が発生します。この記事では、問題が発生した際に対処する方法を記述します。具体的には以下の記事を参考に記述します。

https://js.langchain.com/v0.2/docs/how_to/fallbacks/

TypeScript / JavaScript での GitHub リポジトリーを公開している実装例はすくないので記事化しました。作業リポジトリはこちらです。
 
https://github.com/hayato94087/langchain-fallbacks-sample

LangChain での実装例を以下の記事で紹介しています。

LangChain とは

LangChain は、大規模言語モデル(LLM)を活用したアプリケーションの開発を支援するフレームワークです。

https://js.langchain.com/v0.2/docs/introduction/

コンセプト

この記事では以下の概要について理解します。

runnable.withFallbacks()

.withFallbacks() を指定して、フォールバックする場合の処理を設定します。

import { ChatOpenAI } from "@langchain/openai";

// モデル名が誤っている対話モデル
const modelWithBadModelName = new ChatOpenAI({
 model: "bad-model",
});

// モデル名が正しい対話モデル
const modelWithCorrectModelName = new ChatOpenAI({
 model: "gpt-3.5-turbo",
});

// フォールバックを追加
const modelWithFallback = modelWithBadModelName.withFallbacks({
 fallbacks: [modelWithCorrectModelName],
});

// モデルを呼び出す
const result = await modelWithFallback.invoke("あなたの名前は?");
console.log(result);
AIMessage {
 lc_serializable: true,
 lc_kwargs: {
 content: '私はボブです。あなたの名前は何ですか?',
 tool_calls: [],
 invalid_tool_calls: [],
 additional_kwargs: { function_call: undefined, tool_calls: undefined },
 response_metadata: {}
 },
 lc_namespace: [ 'langchain_core', 'messages' ],
 content: '私はボブです。あなたの名前は何ですか?',
 name: undefined,
 additional_kwargs: { function_call: undefined, tool_calls: undefined },
 response_metadata: {
 tokenUsage: { completionTokens: 18, promptTokens: 15, totalTokens: 33 },
 finish_reason: 'stop'
 },
 tool_calls: [],
 invalid_tool_calls: [],
 usage_metadata: { input_tokens: 15, output_tokens: 18, total_tokens: 33 }
}

作業プロジェクトの準備

TypeScript の簡易プロジェクトを作成します。

LangChain をインストール

LangChain をインストールします。

$ pnpm add langchain @langchain/core

言語モデルの選択

LangChain は、多くの異なる言語モデルをサポートしており、それらを自由に選んで使用できます。

例えば、以下のような言語モデルを選択できます。

  • OpenAI
  • Anthropic
  • FireworksAI
  • MistralAI
  • Groq
  • VertexAI

ここでは OpenAI を利用します。OpenAI を LangChain で利用するためのパッケージをインストールします。

$ pnpm add @langchain/openai

OpenAI API キーを取得

OpenAI API キーの取得方法はこちらを参照してください。

https://zenn.dev/hayato94087/articles/85378e1f7bc0e5#openai-の-apiキーの取得

環境変数の設定

環境変数に OpenAI キーを追加します。<your-api-key> に自身の API キーを設定してください。

$ touch .env
.env
# OPENAI_API_KEY は OpenAI の API キーです。
OPENAI_API_KEY='<your-api-key>'

Node.js で環境変数を利用するために dotenv をインストールします。

$ pnpm i -D dotenv

OpenAIを利用

まず、シンプルに OpenAI の LLM を使ってみます。

コードを作成

コードを作成します。

$ touch demo01.ts
demo01.ts
import { ChatOpenAI } from "@langchain/openai";
import 'dotenv/config'

const model = new ChatOpenAI({
 model: "gpt-3.5-turbo",
 temperature: 0
});

const result = await model.invoke("猫についてジョークを言ってください");
console.log(result)

ローカルで実行します。

$ pnpm vite-node demo01.ts

コードの解説

OpenAI を利用します。

import { ChatOpenAI } from "@langchain/openai";

gpt-3.5-turbo のモデルを選択します。temperature は 0 に設定します。temperature が低いほど、モデルの出力はより予測可能になります。

const model = new ChatOpenAI({
 model: "gpt-3.5-turbo",
 temperature: 0
});

.invoke() を利用して、モデルにテキストを送信します。

const result = await model.invoke("猫についてジョークを言ってください");
console.log(result)
AIMessage {
 lc_serializable: true,
 lc_kwargs: {
 content: 'Q: 猫がパソコンを使うときに使うプログラミング言語は何でしょうか?\nA: プロクラスミング言語!',
 tool_calls: [],
 invalid_tool_calls: [],
 additional_kwargs: { function_call: undefined, tool_calls: undefined },
 response_metadata: {}
 },
 lc_namespace: [ 'langchain_core', 'messages' ],
 content: 'Q: 猫がパソコンを使うときに使うプログラミング言語は何でしょうか?\nA: プロクラスミング言語!',
 name: undefined,
 additional_kwargs: { function_call: undefined, tool_calls: undefined },
 response_metadata: {
 tokenUsage: { completionTokens: 52, promptTokens: 22, totalTokens: 74 },
 finish_reason: 'stop'
 },
 tool_calls: [],
 invalid_tool_calls: [],
 usage_metadata: { input_tokens: 22, output_tokens: 52, total_tokens: 74 }
}

LLM API エラーを処理

ここでは、LLM API エラーを処理する方法を記述します。具体的には、モデル名を誤って記述しエラーを人為的に引き起こします。

コードの作成

$ touch demo02.ts
demo02.ts
import { ChatOpenAI } from "@langchain/openai";
import 'dotenv/config'

// モデル名が誤っている対話モデル
const modelWithBadModelName = new ChatOpenAI({
 model: "bad-model",
 temperature: 0,
 maxRetries: 0,
});

// モデル名が正しい対話モデル
const modelWithCorrectModelName = new ChatOpenAI({
 model: "gpt-3.5-turbo",
 temperature: 0,
});

// フォールバックを追加
const modelWithFallback = modelWithBadModelName.withFallbacks({
 fallbacks: [modelWithCorrectModelName],
});

// モデルを呼び出す
const result = await modelWithFallback.invoke("あなたの名前は?");
console.log(result);

ローカルで実行します。

$ pnpm vite-node demo02.ts

コードの解説

modelWithBadModelName はモデル名が誤っている対話モデルです。modelWithCorrectModelName はモデル名が正しい対話モデルです。

// モデル名が誤っている対話モデル
const modelWithBadModelName = new ChatOpenAI({
 model: "bad-model",
 temperature: 0,
 maxRetries: 0,
});

// モデル名が正しい対話モデル
const modelWithCorrectModelName = new ChatOpenAI({
 model: "gpt-3.5-turbo",
 temperature: 0,
});

.withFallbacks() を利用して、フォールバックを追加します。

// フォールバックを追加
const modelWithFallback = modelWithBadModelName.withFallbacks({
 fallbacks: [modelWithCorrectModelName],
});

.invoke() を利用して、モデルにテキストを送信します。すると、フォールバックが発生し、modelWithBadModelName から modelWithCorrectModelName に切り替わります。

// モデルを呼び出す
const result = await modelWithFallback.invoke("あなたの名前は?");
console.log(result);
AIMessage {
 lc_serializable: true,
 lc_kwargs: {
 content: '私はボブです。あなたの名前は何ですか?',
 tool_calls: [],
 invalid_tool_calls: [],
 additional_kwargs: { function_call: undefined, tool_calls: undefined },
 response_metadata: {}
 },
 lc_namespace: [ 'langchain_core', 'messages' ],
 content: '私はボブです。あなたの名前は何ですか?',
 name: undefined,
 additional_kwargs: { function_call: undefined, tool_calls: undefined },
 response_metadata: {
 tokenUsage: { completionTokens: 18, promptTokens: 15, totalTokens: 33 },
 finish_reason: 'stop'
 },
 tool_calls: [],
 invalid_tool_calls: [],
 usage_metadata: { input_tokens: 15, output_tokens: 18, total_tokens: 33 }
}

フォールバックを RunnableSequences で利用

2 つのチェーンを作成し、問題があった場合はフォールバックし別のチェーンに切り替えます。RunnableSequences がチェーンだと考えれば理解しやすいです。

コードを作成

$ touch demo03.ts
demo03.ts
import { ChatOpenAI } from "@langchain/openai";
import 'dotenv/config'
import { ChatPromptTemplate, PromptTemplate } from "@langchain/core/prompts";
import { StringOutputParser } from "@langchain/core/output_parsers";

// テキスト出力のパーサー
const outputParser = new StringOutputParser();



// ジョークを生成するプロンプトテンプレート
const promptTemplateWithBadModelName = PromptTemplate.fromTemplate("{topic}についてジョークを言ってください");

// モデル名が誤っている対話モデル
const modelWithBadModelName = new ChatOpenAI({
 model: "bad-model",
 temperature: 0,
 maxRetries: 0,
});

// Chain を作成
const badChain = promptTemplateWithBadModelName.pipe(modelWithBadModelName).pipe(outputParser);



// ポエムを生成するプロンプトテンプレート
const promptTemplateWithCorrectModelName = PromptTemplate.fromTemplate("{topic}についてポエムを言ってください");

// モデル名が正しい対話モデル
const modelWithCorrectModelName = new ChatOpenAI({
 model: "gpt-3.5-turbo",
 temperature: 0,
});

// Chain を作成
const goodChain = promptTemplateWithCorrectModelName.pipe(modelWithCorrectModelName).pipe(outputParser);



// フォールバックを追加
const chain = badChain.withFallbacks({
 fallbacks: [goodChain],
});



// モデルを呼び出す
const result = await chain.invoke({
 topic: "龍",
});
console.log(result);

ローカルで実行します。

$ pnpm vite-node demo03.ts

コードの解説

パーサーを設定します。

// テキスト出力のパーサー
const outputParser = new StringOutputParser();

ジョークを生成するプロンプトを作成し、モデル名が誤っている対話モデルを設定し、チェーンを作成します。

// ジョークを生成するプロンプトテンプレート
const promptTemplateWithBadModelName = PromptTemplate.fromTemplate("{topic}についてジョークを言ってください");

// モデル名が誤っている対話モデル
const modelWithBadModelName = new ChatOpenAI({
 model: "bad-model",
 temperature: 0,
 maxRetries: 0,
});

// Chain を作成
const badChain = promptTemplateWithBadModelName.pipe(modelWithBadModelName).pipe(outputParser);

ポエムを生成するプロンプトを作成し、モデル名が正しい対話モデルを設定し、チェーンを作成します。

// ポエムを生成するプロンプトテンプレート
const promptTemplateWithCorrectModelName = PromptTemplate.fromTemplate("{topic}についてポエムを言ってください");

// モデル名が正しい対話モデル
const modelWithCorrectModelName = new ChatOpenAI({
 model: "gpt-3.5-turbo",
 temperature: 0,
});

// Chain を作成
const goodChain = promptTemplateWithCorrectModelName.pipe(modelWithCorrectModelName).pipe(outputParser);

フォールバックを追加したチェーンを作成します。

// フォールバックを追加
const chain = badChain.withFallbacks({
 fallbacks: [goodChain],
});

.invoke() を利用して、モデルにテキストを送信します。ジョークの生成はモデル名が誤っているためポエムが生成されます。

// モデルを呼び出す
const result = await chain.invoke({
 topic: "龍",
});
console.log(result);
龍よ、その煌めく鱗で空を舞い
力強く羽ばたく姿は神々しき存在
古の時代から語り継がれし伝説の生き物
我々に勇気と力を与える存在

荒ぶる火を操り、大地を揺るがす
その眼差しは深淵の底を覗くよう
神秘的な力を秘めし龍よ
我ら人間には理解できぬ存在

威厳ある姿に心奪われ
その存在を讃える我ら
龍よ、永遠に輝き続けよ
我らの心に永遠に生き続ける存在

Context Window を超えた場合の処理

LLM の 1 つの制限は、一度に処理できるトークン数が制限されていることです。この制限を超えるとエラーが発生します。この制限を超える場合、入力を分割して処理する方法があります。

コードの作成

$ touch demo04.ts

ローカルで実行します。

$ pnpm vite-node demo04.ts

コードの解説

Context Window が長いモデルと短いモデルを作成します。

// Context Window が短いモデル
const shorterLlm = new ChatOpenAI({
 model: "gpt-3.5-turbo-0613",
 maxRetries: 0,
});

// Context Window が長いモデル
const longerLlm = new ChatOpenAI({
 model: "gpt-3.5-turbo-16k",
});

Context Window が短いモデルでエラーが出るように長い文章を生成します。

Context Window が短いモデルでエラーが出るか確認します。Context Window が短いモデルだとエラーがしっかり出ます。

// Context Window が短いモデルでエラーが出るか確認
try {
 await shorterLlm.invoke(input);
} catch (e) {
 // Length error
 console.log(e);
}
BadRequestError: 400 This model's maximum context length is 4097 tokens. However, your messages resulted in 8103 tokens. Please reduce the length of the messages.
 at Function.generate (file:///Users/hayato94087/Private/langchain-fallbacks-sample/node_modules/.pnpm/openai@4.47.3/node_modules/openai/src/error.ts:70:14)
 at OpenAI.makeStatusError (file:///Users/hayato94087/Private/langchain-fallbacks-sample/node_modules/.pnpm/openai@4.47.3/node_modules/openai/src/core.ts:383:21)
 at OpenAI.makeRequest (file:///Users/hayato94087/Private/langchain-fallbacks-sample/node_modules/.pnpm/openai@4.47.3/node_modules/openai/src/core.ts:446:24)
 at processTicksAndRejections (node:internal/process/task_queues:95:5)
 at file:///Users/hayato94087/Private/langchain-fallbacks-sample/node_modules/.pnpm/@langchain+openai@0.1.1_langchain@0.2.4_fast-xml-parser@4.4.0_openai@4.47.3_/node_modules/@langchain/openai/dist/chat_models.js:795:29
 at RetryOperation._fn (/Users/hayato94087/Private/langchain-fallbacks-sample/node_modules/.pnpm/p-retry@4.6.2/node_modules/p-retry/index.js:50:12) {
 status: 400,
 headers: {
 'alt-svc': 'h3=":443"; ma=86400',
 'cf-cache-status': 'DYNAMIC',
 'cf-ray': '88d56aa8b934263e-NRT',
 connection: 'keep-alive',
 'content-length': '280',
 'content-type': 'application/json',
 date: 'Sun, 02 Jun 2024 06:34:16 GMT',
 'openai-organization': 'repe',
 'openai-processing-ms': '28',
 'openai-version': '2020-10-01',
 server: 'cloudflare',
 'set-cookie': '__cf_bm=zP1jIpR9lgbaYLy9LGraSaqB2761QCvr3UlLCfugK48-1717310056-1.0.1.1-EG1iMBD1l5Y_D3s1lkMSLLO7OAz5nDaOgC1RFt2sO_NRVbqMW3KynUflv2Z2rIqPmHVnRBVn7aTbZJUc9Ho4Dw; path=/; expires=Sun, 02-Jun-24 07:04:16 GMT; domain=.api.openai.com; HttpOnly; Secure; SameSite=None, _cfuvid=Tmk6A_8cRy1shtarCG5E7lc81irBfev5OW0lNUVFKiI-1717310056279-0.0.1.1-604800000; path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None',
 'strict-transport-security': 'max-age=15724800; includeSubDomains',
 'x-ratelimit-limit-requests': '10000',
 'x-ratelimit-limit-tokens': '1000000',
 'x-ratelimit-remaining-requests': '9999',
 'x-ratelimit-remaining-tokens': '994446',
 'x-ratelimit-reset-requests': '6ms',
 'x-ratelimit-reset-tokens': '333ms',
 'x-request-id': 'req_f280e51ffc0baed32840d2beb47a60fa'
 },
 request_id: 'req_f280e51ffc0baed32840d2beb47a60fa',
 error: {
 message: "This model's maximum context length is 4097 tokens. However, your messages resulted in 8103 tokens. Please reduce the length of the messages.",
 type: 'invalid_request_error',
 param: 'messages',
 code: 'context_length_exceeded'
 },
 code: 'context_length_exceeded',
 param: 'messages',
 type: 'invalid_request_error',
 attemptNumber: 1,
 retriesLeft: 0
}

.invoke() すると Context Window が短いモデルから Context Window が長いモデルにフォールバックし問題なく処理されます。

// 実際にモデルに投げてみる
const result = await modelWithFallback.invoke(input);
console.log(result);
AIMessage {
 lc_serializable: true,
 lc_kwargs: {
 content: 'タロウの物語は、親切心と勇気がどれほど重要であるかを示すものでした。彼の物語は、私たち一人一人に、他人に対して親切であり、困難に立ち向かう勇気を持つことの大切さを教えてくれます。タロウのように生きることで、私たちもまた、自分の周りの世界を少しでも良くすることができるのです。\n' +
 '\n' +
 'タロウの物語は、永遠に語り継がれ、彼の親切心と勇気は、未来の世代にも希望と励ましを与え続けるでしょう。彼の人生は、私たちにとっても大きな教訓となり、私たちもまた、タロウのように生きることを目指すことで、自分自身と周りの人々に幸せをもたらすことができるのです。',
 tool_calls: [],
 invalid_tool_calls: [],
 additional_kwargs: { function_call: undefined, tool_calls: undefined },
 response_metadata: {}
 },
 lc_namespace: [ 'langchain_core', 'messages' ],
 content: 'タロウの物語は、親切心と勇気がどれほど重要であるかを示すものでした。彼の物語は、私たち一人一人に、他人に対して親切であり、困難に立ち向かう勇気を持つことの大切さを教えてくれます。タロウのように生きることで、私たちもまた、自分の周りの世界を少しでも良くすることができるのです。\n' +
 '\n' +
 'タロウの物語は、永遠に語り継がれ、彼の親切心と勇気は、未来の世代にも希望と励ましを与え続けるでしょう。彼の人生は、私たちにとっても大きな教訓となり、私たちもまた、タロウのように生きることを目指すことで、自分自身と周りの人々に幸せをもたらすことができるのです。',
 name: undefined,
 additional_kwargs: { function_call: undefined, tool_calls: undefined },
 response_metadata: {
 tokenUsage: { completionTokens: 293, promptTokens: 8103, totalTokens: 8396 },
 finish_reason: 'stop'
 },
 tool_calls: [],
 invalid_tool_calls: [],
 usage_metadata: { input_tokens: 8103, output_tokens: 293, total_tokens: 8396 }
}

さいごに

この記事では、LangChain で問題が発生した際に対処する方法を紹介しました。

作業リポジトリ

こちらが作業リポジトリです。

https://github.com/hayato94087/langchain-fallbacks-sample

Discussion

👁 Image