Send reasoning effort 'none' when reasoning is off
CI / Build (push) Waiting to run
CI / Test (push) Waiting to run
CI / Build (push) Waiting to run
CI / Test (push) Waiting to run
When a reasoning-capable model has reasoning effort set to off, send 'none' rather than omitting the field or sending 'off'. This applies to both the ChatGPT Codex Responses API and OpenAI-compatible chat completions (both reasoning_effort and reasoning object formats). Non-reasoning models continue to send no reasoning field at all. Bumps version to 0.3.4.
This commit is contained in:
Generated
+1
-1
@@ -226,7 +226,7 @@ checksum = "8ae3f5d315924270530207e2a68396c3cc547f6dca3fbdca317cfb1a51edb593"
|
||||
|
||||
[[package]]
|
||||
name = "cassady"
|
||||
version = "0.3.3"
|
||||
version = "0.3.4"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "cassady"
|
||||
version = "0.3.3"
|
||||
version = "0.3.4"
|
||||
edition = "2021"
|
||||
description = "Cassady/Cass minimal terminal coding agent"
|
||||
license = "MIT"
|
||||
|
||||
@@ -197,6 +197,8 @@ fn responses_body(
|
||||
body["reasoning"] = json!({"effort": "minimal", "summary": "auto"});
|
||||
} else if let Some(effort) = reasoning_effort.request_value() {
|
||||
body["reasoning"] = json!({"effort": effort, "summary": "auto"});
|
||||
} else if reasoning_effort == ReasoningEffort::Off {
|
||||
body["reasoning"] = json!({"effort": "none", "summary": "auto"});
|
||||
}
|
||||
body
|
||||
}
|
||||
@@ -504,6 +506,24 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn responses_body_sends_none_effort_when_reasoning_is_off() {
|
||||
let body = responses_body(
|
||||
"gpt-test",
|
||||
vec![ModelMessage::User {
|
||||
content: "hello".into(),
|
||||
}],
|
||||
Vec::new(),
|
||||
ReasoningEffort::Off,
|
||||
false,
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
body["reasoning"],
|
||||
json!({"effort": "none", "summary": "auto"})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn stream_parser_collects_text_and_function_call() {
|
||||
let (tx, _rx) = mpsc::unbounded_channel();
|
||||
|
||||
@@ -28,11 +28,13 @@ impl ProviderClient {
|
||||
match config.active_provider.kind.as_str() {
|
||||
DEFAULT_PROVIDER_KIND => {
|
||||
let api_key = config.resolved_api_key()?;
|
||||
let reasoning_request_format = config
|
||||
.model_metadata
|
||||
.as_ref()
|
||||
let model_metadata = config.model_metadata.as_ref();
|
||||
let reasoning_request_format = model_metadata
|
||||
.map(|model| model.reasoning.request_format)
|
||||
.unwrap_or_default();
|
||||
let reasoning_supported = model_metadata
|
||||
.map(|model| model.reasoning.supported)
|
||||
.unwrap_or(false);
|
||||
Ok(Self::OpenAiCompatible(OpenAiCompatibleProvider::new(
|
||||
OpenAiCompatibleSettings {
|
||||
model: config.model.clone(),
|
||||
@@ -40,6 +42,7 @@ impl ProviderClient {
|
||||
api_key,
|
||||
reasoning_effort: options.reasoning_effort,
|
||||
reasoning_request_format,
|
||||
reasoning_supported,
|
||||
},
|
||||
)))
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ pub struct OpenAiCompatibleProvider {
|
||||
api_key: String,
|
||||
reasoning_effort: ReasoningEffort,
|
||||
reasoning_request_format: ReasoningRequestFormat,
|
||||
reasoning_supported: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
@@ -27,6 +28,7 @@ pub struct OpenAiCompatibleSettings {
|
||||
pub api_key: String,
|
||||
pub reasoning_effort: ReasoningEffort,
|
||||
pub reasoning_request_format: ReasoningRequestFormat,
|
||||
pub reasoning_supported: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
@@ -45,6 +47,7 @@ impl OpenAiCompatibleProvider {
|
||||
api_key: settings.api_key,
|
||||
reasoning_effort: settings.reasoning_effort,
|
||||
reasoning_request_format: settings.reasoning_request_format,
|
||||
reasoning_supported: settings.reasoning_supported,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,6 +68,7 @@ impl OpenAiCompatibleProvider {
|
||||
&mut body,
|
||||
self.reasoning_effort,
|
||||
self.reasoning_request_format,
|
||||
self.reasoning_supported,
|
||||
);
|
||||
let resp = self
|
||||
.client
|
||||
@@ -219,9 +223,17 @@ fn apply_reasoning_request(
|
||||
body: &mut Value,
|
||||
effort: ReasoningEffort,
|
||||
format: ReasoningRequestFormat,
|
||||
supported: bool,
|
||||
) {
|
||||
let Some(effort) = effort.request_value() else {
|
||||
if !supported {
|
||||
return;
|
||||
}
|
||||
let effort_str = match effort {
|
||||
ReasoningEffort::Off => "none",
|
||||
_ => match effort.request_value() {
|
||||
Some(value) => value,
|
||||
None => return,
|
||||
},
|
||||
};
|
||||
let Value::Object(obj) = body else {
|
||||
return;
|
||||
@@ -230,11 +242,11 @@ fn apply_reasoning_request(
|
||||
ReasoningRequestFormat::ReasoningEffort => {
|
||||
obj.insert(
|
||||
"reasoning_effort".to_string(),
|
||||
Value::String(effort.to_string()),
|
||||
Value::String(effort_str.to_string()),
|
||||
);
|
||||
}
|
||||
ReasoningRequestFormat::ReasoningObject => {
|
||||
obj.insert("reasoning".to_string(), json!({ "effort": effort }));
|
||||
obj.insert("reasoning".to_string(), json!({ "effort": effort_str }));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -316,3 +328,60 @@ fn chat_url(base: &str) -> String {
|
||||
format!("{}/chat/completions", base.trim_end_matches('/'))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn reasoning_effort_format_sends_none_when_off_and_supported() {
|
||||
let mut body = json!({"model": "test"});
|
||||
apply_reasoning_request(
|
||||
&mut body,
|
||||
ReasoningEffort::Off,
|
||||
ReasoningRequestFormat::ReasoningEffort,
|
||||
true,
|
||||
);
|
||||
assert_eq!(
|
||||
body["reasoning_effort"],
|
||||
Value::String("none".to_string())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reasoning_object_format_sends_none_when_off_and_supported() {
|
||||
let mut body = json!({"model": "test"});
|
||||
apply_reasoning_request(
|
||||
&mut body,
|
||||
ReasoningEffort::Off,
|
||||
ReasoningRequestFormat::ReasoningObject,
|
||||
true,
|
||||
);
|
||||
assert_eq!(body["reasoning"], json!({ "effort": "none" }));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reasoning_sends_nothing_when_unsupported_even_if_off() {
|
||||
let mut body = json!({"model": "test"});
|
||||
apply_reasoning_request(
|
||||
&mut body,
|
||||
ReasoningEffort::Off,
|
||||
ReasoningRequestFormat::ReasoningEffort,
|
||||
false,
|
||||
);
|
||||
assert!(body.get("reasoning_effort").is_none());
|
||||
assert!(body.get("reasoning").is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reasoning_sends_nothing_when_unsupported_even_if_high() {
|
||||
let mut body = json!({"model": "test"});
|
||||
apply_reasoning_request(
|
||||
&mut body,
|
||||
ReasoningEffort::High,
|
||||
ReasoningRequestFormat::ReasoningObject,
|
||||
false,
|
||||
);
|
||||
assert!(body.get("reasoning").is_none());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user