本文へ移動
runlot
データAI

AI

worker から言語モデルを呼び出せます。env.ai というひとつのバインディングで frontier モデルと value モデルの両方に届き、コストは組織のクレジット残高から差し引かれます。

最初のデプロイからワーカーで env.ai を使えます。オンにするものはありません。

env.ai は worker の中にのみ存在します。worker のエントリーポイントを持たず静的アセットのみを配信するプロジェクトでは利用できません。

呼び出し方

const r = await env.ai.run("anthropic/claude-sonnet-5", {
  messages: [{ role: "user", content: "Write the order confirmation email." }],
  maxTokens: 1024,
});

r.text        // the generated text
r.usage       // { inputTokens, outputTokens, cachedInputTokens }
r.model          // the model that actually ran
r.stopReason     // why generation stopped
r.usageComplete  // whether usage is the exact final total

呼び出しが成功しても text が空になることがあります。特に推論モデルは、表示するテキストを出す前に出力予算を推論で使い切り、stopReason: "max_tokens" で停止することがあります。stopReason、usage、usageComplete を一緒に記録してください。空文字列だけでは、モデルに答える内容がなかったのか、予算を使い切ったのか判断できません。

usageComplete が false の場合、usage は終了前に Runlot が観測したプロバイダー報告の小計にすぎません。ゼロでも、プロバイダーが作業せずコストも発生しなかったという意味ではありません。Runlot は欠けたトークンを作りません。

単一のメッセージの場合、prompt が省略形です。ユーザーメッセージ 1 件と同じ意味になります。

const r = await env.ai.run("@runlot/nova-micro", { prompt: "Summarise this in one line." });
オプション
messages{ role, content } の配列
prompt単一のユーザーメッセージの省略形
systemシステムプロンプト
maxTokens生成する最大トークン数
temperatureサンプリング温度
reasoning{ effort } — モデルが回答前にどれだけ深く考えるかを指定します。モデルごとの値は env.ai.models() の行(effortLevels)にあります。省略するとプロバイダーの既定値で実行されます
responseFormat{ type: "json_schema", name?, schema } — 回答が schema に準拠する JSON になります。env.ai.models() の行に structuredOutput: true があるモデルでのみ動作します
toolsサーバー側ツール: { name, description, inputJsonSchema, execute }[]
maxToolRoundsモデル/ツールラウンドの上限、1–32(既定値 8)
streamtrue にすると ReadableStream を返します

role は user か assistant のいずれかで、それ以外はありません。システムプロンプトは messages ではなく system に指定します。

Next.js プロジェクトでは @runlot/next から env を import してください。エクスポートされる RunlotEnv 型に env.ai が含まれるため、別の ambient 宣言なしで run、embed、models、ツール結果、使用量、終了結果に型が付きます。

Studio と runlot dev のリレーは stopReason、cachedInputTokens、usageComplete を保持します。ローカルコードでも、リレー境界で終了メタデータを失わず同じ値を受け取れます。

推論の effort と構造化出力

reasoning.effort は回答の深さと速度・コストを交換します。値はモデル系ごとに異なるため、env.ai.models() が各行の値を effortLevels として返します — OpenAI の行は none から max、Claude の行は low から max、Gemini は low/medium/high です。モデルが持たない値を要求すると 400 bad_effort が許可値の一覧とともに返り、effort の制御をそもそも持たないモデル(@runlot/…)は無視せず拒否します。

responseFormat は回答をあなたの JSON Schema に制約するので、ツールのループなしで JSON.parse(r.text) がそのまま記述したオブジェクトになります。

const r = await env.ai.run("openai/gpt-6-luna", {
  prompt: "注文 A-123 は火曜に出荷されました。取り出してください。",
  reasoning: { effort: "low" },
  responseFormat: {
    type: "json_schema",
    schema: {
      type: "object",
      properties: { orderId: { type: "string" }, status: { type: "string" } },
      required: ["orderId", "status"],
      additionalProperties: false,
    },
  },
});
const order = JSON.parse(r.text); // { orderId, status }

どのモデルが対応するかは env.ai.models() の行(structuredOutput: true)にあります — 現在は openai/* のすべてと anthropic/claude-haiku-4-5、2 つの Gemini 行です。それ以外は散文を黙って返すのではなく 400 format_unsupported で答えます。2 つの組み合わせは名前付きで拒否されます: responseFormat と stream の同時指定(プロバイダーが非ストリーミング呼び出しに範囲を限定しているため)、そして tools との同時指定(ツール引数を形作る方法はツール自身の inputJsonSchema です)。スキーマはプロバイダーの制約デコーダが受け取る形で書いてください: オブジェクトスキーマ、すべてのフィールドを required に、additionalProperties: false。

サーバー側ツール

execute は env.ai.run を呼び出した同じアプリ worker 世代の中で実行されます。モデルに渡るのはツール名、説明、JSON Schema だけで、callback URL、資格情報、関数コードが worker の外へ出ることはありません。

const answer = await env.ai.run("anthropic/claude-sonnet-5", {
  prompt: "注文 A-123 はどこにありますか?",
  tools: [{
    name: "lookup_order",
    description: "現在のユーザーが所有する注文を検索します。",
    inputJsonSchema: {
      type: "object",
      properties: { orderId: { type: "string" } },
      required: ["orderId"],
      additionalProperties: false,
    },
    async execute({ orderId }, { signal }) {
      const order = await lookupOrder(orderId, { signal });
      if (order.status === "cancelled") {
        return { stop: true, text: "注文はキャンセルされています。", data: order };
      }
      return { data: order };
    },
  }],
});

{ text }、{ data }、{ isError: true, text }、または { stop: true, text?, data? } を返します。例外をスローするとモデルが処理できるエラー結果になり、実行全体が自動的に終了するわけではありません。stop: true が終了結果になるのは isError が true でない場合だけです。Runlot はその結果を送信し、識別済みのリモート実行をキャンセルして確認を待ちます。その後のツール呼び出しを実行せず、新しいモデルラウンドも開始せずに、ただちに結果を返します。

終了した非ストリーミング結果には、停止前までに観測したすべての値が残ります。

{
  text,                    // ツールが停止させる前までのモデルテキスト
  model,
  stopReason: "tool_stop",
  usage,                   // 観測済みの累積使用量
  usageComplete,
  finalTool: { id, name, args, result },
}

クレジットで実行した tool_stop では usageComplete は true です。この経路はツール呼び出しを公開する前に、完了した各プロバイダーラウンドの使用量を報告して精算します。Studio 経由の Claude または Codex サブスクリプションが待機中のツールで停止した場合は false です。これらのアダプターは実行完了時にのみ使用量を報告するため、Runlot が持つのは以前に完了した作業の小計だけです。正常に完了した実行は true、すべてのエラーは false です。BYOS の監査記録も最終合計を作らず、同じ観測小計を記録します。

キャンセルは、プロバイダー作業に料金が一切発生しなかったという保証ではありません。サービスから実行 ID が届く前には wrapper が確認できるリモート実行がなく、中断時にはプロバイダーが Runlot に報告しなかった使用量が残る可能性があります。現在ツールを利用できるのは anthropic/* と openai/* です。その他のモデル系列ではツールを黙って無視せず tools_unsupported を返します。

ストリーミング

stream: true を指定すると、SSE フレームの ReadableStream が得られます。そのまま Response に渡せます。ツール呼び出し中は生成が一時停止し、execute の完了後のテキストは同じストリームで続きます。ツール制御フレームがテキストとして公開されることはありません。

const stream = await env.ai.run("@runlot/nova-micro", { prompt, stream: true });
return new Response(stream, { headers: { "content-type": "text/event-stream" } });

フレームは次のようになります。ツールを含む公開ストリームは usage イベントも転送します。クレジット経路で cumulative: true のないイベントは 1 ラウンドの増分なので、実行中の合計に加えます。サブスクリプション経路で cumulative: true のイベントは、それまでに観測した全体を示す置き換えスナップショットです。加算せず実行中の合計を置き換えてください。そうしないと最後のスナップショットを二重に数えます。最後の done または error フレームには、最後のラウンドだけでなく累積 text と usage が入ります。

data: {"delta":"Hello"}

event: usage
data: {"inputTokens":12,"outputTokens":3,"cachedInputTokens":4,"round":1}

data: {"delta":" there"}

event: done
data: {"text":"Hello there","model":"anthropic/claude-sonnet-5","stopReason":"end_turn","usage":{"inputTokens":12,"outputTokens":3,"cachedInputTokens":4},"usageComplete":true}

生成が途中で打ち切られた場合、最後のフレームは event: error になります。

event: error
data: {"code":"upstream_error","message":"…","text":"Hello","model":"anthropic/claude-sonnet-5","usage":{"inputTokens":12,"outputTokens":3,"cachedInputTokens":4},"usageComplete":false}

最後のフレームは、累積使用量が完全かどうかを必ず示します。usageComplete が false なら、中断前に Runlot が観測したプロバイダー報告の小計だけが入り、ゼロも観測小計にすぎません。プロバイダーが消費しても報告しなかったトークンまでは保証できません。ストリームが開始した後は、失敗はスローされたエラーではなくこのフレームとして届きます。 200 は最初のフレームですでに返されています。ストリームを読み取る箇所では必ず event: error を処理してください。

カタログ

このプロジェクトのコードから実際に呼び出せるモデルの一覧を確認できます。

const models = await env.ai.models();

ティアは 2 種類あり、それぞれ価格が異なります。

ティアモデル ID価格
Frontieranthropic/… · openai/… · google/…プロバイダーの定価をマークアップなしでそのまま反映
値@runlot/…runlot が設定

カタログにないモデル ID は 400 になります。この一覧は runlot が管理する固定リストです。

各行には kind があります。env.ai.run 用は chat、env.ai.embed 用は embedding です。埋め込みとそれに付随するベクトルストアは別のページにあります。

無料枠

すべての組織には value モデルに使える1 日あたり $0.11 が付与されます。これは UTC の深夜 0 時にリセットされます。クレジットがまったくなくても、この範囲内であれば毎日 @runlot/… モデルを呼び出せます。

Frontier モデルには無料枠がありません。 1 日あたりの無料枠を frontier モデルにも開放すると、1 回の呼び出しで使い切ってしまいます。それは無料ティアではなく壊れた仕組みです。anthropic/…・openai/…・google/… には組織にクレジットが必要で、なければ 402 になります。

クレジットはお金です

クレジットはトークンの束ではなく、金額の残高です。呼び出しごとに差し引かれる金額は、その呼び出し時点の価格表によって決まります。

プロバイダーの定価は変動します。たとえば Google の価格は 2027-01-01 に倍になります。クレジットがトークンの束であれば、価格変更によってユーザーがすでに購入したクレジットの価値が気付かないうちに変わってしまいます。金額の残高であればそれを防げます。購入するのは金額であり、それで何が買えるかは呼び出し時点で決まります。

どちらのティアも同じ通貨で価格が付けられています。違いは価格の出どころだけです。別途覚える必要がある単位はありません。

画面と CLI では USD で表示されます。API と --json 出力の整数値はマイクロセントです(1 マイクロセント = 1e-8 USD)。セント単位では小さな呼び出しがゼロに丸められ、気付かないうちに無料になってしまいます。千トークンの呼び出しでも1セントの百分の一未満です。

usage.inputTokens は入力の合計です。usage.cachedInputTokens はその合計に含まれるキャッシュ入力部分であり、別に加算する数ではありません。Runlot はプロバイダーごとに異なるキャッシュカウンターをこの形に正規化してから価格を計算します。キャッシュ読み取りにはキャッシュ入力レート、非キャッシュ入力とキャッシュ書き込みには通常の入力レートを適用します。この正規化の修正は、修正版リリース以降の呼び出しにのみ適用され、過去の台帳項目を後から請求し直しません。

制限

無料プロ
同時呼び出し数(プロジェクトごと)210
1 分あたりの呼び出し数(プロジェクトごと)30300
呼び出しごとの maxTokens4,096モデルの最大値
呼び出しごとの入力128k トークンモデルの最大値
無料の1日あたりの割り当て(価値)org あたり $0.11同じ

エラー

拒否または失敗した呼び出しは例外をスローします。ストリーム開始前のエラーには e.status(HTTP ステータス)と e.code(短い文字列)が含まれます。ツールを含む実行が開始後に失敗した場合、エラーには e.text、e.model、累積 e.usage、e.usageComplete: false も含まれるため、復旧コードは失敗前までに観測したテキストとプロバイダー報告の小計を保持できます。この小計は観測値であり、見えないプロバイダーコストがゼロという保証ではありません。

callback がすでに { stop: true } を返した後でキャンセル確認に失敗した場合、エラーに e.finalTool が含まれることもあります。callback の副作用はすでに反映されている可能性があります。env.ai.run が例外をスローしたという理由だけでツールを再実行しないでください。まず finalTool.id、引数、アプリ側のべき等性記録を確認してください。ストリームエラーも同じ finalTool フィールドを使います。

ステータスcode意味
400unknown_modelモデル id がカタログにありません。一覧は env.ai.models() で確認できます。
400bad_effortreasoning.effort がそのモデルの値ではありません。メッセージが許可値を列挙します。
400bad_formatresponseFormat の形が不正です — type が json_schema でない、schema がない、または name が不正です。
400format_unsupportedそのモデルに構造化出力がないか、responseFormat を stream または tools と一緒に指定しました。
402no_creditクレジットがないか、無料の1日あたりの割り当てを使い切っています。
413input_too_large入力が呼び出しごとの上限を超えています。
429rate_limited1分あたりの上限です。
429too_many_concurrent同時実行数の上限です。
429upstream_rate_limitedプロバイダーが混雑しています。
502upstream_errorプロバイダーの呼び出しが失敗しました。
503model_unavailableこのデプロイではそのモデルを呼び出せません。
504upstream_timeoutプロバイダーの呼び出しが時間切れになりました。

rate_limited と upstream_rate_limited には Retry-After レスポンスヘッダーが付きます。too_many_concurrent には付きません — いつ枠が空くかは分からないためです。実行中の呼び出しが終わると解消します。

チャージ

ダッシュボードの org 画面にある Billing タブからチャージできます。

自動チャージ

残高が設定した水準を下回ると、保存されたカードへ自動的に請求されます。デフォルトではオフです。

チャージ額$10・$20・$50・$100 のいずれか
しきい値0 より大きく、チャージ額未満
1日あたりの請求回数5
連続失敗回数3回でオフになります
  • 最初のチャージは手動です。 その支払いでカードが保存されます。保存された支払い方法がない場合、自動チャージはオンにできません。
  • オンにすることは明示的な同意です。 支払い画面にいない間にカードへ請求されるため、画面には請求条件がすべて記載され、確認が求められます。
  • 1日5回の請求が支出の上限です。 1日の最大額はチャージ額の5倍です。ループで呼び出すバグのあるコードもそこで止まります。回数は調整できません — 代わりにチャージ額を上げてください。
  • しきい値は0より大きくなければなりません。 チャージは非同期であるため、反映されるまで持ちこたえるだけの残高が必要です。しきい値0は拒否されます。
  • しきい値はチャージ額未満でなければなりません。 そうでないと、1回チャージしてもしきい値を下回ったままとなり、1日の上限までチャージし続けてしまいます。これも拒否されます。
  • 3回連続で拒否されるとオフになります。 失敗のたびに通知が送られ、3回目で設定がオフになります。お客様のカードを黙って永遠に再試行することはありません。

CLI

runlot ai                 # calls, tokens, money spent, and your limits
runlot ai models          # the catalog with per-1M-token prices
runlot ai credit          # the org's credit balance and auto top-up settings
runlot ai delete          # turn it off (admin)

3つの読み取りコマンドはすべて --json を受け付けます。モデルを呼び出すコマンドはありません — CLI はワーカーの外にあり、env.ai はその内側からのみ到達できます。

対応していないもの

  • 任意のモデルのパススルー。 カタログは私たちが選定・管理する固定リストです。
  • 外部からの入口。 env.ai はデプロイしたワーカーの内側からのみ到達できます。API キーも外部エンドポイントもなく、runlot の外から呼び出す方法はありません。これは私たちが引いた境界であり、実装し忘れた機能ではありません。
  • 自前のキーの持ち込み。 あなたのプロバイダーキーを受け取って私たち経由で呼び出す経路はありません。
  • ファインチューニング、画像・音声モデル。 まだ対応していません。埋め込みとベクトル検索はあります — 埋め込みとベクトルを参照してください。

このページの目次