feat: add gateway client with env-driven assembly

Includes config aggregation for multi-source env keys, from_env and
from_settings factories with explicit shared-backend injection,
gather_bounded, top-level exports, tightened import-linter layers with
the gate removed from the Makefile, and the finalized .env.example.
This commit is contained in:
2026-07-20 07:47:05 -04:00
parent 936895919c
commit 7b9815f4bc
30 changed files with 1701 additions and 253 deletions
+77 -25
View File
@@ -31,48 +31,89 @@ class _DummyPermit:
class _DummyLimiter:
async def try_acquire(self, source_key: str, est_tokens: int): return _DummyPermit()
async def acquire(self, source_key: str, est_tokens: int): return _DummyPermit()
async def source_stats(self, source_key: str): return SourceStats(0, 0, 0)
async def try_acquire(self, source_key: str, est_tokens: int):
return _DummyPermit()
async def acquire(self, source_key: str, est_tokens: int):
return _DummyPermit()
async def source_stats(self, source_key: str):
return SourceStats(0, 0, 0)
async def mark_progress(self) -> None: ...
async def progress_age_s(self) -> float: return 0.0
async def progress_age_s(self) -> float:
return 0.0
class _DummyGate:
async def try_enter(self, source_name: str, owner: str): raise NotImplementedError
async def record_success(self, entry): raise NotImplementedError
async def record_failure(self, entry, reason: str, force_open: bool): raise NotImplementedError
async def release_probe(self, entry): raise NotImplementedError
async def retry_after_s(self, sources): return 0.0
async def try_enter(self, source_name: str, owner: str):
raise NotImplementedError
async def record_success(self, entry):
raise NotImplementedError
async def record_failure(self, entry, reason: str, force_open: bool):
raise NotImplementedError
async def release_probe(self, entry):
raise NotImplementedError
async def retry_after_s(self, sources):
return 0.0
class _DummyMw:
async def __call__(self, request, call_next): return await call_next(request)
async def __call__(self, request, call_next):
return await call_next(request)
class _DummyTransport:
async def complete(self, *, messages, source, stream, overlay, call_id): raise NotImplementedError
async def complete(self, *, messages, source, stream, overlay, call_id):
raise NotImplementedError
class _DummyCache:
async def get(self, key: str): return None
async def get(self, key: str):
return None
async def set(self, key: str, value: str, ttl_s: int) -> None: ...
class _DummySelector:
def order(self, sources, stats): return list(sources)
def order(self, sources, stats):
return list(sources)
class _DummyStrategy:
def request_overlay(self, schema): return {}
def parse(self, text: str) -> Any: return {}
def request_overlay(self, schema):
return {}
def parse(self, text: str) -> Any:
return {}
class _DummyRecorder:
async def record_llm_call(
self, *, call_id, parent_call_id, session_id, model, provider, source_name,
messages, response, thinking, prompt_tokens, completion_tokens, usage_source,
latency_ms, ttft_ms, max_inter_token_ms, cache_hit, error, cost,
self,
*,
call_id,
parent_call_id,
session_id,
model,
provider,
source_name,
messages,
response,
thinking,
prompt_tokens,
completion_tokens,
usage_source,
latency_ms,
ttft_ms,
max_inter_token_ms,
cache_hit,
error,
cost,
) -> None: ...
@@ -95,10 +136,15 @@ def test_protocols_are_runtime_checkable(impl, protocol):
def _decision(**overrides) -> GateDecision:
base = dict(
source_name="qwen_1", allowed=True, state=GateState.CLOSED,
epoch=0, is_probe=False, probe_owner=None, retry_after_s=0.0,
)
base = {
"source_name": "qwen_1",
"allowed": True,
"state": GateState.CLOSED,
"epoch": 0,
"is_probe": False,
"probe_owner": None,
"retry_after_s": 0.0,
}
base.update(overrides)
return GateDecision(**base)
@@ -141,9 +187,15 @@ class TestGateDecisionInvariants:
class TestGateUpdate:
def test_bounds(self):
u = GateUpdate(applied=True, state=GateState.CLOSED, epoch=0, failure_count=0, retry_after_s=0.0)
u = GateUpdate(
applied=True, state=GateState.CLOSED, epoch=0, failure_count=0, retry_after_s=0.0
)
assert u.applied
with pytest.raises(ValueError):
GateUpdate(applied=True, state=GateState.CLOSED, epoch=-1, failure_count=0, retry_after_s=0.0)
GateUpdate(
applied=True, state=GateState.CLOSED, epoch=-1, failure_count=0, retry_after_s=0.0
)
with pytest.raises(ValueError):
GateUpdate(applied=True, state=GateState.CLOSED, epoch=0, failure_count=-1, retry_after_s=0.0)
GateUpdate(
applied=True, state=GateState.CLOSED, epoch=0, failure_count=-1, retry_after_s=0.0
)