feat: let chat() take a tenant and caller-defined dimensions
Validation runs before the request enters the onion: every failure inside it is downgraded to a warning by the telemetry layer, so validating in there would not validate anything. The dimensions stay out of the cache key — cache_namespace already carries tenant isolation, and folding meta in would cold-start every existing entry.
This commit is contained in:
@@ -184,6 +184,62 @@ class TestSamplingOverlay:
|
||||
assert [c["seed"] for c in captured] == [1, 2]
|
||||
|
||||
|
||||
class _MemoryRecorder:
|
||||
"""收下遥测行原样存起来;断言"哪些行被写了"必须能看到零行的情形。"""
|
||||
|
||||
def __init__(self):
|
||||
self.rows = []
|
||||
|
||||
async def record_llm_call(self, **fields):
|
||||
self.rows.append(fields)
|
||||
|
||||
|
||||
class TestCallerDimensions:
|
||||
"""调用方自定义维度进遥测(issue #11 Task 4)。"""
|
||||
|
||||
_MSG = [{"role": "user", "content": "hi"}]
|
||||
|
||||
async def test_dimensions_reach_telemetry_row(self):
|
||||
recorder = _MemoryRecorder()
|
||||
async with _client(telemetry=recorder) as client:
|
||||
await client.chat(self._MSG, tenant_id="t1", meta={"batch": "b-42"})
|
||||
row = recorder.rows[-1]
|
||||
assert row["tenant_id"] == "t1"
|
||||
assert json.loads(row["meta"]) == {"batch": "b-42"}
|
||||
|
||||
async def test_default_path_writes_sentinels(self):
|
||||
"""不传两参数时落哨兵值而非 NULL(§4.4: NULL 在 RLS 下是永久不可见的黑洞)。"""
|
||||
recorder = _MemoryRecorder()
|
||||
async with _client(telemetry=recorder) as client:
|
||||
await client.chat(self._MSG)
|
||||
row = recorder.rows[-1]
|
||||
assert row["tenant_id"] == "" and row["meta"] == "{}"
|
||||
|
||||
async def test_invalid_meta_key_rejected_before_any_telemetry(self):
|
||||
"""校验早于遥测(§4.2 核心承诺): 放进洋葱就会被降级成 warning 而调用照常发出。"""
|
||||
recorder = _MemoryRecorder()
|
||||
async with _client(telemetry=recorder) as client:
|
||||
with pytest.raises(ValueError, match="meta"):
|
||||
await client.chat(self._MSG, meta={"BAD-KEY": 1})
|
||||
assert recorder.rows == []
|
||||
|
||||
async def test_non_finite_float_rejected_before_any_telemetry(self):
|
||||
"""nan 产出的是 PG 拒收的非法 JSON;放行等于把调用方 bug 变成静默丢遥测(§6)。"""
|
||||
recorder = _MemoryRecorder()
|
||||
async with _client(telemetry=recorder) as client:
|
||||
with pytest.raises(ValueError, match="nan"):
|
||||
await client.chat(self._MSG, meta={"k": float("nan")})
|
||||
assert recorder.rows == []
|
||||
|
||||
async def test_meta_does_not_enter_cache_key(self):
|
||||
"""仅 meta 不同必须仍命中缓存(F1): 进 key 会让存量缓存全量冷启动且不报错。"""
|
||||
client = _client(cache=InMemoryCache(), cache_namespace="proj", cache_ttl_s=3600)
|
||||
async with client:
|
||||
first = await client.chat(self._MSG, meta={"batch": "b-1"})
|
||||
second = await client.chat(self._MSG, meta={"batch": "b-2"})
|
||||
assert first.cache_hit is False and second.cache_hit is True
|
||||
|
||||
|
||||
class TestModelFingerprint:
|
||||
"""配置级采样参数须进缓存身份,否则改 temperature 后仍读旧缓存(决策 C)。"""
|
||||
|
||||
|
||||
Reference in New Issue
Block a user