feat: carry the per-call tier down to the transport that must send it
The Transport port took the request apart into five arguments, so a tier placed on ChatRequest could never reach _build_payload: the field was set, read by nobody, and silently ignored - the exact shape of failure that sent downstream to extra_body in the first place. complete() now takes reasoning_effort with no default, matching the TelemetryRecorder convention: a default would turn a missing hand-off into a silent 'no opinion'. All four fakes move with it, since @runtime_checkable checks method names and not signatures. EmbeddingTransport and OcrTransport are deliberately left alone - they have no reasoning semantics - and a test now holds that line. _build_payload drops its inline sugar conversion for effective_effort(), so the guard and the hot path share one judgement, and passes the source's effort_fallback for the same reason.
This commit is contained in:
@@ -285,6 +285,9 @@ class RetryMW:
|
||||
stream=request.stream,
|
||||
overlay=request.overlay,
|
||||
call_id=call_id,
|
||||
# 逐次尝试原样重传: 换源不改变调用方要的档位(源级默认由 transport
|
||||
# 自己按选中的源解析,两者在 effective_effort 里汇合)
|
||||
reasoning_effort=request.reasoning_effort,
|
||||
)
|
||||
if result.usage_source == "unavailable":
|
||||
# 用量不可得时按入场预扣量结算(delta==0),否则押金会被整笔退回,
|
||||
|
||||
@@ -12,6 +12,7 @@ from typing import Any, Protocol, runtime_checkable
|
||||
|
||||
from .types import (
|
||||
ChatRequest,
|
||||
Effort,
|
||||
EmbeddingTransportResult,
|
||||
LLMResponse,
|
||||
OcrLayoutResult,
|
||||
@@ -36,7 +37,16 @@ class Middleware(Protocol):
|
||||
|
||||
@runtime_checkable
|
||||
class Transport(Protocol):
|
||||
"""一次原始调用的协议细节(请求组装/流式解析/错误翻译);不含任何治理。"""
|
||||
"""一次原始调用的协议细节(请求组装/流式解析/错误翻译);不含任何治理。
|
||||
|
||||
`reasoning_effort` 是本次调用要求的推理档位(`None` = 不表态,随源级配置)。
|
||||
它必须走**协议参数**而不能让 transport 自己去读 `ChatRequest`: 端口只收拆开的
|
||||
请求要素,是为了让 transport 不依赖洋葱内部的请求类型(P7 端口最内层)。
|
||||
|
||||
该参数**不设默认值**,与 `TelemetryRecorder.record_llm_call` 同一既有约定:
|
||||
库外无第三方实现者,写全签名的成本为零,而默认值会把"某一层漏传"变成静默的
|
||||
"调用方没表态"——一次本该报错的漏配就此变成一次悄悄涨价的调用。
|
||||
"""
|
||||
|
||||
async def complete(
|
||||
self,
|
||||
@@ -46,6 +56,7 @@ class Transport(Protocol):
|
||||
stream: bool,
|
||||
overlay: dict[str, Any],
|
||||
call_id: str,
|
||||
reasoning_effort: Effort | None,
|
||||
) -> TransportResult: ...
|
||||
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@ from polygateway.streaming import StreamLivenessTimeout, stream_with_liveness_ti
|
||||
from polygateway.thinking import (
|
||||
ThinkingCapability,
|
||||
ThinkingUnsupportedError,
|
||||
effective_effort,
|
||||
get_capability,
|
||||
observe_thinking,
|
||||
reconcile_thinking,
|
||||
@@ -351,6 +352,7 @@ class OpenAICompatTransport:
|
||||
profile: ProviderProfile,
|
||||
stream: bool,
|
||||
overlay: dict[str, Any],
|
||||
reasoning_effort: Effort | None,
|
||||
) -> dict[str, Any]:
|
||||
payload: dict[str, Any] = {"model": source.model, "messages": messages, "stream": stream}
|
||||
if stream:
|
||||
@@ -360,17 +362,19 @@ class OpenAICompatTransport:
|
||||
capability = get_capability(source.model, table=self._capabilities)
|
||||
first_time = source.model not in self._warned_models
|
||||
self._warned_models.add(source.model)
|
||||
# `enable_thinking` 的档位语法糖(True → auto,False → none,None 不表态);
|
||||
# 就地转换是过渡形态,T5 起由 thinking.effective_effort() 统一收口并接上
|
||||
# 源级/请求级档位(设计 §4.2)
|
||||
enabled = source.enable_thinking
|
||||
effort = None if enabled is None else (Effort.AUTO if enabled else Effort.NONE)
|
||||
# 三层优先级在此汇合: 请求级 > 源级 > enable_thinking 语法糖(设计 §4.2)。
|
||||
# 判定与装配守卫共用同一个纯函数,两处分叉就会变成"装配期放行、运行期报错"
|
||||
payload.update(
|
||||
resolve_thinking(
|
||||
profile,
|
||||
capability,
|
||||
effort,
|
||||
effective_effort(
|
||||
request_effort=reasoning_effort,
|
||||
source_effort=source.reasoning_effort,
|
||||
enable_thinking=source.enable_thinking,
|
||||
),
|
||||
model=source.model,
|
||||
fallback=source.effort_fallback,
|
||||
warn_unregistered=first_time,
|
||||
).payload
|
||||
)
|
||||
@@ -388,12 +392,22 @@ class OpenAICompatTransport:
|
||||
stream: bool,
|
||||
overlay: dict[str, Any],
|
||||
call_id: str,
|
||||
reasoning_effort: Effort | None,
|
||||
) -> TransportResult:
|
||||
"""一次原始调用;HTTP/线路/流式异常按 ARCH §6.2 翻译为领域错误。"""
|
||||
"""一次原始调用;HTTP/线路/流式异常按 ARCH §6.2 翻译为领域错误。
|
||||
|
||||
`reasoning_effort` 是**请求级**档位(`None` = 不表态);它与源级配置的优先级
|
||||
在 `_build_payload` 里由 `effective_effort` 裁定,本层只负责把它送到。
|
||||
"""
|
||||
profile = get_provider(source.provider, registry=self._registry)
|
||||
try:
|
||||
payload = self._build_payload(
|
||||
messages=messages, source=source, profile=profile, stream=stream, overlay=overlay
|
||||
messages=messages,
|
||||
source=source,
|
||||
profile=profile,
|
||||
stream=stream,
|
||||
overlay=overlay,
|
||||
reasoning_effort=reasoning_effort,
|
||||
)
|
||||
except ThinkingUnsupportedError as exc:
|
||||
# 推理开关不可满足是**请求本身**的问题: 换源重试都救不了它。只捕这个
|
||||
|
||||
Reference in New Issue
Block a user