feat: strip extra_body on the embedding and OCR paths with a warning
Stripping is load-bearing, not tidying: those transports never send the value, so leaving it would make telemetry record a parameter never sent.
This commit is contained in:
@@ -41,7 +41,12 @@ from polygateway.middleware.ratelimit import QuotaGate
|
||||
from polygateway.middleware.retry import _failure_reason, backoff_delay
|
||||
from polygateway.middleware.telemetry import TelemetryEmitter
|
||||
from polygateway.sources import SourceCooldownMemo
|
||||
from polygateway.types import ChatRequest, EmbeddingResponse, LLMResponse
|
||||
from polygateway.types import (
|
||||
ChatRequest,
|
||||
EmbeddingResponse,
|
||||
LLMResponse,
|
||||
strip_unsupported_extra_body,
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Awaitable, Callable, Mapping
|
||||
@@ -111,7 +116,9 @@ class EmbeddingClient:
|
||||
if expected_dim is not None and expected_dim < 1:
|
||||
raise ValueError("expected_dim 必须 ≥ 1")
|
||||
self._scope = scope
|
||||
self._sources = list(sources)
|
||||
# embed payload 硬编码 {model, input},带 extra_body 的源必须先剥离,
|
||||
# 否则遥测会记录一个从未发出的采样参数(issue #4 决策 G)
|
||||
self._sources = strip_unsupported_extra_body(list(sources), path="embedding")
|
||||
self._selector = selector
|
||||
self._quota = QuotaGate(limiter)
|
||||
self._breaker = BreakerGate(breaker)
|
||||
|
||||
@@ -44,6 +44,7 @@ from polygateway.types import (
|
||||
OcrLayoutResult,
|
||||
OcrTextResult,
|
||||
Usage,
|
||||
strip_unsupported_extra_body,
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@@ -113,7 +114,9 @@ class OcrClient:
|
||||
if quota_full not in ("wait", "fail_fast"):
|
||||
raise ValueError(f"quota_full 必须是 wait|fail_fast: {quota_full!r}")
|
||||
self._scope = scope
|
||||
self._sources = list(sources)
|
||||
# MonkeyOCR 只发 multipart 表单,带 extra_body 的源必须先剥离,否则
|
||||
# 遥测会记录一个从未发出的采样参数(issue #4 决策 G)
|
||||
self._sources = strip_unsupported_extra_body(list(sources), path="OCR")
|
||||
self._selector = selector
|
||||
self._feed_health = isinstance(selector, OutcomeAwareSelector)
|
||||
self._quota = QuotaGate(limiter)
|
||||
|
||||
@@ -4,12 +4,15 @@
|
||||
fake,字段顺序即公共承诺;新增字段只增不删且必带默认值。
|
||||
"""
|
||||
|
||||
import dataclasses
|
||||
import json
|
||||
from collections.abc import Mapping
|
||||
from dataclasses import dataclass, field
|
||||
from types import MappingProxyType
|
||||
from typing import Any
|
||||
|
||||
from loguru import logger
|
||||
|
||||
_MISSING_DONE_DOMAIN = frozenset({"retry", "salvage"})
|
||||
|
||||
_PROTECTED_OVERLAY_KEYS: Mapping[str, str] = MappingProxyType(
|
||||
@@ -234,6 +237,34 @@ class SourceConfig:
|
||||
object.__setattr__(self, "extra_body", MappingProxyType(validated))
|
||||
|
||||
|
||||
def strip_unsupported_extra_body(
|
||||
sources: list[SourceConfig], *, path: str
|
||||
) -> list[SourceConfig]:
|
||||
"""剥离非 chat 路径不消费的 `extra_body` 并 warning(issue #4 决策 G)。
|
||||
|
||||
剥离是必需的而非顺手清理: embedding 的 payload 硬编码 `{model, input}`、
|
||||
MonkeyOCR 只发 multipart 表单,两者都不会把 `extra_body` 发出去;但遥测的
|
||||
`sampling` 列会并上 `source.extra_body`,不剥离就等于**记录一个从未发出的
|
||||
参数**——那是数据造假,污染的恰是事后复现的唯一依据。
|
||||
|
||||
选择 warning 放行而非报错: 这两条路径本无采样语义,配错的后果远轻于 chat
|
||||
路径,不值得让下游整个装配起不来(2026-07-31 人类拍板)。
|
||||
"""
|
||||
stripped = []
|
||||
for source in sources:
|
||||
if source.extra_body:
|
||||
logger.warning(
|
||||
"{} 路径暂不支持 extra_body,源 {} 的该配置已被忽略"
|
||||
"(需要 dimensions 等参数请提 issue): {}",
|
||||
path,
|
||||
source.name,
|
||||
dict(source.extra_body),
|
||||
)
|
||||
source = dataclasses.replace(source, extra_body={})
|
||||
stripped.append(source)
|
||||
return stripped
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class RetryPolicy:
|
||||
"""重试策略;max_attempts = 总尝试次数(含首次,M1 设计 §2.3 统一语义)。"""
|
||||
|
||||
Reference in New Issue
Block a user