mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-03 01:40:21 +08:00
feat: 添加格式转换追踪、模型过滤规则和 Provider 超时配置
1. Usage 格式转换追踪 - 新增 endpoint_api_format 和 has_format_conversion 字段 - 在用量记录中显示格式转换信息(请求格式 → 端点格式) - 兼容历史数据的回填逻辑 2. Provider API Key 模型过滤规则 - 新增 model_include_patterns 和 model_exclude_patterns 字段 - 支持 * 和 ? 通配符,不区分大小写 - 自动获取模型时应用过滤规则 3. Provider 超时配置 - 新增 stream_first_byte_timeout 和 request_timeout 字段 - 支持每个 Provider 单独配置超时时间 - 优先使用 Provider 配置,否则回退到全局配置 Close #122, Close #123 Co-Authored-By: AAEE86 <33052466+AAEE86@users.noreply.github.com>
This commit is contained in:
@@ -109,6 +109,9 @@ class MessageTelemetry:
|
||||
provider_endpoint_id: Optional[str] = None,
|
||||
provider_api_key_id: Optional[str] = None,
|
||||
api_format: Optional[str] = None,
|
||||
# 格式转换追踪
|
||||
endpoint_api_format: Optional[str] = None, # 端点原生 API 格式
|
||||
has_format_conversion: bool = False, # 是否发生了格式转换
|
||||
# 模型映射信息
|
||||
target_model: Optional[str] = None,
|
||||
# Provider 响应元数据(如 Gemini 的 modelVersion)
|
||||
@@ -135,6 +138,8 @@ class MessageTelemetry:
|
||||
cache_read_input_tokens=cache_read_tokens,
|
||||
request_type="chat",
|
||||
api_format=api_format,
|
||||
endpoint_api_format=endpoint_api_format,
|
||||
has_format_conversion=has_format_conversion,
|
||||
is_stream=is_stream,
|
||||
response_time_ms=response_time_ms,
|
||||
first_byte_time_ms=first_byte_time_ms, # 传递首字时间
|
||||
@@ -195,6 +200,9 @@ class MessageTelemetry:
|
||||
response_body: Optional[Dict[str, Any]] = None,
|
||||
response_headers: Optional[Dict[str, Any]] = None,
|
||||
client_response_headers: Optional[Dict[str, Any]] = None,
|
||||
# 格式转换追踪
|
||||
endpoint_api_format: Optional[str] = None,
|
||||
has_format_conversion: bool = False,
|
||||
# 模型映射信息
|
||||
target_model: Optional[str] = None,
|
||||
) -> None:
|
||||
@@ -230,6 +238,8 @@ class MessageTelemetry:
|
||||
cache_read_input_tokens=cache_read_tokens,
|
||||
request_type="chat",
|
||||
api_format=api_format,
|
||||
endpoint_api_format=endpoint_api_format,
|
||||
has_format_conversion=has_format_conversion,
|
||||
is_stream=is_stream,
|
||||
response_time_ms=response_time_ms,
|
||||
status_code=status_code,
|
||||
@@ -265,6 +275,9 @@ class MessageTelemetry:
|
||||
response_body: Optional[Dict[str, Any]] = None,
|
||||
response_headers: Optional[Dict[str, Any]] = None,
|
||||
client_response_headers: Optional[Dict[str, Any]] = None,
|
||||
# 格式转换追踪
|
||||
endpoint_api_format: Optional[str] = None,
|
||||
has_format_conversion: bool = False,
|
||||
target_model: Optional[str] = None,
|
||||
) -> None:
|
||||
"""
|
||||
@@ -286,6 +299,8 @@ class MessageTelemetry:
|
||||
cache_read_input_tokens=cache_read_tokens,
|
||||
request_type="chat",
|
||||
api_format=api_format,
|
||||
endpoint_api_format=endpoint_api_format,
|
||||
has_format_conversion=has_format_conversion,
|
||||
is_stream=is_stream,
|
||||
response_time_ms=response_time_ms,
|
||||
first_byte_time_ms=first_byte_time_ms,
|
||||
@@ -488,6 +503,9 @@ class BaseMessageHandler:
|
||||
key_id = ctx.key_id
|
||||
first_byte_time_ms = ctx.first_byte_time_ms
|
||||
api_format = ctx.api_format
|
||||
# 格式转换追踪
|
||||
endpoint_api_format = ctx.provider_api_format or None
|
||||
has_format_conversion = ctx.needs_conversion
|
||||
|
||||
# 如果 provider 为空,记录警告(不应该发生,但用于调试)
|
||||
if not provider:
|
||||
@@ -512,6 +530,8 @@ class BaseMessageHandler:
|
||||
provider_api_key_id=key_id,
|
||||
first_byte_time_ms=first_byte_time_ms,
|
||||
api_format=api_format,
|
||||
endpoint_api_format=endpoint_api_format,
|
||||
has_format_conversion=has_format_conversion,
|
||||
)
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
@@ -713,7 +713,8 @@ class ChatHandlerBase(BaseMessageHandler, ABC):
|
||||
)
|
||||
|
||||
# 流式请求使用 stream_first_byte_timeout 作为首字节超时
|
||||
request_timeout = config.stream_first_byte_timeout
|
||||
# 优先使用 Provider 配置,否则使用全局配置
|
||||
request_timeout = provider.stream_first_byte_timeout or config.stream_first_byte_timeout
|
||||
|
||||
# 创建 HTTP 客户端(支持代理配置,从 Provider 读取)
|
||||
from src.clients.http_client import HTTPClientPool
|
||||
@@ -851,6 +852,9 @@ class ChatHandlerBase(BaseMessageHandler, ABC):
|
||||
provider_request_headers=ctx.provider_request_headers,
|
||||
response_headers=ctx.response_headers,
|
||||
client_response_headers=client_response_headers,
|
||||
# 格式转换追踪
|
||||
endpoint_api_format=ctx.provider_api_format or None,
|
||||
has_format_conversion=ctx.needs_conversion,
|
||||
target_model=ctx.mapped_model,
|
||||
)
|
||||
|
||||
@@ -987,7 +991,8 @@ class ChatHandlerBase(BaseMessageHandler, ABC):
|
||||
from src.clients.http_client import HTTPClientPool
|
||||
|
||||
# 非流式请求使用 http_request_timeout 作为整体超时
|
||||
request_timeout = config.http_request_timeout
|
||||
# 优先使用 Provider 配置,否则使用全局配置
|
||||
request_timeout = provider.request_timeout or config.http_request_timeout
|
||||
http_client = await HTTPClientPool.get_proxy_client(
|
||||
proxy_config=provider.proxy,
|
||||
)
|
||||
@@ -1140,6 +1145,9 @@ class ChatHandlerBase(BaseMessageHandler, ABC):
|
||||
is_stream=False,
|
||||
provider_request_headers=provider_request_headers,
|
||||
api_format=api_format,
|
||||
# 格式转换追踪
|
||||
endpoint_api_format=provider_api_format_for_error or None,
|
||||
has_format_conversion=needs_conversion_for_error,
|
||||
provider_id=provider_id,
|
||||
provider_endpoint_id=endpoint_id,
|
||||
provider_api_key_id=key_id,
|
||||
@@ -1203,6 +1211,9 @@ class ChatHandlerBase(BaseMessageHandler, ABC):
|
||||
provider_request_headers=provider_request_headers,
|
||||
response_headers=response_headers,
|
||||
client_response_headers={"content-type": "application/json"},
|
||||
# 格式转换追踪
|
||||
endpoint_api_format=provider_api_format_for_error or None,
|
||||
has_format_conversion=needs_conversion_for_error,
|
||||
target_model=mapped_model_result,
|
||||
)
|
||||
client_format = (client_api_format_for_error or "").upper()
|
||||
@@ -1249,6 +1260,9 @@ class ChatHandlerBase(BaseMessageHandler, ABC):
|
||||
response_headers=error_response_headers,
|
||||
# 非流式失败返回给客户端的是 JSON 错误响应
|
||||
client_response_headers={"content-type": "application/json"},
|
||||
# 格式转换追踪
|
||||
endpoint_api_format=provider_api_format_for_error or None,
|
||||
has_format_conversion=needs_conversion_for_error,
|
||||
# 模型映射信息
|
||||
target_model=mapped_model_result,
|
||||
)
|
||||
|
||||
@@ -739,7 +739,8 @@ class CliMessageHandlerBase(BaseMessageHandler):
|
||||
)
|
||||
|
||||
# 流式请求使用 stream_first_byte_timeout 作为首字节超时
|
||||
request_timeout = config.stream_first_byte_timeout
|
||||
# 优先使用 Provider 配置,否则使用全局配置
|
||||
request_timeout = provider.stream_first_byte_timeout or config.stream_first_byte_timeout
|
||||
|
||||
logger.debug(
|
||||
f" └─ [{self.request_id}] 发送流式请求: "
|
||||
@@ -1068,7 +1069,8 @@ class CliMessageHandlerBase(BaseMessageHandler):
|
||||
provider_parser = self.parser
|
||||
|
||||
# 使用共享的 TTFB 超时函数读取首字节
|
||||
ttfb_timeout = config.stream_first_byte_timeout
|
||||
# 优先使用 Provider 配置,否则使用全局配置
|
||||
ttfb_timeout = provider.stream_first_byte_timeout or config.stream_first_byte_timeout
|
||||
first_chunk, aiter = await read_first_chunk_with_ttfb_timeout(
|
||||
byte_iterator,
|
||||
timeout=ttfb_timeout,
|
||||
@@ -1804,6 +1806,9 @@ class CliMessageHandlerBase(BaseMessageHandler):
|
||||
response_body=response_body,
|
||||
response_headers=ctx.response_headers,
|
||||
client_response_headers=client_response_headers,
|
||||
# 格式转换追踪
|
||||
endpoint_api_format=ctx.provider_api_format or None,
|
||||
has_format_conversion=ctx.needs_conversion,
|
||||
# 模型映射信息
|
||||
target_model=ctx.mapped_model,
|
||||
)
|
||||
@@ -1845,6 +1850,9 @@ class CliMessageHandlerBase(BaseMessageHandler):
|
||||
is_stream=True,
|
||||
provider_request_headers=ctx.provider_request_headers,
|
||||
api_format=ctx.api_format,
|
||||
# 格式转换追踪
|
||||
endpoint_api_format=ctx.provider_api_format or None,
|
||||
has_format_conversion=ctx.needs_conversion,
|
||||
# Provider 侧追踪信息(用于记录真实成本)
|
||||
provider_id=ctx.provider_id,
|
||||
provider_endpoint_id=ctx.endpoint_id,
|
||||
@@ -1977,6 +1985,9 @@ class CliMessageHandlerBase(BaseMessageHandler):
|
||||
provider_request_headers=ctx.provider_request_headers,
|
||||
response_headers=ctx.response_headers,
|
||||
client_response_headers=client_response_headers,
|
||||
# 格式转换追踪
|
||||
endpoint_api_format=ctx.provider_api_format or None,
|
||||
has_format_conversion=ctx.needs_conversion,
|
||||
# 模型映射信息
|
||||
target_model=ctx.mapped_model,
|
||||
)
|
||||
@@ -2101,7 +2112,8 @@ class CliMessageHandlerBase(BaseMessageHandler):
|
||||
from src.clients.http_client import HTTPClientPool
|
||||
|
||||
# 非流式请求使用 http_request_timeout 作为整体超时
|
||||
request_timeout = config.http_request_timeout
|
||||
# 优先使用 Provider 配置,否则使用全局配置
|
||||
request_timeout = provider.request_timeout or config.http_request_timeout
|
||||
http_client = await HTTPClientPool.get_proxy_client(
|
||||
proxy_config=provider.proxy,
|
||||
)
|
||||
@@ -2274,6 +2286,9 @@ class CliMessageHandlerBase(BaseMessageHandler):
|
||||
is_stream=False,
|
||||
provider_request_headers=provider_request_headers,
|
||||
api_format=api_format,
|
||||
# 格式转换追踪
|
||||
endpoint_api_format=provider_api_format or None,
|
||||
has_format_conversion=needs_conversion,
|
||||
# Provider 侧追踪信息(用于记录真实成本)
|
||||
provider_id=provider_id,
|
||||
provider_endpoint_id=endpoint_id,
|
||||
@@ -2346,6 +2361,9 @@ class CliMessageHandlerBase(BaseMessageHandler):
|
||||
response_headers=error_response_headers,
|
||||
# 非流式失败返回给客户端的是 JSON 错误响应
|
||||
client_response_headers={"content-type": "application/json"},
|
||||
# 格式转换追踪
|
||||
endpoint_api_format=provider_api_format or None,
|
||||
has_format_conversion=needs_conversion,
|
||||
# 模型映射信息
|
||||
target_model=mapped_model_result,
|
||||
)
|
||||
|
||||
@@ -213,7 +213,8 @@ class StreamProcessor:
|
||||
|
||||
try:
|
||||
# 使用共享的 TTFB 超时函数读取首字节
|
||||
ttfb_timeout = config.stream_first_byte_timeout
|
||||
# 优先使用 Provider 配置,否则使用全局配置
|
||||
ttfb_timeout = provider.stream_first_byte_timeout or config.stream_first_byte_timeout
|
||||
first_chunk, aiter = await read_first_chunk_with_ttfb_timeout(
|
||||
byte_iterator,
|
||||
timeout=ttfb_timeout,
|
||||
@@ -422,6 +423,8 @@ class StreamProcessor:
|
||||
f"[{self.request_id}] needs_conversion=True 但 provider_format 为空,回退到透传模式"
|
||||
)
|
||||
needs_conversion = False
|
||||
# 保持 ctx 与实际行为一致,避免 Usage 记录误标记为转换
|
||||
ctx.needs_conversion = False
|
||||
|
||||
def _mark_stream_started() -> None:
|
||||
nonlocal start_time, streaming_started
|
||||
|
||||
Reference in New Issue
Block a user