fix: close the failure modes review found in the new code
Three of them were the same shape as the bug this branch exists to fix: something goes wrong, the library swallows it, and the caller is left with a number that means the opposite of what happened. The throttle key had no source in it. Five sources on one model is the normal case here, so the first one to break would warn once and silence the other four for the life of the process, and the message never said which gateway to look at. An unknown verdict in a cached entry threw away the whole response. The rehydrator tolerates unknown fields but not unknown values of a known field, so two library versions sharing a Redis would each invalidate the other's entries: halved hit rate, and the only log line says the cache rebuild failed. A purely observational field should not be able to void a response whose content is intact. Normalising for telemetry now degrades instead of raising, both for a bare string and for a value outside the domain. Either one used to reach the same except and cost the whole row, which is exactly how 1.3.0 lost nineteen calls without anyone noticing.
This commit is contained in:
@@ -322,9 +322,12 @@ class OpenAICompatTransport:
|
||||
# 未登记模型只喊一次: 装配期已喊过,逐次调用再喊是日志洪水。
|
||||
# 实例级而非模块级 —— 模块级可变状态违反纯 asyncio 中立铁律
|
||||
self._warned_models: set[str] = set()
|
||||
# 对账告警独立节流,**不复用** `_warned_models`: 那个 set 的语义是"未登记
|
||||
# 能力已告警过",两件事共用一个开关会互相压制——一方喊过就把另一方静音
|
||||
self._warned_mismatches: set[tuple[str, bool | None]] = set()
|
||||
# 对账告警独立节流,**不复用** `_warned_models`: 两者语义不同(那个 set 记
|
||||
# 的是"未登记能力已告警过",这个记的是"某源某方向的矛盾已告警过"),共用
|
||||
# 一个容器会让两种告警的生命周期纠缠在一起——将来任一侧想加清空/过期策略,
|
||||
# 都会连带改掉另一侧的行为。(键空间恰好不相交,故当下**不会**互相压制;
|
||||
# 分开维护的理由是语义,不是碰撞)
|
||||
self._warned_mismatches: set[tuple[str, str, bool | None]] = set()
|
||||
self._client_factory = client_factory or _default_client_factory
|
||||
self._clients: dict[str, httpx.AsyncClient] = {}
|
||||
|
||||
@@ -410,11 +413,20 @@ class OpenAICompatTransport:
|
||||
return result
|
||||
|
||||
def _warn_on_thinking_mismatch(self, source: SourceConfig, result: TransportResult) -> None:
|
||||
"""声明与观测矛盾即 warning;按 (model, direction) 节流,同组合只喊一次。
|
||||
"""声明与观测矛盾即 warning;按 (source, model, direction) 节流,同组合只喊一次。
|
||||
|
||||
节流键必须含方向: 同一模型的开、关两档是两个独立的矛盾,合并键会让先出现
|
||||
的那一档把另一档永久静音。逐次调用刷屏会把告警变成噪声,噪声等于没有告警。
|
||||
三段缺一不可。**方向**: 同一模型的开、关两档是两个独立的矛盾。**源名**:
|
||||
多源多账号是本库的核心场景,同一 model 跨 N 个源是常态,而每个源背后是
|
||||
独立的账号/网关,一个源的行为不代表另一个——漏掉源名,5 个源里第一个出
|
||||
问题的喊完一次,其余四个永久静音。逐次调用刷屏会把告警变成噪声,噪声等于
|
||||
没有告警。
|
||||
|
||||
**先判键再对账**: `reconcile_thinking` 会拼含完整 `evidence` 的长字符串,
|
||||
而非流式档每次调用都命中这一分支,节流后再拼是纯粹的热路径浪费。
|
||||
"""
|
||||
key = (source.name, source.model, source.enable_thinking)
|
||||
if key in self._warned_mismatches:
|
||||
return
|
||||
message = reconcile_thinking(
|
||||
enable_thinking=source.enable_thinking,
|
||||
observation=result.thinking_observation,
|
||||
@@ -423,11 +435,13 @@ class OpenAICompatTransport:
|
||||
)
|
||||
if message is None:
|
||||
return
|
||||
key = (source.model, source.enable_thinking)
|
||||
if key in self._warned_mismatches:
|
||||
return
|
||||
self._warned_mismatches.add(key)
|
||||
logger.warning(message)
|
||||
# 源名拼在调用点而不是加进 `reconcile_thinking` 的签名: 那是纯判定函数,
|
||||
# 输入只该含判定依据(声明/观测/能力/模型),源名是**定位信息**,进不了判据。
|
||||
# 单参数传入 loguru: 文案里带 `thinking:{type:disabled}` 这类字面花括号
|
||||
# (能力表 evidence),将来有人给这行加个格式化参数就会炸在成功调用的返回
|
||||
# 路径上(与 telemetry/sqlite.py 的缺列告警同一先例)
|
||||
logger.warning("源 {} —— {}", source.name, message)
|
||||
|
||||
async def embed(
|
||||
self, *, texts: list[str], source: SourceConfig, call_id: str
|
||||
|
||||
Reference in New Issue
Block a user