Compare commits
3 Commits
ffd59b05a1
...
771503e69c
| Author | SHA1 | Date | |
|---|---|---|---|
| 771503e69c | |||
| 89a66f1d58 | |||
| 8be579dc3c |
1
TODO.md
1
TODO.md
|
|
@ -16,7 +16,6 @@
|
|||
- spawn 失敗時に Pod の stderr が TUI に表示されない → [tickets/tui-spawn-error-surface.md](tickets/tui-spawn-error-surface.md)
|
||||
- role:system の system message を TUI に表示する仕組み → [tickets/tui-system-message-render.md](tickets/tui-system-message-render.md)
|
||||
- 巻き戻されたターンの入力テキストを編集領域に復元 → [tickets/tui-empty-turn-restore.md](tickets/tui-empty-turn-restore.md)
|
||||
- ステータスライン `↑` をキャッシュ控除後の純アップロード量に → [tickets/tui-upload-tokens-net.md](tickets/tui-upload-tokens-net.md)
|
||||
- Manifest: Tool Output / File Upload 上限の分離とデフォルト緩和 → [tickets/manifest-output-upload-limits.md](tickets/manifest-output-upload-limits.md)
|
||||
- メモリ機構
|
||||
- 使用頻度メトリクス + Knowledge 化候補レポート → [tickets/memory-usage-metrics.md](tickets/memory-usage-metrics.md)
|
||||
|
|
|
|||
|
|
@ -76,10 +76,12 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||
Event::Usage {
|
||||
input_tokens,
|
||||
output_tokens,
|
||||
cache_read_input_tokens,
|
||||
} => {
|
||||
println!(
|
||||
"[usage] in={} out={}",
|
||||
"[usage] in={} (cache_read={}) out={}",
|
||||
input_tokens.unwrap_or(0),
|
||||
cache_read_input_tokens.unwrap_or(0),
|
||||
output_tokens.unwrap_or(0)
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -229,6 +229,7 @@ impl PodController {
|
|||
let _ = tx.send(Event::Usage {
|
||||
input_tokens: event.input_tokens,
|
||||
output_tokens: event.output_tokens,
|
||||
cache_read_input_tokens: event.cache_read_input_tokens,
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -281,9 +281,19 @@ pub enum Event {
|
|||
#[serde(default)]
|
||||
is_error: bool,
|
||||
},
|
||||
/// Token accounting for one LLM request.
|
||||
///
|
||||
/// `input_tokens` is the prompt prefix occupancy (cache reads /
|
||||
/// cache writes included), as normalised by the worker layer.
|
||||
/// `cache_read_input_tokens` is the cache-hit subset of that
|
||||
/// occupancy; subtracting it yields the "net upload" the client
|
||||
/// actually paid full price to send on this request, which is what
|
||||
/// the TUI status line accumulates per turn.
|
||||
Usage {
|
||||
input_tokens: Option<u64>,
|
||||
output_tokens: Option<u64>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
cache_read_input_tokens: Option<u64>,
|
||||
},
|
||||
RunEnd {
|
||||
result: RunResult,
|
||||
|
|
|
|||
|
|
@ -47,7 +47,11 @@ pub struct App {
|
|||
/// `Resume` or a fresh `Run`).
|
||||
pub paused: bool,
|
||||
pub run_requests: usize,
|
||||
pub run_input_tokens: u64,
|
||||
/// Sum of `input_tokens - cache_read_input_tokens` across the
|
||||
/// current turn's LLM requests — i.e. the net tokens this turn
|
||||
/// actually had to upload at full price (cache writes included,
|
||||
/// cache reads excluded). Reset on `RunEnd`.
|
||||
pub run_upload_tokens: u64,
|
||||
pub run_output_tokens: u64,
|
||||
pub turn_index: usize,
|
||||
pub current_tool: Option<String>,
|
||||
|
|
@ -79,7 +83,7 @@ impl App {
|
|||
running: false,
|
||||
paused: false,
|
||||
run_requests: 0,
|
||||
run_input_tokens: 0,
|
||||
run_upload_tokens: 0,
|
||||
run_output_tokens: 0,
|
||||
turn_index: 0,
|
||||
current_tool: None,
|
||||
|
|
@ -465,8 +469,16 @@ impl App {
|
|||
Event::Usage {
|
||||
input_tokens,
|
||||
output_tokens,
|
||||
cache_read_input_tokens,
|
||||
} => {
|
||||
self.run_input_tokens += input_tokens.unwrap_or(0);
|
||||
// Subtract the cache-hit portion so a tool loop that
|
||||
// re-sends the same prefix on every request doesn't
|
||||
// re-count it. cache_creation stays in (it is full
|
||||
// price on this request).
|
||||
let net_input = input_tokens
|
||||
.unwrap_or(0)
|
||||
.saturating_sub(cache_read_input_tokens.unwrap_or(0));
|
||||
self.run_upload_tokens += net_input;
|
||||
self.run_output_tokens += output_tokens.unwrap_or(0);
|
||||
}
|
||||
Event::Error { code, message } => {
|
||||
|
|
@ -475,13 +487,13 @@ impl App {
|
|||
Event::RunEnd { result } => {
|
||||
self.blocks.push(Block::TurnStats {
|
||||
requests: self.run_requests,
|
||||
input_tokens: self.run_input_tokens,
|
||||
upload_tokens: self.run_upload_tokens,
|
||||
output_tokens: self.run_output_tokens,
|
||||
});
|
||||
self.running = false;
|
||||
self.paused = matches!(result, RunResult::Paused);
|
||||
self.run_requests = 0;
|
||||
self.run_input_tokens = 0;
|
||||
self.run_upload_tokens = 0;
|
||||
self.run_output_tokens = 0;
|
||||
self.current_tool = None;
|
||||
self.assistant_streaming = false;
|
||||
|
|
|
|||
|
|
@ -43,7 +43,10 @@ pub enum Block {
|
|||
Compact(CompactEvent),
|
||||
TurnStats {
|
||||
requests: usize,
|
||||
input_tokens: u64,
|
||||
/// Net tokens uploaded across the turn's LLM requests
|
||||
/// (cache reads excluded; cache writes included). Same value
|
||||
/// the status line accumulates while the turn is in flight.
|
||||
upload_tokens: u64,
|
||||
output_tokens: u64,
|
||||
},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -406,13 +406,13 @@ fn render_block_into(lines: &mut Vec<Line<'static>>, block: &Block, width: u16,
|
|||
Block::Compact(evt) => render_compact(lines, evt, width, mode),
|
||||
Block::TurnStats {
|
||||
requests,
|
||||
input_tokens,
|
||||
upload_tokens,
|
||||
output_tokens,
|
||||
} => {
|
||||
let text = format!(
|
||||
"{} reqs ↑{}/↓{}",
|
||||
requests,
|
||||
fmt_tokens(*input_tokens),
|
||||
fmt_tokens(*upload_tokens),
|
||||
fmt_tokens(*output_tokens),
|
||||
);
|
||||
lines.push(
|
||||
|
|
@ -780,14 +780,14 @@ fn draw_status(frame: &mut Frame, app: &App, area: Rect) {
|
|||
format!(
|
||||
"request: {} | ↑{}/↓{} | tool: {tool}",
|
||||
app.run_requests,
|
||||
fmt_tokens(app.run_input_tokens),
|
||||
fmt_tokens(app.run_upload_tokens),
|
||||
fmt_tokens(app.run_output_tokens),
|
||||
)
|
||||
} else {
|
||||
format!(
|
||||
"request: {} | ↑{}/↓{}",
|
||||
app.run_requests,
|
||||
fmt_tokens(app.run_input_tokens),
|
||||
fmt_tokens(app.run_upload_tokens),
|
||||
fmt_tokens(app.run_output_tokens),
|
||||
)
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,41 +0,0 @@
|
|||
# TUI: ステータスライン `↑` をキャッシュ控除後の純アップロード量にする
|
||||
|
||||
## 背景
|
||||
|
||||
TUI ステータスラインと `Block::TurnStats` で表示している `↑{input}/↓{output}` の `↑` が、tool ループの長いターンで現実離れした大きな値になる。
|
||||
|
||||
原因は2つ重なっている:
|
||||
|
||||
1. **累積範囲**: `crates/tui/src/app.rs:469-470` で `Event::Usage` ごとに `run_input_tokens += input_tokens` を加算し、`RunEnd` で 0 リセット。1ターン内の全リクエストの input_tokens を単純合計している。
|
||||
2. **`input_tokens` の規約**: `crates/llm-worker/src/llm_client/event.rs:61-66` のコメント通り、`input_tokens` は「送信した prompt prefix 全体(cache_read + cache_creation 込み)」の占有量で、Anthropic scheme でも `convert_usage` で cache 込みに正規化済み。tool ループでは各リクエストが直前までの history 全体を prefix として送る → そのほとんどが cache hit → cache_read 分が毎リクエスト重複加算される。
|
||||
|
||||
結果として `↑` が「このターンで新たにアップロードしたトークン数」として読めず、ユーザー体験が悪い。
|
||||
|
||||
## 方針
|
||||
|
||||
**ターン内累積は維持**しつつ、各リクエストの加算値を `input_tokens - cache_read_input_tokens`(= cache_creation + 非キャッシュ入力 = 「このリクエストで full price で送った分」)に変える。
|
||||
|
||||
そのために protocol-level `Event::Usage` に cache 内訳を追加し、pod が worker UsageEvent から passthrough する。llm-worker 内部 (`UsageEvent`) と session-store (`LogEntry::LlmUsage`) には既に cache_read / cache_creation が載っているので、protocol の穴埋めだけで済む。
|
||||
|
||||
cache_creation は「このリクエストで新たにキャッシュに書いた分」で実質アップロードに含めて良い扱いとする(料金的にも full price 側)。よって表示式は `input_tokens - cache_read_input_tokens`。
|
||||
|
||||
## 要件
|
||||
|
||||
- protocol `Event::Usage` (`crates/protocol/src/lib.rs:284`) に `cache_read_input_tokens: Option<u64>`(最低限これ。cache_creation も載せるかは実装時に判断)を追加。
|
||||
- pod controller (`crates/pod/src/controller.rs:228-233`) が worker の `UsageEvent` から cache_read を passthrough する。
|
||||
- TUI 側 (`app.rs:469`, `block.rs:44-48`, `ui.rs:407-422 / 778-793`) は `input_tokens - cache_read_input_tokens` を累積・表示する。`Block::TurnStats` のフィールド意味も同方向に揃える(純アップロード量)。
|
||||
- `pod/examples/pod_protocol.rs:76` のサンプルプリントも整合させる。
|
||||
- 既存テスト・session-store 側ログ(`LogEntry::LlmUsage` は cache 内訳を既に持っているので変更不要)が壊れないこと。
|
||||
|
||||
## 完了条件
|
||||
|
||||
- TUI で tool ループの長いターンを回しても、`↑` が同じ history を毎回再カウントせず、現実的なオーダーに収まる。
|
||||
- `Block::TurnStats` の `↑` も同じ意味(純アップロード)で揃う。
|
||||
- protocol / pod / TUI が cache_read 情報を受け渡せる経路ができている。
|
||||
|
||||
## 範囲外
|
||||
|
||||
- TUI 以外のクライアント(pod_cli 等)の表示変更。
|
||||
- 料金計算・課金表示の追加(あくまで「アップロード量」の意味付け修正)。
|
||||
- セッション全体(複数ターン)の累積表示。
|
||||
- cache hit 率のような追加指標の表示。
|
||||
Loading…
Reference in New Issue
Block a user