use anyhow::Result;
use serde_json::Value;
use crate::gate::{GateKind, PlanEntry, Transform};
use crate::ir::Request;
use crate::provider::Provider;
pub const TERSE_INSTRUCTION: &str = include_str!("../../prompts/output_terse.txt");
pub const DRAFT_INSTRUCTION: &str = include_str!("../../prompts/output_draft.txt");
pub const COMPACT_CODE_INSTRUCTION: &str = include_str!("../../prompts/output_compact_code.txt");
pub const TOKEN_BUDGET_TMPL: &str = include_str!("../../prompts/output_token_budget.txt");
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum OutputLevel {
Terse,
Draft,
}
impl OutputLevel {
pub fn parse(s: &str) -> Self {
match s.to_ascii_lowercase().as_str() {
"draft" | "cod" => OutputLevel::Draft,
_ => OutputLevel::Terse,
}
}
fn instruction(self) -> &'static str {
match self {
OutputLevel::Terse => TERSE_INSTRUCTION,
OutputLevel::Draft => DRAFT_INSTRUCTION,
}
}
}
pub struct OutputControlStage {
pub level: OutputLevel,
pub max_tokens: Option<u64>,
pub token_budget: Option<u64>,
pub compact_code: bool,
}
impl Transform for OutputControlStage {
fn name(&self) -> &str {
"output-control"
}
fn gate_kind(&self) -> GateKind {
GateKind::OutputShaping
}
fn apply(
&self,
req: &mut Request,
provider: &dyn Provider,
_plan: &mut Vec<PlanEntry>,
) -> Result<()> {
if !tool_call_shaped(req) {
provider.add_system_instruction(req, self.level.instruction());
if let Some(budget) = self.token_budget
&& !reasoning_model_request(req)
{
provider.add_system_instruction(
req,
&TOKEN_BUDGET_TMPL.replace("{budget}", &budget.to_string()),
);
}
if self.compact_code {
provider.add_system_instruction(req, COMPACT_CODE_INSTRUCTION);
}
}
if let Some(cap) = self.max_tokens
&& provider.max_tokens(req).is_none()
{
provider.set_max_tokens(req, cap);
}
Ok(())
}
}
fn tool_call_shaped(req: &Request) -> bool {
let raw = req.raw();
raw.get("tools")
.and_then(Value::as_array)
.is_some_and(|t| !t.is_empty())
&& raw.get("tool_choice").and_then(Value::as_str) != Some("none")
}
fn reasoning_model_request(req: &Request) -> bool {
let raw = req.raw();
raw.get("reasoning").is_some()
|| raw.get("reasoning_effort").is_some()
|| raw.get("thinking").is_some()
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ir::ProviderKind;
use crate::pipeline;
use crate::provider::OpenAiProvider;
use crate::tokenizer::counter_for;
use serde_json::json;
fn run_one(body: Value, stage: OutputControlStage) -> Request {
let mut req = Request::from_value(ProviderKind::OpenAi, body);
let counter = counter_for(ProviderKind::OpenAi, Some("gpt-4o")).unwrap();
let stages: Vec<Box<dyn Transform>> = vec![Box::new(stage)];
let _ = pipeline::run(&mut req, &OpenAiProvider, counter.as_ref(), &stages);
req
}
#[test]
fn level_parses() {
assert_eq!(OutputLevel::parse("draft"), OutputLevel::Draft);
assert_eq!(OutputLevel::parse("terse"), OutputLevel::Terse);
assert_eq!(OutputLevel::parse("ultra"), OutputLevel::Terse);
assert_eq!(OutputLevel::parse("whatever"), OutputLevel::Terse);
}
#[test]
fn draft_injects_chain_of_draft() {
let req = run_one(
json!({"messages":[{"role":"user","content":"hi"}]}),
OutputControlStage {
level: OutputLevel::Draft,
max_tokens: None,
token_budget: None,
compact_code: false,
},
);
let sys = req.get_str("/messages/0/content").unwrap();
assert!(sys.contains("draft") && sys.contains("step"));
}
#[test]
fn token_budget_injects_soft_limit() {
let req = run_one(
json!({"messages":[{"role":"user","content":"hi"}]}),
OutputControlStage {
level: OutputLevel::Terse,
max_tokens: None,
token_budget: Some(120),
compact_code: false,
},
);
let joined: String = req
.raw()
.pointer("/messages")
.and_then(Value::as_array)
.unwrap()
.iter()
.filter_map(|m| m.get("content").and_then(Value::as_str))
.collect();
assert!(joined.contains("120 tokens"), "soft budget injected");
}
#[test]
fn tool_call_request_skips_prose_shaping_but_keeps_cap() {
let req = run_one(
json!({"messages":[{"role":"user","content":"book a flight"}],
"tools":[{"type":"function","function":{"name":"book","parameters":{}}}],
"tool_choice":"auto"}),
OutputControlStage {
level: OutputLevel::Terse,
max_tokens: Some(900),
token_budget: Some(120),
compact_code: true,
},
);
let joined: String = req
.raw()
.pointer("/messages")
.and_then(Value::as_array)
.unwrap()
.iter()
.filter_map(|m| m.get("content").and_then(Value::as_str))
.collect();
assert!(
!joined.contains("concise") && !joined.contains("120 tokens"),
"no prose-shaping instructions on a tool-call request: {joined}"
);
assert_eq!(
req.raw()
.get("max_completion_tokens")
.and_then(Value::as_u64),
Some(900),
"hard cap still set (free)"
);
}
#[test]
fn tool_choice_none_restores_prose_shaping() {
let req = run_one(
json!({"messages":[{"role":"user","content":"book a flight"}],
"tools":[{"type":"function","function":{"name":"book","parameters":{}}}],
"tool_choice":"none"}),
OutputControlStage {
level: OutputLevel::Terse,
max_tokens: None,
token_budget: None,
compact_code: false,
},
);
let sys = req.get_str("/messages/0/content").unwrap();
assert!(sys.contains("concise"), "prose shaping applies: {sys}");
}
#[test]
fn terse_injects_concise() {
let req = run_one(
json!({"messages":[{"role":"user","content":"hi"}]}),
OutputControlStage {
level: OutputLevel::Terse,
max_tokens: None,
token_budget: None,
compact_code: false,
},
);
let sys = req.get_str("/messages/0/content").unwrap();
assert!(sys.contains("concise"));
assert_eq!(
req.raw()
.pointer("/messages/0/role")
.and_then(Value::as_str),
Some("system")
);
}
#[test]
fn sets_max_tokens_only_when_absent() {
let req = run_one(
json!({"messages":[{"role":"user","content":"hi"}]}),
OutputControlStage {
level: OutputLevel::Terse,
max_tokens: Some(256),
token_budget: None,
compact_code: false,
},
);
assert_eq!(OpenAiProvider.max_tokens(&req), Some(256));
let req2 = run_one(
json!({"max_tokens":99,"messages":[{"role":"user","content":"hi"}]}),
OutputControlStage {
level: OutputLevel::Terse,
max_tokens: Some(256),
token_budget: None,
compact_code: false,
},
);
assert_eq!(
OpenAiProvider.max_tokens(&req2),
Some(99),
"must not overwrite a caller-set cap"
);
}
fn joined_content(req: &Request) -> String {
req.raw()
.pointer("/messages")
.and_then(Value::as_array)
.unwrap()
.iter()
.filter_map(|m| m.get("content").and_then(Value::as_str))
.collect()
}
#[test]
fn reasoning_request_skips_soft_budget_keeps_terse_and_cap() {
let req = run_one(
json!({"model":"deepseek/deepseek-r1","reasoning":{"effort":"high"},
"messages":[{"role":"user","content":"hi"}]}),
OutputControlStage {
level: OutputLevel::Terse,
max_tokens: Some(256),
token_budget: Some(120),
compact_code: false,
},
);
let joined = joined_content(&req);
assert!(
!joined.contains("120 tokens"),
"soft budget must be skipped on a reasoning model: {joined}"
);
assert!(
joined.contains("concise"),
"terse instruction still injected: {joined}"
);
assert_eq!(OpenAiProvider.max_tokens(&req), Some(256), "hard cap stays");
}
#[test]
fn reasoning_field_skips_soft_budget() {
let req = run_one(
json!({"model":"some-model","reasoning":{"effort":"low"},
"messages":[{"role":"user","content":"hi"}]}),
OutputControlStage {
level: OutputLevel::Terse,
max_tokens: None,
token_budget: Some(120),
compact_code: false,
},
);
assert!(!joined_content(&req).contains("120 tokens"));
}
#[test]
fn non_reasoning_model_still_gets_soft_budget() {
let req = run_one(
json!({"model":"gpt-4o-mini",
"messages":[{"role":"user","content":"hi"}]}),
OutputControlStage {
level: OutputLevel::Terse,
max_tokens: None,
token_budget: Some(120),
compact_code: false,
},
);
assert!(
joined_content(&req).contains("120 tokens"),
"soft budget must still be injected on a non-reasoning model"
);
}
fn req_with(body: Value) -> Request {
Request::from_value(ProviderKind::OpenAi, body)
}
#[test]
fn detects_reasoning_request_fields() {
assert!(reasoning_model_request(&req_with(
json!({"model":"x","reasoning":{"effort":"low"}})
)));
assert!(reasoning_model_request(&req_with(
json!({"model":"x","reasoning_effort":"high"})
)));
assert!(reasoning_model_request(&req_with(
json!({"model":"claude-3-7-sonnet","thinking":{"type":"enabled","budget_tokens":1024}})
)));
}
#[test]
fn model_id_alone_never_marks_reasoning() {
for id in [
"deepseek/deepseek-r1",
"o1-mini",
"openai/gpt-5",
"qwen/qwq-32b",
"gpt-4o",
"phi-4",
"solar-pro",
] {
assert!(
!reasoning_model_request(&req_with(json!({"model": id}))),
"{id}: id-based detection must never fire (fields-only)"
);
}
assert!(!reasoning_model_request(&req_with(
json!({"messages":[{"role":"user","content":"hi"}]})
)));
}
#[test]
fn compact_code_injects_instruction() {
let req = run_one(
json!({"messages":[{"role":"user","content":"hi"}]}),
OutputControlStage {
level: OutputLevel::Terse,
max_tokens: None,
token_budget: None,
compact_code: true,
},
);
let joined: String = req
.raw()
.pointer("/messages")
.and_then(Value::as_array)
.unwrap()
.iter()
.filter_map(|m| m.get("content").and_then(Value::as_str))
.collect();
assert!(
joined.contains("minified"),
"compact-code instruction injected"
);
}
}