mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-02 17:30:23 +08:00
fix(request-body): 使用 deepcopy 防止请求体在处理流程中被意外修改
handler 基类和格式转换 registry 中,原始请求体通过浅拷贝或直接引用传递, 导致下游处理(模型映射、格式转换、重试整流)可能修改原始数据, 影响后续重试或并发请求的正确性。统一改用 copy.deepcopy 隔离副本。
This commit is contained in:
@@ -22,6 +22,7 @@ Chat Handler Base - Chat API 格式的通用基类
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import copy
|
||||
import json
|
||||
from abc import ABC, abstractmethod
|
||||
from collections.abc import AsyncGenerator, Awaitable, Callable
|
||||
@@ -499,7 +500,7 @@ class ChatHandlerBase(BaseMessageHandler, ABC):
|
||||
|
||||
# 可变请求体容器:允许 TaskService 在遇到 Thinking 签名错误时整流请求体后重试
|
||||
# 结构: {"body": 实际请求体, "_rectified": 是否已整流, "_rectified_this_turn": 本轮是否整流}
|
||||
request_body_ref: dict[str, Any] = {"body": original_request_body}
|
||||
request_body_ref: dict[str, Any] = {"body": copy.deepcopy(original_request_body)}
|
||||
|
||||
# 创建类型安全的流式上下文
|
||||
ctx = StreamContext(
|
||||
@@ -723,10 +724,9 @@ class ChatHandlerBase(BaseMessageHandler, ABC):
|
||||
)
|
||||
|
||||
# 应用模型映射到请求体
|
||||
request_body = copy.deepcopy(original_request_body)
|
||||
if mapped_model:
|
||||
request_body = self.apply_mapped_model(original_request_body, mapped_model)
|
||||
else:
|
||||
request_body = dict(original_request_body)
|
||||
request_body = self.apply_mapped_model(request_body, mapped_model)
|
||||
|
||||
provider_type = str(getattr(provider, "provider_type", "") or "").lower()
|
||||
behavior = get_provider_behavior(
|
||||
|
||||
@@ -11,6 +11,7 @@ ChatSyncExecutor - 非流式请求执行器
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import copy
|
||||
import json
|
||||
from dataclasses import dataclass, field
|
||||
from typing import TYPE_CHECKING, Any
|
||||
@@ -123,7 +124,7 @@ class ChatSyncExecutor:
|
||||
|
||||
# 可变请求体容器:允许 TaskService 在遇到 Thinking 签名错误时整流请求体后重试
|
||||
# 结构: {"body": 实际请求体, "_rectified": 是否已整流, "_rectified_this_turn": 本轮是否整流}
|
||||
request_body_ref: dict[str, Any] = {"body": original_request_body}
|
||||
request_body_ref: dict[str, Any] = {"body": copy.deepcopy(original_request_body)}
|
||||
|
||||
# 捕获的上下文变量
|
||||
ctx = self._ctx
|
||||
|
||||
@@ -4,6 +4,7 @@ from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import codecs
|
||||
import copy
|
||||
import json
|
||||
import time
|
||||
from collections.abc import AsyncGenerator
|
||||
@@ -97,7 +98,7 @@ class CliStreamMixin:
|
||||
|
||||
# 可变请求体容器:允许 TaskService 在遇到 Thinking 签名错误时整流请求体后重试
|
||||
# 结构: {"body": 实际请求体, "_rectified": 是否已整流, "_rectified_this_turn": 本轮是否整流}
|
||||
request_body_ref: dict[str, Any] = {"body": original_request_body}
|
||||
request_body_ref: dict[str, Any] = {"body": copy.deepcopy(original_request_body)}
|
||||
|
||||
# 使用子类实现的方法提取 model(不同 API 格式的 model 位置不同)
|
||||
# 注意:使用 original_request_body,因为整流只修改 messages,不影响 model 字段
|
||||
@@ -309,11 +310,10 @@ class CliStreamMixin:
|
||||
)
|
||||
|
||||
# 应用模型映射到请求体(子类可覆盖此方法处理不同格式)
|
||||
request_body = copy.deepcopy(original_request_body)
|
||||
if mapped_model:
|
||||
ctx.mapped_model = mapped_model # 保存映射后的模型名,用于 Usage 记录
|
||||
request_body = self.apply_mapped_model(original_request_body, mapped_model)
|
||||
else:
|
||||
request_body = original_request_body
|
||||
request_body = self.apply_mapped_model(request_body, mapped_model)
|
||||
|
||||
client_api_format = (
|
||||
ctx.client_api_format.value
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import copy
|
||||
import json
|
||||
import time
|
||||
from typing import TYPE_CHECKING, Any
|
||||
@@ -115,7 +116,7 @@ class CliSyncMixin:
|
||||
|
||||
# 可变请求体容器:允许 TaskService 在遇到 Thinking 签名错误时整流请求体后重试
|
||||
# 结构: {"body": 实际请求体, "_rectified": 是否已整流, "_rectified_this_turn": 本轮是否整流}
|
||||
request_body_ref: dict[str, Any] = {"body": original_request_body}
|
||||
request_body_ref: dict[str, Any] = {"body": copy.deepcopy(original_request_body)}
|
||||
|
||||
async def sync_request_func(
|
||||
provider: "Provider",
|
||||
@@ -136,11 +137,10 @@ class CliSyncMixin:
|
||||
)
|
||||
|
||||
# 应用模型映射到请求体(子类可覆盖此方法处理不同格式)
|
||||
request_body = copy.deepcopy(request_body_ref["body"])
|
||||
if mapped_model:
|
||||
mapped_model_result = mapped_model # 保存映射后的模型名,用于 Usage 记录
|
||||
request_body = self.apply_mapped_model(request_body_ref["body"], mapped_model)
|
||||
else:
|
||||
request_body = dict(request_body_ref["body"])
|
||||
request_body = self.apply_mapped_model(request_body, mapped_model)
|
||||
|
||||
client_api_format = (
|
||||
api_format.value if hasattr(api_format, "value") else str(api_format)
|
||||
|
||||
@@ -10,6 +10,7 @@ source -> internal -> target
|
||||
"""
|
||||
|
||||
import ast
|
||||
import copy
|
||||
import importlib
|
||||
import inspect
|
||||
import threading
|
||||
@@ -250,7 +251,7 @@ class FormatConversionRegistry:
|
||||
output_limit: int | None = None,
|
||||
) -> dict[str, Any]:
|
||||
if self._same_normalizer(source_format, target_format) and not target_variant:
|
||||
return request
|
||||
return copy.deepcopy(request)
|
||||
|
||||
# 同 normalizer + variant: 优先尝试轻量补丁(跳过 internal 转换)
|
||||
if self._same_normalizer(source_format, target_format) and target_variant:
|
||||
@@ -258,7 +259,7 @@ class FormatConversionRegistry:
|
||||
with _track_conversion_metrics(
|
||||
"request_patch", str(source_format).upper(), str(target_format).upper()
|
||||
):
|
||||
patched = normalizer.patch_for_variant(request, target_variant)
|
||||
patched = normalizer.patch_for_variant(copy.deepcopy(request), target_variant)
|
||||
if patched is not None:
|
||||
return patched
|
||||
|
||||
@@ -269,7 +270,7 @@ class FormatConversionRegistry:
|
||||
"request", str(source_format).upper(), str(target_format).upper()
|
||||
):
|
||||
try:
|
||||
internal = src.request_to_internal(request)
|
||||
internal = src.request_to_internal(copy.deepcopy(request))
|
||||
internal.output_limit = output_limit
|
||||
repair_stats = self._repair_internal_tool_call_ids(internal)
|
||||
if repair_stats["generated_tool_use_ids"] or repair_stats["filled_tool_result_ids"]:
|
||||
@@ -295,7 +296,7 @@ class FormatConversionRegistry:
|
||||
) -> dict[str, Any]:
|
||||
"""异步版本的 convert_request,在 internal 阶段执行图片 URL 下载等异步操作。"""
|
||||
if self._same_normalizer(source_format, target_format) and not target_variant:
|
||||
return request
|
||||
return copy.deepcopy(request)
|
||||
|
||||
# 同 normalizer + variant: 优先尝试轻量补丁(跳过 internal 转换)
|
||||
if self._same_normalizer(source_format, target_format) and target_variant:
|
||||
@@ -303,7 +304,7 @@ class FormatConversionRegistry:
|
||||
with _track_conversion_metrics(
|
||||
"request_patch", str(source_format).upper(), str(target_format).upper()
|
||||
):
|
||||
patched = normalizer.patch_for_variant(request, target_variant)
|
||||
patched = normalizer.patch_for_variant(copy.deepcopy(request), target_variant)
|
||||
if patched is not None:
|
||||
return patched
|
||||
|
||||
@@ -314,7 +315,7 @@ class FormatConversionRegistry:
|
||||
"request", str(source_format).upper(), str(target_format).upper()
|
||||
):
|
||||
try:
|
||||
internal = src.request_to_internal(request)
|
||||
internal = src.request_to_internal(copy.deepcopy(request))
|
||||
internal.output_limit = output_limit
|
||||
repair_stats = self._repair_internal_tool_call_ids(internal)
|
||||
if repair_stats["generated_tool_use_ids"] or repair_stats["filled_tool_result_ids"]:
|
||||
@@ -352,15 +353,15 @@ class FormatConversionRegistry:
|
||||
而不是上游返回的映射后模型名。
|
||||
"""
|
||||
if self._same_normalizer(source_format, target_format):
|
||||
response_copy = copy.deepcopy(response)
|
||||
# 即使格式相同,也需要替换 model 字段
|
||||
if requested_model and isinstance(response, dict):
|
||||
response = dict(response) # 避免修改原始响应
|
||||
if requested_model and isinstance(response_copy, dict):
|
||||
# 支持不同格式的 model 字段名
|
||||
if "model" in response:
|
||||
response["model"] = requested_model
|
||||
elif "modelVersion" in response:
|
||||
response["modelVersion"] = requested_model
|
||||
return response
|
||||
if "model" in response_copy:
|
||||
response_copy["model"] = requested_model
|
||||
elif "modelVersion" in response_copy:
|
||||
response_copy["modelVersion"] = requested_model
|
||||
return response_copy
|
||||
|
||||
src = self._require_normalizer(source_format)
|
||||
tgt = self._require_normalizer(target_format)
|
||||
@@ -369,7 +370,7 @@ class FormatConversionRegistry:
|
||||
"response", str(source_format).upper(), str(target_format).upper()
|
||||
):
|
||||
try:
|
||||
internal = src.response_to_internal(response)
|
||||
internal = src.response_to_internal(copy.deepcopy(response))
|
||||
return tgt.response_from_internal(internal, requested_model=requested_model)
|
||||
except Exception as e:
|
||||
raise FormatConversionError(source_format, target_format, str(e)) from e
|
||||
@@ -381,7 +382,7 @@ class FormatConversionRegistry:
|
||||
target_format: str,
|
||||
) -> dict[str, Any]:
|
||||
if self._same_normalizer(source_format, target_format):
|
||||
return error_response
|
||||
return copy.deepcopy(error_response)
|
||||
|
||||
src = self._require_normalizer(source_format)
|
||||
tgt = self._require_normalizer(target_format)
|
||||
@@ -400,7 +401,7 @@ class FormatConversionRegistry:
|
||||
"error", str(source_format).upper(), str(target_format).upper()
|
||||
):
|
||||
try:
|
||||
internal = src.error_to_internal(error_response)
|
||||
internal = src.error_to_internal(copy.deepcopy(error_response))
|
||||
return tgt.error_from_internal(internal)
|
||||
except Exception as e:
|
||||
raise FormatConversionError(source_format, target_format, str(e)) from e
|
||||
@@ -428,7 +429,7 @@ class FormatConversionRegistry:
|
||||
tgt_base = self._video_format_to_base(target_format)
|
||||
|
||||
if src_base == tgt_base:
|
||||
return request
|
||||
return copy.deepcopy(request)
|
||||
|
||||
src = self._require_normalizer(src_base)
|
||||
tgt = self._require_normalizer(tgt_base)
|
||||
@@ -437,7 +438,7 @@ class FormatConversionRegistry:
|
||||
"video_request", str(source_format).upper(), str(target_format).upper()
|
||||
):
|
||||
try:
|
||||
internal = src.video_request_to_internal(request)
|
||||
internal = src.video_request_to_internal(copy.deepcopy(request))
|
||||
return tgt.video_request_from_internal(internal)
|
||||
except Exception as e:
|
||||
raise FormatConversionError(source_format, target_format, str(e)) from e
|
||||
@@ -462,7 +463,7 @@ class FormatConversionRegistry:
|
||||
tgt_base = self._video_format_to_base(target_format)
|
||||
|
||||
if src_base == tgt_base:
|
||||
return task_response
|
||||
return copy.deepcopy(task_response)
|
||||
|
||||
src = self._require_normalizer(src_base)
|
||||
tgt = self._require_normalizer(tgt_base)
|
||||
@@ -471,7 +472,7 @@ class FormatConversionRegistry:
|
||||
"video_task", str(source_format).upper(), str(target_format).upper()
|
||||
):
|
||||
try:
|
||||
internal = src.video_task_to_internal(task_response)
|
||||
internal = src.video_task_to_internal(copy.deepcopy(task_response))
|
||||
return tgt.video_task_from_internal(internal)
|
||||
except Exception as e:
|
||||
raise FormatConversionError(source_format, target_format, str(e)) from e
|
||||
|
||||
Reference in New Issue
Block a user