Expand description
The ordered request-shaping pipeline, and the one statement of its order.
§The stages
| # | Stage | Lives in | Reads |
|---|---|---|---|
| 1 | Strip prior reasoning | super::messages | messages |
| 2 | Coalesce for capabilities | super::messages | messages |
| 2b | Strip unsupported tools | super::tools | tools, capabilities |
| 3 | Truncate stale history | super::truncation | messages, payload size |
| 4 | Resolve the sampling hierarchy | super::sampling | top-level keys |
| 5 | Pin cache_prompt | super::sampling | top-level keys |
| 5b | Suppress an unreadable reasoning_effort | super::effort_gate | resolved effort, template caps |
| — | Log the sampling decision | super::sampling_log | the decision |
| 6 | Constrain dialect tool calls | super::constrain | tools, tool_choice, tags |
§The order is load-bearing
1 before 2. Coalescing merges message content. Stripping afterwards
would have to find and excise <think> blocks inside text that has already
been concatenated with "\n\n" separators from other turns.
2 before 3. Both stages 1 and 2 only ever shrink the body, and stage 3 measures it. Truncating first would size its budget against bytes that were about to be discarded anyway, and trim history that did not need trimming. The same goes for stage 2b: a stripped tools array must not count against the truncation budget.
3 before 4. Stage 3 measures the payload; stage 4 inserts up to seven sampling keys. Resolving sampling first would have truncation size its budget against keys the client never sent. The margin is small, but it is the difference between measuring the conversation and measuring our own additions to it.
4 before 5. cache_prompt is not an InferenceConfig field, so
pinning it last means the resolved sampling patch can never overwrite it.
5b after 4. Stage 5b deletes a reasoning_effort the model’s observed
template never reads, and the value it has to catch is usually not the
client’s. It arrives from the ladder — a :high profile, a per-model
default, a global setting — which does not exist until stage 4 has folded
it. Placed at 2b beside the tool strip, where the capability shape is
otherwise identical, the gate would delete the client’s key and stage 4
would then force-insert gglib’s own resolved level straight past it (the
patch is inserted, not merged), so the case that matters most would
sail through untouched while the tests still passed on a client-sent
level. The stage runs after 4 for the same reason it takes
&mut SamplingDecision: it can only suppress a value once something has
resolved one, and it must correct that decision’s own record when it does.
a_ladder_supplied_effort_is_suppressed_not_just_a_client_one fails if
this ever moves.
6 after 3. The grammar stage 6 adds a top-level key, so it runs after the measurement for the same reason sampling does: the truncation budget measures the client’s conversation, not our own additions to it.
§Why the seam is &mut Value and not a typed request struct
The proxy forwards requests from arbitrary external clients — IDE
extensions, gateways — which send OpenAI parameters this workspace has
never heard of. Round-tripping through a typed ChatRequest would silently
drop every field the struct does not model: a passthrough regression that
is invisible in tests and painful in the field. Mutating a Value in place
preserves them by construction. The adapter builds its body with json! and
already holds a Value, so this is also the cheaper side for it.
§One pipeline, two callers, no second route
Every request path calls apply. The proxy used to run the stages by hand
with its own truncation pass spliced between them, because truncation gated
on the payload’s size in wire bytes and could reject the request with an
axum response — neither of which fits here. Measuring the serialized
Value and returning a domain error removed both obstacles, so there is now
exactly one implementation of the order above and nothing to keep in sync.
Structs§
- Pipeline
Report - What the pipeline did, for the caller that has to report or verify it.
Functions§
- apply
- Apply every request-shaping transform, in order, in place.