mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-02 01:10:23 +08:00
refactor(normalizer): 移除 request key reorder 机制,保持自然插入顺序
移除 OpenAI/OpenAI CLI normalizer 中的 _reorder_request_prefix_keys 及 基类 _reorder_request_keys 死代码,request_from_internal 直接返回构建 顺序的 dict,测试同步更新为验证自然插入顺序。
This commit is contained in:
@@ -160,21 +160,6 @@ class FormatNormalizer(ABC):
|
|||||||
def _extract_extra(self, payload: dict[str, Any], known_keys: set[str]) -> dict[str, Any]:
|
def _extract_extra(self, payload: dict[str, Any], known_keys: set[str]) -> dict[str, Any]:
|
||||||
return {k: v for k, v in payload.items() if k not in known_keys}
|
return {k: v for k, v in payload.items() if k not in known_keys}
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _reorder_request_keys(
|
|
||||||
payload: dict[str, Any],
|
|
||||||
prefix_keys: tuple[str, ...],
|
|
||||||
) -> dict[str, Any]:
|
|
||||||
"""将指定字段前置,其余保持原序,用于稳定请求体前缀以提升 prompt cache 命中率。"""
|
|
||||||
ordered: dict[str, Any] = {}
|
|
||||||
for key in prefix_keys:
|
|
||||||
if key in payload:
|
|
||||||
ordered[key] = payload[key]
|
|
||||||
for key, value in payload.items():
|
|
||||||
if key not in ordered:
|
|
||||||
ordered[key] = value
|
|
||||||
return ordered
|
|
||||||
|
|
||||||
def _merge_dropped(self, target: dict[str, int], source: dict[str, int]) -> None:
|
def _merge_dropped(self, target: dict[str, int], source: dict[str, int]) -> None:
|
||||||
for k, v in source.items():
|
for k, v in source.items():
|
||||||
target[k] = target.get(k, 0) + int(v)
|
target[k] = target.get(k, 0) + int(v)
|
||||||
|
|||||||
@@ -453,7 +453,7 @@ class OpenAINormalizer(FormatNormalizer):
|
|||||||
if key in _CHAT_PASSTHROUGH_KEYS and key not in result and key not in _HANDLED_KEYS:
|
if key in _CHAT_PASSTHROUGH_KEYS and key not in result and key not in _HANDLED_KEYS:
|
||||||
result[key] = value
|
result[key] = value
|
||||||
|
|
||||||
return self._reorder_request_prefix_keys(result)
|
return result
|
||||||
|
|
||||||
# =========================
|
# =========================
|
||||||
# Responses
|
# Responses
|
||||||
@@ -1401,11 +1401,6 @@ class OpenAINormalizer(FormatNormalizer):
|
|||||||
joined = "\n\n".join(parts)
|
joined = "\n\n".join(parts)
|
||||||
return joined or None
|
return joined or None
|
||||||
|
|
||||||
_REQUEST_PREFIX_KEYS = ("model", "tools", "messages")
|
|
||||||
|
|
||||||
def _reorder_request_prefix_keys(self, payload: dict[str, Any]) -> dict[str, Any]:
|
|
||||||
return self._reorder_request_keys(payload, self._REQUEST_PREFIX_KEYS)
|
|
||||||
|
|
||||||
def _openai_tools_to_internal(self, tools: Any) -> list[ToolDefinition] | None:
|
def _openai_tools_to_internal(self, tools: Any) -> list[ToolDefinition] | None:
|
||||||
if not tools or not isinstance(tools, list):
|
if not tools or not isinstance(tools, list):
|
||||||
return None
|
return None
|
||||||
|
|||||||
@@ -76,8 +76,6 @@ from src.core.api_format.conversion.stream_events import (
|
|||||||
from src.core.api_format.conversion.stream_state import StreamState
|
from src.core.api_format.conversion.stream_state import StreamState
|
||||||
from src.core.logger import logger
|
from src.core.logger import logger
|
||||||
|
|
||||||
_OPENAI_CLI_REQUEST_PREFIX_KEYS = ("model", "instructions", "tools", "input")
|
|
||||||
|
|
||||||
|
|
||||||
def _is_chat_completions_response(data: dict[str, Any]) -> bool:
|
def _is_chat_completions_response(data: dict[str, Any]) -> bool:
|
||||||
"""检测数据是否为 OpenAI Chat Completions 格式(而非 Responses API 格式)。
|
"""检测数据是否为 OpenAI Chat Completions 格式(而非 Responses API 格式)。
|
||||||
@@ -103,18 +101,6 @@ def _get_openai_chat_normalizer() -> "FormatNormalizer | None":
|
|||||||
return format_conversion_registry.get_normalizer("openai:chat")
|
return format_conversion_registry.get_normalizer("openai:chat")
|
||||||
|
|
||||||
|
|
||||||
def reorder_openai_cli_request_prefix_keys(payload: dict[str, Any]) -> dict[str, Any]:
|
|
||||||
"""Keep the stable OpenAI Responses prefix ahead of the typically dynamic tail."""
|
|
||||||
ordered: dict[str, Any] = {}
|
|
||||||
for key in _OPENAI_CLI_REQUEST_PREFIX_KEYS:
|
|
||||||
if key in payload:
|
|
||||||
ordered[key] = payload[key]
|
|
||||||
for key, value in payload.items():
|
|
||||||
if key not in ordered:
|
|
||||||
ordered[key] = value
|
|
||||||
return ordered
|
|
||||||
|
|
||||||
|
|
||||||
class OpenAICliNormalizer(FormatNormalizer):
|
class OpenAICliNormalizer(FormatNormalizer):
|
||||||
FORMAT_ID = "openai:cli"
|
FORMAT_ID = "openai:cli"
|
||||||
capabilities = FormatCapabilities(
|
capabilities = FormatCapabilities(
|
||||||
@@ -282,7 +268,6 @@ class OpenAICliNormalizer(FormatNormalizer):
|
|||||||
result: dict[str, Any] = {"model": internal.model}
|
result: dict[str, Any] = {"model": internal.model}
|
||||||
if instructions_text or has_explicit_instructions:
|
if instructions_text or has_explicit_instructions:
|
||||||
result["instructions"] = instructions_text or ""
|
result["instructions"] = instructions_text or ""
|
||||||
# Keep the stable system prefix ahead of the typically dynamic input payload.
|
|
||||||
result["input"] = self._internal_messages_to_input(
|
result["input"] = self._internal_messages_to_input(
|
||||||
internal.messages,
|
internal.messages,
|
||||||
system_to_developer=False,
|
system_to_developer=False,
|
||||||
@@ -409,7 +394,7 @@ class OpenAICliNormalizer(FormatNormalizer):
|
|||||||
):
|
):
|
||||||
result[key] = value
|
result[key] = value
|
||||||
|
|
||||||
return self._reorder_request_prefix_keys(result)
|
return result
|
||||||
|
|
||||||
# =========================
|
# =========================
|
||||||
# Responses
|
# Responses
|
||||||
@@ -2135,9 +2120,6 @@ class OpenAICliNormalizer(FormatNormalizer):
|
|||||||
joined = "\n\n".join(parts)
|
joined = "\n\n".join(parts)
|
||||||
return joined or None
|
return joined or None
|
||||||
|
|
||||||
def _reorder_request_prefix_keys(self, payload: dict[str, Any]) -> dict[str, Any]:
|
|
||||||
return reorder_openai_cli_request_prefix_keys(payload)
|
|
||||||
|
|
||||||
def _error_type_from_value(self, value: str) -> ErrorType:
|
def _error_type_from_value(self, value: str) -> ErrorType:
|
||||||
for t in ErrorType:
|
for t in ErrorType:
|
||||||
if t.value == value:
|
if t.value == value:
|
||||||
|
|||||||
@@ -504,7 +504,7 @@ def test_openai_cli_custom_tool_and_choice_convert_to_openai_chat() -> None:
|
|||||||
|
|
||||||
out = reg.convert_request(openai_cli_req, "openai:cli", "openai:chat")
|
out = reg.convert_request(openai_cli_req, "openai:cli", "openai:chat")
|
||||||
|
|
||||||
assert list(out.keys())[:3] == ["model", "tools", "messages"]
|
assert list(out.keys())[:3] == ["model", "messages", "tools"]
|
||||||
assert out["tools"] == [
|
assert out["tools"] == [
|
||||||
{
|
{
|
||||||
"type": "custom",
|
"type": "custom",
|
||||||
@@ -583,7 +583,7 @@ def test_openai_chat_web_search_options_convert_to_openai_cli_tools() -> None:
|
|||||||
|
|
||||||
out = reg.convert_request(openai_chat_req, "openai:chat", "openai:cli")
|
out = reg.convert_request(openai_chat_req, "openai:chat", "openai:cli")
|
||||||
|
|
||||||
assert list(out.keys())[:3] == ["model", "tools", "input"]
|
assert list(out.keys())[:3] == ["model", "input", "tools"]
|
||||||
assert out["tools"] == [
|
assert out["tools"] == [
|
||||||
{
|
{
|
||||||
"type": "web_search",
|
"type": "web_search",
|
||||||
@@ -760,6 +760,22 @@ def test_openai_cli_request_tool_choice_flat_function_roundtrip_preserved() -> N
|
|||||||
assert out["tool_choice"] == {"type": "function", "name": "read_file"}
|
assert out["tool_choice"] == {"type": "function", "name": "read_file"}
|
||||||
|
|
||||||
|
|
||||||
|
def test_openai_cli_request_from_internal_keeps_natural_insertion_order() -> None:
|
||||||
|
normalizer = OpenAICliNormalizer()
|
||||||
|
internal = normalizer.request_to_internal(
|
||||||
|
{
|
||||||
|
"model": "gpt-test",
|
||||||
|
"input": [],
|
||||||
|
"max_output_tokens": 32,
|
||||||
|
"tools": [{"type": "function", "name": "read_file"}],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
out = normalizer.request_from_internal(internal)
|
||||||
|
|
||||||
|
assert list(out.keys())[:4] == ["model", "input", "max_output_tokens", "tools"]
|
||||||
|
|
||||||
|
|
||||||
def test_claude_explicit_effort_preserved_in_openai_cli() -> None:
|
def test_claude_explicit_effort_preserved_in_openai_cli() -> None:
|
||||||
reg = _make_registry_with_cli()
|
reg = _make_registry_with_cli()
|
||||||
|
|
||||||
|
|||||||
@@ -237,6 +237,27 @@ def test_openai_request_preserves_empty_string_legacy_function_call_arguments()
|
|||||||
assert out["messages"][1]["tool_calls"][0]["function"]["arguments"] == ""
|
assert out["messages"][1]["tool_calls"][0]["function"]["arguments"] == ""
|
||||||
|
|
||||||
|
|
||||||
|
def test_openai_request_from_internal_keeps_natural_insertion_order() -> None:
|
||||||
|
n = OpenAINormalizer()
|
||||||
|
|
||||||
|
req = {
|
||||||
|
"model": "gpt-4o-mini",
|
||||||
|
"messages": [{"role": "user", "content": "weather?"}],
|
||||||
|
"max_tokens": 12,
|
||||||
|
"tools": [
|
||||||
|
{
|
||||||
|
"type": "function",
|
||||||
|
"function": {"name": "get_weather", "parameters": {"type": "object"}},
|
||||||
|
}
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
internal = n.request_to_internal(req)
|
||||||
|
out = n.request_from_internal(internal)
|
||||||
|
|
||||||
|
assert list(out.keys())[:4] == ["model", "messages", "max_tokens", "tools"]
|
||||||
|
|
||||||
|
|
||||||
def test_openai_request_content_image_and_unknown_drop() -> None:
|
def test_openai_request_content_image_and_unknown_drop() -> None:
|
||||||
n = OpenAINormalizer()
|
n = OpenAINormalizer()
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user