Skip to main content

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;