mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-02 01:10:23 +08:00
feat(conversion): 支持 Claude output_config.effort 跨格式转换,新增 xhigh 档位
- 新增 Claude output_config.effort 与标准化 reasoning_effort 的双向映射 - 新增 xhigh 档位(budget_tokens=8192),对应 Claude effort=max - reasoning_effort 独立于 thinking 存入 extra,支持无 thinking 场景的跨格式传递 - OpenAI/Responses API 输出时 xhigh 自动降级为 high
This commit is contained in:
@@ -130,15 +130,34 @@ REASONING_EFFORT_TO_THINKING_BUDGET: dict[str, int] = {
|
||||
"low": 1280,
|
||||
"medium": 2048,
|
||||
"high": 4096,
|
||||
"xhigh": 8192,
|
||||
}
|
||||
|
||||
# thinking budget_tokens -> OpenAI reasoning_effort(反向映射,取最近区间)
|
||||
THINKING_BUDGET_TO_REASONING_EFFORT: list[tuple[int, str]] = [
|
||||
(1664, "low"), # <= 1664 -> low (midpoint of 1280..2048)
|
||||
(3072, "medium"), # <= 3072 -> medium (midpoint of 2048..4096)
|
||||
(2**31, "high"), # > 3072 -> high
|
||||
(6144, "high"), # <= 6144 -> high (midpoint of 4096..8192)
|
||||
(2**31, "xhigh"), # > 6144 -> xhigh
|
||||
]
|
||||
|
||||
# Claude output_config.effort -> 标准化 reasoning_effort
|
||||
CLAUDE_EFFORT_TO_REASONING_EFFORT: dict[str, str] = {
|
||||
"low": "low",
|
||||
"medium": "medium",
|
||||
"high": "high",
|
||||
"max": "xhigh",
|
||||
# "auto" 不映射,让目标格式使用默认行为
|
||||
}
|
||||
|
||||
# 标准化 reasoning_effort -> Claude output_config.effort
|
||||
REASONING_EFFORT_TO_CLAUDE_EFFORT: dict[str, str] = {
|
||||
"low": "low",
|
||||
"medium": "medium",
|
||||
"high": "high",
|
||||
"xhigh": "max",
|
||||
}
|
||||
|
||||
|
||||
# OpenAI web_search_options.search_context_size -> Claude web_search max_uses
|
||||
WEB_SEARCH_CONTEXT_SIZE_TO_MAX_USES: dict[str, int] = {
|
||||
@@ -190,6 +209,8 @@ __all__ = [
|
||||
"RETRYABLE_ERROR_TYPES",
|
||||
"REASONING_EFFORT_TO_THINKING_BUDGET",
|
||||
"THINKING_BUDGET_TO_REASONING_EFFORT",
|
||||
"CLAUDE_EFFORT_TO_REASONING_EFFORT",
|
||||
"REASONING_EFFORT_TO_CLAUDE_EFFORT",
|
||||
"WEB_SEARCH_CONTEXT_SIZE_TO_MAX_USES",
|
||||
"CLAUDE_DEFAULT_MAX_TOKENS",
|
||||
"THINKING_BUDGET_TOKENS_PERCENTAGE",
|
||||
|
||||
@@ -11,7 +11,9 @@ import json
|
||||
from typing import Any
|
||||
|
||||
from src.core.api_format.conversion.field_mappings import (
|
||||
CLAUDE_EFFORT_TO_REASONING_EFFORT,
|
||||
ERROR_TYPE_MAPPINGS,
|
||||
REASONING_EFFORT_TO_CLAUDE_EFFORT,
|
||||
RETRYABLE_ERROR_TYPES,
|
||||
STOP_REASON_MAPPINGS,
|
||||
THINKING_BUDGET_TOKENS_MIN,
|
||||
@@ -187,11 +189,19 @@ class ClaudeNormalizer(FormatNormalizer):
|
||||
"tools",
|
||||
"tool_choice",
|
||||
"thinking",
|
||||
"output_config",
|
||||
},
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
# output_config.effort -> reasoning_effort (独立于 thinking)
|
||||
output_config = request.get("output_config")
|
||||
if isinstance(output_config, dict):
|
||||
effort = output_config.get("effort")
|
||||
if isinstance(effort, str) and effort in CLAUDE_EFFORT_TO_REASONING_EFFORT:
|
||||
internal.extra["reasoning_effort"] = CLAUDE_EFFORT_TO_REASONING_EFFORT[effort]
|
||||
|
||||
if dropped:
|
||||
internal.extra.setdefault("raw", {})["dropped_blocks"] = dropped
|
||||
|
||||
@@ -314,6 +324,11 @@ class ClaudeNormalizer(FormatNormalizer):
|
||||
result["max_tokens"] = bt + 1
|
||||
result["thinking"] = thinking_out
|
||||
|
||||
# reasoning_effort -> output_config.effort (独立于 thinking)
|
||||
effort = internal.extra.get("reasoning_effort") if internal.extra else None
|
||||
if isinstance(effort, str) and effort in REASONING_EFFORT_TO_CLAUDE_EFFORT:
|
||||
result["output_config"] = {"effort": REASONING_EFFORT_TO_CLAUDE_EFFORT[effort]}
|
||||
|
||||
# web_search_options -> Claude web_search tool
|
||||
web_search_opts = internal.extra.get("web_search_options") if internal.extra else None
|
||||
if isinstance(web_search_opts, dict):
|
||||
|
||||
@@ -216,6 +216,7 @@ class OpenAINormalizer(FormatNormalizer):
|
||||
budget_tokens=REASONING_EFFORT_TO_THINKING_BUDGET[reasoning_effort],
|
||||
extra={"reasoning_effort": reasoning_effort},
|
||||
)
|
||||
extra["reasoning_effort"] = reasoning_effort
|
||||
|
||||
# web_search_options 存入 extra 供目标 normalizer 使用
|
||||
web_search_options = request.get("web_search_options")
|
||||
@@ -344,6 +345,7 @@ class OpenAINormalizer(FormatNormalizer):
|
||||
result["tool_choice"] = self._tool_choice_to_openai(internal.tool_choice)
|
||||
|
||||
# thinking -> reasoning_effort
|
||||
effort: str | None = None
|
||||
if internal.thinking and internal.thinking.enabled:
|
||||
effort = internal.thinking.extra.get("reasoning_effort")
|
||||
if not effort and internal.thinking.budget_tokens is not None:
|
||||
@@ -351,8 +353,14 @@ class OpenAINormalizer(FormatNormalizer):
|
||||
if internal.thinking.budget_tokens <= threshold:
|
||||
effort = level
|
||||
break
|
||||
if effort:
|
||||
result["reasoning_effort"] = effort
|
||||
# 兜底: 从 internal.extra 读取 (支持 output_config.effort 独立于 thinking 的场景)
|
||||
if not effort and internal.extra:
|
||||
effort = internal.extra.get("reasoning_effort")
|
||||
if effort:
|
||||
# OpenAI Chat Completions 仅支持 low/medium/high,xhigh 降级为 high
|
||||
if effort == "xhigh":
|
||||
effort = "high"
|
||||
result["reasoning_effort"] = effort
|
||||
|
||||
# parallel_tool_calls
|
||||
if internal.parallel_tool_calls is not None:
|
||||
|
||||
@@ -196,6 +196,10 @@ class OpenAICliNormalizer(FormatNormalizer):
|
||||
},
|
||||
)
|
||||
|
||||
# reasoning_effort 同步存入 extra (支持独立于 thinking 的跨格式转换)
|
||||
if thinking and thinking.extra.get("reasoning_effort"):
|
||||
internal.extra["reasoning_effort"] = thinking.extra["reasoning_effort"]
|
||||
|
||||
return internal
|
||||
|
||||
def request_from_internal(
|
||||
@@ -259,11 +263,13 @@ class OpenAICliNormalizer(FormatNormalizer):
|
||||
result["tool_choice"] = self._tool_choice_to_openai(internal.tool_choice)
|
||||
|
||||
# thinking -> reasoning (Responses API)
|
||||
effort: str | None = None
|
||||
if internal.thinking and internal.thinking.enabled:
|
||||
# 优先还原原始 reasoning 对象
|
||||
original_reasoning = internal.thinking.extra.get("reasoning")
|
||||
if isinstance(original_reasoning, dict):
|
||||
result["reasoning"] = original_reasoning
|
||||
effort = None # 已还原,不需要再构造
|
||||
else:
|
||||
effort = internal.thinking.extra.get("reasoning_effort")
|
||||
if not effort and internal.thinking.budget_tokens is not None:
|
||||
@@ -271,8 +277,14 @@ class OpenAICliNormalizer(FormatNormalizer):
|
||||
if internal.thinking.budget_tokens <= threshold:
|
||||
effort = level
|
||||
break
|
||||
if effort:
|
||||
result["reasoning"] = {"effort": effort}
|
||||
# 兜底: 从 internal.extra 读取
|
||||
if not effort and internal.extra and "reasoning" not in result:
|
||||
effort = internal.extra.get("reasoning_effort")
|
||||
if effort:
|
||||
# xhigh 降级为 high (Responses API 也仅支持 low/medium/high)
|
||||
if effort == "xhigh":
|
||||
effort = "high"
|
||||
result["reasoning"] = {"effort": effort}
|
||||
|
||||
# parallel_tool_calls
|
||||
if internal.parallel_tool_calls is not None:
|
||||
|
||||
Reference in New Issue
Block a user