この記事で扱う範囲
セッションごとに、最初の2回のモデル呼び出しを特定のモデルへ強制する処理があります。この記事では、その判定、カウンタ、再試行、日次の支出停止がどう組み合わさっているかを整理します。
強制の対象
- 各セッションの最初の2回のモデル呼び出しは
gpt-5.6-terraに強制されます。 - ゲストセッションも対象です。
- 強制はプランに依存しません。
- モデルピッカーでの選択にも依存しません。
判定に使うのは、そのセッションで消費済みの強制枠の数だけです。
クライアントに返るモデルID
強制された呼び出しでも、クライアントが受け取るのは自分が要求したモデルIDです。
- UI が持つ
requestedModelは、要求されたIDです。 - サジェスト終了イベントが運ぶ
requestedModelも、同じく要求されたIDです。
つまり、クライアント側の値だけを見ても、その呼び出しが強制されたかどうかはわかりません。調査するときはこの点を前提にしておく必要があります。
カウンタはストリーム開始前に進む
強制枠のカウンタは、ストリームを開始する前に加算されます。そのため、失敗した呼び出しも枠を1つ消費します。
たとえば最初の2回がどちらも失敗した場合、強制枠はそこで使い切られます。3回目の呼び出しは要求されたモデルへ送られます。
再試行の条件
強制モデルでの呼び出しが失敗したとき、要求されたモデルで再試行するのは次の2条件を両方満たす場合だけです。
- クライアントにまだ何も表示されていない段階での失敗であること
- 失敗の原因がタイムアウトでないこと
強制モデルでタイムアウトした場合、再試行はしません。すでに何かがクライアントに見えている場合も、再試行しません。
日次の支出停止はフェイルクローズ
日次の支出に対する停止スイッチがあります。これはフェイルクローズで動きます。支出チェックの値を読み取れなかった場合も、呼び出しは送信されません。
判定のスケッチ
以下は説明用に書いたスケッチです。本番コードを貼り付けたものではありません。依存関係は deps にまとめ、エラー型や関数名は仮のものです。支出チェックを先頭に置いていますが、他の処理との順序はこのスケッチ上の簡略化です。
const FORCED_MODEL = "gpt-5.6-terra";
const FORCED_CALLS_PER_SESSION = 2;
type CallContext = {
sessionId: string; // ゲストセッションも同じ扱い
requestedModel: string; // クライアントが要求したモデルID
};
async function runModelCall(ctx: CallContext, deps: Deps): Promise<void> {
// 日次支出チェック。読めなければ送信しない(フェイルクローズ)
const spend = await deps.readDailySpend().catch(() => null);
if (spend === null || spend.exceeded) {
throw new SpendBlockedError();
}
// プランやモデルピッカーは参照しない。
// ストリーム開始前に枠を確保するので、失敗しても枠は戻らない。
const forced = await deps.tryClaimForcedSlot(
ctx.sessionId,
FORCED_CALLS_PER_SESSION,
);
const model = forced ? FORCED_MODEL : ctx.requestedModel;
let visible = false;
try {
await deps.stream(model, ctx, () => {
visible = true; // 最初の出力がクライアントに届いた
});
} catch (err) {
const canRetry = forced && !visible && !deps.isTimeout(err);
if (!canRetry) throw err;
await deps.stream(ctx.requestedModel, ctx, () => {});
}
// クライアントには要求されたIDを返す
deps.emitSuggestionEnd({ requestedModel: ctx.requestedModel });
}
スケッチ上の要点は3つです。
-
tryClaimForcedSlotは、ストリームより前に呼びます。 -
canRetryは、強制された呼び出しであること、何も表示されていないこと、タイムアウトでないことの3つを満たすときだけ真になります。 -
emitSuggestionEndに渡すのは、常にctx.requestedModelです。
読むときの注意
- 強制枠の消費数と成功した呼び出しの数は一致しません。
- 再試行が起きても、強制枠が返却されることはありません。
- 支出チェックが読めないと、そのセッションが強制枠の範囲内かどうかに関係なく、呼び出しは止まります。
ここで扱った挙動は、LiveSuggest のモデル呼び出し処理に関するものです。