mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-02 09:20:22 +08:00
feat(endpoint): 端点默认 body_rules 机制与 Codex 规则回填
- EndpointDefinition 新增 default_body_rules 字段,openai:cli/compact 配置 Codex 默认规则
- 创建端点时若未指定 body_rules 则自动填充对应格式的默认值
- 新增 GET /defaults/{api_format}/body-rules 接口查询默认规则
- 前端 EndpointFormDialog 增加"重置请求体"按钮,支持一键恢复默认
- Alembic 迁移回填已有 Codex 端点的默认 body_rules
- 新增 metadata 和 endpoint 创建默认值的单元测试
This commit is contained in:
@@ -18,6 +18,7 @@ from src.api.base.admin_adapter import AdminApiAdapter
|
||||
from src.api.base.context import ApiRequestContext
|
||||
from src.api.base.models_service import invalidate_models_list_cache
|
||||
from src.api.base.pipeline import ApiRequestPipeline
|
||||
from src.core.api_format.metadata import get_default_body_rules_for_endpoint
|
||||
from src.core.api_format.signature import parse_signature_key
|
||||
from src.core.exceptions import InvalidRequestException, NotFoundException
|
||||
from src.core.logger import logger
|
||||
@@ -136,6 +137,17 @@ async def create_provider_endpoint(
|
||||
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
|
||||
|
||||
|
||||
@router.get("/defaults/{api_format}/body-rules")
|
||||
async def get_default_endpoint_body_rules(
|
||||
api_format: str,
|
||||
request: Request,
|
||||
db: Session = Depends(get_db),
|
||||
) -> dict[str, Any]:
|
||||
"""获取指定 endpoint signature 的默认 body_rules。"""
|
||||
adapter = AdminGetDefaultBodyRulesAdapter(api_format=api_format)
|
||||
return await pipeline.run(adapter=adapter, http_request=request, db=db, mode=adapter.mode)
|
||||
|
||||
|
||||
@router.get("/{endpoint_id}", response_model=ProviderEndpointResponse)
|
||||
async def get_endpoint(
|
||||
endpoint_id: str,
|
||||
@@ -324,6 +336,9 @@ class AdminCreateProviderEndpointAdapter(AdminApiAdapter):
|
||||
endpoint_kind = sig.endpoint_kind.value
|
||||
# 使用归一化后的 signature key,确保格式一致性
|
||||
normalized_api_format = sig.key
|
||||
body_rules = self.endpoint_data.body_rules
|
||||
if body_rules is None:
|
||||
body_rules = get_default_body_rules_for_endpoint(normalized_api_format) or None
|
||||
|
||||
new_endpoint = ProviderEndpoint(
|
||||
id=str(uuid.uuid4()),
|
||||
@@ -334,7 +349,7 @@ class AdminCreateProviderEndpointAdapter(AdminApiAdapter):
|
||||
base_url=self.endpoint_data.base_url,
|
||||
custom_path=self.endpoint_data.custom_path,
|
||||
header_rules=self.endpoint_data.header_rules,
|
||||
body_rules=self.endpoint_data.body_rules,
|
||||
body_rules=body_rules,
|
||||
max_retries=self.endpoint_data.max_retries,
|
||||
is_active=True,
|
||||
config=self.endpoint_data.config,
|
||||
@@ -593,3 +608,19 @@ class AdminDeleteProviderEndpointAdapter(AdminApiAdapter):
|
||||
"message": f"Endpoint {self.endpoint_id} 已删除",
|
||||
"affected_keys_count": affected_keys_count,
|
||||
}
|
||||
|
||||
|
||||
@dataclass
|
||||
class AdminGetDefaultBodyRulesAdapter(AdminApiAdapter):
|
||||
api_format: str
|
||||
|
||||
async def handle(self, context: ApiRequestContext) -> Any: # type: ignore[override]
|
||||
try:
|
||||
normalized_api_format = parse_signature_key(self.api_format).key
|
||||
except Exception as exc:
|
||||
raise InvalidRequestException(f"无效的 api_format: {self.api_format}") from exc
|
||||
|
||||
return {
|
||||
"api_format": normalized_api_format,
|
||||
"body_rules": get_default_body_rules_for_endpoint(normalized_api_format),
|
||||
}
|
||||
|
||||
@@ -51,6 +51,7 @@ from src.core.api_format.metadata import (
|
||||
can_passthrough_endpoint,
|
||||
get_auth_config_for_endpoint,
|
||||
get_data_format_id_for_endpoint,
|
||||
get_default_body_rules_for_endpoint,
|
||||
get_default_path_for_endpoint,
|
||||
get_endpoint_definition,
|
||||
get_extra_headers_for_endpoint,
|
||||
@@ -98,6 +99,7 @@ __all__ = [
|
||||
"get_extra_headers_for_endpoint",
|
||||
"get_protected_keys_for_endpoint",
|
||||
"get_data_format_id_for_endpoint",
|
||||
"get_default_body_rules_for_endpoint",
|
||||
"can_passthrough_endpoint",
|
||||
# Utils
|
||||
"is_cli_format",
|
||||
|
||||
@@ -9,8 +9,10 @@ API endpoint metadata (new mode).
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Iterable, Mapping, Sequence
|
||||
from copy import deepcopy
|
||||
from dataclasses import dataclass, field
|
||||
from types import MappingProxyType
|
||||
from typing import Any
|
||||
|
||||
from src.core.api_format.enums import ApiFamily, AuthMethod, EndpointKind
|
||||
from src.core.api_format.signature import EndpointSignature, make_signature_key, parse_signature_key
|
||||
@@ -47,6 +49,7 @@ class EndpointDefinition:
|
||||
stream_in_body: bool = True
|
||||
|
||||
data_format_id: str = ""
|
||||
default_body_rules: Sequence[dict[str, Any]] = field(default_factory=tuple)
|
||||
|
||||
@property
|
||||
def signature(self) -> EndpointSignature:
|
||||
@@ -65,6 +68,19 @@ class EndpointDefinition:
|
||||
yield value
|
||||
|
||||
|
||||
_CODEX_DEFAULT_BODY_RULES: tuple[dict[str, Any], ...] = (
|
||||
{"action": "drop", "path": "max_output_tokens"},
|
||||
{"action": "drop", "path": "temperature"},
|
||||
{"action": "drop", "path": "top_p"},
|
||||
{"action": "set", "path": "store", "value": False},
|
||||
{
|
||||
"action": "set",
|
||||
"path": "instructions",
|
||||
"value": "You are GPT-5.",
|
||||
"condition": {"path": "instructions", "op": "not_exists"},
|
||||
},
|
||||
)
|
||||
|
||||
_ENDPOINT_DEFINITIONS: dict[tuple[ApiFamily, EndpointKind], EndpointDefinition] = {
|
||||
# Claude
|
||||
(ApiFamily.CLAUDE, EndpointKind.CHAT): EndpointDefinition(
|
||||
@@ -122,6 +138,7 @@ _ENDPOINT_DEFINITIONS: dict[tuple[ApiFamily, EndpointKind], EndpointDefinition]
|
||||
auth_type="bearer",
|
||||
protected_keys=frozenset({"authorization", "content-type"}),
|
||||
data_format_id="openai_responses",
|
||||
default_body_rules=_CODEX_DEFAULT_BODY_RULES,
|
||||
),
|
||||
(ApiFamily.OPENAI, EndpointKind.COMPACT): EndpointDefinition(
|
||||
api_family=ApiFamily.OPENAI,
|
||||
@@ -135,6 +152,7 @@ _ENDPOINT_DEFINITIONS: dict[tuple[ApiFamily, EndpointKind], EndpointDefinition]
|
||||
# compact endpoint is non-streaming by design.
|
||||
stream_in_body=False,
|
||||
data_format_id="openai_responses",
|
||||
default_body_rules=_CODEX_DEFAULT_BODY_RULES,
|
||||
),
|
||||
(ApiFamily.OPENAI, EndpointKind.VIDEO): EndpointDefinition(
|
||||
api_family=ApiFamily.OPENAI,
|
||||
@@ -292,6 +310,15 @@ def get_data_format_id_for_endpoint(
|
||||
return ""
|
||||
|
||||
|
||||
def get_default_body_rules_for_endpoint(
|
||||
value: str | EndpointSignature | tuple[ApiFamily, EndpointKind],
|
||||
) -> list[dict[str, Any]]:
|
||||
definition = resolve_endpoint_definition(value)
|
||||
if not definition or not definition.default_body_rules:
|
||||
return []
|
||||
return deepcopy(list(definition.default_body_rules))
|
||||
|
||||
|
||||
def can_passthrough_endpoint(
|
||||
client: str | EndpointSignature | tuple[ApiFamily, EndpointKind],
|
||||
provider: str | EndpointSignature | tuple[ApiFamily, EndpointKind],
|
||||
@@ -336,6 +363,7 @@ __all__ = [
|
||||
"get_extra_headers_for_endpoint",
|
||||
"get_protected_keys_for_endpoint",
|
||||
"get_data_format_id_for_endpoint",
|
||||
"get_default_body_rules_for_endpoint",
|
||||
"can_passthrough_endpoint",
|
||||
"make_endpoint_signature",
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user