シリーズ: AI とともにコードを学ぶ

エンジンがあなたの持ち込むどんなモデルとも会話する方法

AIレイヤーはインターフェースだ:エンジンを再リンクせずどんなモデルでも持ち込める。

AIはインターフェース、依存関係ではない 呼び出し側は手前に。モデルは奥に。小さく、安定した、あえて愚直な契約。 ビヘイビアツリー ナビゲーション 群衆シミュレーション 感覚系 意思決定ツリー モデル管理インターフェース — ハンドルとテンソル オンデバイスSLM クラウドLLM 学習済みポリシー ヒューリスティック モデルを差し替える。ベンダーを差し替える。この線より上は何も変わらない。
すべてのインターフェース境界は、呼び出し側を壊さずに将来の変更が起こり得る場所だ。

好きなモデルを持ち込んでほしい。RakuAIのAIレイヤーはインポートではなくインターフェースだ——だからモデル市場が「誰が一番か」で四半期ごとに大騒ぎしても、あなたのエンジンは気にする必要がない。

今週末、誰かが鋭い質問をしてきた。「AIがエンジン内のランタイム・プリミティブだとすると、エンジンは組み込んだモデルに縛られてしまう。次にモデルベンダーが動いたら、エンジンを書き直さなければならない」

正当な懸念だ。そして間違っている、しかし正当だ。

ランタイム内のAIプリミティブは特定のモデルではない。それはインターフェースだ。五つの独立したサブシステム、それぞれが独自のC APIを持ち、どのモデル、どの重み、どの推論パスが答えを生成しているかを誰も知る必要なく、エンジンの他の部分からアドレス可能だ。モデルはインターフェースの背後に存在する。呼び出し側はその手前に存在する。両者の間の契約は小さく、安定していて、あえて愚直だ。

これは、その境界線がどう引かれているか、そしてそれを引いたことでなぜ価値を得続けているかについての投稿だ。

「AIプリミティブ」が実際に指すもの

私たちのランタイムにおけるAIレイヤーは一つのものではない。五つだ。

  • ビヘイビアツリー。 決定論的実行レイヤー。意図が判明した後、エージェントに何をすべきかを伝える。
  • ナビゲーションメッシュとパスファインディング。 「この空間をどう移動するか」のレイヤー。
  • 群衆シミュレーション。 「多くのエージェントがどう互いを避け、まとまりのある振る舞いをするか」のレイヤー。
  • 感覚系と知覚。 「エージェントが何を観測するか」のレイヤー。
  • 意思決定ツリーと複雑なAIロジック。 「知っているすべてを踏まえて、何をしたいか」のレイヤー。

それぞれが独自のサブシステムだ。それぞれが独自のDLLとして出荷される。それぞれが公開C APIを持つ。どれも特定のモデルをインポートしない。すべてがそれぞれのインターフェースを通じてエンジンの残りの部分と会話し、互いにも同じ方法で会話する。

.raku体験ファイルが"ai_behavior": "strafe"と書くとき、それはモデルを名指しているのではない。登録済みのビヘイビアを名指しているのだ。ランタイムはこの文字列を解決する。文字列は実装にマッピングされる。実装はビヘイビアツリーでも、意思決定ツリーでも、学習済みポリシーでも、手書きのヒューリスティックでもよい。呼び出し側は知らない。ファイルも知らない。実装はどちらにも触れずに差し替えられる。

それがすべてのトリックだ。

モデル管理レイヤーは独自のサブシステムだ

ランタイム内で実際の機械学習推論が必要になったとき、私たちはそれを上記五つのサブシステムのどれかに埋め込まなかった。独自のAPI面を持つ第六の関心事を追加した:モデル管理だ。

// おおよその表面イメージ。投稿用に簡略化。
RakuModelHandle raku_ai_load_model(const char* model_id, RakuModelOptions opts);
RakuInferenceResult raku_ai_infer(RakuModelHandle h, const RakuTensor* input);
void raku_ai_unload_model(RakuModelHandle h);

モデルを呼び出したいビヘイビアツリーノードは、このAPIを経由する。ベンダーSDKをインポートするのではない。特定のランタイムにリンクするのではない。モデル管理サブシステムにハンドルを求め、それを使う。

つまり、特定のモデルフォーマット、ベンダー、推論フレームワークについて知っているのは、コードベース内でモデル管理サブシステムだけだ。エンジンの他のあらゆる場所は、ハンドルとテンソルしか見ない。モデルを差し替える。ランタイムを差し替える。ベンダーを差し替える。他には何も変える必要がない。

これは地味なインフラ作業だ。しかしこれこそが、AIエコシステムがどのモデルが新しい最良かで四半期ごとに大騒ぎしても、私たちがパニックにならずに済む理由だ。

この境界線はファイルフォーマットも守る

任意の.raku体験ファイルの先頭にあるaiブロックを見てほしい。次のようなキーがある。

"ai": {
  "dda_enabled": true,
  "target_flow_state": 0.7,
  "profiler_mode": "active"
}

これらのどれもモデルを名指していない。能力を名指している。「動的難易度調整はオンだ。ランタイムはフロー状態0.7を目指すべきだ。プロファイラーは稼働中だ」。どのサブシステムがこれらの能力を届けるために関わるかは、ランタイムが決める。今四半期の正解がビヘイビアツリーなら、それが動く。来四半期に小さなオンデバイスモデルになったとしても、ファイルは変わらない。

システム内で引くすべてのインターフェース境界は、呼び出し側を壊さずに将来の変更が起こり得る場所だ。私たちは最初からそれを積極的に引いた。今、その貯蓄を使っているところだ。

これがますます重要になり続ける理由

三つの理由がある。

一つ。モデル市場は動き続ける。 去年の最良のモデルは今年の高価なモデルだ。今年の最良は来年の陳腐なものだ。モデルベンダーをエンジンにハードコードしたチームは、今までにその統合を二度も三度もやり直している。モデル管理インターフェースを間に置いたチームは、一度で済んでいる。

二つ。オンデバイスがかつてより重要になっている。 このインターフェースのおかげで、開発時はサーバーサイドモデル、本番ではオンデバイスモデルに対して、同じai_behavior: "strafe"を実行できる。呼び出し側は知らない。この柔軟性こそが、体験レイヤーを書き直さずにオンデバイス推論を実現可能にしている唯一の理由だ。

三つ。開発ループ内のAIアシスタントがクリーンな境界線から恩恵を受ける。 アシスタントに新しいビヘイビアの追加を頼むとき、アシスタントが尊重すべき契約は登録済みビヘイビア・インターフェースだ。ベンダーSDKの絡み合いではない。インターフェースがクリーンであるほど、アシスタントはより速く正しいコードを生成し、私側のレビューコストは小さくなる。

ここで難しいこと

正直なコストを述べる。

インターフェース設計は実装より時間がかかる。 インターフェースの段階を飛ばして動くバージョンをただ書くのは本当に魅力的だ。抵抗してほしい。ここで取る近道はすべて、実装を差し替える必要が出たときに後で払うことになる。

インターフェースに何を入れるかについて規律が必要だ。 すべてのパラメータは簡単には破れない契約だ。必要だと思うより少なく追加せよ。何が本当に汎用的かを二つ目のユースケースが示すまで待て。AIインターフェースの最初のバージョンには、後になって不要だと判明した五つのパラメータがあった。それらを後で取り除くのは苦痛だった。

登録済みビヘイビアにはバージョン管理が必要だ。 "strafe"がランタイムのあるビルドで一つの意味を持ち、次のビルドでわずかに異なる意味を持つとき、呼び出し側はゲームプレイの回帰によってそれを知る。私たちはビヘイビアをバージョン管理し、.rakuファイルをランタイムバージョンに固定している。面倒だ。必要だ。

時には実際に依存関係が欲しくなる。 これが異端的な部分だ。特定のモデルが、汎用インターフェースでは表現できない特定の能力を持つケースがある。正直な対処は、その能力が汎用的になるようインターフェースを拡張することであり、モデルを呼び出し側に漏らすことではない。私たちはこれまで一度ならず近道を取りたい誘惑に駆られたことがある。

アーキテクチャ上の議論は単純だ。AIはランタイム・プリミティブだ。それを駆動するファイルフォーマットはモデルではなく能力を名指す。ランタイムは能力を、現在それを届けているサブシステムへと解決する。三つのレイヤー間の配線がインターフェースだ。インターフェースは小さく、安定していて、あえて愚直だ。

これが投稿のすべてだ。

構築に戻る。

あなたのモデルを持ってきてほしい。インターフェースは待っている。

RakuAIはハンドルとテンソルを通じてどんなモデルとも会話する——開発ではサーバーサイド、本番ではオンデバイス、呼び出し側は違いを一切知らない。あなたの重みが、モデル市場より長生きするよう作られた空間ランタイムにどう組み込まれるか見てほしい。

← すべての記事