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]]
|
[[package]]
|
||||||
name = "cassady"
|
name = "cassady"
|
||||||
version = "0.3.3"
|
version = "0.3.4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "cassady"
|
name = "cassady"
|
||||||
version = "0.3.3"
|
version = "0.3.4"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
description = "Cassady/Cass minimal terminal coding agent"
|
description = "Cassady/Cass minimal terminal coding agent"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
|
|||||||
@@ -197,6 +197,8 @@ fn responses_body(
|
|||||||
body["reasoning"] = json!({"effort": "minimal", "summary": "auto"});
|
body["reasoning"] = json!({"effort": "minimal", "summary": "auto"});
|
||||||
} else if let Some(effort) = reasoning_effort.request_value() {
|
} else if let Some(effort) = reasoning_effort.request_value() {
|
||||||
body["reasoning"] = json!({"effort": effort, "summary": "auto"});
|
body["reasoning"] = json!({"effort": effort, "summary": "auto"});
|
||||||
|
} else if reasoning_effort == ReasoningEffort::Off {
|
||||||
|
body["reasoning"] = json!({"effort": "none", "summary": "auto"});
|
||||||
}
|
}
|
||||||
body
|
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]
|
#[test]
|
||||||
fn stream_parser_collects_text_and_function_call() {
|
fn stream_parser_collects_text_and_function_call() {
|
||||||
let (tx, _rx) = mpsc::unbounded_channel();
|
let (tx, _rx) = mpsc::unbounded_channel();
|
||||||
|
|||||||
@@ -28,11 +28,13 @@ impl ProviderClient {
|
|||||||
match config.active_provider.kind.as_str() {
|
match config.active_provider.kind.as_str() {
|
||||||
DEFAULT_PROVIDER_KIND => {
|
DEFAULT_PROVIDER_KIND => {
|
||||||
let api_key = config.resolved_api_key()?;
|
let api_key = config.resolved_api_key()?;
|
||||||
let reasoning_request_format = config
|
let model_metadata = config.model_metadata.as_ref();
|
||||||
.model_metadata
|
let reasoning_request_format = model_metadata
|
||||||
.as_ref()
|
|
||||||
.map(|model| model.reasoning.request_format)
|
.map(|model| model.reasoning.request_format)
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
let reasoning_supported = model_metadata
|
||||||
|
.map(|model| model.reasoning.supported)
|
||||||
|
.unwrap_or(false);
|
||||||
Ok(Self::OpenAiCompatible(OpenAiCompatibleProvider::new(
|
Ok(Self::OpenAiCompatible(OpenAiCompatibleProvider::new(
|
||||||
OpenAiCompatibleSettings {
|
OpenAiCompatibleSettings {
|
||||||
model: config.model.clone(),
|
model: config.model.clone(),
|
||||||
@@ -40,6 +42,7 @@ impl ProviderClient {
|
|||||||
api_key,
|
api_key,
|
||||||
reasoning_effort: options.reasoning_effort,
|
reasoning_effort: options.reasoning_effort,
|
||||||
reasoning_request_format,
|
reasoning_request_format,
|
||||||
|
reasoning_supported,
|
||||||
},
|
},
|
||||||
)))
|
)))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ pub struct OpenAiCompatibleProvider {
|
|||||||
api_key: String,
|
api_key: String,
|
||||||
reasoning_effort: ReasoningEffort,
|
reasoning_effort: ReasoningEffort,
|
||||||
reasoning_request_format: ReasoningRequestFormat,
|
reasoning_request_format: ReasoningRequestFormat,
|
||||||
|
reasoning_supported: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
@@ -27,6 +28,7 @@ pub struct OpenAiCompatibleSettings {
|
|||||||
pub api_key: String,
|
pub api_key: String,
|
||||||
pub reasoning_effort: ReasoningEffort,
|
pub reasoning_effort: ReasoningEffort,
|
||||||
pub reasoning_request_format: ReasoningRequestFormat,
|
pub reasoning_request_format: ReasoningRequestFormat,
|
||||||
|
pub reasoning_supported: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
@@ -45,6 +47,7 @@ impl OpenAiCompatibleProvider {
|
|||||||
api_key: settings.api_key,
|
api_key: settings.api_key,
|
||||||
reasoning_effort: settings.reasoning_effort,
|
reasoning_effort: settings.reasoning_effort,
|
||||||
reasoning_request_format: settings.reasoning_request_format,
|
reasoning_request_format: settings.reasoning_request_format,
|
||||||
|
reasoning_supported: settings.reasoning_supported,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,6 +68,7 @@ impl OpenAiCompatibleProvider {
|
|||||||
&mut body,
|
&mut body,
|
||||||
self.reasoning_effort,
|
self.reasoning_effort,
|
||||||
self.reasoning_request_format,
|
self.reasoning_request_format,
|
||||||
|
self.reasoning_supported,
|
||||||
);
|
);
|
||||||
let resp = self
|
let resp = self
|
||||||
.client
|
.client
|
||||||
@@ -219,9 +223,17 @@ fn apply_reasoning_request(
|
|||||||
body: &mut Value,
|
body: &mut Value,
|
||||||
effort: ReasoningEffort,
|
effort: ReasoningEffort,
|
||||||
format: ReasoningRequestFormat,
|
format: ReasoningRequestFormat,
|
||||||
|
supported: bool,
|
||||||
) {
|
) {
|
||||||
let Some(effort) = effort.request_value() else {
|
if !supported {
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
let effort_str = match effort {
|
||||||
|
ReasoningEffort::Off => "none",
|
||||||
|
_ => match effort.request_value() {
|
||||||
|
Some(value) => value,
|
||||||
|
None => return,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
let Value::Object(obj) = body else {
|
let Value::Object(obj) = body else {
|
||||||
return;
|
return;
|
||||||
@@ -230,11 +242,11 @@ fn apply_reasoning_request(
|
|||||||
ReasoningRequestFormat::ReasoningEffort => {
|
ReasoningRequestFormat::ReasoningEffort => {
|
||||||
obj.insert(
|
obj.insert(
|
||||||
"reasoning_effort".to_string(),
|
"reasoning_effort".to_string(),
|
||||||
Value::String(effort.to_string()),
|
Value::String(effort_str.to_string()),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
ReasoningRequestFormat::ReasoningObject => {
|
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('/'))
|
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