TypeChat を丁寧に紐解く

人力要約

  • Microsoft の TypeScript のチームが新しいライブラリを公開したよ

  • TypeChat は型定義を利用した自然言語インターフェースだよ

  • プロンプトを最適化するのではなく、スキーマを最適化するものだよ

  • 応答に対して型チェックをして妥当性を保証するよ

  • サンプルを動かしてみたよ(方法を解説をしているよ)

  • 筆者の想像以上に実装はシンプルでわかりやすくできているよ

  • 筆者はハルシネーションに気付いていない可能性があるので鵜呑みにはしないでね


TypeChat とはなんだろう?

公式情報のみをもとに読み進めていきます。私が上記の要約に辿り着く過程をログとして出力。動かしたところを見たい方は斜め読みを推奨。

上記が公式の本元のページ。引用。

TypeChat

TypeChat helps get well-typed responses from language models to build pragmatic natural language interfaces.

All powered through your types.

https://microsoft.github.io/TypeChat/

「TypeChat は言語モデルから適切に型定義されたレスポンスを取得して、実用的な自然言語インターフェースを構築する用途で使えます。」

「自然言語インターフェース」というものが何を指しているのか。Function Calling と何が違うのか?このあたりが疑問点。次に行きます。

Twitter だとこちらのツイートが言及されている中で最も古く、この方のプロフィールに「Technical Fellow at Microsoft」とあるので、一次情報として適切でしょう。

Today we’re releasing TypeChat, an open source project that uses AI to bridge between natural language and application schema and APIs. It uses TypeScript in novel and interesting ways. Check it out and let us know what you think.

https://twitter.com/ahejlsberg/status/1682068311415341057

「TypeChat は AI を使って自然言語とアプリケーションスキーマや API の橋渡しをするオープンソースのプロジェクト。TypeScript を斬新かつ興味深い手法で利用している。」

「natural language interfaces」というのは、自然言語と他のものを繋ぐインターフェースの役割を果たしているということのよう。次のツイート。

Twitter で TypeChat に言及されている次に古いツイート。プロフィールに「Software Engineer at Microsoft Edge」とあるので、TypeScript のチームでないにせよ、ほぼ一次情報として取り扱って良いでしょう。

TypeScript team just released TypeChat

TypeChat replaces prompt engineering with schema engineering: Instead of writing unstructured natural language prompts to describe the format of your desired output, you write TS type definitions

https://twitter.com/mohamedmansour/status/1682072099979145216

「TypeScript チームは TypeChat をリリース。TypeChat はプロンプトエンジニアリングをスキーマエンジニアリングに置き換えるもの。構造化されていない自然言語のプロンプトからお目当ての出力を得る代わりに、TypeScript の型定義を出力。」

意訳を挟みましたが、おそらく含意は汲み取れているでしょう。プロンプトの最適化ではなく、スキーマを最適化すると。次は GitHub の README。

簡単に要約。

  • TypeChat は、型を用いて自然言語インターフェースを簡単に構築できるライブラリ。

  • 従来の自然言語インターフェースの構築は困難であったが、大規模言語モデルのおかげで自然言語入力を意図とマッチさせることができるようになった。

  • 一方で、応答の制約や構造化、出力の妥当性の保証などの課題に直面。これらを解決するための「プロンプトエンジニアリング」の学習コストは高く、プロンプトが長くなるにつれて出力を自在に操りづらくなる。

  • TypeChat はプロンプトエンジニアリングをスキーマエンジニアリングに置き換える。感情分析のような簡単な例からショッピングカートや音楽アプリなどの複雑な例まで。

  • 次の 3 STEP で利用。

    • 1. 型定義を使用してプロンプトを構築。

    • 2. 応答がスキーマに沿っているかを検証。失敗すると沿うよう修正。

    • 3. インスタンスを簡潔に要約、ユーザの意図と合致しているか確認。

最後にこの言葉で締めくくる。

Types are all you need!

https://github.com/microsoft/TypeChat

なるほど。なんとなくつかめてきた。ただし、実際の挙動と「インスタンス」という言葉が具体的に何を指しているのかが疑問点。

サンプルを動かしてみる

https://github.com/microsoft/TypeChat/tree/main/examples

動作確認をします。公式準拠のため、もし動かなければ上記のページを読んでみてください。

ローカルパソコンで動かすパターンと GitHub Codespaces で動かすパターンの二通りのオプションがありますが、今回は前者を。執筆時には TypeChat の Node.js の対応バージョンが 18.16.0 LTS 以降であることに注意。

Node.js のインストール方法は下記から。わかる人はお好きな手段で。Node.js が環境にある方は次のコマンドを実行。ビルドまで。

git clone https://github.com/microsoft/TypeChat
cd TypeChat
npm install
npm run build-all

次に API Key を設定。TypeChat ディレクトリにて新しいファイルを作成して`.env`と命名。あるいは `touch .env`。

# For OpenAI
OPENAI_MODEL=...
OPENAI_API_KEY=...

# For Azure OpenAI
AZURE_OPENAI_ENDPOINT=...
AZURE_OPENAI_API_KEY=...

OpenAI API を使うパターンと Azure OpenAI Service を使うパターンがあります。どちらか片方のみ必要な情報を入力してください。

OpenAI API を利用する方はこちらから API Key を取得してください。

OPENAI_MODEL は `gpt-3.5-turbo` あるいは `gpt-4`。

Azure OpenAI Service を利用する方は、「リソース管理」 > 「キーとエンドポイント」から。AZURE_OPENAI_ENDPOINT については下記の形式でエンドポイントを指定してください。

https://YOUR_RESOURCE_NAME.openai.azure.com/openai/deployments/YOUR_DEPLOYMENT_NAME/chat/completions?api-version=XXXX-XX-XX

保存後、動かしてみたいサンプルのディレクトリに移動して `main.js` を実行してください。`dist/input.txt` の一行ごとに API リクエストを飛ばします。

cd example/試してみたいディレクトリ
node ./dist/main.js ./dist/input.txt

たとえば、`TypeChat/examples/coffeeShop` にて実行をすると下記のようなログが出力されます。

➤ node ./dist/main.js ./dist/input.txt

☕> i'd like a latte that's it
{
  "items": [
    {
      "type": "lineitem",
      "product": {
        "type": "LatteDrinks",
        "name": "latte"
      },
      "quantity": 1
    }
  ]
}
Success!
☕> i'll have a dark roast coffee thank you
{
  "items": [
    {
      "type": "lineitem",
      "product": {
        "type": "CoffeeDrinks",
        "name": "coffee"
      },
      "quantity": 1
    }
  ]
}
Success!
...

型定義はどうなっているのか。`coffeeShop/src/coffeeShopSchema.ts` を読んでみる。上記の例に関係する部分のみ抜粋。

export interface LineItem {
    type: 'lineitem',
    product: Product;
    quantity: number;
}

export type Product = BakeryProducts | LatteDrinks | EspressoDrinks | CoffeeDrinks;

export interface LatteDrinks {
    type: 'LatteDrinks';
    name: 'cappuccino' | 'flat white' | 'latte' | 'latte macchiato' | 'mocha' | 'chai latte';
    temperature?: CoffeeTemperature;
    size?: CoffeeSize;  // The default is 'grande'
    options?: (Milks | Sweeteners | Syrups | Toppings | Caffeines | LattePreparations)[];
}

export interface CoffeeDrinks {
    type: 'CoffeeDrinks';
    name: 'americano' | 'coffee';
    temperature?: CoffeeTemperature;
    size?: CoffeeSize;  // The default is 'grande'
    options?: (Creamers | Sweeteners | Syrups | Toppings | Caffeines | LattePreparations)[];
}

プロンプトに対して緻密な設計で必要十分な情報が取得できていることがわかる。すごい。次に`TypeChat/examples/math` にて実行。

➤ node ./dist/main.js ./dist/input.txt
➕➖✖️➗🟰> 1 + 2
import { API } from "./schema";
function program(api: API) {
  return api.add(1, 2);
}
Running program:
add(1, 2)
Result: 3
➕➖✖️➗🟰> 1 + 2 * 3
import { API } from "./schema";
function program(api: API) {
  return api.add(1, api.mul(2, 3));
}
Running program:
mul(2, 3)
add(1, 6)
Result: 7
➕➖✖️➗🟰> 2 * 3 + 4 * 5
import { API } from "./schema";
function program(api: API) {
  const step1 = api.mul(2, 3);
  return api.add(step1, api.mul(4, 5));
}
Running program:
mul(2, 3)
mul(4, 5)
add(6, 20)
Result: 26
➕➖✖️➗🟰> 2 3 * 4 5 * +
import { API } from "./schema";
function program(api: API) {
  return api.add(api.mul(2, 3), api.mul(4, 5));
}
Running program:
mul(2, 3)
mul(4, 5)
add(6, 20)
Result: 26
➕➖✖️➗🟰> multiply two by three, then multiply four by five, then sum the results
import { API } from "./schema";
function program(api: API) {
  const step1 = api.mul(2, 3);
  const step2 = api.mul(4, 5);
  return api.add(step1, step2);
}
Running program:
mul(2, 3)
mul(4, 5)
add(6, 20)
Result: 26
...

`math/src/mathSchema.ts` を読んでみる。こちらはもっとシンプル。

export type API = {
    add(x: number, y: number): number;
    sub(x: number, y: number): number;
    mul(x: number, y: number): number;
    div(x: number, y: number): number;
    neg(x: number): number;
    id(x: number): number;
    unknown(text: string): number;
}

これだけの型定義で一般的な加減乗除はもちろん逆ポーランド記法まで解釈して、さらに自然言語まで入力できる。Wolfram Alpha のウェブサイトでできることがインターフェース上で簡潔してしまう。Function Calling もとんでもないですが、これは本当にすごい。

ここで、各サンプルの `./dist/input.txt` の行数分 API リクエストが実行されます。したがって、たとえば coffeeShop のサンプルだと一回の実行で 47回リクエストが走りますので、特に GPT-4 を利用される方はご注意を。

Code Interpreter に src について聞いてみたよ

だいたい挙動はわかったところで、さてどうやってこれを実装しているのかが気になります。ただ全部のソースを読むには脳内 CPU 利用率をあげねばなりません。そろそろ寝たい私にはつらい。そこで、Code Interpreter の出番です。

ChatGPT Plus に加入していると使える Code Interpreter。コードを実行したり、ファイルを作成したり、すごく便利。そんな Code Interpreter に TypeChat の src を zip に圧縮して、解説をしてもらいました。

(ハルシネーションには注意してください。私は TypeScript に関して仮免許しか交付されておりません。各種ドキュメントを読み散らかした程度の初心者と理解してください。)

なるほど。下記の6つのファイルのみ。6つのファイルのみ!?よくよくみてみると各ファイルも高々 200 行程度。

- index.ts
- interactive.ts
- model.ts
- program.ts
- result.ts
- tsconfig.json
- typechat.ts
- validate.ts

とりあえず Code Interpreter の出力を読んでいきます。各ファイルの機能が必要十分でとてもわかりやすい。一部ハルシネーションは起きているのだと思いますが、GPT-4 の出力ログ以上にわかりやすいコメントができる気がしないので特にコメントなしです。各ファイルも実際に開いて見てみると、コメントもたくさん用意されており、本当にドキュメントを読むかのように読めてしまう。よろしければ読んでみてください。

まとめ

はございません。一番上に要約をしております。お読みいただき、ありがとうございます。

すべて勉強代に充てさせていただきます!アウトプットします!