mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-09 04:30:20 +08:00
Merge pull request #757 from zhefox/fix/responses-chat-reasoning-summary
fix(formats): degrade Responses reasoning summaries for Chat
This commit is contained in:
@@ -1680,6 +1680,11 @@ fn thinking_extension_key_is_cross_format_safe(
|
||||
FormatId::OpenAiChat | FormatId::ClaudeMessages | FormatId::GeminiGenerateContent,
|
||||
"openai_responses" | "openai_cli",
|
||||
"effort",
|
||||
) | (
|
||||
FormatId::OpenAiResponses | FormatId::OpenAiResponsesCompact,
|
||||
FormatId::OpenAiChat,
|
||||
"openai_responses" | "openai_cli",
|
||||
"summary",
|
||||
) | (
|
||||
FormatId::ClaudeMessages,
|
||||
_,
|
||||
@@ -2553,19 +2558,18 @@ fn validate_openai_responses_to_chat(
|
||||
});
|
||||
}
|
||||
}
|
||||
if let Some(reasoning) = object.get("reasoning").and_then(Value::as_object) {
|
||||
for field in ["summary", "budget_tokens"] {
|
||||
if reasoning.contains_key(field) {
|
||||
return Err(FormatError::LossyConversionBlocked {
|
||||
source_format: FormatId::OpenAiResponses.as_str().to_string(),
|
||||
target_format: FormatId::OpenAiChat.as_str().to_string(),
|
||||
field: format!("reasoning.{field}"),
|
||||
reason:
|
||||
"OpenAI Chat reasoning_effort cannot carry this Responses reasoning field"
|
||||
.to_string(),
|
||||
});
|
||||
}
|
||||
}
|
||||
if object
|
||||
.get("reasoning")
|
||||
.and_then(Value::as_object)
|
||||
.is_some_and(|reasoning| reasoning.contains_key("budget_tokens"))
|
||||
{
|
||||
return Err(FormatError::LossyConversionBlocked {
|
||||
source_format: FormatId::OpenAiResponses.as_str().to_string(),
|
||||
target_format: FormatId::OpenAiChat.as_str().to_string(),
|
||||
field: "reasoning.budget_tokens".to_string(),
|
||||
reason: "OpenAI Chat reasoning_effort cannot carry this Responses reasoning field"
|
||||
.to_string(),
|
||||
});
|
||||
}
|
||||
if let Some(tools) = object.get("tools").and_then(Value::as_array) {
|
||||
for tool in tools {
|
||||
@@ -5734,6 +5738,84 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pure_openai_responses_to_chat_drops_reasoning_summary() {
|
||||
for summary in ["auto", "concise", "detailed"] {
|
||||
let body = json!({
|
||||
"model": "gpt-5.6-sol",
|
||||
"input": [{"role": "user", "content": "hello"}],
|
||||
"reasoning": {
|
||||
"effort": "high",
|
||||
"summary": summary
|
||||
}
|
||||
});
|
||||
|
||||
let converted = convert_request_pure("openai:responses", "openai:chat", &body)
|
||||
.expect("Responses reasoning summary should be safely omitted for Chat")
|
||||
.value;
|
||||
|
||||
assert_eq!(converted["reasoning_effort"], "high");
|
||||
assert!(converted.get("reasoning").is_none());
|
||||
}
|
||||
|
||||
let summary_only = json!({
|
||||
"model": "gpt-5.6-sol",
|
||||
"input": "hello",
|
||||
"reasoning": {"summary": "auto"}
|
||||
});
|
||||
let converted = convert_request_pure("openai:responses", "openai:chat", &summary_only)
|
||||
.expect("Responses summary alone should be safely omitted for Chat")
|
||||
.value;
|
||||
|
||||
assert!(converted.get("reasoning_effort").is_none());
|
||||
assert!(converted.get("reasoning").is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn runtime_openai_responses_to_chat_drops_reasoning_summary() {
|
||||
let body = json!({
|
||||
"model": "deployment-alias",
|
||||
"input": "hello",
|
||||
"reasoning": {
|
||||
"effort": "high",
|
||||
"summary": "auto"
|
||||
}
|
||||
});
|
||||
|
||||
let converted = convert_request(
|
||||
"openai:responses",
|
||||
"openai:chat",
|
||||
&body,
|
||||
&FormatContext::default().with_mapped_model("qwen3.6-upstream-35004"),
|
||||
)
|
||||
.expect("runtime Responses summary should not block Chat provider body construction");
|
||||
|
||||
assert_eq!(converted["model"], "qwen3.6-upstream-35004");
|
||||
assert_eq!(converted["reasoning_effort"], "high");
|
||||
assert!(converted.get("reasoning").is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pure_openai_responses_to_chat_rejects_reasoning_budget_tokens() {
|
||||
let body = json!({
|
||||
"model": "gpt-5.6-sol",
|
||||
"input": [{"role": "user", "content": "hello"}],
|
||||
"reasoning": {
|
||||
"effort": "high",
|
||||
"budget_tokens": 4096
|
||||
}
|
||||
});
|
||||
|
||||
let error = convert_request_pure("openai:responses", "openai:chat", &body)
|
||||
.expect_err("Responses reasoning budget must remain fail-closed for Chat");
|
||||
|
||||
assert!(matches!(
|
||||
error,
|
||||
super::FormatError::LossyConversionBlocked { ref field, .. }
|
||||
if field == "reasoning.budget_tokens"
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn runtime_openai_cross_format_preserves_gpt_5_6_reasoning_efforts() {
|
||||
for target in ["openai:responses", "openai:responses:compact"] {
|
||||
|
||||
Reference in New Issue
Block a user