コストの追跡
ZeroClawは、トークンを消費するすべてのモデル呼び出しを追記専用の台帳に記録します。料金が一部しか判明していない呼び出しも含まれます。支出を発生元のエージェントに紐付け、料金が判明している部分に対して日次・月次の予算を適用し、支出と料金未設定の露出の両方をオペレーターに提示します。料金ルールは設定ファイルに置かれているため、オペレーターは再ビルドなしで編集できます。
このページでは、スキーマ、ルックアップパイプライン、およびオペレーターサーフェスについて説明します。コードは crates/zeroclaw-config/src/cost/ および crates/zeroclaw-runtime/src/agent/cost.rs にあります。
Config schema
2つの関連セクションがこの領域を管理します。cost は予算の適用と記録動作を扱います。cost.rates.* はオペレーターが管理する料金表です。各サブセクションのドット区切りパスは、末尾の <alias> セグメントを価格設定対象のアップストリームリソースに置き換えた、対応する providers.* パスに一致します。
キーがエイリアスではなくリソース ID である理由
[providers.models.anthropic.<alias>] のエントリは、エイリアスバリデーターに従ってオペレーターが選択したエイリアス(glados、production)をキーとします。エイリアスは小文字の ASCII、単一のアンダースコア、ハイフンなしです。[cost.rates.providers.models.anthropic.<resource>] のエントリは、使用状況テレメトリに表示される アップストリームモデル ID(claude-opus-4-7、gpt-4o-mini、whisper-1)をキーとします。これらの ID 文字列はプロバイダーの名前空間に由来し、ほとんどの場合ハイフンを含みます。
スキーマは、すべての料金表 HashMap に #[resource_key] を付与します(crates/zeroclaw-macros/src/lib.rs 内)。この属性により、対象フィールドは create_map_key / rename_map_key における validate_alias_key の対象から除外されるため、ゲートウェイの POST /api/config/map-key はハイフンを含む id を受け付けるようになります。これがないと、create_map_key は現実的なすべてのモデル id を拒否し、料金表 UI が機能しなくなります。エイリアスとリソース id はディスク上の構造(HashMap<String, T>)を共有していますが、これらは異なるバリデータを持つ別個の命名システムです。
スロットリストは唯一の信頼できる情報源です
[cost.rates.providers.models.<type>]、[cost.rates.providers.tts.<type>]、[cost.rates.providers.transcription.<type>] 配下のプロバイダータイプごとのスロットは、[providers.*] のスロットラッパーを駆動するのと同じマクロから展開されます:
#![allow(unused)]
fn main() {
// crates/zeroclaw-config/src/providers.rs
for_each_model_provider_slot!(emit_model_cost_rates_struct);
for_each_tts_provider_slot!(emit_tts_cost_rates_struct, super::schema::TtsCostRates);
for_each_transcription_provider_slot!(emit_transcription_cost_rates_struct, super::schema::TranscriptionCostRates);
}
新しいモデルプロバイダータイプの追加は、for_each_model_provider_slot! の1行で済みます。レートシートスロット、プロバイダー設定スロット、ダッシュボードのドロップダウンはすべてそこから展開されます。手書きのディスパッチテーブルも、フロントエンド上の並列的な文字列リストも不要です。
リクエスト時の料金
[cost.rates.*] から記録される cost_usd 値までのパイプラインは次のとおりです:
-
オーケストレーターの起動時に料金マップを構築します。 channels スーパーバイザーがエージェント用のランタイムコンテキストをインスタンス化する際、
config.cost.rates.providers.models.iter_entries()を走査し、料金をHashMap<provider_type, HashMap<key, f64>>にマージします。ここでkeyは"<model_id>.input"、"<model_id>.output"、または"<model_id>.cached_input"です。レガシーなエイリアスごとの[providers.models.<type>.<alias>].pricingテーブルもマージされます。競合した場合は[cost.rates.*]が優先されます。これは将来を見据えたサーフェスだからです。(crates/zeroclaw-channels/src/orchestrator/mod.rsのcost_tracking: CostTracker::get_or_init_global(...).map(|tracker| ...)配下のクロージャを参照してください。) -
エージェントループ内での記録。 すべての成功した LLM レスポンスは、
crates/zeroclaw-runtime/src/agent/cost.rs内のrecord_tool_loop_cost_usage(provider_name, model, usage)に到達します。この関数はprovider_nameに対応する価格マップのスロットを取得し、resolve_rates(map, model)を呼び出し、トークン数を乗算して、グローバルなCostTrackerを介してCostRecordを保存します。 -
ディメンションごとの解決と検証。
resolve_rates_optはまずモデル ID で検索し、provider/model形式の文字列に対しては パスサフィックス形式で再検索します(そのため、オペレーターが短い形式のみを保存していた場合、anthropic/claude-opus-4-7はclaude-opus-4-7にフォールバックします)。各ディメンションごとに 1 つのOptionを保持するため、ライブ価格とグローバルカタログは欠落分のみを補完します。意図的に設定された0.0の入力またはバウンレートは、設定済みかつ無料のまま扱われます。0.0のキャッシュ入力レートも設定済みですが、無料ではありません。既存のコストコンストラクタはこれを「キャッシュ割引なし」として扱い、キャッシュトークンを標準入力レートで課金します。負の値、非有限値、または(共通の安全上限である「設定単位あたり$1,000,000」を超える)非現実的に大きいレートは、古い、あるいは緩い設定読み込みパスを通過していた場合でも、記録の境界で利用不可として扱われます。欠落している入力、キャッシュ入力、出力の各次元は、その次元にトークンが含まれる場合にのみ評価されます。一度だけ出力される実行時警告には、不完全な次元が示されます。そのため、一部のみ価格計算された呼び出しでは、既知のコストを記録し、価格を算出できなかったトークンのサブセットのみを別途記録します。
-
CostTracker はプロセスグローバルなシングルトンです(
crates/zeroclaw-config/src/cost/tracker.rs内のOnceLock)。リロードは既存のトラッカーに最新のCostConfigを適用し、起動時にコストトラッキングが無効だった場合、後からcost.enabled = trueでリロードするとオンデマンドでトラッカーが構築されます。オーケストレーターの価格マップも、デーモンのリロードのたびにライブ設定から再構築されるため、料金の編集はリロード後の次のリクエストから有効になります。
ゲートウェイからのライブ料金
オペレーターはすべてのモデルの料金を手作業で管理する必要はありません。プロバイダーは、そのプロバイダーブロックで live_pricing = true を設定する(既存の api_key とモデル設定とともに)ことで、独自のゲートウェイからトークン価格を直接取得することを選択できます。価格はゲートウェイ自身の /models リストから取得されます。
動作:
- ゲートウェイが主要な情報源です。 プロバイダーの既存の
/modelsエンドポイント(オンボーディングでモデル一覧の取得に使用するものと同じ)を解析し、モデルごとの料金を取得します。そこに料金を公開しているゲートウェイは、トークンあたりの小数文字列(OpenRouter と Kilo のpricing{prompt,completion,...})として報告し、これらは 100 万トークンあたりの USD にスケールされます。/modelsの一覧に料金がまったく含まれないゲートウェイ(モデル id のみを一覧する opencode zen など)は、以下の models.dev フォールバックでカバーされます。エンドポイント URL や認証情報の 2 つ目のコピーは不要です。これらはプロバイダーの既存の設定から読み取られます。 - models.dev フォールバック。 ゲートウェイが価格を設定していないモデル(または
kilocliのようなサブプロセスゲートウェイなど、HTTP/models一覧がまったくないプロバイダー)は、公開の models.dev カタログ(api.json)にフォールバックし、ファミリーの models.dev 名でキー付けされます(crates/zeroclaw-providers/src/catalog.rsのcatalog_source_forを参照)。フォールバックカタログは各リフレッシュサイクルで新たに取得されるため、両方のソースが同じ1時間ごとのペースで上流の価格変更を追跡します。 - 設定が常に優先されます。 ライブ価格は、モデルに
[cost.rates]/pricingエントリがないディメンション_のみ_を埋めます。設定されたレート(意図的な0.0を含む)は決して上書きされません。これはギャップ埋めであり、置き換えではありません。 - ゲートウェイごとに1回の呼び出し、フラグ付きモデルのみ。 ゲートウェイを共有するエイリアスは単一の
/modelsフェッチに重複排除されます。そのレスポンスからはオプトインされた各エイリアス自身の設定済みmodelのみが埋められ、ゲートウェイがリストするすべてのモデルは埋められません。 - バックグラウンド更新、ブロックしない。 単一のタスクがプロセス全体の価格スナップショットを1時間ごとに更新します。コスト記録パスはキャッシュされたスナップショットを同期的に読み取り、インラインでネットワーク呼び出しを行うことはないため、遅いゲートウェイがリクエストの会計処理を停滞させることはありません。
- デフォルトはオフ。
live_pricing = trueを設定しているプロバイダーがない場合、リフレッシャータスクは存在せず、ネットワークトラフィックも発生しません。動作は機能を含まないビルドと同一です。実行時(設定リロード)に最後のフラグ付きプロバイダーをオフにすると、次のリフレッシュサイクルでスナップショットがクリアされ、再起動なしでライブ価格の取得が停止します。スナップショットはzeroclaw_providers::pricing内にのみ存在します(crates/zeroclaw-providers/src/pricing.rsを参照)。record_tool_loop_cost_usageによって読み取られ、channels スーパーバイザーとゲートウェイ起動時から一度だけスポーンされます。
[cost.rates] と同様に、ライブ価格はスナップショットが投入された後に行われたリクエストにのみ影響します。過去のレコードに対する遡及的な再価格設定はありません。
永続化
CostTracker::record_usage_with_agent は、トークンを含むレスポンスごとに 1 件の CostRecord を <workspace>/state/costs.jsonl に追記します。1 行につき 1 つの JSON オブジェクトです。この台帳は起動時に読み込まれるため、ダッシュボードの今月のエージェント別集計は再起動後も保持されます。
Each TokenUsage レコードには、2 つの価格設定の出所情報フィールドが含まれます:
unpriced_tokens: 実効レートが取得できなかった入力・キャッシュ入力・出力トークンのサブセット。完全に価格設定された呼び出しでは0となり、意図的に無料とされた0.0の入力または出力レートで価格設定された呼び出しや、0.0のキャッシュ入力レートが標準の入力レートにフォールバックした呼び出しも含まれます。pricing_available: そのプロバンス(出所情報)の互換性サマリーです。新しいレコードでは、unpriced_tokens > 0の場合にこの値がfalseに設定されます。
pricing_available = true および unpriced_tokens = 0 で、これらのフィールドが存在する前に書き込まれた台帳の行をデシリアライズします。そのため、レガシー行の支出がゼロであっても、過去の価格情報が利用可能だったことを証明するものではありません。欠落している由来情報は、事後に安全に再構築することができません。
cost_usd は、その時点で有効なレートシートから記録時に計算されます。レコードは不変です。一部のリクエストがすでに記録された後にオペレーターがレートを追加した場合、それらの既存レコードは cost_usd = 0 のまま保持されます。コストが0以外になるのは、レートが設定された後(かつデーモンが再読み込みされてオーケストレーターの料金マップが再構築された後)に行われたリクエストのみです。
これはレートシートを初めて有効化した後、最もよくある想定外の事象です。修正方法は新しいリクエストを待つことです。遡及的な再価格設定は行われません。
予算管理
CostConfig::enforcement.mode は、予測されるコストによって daily_total または monthly_total が設定された上限を超える場合の動作を決定します:
warn: デフォルト。warn レベルのログでイベントを記録し、リクエストを通過させます。block:BudgetExceededエラーでリクエストを拒否します。route_down: 元のモデルの代わりにroute_down_model(より安価な代替モデル)を使用します。この置き換えはリクエストがディスパッチされる前に行われます。
allow_override = true を指定すると、CLI(zeroclaw --override)でオーバーライドトークンを渡すことで、リクエストが block をバイパスできるようになります。デフォルトは false です。warn_at_percent は、ハードリミットの前にゲートウェイが警告バナーを表示するタイミングを制御します。デフォルトは 80% です。
予算比較は記録済みのcost_usdを使用するため、unpriced_tokensのサブセットを考慮できません。当月にunpriced_tokensが含まれている間は、日次または月次の合計が上限を下回っていても安全性の保証にはなりません。上限の強制に依存する前に、不足している料金次元を設定し、新しいリクエストを生成してください。既存の台帳行が遡って再計算されることは決してありません。
エージェントごとの属性
cost.track_per_agent が true(デフォルト)の場合、記録されるすべての CostRecord には発生元のエージェントエイリアスが付与されます。ダッシュボードの Spend by agent パネルと GET /api/cost?agent=<alias> はこのフィールドを利用します。track_per_agent = false に設定することは、追加の HashMap 集約がプロファイルに現れるような大量処理のインストール環境向けの最適化です。トレードオフとして、どこでもエージェント単位のディメンションが失われます。
オペレーターサーフェス
CLIステータス
zeroclaw status は台帳から本日と当月の支出を表示します。当月 (UTC) のどこかでいずれかのモデルが unpriced_tokens > 0 を記録している場合は、価格情報が利用できない旨の警告も表示し、該当モデルの一覧と、それらにまたがる未課金トークンの合計数を示します。この警告は、ダッシュボードが使用する日次の by_model 内訳ではなく、月単位のモデル集計 (CostTracker::get_current_month_model_stats) を参照します。そのため、月内の前日に発生した未価格設定の使用量は、UTC の日付が切り替わった後も、再起動後も表示され続けます。この警告は支出の合計金額からは意図的に独立しています。価格が設定済みの行と未設定の行が混在する場合も表示され、完全に設定された無料モデルの場合は表示されません。
この警告は、表示される支出額が下限値であり、残りの上限余裕額が上限値であることを意味します。完全な会計処理ではありません。レガシーの行は、過去の価格設定の由来が不明であるため、この警告を発生させることはありません。
設定UI
/config/cost→ Limits タブ: フラットな[cost].*フィールド (enabled, limits, enforcement, track_per_agent) のすべて。レートシートの行はここでは編集しません。これらはモデルを所有するプロバイダーに紐付けられているため、1 つ下の階層に配置されています。/config/providers.<category>/<type>→ Costs タブ:そのプロバイダータイプのレートシートエディター。+ Add入力は、設定済みのエイリアス全体にわたるproviders.<category>.<type>.*.modelから取得したアップストリームリソース ID を候補として表示するため、オペレーターは実際にバインドしたすべてのモデルに対してワンクリックでレート行を追加できます。これは[cost.rates.providers.<category>.<type>.*]を編集する唯一のエントリーポイントです。
ダッシュボード
ダッシュボードの Cost タブには、3つのパネルと Window ピッカー(today / last 7 days / last 30 days / this month / all time)が表示されます:
- 支出合計:
costs.jsonlからの日次および月次の合計。 - エージェント別の使用量 ·
<window>: 選択したウィンドウにおけるエージェントごとの集計。track_per_agentが true の場合に表示されます。 - モデル別のコスト ·
<window>: モデルごとの集計です。各行のモデル ID はクリック可能で、クリックすると設定済みエイリアスから所有プロバイダーの種別を解決し、そのプロバイダーの Costs タブに移動します。モデル ID がいずれの設定済みプロバイダーにもバインドされていない場合、クリックは何も実行しません(孤立したモデルには対象となる料金表のルートが存在しないためです)。
ゲートウェイ
GET /api/cost: 現在のCostSummary(ダッシュボードのコスト概要の形式に一致)。単一エージェントの表示には?agent=<alias>を追加します。GET /api/config/templates: スキーマが登録するマップキー付きセクションすべてを返します。Rates タブのカテゴリ × プロバイダータイプのドロップダウンで使用されます。POST /api/config/map-key?path=cost.rates.providers.<category>.<type>&key=<resource>は新しいレートの行を作成します。該当するマップセクションが存在しない場合、パスは拒否されます。リソースキーはvalidate_alias_keyではなく#[resource_key]を通過します。
トラブルシューティング
レートを設定した後もダッシュボードがすべてのエージェントで $0.0000 と表示される。 古いレコードは変更できません。これらは記録された時点でレートが設定されていなかったため、cost_usd = 0 として記録されています。デーモンのリロード後に新しいチャットリクエストを行い、Cost overview > Session および Spend by model を確認してください。どちらも新しいリクエストで値が入力されるはずです。
zeroclaw statusで、一部のレートが設定されているにもかかわらず料金が利用できないと表示される。 料金はトークンを含む次元ごとに解決される。例えば、入力レートは出力トークンの料金を決定せず、キャッシュ入力レートは非キャッシュ入力の料金を決定しない。ランタイムの警告で指定された次元を追加すること。設定された0.0の入力または出力レートは有効な無料レートであり、ステータス警告をトリガーしない。また、設定された0.0のキャッシュ入力レートは、キャッシュトークンを標準の入力レートで課金するため、同様に警告をトリガーしない。
保存後に cost.rates.* パスに対するドリフトが検出されました。 v0.8.0 より前のデーモンには、ダーティ保存パスでハイフン区切りの HashMap キーを破損させ、レートシートへの書き込みをすべて暗黙的に破棄してしまう不具合がありました。v0.8.0 以降でこの現象が発生した場合は、本物のバグです。ダーティパスの解決処理は crates/zeroclaw-config/src/schema.rs::apply_dirty_path にあります。デーモンのバージョンとドリフトが発生したパスを添えて issue を報告してください。
missing_pricing の警告がログを埋め尽くす。 ランタイムは、resolve_rates_opt が設定値、ライブ値、グローバルカタログの料金をマージした後も、トークンを伴う入力、キャッシュ入力、または出力の次元に価格が設定されていない場合、(provider_type, model) の組ごとに一度だけこの警告を出力します。警告には価格が未設定の次元が明記されます。そのため、一部の次元にすでに価格が設定されていても、部分的にしか設定されていないモデルで警告が出ることがあります。警告で指定された正確なモデル ID に対して、不足している次元のみを追加してください。一部のプロバイダーは、設定されたIDが claude-3-5-sonnet であっても、claude-3-5-sonnet-20241022 のようなバージョン付きIDを返すことがあります。リゾルバーは、報告されたギャップを扱う前に、文書化されたパスサフィックス候補も試行します。