yoi/tickets/tool-output-limit.review.md
2026-04-15 04:08:56 +09:00

88 lines
4.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# レビュー: ツール実行結果のサイズ上限
対象コミット: 未コミット(作業ツリー)
対象差分: `crates/manifest/src/lib.rs`, `crates/llm-worker/src/{lib,tool,worker}.rs`, `crates/pod/src/pod.rs`
## 要件達成状況
| 要件 | 状態 |
|---|---|
| 単一チョークポイントで全ツールに効く | ✅ `worker::execute_tools` 内に配置 |
| マニフェストで設定可能(デフォルト 16KB | 🚨 省略時にデフォルトが効かない(下記参照) |
| 切り詰めマーカーで LLM が検知できる | ✅ `[truncated: N bytes dropped, refine your query]` |
| `summary` 非改変 | ✅ `ToolResult::content` のみを書き換え |
| `content: None` スキップ | ✅ `as_mut()` の else で continue |
| 未知ツール名を許容 | ✅ `HashMap::get` なので自然にフォールバック |
| UTF-8 安全な切断 | ✅ `is_char_boundary` ループで実装 |
| 切り詰め関数の単体テスト | ✅ ASCII / UTF-8 / 境界 / per_tool |
## 指摘
### 1. 🚨 デフォルト 16KB が `[worker.tool_output]` 省略時に効かない
チケット本文:
> `[worker.tool_output]` セクション自体は省略可能。省略時はデフォルト 16KB が全ツールに適用。
現状の実装:
- `manifest::WorkerManifest.tool_output: Option<ToolOutputLimits>` + `#[serde(default)]`
→ TOML で省略すると `None`
- `pod::apply_worker_manifest`:
```rust
worker.set_tool_output_limits(wm.tool_output.as_ref().map(|l| ...));
```
`None` がそのまま worker に伝播
- `Worker::execute_tools`:
```rust
if let Some(limits) = self.tool_output_limits.as_ref() { ... }
```
`None` なら truncate ブロック丸ごとスキップ
結果、マニフェストに `[worker.tool_output]` を書き忘れると truncate 自体が無効化される。事故再発防止が目的のチケットで、**ユーザーの明示的なオプトイン**に縮退しており要件違反。
**判断**: 要修正(必須)。
**修正方針**: `manifest::WorkerManifest.tool_output` を非 Option にして `#[serde(default)]` + `impl Default for ToolOutputLimits` を利用する。Pod 側の `as_ref().map(...)``Some(...)` を常に渡す形に直す。
---
### 2. 🟡 truncate が post_tool_call interceptor の **前** に挿入されている
現状の順序:
```
Phase 2: ツール実行
→ truncate
Phase 3: post_tool_call interceptor
→ 履歴に積む
```
この順だと post_tool_call interceptor が切り詰め済みの content しか見られず、ログ・監査・分類などで full content を使いたいケースで情報ロスが起きる。
また interceptor が content を差し替えた場合、その差し替え後の巨大データは truncate を経由せず履歴に入る(防御が迂回される)。
**判断**: 修正する。truncate を Phase 3 の直後、履歴コミット直前に移動する。
**修正方針**: `worker::execute_tools` 内の truncate ループを Phase 3 の for ループ後に移動。それ以外の構造は変えない。
---
### 3. 🟢 `ToolOutputLimits` 型が manifest と llm-worker で二重定義
`manifest::ToolOutputLimits`serde 付き)と `llm_worker::ToolOutputLimits`serde なし)が同構造で、`pod::apply_worker_manifest` で手変換している。
manifest クレートが llm-worker に依存しない方針は正しく、他の manifest 型(`CompactionConfig` など)も Pod 境界で類似の変換を行っているため、**既存パターンと整合**している。
**判断**: 不問。現行のまま。
---
### 4. 🟢 極小 limit でのオーバーラン
`truncate_content` は limit が suffix のリザーブ分(約 70 bytesを下回ると、body_budget が 0 になっても suffix 自体が 50〜70 bytes あるため結果サイズが limit を超える。実運用で limit < 128 を設定するケースは無く実害は無い
**判断**: 不問必要なら将来 `debug_assert!(limit >= 128)` を足す程度で十分
## 結論
**指摘1・2の修正を条件に受け入れ可**34 はそのまま