gglib_core/request_pipeline/sampling_log.rs
1//! The one `debug!` line that describes a request's whole sampling decision.
2//!
3//! # Why it is not inside [`resolve_sampling`](super::sampling::resolve_sampling)
4//!
5//! It was, and that made it wrong on exactly the model this arc exists for.
6//! `resolve_sampling` is stage 4; [`effort_gate`](super::effort_gate) is stage
7//! 5b, and it *deletes* a resolved `reasoning_effort` when the model's observed
8//! template does not read the variable. A line rendered inside stage 4 therefore
9//! printed
10//!
11//! ```text
12//! reasoning_effort=Some(High) … from=… reasoning_effort=profile …
13//! ```
14//!
15//! for a value that stage 5b was about to throw away — and since neither
16//! reasoning control is echoed by any readback ([ADR 0007] finding 7a), that log
17//! line **is** the record. An operator grepping `sampling resolved` on a
18//! suppressing model would have found gglib stating, in its only surviving
19//! account of the request, that it sent a level it did not send.
20//!
21//! The alternative was to leave the stage-4 line alone and make stage 5b's own
22//! line loud enough to correct it. That was rejected: the misleading line fires
23//! on *every* request while the correction fires only on a suppression, so the
24//! reader has to know to go looking for a second line before they can trust the
25//! first. A record that is only true when read alongside another record is not a
26//! record. Rendering once, after every stage that can still change the answer,
27//! costs one function call and makes the common line honest by construction.
28//!
29//! Stage 5b keeps its own `debug!` for what this line structurally cannot say:
30//! after suppression `resolved.reasoning_effort` is `None` and its provenance
31//! reads `suppressed-by-template`, so **which** level was dropped and **which**
32//! rung asked for it exist nowhere else.
33//!
34//! [ADR 0007]: https://github.com/mmogr/gglib/blob/main/docs/adr/0007-ask-the-server-for-template-capabilities.md
35
36use tracing::debug;
37
38use super::sampling::SamplingDecision;
39
40/// Render one request's resolved sampling parameters and their provenance.
41///
42/// Call after the last stage that can still change `decision` — today that is
43/// stage 5b. See the module docs for why the position is load-bearing.
44///
45/// Guarded on the level being enabled because both `layer_names` and
46/// [`FieldSources::describe`](crate::domain::FieldSources::describe) allocate,
47/// and this sits on the busiest path in the system.
48pub(super) fn log_resolution(decision: &SamplingDecision) {
49 if !tracing::enabled!(tracing::Level::DEBUG) {
50 return;
51 }
52 let r = &decision.resolved;
53 debug!(
54 temperature = ?r.temperature,
55 top_p = ?r.top_p,
56 top_k = ?r.top_k,
57 max_tokens = ?r.max_tokens,
58 presence_penalty = ?r.presence_penalty,
59 repeat_penalty = ?r.repeat_penalty,
60 min_p = ?r.min_p,
61 frequency_penalty = ?r.frequency_penalty,
62 dynatemp_range = ?r.dynatemp_range,
63 dynatemp_exponent = ?r.dynatemp_exponent,
64 top_n_sigma = ?r.top_n_sigma,
65 dry_multiplier = ?r.dry_multiplier,
66 dry_base = ?r.dry_base,
67 dry_allowed_length = ?r.dry_allowed_length,
68 dry_penalty_last_n = ?r.dry_penalty_last_n,
69 // Logged like the rest, and load-bearing in a way the rest are not: no
70 // readback will ever echo either of these, so this line and the
71 // provenance record are the only evidence of what was resolved. See ADR
72 // 0007 finding 7a — and the module docs for why that makes the position
73 // of this call part of the contract.
74 reasoning_effort = ?r.reasoning_effort,
75 reasoning_budget_tokens = ?r.reasoning_budget_tokens,
76 from = %decision.sources.describe(&decision.layer_names),
77 // Which class floor was used. `sources` says a value came from "floor"
78 // but not which one, and the explain surfaces cannot show this at all —
79 // they resolve stored configuration with no request in hand.
80 floor = decision.floor.label(),
81 // Reported separately from `from`, which still names the rung the
82 // temperature *would* have come from. The ceiling does not replace that
83 // rung, it caps what it supplied.
84 agentic_turn = decision.agentic_turn,
85 agentic_ceiling = decision.agentic_ceiling_applied,
86 "sampling resolved"
87 );
88}
89
90#[cfg(test)]
91#[path = "sampling_log_tests.rs"]
92mod sampling_log_tests;