mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-02 01:10:23 +08:00
@@ -868,7 +868,7 @@ export const MOCK_API_FORMATS = {
|
|||||||
{ value: 'claude:chat', label: 'Claude Chat', default_path: '/v1/messages', aliases: [] },
|
{ value: 'claude:chat', label: 'Claude Chat', default_path: '/v1/messages', aliases: [] },
|
||||||
{ value: 'claude:cli', label: 'Claude CLI', default_path: '/v1/messages', aliases: [] },
|
{ value: 'claude:cli', label: 'Claude CLI', default_path: '/v1/messages', aliases: [] },
|
||||||
{ value: 'openai:chat', label: 'OpenAI Chat', default_path: '/v1/chat/completions', aliases: [] },
|
{ value: 'openai:chat', label: 'OpenAI Chat', default_path: '/v1/chat/completions', aliases: [] },
|
||||||
{ value: 'openai:cli', label: 'OpenAI CLI', default_path: '/responses', aliases: [] },
|
{ value: 'openai:cli', label: 'OpenAI CLI', default_path: '/v1/responses', aliases: [] },
|
||||||
{ value: 'openai:video', label: 'OpenAI Video', default_path: '/v1/videos', aliases: [] },
|
{ value: 'openai:video', label: 'OpenAI Video', default_path: '/v1/videos', aliases: [] },
|
||||||
{ value: 'gemini:chat', label: 'Gemini Chat', default_path: '/v1beta/models/{model}:{action}', aliases: [] },
|
{ value: 'gemini:chat', label: 'Gemini Chat', default_path: '/v1beta/models/{model}:{action}', aliases: [] },
|
||||||
{ value: 'gemini:cli', label: 'Gemini CLI', default_path: '/v1beta/models/{model}:{action}', aliases: [] },
|
{ value: 'gemini:cli', label: 'Gemini CLI', default_path: '/v1beta/models/{model}:{action}', aliases: [] },
|
||||||
|
|||||||
@@ -217,7 +217,7 @@ const MOCK_ENDPOINT_STATUS = {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
api_format: 'openai:cli',
|
api_format: 'openai:cli',
|
||||||
api_path: '/responses',
|
api_path: '/v1/responses',
|
||||||
total_attempts: 2340,
|
total_attempts: 2340,
|
||||||
success_count: 2200,
|
success_count: 2200,
|
||||||
failed_count: 100,
|
failed_count: 100,
|
||||||
|
|||||||
@@ -474,6 +474,16 @@ async def test_model(
|
|||||||
"temperature": 0.7,
|
"temperature": 0.7,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# 获取端点规则(不在此处应用,传递给 check_endpoint 在格式转换后应用)
|
||||||
|
body_rules = getattr(endpoint, "body_rules", None)
|
||||||
|
header_rules = getattr(endpoint, "header_rules", None)
|
||||||
|
extra_headers = endpoint_config.get("extra_headers") or {}
|
||||||
|
|
||||||
|
if body_rules:
|
||||||
|
logger.debug(f"[test-model] 将传递 body_rules 给 check_endpoint: {body_rules}")
|
||||||
|
if header_rules:
|
||||||
|
logger.debug(f"[test-model] 将传递 header_rules 给 check_endpoint: {header_rules}")
|
||||||
|
|
||||||
# 发送测试请求
|
# 发送测试请求
|
||||||
async with httpx.AsyncClient(
|
async with httpx.AsyncClient(
|
||||||
timeout=endpoint_config["timeout"], verify=get_ssl_context()
|
timeout=endpoint_config["timeout"], verify=get_ssl_context()
|
||||||
@@ -486,7 +496,10 @@ async def test_model(
|
|||||||
endpoint_config["base_url"],
|
endpoint_config["base_url"],
|
||||||
endpoint_config["api_key"],
|
endpoint_config["api_key"],
|
||||||
check_request,
|
check_request,
|
||||||
endpoint_config.get("extra_headers"),
|
extra_headers if extra_headers else None,
|
||||||
|
# 端点规则(在 check_endpoint 内部格式转换后应用)
|
||||||
|
body_rules=body_rules,
|
||||||
|
header_rules=header_rules,
|
||||||
# 用量计算参数(现在强制记录)
|
# 用量计算参数(现在强制记录)
|
||||||
db=db,
|
db=db,
|
||||||
user=current_user,
|
user=current_user,
|
||||||
|
|||||||
@@ -630,6 +630,9 @@ class ChatAdapterBase(ApiAdapter):
|
|||||||
api_key: str,
|
api_key: str,
|
||||||
request_data: dict[str, Any],
|
request_data: dict[str, Any],
|
||||||
extra_headers: dict[str, str] | None = None,
|
extra_headers: dict[str, str] | None = None,
|
||||||
|
# 端点规则参数
|
||||||
|
body_rules: list[dict[str, Any]] | None = None,
|
||||||
|
header_rules: list[dict[str, Any]] | None = None,
|
||||||
# 用量计算参数(现在强制记录)
|
# 用量计算参数(现在强制记录)
|
||||||
db: Any | None = None,
|
db: Any | None = None,
|
||||||
user: Any | None = None,
|
user: Any | None = None,
|
||||||
@@ -647,6 +650,8 @@ class ChatAdapterBase(ApiAdapter):
|
|||||||
api_key: API 密钥(已解密)
|
api_key: API 密钥(已解密)
|
||||||
request_data: 请求数据
|
request_data: 请求数据
|
||||||
extra_headers: 端点配置的额外请求头
|
extra_headers: 端点配置的额外请求头
|
||||||
|
body_rules: 请求体规则(在格式转换后应用)
|
||||||
|
header_rules: 请求头规则(在请求头构建后应用)
|
||||||
db: 数据库会话
|
db: 数据库会话
|
||||||
user: 用户对象
|
user: 用户对象
|
||||||
provider_name: 提供商名称
|
provider_name: 提供商名称
|
||||||
@@ -658,12 +663,30 @@ class ChatAdapterBase(ApiAdapter):
|
|||||||
测试响应数据
|
测试响应数据
|
||||||
"""
|
"""
|
||||||
from src.api.handlers.base.endpoint_checker import run_endpoint_check
|
from src.api.handlers.base.endpoint_checker import run_endpoint_check
|
||||||
|
from src.api.handlers.base.request_builder import apply_body_rules
|
||||||
|
from src.core.api_format.headers import HeaderBuilder
|
||||||
|
|
||||||
# 使用子类配置方法构建请求组件
|
# 使用子类配置方法构建请求组件
|
||||||
url = cls.build_endpoint_url(base_url)
|
url = cls.build_endpoint_url(base_url)
|
||||||
headers = cls.build_headers_with_extra(api_key, extra_headers)
|
headers = cls.build_headers_with_extra(api_key, extra_headers)
|
||||||
body = cls.build_request_body(request_data)
|
body = cls.build_request_body(request_data)
|
||||||
|
|
||||||
|
# 应用请求体规则(在格式转换后应用,确保规则效果不被覆盖)
|
||||||
|
if body_rules:
|
||||||
|
body = apply_body_rules(body, body_rules)
|
||||||
|
|
||||||
|
# 应用请求头规则(在请求头构建后应用)
|
||||||
|
if header_rules:
|
||||||
|
# 获取认证头名称,防止被规则覆盖
|
||||||
|
from src.core.api_format import get_auth_config_for_endpoint
|
||||||
|
auth_header, _ = get_auth_config_for_endpoint(cls.FORMAT_ID)
|
||||||
|
protected_keys = {auth_header.lower(), "content-type"}
|
||||||
|
|
||||||
|
header_builder = HeaderBuilder()
|
||||||
|
header_builder.add_many(headers)
|
||||||
|
header_builder.apply_rules(header_rules, protected_keys)
|
||||||
|
headers = header_builder.build()
|
||||||
|
|
||||||
# 使用通用的endpoint checker执行请求
|
# 使用通用的endpoint checker执行请求
|
||||||
return await run_endpoint_check(
|
return await run_endpoint_check(
|
||||||
client=client,
|
client=client,
|
||||||
|
|||||||
@@ -598,6 +598,9 @@ class CliAdapterBase(ApiAdapter):
|
|||||||
api_key: str,
|
api_key: str,
|
||||||
request_data: dict[str, Any],
|
request_data: dict[str, Any],
|
||||||
extra_headers: dict[str, str] | None = None,
|
extra_headers: dict[str, str] | None = None,
|
||||||
|
# 端点规则参数
|
||||||
|
body_rules: list[dict[str, Any]] | None = None,
|
||||||
|
header_rules: list[dict[str, Any]] | None = None,
|
||||||
# 用量计算参数
|
# 用量计算参数
|
||||||
db: Any | None = None,
|
db: Any | None = None,
|
||||||
user: Any | None = None,
|
user: Any | None = None,
|
||||||
@@ -622,6 +625,8 @@ class CliAdapterBase(ApiAdapter):
|
|||||||
api_key: API 密钥(已解密)
|
api_key: API 密钥(已解密)
|
||||||
request_data: 请求数据
|
request_data: 请求数据
|
||||||
extra_headers: 端点配置的额外请求头
|
extra_headers: 端点配置的额外请求头
|
||||||
|
body_rules: 请求体规则(在格式转换后应用)
|
||||||
|
header_rules: 请求头规则(在请求头构建后应用)
|
||||||
db: 数据库会话
|
db: 数据库会话
|
||||||
user: 用户对象
|
user: 用户对象
|
||||||
provider_name: 提供商名称
|
provider_name: 提供商名称
|
||||||
@@ -633,6 +638,8 @@ class CliAdapterBase(ApiAdapter):
|
|||||||
测试响应数据
|
测试响应数据
|
||||||
"""
|
"""
|
||||||
from src.api.handlers.base.endpoint_checker import run_endpoint_check
|
from src.api.handlers.base.endpoint_checker import run_endpoint_check
|
||||||
|
from src.api.handlers.base.request_builder import apply_body_rules
|
||||||
|
from src.core.api_format.headers import HeaderBuilder
|
||||||
|
|
||||||
# 构建请求组件
|
# 构建请求组件
|
||||||
url = cls.build_endpoint_url(base_url, request_data, model_name)
|
url = cls.build_endpoint_url(base_url, request_data, model_name)
|
||||||
@@ -646,6 +653,22 @@ class CliAdapterBase(ApiAdapter):
|
|||||||
headers = cls.build_headers_with_extra(api_key, merged_extra if merged_extra else None)
|
headers = cls.build_headers_with_extra(api_key, merged_extra if merged_extra else None)
|
||||||
body = cls.build_request_body(request_data)
|
body = cls.build_request_body(request_data)
|
||||||
|
|
||||||
|
# 应用请求体规则(在格式转换后应用,确保规则效果不被覆盖)
|
||||||
|
if body_rules:
|
||||||
|
body = apply_body_rules(body, body_rules)
|
||||||
|
|
||||||
|
# 应用请求头规则(在请求头构建后应用)
|
||||||
|
if header_rules:
|
||||||
|
# 获取认证头名称,防止被规则覆盖
|
||||||
|
from src.core.api_format import get_auth_config_for_endpoint
|
||||||
|
auth_header, _ = get_auth_config_for_endpoint(cls.FORMAT_ID)
|
||||||
|
protected_keys = {auth_header.lower(), "content-type"}
|
||||||
|
|
||||||
|
header_builder = HeaderBuilder()
|
||||||
|
header_builder.add_many(headers)
|
||||||
|
header_builder.apply_rules(header_rules, protected_keys)
|
||||||
|
headers = header_builder.build()
|
||||||
|
|
||||||
# 获取有效的模型名称
|
# 获取有效的模型名称
|
||||||
effective_model_name = model_name or request_data.get("model")
|
effective_model_name = model_name or request_data.get("model")
|
||||||
|
|
||||||
|
|||||||
@@ -123,15 +123,20 @@ def build_test_request_body(
|
|||||||
# 获取测试请求数据(OpenAI 格式)
|
# 获取测试请求数据(OpenAI 格式)
|
||||||
source_data = get_test_request_data(request_data)
|
source_data = get_test_request_data(request_data)
|
||||||
|
|
||||||
# CLI 格式使用基础格式进行转换(claude:cli -> claude:chat)
|
# 确定目标格式
|
||||||
target_format = get_base_format(format_id) or format_id
|
# 注意:OPENAI_CLI 使用不同的数据格式(input 而非 messages),需要完整转换
|
||||||
|
# 其他 CLI 格式(如 CLAUDE_CLI、GEMINI_CLI)与基础格式数据结构相同,可使用基础格式
|
||||||
|
normalized_format = str(format_id).strip()
|
||||||
|
format_lower = normalized_format.lower().replace("_", ":")
|
||||||
|
if format_lower == "openai:cli":
|
||||||
|
# OPENAI_CLI 需要完整转换:messages -> input
|
||||||
|
target_format = "openai:cli"
|
||||||
|
else:
|
||||||
|
# 其他 CLI 格式使用基础格式进行转换(claude:cli -> claude:chat)
|
||||||
|
target_format = get_base_format(format_lower) or format_lower
|
||||||
|
|
||||||
# 使用注册表进行格式转换 (openai:chat -> 目标基础格式)
|
# 使用注册表进行格式转换 (openai:chat -> 目标格式)
|
||||||
return format_conversion_registry.convert_request(
|
return format_conversion_registry.convert_request(source_data, "openai:chat", target_format)
|
||||||
source_data,
|
|
||||||
make_signature_key("openai", "chat"),
|
|
||||||
target_format,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
# ==============================================================================
|
# ==============================================================================
|
||||||
|
|||||||
@@ -252,6 +252,9 @@ class GeminiChatAdapter(ChatAdapterBase):
|
|||||||
api_key: str,
|
api_key: str,
|
||||||
request_data: dict[str, Any],
|
request_data: dict[str, Any],
|
||||||
extra_headers: dict[str, str] | None = None,
|
extra_headers: dict[str, str] | None = None,
|
||||||
|
# 端点规则参数
|
||||||
|
body_rules: list[dict[str, Any]] | None = None,
|
||||||
|
header_rules: list[dict[str, Any]] | None = None,
|
||||||
# 用量计算参数
|
# 用量计算参数
|
||||||
db: Any | None = None,
|
db: Any | None = None,
|
||||||
user: Any | None = None,
|
user: Any | None = None,
|
||||||
@@ -261,6 +264,10 @@ class GeminiChatAdapter(ChatAdapterBase):
|
|||||||
model_name: str | None = None,
|
model_name: str | None = None,
|
||||||
) -> dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
"""测试 Gemini API 模型连接性(非流式)"""
|
"""测试 Gemini API 模型连接性(非流式)"""
|
||||||
|
from src.api.handlers.base.endpoint_checker import run_endpoint_check
|
||||||
|
from src.api.handlers.base.request_builder import apply_body_rules
|
||||||
|
from src.core.api_format.headers import HeaderBuilder
|
||||||
|
|
||||||
# Gemini需要从request_data或model_name参数获取model名称
|
# Gemini需要从request_data或model_name参数获取model名称
|
||||||
effective_model_name = model_name or request_data.get("model", "")
|
effective_model_name = model_name or request_data.get("model", "")
|
||||||
if not effective_model_name:
|
if not effective_model_name:
|
||||||
@@ -277,8 +284,21 @@ class GeminiChatAdapter(ChatAdapterBase):
|
|||||||
headers = cls.build_headers_with_extra(api_key, extra_headers)
|
headers = cls.build_headers_with_extra(api_key, extra_headers)
|
||||||
body = cls.build_request_body(request_data)
|
body = cls.build_request_body(request_data)
|
||||||
|
|
||||||
# 使用基类的通用endpoint checker
|
# 应用请求体规则(在格式转换后应用,确保规则效果不被覆盖)
|
||||||
from src.api.handlers.base.endpoint_checker import run_endpoint_check
|
if body_rules:
|
||||||
|
body = apply_body_rules(body, body_rules)
|
||||||
|
|
||||||
|
# 应用请求头规则(在请求头构建后应用)
|
||||||
|
if header_rules:
|
||||||
|
# 获取认证头名称,防止被规则覆盖
|
||||||
|
from src.core.api_format import get_auth_config_for_endpoint
|
||||||
|
auth_header, _ = get_auth_config_for_endpoint(cls.FORMAT_ID)
|
||||||
|
protected_keys = {auth_header.lower(), "content-type"}
|
||||||
|
|
||||||
|
header_builder = HeaderBuilder()
|
||||||
|
header_builder.add_many(headers)
|
||||||
|
header_builder.apply_rules(header_rules, protected_keys)
|
||||||
|
headers = header_builder.build()
|
||||||
|
|
||||||
return await run_endpoint_check(
|
return await run_endpoint_check(
|
||||||
client=client,
|
client=client,
|
||||||
|
|||||||
@@ -67,12 +67,17 @@ class OpenAICliAdapter(CliAdapterBase):
|
|||||||
def build_endpoint_url(
|
def build_endpoint_url(
|
||||||
cls, base_url: str, request_data: dict[str, Any], model_name: str | None = None
|
cls, base_url: str, request_data: dict[str, Any], model_name: str | None = None
|
||||||
) -> str:
|
) -> str:
|
||||||
"""构建OpenAI CLI API端点URL"""
|
"""构建OpenAI CLI API端点URL
|
||||||
|
|
||||||
|
OPENAI_CLI 格式使用 /responses 端点(Responses API),
|
||||||
|
与 OPENAI 格式的 /v1/chat/completions 端点(Chat Completions API)不同。
|
||||||
|
"""
|
||||||
base_url = base_url.rstrip("/")
|
base_url = base_url.rstrip("/")
|
||||||
|
# OPENAI_CLI 使用 /responses 端点
|
||||||
if base_url.endswith("/v1"):
|
if base_url.endswith("/v1"):
|
||||||
return f"{base_url}/chat/completions"
|
return f"{base_url}/responses"
|
||||||
else:
|
else:
|
||||||
return f"{base_url}/v1/chat/completions"
|
return f"{base_url}/v1/responses"
|
||||||
|
|
||||||
# build_request_body 使用基类实现
|
# build_request_body 使用基类实现
|
||||||
# OPENAI -> OPENAI_CLI 无转换器,会直接透传原始请求
|
# OPENAI -> OPENAI_CLI 无转换器,会直接透传原始请求
|
||||||
|
|||||||
@@ -116,7 +116,7 @@ _ENDPOINT_DEFINITIONS: dict[tuple[ApiFamily, EndpointKind], EndpointDefinition]
|
|||||||
api_family=ApiFamily.OPENAI,
|
api_family=ApiFamily.OPENAI,
|
||||||
endpoint_kind=EndpointKind.CLI,
|
endpoint_kind=EndpointKind.CLI,
|
||||||
aliases=("openai_cli", "responses"),
|
aliases=("openai_cli", "responses"),
|
||||||
default_path="/responses",
|
default_path="/v1/responses",
|
||||||
auth_method=AuthMethod.BEARER,
|
auth_method=AuthMethod.BEARER,
|
||||||
auth_header="Authorization",
|
auth_header="Authorization",
|
||||||
auth_type="bearer",
|
auth_type="bearer",
|
||||||
|
|||||||
Reference in New Issue
Block a user