airvzxf / airvzxf/moagan

feat(llm): add top_p_auto + top_k_auto probe + table + sidecar (D7)

オープン
#930 コメント 0 件 リアクション 0 件 担当者 0 名 GitHub で見る
area:llm enhancement priority:P2 size:L
主要言語
Rust
スター
0
フォーク
1
平均マージ
35分
マージ済み PR(30日)
238

説明

## Goal

Add the `top_p_auto` and `top_k_auto` probe + table + sidecar infrastructure per #900 D7. The exploration report confirmed that today only `temperature_auto` and `max_tokens_auto` exist as auto-probed fields — `top_p` and `top_k` are sent to the upstream without any per-model capability probing. D7 mandates adding them so the wire body is provably accepted by each upstream.

## Why now

After the migration wave (#5-#11) finishes, the runtime speaks `LlmClient` end-to-end. The next two features (#12 + #13) are pure additions with no breaking changes — they extend the auto-probe surface to cover the remaining sampling parameters the runtime can omit (`top_p`, `top_k`). #12 lands the data structures; #13 lands the CLI verbs.

## Scope

**In scope:**
- New file `src/llm/probe_top_p.rs` (~400 LOC, mirroring `src/llm/probe_table.rs`):
- `TopPTable` (in-memory cache + persistence).
- `TopPEntry { detected_at, verified_at, auto: bool, top_p: f32 }`.
- `TopPFile` (serde shape, schema_version = 1).
- `TopPTable::from_home(&home, floor, save)`, `probe_and_store(...)`, `set_operator_cap(...)`, `nearest_supported(...)`.
- New file `src/llm/probe_top_k.rs` (~400 LOC, mirroring `src/llm/probe.rs`):
- `TopKTable` (in-memory cache + persistence).
- `TopKEntry { detected_at, verified_at, auto: bool, top_k: u32 }`.
- `TopKFile` (serde shape, schema_version = 1).
- Same set of methods as `TopPTable`.
- Sidecar files: `/top_p_auto.toml`, `/top_k_auto.toml` (per #900 D7's two-plane naming convention — `_auto` suffix marks mechanism).
- Module registration: `pub mod probe_top_p; pub mod probe_top_k;` in `src/llm/mod.rs`.
- Two-plane naming (`_auto` suffix) per #900 D7:
- Wire field: `top_p`, `top_k` (no suffix).
- CLI verb: `moagan probe top_p`, `moagan probe top_k` (no suffix, in #13).
- Sidecar file: `top_p_auto.toml`, `top_k_auto.toml` (`_auto` suffix).
- Env var: `MOAGAN_TOP_P_AUTO`, `MOAGAN_TOP_K_AUTO` (`_AUTO` suffix in uppercase).
- Config key: `top_p_auto_enabled`, `top_k_auto_enabled` (`_auto` suffix).

**Out of scope:**
- CLI verbs `moagan probe top_p`, `moagan probe top_k` — issue #13.
- Cascade absorption (D9) — issue #14.
- Audit-hash kill (D8) — issue #14.
- Legacy `Provider` deletion — issue #15.

## Approach

### 1. `TopPTable` (`src/llm/probe_top_p.rs`)

Mirror `src/llm/probe_table.rs:137-400` field-by-field. The probe algorithm walks the range `0.0..1.0` in `0.05` increments (matches typical upstream quantisation), accepting the first `top_p` that the upstream returns `200 OK` for and rejects the next-larger one. The dispatch clamp at `top_p` dispatch time uses `nearest_supported(req.top_p) → f32` (snaps to the nearest accepted `top_p` in the table).

### 2. `TopKTable` (`src/llm/probe_top_k.rs`)

Same shape as `TopPTable`, but walks `1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024` (powers of 2, matching typical upstream quantisation) and snaps to the nearest accepted power-of-2.

### 3. Sidecar schemas

```toml
# /top_p_auto.toml
schema_version = 1

[providers.minimax."MiniMax-M3"]
detected_at = "2026-09-12T11:23:45Z"
verified_at = "2026-09-12T11:23:45Z"
auto = true
top_p = 0.95

[providers.opencode."kimi-k3"]
detected_at = "2026-09-12T11:24:00Z"
verified_at = "2026-09-12T11:24:00Z"
auto = false # operator pinned
top_p = 1.0
```

```toml
# /top_k_auto.toml
schema_version = 1

[providers.minimax."MiniMax-M3"]
detected_at = "2026-09-12T11:24:30Z"
verified_at = "2026-09-12T11:24:30Z"
auto = true
top_k = 40
```

### 4. Wire-body integration

`LlmRequest::top_p` and `LlmRequest::top_k` (introduced in #1) get the same pre-dispatch clamp that `temperature` already gets via `TemperatureTable::nearest_supported`:

```rust
// In phase.rs::dispatch_to_provider (already migrated in #5):
let req = LlmRequest {
top_p: top_p_table.nearest_supported(req.top_p, &default_provider, &default_model),
top_k: top_k_table.nearest_supported(req.top_k, &default_provider, &default_model),
..req
};
```

### 5. Unit tests

- `TopPTable::probe_and_store_accepts_first_value` — wiremock returns `200` for `top_p=0.5`, rejects (`400`) for `top_p=0.55`. Probe returns `0.5`.
- `TopPTable::nearest_supported_snap` — table has `[0.5, 0.9, 0.95]`, request `top_p=0.93` → snaps to `0.95`.
- `TopPTable::set_operator_cap_pins_value` — operator sets `cap=0.9`, request `top_p=0.95` → snaps to `0.9`.
- `TopPTable::persistence_round_trip` — write sidecar, reload, assert entries match.
- Same 4 tests for `TopKTable`.

## Acceptance criteria

- [ ] `cargo build --release --all-features` succeeds with **zero warnings**.
- [ ] New unit tests at `src/llm/probe_top_p.rs` and `src/llm/probe_top_k.rs` cover the 4+4 = 8 listed cases and pass.
- [ ] `/top_p_auto.toml` and `/top_k_auto.toml` are created on first probe, with `schema_version = 1`.
- [ ] `src/llm/mod.rs` exports `TopPTable`, `TopPEntry`, `TopKTable`, `TopKEntry`.
- [ ] The dispatch clamp at `phase.rs::dispatch_to_provider` snaps `req.top_p` / `req.top_k` to the nearest supported value via the new tables.
- [ ] `make fmt-check guard-deps lint build test-ci` green.

## Merge order

```
#1-#11 (all foundation + migration)

#12 (this issue — top_p_auto + top_k_auto probe + table + sidecar)

#13 (CLI verbs moagan probe top_p + moagan probe top_k)

#14-#15 (cleanup wave)
```

## Validation

- `make fmt-check guard-deps lint build test-ci` green after the PR lands.
- 8 new unit tests pass (4 top_p + 4 top_k).
- Manual: `touch /top_p_auto.toml && moagan probe top_p --provider mock:mock-model` populates the sidecar (CLI surface lands in #13, but the underlying probe method is exercised via unit tests in this issue).

## Version target

v0.18.0 (or v0.18.x patch — this is purely additive, no breaking changes). Could ship on v0.17.x if the operator prefers incremental feature releases; v0.18.0 is the natural target since the migration is already there.

## References

- [EPIC #847](https://github.com/airvzxf/moagan/issues/847) — the umbrella.
- [#900 D7](https://github.com/airvzxf/moagan/issues/900) — `_auto` suffix nomenclature (two-plane).
- [`src/llm/probe_table.rs`](../blob/main/src/llm/probe_table.rs) — `MaxTokensTable` (mirrored by `TopPTable` / `TopKTable`).
- [`src/llm/temperature_probe.rs:80-110`](../blob/main/src/llm/temperature_probe.rs) — `temperatures_auto.toml` schema spec (mirrored by the new `top_p_auto.toml` / `top_k_auto.toml`).
- [`src/fs_layout.rs:232-247`](../blob/main/src/fs_layout.rs) — `MoaganHome::param_rejections_path()` etc. (extended with `top_p_auto_path()` and `top_k_auto_path()`).

コントリビューションガイド

コントリビューションガイドを開く

評価

この issue はまだ評価されていません。

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。