Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 10 additions & 10 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 4 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -70,10 +70,10 @@ opt-level = 3
# never re-exported through a local shim. And the reverse: something only
# this side uses (the at-rest crypto, IMDSv2 credentials, the gateway error)
# lives here, not in core.
tw-bedrock = { git = "https://github.com/ThinkWatchProject/ThinkWatch-Core.git", tag = "v0.81.0" }
tw-breaker = { git = "https://github.com/ThinkWatchProject/ThinkWatch-Core.git", tag = "v0.81.0" }
tw-dialect = { git = "https://github.com/ThinkWatchProject/ThinkWatch-Core.git", tag = "v0.81.0" }
tw-guard = { git = "https://github.com/ThinkWatchProject/ThinkWatch-Core.git", tag = "v0.81.0" }
tw-bedrock = { git = "https://github.com/ThinkWatchProject/ThinkWatch-Core.git", rev = "14f52f25a434faf1e7336746b57c9ad8d8aebb96" }
tw-breaker = { git = "https://github.com/ThinkWatchProject/ThinkWatch-Core.git", rev = "14f52f25a434faf1e7336746b57c9ad8d8aebb96" }
tw-dialect = { git = "https://github.com/ThinkWatchProject/ThinkWatch-Core.git", rev = "14f52f25a434faf1e7336746b57c9ad8d8aebb96" }
tw-guard = { git = "https://github.com/ThinkWatchProject/ThinkWatch-Core.git", rev = "14f52f25a434faf1e7336746b57c9ad8d8aebb96" }

# Web framework
axum = { version = "0.8", features = ["macros", "ws"] }
Expand Down
4 changes: 4 additions & 0 deletions crates/common/src/models/provider.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,10 @@ pub struct Model {
/// limit.
#[serde(default)]
pub max_output_tokens: Option<i32>,
/// The reasoning setting: `off`, or a level (`low` … `max`) for
/// requests whose reasoning is on. `None` ⇒ requests go out as sent.
#[serde(default)]
pub reasoning_effort: Option<String>,
}

fn default_enabled() -> bool {
Expand Down
56 changes: 52 additions & 4 deletions crates/gateway/src/lifecycle/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,9 @@ pub(crate) struct Sent {
/// Waiting to go out again: the attempt that failed was counted
/// against the route already, and nothing is under way.
pub waiting: bool,
/// The attempt under way sent a different reasoning effort than the
/// caller asked (see `proxy::effort`).
pub effort: Option<crate::proxy::effort::EffortChange>,
}

/// Build the streaming pump: forward the upstream's bytes to the caller —
Expand Down Expand Up @@ -284,6 +287,7 @@ pub(crate) fn build_chat_pump(
retries: 0,
last_sent: std::time::Instant::now(),
waiting: false,
effort: None,
}));
// Gemini clients get no keep-alive comments: the google-genai SDK reads
// every SSE line as JSON.
Expand Down Expand Up @@ -314,7 +318,7 @@ pub(crate) fn build_chat_pump(
}
let sent_at = std::time::Instant::now();
if let Ok(mut s) = sent.lock() {
*s = Sent { retries: resend.budget.retries(), last_sent: sent_at, waiting: false };
*s = Sent { retries: resend.budget.retries(), last_sent: sent_at, waiting: false, effort: None };
}
// What an attempt that failed had read is not this one's: its
// usage is not billed, its error not the request's.
Expand Down Expand Up @@ -365,6 +369,9 @@ pub(crate) fn build_chat_pump(
return;
}
};
if let Ok(mut s) = sent.lock() {
s.effort = wire.effort;
}
if let Ok(mut r) = readers.lock() {
*r = Readers {
sniffer: Some(tw_dialect::usage::Sniffer::new()),
Expand Down Expand Up @@ -683,6 +690,7 @@ pub(crate) fn build_chat_pump(
retries: 0,
last_sent: started_at,
waiting: false,
effort: None,
});
let invoked = Invoked {
client_ip: identity.ip_address.clone(),
Expand Down Expand Up @@ -1099,6 +1107,7 @@ fn resending(
retries: retry,
last_sent: std::time::Instant::now(),
waiting: true,
effort: None,
};
}
tracing::info!(
Expand Down Expand Up @@ -1524,9 +1533,12 @@ impl Surface for ChatCompletionSurface {
deps.request.request_started_at.elapsed().as_millis() as i64,
logged_status,
with_usage_detail(
retried_detail(
pinned_detail(error_detail, deps.route.pinned),
deps.route.sel_record.retries,
effort_detail(
retried_detail(
pinned_detail(error_detail, deps.route.pinned),
deps.route.sel_record.retries,
),
deps.route.sel_record.effort,
),
&usage,
usage_estimated(&invoked.view),
Expand Down Expand Up @@ -1594,6 +1606,23 @@ fn retried_detail(detail: Option<serde_json::Value>, retries: u32) -> Option<ser
Some(serde_json::Value::Object(d))
}

/// The audit detail, with `"effort"` when the hop that answered sent a
/// different reasoning effort than the caller asked (see `proxy::effort`).
fn effort_detail(
detail: Option<serde_json::Value>,
effort: Option<crate::proxy::effort::EffortChange>,
) -> Option<serde_json::Value> {
let Some(effort) = effort else {
return detail;
};
let mut d = match detail {
Some(serde_json::Value::Object(d)) => d,
_ => serde_json::Map::new(),
};
d.insert("effort".into(), effort.detail());
Some(serde_json::Value::Object(d))
}

/// The usage a captured view bills — shared by `record_usage` and
/// `emit_audit` so the budget and the audit row can never disagree.
fn extract_usage(view: &CapturedView<ChatCompletionSurface>) -> tw_dialect::usage::Usage {
Expand Down Expand Up @@ -1822,6 +1851,7 @@ mod tests {
retries: 0,
last_sent: std::time::Instant::now(),
waiting: false,
effort: None,
});
resending(
"up",
Expand Down Expand Up @@ -1987,6 +2017,24 @@ mod tests {
assert!(r.sealed_refusal().is_none());
}

#[test]
fn a_changed_reasoning_effort_is_said_on_the_log_row() {
use crate::proxy::effort::{EffortChange, Reason};
assert_eq!(effort_detail(None, None), None);
let change = EffortChange {
asked: "xhigh",
sent: Some("high"),
reason: Reason::Rejected,
};
assert_eq!(
effort_detail(Some(serde_json::json!({"retries": 1})), Some(change)),
Some(serde_json::json!({
"retries": 1,
"effort": {"asked": "xhigh", "sent": "high", "reason": "rejected"},
}))
);
}

#[test]
fn a_pinned_route_is_said_on_the_log_row() {
assert_eq!(pinned_detail(None, false), None);
Expand Down
Loading
Loading