mirror of
https://github.com/fawney19/Aether.git
synced 2026-10-04 08:27:46 +08:00
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,
|
FormatId::OpenAiChat | FormatId::ClaudeMessages | FormatId::GeminiGenerateContent,
|
||||||
"openai_responses" | "openai_cli",
|
"openai_responses" | "openai_cli",
|
||||||
"effort",
|
"effort",
|
||||||
|
) | (
|
||||||
|
FormatId::OpenAiResponses | FormatId::OpenAiResponsesCompact,
|
||||||
|
FormatId::OpenAiChat,
|
||||||
|
"openai_responses" | "openai_cli",
|
||||||
|
"summary",
|
||||||
) | (
|
) | (
|
||||||
FormatId::ClaudeMessages,
|
FormatId::ClaudeMessages,
|
||||||
_,
|
_,
|
||||||
@@ -2553,19 +2558,18 @@ fn validate_openai_responses_to_chat(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let Some(reasoning) = object.get("reasoning").and_then(Value::as_object) {
|
if object
|
||||||
for field in ["summary", "budget_tokens"] {
|
.get("reasoning")
|
||||||
if reasoning.contains_key(field) {
|
.and_then(Value::as_object)
|
||||||
return Err(FormatError::LossyConversionBlocked {
|
.is_some_and(|reasoning| reasoning.contains_key("budget_tokens"))
|
||||||
source_format: FormatId::OpenAiResponses.as_str().to_string(),
|
{
|
||||||
target_format: FormatId::OpenAiChat.as_str().to_string(),
|
return Err(FormatError::LossyConversionBlocked {
|
||||||
field: format!("reasoning.{field}"),
|
source_format: FormatId::OpenAiResponses.as_str().to_string(),
|
||||||
reason:
|
target_format: FormatId::OpenAiChat.as_str().to_string(),
|
||||||
"OpenAI Chat reasoning_effort cannot carry this Responses reasoning field"
|
field: "reasoning.budget_tokens".to_string(),
|
||||||
.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) {
|
if let Some(tools) = object.get("tools").and_then(Value::as_array) {
|
||||||
for tool in tools {
|
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]
|
#[test]
|
||||||
fn runtime_openai_cross_format_preserves_gpt_5_6_reasoning_efforts() {
|
fn runtime_openai_cross_format_preserves_gpt_5_6_reasoning_efforts() {
|
||||||
for target in ["openai:responses", "openai:responses:compact"] {
|
for target in ["openai:responses", "openai:responses:compact"] {
|
||||||
|
|||||||
Reference in New Issue
Block a user