Skip to main content

lean_ctx/proxy/
verbosity.rs

1//! Cache-safe wire verbosity steer (#895 Track B).
2//!
3//! Opt-in (`proxy.verbosity_steer`). When on, the proxy appends a single
4//! **constant** "be concise" instruction to the *last user turn* of each
5//! request. Output-shaping for non-rules-aware clients: lean-ctx already steers
6//! verbosity through rules injection for editors that load rules, but raw API
7//! clients (and the holdout's treatment arm) need it on the wire.
8//!
9//! Cache-safety, by construction:
10//! - The steer is **constant** (identical bytes every turn), so it never adds
11//!   per-turn entropy.
12//! - It is appended **after** all existing content of the last user turn — i.e.
13//!   strictly after the last `cache_control` breakpoint — and never modifies a
14//!   `cache_control`-anchored block. The provider's cached prefix (everything up
15//!   to the last breakpoint) stays byte-identical across turns (#448/#498); only
16//!   the always-reprocessed tail grows by the constant suffix.
17//! - For array content the suffix is a **new** trailing text element; existing
18//!   blocks (including cache anchors) are left untouched. For plain-string
19//!   content (OpenAI prefix-cached; Anthropic strings carry no `cache_control`)
20//!   the suffix is concatenated.
21//! - Idempotent: if the steer is already present it is not appended again.
22
23use serde_json::{Map, Value};
24
25/// The constant verbosity instruction. Kept short and directive; its byte
26/// stability is what makes the steer prompt-cache-safe.
27pub const STEER: &str =
28    "Be concise: answer directly and do not restate the question or surrounding context.";
29
30/// Suffix form for plain-string content (leading separation from prior text).
31fn string_suffix() -> String {
32    format!("\n\n{STEER}")
33}
34
35/// True when `text` already ends with the steer, so we never double-append
36/// (keeps the rewrite idempotent and deterministic).
37fn already_steered(text: &str) -> bool {
38    text.trim_end().ends_with(STEER)
39}
40
41/// A `{ "type": "text", "text": STEER }` block for array content.
42fn steer_text_block() -> Value {
43    let mut m = Map::new();
44    m.insert("type".to_string(), Value::String("text".to_string()));
45    m.insert("text".to_string(), Value::String(STEER.to_string()));
46    Value::Object(m)
47}
48
49/// A `{ "text": STEER }` part for Gemini `parts` arrays.
50fn steer_gemini_part() -> Value {
51    let mut m = Map::new();
52    m.insert("text".to_string(), Value::String(STEER.to_string()));
53    Value::Object(m)
54}
55
56/// Append the steer to a content value that is either a plain string or an array
57/// of text blocks. `block` builds the trailing element for the array case.
58/// Returns whether the value changed.
59fn append_to_content(content: &mut Value, block: impl FnOnce() -> Value) -> bool {
60    match content {
61        Value::String(s) => {
62            if already_steered(s) {
63                return false;
64            }
65            s.push_str(&string_suffix());
66            true
67        }
68        Value::Array(parts) => {
69            // Already steered if the last text element is the steer.
70            let last_text = parts
71                .iter()
72                .rev()
73                .find_map(|p| p.get("text").and_then(Value::as_str));
74            if last_text.is_some_and(already_steered) {
75                return false;
76            }
77            parts.push(block());
78            true
79        }
80        _ => false,
81    }
82}
83
84/// Index of the last message in `messages` whose `role` matches.
85fn last_role_index(messages: &[Value], role: &str) -> Option<usize> {
86    messages
87        .iter()
88        .rposition(|m| m.get("role").and_then(Value::as_str) == Some(role))
89}
90
91/// Append the steer to the last user message of an Anthropic `/v1/messages`
92/// body. No-op (returns false) when there is no user turn.
93pub fn apply_anthropic(doc: &mut Value) -> bool {
94    let Some(messages) = doc.get_mut("messages").and_then(Value::as_array_mut) else {
95        return false;
96    };
97    let Some(idx) = last_role_index(messages, "user") else {
98        return false;
99    };
100    let Some(content) = messages[idx].get_mut("content") else {
101        return false;
102    };
103    let changed = append_to_content(content, steer_text_block);
104    if changed {
105        record();
106    }
107    changed
108}
109
110/// Append the steer to the last user message of an OpenAI Chat Completions body.
111/// OpenAI prefix-caches automatically, so appending to the newest turn never
112/// disturbs a previously cached prefix.
113pub fn apply_openai_chat(doc: &mut Value) -> bool {
114    let Some(messages) = doc.get_mut("messages").and_then(Value::as_array_mut) else {
115        return false;
116    };
117    let Some(idx) = last_role_index(messages, "user") else {
118        return false;
119    };
120    let Some(content) = messages[idx].get_mut("content") else {
121        return false;
122    };
123    let changed = append_to_content(content, steer_text_block);
124    if changed {
125        record();
126    }
127    changed
128}
129
130/// Append the steer to the last user item of an OpenAI Responses body. `input`
131/// is either a plain string or an array of role/content items.
132pub fn apply_openai_responses(doc: &mut Value) -> bool {
133    let changed = match doc.get_mut("input") {
134        Some(Value::String(s)) => {
135            if already_steered(s) {
136                false
137            } else {
138                s.push_str(&string_suffix());
139                true
140            }
141        }
142        Some(Value::Array(items)) => {
143            let Some(idx) = last_role_index(items, "user") else {
144                return false;
145            };
146            match items[idx].get_mut("content") {
147                Some(content) => append_to_content(content, steer_text_block),
148                None => false,
149            }
150        }
151        _ => false,
152    };
153    if changed {
154        record();
155    }
156    changed
157}
158
159/// Append the steer to the last user turn of a Gemini `generateContent` body
160/// (`contents[].parts[]`).
161pub fn apply_google(doc: &mut Value) -> bool {
162    let Some(contents) = doc.get_mut("contents").and_then(Value::as_array_mut) else {
163        return false;
164    };
165    let Some(idx) = last_role_index(contents, "user") else {
166        return false;
167    };
168    let Some(parts) = contents[idx].get_mut("parts").and_then(Value::as_array_mut) else {
169        return false;
170    };
171    let last_text = parts
172        .iter()
173        .rev()
174        .find_map(|p| p.get("text").and_then(Value::as_str));
175    if last_text.is_some_and(already_steered) {
176        return false;
177    }
178    parts.push(steer_gemini_part());
179    record();
180    true
181}
182
183use std::sync::atomic::{AtomicU64, Ordering};
184
185/// Count of requests steered, surfaced via [`steered_count`] for `/status`.
186static STEERED: AtomicU64 = AtomicU64::new(0);
187
188fn record() {
189    STEERED.fetch_add(1, Ordering::Relaxed);
190}
191
192/// Total requests that received a wire verbosity steer (telemetry).
193#[must_use]
194pub fn steered_count() -> u64 {
195    STEERED.load(Ordering::Relaxed)
196}
197
198#[cfg(test)]
199mod tests {
200    use super::*;
201    use serde_json::json;
202
203    #[test]
204    fn anthropic_appends_block_to_last_user() {
205        let mut doc = json!({
206            "messages": [
207                {"role": "user", "content": "first"},
208                {"role": "assistant", "content": "ok"},
209                {"role": "user", "content": [{"type": "text", "text": "second"}]}
210            ]
211        });
212        assert!(apply_anthropic(&mut doc));
213        let last = &doc["messages"][2]["content"];
214        let arr = last.as_array().unwrap();
215        assert_eq!(arr.len(), 2, "new steer block appended");
216        assert_eq!(arr[1]["text"], STEER);
217        // The earlier user turn is untouched.
218        assert_eq!(doc["messages"][0]["content"], "first");
219    }
220
221    #[test]
222    fn anthropic_string_content_concatenates() {
223        let mut doc = json!({"messages": [{"role": "user", "content": "hello"}]});
224        assert!(apply_anthropic(&mut doc));
225        let s = doc["messages"][0]["content"].as_str().unwrap();
226        assert!(s.starts_with("hello"));
227        assert!(s.ends_with(STEER));
228    }
229
230    #[test]
231    fn never_modifies_cache_control_blocks() {
232        let mut doc = json!({
233            "messages": [{
234                "role": "user",
235                "content": [
236                    {"type": "text", "text": "cached", "cache_control": {"type": "ephemeral"}}
237                ]
238            }]
239        });
240        assert!(apply_anthropic(&mut doc));
241        let arr = doc["messages"][0]["content"].as_array().unwrap();
242        // The cache-anchored block survives verbatim; the steer is a NEW block
243        // appended strictly after it.
244        assert_eq!(arr.len(), 2);
245        assert!(arr[0].get("cache_control").is_some());
246        assert_eq!(arr[0]["text"], "cached");
247        assert_eq!(arr[1]["text"], STEER);
248        assert!(arr[1].get("cache_control").is_none());
249    }
250
251    #[test]
252    fn idempotent_no_double_append() {
253        let mut doc = json!({"messages": [{"role": "user", "content": "hi"}]});
254        assert!(apply_anthropic(&mut doc));
255        let once = doc.clone();
256        assert!(!apply_anthropic(&mut doc), "second pass is a no-op");
257        assert_eq!(doc, once, "byte-identical after a redundant pass");
258    }
259
260    #[test]
261    fn deterministic_constant_suffix() {
262        let mk = || json!({"messages": [{"role": "user", "content": "ask"}]});
263        let mut a = mk();
264        let mut b = mk();
265        apply_anthropic(&mut a);
266        apply_anthropic(&mut b);
267        assert_eq!(a, b, "constant steer ⇒ byte-identical rewrite");
268    }
269
270    #[test]
271    fn openai_chat_appends_to_last_user() {
272        let mut doc = json!({
273            "messages": [
274                {"role": "system", "content": "sys"},
275                {"role": "user", "content": "q"}
276            ]
277        });
278        assert!(apply_openai_chat(&mut doc));
279        let s = doc["messages"][1]["content"].as_str().unwrap();
280        assert!(s.ends_with(STEER));
281        assert_eq!(doc["messages"][0]["content"], "sys", "system untouched");
282    }
283
284    #[test]
285    fn responses_string_and_array_input() {
286        let mut s = json!({"input": "plain"});
287        assert!(apply_openai_responses(&mut s));
288        assert!(s["input"].as_str().unwrap().ends_with(STEER));
289
290        let mut a =
291            json!({"input": [{"role": "user", "content": [{"type": "text", "text": "q"}]}]});
292        assert!(apply_openai_responses(&mut a));
293        let parts = a["input"][0]["content"].as_array().unwrap();
294        assert_eq!(parts.last().unwrap()["text"], STEER);
295    }
296
297    #[test]
298    fn google_appends_part_to_last_user() {
299        let mut doc = json!({
300            "contents": [
301                {"role": "user", "parts": [{"text": "q1"}]},
302                {"role": "model", "parts": [{"text": "a1"}]},
303                {"role": "user", "parts": [{"text": "q2"}]}
304            ]
305        });
306        assert!(apply_google(&mut doc));
307        let parts = doc["contents"][2]["parts"].as_array().unwrap();
308        assert_eq!(parts.len(), 2);
309        assert_eq!(parts[1]["text"], STEER);
310    }
311
312    #[test]
313    fn no_user_turn_is_noop() {
314        let mut doc = json!({"messages": [{"role": "assistant", "content": "x"}]});
315        assert!(!apply_anthropic(&mut doc));
316    }
317}