TypeScript で LangChain の最初の一歩
このエントリーは 3-shake Advent Calendar 2023 の10日目の記事です。
今年は Python をガッツリ触ったり、 LLM などの方面に手を出してきており、新しいことにまみれております。
その中で LLM のシステム作るんだったら Python だろ?っていう中で TypeScript でもちゃんとできるよーっていうことで紹介していきたいと思います。 私が、あんまり Python でアプリ作っていくのが好きじゃないのもあります
もちろん、 Python よりも TypeScript のほうが機能が少なめではありますので、そのあたりは、目をつぶっております。
今回の記事の想定する読み手としては、以下の人たちです。
- LLM 導入しろとか言われていんだけどよくわからん、何から手を付けりゃ良いんじゃという人
- TypeScript が好きな人
- LangChain どこから始めれば良いんかわからん人
LangChainについて
フレームワークというからには、フレームワークの作法に沿って作ればいいだけです。
「LLM についてよくわからん、使ってみて概要を図りたい」という人は、このフレームワークを利用することから入るのも一つの手かと思います。
LangChainを使う意義
私の個人的解釈ですが、今現在 LLM のモデルは色々出てきており、数年前のフロントエンド開発の時代よりもより速い速度で進化しているようにも見えます。それに追従していくのはとても大変です。
そのような時代において、 LLM を使ったアプリケーションを作るのであれば、 LangChain のようなフレームワークでアプリケーションのロジックを実装して、モデルの差を中和するのが一つの意義だと思っております。
使い方(準備)
今回ただの紹介でもあるので簡単に、単純な呼び出しと Prompt
について書きたいと思います。
とはいえ、コレができるだけでも簡単なチャットシステムの元は作れるようになります。
基本インストールは以下のように、 langchain
を入れるだけです。その他、私自身、API_KEYなどを環境変数を通して利用したいため、 envalid
を利用します。
pnpm i langchain envalid
また TypeScriptを利用するので、以下が入りますね
pnpm i -D typescript @types/node
tsconfigを作らないと行けないので以下を実行
npx tsc --init
コレでアプリケーションに最低限必要な部分はできました。
はじめの一歩
実際にLLMを呼ぶのは以下のようになります。以下の例だと Hello wolrd
と投げて、それをコンソールに出力しています。
import { ChatOpenAI } from 'langchain/chat_models/openai'
import { cleanEnv, str } from 'envalid'
const env = cleanEnv(process.env, {
OPENAI_API_KEY: str({ example: 'sk-xxx' }),
OPENAI_ORGANIZATION_ID: str({ example: 'org-xxx' }),
})
const llm = new ChatOpenAI({
modelName: 'gpt-3.5-turbo-1106',
temperature: 0.9,
configuration: {
organization: env.OPENAI_ORGANIZATION_ID,
apiKey: env.OPENAI_API_KEY,
},
verbose: true,
maxRetries: 3
})
llm.invoke('Hello world!').then(console.log)
実行は以下のとおりです
pnpx ts-node main.ts
こんな感じで、 content
で返却されているのがわかると思います
AIMessage {
lc_serializable: true,
lc_kwargs: {
content: 'Hello there! How can I assist you today?',
additional_kwargs: { function_call: undefined, tool_calls: undefined }
},
lc_namespace: [ 'langchain_core', 'messages' ],
content: 'Hello there! How can I assist you today?',
name: undefined,
additional_kwargs: { function_call: undefined, tool_calls: undefined }
}
また、 modelName
でどの言語モデルを使うかを指定できますので、以下を見ながらどれを使うかなどを確認してみるとよいかと思います。
他のLLMを使う
私はGoogle Cloudが好きです(突然
なので、 PaLM
も使ってみたいですという人がいた場合、デフォルトでは PaLM
への接続はできないのでモジュールを追加します。
pnpm i @google-ai/generativelanguage
そして、PaLM API KEYを取得します。コチラのページのとおりに Maker Suite
から取得します
とはいえ、何ぞコレはという人もいると思いますが、コチラ使わずに作成するとしたら、Google CloudのAPICredentialsからAPIKeyを作成してください
API_KEYの権限としては、 Generative Language API
があれば良いです。
以下のように使うことが出来ます
import { cleanEnv, str } from 'envalid'
import { ChatGooglePaLM } from 'langchain/chat_models/googlepalm'
const env = cleanEnv(process.env, {
GOOGLE_APPLICATION_CREDENTIALS: str({ example: 'xxx' }),
})
const llm = new ChatGooglePaLM({
apiKey: env.GOOGLE_APPLICATION_CREDENTIALS,
modelName: 'models/chat-bison-001',
temperature: 0.9,
verbose: true,
maxRetries: 3
})
llm.invoke('Hello world!').then(console.log)
ChatOpenAI
は BaseChatModel
を継承しており、例えば ChatGooglePaLM
も BaseChatModel
を継承しているため、 llm.invoke('Hello world!').then(console.log)
は変更せずに切り替えが簡単にできます。
注意点として、 PaLM
の場合は chat-〇〇
と text-〇〇
の2種類があります。
- chat-〇〇
- マルチターン対話に利用ができます
-
ChatGooglePaLM
で利用可能
- text-〇〇
- 単純な回答の返却返却に利用
-
GooglePaLM
で利用可能
間違えていると以下のようなエラーが出ると思います
5 NOT_FOUND: models/text-bison-001 is not found for API version v1beta2, or is not supported for generateMessage. Call ListModels to see the list of available models and their supported methods
Prompt
簡単に言えば、LLMへの命令です。チューニングすることで、LLMの返答を「いい感じに」することができます。
「いい感じ」とは?と思うかもしれませんが、完全な正解はありません。以下のパターンが現在Google Cloudでもベストと言われておりますが、正直モデルの精度などが変わってくれば色々と変わってくるでしょう。
最も重要なコンテンツや情報を明確に伝えます。
プロンプトを構造化する: まず、ロールを定義し、コンテキスト/入力データを提供してから、指示を提供します。
モデルに焦点を絞り、より正確な結果を生成するため、さまざまなサンプルを使用します。
制約を使用して、モデルの出力の範囲を制限する。これにより、指示が間違って事実に反するのを防ぐことができます。
複雑なタスクを複数の簡単なプロンプトに分割します。
モデルを生成する前に、独自のレスポンスを評価または確認するようモデルに指示します。(「回答は 3 文に制限してください」、「この評価は 1 ~ 10 の範囲で簡潔に評価してください」、「こちらは正しいと思いますか」
とりあえず、 LangChain で Prompt
を使ってみます.
Prompt
には命令を記載し、その中に変数を入れることができます。
そして、 pipe
を利用してllmを通して出力を得ているという流れです。
import { cleanEnv, str } from 'envalid'
import { ChatOpenAI } from 'langchain/chat_models/openai'
import { PromptTemplate } from "langchain/prompts";
const env = cleanEnv(process.env, {
OPENAI_API_KEY: str({ example: 'sk-xxx' }),
OPENAI_ORGANIZATION_ID: str({ example: 'org-xxx' }),
})
const llm = new ChatOpenAI({
modelName: 'gpt-4-1106-preview',
configuration: {
organization: env.OPENAI_ORGANIZATION_ID,
apiKey: env.OPENAI_API_KEY,
},
temperature: 0.9,
verbose: true,
maxRetries: 3
})
const oneInputPrompt = new PromptTemplate({
inputVariables: ["name"],
template: "{name}というキャラクターを教えてください",
});
oneInputPrompt.pipe(llm).invoke({
name: "アーニャ",
}).then(console.log)
結果は以下のようになりました。
[llm/end] [1:llm:ChatOpenAI] [45.76s] Exiting LLM run with output: {
"generations": [
[
{
"text": "「アーニャ」というキャラクターは複数のフィクション作品に登場することがありますが、2021年時点で特に有名な「アーニャ」としては、「SPY×FAMILY」(スパイファミリー)という日本の漫画作品に出てくるアーニャ・フォージャーが挙げられます。\n\n「SPY×FAMILY」は遠藤達哉による漫画で、週刊少年ジャンプのウェブコミック配信サイト「少年ジャンプ+」で連載されています。物語は、東国と西国という架空の二つの国家間の冷戦を背景に展開し、スーパースパイ「黄昏(とうか)」ことロイド・フォージャーが敵国への潜入任務のために偽の家族を作るところから始まります。ロイドは、就学面接を通過するための娘として孤児院からアーニャを養子に迎えます。\n\nアーニャは、ロイドが知らない秘密を持っていて、実は彼女はテレパスの能力を持っています。読心能力により、他人の心を読むことができるため、ロイドがスパイであることや、後に養母となるヨル・フォージャーが実は殺し屋であることなどを知ってしまいます。しかし、彼女はその秘密を知りながらも愛情深く、この偽の家族が本物のように幸せに暮らすことを望んでいます。\n\nアーニャは、彼女の無邪気さ、ユーモラスな行動、時に見せる思いやりと賢さで、読者から非常に愛されているキャラクターです。また、彼女の特徴的な髪型や表情は、ファンアートやコスプレなどでもよく見られます。\n\nそのほかにも「アーニャ」という名前のキャラクターは他の作品にも登場することがあるため、別の「アーニャ」について質問されている場合は、具体的な作品名やコンテキストを教えていただければ、より詳しい情報を提供することができます。",
"message": {
"lc": 1,
"type": "constructor",
"id": [
"langchain_core",
"messages",
"AIMessage"
],
"kwargs": {
"content": "「アーニャ」というキャラクターは複数のフィクション作品に登場することがありますが、2021年時点で特に有名な「アーニャ」としては、「SPY×FAMILY」(スパイファミリー)という日本の漫画作品に出てくるアーニャ・フォージャーが挙げられます。\n\n「SPY×FAMILY」は遠藤達哉による漫画で、週刊少年ジャンプのウェブコミック配信サイト「少年ジャンプ+」で連載されています。物語は、東国と西国という架空の二つの国家間の冷戦を背景に展開し、スーパースパイ「黄昏(とうか)」ことロイド・フォージャーが敵国への潜入任務のために偽の家族を作るところから始まります。ロイドは、就学面接を通過するための娘として孤児院からアーニャを養子に迎えます。\n\nアーニャは、ロイドが知らない秘密を持っていて、実は彼女はテレパスの能力を持っています。読心能力により、他人の心を読むことができるため、ロイドがスパイであることや、後に養母となるヨル・フォージャーが実は殺し屋であることなどを知ってしまいます。しかし、彼女はその秘密を知りながらも愛情深く、この偽の家族が本物のように幸せに暮らすことを望んでいます。\n\nアーニャは、彼女の無邪気さ、ユーモラスな行動、時に見せる思いやりと賢さで、読者から非常に愛されているキャラクターです。また、彼女の特徴的な髪型や表情は、ファンアートやコスプレなどでもよく見られます。\n\nそのほかにも「アーニャ」という名前のキャラクターは他の作品にも登場することがあるため、別の「アーニャ」について質問されている場合は、具体的な作品名やコンテキストを教えていただければ、より詳しい情報を提供することができます。",
"additional_kwargs": {}
}
},
"generationInfo": {
"finish_reason": "stop"
}
}
]
],
"llmOutput": {
"tokenUsage": {
"completionTokens": 702,
"promptTokens": 25,
"totalTokens": 727
}
}
}
このようにして変数を利用しながら、ユーザからの input をフォーマットして llm に投げます。
現状(ver 0.0.203)では PaLM が回答を返さない場合、エラーを出力しますので注意をしてください。
例えば日本語の場合はうまくいかないこともあると思います。その場合は英語を入力として与えると安定する傾向にあります。
例えば、そのようなときに input は良いが返却を日本語にしてほしいということもあるかと思います。その際には以下のように SystemPrompt
を与えて、日本語で返却してもらえるようにします。
const systemTemplate =
"You are a helpful assistant. please always answer in {output_language}.";
const oneInputPrompt = "Please tell me about the character {name}"
const chatPrompt = ChatPromptTemplate.fromMessages([
SystemMessagePromptTemplate.fromTemplate(systemTemplate),
HumanMessagePromptTemplate.fromTemplate(oneInputPrompt),
])
chatPrompt.pipe(llm).invoke({
output_language: "Japanese",
name: "Anya",
}).then(console.log)
結果は以下のようになります。日本語で返却されていますね。
[llm/end] [1:llm:ChatOpenAI] [27.51s] Exiting LLM run with output: {
"generations": [
[
{
"text": "キャラクター「アーニャ」と言うと、漫画やアニメの世界にはいくつかのキャラクターがいますが、最近人気を集めているのは『SPY×FAMILY』という漫画に登場するアーニャ・フォージャーです。\n\n『SPY×FAMILY』は遠藤達哉による日本の漫画で、2022年現在、『週刊少年ジャンプ』のウェブ版である『少年ジャンプ+』にて連載されています。物語は、西側の情報機関に所属するスパイの「黄昏(たそがれ)」が、任務を遂行するために偽の家族を作るところから始まります。\n\nアーニャ・フォージャーは、黄昏が養女として引き取った少女で、実は念読み能力を持っています。彼女はその能力を隠しながら、偽の家族である父親と母親(実は殺し屋)との関係を深めていく中で、多くの魅力的なシーンを生み出します。\n\nアーニャは非常に愛らしく、無邪気な性格をしており、またその特殊能力と相まって、読者や視聴者から大変な愛情を受けているキャラクターです。",
"message": {
"lc": 1,
"type": "constructor",
"id": [
"langchain_core",
"messages",
"AIMessage"
],
"kwargs": {
"content": "キャラクター「アーニャ」と言うと、漫画やアニメの世界にはいくつかのキャラクターがいますが、最近人気を集めているのは『SPY×FAMILY』という漫画に登場するアーニャ・フォージャーです。\n\n『SPY×FAMILY』は遠藤達哉による日本の漫画で、2022年現在、『週刊少年ジャンプ』のウェブ版である『少年ジャンプ+』にて連載されています。物語は、西側の情報機関に所属するスパイの「黄昏(たそがれ)」が、任務を遂行するために偽の家族を作るところから始まります。\n\nアーニャ・フォージャーは、黄昏が養女として引き取った少女で、実は念読み能力を持っています。彼女はその能力を隠しながら、偽の家族である父親と母親(実は殺し屋)との関係を深めていく中で、多くの魅力的なシーンを生み出します。\n\nアーニャは非常に愛らしく、無邪気な性格をしており、またその特殊能力と相まって、読者や視聴者から大変な愛情を受けているキャラクターです。",
"additional_kwargs": {}
}
},
"generationInfo": {
"finish_reason": "stop"
}
}
]
],
"llmOutput": {
"tokenUsage": {
"completionTokens": 415,
"promptTokens": 31,
"totalTokens": 446
}
}
}
と書きましたが実際、OpenAIはうまくいきましたが、PaLMさんは英語で返却してきました。
なので、こういうときはもう少し Prompt
をいじるか、諦めて、 Google Cloud Translate に投げるようにするのも良いのではないかと思います。
まとめ
TypeScriptでのLangChainに付いて使い方等を記載させてもらいました。
その中でも単純な的な形で返却する機能の紹介と、 Prompt
の作成の仕方について記載させてもらいました。
LangChainには、もっと様々な機能がありますので、この最初の一歩から、アイデアを膨らませて、アプリケーションに取り込んでみてはいかがでしょうか?
3-shake Advent Calendar 2023 10日目としては以上となります。
弊社では 生成AIを活用したSRE業務自動化への取り組みを発表 しております。
Generative AIの利活用、SRE活動でお悩み等ありましたら、弊社までぜひお問い合わせください!
Discussion