ChatGPTみたいなものをTypescriptで作ってみる 

話題のChatGPT、楽しいですよね。私もChatGPTやBingChatで色々試しています。
んで、当然そうなるとそういった対話アプリみたいなもの、自分たちでも作ってみたいじゃないですか。

実際のところ、ChatGPTはOpenAI社が構築している大規模言語モデル（LLM）の1アプリケーションでしかありません。そこら編を体験するためにも、実際に大規模言語モデルを活用したアプリケーションを作ってみた、というのがこの記事の趣旨です。

## 大規模言語モデルを利用するには
OpenAIはすでにAPIをリリースしており、登録すればAPIや標準SDKを利用してOpenAIが提供するAIサービスを利用することができます。

https://platform.openai.com/docs/introduction

その中にはAI画像生成のライブラリなどもありますが、今回は大規模言語モデルを利用します。

大規模言語モデルというのは、早い話がWeb上にある文書を大量に収集し、そこに適切なフィルタリングやアノテーションを行ったものをもとに構築された言語モデルとなります。ChatGPTなどは、これを利用して会話（のようなこと）をしているわけですね。

もちろんそのままAPIを叩いてもいいわけですは、それらをラップしたいい感じのライブラリが色々出てきています。その中でも今回は代表的なものの一つである[LangChain](https://github.com/hwchase17/langchain)を利用します。

…のですが、LangChain自身はPythonなので私が~~宗教上の理由で使えなくて~~ちょっと得意ではなく、探してみたところLangChain.jsというものがちょうど今年に入ってからでてきていました。

https://github.com/hwchase17/langchain

かなり新し目ですが、「Python版LangChainとAPI互換性を保って開発している」とのことなので、今回はこれにトライしてみます。

## まずはTypescriptで使うための下準備
Typescriptで利用するための準備として、とりあえずは必須のライブラリをインストールしてやる必要があります。
また[ドキュメント](https://hwchase17.github.io/langchainjs/)にもあるのですが、nodeは16以上必須、できれば18以上がベターなようです。

というわけで、以下のような`package.json`と、`tsconfig.json`を作っておきましょう。`./src`以下にソースコードを配置する想定です。わかっている人はこれに自分の好きな設定を足していってくださいね。

```json:package.json
{
  "type": "module",
  "scripts": {
    "build": "tsc --build",
    "clean": "tsc --build --clean",
    "execute": "npm run build && node dist/index.js"
  },
  "devDependencies": {
    "@types/node": "^18.14.4",
    "typescript": "^4.9.5"
  }
}

```

```json:tsconfig.json
{
  "compilerOptions": {
    "module": "nodenext",
    "outDir": "dist"
  },
  "include": [
    "src/**/*"
  ]
}

```

## まずはシンプルな会話を試してみる
OpenAIのtoken取得は、各位ググってなんとかしていただくとして（冗談でなくページコロコロ変わるので自分で調べてもらったほうが早そう）、取得したトークンは環境変数かなんかで入れておいてください。（私はこういうのにはだいたい[direnv](https://github.com/direnv/direnv)使います）

```sh
export OPENAI_API_KEY=YOUR_KEY
```

そのうえで、`src/index.ts`を作成して、以下のように書きましょう。

```ts
import { OpenAI } from "langchain";
const model = new OpenAI({ temperature: 0.9 });

const res = await model.call(
  "What would be a good company name a company that makes colorful socks?"
);
console.log(res);
```

これは単純な会話文の補完をおこなっており、「カラフルな靴下を作っている会社に良い名前は？」という質問を英語でしています。
例えば手元で試すと、

```sh
{ text: '\n\nSock It To Me.' }
```

というレスポンスが返ってきました。そう、これは会話文のようですね！
このようにChatGPTのような仕組みの基礎が、こんな簡単に作れるのがLangChainの魅力です。

ちなみに、ｍｏｄｅｌ生成の部分で指定している `temperature` ですが、これはモデルのランダム性を表すものです。0〜1で設定するもので、0.9はかなり乱雑度が高い状態になります。

試しに今の状態で10回くらい同じ会話を繰り返すと以下のようになります。

```
{ text: '\n\nSockSpectrum.' }

{ text: '\n\nSocktastic!' }

{ text: '\n\nFancy Feet Socks.' }

{ text: '\n\nPrimpful Socks.' }

{ text: '\n\nBright Strides Socks.' }

{ text: '\n\nHappy Feet Socks.' }

{ text: '\n\nCheery Socks.' }

{ text: '\n\nRainbow Socks Co.' }

{ text: '\n\nVivid Socks.' }

{ text: '\n\nSockologic.' }
```

一方で、これをもっと低いtempeareture、例えば0.1とかでやると、、、

```
{ text: '\n\nSocktastic!' }

{ text: '\n\nSocktastic!' }

{ text: '\n\nSocktastic!' }

{ text: '\n\nSocktastic!' }

{ text: '\n\nSocktastic!' }

{ text: '\n\nSocktastic!' }

{ text: '\n\nSocktastic!' }

{ text: '\n\nSocktastic!' }

{ text: '\n\nSocktastic!' }

{ text: '\n\nSocktastic!' }

{ text: '\n\nSocktastic!' }
```

このように同じ回答が続くようになります。

## 日本語は？
試しにこれを日本語にしてみましょうか。こんな風に。

```ts
import { OpenAI } from "langchain";
const model = new OpenAI({ temperature: 0.9 });

const res = await model.call(
  "戦国時代を舞台にした歴史戦略ゲームにいい名前を考えてください。"
)
console.log(res);
```
答えはこんな感じです。

```
{ text: '\n\n・戦国華撃\n・戦国ノ烈傑\n・戦国征伐歴史\n・天龍戦国\n・関羽将軍伝説\n・戦国勇将伝　' }
```

…何故か複数考えてくれましたが、こんなふうに日本語もいけます。ただし、OpenAIの課金モデルはWord単位なのですが、日本語の場合1文字 = 1word となってしまうようで、日本語を使うとかなり課金額がかさんでしまうようです。

## 試していて面白かったこと。
そもそもいま会話文の体で話していますが、じつはこの大規模言語モデルは、対話ではなく「文章補完」がメインタスクとして作られていた、ということがいわれます。

日本語で試しているときに、以下のように文末を省略したことがありました。

```ts
import { OpenAI } from "langchain";
const model = new OpenAI({ temperature: 0.9 });

const res = await model.call(
  "戦国時代を舞台にした歴史戦略ゲームにいい名前を考えて"
);
console.log(res);
```

このときに返答された内容がこちらです。

```
{ text: 'ください\n\n・風雲戦譚\n・戦国勇者伝\n・天下布武\n・戦国究極戦\n・極戦国志' }
```

…「ください」？

そう、これはすべてをつなげると

> *戦国時代を舞台にした歴史戦略ゲームにいい名前を考えて*ください
> ・風雲戦譚
> ・戦国勇者伝
> ・天下布武
> ・戦国究極戦
> ・極戦国志

このようになる、ということで、これは実際のところ文章補完として動作しているんですね。

おそらくChatGPTではもうちょっと応答っぽく見せるための処理を行っているのだとはおもうのですが、基本的にはこのように「本来補完であったものが会話っぽい生成を返すようになった」というところが垣間見えて、非常に面白いと思いました。