🦜

TypeScript で LangChain の最初の一歩

2023/12/10に公開

このエントリーは 3-shake Advent Calendar 2023 の10日目の記事です。

今年は Python をガッツリ触ったり、 LLM などの方面に手を出してきており、新しいことにまみれております。
その中で LLM のシステム作るんだったら Python だろ?っていう中で TypeScript でもちゃんとできるよーっていうことで紹介していきたいと思います。 私が、あんまり Python でアプリ作っていくのが好きじゃないのもあります
もちろん、 Python よりも TypeScript のほうが機能が少なめではありますので、そのあたりは、目をつぶっております。

今回の記事の想定する読み手としては、以下の人たちです。

  • LLM 導入しろとか言われていんだけどよくわからん、何から手を付けりゃ良いんじゃという人
  • TypeScript が好きな人
  • LangChain どこから始めれば良いんかわからん人

LangChainについて

https://www.langchain.com/
https://js.langchain.com/docs/get_started/introduction
LLM のアプリケーションを作る上で、フレームワークです。
フレームワークというからには、フレームワークの作法に沿って作ればいいだけです。
「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 と投げて、それをコンソールに出力しています。

main.ts
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 でどの言語モデルを使うかを指定できますので、以下を見ながらどれを使うかなどを確認してみるとよいかと思います。
https://platform.openai.com/docs/models/continuous-model-upgrades

他のLLMを使う

私はGoogle Cloudが好きです(突然
なので、 PaLM も使ってみたいですという人がいた場合、デフォルトでは PaLM への接続はできないのでモジュールを追加します。

pnpm i @google-ai/generativelanguage

そして、PaLM API KEYを取得します。コチラのページのとおりに Maker Suite から取得します
https://developers.generativeai.google/tutorials/setup?hl=ja
とはいえ、何ぞコレはという人もいると思いますが、コチラ使わずに作成するとしたら、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 を継承しており、例えば ChatGooglePaLMBaseChatModel を継承しているため、 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 の範囲で簡潔に評価してください」、「こちらは正しいと思いますか」

https://developers.google.com/machine-learning/resources/prompt-eng?hl=ja#prompting_best_practices

とりあえず、 LangChain で Prompt を使ってみます.
Prompt には命令を記載し、その中に変数を入れることができます。
そして、 pipe を利用してllmを通して出力を得ているという流れです。

main.ts
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 を与えて、日本語で返却してもらえるようにします。

main.ts
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