モデル / データセット
ax-llm/ax avatar
ax-llm/ax

Ax を採用する前に読む: TypeScript 向け DSPy 実装の設計と境界

The pretty much "official" DSPy framework for Typescript

スター 2,924フォーク 194TypeScriptApache-2.0

ひと目でわかる

これは何?
ax-llm/ax は署名による型付き構造化生成を軸に、エージェント、フロー、オプティマイザを TypeScript で扱う。README とリポジトリ構成から読み取れる仕組みと、採用判断で確認すべき点を整理する。
誰に向いている?
TypeScript で構造化出力と検証をアプリ側に書き続けるのに疲れているチーム、複数プロバイダを同じコードで切り替えたいチームには向く。逆に、単発のチャット呼び出ししかせず、Zod などのスキーマ検証をすでに薄く運用できている場合、Ax の抽象を学ぶ価値は小さい。
商用利用できる?
できます。Apache-2.0 は寛容なライセンスで、著作権表示とライセンス表示を残せば、使用・改変・販売が可能です。
今もメンテナンスされている?
されています。最後のコミットは 6 日前です。
何の言語で書かれている?
主に TypeScript です(GitHub の言語統計による)。

回答はプロジェクトの GitHub データ(最終同期:2026年9月15日)と当サイトの分析に基づくもので、法的助言ではありません。

オープンソース詳細解説

Ax が埋めようとしている穴: プロンプト文字列と型の断絶

LLM を使うコードは、多くの場合プロンプト文字列と、その出力をパースするアドホックなコードの組み合わせになる。出力が JSON として壊れていないかを確認し、期待したキーが存在するかを確認し、型を当てる。この作業はプロバイダを変えるたびに書き直しになる。Ax はここを署名 (signature) という単位で扱う。README の例では `'review:string -> sentiment:class "positive, negative, neutral"'` という文字列が署名であり、`class` で列挙を宣言すると戻り値がそのリテラル union として型付けされると説明されている。対象読者は、TypeScript で LLM を組み込んだアプリを書き、出力の検証コードが増えていくことに問題を感じている開発者である。プロバイダごとの差分をアプリ側から追い出したい、という動機も README の記述から読み取れる。

署名から型付き生成までの流れ

README の mermaid 図は処理の流れを段階で示している。署名は文字列 DSL、fluent な `f()` ビルダー、あるいは Zod、Valibot、ArkType といった Standard Schema v1 のバリデータとして書ける。署名は AxGen に入り、AxGen がプロバイダ記述子と AI クライアントを呼ぶ。その先に AxAgent、AxFlow、GEPA とオプティマイザ成果物がぶら下がる。つまり署名が中心で、エージェントもフローもオプティマイザも同じ生成の上に載る構造だと読める。ホットパスについては README が「render the signature, call the provider, parse the result, and return a typed value」と短く説明しており、間に挟まる層を薄く保つ方針が読み取れる。ここで注意したいのは、この記述が設計意図の説明であって、実測値ではない点である。

デプロイメントプロファイルという切り替え単位

README で特徴的なのが、プロバイダ名がモデル名ではなくデプロイメントプロファイルとして扱われる点だ。`ai({ name: "openai", apiKey: ... })` の `name` を `"anthropic"`、`"google-gemini"`、`"meta"`、`"together"`、`"fireworks"`、`"deepseek"`、`"grok"` などに変えても、署名とコードは同じままでよいと説明されている。理由も明記されている。DeepSeek のモデルを Together がホストする場合、使われるのは Together のエンドポイントと推論規則であり、DeepSeek ネイティブの wire format ではない、という整理である。モデル ID はプロファイルの内側で解決される。この設計は、同じモデルを別のホストから呼ぶときに挙動が変わる問題を、名前空間のレベルで扱おうとしている。プロファイルの一覧とクラスの移行については `docs/AI_PROFILES.md` を参照するよう README は案内している。

ストリーミングを既定にした理由と、それが合わない場面

README はストリーミングを既定とすると明言している。理由は、モデルが生成を終える前に仕事を始められるからである。到着したフィールドを順にパースし、ストリーミング中の表明 (assertion) を評価し、不正と分かった時点で早期に失敗させ、進行中のストリームをキャンセルし、無効と分かっている出力にトークンを使い切る前に修正を始める。最終オブジェクトだけが欲しい場合は `forward()` がそれを返し、逐次出力が欲しい場合は `streamingForward()` がストリームを直接公開する。ここは設計上のトレードオフがはっきり出る箇所だ。バッチ処理のように途中結果を誰も見ない用途では、ストリーミングを既定にすることの利点は小さく、途中で打ち切る制御を前提としたアプリ設計を強いる。採用するなら `forward()` と `streamingForward()` のどちらを主要経路にするかを先に決めておきたい。

オプティマイザと成果物を持ち運べるという主張

README はオプティマイザとして GEPA、few-shot ブートストラッピング、ポータブルなオプティマイザ成果物、評価と適用のフローを挙げている。DSPy 系の枠組みでは、プロンプトを手で書く代わりに、署名と評価データからプロンプトや例を最適化する。Ax が「ポータブル」と表現しているのは、最適化の結果を言語をまたいで持ち運べる形にするという意味だと読めるが、README にはその成果物の具体的なファイル形式やスキーマまでは書かれていない。ここは採用前に一次情報を確認すべき箇所である。オプティマイザを回すには評価データセットと実行時間が必要で、GEPA を試すこと自体がコストになる。構造化生成だけが目的なら、署名と AxGen の範囲で止める判断もあり得る。

TypeScript 以外の言語パッケージの位置づけ

README は Ax を TypeScript ファーストと明記し、`@ax-llm/ax` が現行の公開パッケージだとしている。そのうえで、同じ署名、プロバイダ対応、エージェント、フロー、ランタイム契約、オプティマイザが、検証済みの生成ライブラリとして Python、Java、C++、Go、Rust にもコンパイルされると説明している。生成されたソースは `packages/<language>` にチェックインされており、対応 API を確認できる。AxIR が変わったときは `npm run axir:generate-packages` でパッケージを更新する。ここで注意したいのは、TypeScript が実装の本体であり、他言語はそこから生成される立場だという点である。他言語を主軸に据えるチームは、生成物の更新が TypeScript 側の変更に依存することを前提として受け入れる必要がある。

動かすまでの手順と、確認に使うコマンド

最小の例は README に載っている。`import { ai, ax } from "@ax-llm/ax"` で読み込み、`ai({ name: "openai", apiKey: process.env.OPENAI_APIKEY })` でクライアントを作り、`ax('review:string -> sentiment:class "positive, negative, neutral"')` で署名を定義し、`classify.forward(llm, { review: "..." })` を待つ。戻り値の `sentiment` はリテラル union として型付けされる。各言語の例を動かすにはリポジトリのランナーを使う。`npm run example -- list` で一覧を出し、`npm run example -- python src/examples/python/generation/axgen-openai.py` のように言語とパスを渡す。Java、C++、Go、Rust も同じ形式で指定できる。オーバーヘッドを自分のプロバイダで測るためのストリーミングベンチマークも用意されており、`AX_STREAM_BENCH_PROVIDER`、`AX_STREAM_BENCH_MODEL`、`AX_STREAM_BENCH_RUNS`、`AX_STREAM_BENCH_WARMUP_RUNS` を環境変数で与えて `npm run tsx src/examples/streaming-latency.ts` を実行する。README は Claude と Gemini での最近の実行について、待ち時間の大半はプロバイダのキューとモデル生成が占め、AxGen は生の `ai.chat()` 経路に近いと述べているが、これは README の記述であって本稿が再現した数値ではない。

採用を見送るべき条件と、比較対象としての DSPy

Ax が向かないのは、LLM 呼び出しが単発で、出力の形も一つか二つしかない場合だ。署名を学び、プロファイルの概念を理解し、生成パッケージの更新サイクルを追うコストに見合わない。また、Python の DSPy 本体と同じ語彙と挙動を期待するなら、Ax は TypeScript ファーストの別実装であり、README 自身が言語間で同じ意味論をコンパイルすると説明している以上、差分は必ず存在する。比較対象として DSPy を挙げると、違いは言語と配布の形にある。DSPy は Python のライブラリとして署名とオプティマイザを提供し、Ax は TypeScript を実装本体として同じモデルを他言語のパッケージ形状にコンパイルする。Python だけを使い、既存の DSPy 資産があるなら乗り換える理由は薄い。逆に TypeScript のサービスに組み込み、Zod などの Standard Schema v1 バリデータをすでに使っているなら、署名の書き方としてそのまま流用できる点は具体的な利点になる。

ライセンスと更新コストの見取り図

ライセンスは Apache-2.0 である。この表示から読み取れるのは、商用利用を含む利用と改変が認められる類型だという程度で、特許条項や再配布時の表示義務を含む条件の解釈は法務の領域であり、ここで助言はしない。更新コストについては、README が `docs/RELEASE.md` をパッケージとリリースの形状の説明として案内しており、リリース番号が 24.x 系で継続的に出ていることがリポジトリ情報から確認できる。AxIR の変更時に `npm run axir:generate-packages` を実行する運用が示されていることから、コアの意味論が変わると生成パッケージ側も再生成が必要になる。TypeScript だけを使う場合でも、依存するパッケージのバージョンを上げる際は、署名 DSL とプロファイル名の互換性を `docs/AI_PROFILES.md` と突き合わせて確認するのが現実的な手順になる。

編集部の結論

TypeScript で構造化出力と検証をアプリ側に書き続けるのに疲れているチーム、複数プロバイダを同じコードで切り替えたいチームには向く。逆に、単発のチャット呼び出ししかせず、Zod などのスキーマ検証をすでに薄く運用できている場合、Ax の抽象を学ぶ価値は小さい。導入前に確認すべきは、README が示す `npm run example -- list` で自分の言語の例が動くか、`docs/AI_PROFILES.md` のプロファイル名が自分の使うプロバイダを網羅しているか、そして `forward()` と `streamingForward()` のどちらを主要経路にするかである。特にストリーミングを既定とする設計は、途中で検証して打ち切る前提のアプリには合うが、最終オブジェクトだけを一度に受け取りたいバッチ処理では `forward()` を明示的に選ぶ必要がある。

公式情報源

  1. ax-llm/ax on GitHub
  2. License: Apache-2.0
  3. Project website
  4. README
  5. Releases
コミュニティノート

コミュニティノート