TypeScriptで型安全なAI呼び出し
OpenAIには、tools(旧function calling)という、関数の引数などを定義したschemaを与えるとその関数を呼び出してくれる機能があって、それを使えばAIの返答を割と効率で構造化できるんですが、その関数定義や実装がものすごくめんどうなので、簡単にできないかを考えていました。
C++だとtemplateを型レベルプログラミングでどうにかできそうな気がするのですが、調べた感じtypescriptだとそれは難しいっぽく、似たようなことをやろうとするとプリプロセッサのようなものが必要になるようです(実際そういうライブラリはある)。事前として、型定義っぽいオブジェクトを作ってそれを渡すとそこから型を作ってくれるライブラリもあったので、それを使うことにしました。
今回は、io-tsを採用して、io-tsの様式で定義した型情報を使ってtoolsのスキーマを定義するライブラリを作成しました。それが"typai"です。
GitHub: https://github.com/jonigata/typai
インストール方法
typaiは npm でインストールできます。以下のコマンドを実行してください:
npm install typai
typaiは以下のライブラリに依存しています。
- openai
- io-ts
- json5
実装の概要
typaiの核となる機能は、queryAi
関数です。この関数は、OpenAIのクライアント、モデル名、プロンプト(または会話履歴)、そしてツールの配列を受け取り、AIの応答を型安全な形で返します。
以下は、基本的な使用例です。Toolのparmetersのところがio-tsの型です。
import OpenAI from 'openai';
import { queryAi, Tool } from 'typai';
import * as t from 'io-ts';
// Initialize OpenAI client, and by changing the baseUrl, openrouter can also be used
const openai = new OpenAI({
apiKey: process.env.OPENAI_API_KEY
});
// Define a tool
const echoTool: Tool<{ message: string }> = {
name: 'Echo',
description: 'Echoes back the input',
parameters: t.type({
message: t.string
})
};
// Use the tool
async function main() {
try {
const result = await queryAi(
openai,
"anthropic/claude-3-sonnet:beta",
"Please echo back: Hello, World!",
[echoTool]
);
console.log("Tool called:", result.tool.name);
console.log("Parameters:", result.parameters);
} catch (error) {
console.error("Error:", error);
}
}
main();
実装で難しかったところ
1. ジェネリクスの活用
ツールのパラメータ型を柔軟に定義するため、ジェネリクスを多用しました。例えば、Tool
型は以下のように定義されています:
export type Tool<T> = {
name: string;
description: string;
parameters: t.Type<T>;
}
これにより、各ツールが独自のパラメータ型を持つことが可能になりました。
2. io-tsとTypeScriptの型システムの統合
io-tsの型定義とTypeScriptの型システムを統合する過程で、いくつかの課題がありました。特に、io-tsのType<A, O, I>
とTypeScriptの型システムをうまく連携させるのに苦労しました。
例えば、generateJsonSchema
関数の実装では、以下のような型チェックが必要でした:
function generateJsonSchema(type: t.Type<any>) {
if (type instanceof t.InterfaceType) {
// InterfaceType specific logic
} else if (type instanceof t.UnionType) {
// UnionType specific logic
}
// ...
}
3. 非同期処理の型安全性
queryAi
関数は非同期で動作するため、Promiseの型安全性を確保する必要がありました。TypeScriptの型推論を最大限に活用し、以下のような型定義を行いました:
export async function queryAi<T>(
openai: OpenAI,
model: string,
promptOrMessages: string | ChatCompletionMessageParam[],
tools: Tool<T>[]
): Promise<ToolCall<T>>
4.claude
上の3つ全部ウソです。claudeがうまくやってくれたのであまり苦労しませんでした。このドキュメントの素案書いたのもclaudeです。一番大変で「◯ね」って思ったのはtsconfig.jsonとpackage.jsonです。
今後の展望
- より高度なツール定義のサポート
- スキーマ生成のさらなる最適化
- 他のAI APIプロバイダーへの対応
typaiはまだ開発途上のライブラリですが、AI対話における型安全性の重要性は今後ますます高まると考えています。皆様のフィードバックやコントリビューションをお待ちしております。
コントリビューション
typaiはオープンソースプロジェクトです。バグ報告、機能リクエスト、プルリクエストなど、あらゆる形での貢献を歓迎しています。詳細はGitHubリポジトリをご覧ください。
ライセンス
typaiはISCライセンスの下で公開されています。詳細はLICENSEファイルをご覧ください。
ISCライセンスって聞いたことなかったんですが、MITより弱いらしいです。おおよそAS ISなやつですね。