Skip to content

Reference

llmbroker

llmbroker — a standalone, host-agnostic LLM-provider broker.

AsyncBroker

Façade over the LLM pool: route completions, inspect state, edit the catalog.

Every constructor argument is documented in docs/ — "Model pool and calls" and "Direct model calls".

Source code in src/llmbroker/broker/broker.py
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
class AsyncBroker:
    """Façade over the LLM pool: route completions, inspect state, edit the catalog.

    Every constructor argument is documented in ``docs/`` — "Model pool and calls"
    and "Direct model calls".
    """

    def __init__(  # noqa: PLR0913
        self,
        registry: RegistryProtocol | str | Path | None = None,
        *,
        secrets: SecretsProtocol | None = None,
        store: StoreProtocol | None = None,
        optimize: bool | Optimizer = True,
        sync: str | None | _SyncDefault = _SYNC_DEFAULT,
        sync_interval: float | None = _DEFAULT_SYNC_INTERVAL,
        home: str | Path | None = None,
        direct: Sequence[str | LLMConfig] = (),
    ) -> None:
        _check_sync_interval(sync_interval)
        source = _resolve_sync(sync, registry)
        self._home = home_dir(home)
        source_secrets: SecretsProtocol | None = None
        source_store: StoreProtocol | None = None
        source_label: str | None = None
        if registry is None:
            registry, source_secrets, source_store = zero_config_ports(self._home)
        elif isinstance(registry, (str, Path)):
            source_label = str(registry)
            registry, source_secrets, source_store = resolve_source(registry)

        secrets = (
            as_secrets(secrets) if secrets is not None else (source_secrets or default_secrets())
        )
        store = store if store is not None else (source_store or default_store())

        if isinstance(optimize, Optimizer):
            self._optimizer: Optimizer | None = optimize
        elif optimize:
            self._optimizer = Optimizer()
        else:
            self._optimizer = None

        self._registry = registry
        self._secrets = secrets
        self._store = store
        self._presets = PresetSource(self._home)
        self._shared_ring = KeyRing(secrets)
        self._rings: dict[str, KeyRing] = {}
        self._known: frozenset[str] | None = None

        self._declared = tuple(direct)
        self._autofetch = sync_interval is not None
        self._last_declared: DeclaredModels | None = None
        pool = LLMPool(optimizer=self._optimizer)
        self._pool = pool
        self._catalog = Catalog(
            registry,
            secrets,
            pool,
            self._shared_ring,
            store,
            overlay=self._resolve_declared if self._declared else None,
            autofill=self._autofetch,
            relearn=self._relearn,
        )

        self._learner: Learner | None = None
        if self._optimizer is not None:
            self._learner = Learner(self._optimizer, store, pool)

        self._router = Router(
            pool,
            store,
            optimizer=self._optimizer,
            learner=self._learner,
        )
        self._pool_view = PoolView(
            pool,
            self._metrics_map,
            lambda: self._catalog.health,
            lambda: self._catalog.direct_missing_keys,
            lambda: self._catalog.payable,
        )

        self._refresher = ModelListRefresher(
            registry,
            self._catalog,
            store,
            self._presets,
            source=source,
            interval=sync_interval,
            home=self._home,
            declared=self._declared,
            target_label=source_label,
            live=lambda: self._provisioned,
            rebuild=self.rebuild,
        )

        self._provisioned = False
        self._provision_lock = asyncio.Lock()
        self._last_underprov_alert: float = float("-inf")
        self._underprov_alert_interval: float = 60.0
        self._next_exhaustion_rebuild: float = float("-inf")
        self.llms = self._caller(self._shared_ring)

    def _caller(self, ring: KeyRing) -> AsyncLLMs:
        return AsyncLLMs(
            ring,
            router=self._router,
            catalog=self._catalog,
            pool_view=self._pool_view,
            store=self._store,
            learner=self._learner,
            ensure_pool=self.ensure_pool,
            on_exhausted=self._on_exhausted,
        )

    def for_scope(self, scope: str) -> AsyncLLMs:
        """A caller that pays with ``scope``\'s own keys, falling back to the shared
        ones, and writes ``scope`` on every row it journals. Costs no I/O."""
        if not scope:
            raise ValueError("scope must not be empty string; use broker.llms for unscoped")
        ring = self._rings.get(scope)
        if ring is None:
            if len(self._rings) >= _MAX_CALLERS:
                self._rings.pop(next(iter(self._rings)))
            ring = KeyRing(
                self._secrets,
                scope=scope,
                shared=self._shared_ring,
                known=self._known,
            )
            self._rings[scope] = ring
        return self._caller(ring)

    async def rebuild(self) -> None:
        """Rebuild the pool: every caller's keys, the registry, pool membership, the
        disabled map and quality, wholesale. Fires on exactly four triggers — start,
        the refresh clock, an explicit ``sync()``, and pool exhaustion."""
        known = await known_refs(self._secrets)
        self._known = known
        # Over a copy: a request may ask for a caller while this is awaiting.
        for ring in (self._shared_ring, *list(self._rings.values())):
            await ring.refresh(known)
        await self._catalog.rebuild(known)

    async def _relearn(self) -> None:
        if self._learner is not None:
            await self._learner.relearn()

    async def _rebuild_safely(self, reason: str) -> None:
        """A rebuild reached from a caller's own call must never fail it: an
        unreadable port leaves the pool exactly as it is, and says so."""
        try:
            await self.rebuild()
        except Exception:  # noqa: BLE001 - a background re-read may not break a request
            logger.exception("pool rebuild on %s failed, continuing on the current pool", reason)

    async def _metrics_map(self) -> dict[str, LLMMetrics]:
        """Per-LLM metrics from whatever is available: the learner's cache, a
        queryable store's tail, or nothing."""
        if self._learner is not None:
            return self._learner.metrics
        if isinstance(self._store, QueryableStoreProtocol):
            return metrics_from_calls(await self._store.calls(limit=TAIL_READ_LIMIT))
        return {}

    async def _resolve_declared(self) -> DeclaredModels:
        """Re-resolve ``direct=``, keeping the resolution already in use when the
        catalog cannot be read or no longer carries an alias. Only the first
        resolution raises — see ``rules/direct-by-name.md``."""
        previous = self._last_declared
        try:
            resolved, moved = await resolve_declared(
                self._declared,
                self._presets,
                previous=previous,
                fetch=self._autofetch,
            )
        except (UnknownModelError, ValueError, OSError) as exc:
            if previous is None:
                raise
            logger.warning(
                "direct= could not be re-resolved (%s) — declared models stay on the"
                " resolution already in use",
                exc,
            )
            return previous
        for line in alias_lines(moved):
            logger.info("direct=: %s", line)
        self._last_declared = resolved
        return resolved

    # ------------------------------------------------------------------
    # Lifecycle
    # ------------------------------------------------------------------

    async def ensure_pool(self) -> None:
        """Lazy idempotent initializer — provisions the pool exactly once, and
        schedules the model list refresh when its interval has elapsed.

        Raises if the registry is empty and nothing filled it — sync a model list in.
        """
        if not self._provisioned:
            async with self._provision_lock:
                if not self._provisioned:
                    await self._refresher.before_provision()
                    await self.rebuild()
                    self._catalog.check_not_empty()
                    self._provisioned = True
        # Outside the lock: a refresh calls sync(), and inside it the catalog is
        # mid-provision.
        self._refresher.schedule()

    @property
    def last_sync_report(self) -> SyncReport | None:
        """What the last sync — explicit or refreshed — did, or ``None`` if none has
        run. A host forwards it to its own admin channel."""
        return self._refresher.last_report

    async def sync(self, source: str | None = None) -> SyncReport | None:
        """Merge the curated preset named by ``source`` into the registry and return
        what it did; with no argument, whatever this installation follows — the paid
        catalog alone has no report. See ``rules/model-list.md``."""
        return await self._refresher.sync(source)

    async def aclose(self) -> None:
        # Before the ports: a refresh in flight would otherwise write through a
        # registry whose driver is closing.
        await self._refresher.aclose()
        await self._router.aclose()
        for port in (self._registry, self._secrets, self._store):
            if isinstance(port, AsyncResourceProtocol):
                await port.aclose()

    async def __aenter__(self) -> "AsyncBroker":
        await self.ensure_pool()
        return self

    async def __aexit__(self, *exc: object) -> None:
        await self.aclose()

    # ------------------------------------------------------------------
    # Routing — delegated to the unscoped caller
    # ------------------------------------------------------------------

    async def ask(  # noqa: PLR0913 - the call knobs, one keyword each
        self,
        prompt: str,
        *,
        operation: str | None = None,
        trace_id: str | None = None,
        wait: float | None = None,
        fastest_of: int | None = None,
        parallel_recovery: bool = True,
        response_format: dict | None = None,
    ) -> AsyncResult:
        return await self.llms.ask(
            prompt,
            operation=operation,
            trace_id=trace_id,
            wait=wait,
            fastest_of=fastest_of,
            parallel_recovery=parallel_recovery,
            response_format=response_format,
        )

    async def chat(  # noqa: PLR0913 - what to send, and the call knobs
        self,
        messages: list[dict],
        *,
        tools: list[dict] | None = None,
        operation: str | None = None,
        trace_id: str | None = None,
        wait: float | None = None,
        fastest_of: int | None = None,
        parallel_recovery: bool = True,
        response_format: dict | None = None,
    ) -> AsyncResult:
        return await self.llms.chat(
            messages,
            tools=tools,
            operation=operation,
            trace_id=trace_id,
            wait=wait,
            fastest_of=fastest_of,
            parallel_recovery=parallel_recovery,
            response_format=response_format,
        )

    def stream(  # noqa: PLR0913 - the call knobs, one keyword each
        self,
        prompt: str,
        *,
        operation: str | None = None,
        trace_id: str | None = None,
        wait: float | None = None,
        fastest_of: int | None = None,
        parallel_recovery: bool = True,
        response_format: dict | None = None,
    ) -> StreamHandle:
        """Route a completion over the pool as a handle yielding text deltas and naming
        what answered them. ``wait`` bounds the whole answer in provider time; past the
        first delta a death raises ``StreamInterruptedError``. Async-only."""
        return self.llms.stream(
            prompt,
            operation=operation,
            trace_id=trace_id,
            wait=wait,
            fastest_of=fastest_of,
            parallel_recovery=parallel_recovery,
            response_format=response_format,
        )

    async def direct(
        self,
        alias: str | None = None,
        *,
        name: str | None = None,
    ) -> AsyncDirectClient:
        """A client for exactly one model of your own — no pool, no failover.

        Takes exactly one of ``alias`` or ``name=``; raises ``PoolModelError``,
        ``UnknownModelError`` or ``MissingKeyError``. See ``docs/`` "Direct model calls".
        """
        return await self.llms.direct(alias, name=name)

    async def record_quality(
        self,
        score: float,
        *,
        call_id: str | None = None,
        trace_id: str | None = None,
    ) -> None:
        """Rate a past call — the delayed counterpart of ``result.record_quality``.

        Takes exactly one key and rates the newest answered call it names within the
        rating window; raises ``UnknownCallError`` when nothing there answered.
        """
        await self.llms.record_quality(score, call_id=call_id, trace_id=trace_id)

    # ------------------------------------------------------------------
    # Inspection
    # ------------------------------------------------------------------

    async def get(self, name: str) -> AsyncLLM:
        return await self.llms.get(name)

    async def count(self) -> int:
        return await self.llms.count()

    async def snapshot(self) -> PoolSnapshot:
        await self.ensure_pool()
        return await self._pool_view.snapshot()

    # ------------------------------------------------------------------
    # Manual disable — the one verdict that actually excludes
    # ------------------------------------------------------------------

    async def disable_llm(self, name: str) -> None:
        """Set the manual latch: withdraws the slot, survives preset rolls, covers
        every operation including future ones. Only ``enable_llm`` clears it."""
        await self.ensure_pool()
        self._pool.set_disabled(name)
        if isinstance(self._store, DisabledMapProtocol):
            await self._store.set_disabled(name, True)

    async def enable_llm(self, name: str) -> None:
        """Clear the manual latch — a re-enabled model rehabilitates through new
        ratings, no quality reset exists."""
        await self.ensure_pool()
        await self._pool.clear_disabled(name)
        if isinstance(self._store, DisabledMapProtocol):
            await self._store.set_disabled(name, False)

    # ------------------------------------------------------------------
    # Call journal
    # ------------------------------------------------------------------

    async def calls(
        self,
        *,
        limit: int,
        since: datetime | None = None,
        operation: str | None = None,
        trace_id: str | None = None,
        call_id: str | None = None,
    ) -> list[Call]:
        """Newest-first journal tail for the whole installation — one caller's own rows
        are ``for_scope(...).calls(...)``. One row per call attempt, each carrying the
        newest score it was rated with. Never provisions the pool."""
        return await self.llms.calls(
            limit=limit,
            since=since,
            operation=operation,
            trace_id=trace_id,
            call_id=call_id,
        )

    async def stats(
        self,
        *,
        since: datetime | None = None,
        limit: int = _DEFAULT_STATS_LIMIT,
        operation: str | None = None,
    ) -> Mapping[str, LLMStats]:
        """Per-model counts of call records over a window, keyed by model name.
        ``limit`` caps rows read, not the window: totals summing to it mean the
        window may be truncated. Never provisions the pool."""
        return await self.llms.stats(since=since, limit=limit, operation=operation)

    async def _on_exhausted(self, exc: NoLLMAvailableError, ring: KeyRing) -> bool:
        """The reactive trigger: a pool that could not answer is re-read, debounced and
        skipped for a caller already holding every key. Answers whether the re-read
        actually ran, which is what makes the caller's second pass worth taking."""
        self._maybe_alert_underprov(exc)
        now = time.monotonic()
        if now < self._next_exhaustion_rebuild:
            return False
        if await self._fully_keyed(ring):
            return False
        self._next_exhaustion_rebuild = now + _EXHAUSTION_DEBOUNCE_SEC
        await self._rebuild_safely("pool exhaustion")
        return True

    async def _fully_keyed(self, ring: KeyRing) -> bool:
        """Whether this caller can already pay for every ref the pool names. An empty
        pool is not a full set: there the registry itself is what needs re-reading."""
        refs = {cfg.api_key_ref for cfg in self._pool.configs.values() if cfg.api_key_ref}
        return bool(refs) and refs <= await ring.payable(refs)

    def _maybe_alert_underprov(self, exc: NoLLMAvailableError) -> None:
        """Fire when zero *keyed* configs are routable — the genuine alarm.

        Keyless configs are excluded because they are never cooled, so one of them
        would mask "every keyed model is COOLING"; other reasons log their own line.
        """
        if exc.reason != "timeout":
            return
        if self._optimizer is None:
            return
        if not self._pool.configs:
            return
        now = time.monotonic()
        if now - self._last_underprov_alert < self._underprov_alert_interval:
            return
        payable = self._catalog.payable
        keyed_names = [
            name for name, cfg in self._pool.configs.items() if cfg.api_key_ref in payable
        ]
        all_offline = all(
            self._pool.state(name).phase is not LifecyclePhase.AVAILABLE for name in keyed_names
        )
        if all_offline:
            self._last_underprov_alert = now
            logger.warning(
                "pool under-provisioned: all LLMs are COOLING — add more LLMs to the registry",
            )

last_sync_report property

What the last sync — explicit or refreshed — did, or None if none has run. A host forwards it to its own admin channel.

calls(*, limit, since=None, operation=None, trace_id=None, call_id=None) async

Newest-first journal tail for the whole installation — one caller's own rows are for_scope(...).calls(...). One row per call attempt, each carrying the newest score it was rated with. Never provisions the pool.

Source code in src/llmbroker/broker/broker.py
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
async def calls(
    self,
    *,
    limit: int,
    since: datetime | None = None,
    operation: str | None = None,
    trace_id: str | None = None,
    call_id: str | None = None,
) -> list[Call]:
    """Newest-first journal tail for the whole installation — one caller's own rows
    are ``for_scope(...).calls(...)``. One row per call attempt, each carrying the
    newest score it was rated with. Never provisions the pool."""
    return await self.llms.calls(
        limit=limit,
        since=since,
        operation=operation,
        trace_id=trace_id,
        call_id=call_id,
    )

direct(alias=None, *, name=None) async

A client for exactly one model of your own — no pool, no failover.

Takes exactly one of alias or name=; raises PoolModelError, UnknownModelError or MissingKeyError. See docs/ "Direct model calls".

Source code in src/llmbroker/broker/broker.py
421
422
423
424
425
426
427
428
429
430
431
432
async def direct(
    self,
    alias: str | None = None,
    *,
    name: str | None = None,
) -> AsyncDirectClient:
    """A client for exactly one model of your own — no pool, no failover.

    Takes exactly one of ``alias`` or ``name=``; raises ``PoolModelError``,
    ``UnknownModelError`` or ``MissingKeyError``. See ``docs/`` "Direct model calls".
    """
    return await self.llms.direct(alias, name=name)

disable_llm(name) async

Set the manual latch: withdraws the slot, survives preset rolls, covers every operation including future ones. Only enable_llm clears it.

Source code in src/llmbroker/broker/broker.py
466
467
468
469
470
471
472
async def disable_llm(self, name: str) -> None:
    """Set the manual latch: withdraws the slot, survives preset rolls, covers
    every operation including future ones. Only ``enable_llm`` clears it."""
    await self.ensure_pool()
    self._pool.set_disabled(name)
    if isinstance(self._store, DisabledMapProtocol):
        await self._store.set_disabled(name, True)

enable_llm(name) async

Clear the manual latch — a re-enabled model rehabilitates through new ratings, no quality reset exists.

Source code in src/llmbroker/broker/broker.py
474
475
476
477
478
479
480
async def enable_llm(self, name: str) -> None:
    """Clear the manual latch — a re-enabled model rehabilitates through new
    ratings, no quality reset exists."""
    await self.ensure_pool()
    await self._pool.clear_disabled(name)
    if isinstance(self._store, DisabledMapProtocol):
        await self._store.set_disabled(name, False)

ensure_pool() async

Lazy idempotent initializer — provisions the pool exactly once, and schedules the model list refresh when its interval has elapsed.

Raises if the registry is empty and nothing filled it — sync a model list in.

Source code in src/llmbroker/broker/broker.py
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
async def ensure_pool(self) -> None:
    """Lazy idempotent initializer — provisions the pool exactly once, and
    schedules the model list refresh when its interval has elapsed.

    Raises if the registry is empty and nothing filled it — sync a model list in.
    """
    if not self._provisioned:
        async with self._provision_lock:
            if not self._provisioned:
                await self._refresher.before_provision()
                await self.rebuild()
                self._catalog.check_not_empty()
                self._provisioned = True
    # Outside the lock: a refresh calls sync(), and inside it the catalog is
    # mid-provision.
    self._refresher.schedule()

for_scope(scope)

A caller that pays with scope's own keys, falling back to the shared ones, and writes scope on every row it journals. Costs no I/O.

Source code in src/llmbroker/broker/broker.py
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
def for_scope(self, scope: str) -> AsyncLLMs:
    """A caller that pays with ``scope``\'s own keys, falling back to the shared
    ones, and writes ``scope`` on every row it journals. Costs no I/O."""
    if not scope:
        raise ValueError("scope must not be empty string; use broker.llms for unscoped")
    ring = self._rings.get(scope)
    if ring is None:
        if len(self._rings) >= _MAX_CALLERS:
            self._rings.pop(next(iter(self._rings)))
        ring = KeyRing(
            self._secrets,
            scope=scope,
            shared=self._shared_ring,
            known=self._known,
        )
        self._rings[scope] = ring
    return self._caller(ring)

rebuild() async

Rebuild the pool: every caller's keys, the registry, pool membership, the disabled map and quality, wholesale. Fires on exactly four triggers — start, the refresh clock, an explicit sync(), and pool exhaustion.

Source code in src/llmbroker/broker/broker.py
242
243
244
245
246
247
248
249
250
251
async def rebuild(self) -> None:
    """Rebuild the pool: every caller's keys, the registry, pool membership, the
    disabled map and quality, wholesale. Fires on exactly four triggers — start,
    the refresh clock, an explicit ``sync()``, and pool exhaustion."""
    known = await known_refs(self._secrets)
    self._known = known
    # Over a copy: a request may ask for a caller while this is awaiting.
    for ring in (self._shared_ring, *list(self._rings.values())):
        await ring.refresh(known)
    await self._catalog.rebuild(known)

record_quality(score, *, call_id=None, trace_id=None) async

Rate a past call — the delayed counterpart of result.record_quality.

Takes exactly one key and rates the newest answered call it names within the rating window; raises UnknownCallError when nothing there answered.

Source code in src/llmbroker/broker/broker.py
434
435
436
437
438
439
440
441
442
443
444
445
446
async def record_quality(
    self,
    score: float,
    *,
    call_id: str | None = None,
    trace_id: str | None = None,
) -> None:
    """Rate a past call — the delayed counterpart of ``result.record_quality``.

    Takes exactly one key and rates the newest answered call it names within the
    rating window; raises ``UnknownCallError`` when nothing there answered.
    """
    await self.llms.record_quality(score, call_id=call_id, trace_id=trace_id)

stats(*, since=None, limit=_DEFAULT_STATS_LIMIT, operation=None) async

Per-model counts of call records over a window, keyed by model name. limit caps rows read, not the window: totals summing to it mean the window may be truncated. Never provisions the pool.

Source code in src/llmbroker/broker/broker.py
506
507
508
509
510
511
512
513
514
515
516
async def stats(
    self,
    *,
    since: datetime | None = None,
    limit: int = _DEFAULT_STATS_LIMIT,
    operation: str | None = None,
) -> Mapping[str, LLMStats]:
    """Per-model counts of call records over a window, keyed by model name.
    ``limit`` caps rows read, not the window: totals summing to it mean the
    window may be truncated. Never provisions the pool."""
    return await self.llms.stats(since=since, limit=limit, operation=operation)

stream(prompt, *, operation=None, trace_id=None, wait=None, fastest_of=None, parallel_recovery=True, response_format=None)

Route a completion over the pool as a handle yielding text deltas and naming what answered them. wait bounds the whole answer in provider time; past the first delta a death raises StreamInterruptedError. Async-only.

Source code in src/llmbroker/broker/broker.py
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
def stream(  # noqa: PLR0913 - the call knobs, one keyword each
    self,
    prompt: str,
    *,
    operation: str | None = None,
    trace_id: str | None = None,
    wait: float | None = None,
    fastest_of: int | None = None,
    parallel_recovery: bool = True,
    response_format: dict | None = None,
) -> StreamHandle:
    """Route a completion over the pool as a handle yielding text deltas and naming
    what answered them. ``wait`` bounds the whole answer in provider time; past the
    first delta a death raises ``StreamInterruptedError``. Async-only."""
    return self.llms.stream(
        prompt,
        operation=operation,
        trace_id=trace_id,
        wait=wait,
        fastest_of=fastest_of,
        parallel_recovery=parallel_recovery,
        response_format=response_format,
    )

sync(source=None) async

Merge the curated preset named by source into the registry and return what it did; with no argument, whatever this installation follows — the paid catalog alone has no report. See rules/model-list.md.

Source code in src/llmbroker/broker/broker.py
327
328
329
330
331
async def sync(self, source: str | None = None) -> SyncReport | None:
    """Merge the curated preset named by ``source`` into the registry and return
    what it did; with no argument, whatever this installation follows — the paid
    catalog alone has no report. See ``rules/model-list.md``."""
    return await self._refresher.sync(source)

AsyncDirectClient

Async direct client for one named model — stream() and ask(). Pass an httpx.AsyncClient to share a connection pool, or let it open and close its own.

Source code in src/llmbroker/direct.py
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
class AsyncDirectClient:
    """Async direct client for one named model — ``stream()`` and ``ask()``. Pass an
    ``httpx.AsyncClient`` to share a connection pool, or let it open and close its
    own."""

    def __init__(
        self,
        *,
        base_url: str,
        model: str,
        api_key: str,
        timeout: float = _DEFAULT_TIMEOUT,
        client: httpx.AsyncClient | None = None,
    ) -> None:
        self._base_url = base_url
        self._model = model
        self._api_key = api_key
        self._timeout = timeout
        self._http = client
        self._owns_http = client is None

    def _ensure_http(self) -> httpx.AsyncClient:
        if self._http is None:
            self._http = make_client(self._timeout)
        return self._http

    def _request(
        self,
        prompt: str | None,
        messages: list[dict] | None,
        *,
        stream: bool = False,
        params: Mapping[str, object] | None = None,
    ) -> tuple[str, dict[str, str], dict]:
        return build_chat_request(
            self._base_url,
            self._model,
            self._api_key,
            _messages(prompt, messages),
            stream=stream,
            params=params,
        )

    async def ask(
        self,
        prompt: str | None = None,
        *,
        messages: list[dict] | None = None,
        timeout: float | None = None,
        params: Mapping[str, object] | None = None,
    ) -> DirectResult:
        url, headers, body = self._request(prompt, messages, params=params)
        try:
            resp = await self._ensure_http().post(
                url,
                headers=headers,
                json=body,
                timeout=timeout or self._timeout,
            )
        except httpx.TimeoutException as exc:
            raise LLMTimeoutError("direct call timed out") from exc
        return _result(resp, self._model)

    async def stream(
        self,
        prompt: str | None = None,
        *,
        messages: list[dict] | None = None,
        timeout: float | None = None,
        params: Mapping[str, object] | None = None,
    ) -> AsyncIterator[str]:
        url, headers, body = self._request(prompt, messages, stream=True, params=params)
        try:
            async with self._ensure_http().stream(
                "POST",
                url,
                headers=headers,
                json=body,
                timeout=timeout or self._timeout,
            ) as resp:
                if resp.status_code >= ERROR_FLOOR:
                    detail = (await resp.aread()).decode(errors="replace")[:DETAIL_SNIPPET]
                    raise provider_error(resp.status_code, detail, resp.headers)
                produced = False
                async for chunk in aiter_chat_chunks(resp, self._model):
                    delta, _ = parse_stream_chunk(chunk, self._model)
                    if delta:
                        produced = True
                        yield delta
                if not produced:
                    raise empty_answer_error(self._model, NO_DELTA)
        except httpx.TimeoutException as exc:
            raise LLMTimeoutError("direct stream timed out") from exc

    async def aclose(self) -> None:
        if self._owns_http and self._http is not None:
            await self._http.aclose()
            self._http = None

    async def __aenter__(self) -> "AsyncDirectClient":
        return self

    async def __aexit__(self, *exc: object) -> None:
        await self.aclose()

AsyncLLM

Handle returned by AsyncBroker.get(name) — live view into broker internals.

Source code in src/llmbroker/broker/result.py
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
class AsyncLLM:
    """Handle returned by ``AsyncBroker.get(name)`` — live view into broker internals."""

    def __init__(
        self,
        name: str,
        config: LLMConfig,
        pool: LLMPool,
        metrics_source: MetricsSource,
    ) -> None:
        self._name = name
        self._config = config
        self._pool = pool
        self._metrics_source = metrics_source

    @property
    def config(self) -> LLMConfig:
        return self._config

    @property
    def disabled(self) -> bool:
        return self._pool.is_disabled(self._name)

    async def state(self) -> LLMState:
        return self._pool.state(self._name)

    async def metrics(self) -> LLMMetrics:
        all_metrics = await self._metrics_source()
        return all_metrics.get(self._name, LLMMetrics(0, None, None))

AsyncLLMs

Route and rate calls over the broker's one shared pool, paying with this caller's keys and writing its scope on every row it journals.

Source code in src/llmbroker/broker/llms.py
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
class AsyncLLMs:
    """Route and rate calls over the broker's one shared pool, paying with this
    caller's keys and writing its scope on every row it journals."""

    def __init__(  # noqa: PLR0913 - a caller is its ring over the installation's parts
        self,
        ring: KeyRing,
        *,
        router: Router,
        catalog: Catalog,
        pool_view: PoolView,
        store: StoreProtocol,
        learner: Learner | None,
        ensure_pool: Callable[[], Awaitable[None]],
        on_exhausted: Callable[[NoLLMAvailableError, KeyRing], Awaitable[bool]],
    ) -> None:
        self._ring = ring
        self._router = router
        self._catalog = catalog
        self._pool_view = pool_view
        self._store = store
        self._learner = learner
        self._ensure_pool = ensure_pool
        self._on_exhausted = on_exhausted

    @property
    def scope(self) -> str | None:
        """Whose calls these are — the attribution on this caller's journal rows."""
        return self._ring.scope

    # ------------------------------------------------------------------
    # Routing
    # ------------------------------------------------------------------

    async def ask(  # noqa: PLR0913 - the call knobs, one keyword each
        self,
        prompt: str,
        *,
        operation: str | None = None,
        trace_id: str | None = None,
        wait: float | None = None,
        fastest_of: int | None = None,
        parallel_recovery: bool = True,
        response_format: dict | None = None,
    ) -> AsyncResult:
        return await self.chat(
            [{"role": "user", "content": prompt}],
            operation=operation,
            trace_id=trace_id,
            wait=wait,
            fastest_of=fastest_of,
            parallel_recovery=parallel_recovery,
            response_format=response_format,
        )

    async def chat(  # noqa: PLR0913 - what to send, and the call knobs
        self,
        messages: list[dict],
        *,
        tools: list[dict] | None = None,
        operation: str | None = None,
        trace_id: str | None = None,
        wait: float | None = None,
        fastest_of: int | None = None,
        parallel_recovery: bool = True,
        response_format: dict | None = None,
    ) -> AsyncResult:
        await self._ensure_pool()
        try:
            return await self._router.chat(
                self._ring,
                messages,
                tools=tools,
                operation=operation,
                trace_id=trace_id,
                wait=wait,
                fastest_of=fastest_of,
                parallel_recovery=parallel_recovery,
                response_format=response_format,
            )
        except NoLLMAvailableError as exc:
            if not await self._on_exhausted(exc, self._ring):
                raise
        # The ports were re-read inside this request, so it may as well have the
        # answer. ``wait=0``: a second pass may not spend the budget twice.
        return await self._router.chat(
            self._ring,
            messages,
            tools=tools,
            operation=operation,
            trace_id=trace_id,
            wait=0,
            fastest_of=fastest_of,
            parallel_recovery=parallel_recovery,
            response_format=response_format,
        )

    def stream(  # noqa: PLR0913 - the call knobs, one keyword each
        self,
        prompt: str,
        *,
        operation: str | None = None,
        trace_id: str | None = None,
        wait: float | None = None,
        fastest_of: int | None = None,
        parallel_recovery: bool = True,
        response_format: dict | None = None,
    ) -> StreamHandle:
        """Route a completion over the pool as a handle yielding text deltas and naming
        what answered them. ``wait`` bounds the whole answer in provider time; past the
        first delta a death raises ``StreamInterruptedError``. Async-only."""
        receipt = CallReceipt()
        return StreamHandle(
            self._deltas(
                receipt,
                prompt,
                operation=operation,
                trace_id=trace_id,
                wait=wait,
                fastest_of=fastest_of,
                parallel_recovery=parallel_recovery,
                response_format=response_format,
            ),
            receipt,
            operation=operation,
            store=self._store,
            scope=self.scope,
            observe_quality=(
                self._learner.record_quality_observed if self._learner is not None else None
            ),
        )

    async def _deltas(  # noqa: PLR0913 - the call knobs, one keyword each
        self,
        receipt: CallReceipt,
        prompt: str,
        *,
        operation: str | None,
        trace_id: str | None,
        wait: float | None,
        fastest_of: int | None,
        parallel_recovery: bool,
        response_format: dict | None,
    ) -> AsyncGenerator[str, None]:
        await self._ensure_pool()
        messages = [{"role": "user", "content": prompt}]
        produced = False
        try:
            async with aclosing(
                self._router.stream(
                    self._ring,
                    messages,
                    receipt,
                    operation=operation,
                    trace_id=trace_id,
                    wait=wait,
                    fastest_of=fastest_of,
                    parallel_recovery=parallel_recovery,
                    response_format=response_format,
                ),
            ) as deltas:
                async for delta in deltas:
                    produced = True
                    yield delta
            return
        except NoLLMAvailableError as exc:
            # ``produced`` guards invariant 18: past the first delta the answer is
            # already partly the caller's, and a second pass could only splice.
            if produced or not await self._on_exhausted(exc, self._ring):
                raise
        async with aclosing(
            self._router.stream(
                self._ring,
                messages,
                receipt,
                operation=operation,
                trace_id=trace_id,
                wait=0,
                fastest_of=fastest_of,
                parallel_recovery=parallel_recovery,
                response_format=response_format,
            ),
        ) as deltas:
            async for delta in deltas:
                yield delta

    # ------------------------------------------------------------------
    # Direct single-model access (no pool, no failover)
    # ------------------------------------------------------------------

    async def direct(
        self,
        alias: str | None = None,
        *,
        name: str | None = None,
    ) -> AsyncDirectClient:
        """A client for exactly one model of your own — no pool, no failover.

        Takes exactly one of ``alias`` or ``name=``; raises ``PoolModelError``,
        ``UnknownModelError`` or ``MissingKeyError``. See ``docs/`` "Direct model calls".
        """
        cfg, key = await self.resolve_direct(alias, name=name)
        return AsyncDirectClient(
            base_url=cfg.base_url,
            model=cfg.model,
            api_key=key,
            client=self._http(),
        )

    async def resolve_direct(
        self,
        alias: str | None = None,
        *,
        name: str | None = None,
    ) -> tuple[LLMConfig, str]:
        """Look the entry up in its keyspace and resolve it against this caller's ring
        (shared by the synchronous façade)."""
        if (alias is None) == (name is None):
            raise ValueError(
                "direct() takes exactly one of alias (positional) or name= —"
                " they are separate keyspaces",
            )
        stored, declared = await self._catalog.entries()
        cfg = find_declared(stored, declared, alias, name)
        ref = alias if alias is not None else name
        key = await self._ring.resolve(cfg.api_key_ref)
        if key is None:
            hint = self._catalog.key_help(cfg.api_key_ref)
            raise MissingKeyError(
                f"api_key_ref {cfg.api_key_ref!r} for model {ref!r} could not be resolved"
                " — set the env var or configure a secrets backend" + (f". {hint}" if hint else ""),
            )
        return cfg, key

    def _http(self) -> httpx.AsyncClient:
        """The one client of the installation — a caller opens no connection pool of
        its own."""
        return self._router.http

    # ------------------------------------------------------------------
    # Inspection and rating
    # ------------------------------------------------------------------

    async def get(self, name: str) -> AsyncLLM:
        await self._ensure_pool()
        return self._pool_view.get(name)

    async def count(self) -> int:
        await self._ensure_pool()
        return self._pool_view.count()

    async def record_quality(
        self,
        score: float,
        *,
        call_id: str | None = None,
        trace_id: str | None = None,
    ) -> None:
        """Rate a past call, by exactly one key — one rating, on the newest answered
        call the key names within the rating window. Raises ``UnknownCallError`` when
        nothing there answered. See ``docs/`` "Quality rating"."""
        check_score(score)
        if (call_id is None) == (trace_id is None):
            raise ValueError(
                "record_quality() takes exactly one of call_id= or trace_id= —"
                " a rating names the call it rates",
            )
        row = await self._resolve_rated(call_id=call_id, trace_id=trace_id)
        await self._store.record_quality(row.id, score, scope=self.scope)
        if self._learner is not None:
            self._learner.record_quality_observed(row.llm_name, row.operation, row.id, score)

    async def _resolve_rated(self, *, call_id: str | None, trace_id: str | None) -> Call:
        """The one call a rating key names: the newest that answered, inside the window.
        A trace naming more rows than one call's attempts is the host reusing it, which
        is reported — the rating still lands on exactly one call."""
        key = f"call_id={call_id!r}" if call_id is not None else f"trace_id={trace_id!r}"
        # A call id names one row; a trace names one call's attempts.
        limit = 1 if call_id is not None else _RATING_PAGE
        rows = await self.calls(
            limit=limit,
            since=datetime.now(UTC) - _RATING_WINDOW,
            call_id=call_id,
            trace_id=trace_id,
        )
        if trace_id is not None and len(rows) == _RATING_PAGE:
            logger.warning(
                "record_quality: %s matched the %d-row read bound — a trace names one"
                " call, so the rating lands on the newest that answered under it",
                key,
                _RATING_PAGE,
            )
        answered = next((row for row in rows if row.status is CallStatus.OK), None)
        if answered is None:
            raise UnknownCallError(
                f"no answered call found for {key} within the last"
                f" {_RATING_WINDOW.days} days — it may be older than that, purged by"
                " retention, or every attempt under it failed",
            )
        return answered

    # ------------------------------------------------------------------
    # Call journal — the rows this caller's scope is on
    # ------------------------------------------------------------------

    async def calls(
        self,
        *,
        limit: int,
        since: datetime | None = None,
        operation: str | None = None,
        trace_id: str | None = None,
        call_id: str | None = None,
    ) -> list[Call]:
        """Newest-first journal tail for this caller: one row per call attempt, each
        carrying the newest score it was rated with. The unscoped caller sees all.
        Never provisions the pool."""
        check_limit(limit)
        return await self._require_queryable().calls(
            limit=limit,
            scope=self.scope,
            since=to_utc(since, "since") if since is not None else None,
            operation=operation,
            trace_id=trace_id,
            call_id=call_id,
        )

    async def stats(
        self,
        *,
        since: datetime | None = None,
        limit: int = _DEFAULT_STATS_LIMIT,
        operation: str | None = None,
    ) -> Mapping[str, LLMStats]:
        """Per-model counts of this caller's call records over a window, keyed by model
        name. ``limit`` caps rows read, not the window: totals summing to it mean the
        window may be truncated. Never provisions the pool."""
        rows = await self.calls(limit=limit, since=since, operation=operation)
        return stats_from_calls(rows)

    def _require_queryable(self) -> QueryableStoreProtocol:
        if not isinstance(self._store, QueryableStoreProtocol):
            raise TypeError(
                "this store backend is not queryable — use a queryable backend"
                " (e.g. llmbroker.sqlite.Store) for calls()",
            )
        return self._store

scope property

Whose calls these are — the attribution on this caller's journal rows.

calls(*, limit, since=None, operation=None, trace_id=None, call_id=None) async

Newest-first journal tail for this caller: one row per call attempt, each carrying the newest score it was rated with. The unscoped caller sees all. Never provisions the pool.

Source code in src/llmbroker/broker/llms.py
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
async def calls(
    self,
    *,
    limit: int,
    since: datetime | None = None,
    operation: str | None = None,
    trace_id: str | None = None,
    call_id: str | None = None,
) -> list[Call]:
    """Newest-first journal tail for this caller: one row per call attempt, each
    carrying the newest score it was rated with. The unscoped caller sees all.
    Never provisions the pool."""
    check_limit(limit)
    return await self._require_queryable().calls(
        limit=limit,
        scope=self.scope,
        since=to_utc(since, "since") if since is not None else None,
        operation=operation,
        trace_id=trace_id,
        call_id=call_id,
    )

direct(alias=None, *, name=None) async

A client for exactly one model of your own — no pool, no failover.

Takes exactly one of alias or name=; raises PoolModelError, UnknownModelError or MissingKeyError. See docs/ "Direct model calls".

Source code in src/llmbroker/broker/llms.py
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
async def direct(
    self,
    alias: str | None = None,
    *,
    name: str | None = None,
) -> AsyncDirectClient:
    """A client for exactly one model of your own — no pool, no failover.

    Takes exactly one of ``alias`` or ``name=``; raises ``PoolModelError``,
    ``UnknownModelError`` or ``MissingKeyError``. See ``docs/`` "Direct model calls".
    """
    cfg, key = await self.resolve_direct(alias, name=name)
    return AsyncDirectClient(
        base_url=cfg.base_url,
        model=cfg.model,
        api_key=key,
        client=self._http(),
    )

record_quality(score, *, call_id=None, trace_id=None) async

Rate a past call, by exactly one key — one rating, on the newest answered call the key names within the rating window. Raises UnknownCallError when nothing there answered. See docs/ "Quality rating".

Source code in src/llmbroker/broker/llms.py
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
async def record_quality(
    self,
    score: float,
    *,
    call_id: str | None = None,
    trace_id: str | None = None,
) -> None:
    """Rate a past call, by exactly one key — one rating, on the newest answered
    call the key names within the rating window. Raises ``UnknownCallError`` when
    nothing there answered. See ``docs/`` "Quality rating"."""
    check_score(score)
    if (call_id is None) == (trace_id is None):
        raise ValueError(
            "record_quality() takes exactly one of call_id= or trace_id= —"
            " a rating names the call it rates",
        )
    row = await self._resolve_rated(call_id=call_id, trace_id=trace_id)
    await self._store.record_quality(row.id, score, scope=self.scope)
    if self._learner is not None:
        self._learner.record_quality_observed(row.llm_name, row.operation, row.id, score)

resolve_direct(alias=None, *, name=None) async

Look the entry up in its keyspace and resolve it against this caller's ring (shared by the synchronous façade).

Source code in src/llmbroker/broker/llms.py
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
async def resolve_direct(
    self,
    alias: str | None = None,
    *,
    name: str | None = None,
) -> tuple[LLMConfig, str]:
    """Look the entry up in its keyspace and resolve it against this caller's ring
    (shared by the synchronous façade)."""
    if (alias is None) == (name is None):
        raise ValueError(
            "direct() takes exactly one of alias (positional) or name= —"
            " they are separate keyspaces",
        )
    stored, declared = await self._catalog.entries()
    cfg = find_declared(stored, declared, alias, name)
    ref = alias if alias is not None else name
    key = await self._ring.resolve(cfg.api_key_ref)
    if key is None:
        hint = self._catalog.key_help(cfg.api_key_ref)
        raise MissingKeyError(
            f"api_key_ref {cfg.api_key_ref!r} for model {ref!r} could not be resolved"
            " — set the env var or configure a secrets backend" + (f". {hint}" if hint else ""),
        )
    return cfg, key

stats(*, since=None, limit=_DEFAULT_STATS_LIMIT, operation=None) async

Per-model counts of this caller's call records over a window, keyed by model name. limit caps rows read, not the window: totals summing to it mean the window may be truncated. Never provisions the pool.

Source code in src/llmbroker/broker/llms.py
371
372
373
374
375
376
377
378
379
380
381
382
async def stats(
    self,
    *,
    since: datetime | None = None,
    limit: int = _DEFAULT_STATS_LIMIT,
    operation: str | None = None,
) -> Mapping[str, LLMStats]:
    """Per-model counts of this caller's call records over a window, keyed by model
    name. ``limit`` caps rows read, not the window: totals summing to it mean the
    window may be truncated. Never provisions the pool."""
    rows = await self.calls(limit=limit, since=since, operation=operation)
    return stats_from_calls(rows)

stream(prompt, *, operation=None, trace_id=None, wait=None, fastest_of=None, parallel_recovery=True, response_format=None)

Route a completion over the pool as a handle yielding text deltas and naming what answered them. wait bounds the whole answer in provider time; past the first delta a death raises StreamInterruptedError. Async-only.

Source code in src/llmbroker/broker/llms.py
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
def stream(  # noqa: PLR0913 - the call knobs, one keyword each
    self,
    prompt: str,
    *,
    operation: str | None = None,
    trace_id: str | None = None,
    wait: float | None = None,
    fastest_of: int | None = None,
    parallel_recovery: bool = True,
    response_format: dict | None = None,
) -> StreamHandle:
    """Route a completion over the pool as a handle yielding text deltas and naming
    what answered them. ``wait`` bounds the whole answer in provider time; past the
    first delta a death raises ``StreamInterruptedError``. Async-only."""
    receipt = CallReceipt()
    return StreamHandle(
        self._deltas(
            receipt,
            prompt,
            operation=operation,
            trace_id=trace_id,
            wait=wait,
            fastest_of=fastest_of,
            parallel_recovery=parallel_recovery,
            response_format=response_format,
        ),
        receipt,
        operation=operation,
        store=self._store,
        scope=self.scope,
        observe_quality=(
            self._learner.record_quality_observed if self._learner is not None else None
        ),
    )

AsyncResult

Bases: RoutedCall

Returned by AsyncBroker.ask()/chat() — a call that has already answered.

Source code in src/llmbroker/broker/result.py
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
class AsyncResult(RoutedCall):
    """Returned by AsyncBroker.ask()/chat() — a call that has already answered."""

    def __init__(  # noqa: PLR0913
        self,
        *,
        text: str,
        tool_calls: list[dict] | None,
        usage: Usage | None,
        call_id: str,
        llm_name: str,
        operation: str | None = None,
        store: StoreProtocol,
        scope: str | None = None,
        observe_quality: ObserveQuality | None = None,
    ) -> None:
        super().__init__(
            CallReceipt(llm_name=llm_name, call_id=call_id, usage=usage, settled=True),
            operation=operation,
            store=store,
            scope=scope,
            observe_quality=observe_quality,
        )
        self.text = text
        self.tool_calls = tool_calls

    @property
    def llm_name(self) -> str:
        """Name of the model that answered — persist it to rate the call later."""
        return cast(str, self._receipt.llm_name)

    @property
    def call_id(self) -> str:
        """Opaque id of this call; an optional passthrough for host analytics."""
        return cast(str, self._receipt.call_id)

call_id property

Opaque id of this call; an optional passthrough for host analytics.

llm_name property

Name of the model that answered — persist it to rate the call later.

AuthError

Bases: ProviderError

The key was missing, malformed, or rejected (HTTP 401/403).

Source code in src/llmbroker/exceptions.py
130
131
class AuthError(ProviderError):
    """The key was missing, malformed, or rejected (HTTP 401/403)."""

Broker

Synchronous client over an AsyncBroker on a background loop thread.

Source code in src/llmbroker/sync.py
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
class Broker:
    """Synchronous client over an AsyncBroker on a background loop thread."""

    def __init__(  # noqa: PLR0913
        self,
        registry: RegistryProtocol | str | Path | None = None,
        *,
        secrets: SecretsProtocol | None = None,
        store: StoreProtocol | None = None,
        optimize: bool | Optimizer = True,
        sync: str | None | _SyncDefault = _SYNC_DEFAULT,
        sync_interval: float | None = _DEFAULT_SYNC_INTERVAL,
        home: str | Path | None = None,
        direct: Sequence[str | LLMConfig] = (),
    ) -> None:
        self._async = AsyncBroker(
            registry,
            secrets=secrets,
            store=store,
            optimize=optimize,
            sync=sync,
            sync_interval=sync_interval,
            home=home,
            direct=direct,
        )
        self._loop = asyncio.new_event_loop()
        self._thread = threading.Thread(
            target=_run_loop,
            args=(self._loop,),
            daemon=True,
            name="llmbroker-loop",
        )
        self._thread.start()
        # Backstop for a Broker nobody closes. The callback holds only loop + thread,
        # never self, so it does not pin the instance it is registered on.
        self._finalizer = weakref.finalize(self, _shutdown, self._loop, self._thread)
        self.llms = LLMs(self._run, self._async.llms)

    def _run(self, coro: Coroutine[Any, Any, Any]) -> Any:
        return asyncio.run_coroutine_threadsafe(coro, self._loop).result()

    def for_scope(self, scope: str) -> "LLMs":
        """A caller that pays with ``scope``\'s own keys and writes ``scope`` on every
        row it journals. Costs no I/O."""
        return LLMs(self._run, self._async.for_scope(scope))

    def _ensure_pool(self) -> None:
        self._run(self._async.ensure_pool())

    # ── The unscoped caller, delegated ──
    def get(self, name: str) -> LLM:
        return self.llms.get(name)

    def count(self) -> int:
        return self.llms.count()

    def ask(  # noqa: PLR0913 - the call knobs, one keyword each
        self,
        prompt: str,
        *,
        operation: str | None = None,
        trace_id: str | None = None,
        wait: float | None = None,
        fastest_of: int | None = None,
        parallel_recovery: bool = True,
        response_format: dict | None = None,
    ) -> Result:
        return self.llms.ask(
            prompt,
            operation=operation,
            trace_id=trace_id,
            wait=wait,
            fastest_of=fastest_of,
            parallel_recovery=parallel_recovery,
            response_format=response_format,
        )

    def chat(  # noqa: PLR0913 - what to send, and the call knobs
        self,
        messages: list[dict],
        *,
        tools: list[dict] | None = None,
        operation: str | None = None,
        trace_id: str | None = None,
        wait: float | None = None,
        fastest_of: int | None = None,
        parallel_recovery: bool = True,
        response_format: dict | None = None,
    ) -> Result:
        return self.llms.chat(
            messages,
            tools=tools,
            operation=operation,
            trace_id=trace_id,
            wait=wait,
            fastest_of=fastest_of,
            parallel_recovery=parallel_recovery,
            response_format=response_format,
        )

    def direct(self, alias: str | None = None, *, name: str | None = None) -> DirectClient:
        return self.llms.direct(alias, name=name)

    def record_quality(
        self,
        score: float,
        *,
        call_id: str | None = None,
        trace_id: str | None = None,
    ) -> None:
        self.llms.record_quality(score, call_id=call_id, trace_id=trace_id)

    def snapshot(self) -> PoolSnapshot:
        return self._run(self._async.snapshot())

    def sync(self, source: str | None = None) -> SyncReport | None:
        return self._run(self._async.sync(source))

    @property
    def last_sync_report(self) -> SyncReport | None:
        return self._async.last_sync_report

    def disable_llm(self, name: str) -> None:
        self._run(self._async.disable_llm(name))

    def enable_llm(self, name: str) -> None:
        self._run(self._async.enable_llm(name))

    def calls(
        self,
        *,
        limit: int,
        since: datetime | None = None,
        operation: str | None = None,
        trace_id: str | None = None,
        call_id: str | None = None,
    ) -> list[Call]:
        return self.llms.calls(
            limit=limit,
            since=since,
            operation=operation,
            trace_id=trace_id,
            call_id=call_id,
        )

    def stats(
        self,
        *,
        since: datetime | None = None,
        limit: int = _DEFAULT_STATS_LIMIT,
        operation: str | None = None,
    ) -> Mapping[str, LLMStats]:
        return self.llms.stats(since=since, limit=limit, operation=operation)

    # ── lifecycle ──
    def close(self) -> None:
        if not self._finalizer.alive:
            return
        self._run(self._async.aclose())
        # Run the same teardown the GC backstop would, and mark it done so the
        # finalizer does not repeat it later.
        self._finalizer()

    def __enter__(self) -> "Broker":
        self._ensure_pool()
        return self

    def __exit__(self, *exc: object) -> None:
        self.close()

for_scope(scope)

A caller that pays with scope's own keys and writes scope on every row it journals. Costs no I/O.

Source code in src/llmbroker/sync.py
258
259
260
261
def for_scope(self, scope: str) -> "LLMs":
    """A caller that pays with ``scope``\'s own keys and writes ``scope`` on every
    row it journals. Costs no I/O."""
    return LLMs(self._run, self._async.for_scope(scope))

Call dataclass

One call attempt, as the journal holds it. score is the newest host rating of this attempt and is filled on reads only — a rating is its own appended row, never a field the record was written with.

Source code in src/llmbroker/models.py
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
@dataclass(frozen=True, slots=True)
class Call:
    """One call attempt, as the journal holds it. ``score`` is the newest host rating
    of this attempt and is filled on reads only — a rating is its own appended row,
    never a field the record was written with."""

    id: str
    llm_name: str
    operation: str | None
    trace_id: str | None
    status: CallStatus | None
    ts: datetime | None = None
    http_status: int | None = None
    latency_ms: int | None = None
    error_detail: str | None = None
    usage: Usage | None = None
    score: float | None = None
    scope: str | None = None
    cooldown_until: datetime | None = None
    # Set only where the caller's own budget ran out mid-attempt: the bound the
    # model failed to answer within, which is the only latency evidence it left.
    budget_ms: int | None = None

CallStatus

Bases: Enum

How one attempt ended. SUPERSEDED is neutral: a sibling answered first, so it proves neither success nor failure and feeds no routing signal.

Source code in src/llmbroker/models.py
184
185
186
187
188
189
190
191
192
class CallStatus(Enum):
    """How one attempt ended. ``SUPERSEDED`` is neutral: a sibling answered first, so
    it proves neither success nor failure and feeds no routing signal."""

    OK = "ok"
    RATE_LIMITED = "rate_limited"
    UNAVAILABLE = "unavailable"
    ERROR = "error"
    SUPERSEDED = "superseded"

CuratedModel dataclass

One row of the curated paid catalog: a provider's model, with the alias that keeps it current where the catalog carries one.

Source code in src/llmbroker/broker/curated.py
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
@dataclass(frozen=True, slots=True)
class CuratedModel:
    """One row of the curated paid catalog: a provider's model, with the alias that
    keeps it current where the catalog carries one."""

    provider: CuratedProvider
    model: str
    alias: str | None = None
    label: str = ""

    @property
    def name(self) -> str:
        return f"{self.provider.id}-{self.model}"

    def declare(self) -> LLMConfig:
        """A declaration pinned to this row's model id. The alias rides along as the
        catalog line it came from, and pins no less for being there — what follows an
        alias is a bare string passed to ``direct=``."""
        return LLMConfig(
            name=self.name,
            base_url=self.provider.base_url,
            model=self.model,
            api_key_ref=self.provider.api_key_ref,
            alias=self.alias,
        )

declare()

A declaration pinned to this row's model id. The alias rides along as the catalog line it came from, and pins no less for being there — what follows an alias is a bare string passed to direct=.

Source code in src/llmbroker/broker/curated.py
53
54
55
56
57
58
59
60
61
62
63
def declare(self) -> LLMConfig:
    """A declaration pinned to this row's model id. The alias rides along as the
    catalog line it came from, and pins no less for being there — what follows an
    alias is a bare string passed to ``direct=``."""
    return LLMConfig(
        name=self.name,
        base_url=self.provider.base_url,
        model=self.model,
        api_key_ref=self.provider.api_key_ref,
        alias=self.alias,
    )

CuratedProvider dataclass

One paid provider of the curated catalog: where its endpoint is and which key reference pays for it.

Source code in src/llmbroker/broker/curated.py
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
@dataclass(frozen=True, slots=True)
class CuratedProvider:
    """One paid provider of the curated catalog: where its endpoint is and which key
    reference pays for it."""

    id: str
    base_url: str
    api_key_ref: str
    key_help: str = ""
    label: str = ""

    def declare(self, model: str) -> LLMConfig:
        """A declaration for any model id this provider serves, curated or not — the
        config ``direct=`` takes, pinned to that id and following no alias."""
        return LLMConfig(
            name=f"{self.id}-{model}",
            base_url=self.base_url,
            model=model,
            api_key_ref=self.api_key_ref,
        )

declare(model)

A declaration for any model id this provider serves, curated or not — the config direct= takes, pinned to that id and following no alias.

Source code in src/llmbroker/broker/curated.py
28
29
30
31
32
33
34
35
36
def declare(self, model: str) -> LLMConfig:
    """A declaration for any model id this provider serves, curated or not — the
    config ``direct=`` takes, pinned to that id and following no alias."""
    return LLMConfig(
        name=f"{self.id}-{model}",
        base_url=self.base_url,
        model=model,
        api_key_ref=self.api_key_ref,
    )

DictSecrets

Read-only secrets resolver backed by an in-memory mapping (tests / preloaded keys).

Source code in src/llmbroker/standalone/secrets.py
85
86
87
88
89
90
91
92
93
94
class DictSecrets:
    """Read-only secrets resolver backed by an in-memory mapping (tests / preloaded keys)."""

    def __init__(self, mapping: dict[str, str]) -> None:
        self._mapping = dict(mapping)

    async def resolve(self, ref: str) -> str:
        if ref not in self._mapping:
            raise KeyError(f"DictSecrets: ref {ref!r} not found")
        return self._mapping[ref]

DirectClient

Synchronous direct client for one named model — ask() only, since it is a single POST and needs no event loop. Pass an httpx.Client to share a connection pool, or let it open and close its own.

Source code in src/llmbroker/direct.py
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
class DirectClient:
    """Synchronous direct client for one named model — ``ask()`` only, since it is a
    single ``POST`` and needs no event loop. Pass an ``httpx.Client`` to share a
    connection pool, or let it open and close its own."""

    def __init__(
        self,
        *,
        base_url: str,
        model: str,
        api_key: str,
        timeout: float = _DEFAULT_TIMEOUT,
        client: httpx.Client | None = None,
    ) -> None:
        self._base_url = base_url
        self._model = model
        self._api_key = api_key
        self._timeout = timeout
        self._http = client
        self._owns_http = client is None

    def _ensure_http(self) -> httpx.Client:
        if self._http is None:
            self._http = httpx.Client(timeout=self._timeout)
        return self._http

    def ask(
        self,
        prompt: str | None = None,
        *,
        messages: list[dict] | None = None,
        timeout: float | None = None,
        params: Mapping[str, object] | None = None,
    ) -> DirectResult:
        url, headers, body = build_chat_request(
            self._base_url,
            self._model,
            self._api_key,
            _messages(prompt, messages),
            params=params,
        )
        try:
            resp = self._ensure_http().post(
                url,
                headers=headers,
                json=body,
                timeout=timeout or self._timeout,
            )
        except httpx.TimeoutException as exc:
            raise LLMTimeoutError("direct call timed out") from exc
        return _result(resp, self._model)

    def close(self) -> None:
        if self._owns_http and self._http is not None:
            self._http.close()
            self._http = None

    def __enter__(self) -> "DirectClient":
        return self

    def __exit__(self, *exc: object) -> None:
        self.close()

DirectResult dataclass

The full, non-streaming reply from a direct call.

Source code in src/llmbroker/direct.py
26
27
28
29
30
31
@dataclass(frozen=True, slots=True)
class DirectResult:
    """The full, non-streaming reply from a direct call."""

    text: str
    usage: Usage | None = None

EmptyRegistryError

Bases: LLMBrokerError

The registry holds no configs — nothing has been synced into it yet.

The request-time sibling is NoLLMAvailableError(reason="empty_pool"): this one says nothing is configured, that one says nothing is usable now.

Source code in src/llmbroker/exceptions.py
16
17
18
19
20
21
class EmptyRegistryError(LLMBrokerError):
    """The registry holds no configs — nothing has been synced into it yet.

    The request-time sibling is ``NoLLMAvailableError(reason="empty_pool")``:
    this one says nothing is configured, that one says nothing is usable now.
    """

FileStore

Day-split JSONL call journal plus a YAML disabled-verdict map, under one directory.

Source code in src/llmbroker/standalone/store.py
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
class FileStore:
    """Day-split JSONL call journal plus a YAML disabled-verdict map, under one directory."""

    def __init__(self, directory: str | Path, *, retention: timedelta = RETENTION_DEFAULT) -> None:
        self._dir = Path(directory)
        self._calls_dir = self._dir / "calls"
        self._disabled_path = self._dir / "disabled.yml"
        self._retention = retention
        self._purge_clock = PurgeClock()

    def _day_path(self, ts: datetime) -> Path:
        """UTC date, not the record's own offset: the ``since`` bound skips whole
        files by name, so a file must never hold a row outside its named UTC day."""
        return self._calls_dir / f"{ts.astimezone(UTC).date().isoformat()}.jsonl"

    def _append_line(self, ts: datetime, payload: dict) -> None:
        path = self._day_path(ts)
        path.parent.mkdir(parents=True, exist_ok=True)
        line = json.dumps(payload)
        with path.open("a", encoding="utf-8") as fh:
            fh.write(line + "\n")

    async def record(self, call: Call) -> None:
        stamped = with_utc_timestamps(call)
        await asyncio.to_thread(self._append_line, stamped.ts, _call_to_jsonable(stamped))
        await self._maybe_purge()

    async def record_quality(
        self,
        call_id: str,
        score: float,
        *,
        scope: str | None = None,
    ) -> None:
        row = quality_row(call_id, score, scope)
        called_at: datetime = row["called_at"]  # type: ignore[assignment]
        payload = {k: v for k, v in row.items() if k != "called_at" and v is not None}
        payload["ts"] = called_at.isoformat()
        await asyncio.to_thread(self._append_line, called_at, payload)
        await self._maybe_purge()

    def _day_files_newest_first(self) -> list[Path]:
        if not self._calls_dir.exists():
            return []
        return sorted(self._calls_dir.glob("*.jsonl"), reverse=True)

    def _read_tail(
        self,
        limit: int,
        match: dict[str, object],
        since: datetime | None,
    ) -> list[Call]:
        """``match`` maps a ``Call`` attribute name to the value it must equal — the
        file counterpart of the driver stores' column match. One reverse pass: a rating
        is newer than the call it names, so it is always met before that call."""
        result: list[Call] = []
        pending: dict[str, float] = {}
        for path in self._day_files_newest_first():
            if since is not None and self._file_is_wholly_before(path, since):
                continue
            lines = path.read_text(encoding="utf-8").splitlines()
            for raw_line in reversed(lines):
                stripped = raw_line.strip()
                if not stripped:
                    continue
                raw = json.loads(stripped)
                if raw.get("kind") == KIND_QUALITY:
                    rated, value = raw.get("call_id"), raw.get("quality_score")
                    if rated is not None and value is not None:
                        pending.setdefault(rated, value)
                    continue
                call = _call_from_jsonable(raw)
                if any(getattr(call, attr) != want for attr, want in match.items()):
                    continue
                if since is not None and (call.ts is None or call.ts < since):
                    continue
                result.append(replace(call, score=pending.pop(call.id, None)))
                if len(result) >= limit:
                    return result
        return result

    @staticmethod
    def _file_is_wholly_before(path: Path, since: datetime) -> bool:
        """A day file's newest possible record is the last instant of its UTC date, so
        a file whose whole day precedes ``since`` is skipped without being read."""
        try:
            file_date = date.fromisoformat(path.stem)
        except ValueError:
            return False
        return file_date < since.date()

    async def calls(  # noqa: PLR0913 - one narrowing dimension per parameter
        self,
        *,
        limit: int,
        scope: str | None = None,
        since: datetime | None = None,
        operation: str | None = None,
        trace_id: str | None = None,
        call_id: str | None = None,
    ) -> list[Call]:
        """Newest-first tail of the journal, one row per call attempt carrying the
        newest score it was rated with. ``since`` must be timezone-aware and bounds the
        timestamp inclusively; the filters narrow the calls, never the ratings."""
        check_limit(limit)
        bound = to_utc(since, "since") if since is not None else None
        wanted = {
            "scope": scope,
            "operation": operation,
            "trace_id": trace_id,
            "id": call_id,
        }
        match = {attr: want for attr, want in wanted.items() if want is not None}
        return await asyncio.to_thread(self._read_tail, limit, match, bound)

    def _purge_old_day_files(self) -> None:
        cutoff = (datetime.now(UTC) - self._retention).date()
        for path in self._day_files_newest_first():
            try:
                file_date = date.fromisoformat(path.stem)
            except ValueError:
                continue
            if file_date < cutoff:
                path.unlink(missing_ok=True)

    async def _maybe_purge(self) -> None:
        if not self._purge_clock.due():
            return
        await asyncio.to_thread(self._purge_old_day_files)

    def _read_disabled(self) -> dict[str, bool]:
        if not self._disabled_path.exists():
            return {}
        data = yaml.safe_load(self._disabled_path.read_text(encoding="utf-8"))
        return dict(data) if data else {}

    def _write_disabled(self, data: dict[str, bool]) -> None:
        self._disabled_path.parent.mkdir(parents=True, exist_ok=True)
        body = yaml.safe_dump(data, sort_keys=True)
        self._disabled_path.write_text(_DISABLED_HEADER + body, encoding="utf-8")

    async def get_disabled(self, name: str) -> bool:
        data = await asyncio.to_thread(self._read_disabled)
        return bool(data.get(name, False))

    async def set_disabled(self, name: str, flag: bool) -> None:  # noqa: FBT001
        data = await asyncio.to_thread(self._read_disabled)
        data[name] = flag
        await asyncio.to_thread(self._write_disabled, data)

    async def seed_disabled(self, names: list[str]) -> None:
        data = await asyncio.to_thread(self._read_disabled)
        changed = False
        for name in names:
            if name not in data:
                data[name] = False
                changed = True
        if changed:
            await asyncio.to_thread(self._write_disabled, data)

    async def disabled_map(self) -> dict[str, bool]:
        return await asyncio.to_thread(self._read_disabled)

calls(*, limit, scope=None, since=None, operation=None, trace_id=None, call_id=None) async

Newest-first tail of the journal, one row per call attempt carrying the newest score it was rated with. since must be timezone-aware and bounds the timestamp inclusively; the filters narrow the calls, never the ratings.

Source code in src/llmbroker/standalone/store.py
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
async def calls(  # noqa: PLR0913 - one narrowing dimension per parameter
    self,
    *,
    limit: int,
    scope: str | None = None,
    since: datetime | None = None,
    operation: str | None = None,
    trace_id: str | None = None,
    call_id: str | None = None,
) -> list[Call]:
    """Newest-first tail of the journal, one row per call attempt carrying the
    newest score it was rated with. ``since`` must be timezone-aware and bounds the
    timestamp inclusively; the filters narrow the calls, never the ratings."""
    check_limit(limit)
    bound = to_utc(since, "since") if since is not None else None
    wanted = {
        "scope": scope,
        "operation": operation,
        "trace_id": trace_id,
        "id": call_id,
    }
    match = {attr: want for attr, want in wanted.items() if want is not None}
    return await asyncio.to_thread(self._read_tail, limit, match, bound)

InMemoryStore

Explicit in-memory opt-out — no persistence, session-scoped learning; disabled verdicts live only in process memory.

Source code in src/llmbroker/standalone/store.py
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
class InMemoryStore:
    """Explicit in-memory opt-out — no persistence, session-scoped learning;
    disabled verdicts live only in process memory."""

    def __init__(self) -> None:
        self._disabled: dict[str, bool] = {}

    async def record(self, _call: Call) -> None:
        return

    async def record_quality(
        self,
        _call_id: str,
        _score: float,
        *,
        scope: str | None = None,  # noqa: ARG002
    ) -> None:
        return

    async def get_disabled(self, name: str) -> bool:
        return self._disabled.get(name, False)

    async def set_disabled(self, name: str, flag: bool) -> None:  # noqa: FBT001
        self._disabled[name] = flag

    async def seed_disabled(self, names: list[str]) -> None:
        for name in names:
            self._disabled.setdefault(name, False)

    async def disabled_map(self) -> dict[str, bool]:
        return dict(self._disabled)

InvalidProviderResponseError

Bases: LLMRequestError

The provider answered 200 with a body that is not a chat completion, or with one carrying no text and no tool calls — a provider-side failure like a 5xx, so the router cools the model and fails over.

Source code in src/llmbroker/exceptions.py
109
110
111
112
113
114
115
116
117
class InvalidProviderResponseError(LLMRequestError):
    """The provider answered 200 with a body that is not a chat completion, or with one
    carrying no text and no tool calls — a provider-side failure like a 5xx, so the
    router cools the model and fails over."""

    def __init__(self, message: str, *, model: str, detail: str | None = None) -> None:
        super().__init__(message)
        self.model = model
        self.detail = detail

KeyInfo dataclass

Per-provider onboarding metadata for one api_key_ref: a help blurb plus a free-form passthrough of whatever else the TOML [keys.REF] section holds — llmbroker has no taxonomy opinion on it.

Source code in src/llmbroker/models.py
40
41
42
43
44
45
46
47
48
@dataclass(frozen=True, slots=True)
class KeyInfo:
    """Per-provider onboarding metadata for one ``api_key_ref``: a help blurb plus
    a free-form passthrough of whatever else the TOML ``[keys.REF]`` section holds —
    llmbroker has no taxonomy opinion on it."""

    api_key_ref: str
    help: str
    extra: dict[str, str]

LLM

Synchronous analogue of AsyncLLM.

Source code in src/llmbroker/sync.py
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
class LLM:
    """Synchronous analogue of AsyncLLM."""

    def __init__(self, run_fn: "Callable[[Any], Any]", async_llm: AsyncLLM) -> None:
        self._run = run_fn
        self._async = async_llm

    @property
    def config(self) -> LLMConfig:
        return self._async.config

    @property
    def disabled(self) -> bool:
        return self._async.disabled

    def state(self) -> LLMState:
        return self._run(self._async.state())

    def metrics(self) -> LLMMetrics:
        return self._run(self._async.metrics())

LLMBrokerError

Bases: RuntimeError

Base: a lifecycle failure — provisioning or storage, not one request.

Subclasses RuntimeError so a host that already catches RuntimeError around provisioning keeps working.

Source code in src/llmbroker/exceptions.py
 8
 9
10
11
12
13
class LLMBrokerError(RuntimeError):
    """Base: a lifecycle failure — provisioning or storage, not one request.

    Subclasses ``RuntimeError`` so a host that already catches ``RuntimeError``
    around provisioning keeps working.
    """

LLMConfig dataclass

Pure config for one LLM — no secret, safe to expose. from_preset says our curated preset supplied these parameters, and so is what a sync may replace.

Source code in src/llmbroker/models.py
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
@dataclass(frozen=True, slots=True)
class LLMConfig:
    """Pure config for one LLM — no secret, safe to expose. ``from_preset`` says our
    curated preset supplied these parameters, and so is what a sync may replace."""

    name: str
    base_url: str
    model: str
    api_key_ref: str
    parallel: int | None = None
    from_preset: bool = False
    alias: str | None = None
    weight: float = 0.0

    def to_metadata(self) -> dict[str, object]:
        """Structured optional config, serialized for the registry's JSON column.

        Only non-default values are stored, so a plain pool config stays empty.

        >>> LLMConfig(name="g", base_url="https://x/v1", model="m", api_key_ref="K").to_metadata()
        {}
        >>> followed = LLMConfig(name="g", base_url="u", model="m", api_key_ref="K", alias="opus")
        >>> followed.to_metadata()
        {'alias': 'opus'}
        >>> curated = LLMConfig(name="g", base_url="u", model="m", api_key_ref="K")
        >>> replace(curated, from_preset=True).to_metadata()
        {'from_preset': True}
        >>> LLMConfig(name="g", base_url="u", model="m", api_key_ref="K", weight=0.7).to_metadata()
        {'weight': 0.7}
        """
        metadata: dict[str, object] = {}
        if self.parallel is not None:
            metadata["parallel"] = self.parallel
        if self.from_preset:
            metadata["from_preset"] = True
        if self.alias is not None:
            metadata["alias"] = self.alias
        if self.weight:
            metadata["weight"] = self.weight
        return metadata

    @classmethod
    def from_metadata(
        cls,
        *,
        name: str,
        base_url: str,
        model: str,
        api_key_ref: str,
        metadata: dict[str, object] | None,
    ) -> "LLMConfig":
        """Reconstruct from the core columns plus the JSON ``metadata`` blob."""
        metadata = metadata or {}
        raw_parallel = metadata.get("parallel")
        parallel = raw_parallel if isinstance(raw_parallel, int) else None
        raw_from_preset = metadata.get("from_preset")
        from_preset = raw_from_preset if isinstance(raw_from_preset, bool) else False
        raw_alias = metadata.get("alias")
        alias = raw_alias if isinstance(raw_alias, str) else None
        return cls(
            name=name,
            base_url=base_url,
            model=model,
            api_key_ref=api_key_ref,
            parallel=parallel,
            from_preset=from_preset,
            alias=alias,
            weight=_weight_from_metadata(metadata.get("weight"), name),
        )

from_metadata(*, name, base_url, model, api_key_ref, metadata) classmethod

Reconstruct from the core columns plus the JSON metadata blob.

Source code in src/llmbroker/models.py
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
@classmethod
def from_metadata(
    cls,
    *,
    name: str,
    base_url: str,
    model: str,
    api_key_ref: str,
    metadata: dict[str, object] | None,
) -> "LLMConfig":
    """Reconstruct from the core columns plus the JSON ``metadata`` blob."""
    metadata = metadata or {}
    raw_parallel = metadata.get("parallel")
    parallel = raw_parallel if isinstance(raw_parallel, int) else None
    raw_from_preset = metadata.get("from_preset")
    from_preset = raw_from_preset if isinstance(raw_from_preset, bool) else False
    raw_alias = metadata.get("alias")
    alias = raw_alias if isinstance(raw_alias, str) else None
    return cls(
        name=name,
        base_url=base_url,
        model=model,
        api_key_ref=api_key_ref,
        parallel=parallel,
        from_preset=from_preset,
        alias=alias,
        weight=_weight_from_metadata(metadata.get("weight"), name),
    )

to_metadata()

Structured optional config, serialized for the registry's JSON column.

Only non-default values are stored, so a plain pool config stays empty.

LLMConfig(name="g", base_url="https://x/v1", model="m", api_key_ref="K").to_metadata() {} followed = LLMConfig(name="g", base_url="u", model="m", api_key_ref="K", alias="opus") followed.to_metadata() {'alias': 'opus'} curated = LLMConfig(name="g", base_url="u", model="m", api_key_ref="K") replace(curated, from_preset=True).to_metadata() {'from_preset': True} LLMConfig(name="g", base_url="u", model="m", api_key_ref="K", weight=0.7).to_metadata()

Source code in src/llmbroker/models.py
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
def to_metadata(self) -> dict[str, object]:
    """Structured optional config, serialized for the registry's JSON column.

    Only non-default values are stored, so a plain pool config stays empty.

    >>> LLMConfig(name="g", base_url="https://x/v1", model="m", api_key_ref="K").to_metadata()
    {}
    >>> followed = LLMConfig(name="g", base_url="u", model="m", api_key_ref="K", alias="opus")
    >>> followed.to_metadata()
    {'alias': 'opus'}
    >>> curated = LLMConfig(name="g", base_url="u", model="m", api_key_ref="K")
    >>> replace(curated, from_preset=True).to_metadata()
    {'from_preset': True}
    >>> LLMConfig(name="g", base_url="u", model="m", api_key_ref="K", weight=0.7).to_metadata()
    {'weight': 0.7}
    """
    metadata: dict[str, object] = {}
    if self.parallel is not None:
        metadata["parallel"] = self.parallel
    if self.from_preset:
        metadata["from_preset"] = True
    if self.alias is not None:
        metadata["alias"] = self.alias
    if self.weight:
        metadata["weight"] = self.weight
    return metadata

LLMMetrics dataclass

Per-LLM admin read-model derived from Call rows.

Source code in src/llmbroker/models.py
293
294
295
296
297
298
299
@dataclass(frozen=True, slots=True)
class LLMMetrics:
    """Per-LLM admin read-model derived from Call rows."""

    call_count: int
    last_status: CallStatus | None
    last_at: datetime | None

LLMRequestError

Bases: Exception

Base: this request could not be completed.

Source code in src/llmbroker/exceptions.py
50
51
class LLMRequestError(Exception):
    """Base: this request could not be completed."""

LLMSnapshot dataclass

Frozen point-in-time materialization of one LLM: raw facts, no status enum. demoted_operations may contain None — the bucket for calls made with no operation= label.

Source code in src/llmbroker/models.py
317
318
319
320
321
322
323
324
325
326
327
328
@dataclass(frozen=True, slots=True)
class LLMSnapshot:
    """Frozen point-in-time materialization of one LLM: raw facts, no status enum.
    ``demoted_operations`` may contain ``None`` — the bucket for calls made with no
    ``operation=`` label."""

    config: LLMConfig
    disabled: bool
    has_key: bool
    cooldown_until: datetime | None
    demoted_operations: tuple[str | None, ...]
    metrics: LLMMetrics | None

LLMState dataclass

Snapshot of one LLM's live runtime state, built fresh on each read.

Source code in src/llmbroker/models.py
31
32
33
34
35
36
37
@dataclass(frozen=True, slots=True)
class LLMState:
    """Snapshot of one LLM's live runtime state, built fresh on each read."""

    phase: LifecyclePhase = LifecyclePhase.AVAILABLE
    cooldown_until: datetime | None = None
    fail_count: int = 0

LLMStats dataclass

Per-LLM aggregate of call records over a time window.

by_status holds only statuses actually seen, so "how many were not OK" is a subtraction from total, not an assumption about the enum's shape.

Source code in src/llmbroker/models.py
302
303
304
305
306
307
308
309
310
311
312
313
314
@dataclass(frozen=True, slots=True)
class LLMStats:
    """Per-LLM aggregate of call records over a time window.

    ``by_status`` holds only statuses actually seen, so "how many were not OK" is
    a subtraction from ``total``, not an assumption about the enum's shape.
    """

    total: int
    by_status: Mapping[CallStatus, int]
    first_at: datetime | None
    last_at: datetime | None
    last_status: CallStatus | None

LLMTimeoutError

Bases: LLMRequestError

The request did not complete within its timeout.

Source code in src/llmbroker/exceptions.py
88
89
class LLMTimeoutError(LLMRequestError):
    """The request did not complete within its timeout."""

LLMs

Synchronous analogue of AsyncLLMs — one caller over the shared pool.

Source code in src/llmbroker/sync.py
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
class LLMs:
    """Synchronous analogue of AsyncLLMs — one caller over the shared pool."""

    def __init__(self, run_fn: "Callable[[Any], Any]", async_llms: AsyncLLMs) -> None:
        self._run = run_fn
        self._async = async_llms

    @property
    def scope(self) -> str | None:
        return self._async.scope

    def ask(  # noqa: PLR0913 - the call knobs, one keyword each
        self,
        prompt: str,
        *,
        operation: str | None = None,
        trace_id: str | None = None,
        wait: float | None = None,
        fastest_of: int | None = None,
        parallel_recovery: bool = True,
        response_format: dict | None = None,
    ) -> Result:
        return Result(
            self._run,
            self._run(
                self._async.ask(
                    prompt,
                    operation=operation,
                    trace_id=trace_id,
                    wait=wait,
                    fastest_of=fastest_of,
                    parallel_recovery=parallel_recovery,
                    response_format=response_format,
                ),
            ),
        )

    def chat(  # noqa: PLR0913 - what to send, and the call knobs
        self,
        messages: list[dict],
        *,
        tools: list[dict] | None = None,
        operation: str | None = None,
        trace_id: str | None = None,
        wait: float | None = None,
        fastest_of: int | None = None,
        parallel_recovery: bool = True,
        response_format: dict | None = None,
    ) -> Result:
        return Result(
            self._run,
            self._run(
                self._async.chat(
                    messages,
                    tools=tools,
                    operation=operation,
                    trace_id=trace_id,
                    wait=wait,
                    fastest_of=fastest_of,
                    parallel_recovery=parallel_recovery,
                    response_format=response_format,
                ),
            ),
        )

    def direct(self, alias: str | None = None, *, name: str | None = None) -> DirectClient:
        """Return a synchronous direct client (``ask()`` only) for a declared model.

        Streaming is async-only; use the async caller for deltas. Same alias/name
        keyspaces and errors as the async counterpart.
        """
        cfg, key = self._run(self._async.resolve_direct(alias, name=name))
        return DirectClient(base_url=cfg.base_url, model=cfg.model, api_key=key)

    def get(self, name: str) -> LLM:
        return LLM(self._run, self._run(self._async.get(name)))

    def count(self) -> int:
        return self._run(self._async.count())

    def record_quality(
        self,
        score: float,
        *,
        call_id: str | None = None,
        trace_id: str | None = None,
    ) -> None:
        self._run(self._async.record_quality(score, call_id=call_id, trace_id=trace_id))

    def calls(
        self,
        *,
        limit: int,
        since: datetime | None = None,
        operation: str | None = None,
        trace_id: str | None = None,
        call_id: str | None = None,
    ) -> list[Call]:
        return self._run(
            self._async.calls(
                limit=limit,
                since=since,
                operation=operation,
                trace_id=trace_id,
                call_id=call_id,
            ),
        )

    def stats(
        self,
        *,
        since: datetime | None = None,
        limit: int = _DEFAULT_STATS_LIMIT,
        operation: str | None = None,
    ) -> Mapping[str, LLMStats]:
        return self._run(self._async.stats(since=since, limit=limit, operation=operation))

direct(alias=None, *, name=None)

Return a synchronous direct client (ask() only) for a declared model.

Streaming is async-only; use the async caller for deltas. Same alias/name keyspaces and errors as the async counterpart.

Source code in src/llmbroker/sync.py
164
165
166
167
168
169
170
171
def direct(self, alias: str | None = None, *, name: str | None = None) -> DirectClient:
    """Return a synchronous direct client (``ask()`` only) for a declared model.

    Streaming is async-only; use the async caller for deltas. Same alias/name
    keyspaces and errors as the async counterpart.
    """
    cfg, key = self._run(self._async.resolve_direct(alias, name=name))
    return DirectClient(base_url=cfg.base_url, model=cfg.model, api_key=key)

LifecyclePhase

Bases: Enum

The FSM label for one LLM's lifecycle, always derived from cooldown_until vs now.

Source code in src/llmbroker/models.py
24
25
26
27
28
class LifecyclePhase(Enum):
    """The FSM label for one LLM's lifecycle, always derived from cooldown_until vs now."""

    AVAILABLE = "available"
    COOLING = "cooling"

MissingKeyError

Bases: LLMRequestError

The model's api_key_ref could not be resolved, so nothing was sent. Distinct from AuthError, which means a key was sent and rejected.

Source code in src/llmbroker/exceptions.py
83
84
85
class MissingKeyError(LLMRequestError):
    """The model's ``api_key_ref`` could not be resolved, so nothing was sent. Distinct
    from ``AuthError``, which means a key *was* sent and rejected."""

ModelList dataclass

A set of entries and the key help that goes with them — read from a source, or produced by a merge. keys is keyed by api_key_ref.

Source code in src/llmbroker/models.py
136
137
138
139
140
141
142
@dataclass(frozen=True, slots=True)
class ModelList:
    """A set of entries and the key help that goes with them — read from a source,
    or produced by a merge. ``keys`` is keyed by ``api_key_ref``."""

    configs: list[LLMConfig] = field(default_factory=list)
    keys: dict[str, KeyInfo] = field(default_factory=dict)

NoLLMAvailableError

Bases: LLMRequestError

No LLM slot was available for this request. reason is one of empty_pool, no_keys, all_disabled, excluded or timeout, the last carrying retry_at where a return time is known.

Source code in src/llmbroker/exceptions.py
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
class NoLLMAvailableError(LLMRequestError):
    """No LLM slot was available for this request. ``reason`` is one of
    ``empty_pool``, ``no_keys``, ``all_disabled``, ``excluded`` or ``timeout``, the
    last carrying ``retry_at`` where a return time is known."""

    def __init__(
        self,
        message: str,
        *,
        reason: str,
        retry_at: datetime | None = None,
    ) -> None:
        super().__init__(message)
        self.reason = reason
        self.retry_at = retry_at

Optimizer dataclass

Consecutive-failure counter for backoff, plus per-(model, operation) sliding windows of raw quality ratings backing the demoted-last selection order.

Source code in src/llmbroker/optimizer.py
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
@dataclass
class Optimizer:
    """Consecutive-failure counter for backoff, plus per-(model, operation) sliding
    windows of raw quality ratings backing the demoted-last selection order."""

    max_delay: float = 3600.0
    backoff_factor: float = 2.0
    quality_floor: float = 0.3
    quality_confidence: float = 0.95  # z for the Wilson upper bound
    quality_window: int = 30  # ratings kept per (model, operation)
    quality_min_count: int = 10  # verdicts need at least this many
    # Pseudo-ratings the curated weight is worth on an empty window; it loses its
    # majority about where a demotion verdict becomes expressible at all.
    prior_strength: float = 10.0

    _rl_fail_count: dict[str, int] = field(default_factory=dict, init=False, repr=False)
    # Oldest first, one entry per rated call: the id is what keeps a re-rating from
    # counting as a second observation.
    _scores: dict[tuple[str, str | None], deque[tuple[str, float]]] = field(
        default_factory=dict,
        init=False,
        repr=False,
    )

    def rl_fail_count(self, llm_name: str) -> int:
        return self._rl_fail_count.get(llm_name, 0)

    def on_rate_limited(self, llm_name: str) -> None:
        """Increment the consecutive-failure count the router reads for its backoff exponent."""
        self._rl_fail_count[llm_name] = self._rl_fail_count.get(llm_name, 0) + 1

    def on_success(self, llm_name: str) -> None:
        self._rl_fail_count[llm_name] = 0

    # ------------------------------------------------------------------
    # Quality windows + derived per-operation demotion verdicts
    # ------------------------------------------------------------------

    def wilson_bound(self, llm_name: str, operation: str | None) -> float | None:
        """The Wilson-score upper bound backing ``is_demoted``, for diagnostics."""
        window = self._scores.get((llm_name, operation))
        if not window:
            return None
        return wilson_upper([score for _, score in window], _z_score(self.quality_confidence))

    def quality_score(self, llm_name: str, operation: str | None, weight: float) -> float:
        """The curated weight, displaced by the observed window as that window fills.
        Not the Wilson bound, whose optimism on thin evidence would make the
        best-sampled model yield to a barely-tried one.

        >>> Optimizer().quality_score("m", None, 0.8)  # nothing observed yet
        0.8
        """
        window = self._scores.get((llm_name, operation))
        if not window:
            return weight
        n = len(window)
        mean = sum(score for _, score in window) / n
        strength = self.prior_strength * max(0.0, 1.0 - n / self.quality_window)
        if not strength:
            return mean
        return (n * mean + strength * weight) / (n + strength)

    def is_demoted(self, llm_name: str, operation: str | None) -> bool:
        """True iff the window holds at least ``quality_min_count`` ratings and their
        Wilson-score upper bound sits below ``quality_floor``."""
        window = self._scores.get((llm_name, operation))
        if window is None or len(window) < self.quality_min_count:
            return False
        bound = wilson_upper([score for _, score in window], _z_score(self.quality_confidence))
        return bound < self.quality_floor

    def demoted_operations(self, llm_name: str) -> frozenset[str | None]:
        return frozenset(
            operation
            for name, operation in self._scores
            if name == llm_name and self.is_demoted(name, operation)
        )

    def record_quality(
        self,
        llm_name: str,
        operation: str | None,
        call_id: str,
        score: float,
    ) -> None:
        """Fold one call's rating into the (model, operation) window, oldest evicted
        first. A call already in the window keeps its place and takes the new value:
        one rated call is one observation however often the host changes its mind."""
        before = self.is_demoted(llm_name, operation)
        window = self._scores.setdefault(
            (llm_name, operation),
            deque(maxlen=self.quality_window),
        )
        for i, (rated, _) in enumerate(window):
            if rated == call_id:
                window[i] = (call_id, score)
                break
        else:
            window.append((call_id, score))
        self._log_flip(llm_name, operation, before, self.is_demoted(llm_name, operation))

    def load_scores(self, scores: dict[tuple[str, str | None], list[tuple[str, float]]]) -> None:
        """Replace every window wholesale — used by the journal rebuild. Values come
        oldest first, so the next rating evicts the oldest rated call and not the
        newest one the rebuild just put in."""
        keys = set(self._scores) | set(scores)
        before = {key: self.is_demoted(*key) for key in keys}
        self._scores = {
            key: deque(values[-self.quality_window :], maxlen=self.quality_window)
            for key, values in scores.items()
        }
        for key in keys:
            self._log_flip(key[0], key[1], before[key], self.is_demoted(*key))

    def _log_flip(
        self,
        llm_name: str,
        operation: str | None,
        before: bool,  # noqa: FBT001
        after: bool,  # noqa: FBT001
    ) -> None:
        if before == after:
            return
        bound = self.wilson_bound(llm_name, operation)
        if after:
            if bound is not None:
                logger.warning(
                    "%s: quality-demoted for operation=%r (wilson upper %.3f < floor %.2f)",
                    llm_name,
                    operation,
                    bound,
                    self.quality_floor,
                )
            else:
                logger.warning("%s: quality-demoted for operation=%r", llm_name, operation)
        elif bound is not None:
            logger.info(
                "%s: quality demotion cleared for operation=%r (wilson upper %.3f)",
                llm_name,
                operation,
                bound,
            )
        else:
            logger.info("%s: quality demotion cleared for operation=%r", llm_name, operation)

is_demoted(llm_name, operation)

True iff the window holds at least quality_min_count ratings and their Wilson-score upper bound sits below quality_floor.

Source code in src/llmbroker/optimizer.py
 95
 96
 97
 98
 99
100
101
102
def is_demoted(self, llm_name: str, operation: str | None) -> bool:
    """True iff the window holds at least ``quality_min_count`` ratings and their
    Wilson-score upper bound sits below ``quality_floor``."""
    window = self._scores.get((llm_name, operation))
    if window is None or len(window) < self.quality_min_count:
        return False
    bound = wilson_upper([score for _, score in window], _z_score(self.quality_confidence))
    return bound < self.quality_floor

load_scores(scores)

Replace every window wholesale — used by the journal rebuild. Values come oldest first, so the next rating evicts the oldest rated call and not the newest one the rebuild just put in.

Source code in src/llmbroker/optimizer.py
134
135
136
137
138
139
140
141
142
143
144
145
def load_scores(self, scores: dict[tuple[str, str | None], list[tuple[str, float]]]) -> None:
    """Replace every window wholesale — used by the journal rebuild. Values come
    oldest first, so the next rating evicts the oldest rated call and not the
    newest one the rebuild just put in."""
    keys = set(self._scores) | set(scores)
    before = {key: self.is_demoted(*key) for key in keys}
    self._scores = {
        key: deque(values[-self.quality_window :], maxlen=self.quality_window)
        for key, values in scores.items()
    }
    for key in keys:
        self._log_flip(key[0], key[1], before[key], self.is_demoted(*key))

on_rate_limited(llm_name)

Increment the consecutive-failure count the router reads for its backoff exponent.

Source code in src/llmbroker/optimizer.py
59
60
61
def on_rate_limited(self, llm_name: str) -> None:
    """Increment the consecutive-failure count the router reads for its backoff exponent."""
    self._rl_fail_count[llm_name] = self._rl_fail_count.get(llm_name, 0) + 1

quality_score(llm_name, operation, weight)

The curated weight, displaced by the observed window as that window fills. Not the Wilson bound, whose optimism on thin evidence would make the best-sampled model yield to a barely-tried one.

Optimizer().quality_score("m", None, 0.8) # nothing observed yet 0.8

Source code in src/llmbroker/optimizer.py
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
def quality_score(self, llm_name: str, operation: str | None, weight: float) -> float:
    """The curated weight, displaced by the observed window as that window fills.
    Not the Wilson bound, whose optimism on thin evidence would make the
    best-sampled model yield to a barely-tried one.

    >>> Optimizer().quality_score("m", None, 0.8)  # nothing observed yet
    0.8
    """
    window = self._scores.get((llm_name, operation))
    if not window:
        return weight
    n = len(window)
    mean = sum(score for _, score in window) / n
    strength = self.prior_strength * max(0.0, 1.0 - n / self.quality_window)
    if not strength:
        return mean
    return (n * mean + strength * weight) / (n + strength)

record_quality(llm_name, operation, call_id, score)

Fold one call's rating into the (model, operation) window, oldest evicted first. A call already in the window keeps its place and takes the new value: one rated call is one observation however often the host changes its mind.

Source code in src/llmbroker/optimizer.py
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
def record_quality(
    self,
    llm_name: str,
    operation: str | None,
    call_id: str,
    score: float,
) -> None:
    """Fold one call's rating into the (model, operation) window, oldest evicted
    first. A call already in the window keeps its place and takes the new value:
    one rated call is one observation however often the host changes its mind."""
    before = self.is_demoted(llm_name, operation)
    window = self._scores.setdefault(
        (llm_name, operation),
        deque(maxlen=self.quality_window),
    )
    for i, (rated, _) in enumerate(window):
        if rated == call_id:
            window[i] = (call_id, score)
            break
    else:
        window.append((call_id, score))
    self._log_flip(llm_name, operation, before, self.is_demoted(llm_name, operation))

wilson_bound(llm_name, operation)

The Wilson-score upper bound backing is_demoted, for diagnostics.

Source code in src/llmbroker/optimizer.py
70
71
72
73
74
75
def wilson_bound(self, llm_name: str, operation: str | None) -> float | None:
    """The Wilson-score upper bound backing ``is_demoted``, for diagnostics."""
    window = self._scores.get((llm_name, operation))
    if not window:
        return None
    return wilson_upper([score for _, score in window], _z_score(self.quality_confidence))

PendingKey dataclass

One api_key_ref a synced model list wants and the secrets store does not have, with the entries it holds back inactive until it resolves.

Source code in src/llmbroker/models.py
145
146
147
148
149
150
151
152
@dataclass(frozen=True, slots=True)
class PendingKey:
    """One ``api_key_ref`` a synced model list wants and the secrets store does not have,
    with the entries it holds back inactive until it resolves."""

    api_key_ref: str
    help: str
    entry_names: tuple[str, ...]

PoolModelError

Bases: LLMRequestError

direct() was pointed at a preset-managed pool entry.

Pool models are anonymous: reach them through ask/chat/stream, which route and learn. A model you want to name is declared with direct=.

Source code in src/llmbroker/exceptions.py
75
76
77
78
79
80
class PoolModelError(LLMRequestError):
    """``direct()`` was pointed at a preset-managed pool entry.

    Pool models are anonymous: reach them through ``ask``/``chat``/``stream``,
    which route and learn. A model you want to name is declared with ``direct=``.
    """

PoolSnapshot dataclass

Bases: Mapping[str, LLMSnapshot]

Point-in-time view of the whole pool. Iterate it like a dict of name -> LLMSnapshot; the properties describe the pool as a whole and come from the same measurement the degradation alarm uses.

Source code in src/llmbroker/models.py
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
@dataclass(frozen=True, slots=True)
class PoolSnapshot(Mapping[str, LLMSnapshot]):
    """Point-in-time view of the whole pool. Iterate it like a dict of
    ``name -> LLMSnapshot``; the properties describe the pool as a whole and come
    from the same measurement the degradation alarm uses."""

    _llms: Mapping[str, LLMSnapshot]
    _health: PoolHealth
    _direct_missing_keys: tuple[PendingKey, ...] = ()

    @property
    def providers_usable(self) -> int:
        return self._health.providers_usable

    @property
    def providers_total(self) -> int:
        return self._health.providers_total

    @property
    def missing_keys(self) -> tuple[PendingKey, ...]:
        return self._health.missing_keys

    @property
    def direct_missing_keys(self) -> tuple[PendingKey, ...]:
        """Refs the host's own ``direct``-reachable models want and cannot resolve.
        Kept apart from ``missing_keys``, which counts the pool's failover capacity —
        a model that is never routed can neither degrade nor repair it."""
        return self._direct_missing_keys

    @property
    def degraded(self) -> bool:
        return self._health.degraded

    def __getitem__(self, name: str) -> LLMSnapshot:
        return self._llms[name]

    def __iter__(self) -> Iterator[str]:
        return iter(self._llms)

    def __len__(self) -> int:
        return len(self._llms)

direct_missing_keys property

Refs the host's own direct-reachable models want and cannot resolve. Kept apart from missing_keys, which counts the pool's failover capacity — a model that is never routed can neither degrade nor repair it.

ProviderError

Bases: LLMRequestError

The provider returned an error response: status is the HTTP code, detail a short snippet of the body. Catch this for any provider failure, or a subclass for one kind.

Source code in src/llmbroker/exceptions.py
 98
 99
100
101
102
103
104
105
106
class ProviderError(LLMRequestError):
    """The provider returned an error response: ``status`` is the HTTP code, ``detail``
    a short snippet of the body. Catch this for any provider failure, or a subclass
    for one kind."""

    def __init__(self, message: str, *, status: int, detail: str | None = None) -> None:
        super().__init__(message)
        self.status = status
        self.detail = detail

RateLimitError

Bases: ProviderError

The provider rate-limited or was temporarily unavailable (HTTP 429/503).

retry_after is the server-advised wait in seconds, when the response carried a parseable Retry-After header.

Source code in src/llmbroker/exceptions.py
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
class RateLimitError(ProviderError):
    """The provider rate-limited or was temporarily unavailable (HTTP 429/503).

    ``retry_after`` is the server-advised wait in seconds, when the response
    carried a parseable ``Retry-After`` header.
    """

    def __init__(
        self,
        message: str,
        *,
        status: int,
        detail: str | None = None,
        retry_after: int | None = None,
    ) -> None:
        super().__init__(message, status=status, detail=detail)
        self.retry_after = retry_after

Result

Synchronous analogue of AsyncResult.

Source code in src/llmbroker/sync.py
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
class Result:
    """Synchronous analogue of AsyncResult."""

    def __init__(self, run_fn: "Callable[[Any], Any]", async_result: AsyncResult) -> None:
        self._run = run_fn
        self._async = async_result
        self.text = async_result.text
        self.tool_calls = async_result.tool_calls
        self.usage = async_result.usage

    @property
    def llm_name(self) -> str:
        return self._async.llm_name

    @property
    def operation(self) -> str | None:
        return self._async.operation

    @property
    def call_id(self) -> str:
        return self._async.call_id

    def record_quality(self, score: float) -> None:
        self._run(self._async.record_quality(score))

SchemaVersionError

Bases: LLMBrokerError

The store holds a schema version this release cannot use.

Source code in src/llmbroker/exceptions.py
41
42
43
44
45
46
47
class SchemaVersionError(LLMBrokerError):
    """The store holds a schema version this release cannot use."""

    def __init__(self, message: str, *, found: int, expected: int) -> None:
        super().__init__(message)
        self.found = found
        self.expected = expected

Secrets

Read-only env-backed secrets resolver (the default battery). env_file is consulted only where the real environment has no such variable, and a blank value counts as absent either way.

Source code in src/llmbroker/standalone/secrets.py
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
class Secrets:
    """Read-only env-backed secrets resolver (the default battery). ``env_file`` is
    consulted only where the real environment has no such variable, and a blank value
    counts as absent either way."""

    def __init__(self, env_file: str | Path | None = None) -> None:
        self._env_file = Path(env_file) if env_file is not None else None
        self._file_values: dict[str, str] | None = None
        self._file_stamp: tuple[int, int] | None = None

    def _file_mapping(self, path: Path) -> dict[str, str]:
        """Re-parse whenever the file changes, so a key filled in on a running
        broker takes effect on the next resync exactly as an exported one would."""
        try:
            info = path.stat()
            stamp = (info.st_mtime_ns, info.st_size)
        except OSError:
            stamp = None
        if self._file_values is None or stamp != self._file_stamp:
            self._file_stamp = stamp
            try:
                self._file_values = parse_env_file(path.read_text(encoding="utf-8"))
            except OSError:
                self._file_values = {}
        return self._file_values

    def _from_file(self, ref: str) -> str | None:
        if self._env_file is None:
            return None
        # The skeleton `llmbroker env` writes is all `KEY=` lines: an unfilled one
        # must leave the model keyless, not hand the provider an empty credential.
        return self._file_mapping(self._env_file).get(ref) or None

    async def resolve(self, ref: str) -> str:
        # A blank export is as unset as no export at all: whitespace admitted here
        # would put a model with no credential into the pool (invariant 21).
        value = os.environ.get(ref)
        if value is None or not value.strip():
            value = self._from_file(ref)
        if value is None or not value.strip():
            raise KeyError(f"Secrets: env var {ref!r} is not set")
        return value

StreamHandle

Bases: RoutedCall

Returned by stream(): an async iterator of text deltas that also names the model answering them, from the first delta on — or, for an answer that had none, once it ends. Closing it is the consumer's move, exactly as for the raw iterator.

Source code in src/llmbroker/broker/result.py
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
class StreamHandle(RoutedCall):
    """Returned by ``stream()``: an async iterator of text deltas that also names the
    model answering them, from the first delta on — or, for an answer that had none,
    once it ends. Closing it is the consumer's move, exactly as for the raw iterator."""

    def __init__(  # noqa: PLR0913
        self,
        deltas: AsyncGenerator[str, None],
        receipt: CallReceipt,
        *,
        operation: str | None,
        store: StoreProtocol,
        scope: str | None,
        observe_quality: ObserveQuality | None,
    ) -> None:
        super().__init__(
            receipt,
            operation=operation,
            store=store,
            scope=scope,
            observe_quality=observe_quality,
        )
        self._deltas = deltas

    def __aiter__(self) -> AsyncIterator[str]:
        return self._deltas

    async def aclose(self) -> None:
        """Close the stream, handing the model's slot back."""
        await self._deltas.aclose()

aclose() async

Close the stream, handing the model's slot back.

Source code in src/llmbroker/broker/result.py
151
152
153
async def aclose(self) -> None:
    """Close the stream, handing the model's slot back."""
    await self._deltas.aclose()

StreamInterruptedError

Bases: LLMRequestError

A pooled stream died after it had already emitted deltas. Failover is impossible once output has reached the caller, so the deltas already yielded stand and the rest of the answer is lost.

Source code in src/llmbroker/exceptions.py
120
121
122
123
124
125
126
127
class StreamInterruptedError(LLMRequestError):
    """A pooled stream died after it had already emitted deltas. Failover is
    impossible once output has reached the caller, so the deltas already yielded
    stand and the rest of the answer is lost."""

    def __init__(self, message: str, *, llm_name: str) -> None:
        super().__init__(message)
        self.llm_name = llm_name

SyncRefusedError

Bases: LLMBrokerError

A sync result was not applied — it would have emptied a working registry.

report carries what the merge would have done, so a caller can log or forward the facts that led to the refusal.

Source code in src/llmbroker/exceptions.py
24
25
26
27
28
29
30
31
32
33
class SyncRefusedError(LLMBrokerError):
    """A sync result was not applied — it would have emptied a working registry.

    ``report`` carries what the merge would have done, so a caller can log or
    forward the facts that led to the refusal.
    """

    def __init__(self, message: str, *, report: SyncReport) -> None:
        super().__init__(message)
        self.report = report

SyncReport dataclass

What one sync did, as raw facts — no severity verdict, the host derives that.

Source code in src/llmbroker/models.py
169
170
171
172
173
174
175
176
177
178
179
180
181
@dataclass(frozen=True, slots=True)
class SyncReport:
    """What one sync did, as raw facts — no severity verdict, the host derives that."""

    source: str
    applied: bool
    added: tuple[str, ...] = ()
    updated: tuple[str, ...] = ()
    removed: tuple[str, ...] = ()
    orphan_refs: tuple[str, ...] = ()
    pending_keys: tuple[PendingKey, ...] = ()
    active_before: int = 0
    active_after: int = 0

ToolLoopLimitError

Bases: LLMRequestError

The tool loop hit max_steps without a tool-call-free reply. Raised rather than returning empty: the contract admits a result or an exception, never silence.

Source code in src/llmbroker/exceptions.py
92
93
94
95
class ToolLoopLimitError(LLMRequestError):
    """The tool loop hit ``max_steps`` without a tool-call-free reply. Raised rather
    than returning empty: the contract admits a result or an exception, never
    silence."""

UnknownCallError

Bases: LLMBrokerError

The key a rating named matched no answered call inside the rating window — it is older than that, was purged by retention, never existed, or never answered.

Source code in src/llmbroker/exceptions.py
36
37
38
class UnknownCallError(LLMBrokerError):
    """The key a rating named matched no answered call inside the rating window — it
    is older than that, was purged by retention, never existed, or never answered."""

UnknownModelError

Bases: LLMRequestError

No registry entry matched the requested model name.

Source code in src/llmbroker/exceptions.py
71
72
class UnknownModelError(LLMRequestError):
    """No registry entry matched the requested model name."""

Usage dataclass

Resource use the provider reported for one call.

Source code in src/llmbroker/models.py
195
196
197
198
199
200
201
202
@dataclass(frozen=True, slots=True)
class Usage:
    """Resource use the provider reported for one call."""

    prompt_tokens: int | None = None
    completion_tokens: int | None = None
    total_tokens: int | None = None
    extra: dict[str, int] | None = None

arun_tool_loop(llms, messages, *, tools=None, dispatch=None, max_steps=8, **chat_kwargs) async

Drive broker.chat until a tool-call-free reply; execute tools via dispatch. Returns that last round's result: earlier rounds are routed calls of their own, each with its own journal row, so usage is the final round's alone.

Source code in src/llmbroker/tool_loop.py
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
async def arun_tool_loop(
    llms: AsyncBroker,
    messages: list[dict],
    *,
    tools: list[dict] | None = None,
    dispatch: Mapping[str, Callable[..., object]] | None = None,
    max_steps: int = 8,
    **chat_kwargs,
) -> AsyncResult:
    """Drive ``broker.chat`` until a tool-call-free reply; execute tools via dispatch.
    Returns that last round's result: earlier rounds are routed calls of their own,
    each with its own journal row, so ``usage`` is the final round's alone."""
    convo = list(messages)
    dispatch = dispatch or {}
    for _ in range(max_steps):
        result = await llms.chat(convo, tools=tools, **chat_kwargs)
        if _advance_tool_loop(convo, result, dispatch):
            return result
    raise ToolLoopLimitError(_TOOL_LOOP_EXHAUSTED.format(max_steps=max_steps))

curated_paid(*, home=None)

The paid models llmbroker curates, one row per tier.

Source code in src/llmbroker/broker/curated.py
117
118
119
def curated_paid(*, home: str | Path | None = None) -> tuple[CuratedModel, ...]:
    """The paid models llmbroker curates, one row per tier."""
    return models_from(tomllib.loads(_catalog_text(PAID_CATALOG, home)))

curated_pool(*, home=None)

The curated free model list, as a sync would read it.

Source code in src/llmbroker/broker/curated.py
122
123
124
def curated_pool(*, home: str | Path | None = None) -> ModelList:
    """The curated free model list, as a sync would read it."""
    return parse_model_list(tomllib.loads(_catalog_text(POOL_PRESET, home)))

curated_providers(*, home=None)

The paid providers llmbroker curates — base url and key ref for each, so a declaration for a model the catalog does not carry can be built.

Source code in src/llmbroker/broker/curated.py
111
112
113
114
def curated_providers(*, home: str | Path | None = None) -> tuple[CuratedProvider, ...]:
    """The paid providers llmbroker curates — base url and key ref for each, so a
    declaration for a model the catalog does not carry can be built."""
    return providers_from(tomllib.loads(_catalog_text(PAID_CATALOG, home)))

format_report(report)

The whole outcome as text, printed on every run including a no-op.

Source code in src/llmbroker/broker/report.py
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
def format_report(report: SyncReport) -> str:
    """The whole outcome as text, printed on every run including a no-op."""
    verb = "applied" if report.applied else "refused"
    lines = [
        f"sync {report.source}: {verb}"
        f" — {report.active_before} -> {report.active_after} entries with a key",
    ]
    for label, names in (
        ("added", report.added),
        ("updated", report.updated),
        ("removed", report.removed),
    ):
        if names:
            lines.append(f"  {label}: {', '.join(names)}")
    for ref in report.orphan_refs:
        lines.append(
            f"  unused key {ref} — nothing here uses it any more;"
            " revoke it at the provider if you do not need it",
        )
    for pending in report.pending_keys:
        lines.append(
            f"  pending key {pending.api_key_ref} — holds back {', '.join(pending.entry_names)}",
        )
        lines.extend(f"      {line}" for line in pending.help.splitlines() if line.strip())
    if len(lines) == 1:
        lines.append("  no changes")
    return "\n".join(lines)

run_tool_loop(llms, messages, *, tools=None, dispatch=None, max_steps=8, **chat_kwargs)

Synchronous tool loop over a sync Broker, returning the final round's result.

Mirrors arun_tool_loop but calls the blocking Broker.chat; it does not use the async engine directly so it is safe to call from any thread.

Source code in src/llmbroker/tool_loop.py
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
def run_tool_loop(
    llms: Broker,
    messages: list[dict],
    *,
    tools: list[dict] | None = None,
    dispatch: Mapping[str, Callable[..., object]] | None = None,
    max_steps: int = 8,
    **chat_kwargs,
) -> Result:
    """Synchronous tool loop over a sync ``Broker``, returning the final round's result.

    Mirrors ``arun_tool_loop`` but calls the blocking ``Broker.chat``; it does
    not use the async engine directly so it is safe to call from any thread.
    """
    convo = list(messages)
    dispatch = dispatch or {}
    for _ in range(max_steps):
        result = llms.chat(convo, tools=tools, **chat_kwargs)
        if _advance_tool_loop(convo, result, dispatch):
            return result
    raise ToolLoopLimitError(_TOOL_LOOP_EXHAUSTED.format(max_steps=max_steps))

__main__

python -m llmbroker entry point.

aws

AWS Secrets Manager backend. Needs aioboto3 (llmbroker[aws]); importing this package is how a host declares that dependency.

Secrets

AWS Secrets Manager-backed mutable secrets store. A client is opened per call, so there is no shared state and aclose is a no-op.

Source code in src/llmbroker/aws/secrets.py
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
class Secrets:
    """AWS Secrets Manager-backed mutable secrets store. A client is opened per
    call, so there is no shared state and ``aclose`` is a no-op."""

    def __init__(
        self,
        *,
        region_name: str | None = None,
        endpoint_url: str | None = None,
        prefix: str = "llmbroker/",
    ) -> None:
        self._session = aioboto3.Session()
        self._region_name = region_name
        self._endpoint_url = endpoint_url
        self._prefix = prefix

    def _name(self, ref: str) -> str:
        return f"{self._prefix}{ref}"

    def _client(self):
        return self._session.client(
            "secretsmanager",
            region_name=self._region_name,
            endpoint_url=self._endpoint_url,
        )

    async def resolve(self, ref: str) -> str:
        async with self._client() as client:
            try:
                response = await client.get_secret_value(SecretId=self._name(ref))
            except ClientError as exc:
                if exc.response["Error"]["Code"] != "ResourceNotFoundException":
                    raise
                raise KeyError(f"aws.Secrets: ref {ref!r} not found") from exc
            return response["SecretString"]

    async def refs(self, prefix: str = "") -> frozenset[str]:
        """Every ref stored under this instance's prefix, narrowed by ``prefix``.
        Paginated: ListSecrets caps a page, and a truncated answer would read as
        "that key is not here"."""
        wanted = self._name(prefix)
        found: set[str] = set()
        async with self._client() as client:
            paginator = client.get_paginator("list_secrets")
            async for page in paginator.paginate(
                Filters=[{"Key": "name", "Values": [wanted]}],
            ):
                for secret in page.get("SecretList", ()):
                    name = secret.get("Name", "")
                    if name.startswith(wanted):
                        found.add(name[len(self._prefix) :])
        return frozenset(found)

    async def set(self, ref: str, value: str) -> None:
        name = self._name(ref)
        async with self._client() as client:
            try:
                await client.put_secret_value(SecretId=name, SecretString=value)
            except ClientError as exc:
                if exc.response["Error"]["Code"] != "ResourceNotFoundException":
                    raise
                await client.create_secret(
                    Name=name,
                    SecretString=value,
                    Tags=[{"Key": "llmbroker", "Value": "1"}],
                )

    async def aclose(self) -> None:
        return
refs(prefix='') async

Every ref stored under this instance's prefix, narrowed by prefix. Paginated: ListSecrets caps a page, and a truncated answer would read as "that key is not here".

Source code in src/llmbroker/aws/secrets.py
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
async def refs(self, prefix: str = "") -> frozenset[str]:
    """Every ref stored under this instance's prefix, narrowed by ``prefix``.
    Paginated: ListSecrets caps a page, and a truncated answer would read as
    "that key is not here"."""
    wanted = self._name(prefix)
    found: set[str] = set()
    async with self._client() as client:
        paginator = client.get_paginator("list_secrets")
        async for page in paginator.paginate(
            Filters=[{"Key": "name", "Values": [wanted]}],
        ):
            for secret in page.get("SecretList", ()):
                name = secret.get("Name", "")
                if name.startswith(wanted):
                    found.add(name[len(self._prefix) :])
    return frozenset(found)

secrets

AWS Secrets Manager-backed mutable secrets store.

Secrets

AWS Secrets Manager-backed mutable secrets store. A client is opened per call, so there is no shared state and aclose is a no-op.

Source code in src/llmbroker/aws/secrets.py
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
class Secrets:
    """AWS Secrets Manager-backed mutable secrets store. A client is opened per
    call, so there is no shared state and ``aclose`` is a no-op."""

    def __init__(
        self,
        *,
        region_name: str | None = None,
        endpoint_url: str | None = None,
        prefix: str = "llmbroker/",
    ) -> None:
        self._session = aioboto3.Session()
        self._region_name = region_name
        self._endpoint_url = endpoint_url
        self._prefix = prefix

    def _name(self, ref: str) -> str:
        return f"{self._prefix}{ref}"

    def _client(self):
        return self._session.client(
            "secretsmanager",
            region_name=self._region_name,
            endpoint_url=self._endpoint_url,
        )

    async def resolve(self, ref: str) -> str:
        async with self._client() as client:
            try:
                response = await client.get_secret_value(SecretId=self._name(ref))
            except ClientError as exc:
                if exc.response["Error"]["Code"] != "ResourceNotFoundException":
                    raise
                raise KeyError(f"aws.Secrets: ref {ref!r} not found") from exc
            return response["SecretString"]

    async def refs(self, prefix: str = "") -> frozenset[str]:
        """Every ref stored under this instance's prefix, narrowed by ``prefix``.
        Paginated: ListSecrets caps a page, and a truncated answer would read as
        "that key is not here"."""
        wanted = self._name(prefix)
        found: set[str] = set()
        async with self._client() as client:
            paginator = client.get_paginator("list_secrets")
            async for page in paginator.paginate(
                Filters=[{"Key": "name", "Values": [wanted]}],
            ):
                for secret in page.get("SecretList", ()):
                    name = secret.get("Name", "")
                    if name.startswith(wanted):
                        found.add(name[len(self._prefix) :])
        return frozenset(found)

    async def set(self, ref: str, value: str) -> None:
        name = self._name(ref)
        async with self._client() as client:
            try:
                await client.put_secret_value(SecretId=name, SecretString=value)
            except ClientError as exc:
                if exc.response["Error"]["Code"] != "ResourceNotFoundException":
                    raise
                await client.create_secret(
                    Name=name,
                    SecretString=value,
                    Tags=[{"Key": "llmbroker", "Value": "1"}],
                )

    async def aclose(self) -> None:
        return
refs(prefix='') async

Every ref stored under this instance's prefix, narrowed by prefix. Paginated: ListSecrets caps a page, and a truncated answer would read as "that key is not here".

Source code in src/llmbroker/aws/secrets.py
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
async def refs(self, prefix: str = "") -> frozenset[str]:
    """Every ref stored under this instance's prefix, narrowed by ``prefix``.
    Paginated: ListSecrets caps a page, and a truncated answer would read as
    "that key is not here"."""
    wanted = self._name(prefix)
    found: set[str] = set()
    async with self._client() as client:
        paginator = client.get_paginator("list_secrets")
        async for page in paginator.paginate(
            Filters=[{"Key": "name", "Values": [wanted]}],
        ):
            for secret in page.get("SecretList", ()):
                name = secret.get("Name", "")
                if name.startswith(wanted):
                    found.add(name[len(self._prefix) :])
    return frozenset(found)

backends

Zero-dependency storage core: the table spec, the Driver protocol, the ports written once against it, and an in-memory reference driver. A DB backend package supplies one Driver and wraps these ports in its facade classes.

driver

The per-DB storage contract: one round-trip per read, and no logic two correct backends could answer differently — that stays in backends.ports. The journal fold names its columns here; backends.spec refuses to load if one is renamed away.

Driver

Bases: Protocol

Source code in src/llmbroker/backends/driver.py
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
class Driver(Protocol):
    async def ensure_schema(self) -> None: ...

    # ------------------------------------------------------------------
    # Keyed records (registry, disabled, secrets) — every key column is
    # non-null text, so key matching is plain equality.
    # ------------------------------------------------------------------

    async def fetch(self, table: str) -> list[Row]:
        """All rows, ordered by key columns — a stable order, never a ranking (invariant 3)."""
        ...

    async def get(self, table: str, key: Key) -> Row | None: ...

    async def upsert(self, table: str, key: Key, row: Row) -> None: ...

    async def delete(self, table: str, key: Key) -> bool: ...

    # ------------------------------------------------------------------
    # Journal ops (llmbroker_calls) — strictly append-only: no update op
    # exists; quality is its own appended record.
    # ------------------------------------------------------------------

    async def append(self, table: str, row: Row) -> None: ...

    async def journal_view(
        self,
        limit: int,
        match: Row | None = None,
        since: datetime | None = None,
    ) -> list[Row]:
        """Newest-first call rows, each with a ``score`` key holding its newest rating's
        value or ``None``. ``match`` and ``since`` narrow the call rows only; Mongo
        floors stored values and the bound to whole milliseconds."""
        ...

    async def purge(self, table: str, before: datetime) -> int:
        """Delete rows older than ``before``; returns the count removed."""
        ...

    async def aclose(self) -> None: ...
fetch(table) async

All rows, ordered by key columns — a stable order, never a ranking (invariant 3).

Source code in src/llmbroker/backends/driver.py
20
21
22
async def fetch(self, table: str) -> list[Row]:
    """All rows, ordered by key columns — a stable order, never a ranking (invariant 3)."""
    ...
journal_view(limit, match=None, since=None) async

Newest-first call rows, each with a score key holding its newest rating's value or None. match and since narrow the call rows only; Mongo floors stored values and the bound to whole milliseconds.

Source code in src/llmbroker/backends/driver.py
37
38
39
40
41
42
43
44
45
46
async def journal_view(
    self,
    limit: int,
    match: Row | None = None,
    since: datetime | None = None,
) -> list[Row]:
    """Newest-first call rows, each with a ``score`` key holding its newest rating's
    value or ``None``. ``match`` and ``since`` narrow the call rows only; Mongo
    floors stored values and the bound to whole milliseconds."""
    ...
purge(table, before) async

Delete rows older than before; returns the count removed.

Source code in src/llmbroker/backends/driver.py
48
49
50
async def purge(self, table: str, before: datetime) -> int:
    """Delete rows older than ``before``; returns the count removed."""
    ...

inmemory

Trivial dict-based Driver — a test double, and a dependency-free storage option for hosts that want the full port surface without a database.

InMemoryDriver

In-process Driver implementation. Not persisted, not process-shared.

Source code in src/llmbroker/backends/inmemory.py
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
class InMemoryDriver:
    """In-process ``Driver`` implementation. Not persisted, not process-shared."""

    def __init__(self) -> None:
        self._tables: dict[str, dict[Key, Row]] = {}
        self._journals: dict[str, list[Row]] = {}

    async def ensure_schema(self) -> None:
        return

    async def fetch(self, table: str) -> list[Row]:
        return [dict(row) for _, row in sorted(self._tables.get(table, {}).items())]

    async def get(self, table: str, key: Key) -> Row | None:
        row = self._tables.get(table, {}).get(key)
        return dict(row) if row is not None else None

    async def upsert(self, table: str, key: Key, row: Row) -> None:
        self._tables.setdefault(table, {})[key] = dict(row)

    async def delete(self, table: str, key: Key) -> bool:
        return self._tables.get(table, {}).pop(key, None) is not None

    async def append(self, table: str, row: Row) -> None:
        self._journals.setdefault(table, []).append(dict(row))

    async def journal_view(
        self,
        limit: int,
        match: Row | None = None,
        since: datetime | None = None,
    ) -> list[Row]:
        rows = self._journals.get("calls", [])
        ordered = sorted(rows, key=lambda r: r.get("called_at") or _EPOCH, reverse=True)
        newest_rating: dict[object, float] = {}
        for row in ordered:  # newest-first, so the first rating seen per call wins
            if row.get("kind") == KIND_QUALITY and row.get("quality_score") is not None:
                newest_rating.setdefault(row.get("call_id"), row["quality_score"])  # type: ignore[arg-type]
        calls = [r for r in ordered if r.get("kind") == KIND_CALL]
        if match:
            calls = [r for r in calls if all(r.get(k) == v for k, v in match.items())]
        if since is not None:
            calls = [r for r in calls if (r.get("called_at") or _EPOCH) >= since]
        return [{**r, "score": newest_rating.get(r.get("id"))} for r in calls[:limit]]

    async def purge(self, table: str, before: datetime) -> int:
        rows = self._journals.get(table, [])
        keep = [r for r in rows if (r.get("called_at") or before) >= before]
        removed = len(rows) - len(keep)
        self._journals[table] = keep
        return removed

    async def aclose(self) -> None:
        return

ports

Generic ports: the domain protocols implemented once over any Driver. No user scope exists in this layer — the broker turns scope into a ref prefix instead.

DriverRegistry

Registry over any Driver — a pure preset mirror, globally scoped.

Source code in src/llmbroker/backends/ports.py
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
class DriverRegistry:
    """Registry over any ``Driver`` — a pure preset mirror, globally scoped."""

    def __init__(self, driver: Driver) -> None:
        self._driver = driver

    async def load(self) -> list[LLMConfig]:
        rows = await self._driver.fetch("registry")
        configs = [
            LLMConfig.from_metadata(
                name=str(row["name"]),
                base_url=str(row["base_url"]),
                model=str(row["model"]),
                api_key_ref=str(row["api_key_ref"]),
                metadata=row.get("metadata"),  # type: ignore[arg-type]
            )
            for row in rows
        ]
        # Names come back unique from the store's own key; aliases live in the
        # metadata column, where nothing enforces them.
        check_aliases(configs)
        return configs

    async def mirror(self, configs: list[LLMConfig]) -> None:
        """Total mirror: add new, update existing, delete stored entries absent
        from ``configs``. What may be absent is decided before this call, in
        ``broker.merge``; here the merged model list is simply written."""
        check_aliases(configs)
        source_names = {c.name for c in configs}
        existing = {str(row["name"]) for row in await self._driver.fetch("registry")}
        for name in existing - source_names:
            await self._driver.delete("registry", (name,))
        for cfg in configs:
            await self._driver.upsert(
                "registry",
                (cfg.name,),
                {
                    "name": cfg.name,
                    "base_url": cfg.base_url,
                    "model": cfg.model,
                    "api_key_ref": cfg.api_key_ref,
                    "metadata": cfg.to_metadata(),
                },
            )

    async def aclose(self) -> None:
        await self._driver.aclose()
mirror(configs) async

Total mirror: add new, update existing, delete stored entries absent from configs. What may be absent is decided before this call, in broker.merge; here the merged model list is simply written.

Source code in src/llmbroker/backends/ports.py
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
async def mirror(self, configs: list[LLMConfig]) -> None:
    """Total mirror: add new, update existing, delete stored entries absent
    from ``configs``. What may be absent is decided before this call, in
    ``broker.merge``; here the merged model list is simply written."""
    check_aliases(configs)
    source_names = {c.name for c in configs}
    existing = {str(row["name"]) for row in await self._driver.fetch("registry")}
    for name in existing - source_names:
        await self._driver.delete("registry", (name,))
    for cfg in configs:
        await self._driver.upsert(
            "registry",
            (cfg.name,),
            {
                "name": cfg.name,
                "base_url": cfg.base_url,
                "model": cfg.model,
                "api_key_ref": cfg.api_key_ref,
                "metadata": cfg.to_metadata(),
            },
        )
DriverSecrets

Flat ref -> value secrets store over any Driver. Exact-match lookups only — the own→shared prefix fallback lives in the broker.

Source code in src/llmbroker/backends/ports.py
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
class DriverSecrets:
    """Flat ``ref -> value`` secrets store over any ``Driver``. Exact-match lookups
    only — the own→shared prefix fallback lives in the broker."""

    def __init__(self, driver: Driver) -> None:
        self._driver = driver

    async def resolve(self, ref: str) -> str:
        row = await self._driver.get("secrets", (ref,))
        if row is None:
            raise KeyError(f"{type(self).__name__}: ref {ref!r} not found")
        return str(row["value"])

    async def set(self, ref: str, value: str) -> None:
        await self._driver.upsert("secrets", (ref,), {"ref": ref, "value": value})

    async def refs(self, prefix: str = "") -> frozenset[str]:
        rows = await self._driver.fetch("secrets")
        return frozenset(str(row["ref"]) for row in rows if str(row["ref"]).startswith(prefix))

    async def aclose(self) -> None:
        await self._driver.aclose()
DriverStore

Journal (append/read/purge) + admin disabled-map, over any Driver.

Self-purges call rows older than retention, checked at most once per hour on write activity.

Source code in src/llmbroker/backends/ports.py
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
class DriverStore:
    """Journal (append/read/purge) + admin disabled-map, over any ``Driver``.

    Self-purges call rows older than ``retention``, checked at most once per
    hour on write activity.
    """

    def __init__(self, driver: Driver, *, retention: timedelta = RETENTION_DEFAULT) -> None:
        self._driver = driver
        self._retention = retention
        self._purge_clock = PurgeClock()

    async def record(self, call: Call) -> None:
        await self._driver.append("calls", _call_to_row(with_utc_timestamps(call)))
        await self._maybe_purge()

    async def record_quality(
        self,
        call_id: str,
        score: float,
        *,
        scope: str | None = None,
    ) -> None:
        await self._driver.append("calls", quality_row(call_id, score, scope))
        await self._maybe_purge()

    async def calls(  # noqa: PLR0913 - one narrowing dimension per parameter
        self,
        *,
        limit: int,
        scope: str | None = None,
        since: datetime | None = None,
        operation: str | None = None,
        trace_id: str | None = None,
        call_id: str | None = None,
    ) -> list[Call]:
        """Newest-first tail of the journal, one row per call attempt carrying the
        newest score it was rated with. ``since`` bounds ``called_at`` inclusively; the
        filters narrow the call rows only, never the ratings folded onto them."""
        check_limit(limit)
        match: Row = {}
        if scope is not None:
            match["scope"] = scope
        if operation is not None:
            match["operation"] = operation
        if trace_id is not None:
            match["trace_id"] = trace_id
        if call_id is not None:
            match["id"] = call_id
        bound = to_utc(since, "since") if since is not None else None
        rows = await self._driver.journal_view(limit, match or None, bound)
        return [_row_to_call(r) for r in rows]

    async def _purge_old_calls(self) -> None:
        cutoff = datetime.now(UTC) - self._retention
        await self._driver.purge("calls", cutoff)

    async def _maybe_purge(self) -> None:
        if not self._purge_clock.due():
            return
        await self._purge_old_calls()

    # ------------------------------------------------------------------
    # Admin disabled-verdict map
    # ------------------------------------------------------------------

    async def get_disabled(self, name: str) -> bool:
        row = await self._driver.get("disabled", (name,))
        return bool(row["disabled"]) if row else False

    async def set_disabled(self, name: str, flag: bool) -> None:  # noqa: FBT001
        await self._driver.upsert("disabled", (name,), {"name": name, "disabled": int(flag)})

    async def seed_disabled(self, names: list[str]) -> None:
        """Insert-if-absent every name with ``disabled=False`` — never touches existing values."""
        existing = {str(row["name"]) for row in await self._driver.fetch("disabled")}
        for name in names:
            if name not in existing:
                await self._driver.upsert("disabled", (name,), {"name": name, "disabled": 0})

    async def disabled_map(self) -> dict[str, bool]:
        rows = await self._driver.fetch("disabled")
        return {str(r["name"]): bool(r["disabled"]) for r in rows}

    async def aclose(self) -> None:
        await self._driver.aclose()
calls(*, limit, scope=None, since=None, operation=None, trace_id=None, call_id=None) async

Newest-first tail of the journal, one row per call attempt carrying the newest score it was rated with. since bounds called_at inclusively; the filters narrow the call rows only, never the ratings folded onto them.

Source code in src/llmbroker/backends/ports.py
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
async def calls(  # noqa: PLR0913 - one narrowing dimension per parameter
    self,
    *,
    limit: int,
    scope: str | None = None,
    since: datetime | None = None,
    operation: str | None = None,
    trace_id: str | None = None,
    call_id: str | None = None,
) -> list[Call]:
    """Newest-first tail of the journal, one row per call attempt carrying the
    newest score it was rated with. ``since`` bounds ``called_at`` inclusively; the
    filters narrow the call rows only, never the ratings folded onto them."""
    check_limit(limit)
    match: Row = {}
    if scope is not None:
        match["scope"] = scope
    if operation is not None:
        match["operation"] = operation
    if trace_id is not None:
        match["trace_id"] = trace_id
    if call_id is not None:
        match["id"] = call_id
    bound = to_utc(since, "since") if since is not None else None
    rows = await self._driver.journal_view(limit, match or None, bound)
    return [_row_to_call(r) for r in rows]
seed_disabled(names) async

Insert-if-absent every name with disabled=False — never touches existing values.

Source code in src/llmbroker/backends/ports.py
210
211
212
213
214
215
async def seed_disabled(self, names: list[str]) -> None:
    """Insert-if-absent every name with ``disabled=False`` — never touches existing values."""
    existing = {str(row["name"]) for row in await self._driver.fetch("disabled")}
    for name in names:
        if name not in existing:
            await self._driver.upsert("disabled", (name,), {"name": name, "disabled": 0})

spec

One declarative description of the stores, consumed by every driver's ensure_schema. Column types are portable strings — each driver maps them to its own native DDL/wire representation.

TableSpec dataclass

One store's shape: name, identity columns, and portable column types. Every key column is a single non-null text column, and the user scope rides inside the ref string rather than in a column of its own.

Source code in src/llmbroker/backends/spec.py
 9
10
11
12
13
14
15
16
17
18
@dataclass(frozen=True)
class TableSpec:
    """One store's shape: name, identity columns, and portable column types. Every
    key column is a single non-null text column, and the user scope rides inside the
    ref string rather than in a column of its own."""

    name: str
    key: tuple[str, ...]
    columns: dict[str, str] = field(default_factory=dict)
    indexes: tuple[tuple[str, ...], ...] = ()

broker

The async broker engine: the AsyncBroker façade plus its live-pool collaborators.

Implementation lives in focused sibling modules. Request exceptions live in llmbroker.exceptions and the optimizer knob in llmbroker.optimizer.

aliases

The paid catalog and the declared models that follow one of its aliases.

The alias contract — what a re-resolution may rewrite, and what it may never move — is in specs/reference/rules/direct-by-name.md.

AliasChange

Bases: Enum

What a re-resolution found for one declared alias.

Source code in src/llmbroker/broker/aliases.py
20
21
22
23
24
class AliasChange(Enum):
    """What a re-resolution found for one declared alias."""

    MODEL = "model"
    KEY_REF = "key_ref"
AliasFact dataclass

One thing a re-resolution moved: the broker logs these and does not decide what they mean here.

Source code in src/llmbroker/broker/aliases.py
27
28
29
30
31
32
33
34
35
@dataclass(frozen=True, slots=True)
class AliasFact:
    """One thing a re-resolution moved: the broker logs these and does not decide
    what they mean here."""

    change: AliasChange
    alias: str
    was: str = ""
    now: str = ""
catalog_alias_targets(catalog)

Map every catalog alias to the row it now recommends; a row whose provider has no endpoint or no key ref could not be called, so it recommends nothing. An alias names exactly one model, so a duplicate makes the whole file unusable and raises.

Source code in src/llmbroker/broker/aliases.py
41
42
43
44
45
46
47
48
49
50
51
52
53
def catalog_alias_targets(catalog: dict) -> dict[str, CuratedModel]:
    """Map every catalog alias to the row it now recommends; a row whose provider has
    no endpoint or no key ref could not be called, so it recommends nothing. An alias
    names exactly one model, so a duplicate makes the whole file unusable and raises."""
    targets: dict[str, CuratedModel] = {}
    for row in models_from(catalog):
        provider = row.provider
        if row.alias is None or not (provider.id and provider.base_url and provider.api_key_ref):
            continue
        if row.alias in targets:
            raise ValueError(f"paid catalog is invalid — alias '{row.alias}' is used twice")
        targets[row.alias] = row
    return targets
resolve_declared(declared, presets, *, previous=None, fetch=True) async

Turn what the caller declared with direct= into entries, with the catalog's key help — nothing stores a declared model, so this read is the only place that help is available. previous marks a re-resolution and is what the facts diff.

Source code in src/llmbroker/broker/aliases.py
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
async def resolve_declared(
    declared: Sequence[str | LLMConfig],
    presets: PresetSource,
    *,
    previous: DeclaredModels | None = None,
    fetch: bool = True,
) -> tuple[DeclaredModels, tuple[AliasFact, ...]]:
    """Turn what the caller declared with ``direct=`` into entries, with the catalog's
    key help — nothing stores a declared model, so this read is the only place that
    help is available. ``previous`` marks a re-resolution and is what the facts diff."""
    if not declared:
        return DeclaredModels(), ()
    targets: Mapping[str, CuratedModel] = _NO_TARGETS
    if any(isinstance(item, str) for item in declared):
        text = await asyncio.to_thread(
            presets.text,
            PAID_CATALOG,
            prefer_cache=True,
            floor=previous is None,
            fetch=fetch,
        )
        targets = catalog_alias_targets(tomllib.loads(text))
    configs = tuple(
        item if isinstance(item, LLMConfig) else _entry_for_alias(item, targets)
        for item in declared
    )
    wanted = {cfg.api_key_ref for cfg in configs}
    resolved = DeclaredModels(
        configs=configs,
        key_help={
            t.provider.api_key_ref: t.provider.key_help
            for t in targets.values()
            if t.provider.key_help and t.provider.api_key_ref in wanted
        },
    )
    return resolved, _moved(previous, resolved)

broker

The AsyncBroker façade: it owns the three ports, provisions the live pool once, and delegates each operation to the collaborator that owns it.

AsyncBroker

Façade over the LLM pool: route completions, inspect state, edit the catalog.

Every constructor argument is documented in docs/ — "Model pool and calls" and "Direct model calls".

Source code in src/llmbroker/broker/broker.py
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
class AsyncBroker:
    """Façade over the LLM pool: route completions, inspect state, edit the catalog.

    Every constructor argument is documented in ``docs/`` — "Model pool and calls"
    and "Direct model calls".
    """

    def __init__(  # noqa: PLR0913
        self,
        registry: RegistryProtocol | str | Path | None = None,
        *,
        secrets: SecretsProtocol | None = None,
        store: StoreProtocol | None = None,
        optimize: bool | Optimizer = True,
        sync: str | None | _SyncDefault = _SYNC_DEFAULT,
        sync_interval: float | None = _DEFAULT_SYNC_INTERVAL,
        home: str | Path | None = None,
        direct: Sequence[str | LLMConfig] = (),
    ) -> None:
        _check_sync_interval(sync_interval)
        source = _resolve_sync(sync, registry)
        self._home = home_dir(home)
        source_secrets: SecretsProtocol | None = None
        source_store: StoreProtocol | None = None
        source_label: str | None = None
        if registry is None:
            registry, source_secrets, source_store = zero_config_ports(self._home)
        elif isinstance(registry, (str, Path)):
            source_label = str(registry)
            registry, source_secrets, source_store = resolve_source(registry)

        secrets = (
            as_secrets(secrets) if secrets is not None else (source_secrets or default_secrets())
        )
        store = store if store is not None else (source_store or default_store())

        if isinstance(optimize, Optimizer):
            self._optimizer: Optimizer | None = optimize
        elif optimize:
            self._optimizer = Optimizer()
        else:
            self._optimizer = None

        self._registry = registry
        self._secrets = secrets
        self._store = store
        self._presets = PresetSource(self._home)
        self._shared_ring = KeyRing(secrets)
        self._rings: dict[str, KeyRing] = {}
        self._known: frozenset[str] | None = None

        self._declared = tuple(direct)
        self._autofetch = sync_interval is not None
        self._last_declared: DeclaredModels | None = None
        pool = LLMPool(optimizer=self._optimizer)
        self._pool = pool
        self._catalog = Catalog(
            registry,
            secrets,
            pool,
            self._shared_ring,
            store,
            overlay=self._resolve_declared if self._declared else None,
            autofill=self._autofetch,
            relearn=self._relearn,
        )

        self._learner: Learner | None = None
        if self._optimizer is not None:
            self._learner = Learner(self._optimizer, store, pool)

        self._router = Router(
            pool,
            store,
            optimizer=self._optimizer,
            learner=self._learner,
        )
        self._pool_view = PoolView(
            pool,
            self._metrics_map,
            lambda: self._catalog.health,
            lambda: self._catalog.direct_missing_keys,
            lambda: self._catalog.payable,
        )

        self._refresher = ModelListRefresher(
            registry,
            self._catalog,
            store,
            self._presets,
            source=source,
            interval=sync_interval,
            home=self._home,
            declared=self._declared,
            target_label=source_label,
            live=lambda: self._provisioned,
            rebuild=self.rebuild,
        )

        self._provisioned = False
        self._provision_lock = asyncio.Lock()
        self._last_underprov_alert: float = float("-inf")
        self._underprov_alert_interval: float = 60.0
        self._next_exhaustion_rebuild: float = float("-inf")
        self.llms = self._caller(self._shared_ring)

    def _caller(self, ring: KeyRing) -> AsyncLLMs:
        return AsyncLLMs(
            ring,
            router=self._router,
            catalog=self._catalog,
            pool_view=self._pool_view,
            store=self._store,
            learner=self._learner,
            ensure_pool=self.ensure_pool,
            on_exhausted=self._on_exhausted,
        )

    def for_scope(self, scope: str) -> AsyncLLMs:
        """A caller that pays with ``scope``\'s own keys, falling back to the shared
        ones, and writes ``scope`` on every row it journals. Costs no I/O."""
        if not scope:
            raise ValueError("scope must not be empty string; use broker.llms for unscoped")
        ring = self._rings.get(scope)
        if ring is None:
            if len(self._rings) >= _MAX_CALLERS:
                self._rings.pop(next(iter(self._rings)))
            ring = KeyRing(
                self._secrets,
                scope=scope,
                shared=self._shared_ring,
                known=self._known,
            )
            self._rings[scope] = ring
        return self._caller(ring)

    async def rebuild(self) -> None:
        """Rebuild the pool: every caller's keys, the registry, pool membership, the
        disabled map and quality, wholesale. Fires on exactly four triggers — start,
        the refresh clock, an explicit ``sync()``, and pool exhaustion."""
        known = await known_refs(self._secrets)
        self._known = known
        # Over a copy: a request may ask for a caller while this is awaiting.
        for ring in (self._shared_ring, *list(self._rings.values())):
            await ring.refresh(known)
        await self._catalog.rebuild(known)

    async def _relearn(self) -> None:
        if self._learner is not None:
            await self._learner.relearn()

    async def _rebuild_safely(self, reason: str) -> None:
        """A rebuild reached from a caller's own call must never fail it: an
        unreadable port leaves the pool exactly as it is, and says so."""
        try:
            await self.rebuild()
        except Exception:  # noqa: BLE001 - a background re-read may not break a request
            logger.exception("pool rebuild on %s failed, continuing on the current pool", reason)

    async def _metrics_map(self) -> dict[str, LLMMetrics]:
        """Per-LLM metrics from whatever is available: the learner's cache, a
        queryable store's tail, or nothing."""
        if self._learner is not None:
            return self._learner.metrics
        if isinstance(self._store, QueryableStoreProtocol):
            return metrics_from_calls(await self._store.calls(limit=TAIL_READ_LIMIT))
        return {}

    async def _resolve_declared(self) -> DeclaredModels:
        """Re-resolve ``direct=``, keeping the resolution already in use when the
        catalog cannot be read or no longer carries an alias. Only the first
        resolution raises — see ``rules/direct-by-name.md``."""
        previous = self._last_declared
        try:
            resolved, moved = await resolve_declared(
                self._declared,
                self._presets,
                previous=previous,
                fetch=self._autofetch,
            )
        except (UnknownModelError, ValueError, OSError) as exc:
            if previous is None:
                raise
            logger.warning(
                "direct= could not be re-resolved (%s) — declared models stay on the"
                " resolution already in use",
                exc,
            )
            return previous
        for line in alias_lines(moved):
            logger.info("direct=: %s", line)
        self._last_declared = resolved
        return resolved

    # ------------------------------------------------------------------
    # Lifecycle
    # ------------------------------------------------------------------

    async def ensure_pool(self) -> None:
        """Lazy idempotent initializer — provisions the pool exactly once, and
        schedules the model list refresh when its interval has elapsed.

        Raises if the registry is empty and nothing filled it — sync a model list in.
        """
        if not self._provisioned:
            async with self._provision_lock:
                if not self._provisioned:
                    await self._refresher.before_provision()
                    await self.rebuild()
                    self._catalog.check_not_empty()
                    self._provisioned = True
        # Outside the lock: a refresh calls sync(), and inside it the catalog is
        # mid-provision.
        self._refresher.schedule()

    @property
    def last_sync_report(self) -> SyncReport | None:
        """What the last sync — explicit or refreshed — did, or ``None`` if none has
        run. A host forwards it to its own admin channel."""
        return self._refresher.last_report

    async def sync(self, source: str | None = None) -> SyncReport | None:
        """Merge the curated preset named by ``source`` into the registry and return
        what it did; with no argument, whatever this installation follows — the paid
        catalog alone has no report. See ``rules/model-list.md``."""
        return await self._refresher.sync(source)

    async def aclose(self) -> None:
        # Before the ports: a refresh in flight would otherwise write through a
        # registry whose driver is closing.
        await self._refresher.aclose()
        await self._router.aclose()
        for port in (self._registry, self._secrets, self._store):
            if isinstance(port, AsyncResourceProtocol):
                await port.aclose()

    async def __aenter__(self) -> "AsyncBroker":
        await self.ensure_pool()
        return self

    async def __aexit__(self, *exc: object) -> None:
        await self.aclose()

    # ------------------------------------------------------------------
    # Routing — delegated to the unscoped caller
    # ------------------------------------------------------------------

    async def ask(  # noqa: PLR0913 - the call knobs, one keyword each
        self,
        prompt: str,
        *,
        operation: str | None = None,
        trace_id: str | None = None,
        wait: float | None = None,
        fastest_of: int | None = None,
        parallel_recovery: bool = True,
        response_format: dict | None = None,
    ) -> AsyncResult:
        return await self.llms.ask(
            prompt,
            operation=operation,
            trace_id=trace_id,
            wait=wait,
            fastest_of=fastest_of,
            parallel_recovery=parallel_recovery,
            response_format=response_format,
        )

    async def chat(  # noqa: PLR0913 - what to send, and the call knobs
        self,
        messages: list[dict],
        *,
        tools: list[dict] | None = None,
        operation: str | None = None,
        trace_id: str | None = None,
        wait: float | None = None,
        fastest_of: int | None = None,
        parallel_recovery: bool = True,
        response_format: dict | None = None,
    ) -> AsyncResult:
        return await self.llms.chat(
            messages,
            tools=tools,
            operation=operation,
            trace_id=trace_id,
            wait=wait,
            fastest_of=fastest_of,
            parallel_recovery=parallel_recovery,
            response_format=response_format,
        )

    def stream(  # noqa: PLR0913 - the call knobs, one keyword each
        self,
        prompt: str,
        *,
        operation: str | None = None,
        trace_id: str | None = None,
        wait: float | None = None,
        fastest_of: int | None = None,
        parallel_recovery: bool = True,
        response_format: dict | None = None,
    ) -> StreamHandle:
        """Route a completion over the pool as a handle yielding text deltas and naming
        what answered them. ``wait`` bounds the whole answer in provider time; past the
        first delta a death raises ``StreamInterruptedError``. Async-only."""
        return self.llms.stream(
            prompt,
            operation=operation,
            trace_id=trace_id,
            wait=wait,
            fastest_of=fastest_of,
            parallel_recovery=parallel_recovery,
            response_format=response_format,
        )

    async def direct(
        self,
        alias: str | None = None,
        *,
        name: str | None = None,
    ) -> AsyncDirectClient:
        """A client for exactly one model of your own — no pool, no failover.

        Takes exactly one of ``alias`` or ``name=``; raises ``PoolModelError``,
        ``UnknownModelError`` or ``MissingKeyError``. See ``docs/`` "Direct model calls".
        """
        return await self.llms.direct(alias, name=name)

    async def record_quality(
        self,
        score: float,
        *,
        call_id: str | None = None,
        trace_id: str | None = None,
    ) -> None:
        """Rate a past call — the delayed counterpart of ``result.record_quality``.

        Takes exactly one key and rates the newest answered call it names within the
        rating window; raises ``UnknownCallError`` when nothing there answered.
        """
        await self.llms.record_quality(score, call_id=call_id, trace_id=trace_id)

    # ------------------------------------------------------------------
    # Inspection
    # ------------------------------------------------------------------

    async def get(self, name: str) -> AsyncLLM:
        return await self.llms.get(name)

    async def count(self) -> int:
        return await self.llms.count()

    async def snapshot(self) -> PoolSnapshot:
        await self.ensure_pool()
        return await self._pool_view.snapshot()

    # ------------------------------------------------------------------
    # Manual disable — the one verdict that actually excludes
    # ------------------------------------------------------------------

    async def disable_llm(self, name: str) -> None:
        """Set the manual latch: withdraws the slot, survives preset rolls, covers
        every operation including future ones. Only ``enable_llm`` clears it."""
        await self.ensure_pool()
        self._pool.set_disabled(name)
        if isinstance(self._store, DisabledMapProtocol):
            await self._store.set_disabled(name, True)

    async def enable_llm(self, name: str) -> None:
        """Clear the manual latch — a re-enabled model rehabilitates through new
        ratings, no quality reset exists."""
        await self.ensure_pool()
        await self._pool.clear_disabled(name)
        if isinstance(self._store, DisabledMapProtocol):
            await self._store.set_disabled(name, False)

    # ------------------------------------------------------------------
    # Call journal
    # ------------------------------------------------------------------

    async def calls(
        self,
        *,
        limit: int,
        since: datetime | None = None,
        operation: str | None = None,
        trace_id: str | None = None,
        call_id: str | None = None,
    ) -> list[Call]:
        """Newest-first journal tail for the whole installation — one caller's own rows
        are ``for_scope(...).calls(...)``. One row per call attempt, each carrying the
        newest score it was rated with. Never provisions the pool."""
        return await self.llms.calls(
            limit=limit,
            since=since,
            operation=operation,
            trace_id=trace_id,
            call_id=call_id,
        )

    async def stats(
        self,
        *,
        since: datetime | None = None,
        limit: int = _DEFAULT_STATS_LIMIT,
        operation: str | None = None,
    ) -> Mapping[str, LLMStats]:
        """Per-model counts of call records over a window, keyed by model name.
        ``limit`` caps rows read, not the window: totals summing to it mean the
        window may be truncated. Never provisions the pool."""
        return await self.llms.stats(since=since, limit=limit, operation=operation)

    async def _on_exhausted(self, exc: NoLLMAvailableError, ring: KeyRing) -> bool:
        """The reactive trigger: a pool that could not answer is re-read, debounced and
        skipped for a caller already holding every key. Answers whether the re-read
        actually ran, which is what makes the caller's second pass worth taking."""
        self._maybe_alert_underprov(exc)
        now = time.monotonic()
        if now < self._next_exhaustion_rebuild:
            return False
        if await self._fully_keyed(ring):
            return False
        self._next_exhaustion_rebuild = now + _EXHAUSTION_DEBOUNCE_SEC
        await self._rebuild_safely("pool exhaustion")
        return True

    async def _fully_keyed(self, ring: KeyRing) -> bool:
        """Whether this caller can already pay for every ref the pool names. An empty
        pool is not a full set: there the registry itself is what needs re-reading."""
        refs = {cfg.api_key_ref for cfg in self._pool.configs.values() if cfg.api_key_ref}
        return bool(refs) and refs <= await ring.payable(refs)

    def _maybe_alert_underprov(self, exc: NoLLMAvailableError) -> None:
        """Fire when zero *keyed* configs are routable — the genuine alarm.

        Keyless configs are excluded because they are never cooled, so one of them
        would mask "every keyed model is COOLING"; other reasons log their own line.
        """
        if exc.reason != "timeout":
            return
        if self._optimizer is None:
            return
        if not self._pool.configs:
            return
        now = time.monotonic()
        if now - self._last_underprov_alert < self._underprov_alert_interval:
            return
        payable = self._catalog.payable
        keyed_names = [
            name for name, cfg in self._pool.configs.items() if cfg.api_key_ref in payable
        ]
        all_offline = all(
            self._pool.state(name).phase is not LifecyclePhase.AVAILABLE for name in keyed_names
        )
        if all_offline:
            self._last_underprov_alert = now
            logger.warning(
                "pool under-provisioned: all LLMs are COOLING — add more LLMs to the registry",
            )
last_sync_report property

What the last sync — explicit or refreshed — did, or None if none has run. A host forwards it to its own admin channel.

calls(*, limit, since=None, operation=None, trace_id=None, call_id=None) async

Newest-first journal tail for the whole installation — one caller's own rows are for_scope(...).calls(...). One row per call attempt, each carrying the newest score it was rated with. Never provisions the pool.

Source code in src/llmbroker/broker/broker.py
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
async def calls(
    self,
    *,
    limit: int,
    since: datetime | None = None,
    operation: str | None = None,
    trace_id: str | None = None,
    call_id: str | None = None,
) -> list[Call]:
    """Newest-first journal tail for the whole installation — one caller's own rows
    are ``for_scope(...).calls(...)``. One row per call attempt, each carrying the
    newest score it was rated with. Never provisions the pool."""
    return await self.llms.calls(
        limit=limit,
        since=since,
        operation=operation,
        trace_id=trace_id,
        call_id=call_id,
    )
direct(alias=None, *, name=None) async

A client for exactly one model of your own — no pool, no failover.

Takes exactly one of alias or name=; raises PoolModelError, UnknownModelError or MissingKeyError. See docs/ "Direct model calls".

Source code in src/llmbroker/broker/broker.py
421
422
423
424
425
426
427
428
429
430
431
432
async def direct(
    self,
    alias: str | None = None,
    *,
    name: str | None = None,
) -> AsyncDirectClient:
    """A client for exactly one model of your own — no pool, no failover.

    Takes exactly one of ``alias`` or ``name=``; raises ``PoolModelError``,
    ``UnknownModelError`` or ``MissingKeyError``. See ``docs/`` "Direct model calls".
    """
    return await self.llms.direct(alias, name=name)
disable_llm(name) async

Set the manual latch: withdraws the slot, survives preset rolls, covers every operation including future ones. Only enable_llm clears it.

Source code in src/llmbroker/broker/broker.py
466
467
468
469
470
471
472
async def disable_llm(self, name: str) -> None:
    """Set the manual latch: withdraws the slot, survives preset rolls, covers
    every operation including future ones. Only ``enable_llm`` clears it."""
    await self.ensure_pool()
    self._pool.set_disabled(name)
    if isinstance(self._store, DisabledMapProtocol):
        await self._store.set_disabled(name, True)
enable_llm(name) async

Clear the manual latch — a re-enabled model rehabilitates through new ratings, no quality reset exists.

Source code in src/llmbroker/broker/broker.py
474
475
476
477
478
479
480
async def enable_llm(self, name: str) -> None:
    """Clear the manual latch — a re-enabled model rehabilitates through new
    ratings, no quality reset exists."""
    await self.ensure_pool()
    await self._pool.clear_disabled(name)
    if isinstance(self._store, DisabledMapProtocol):
        await self._store.set_disabled(name, False)
ensure_pool() async

Lazy idempotent initializer — provisions the pool exactly once, and schedules the model list refresh when its interval has elapsed.

Raises if the registry is empty and nothing filled it — sync a model list in.

Source code in src/llmbroker/broker/broker.py
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
async def ensure_pool(self) -> None:
    """Lazy idempotent initializer — provisions the pool exactly once, and
    schedules the model list refresh when its interval has elapsed.

    Raises if the registry is empty and nothing filled it — sync a model list in.
    """
    if not self._provisioned:
        async with self._provision_lock:
            if not self._provisioned:
                await self._refresher.before_provision()
                await self.rebuild()
                self._catalog.check_not_empty()
                self._provisioned = True
    # Outside the lock: a refresh calls sync(), and inside it the catalog is
    # mid-provision.
    self._refresher.schedule()
for_scope(scope)

A caller that pays with scope's own keys, falling back to the shared ones, and writes scope on every row it journals. Costs no I/O.

Source code in src/llmbroker/broker/broker.py
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
def for_scope(self, scope: str) -> AsyncLLMs:
    """A caller that pays with ``scope``\'s own keys, falling back to the shared
    ones, and writes ``scope`` on every row it journals. Costs no I/O."""
    if not scope:
        raise ValueError("scope must not be empty string; use broker.llms for unscoped")
    ring = self._rings.get(scope)
    if ring is None:
        if len(self._rings) >= _MAX_CALLERS:
            self._rings.pop(next(iter(self._rings)))
        ring = KeyRing(
            self._secrets,
            scope=scope,
            shared=self._shared_ring,
            known=self._known,
        )
        self._rings[scope] = ring
    return self._caller(ring)
rebuild() async

Rebuild the pool: every caller's keys, the registry, pool membership, the disabled map and quality, wholesale. Fires on exactly four triggers — start, the refresh clock, an explicit sync(), and pool exhaustion.

Source code in src/llmbroker/broker/broker.py
242
243
244
245
246
247
248
249
250
251
async def rebuild(self) -> None:
    """Rebuild the pool: every caller's keys, the registry, pool membership, the
    disabled map and quality, wholesale. Fires on exactly four triggers — start,
    the refresh clock, an explicit ``sync()``, and pool exhaustion."""
    known = await known_refs(self._secrets)
    self._known = known
    # Over a copy: a request may ask for a caller while this is awaiting.
    for ring in (self._shared_ring, *list(self._rings.values())):
        await ring.refresh(known)
    await self._catalog.rebuild(known)
record_quality(score, *, call_id=None, trace_id=None) async

Rate a past call — the delayed counterpart of result.record_quality.

Takes exactly one key and rates the newest answered call it names within the rating window; raises UnknownCallError when nothing there answered.

Source code in src/llmbroker/broker/broker.py
434
435
436
437
438
439
440
441
442
443
444
445
446
async def record_quality(
    self,
    score: float,
    *,
    call_id: str | None = None,
    trace_id: str | None = None,
) -> None:
    """Rate a past call — the delayed counterpart of ``result.record_quality``.

    Takes exactly one key and rates the newest answered call it names within the
    rating window; raises ``UnknownCallError`` when nothing there answered.
    """
    await self.llms.record_quality(score, call_id=call_id, trace_id=trace_id)
stats(*, since=None, limit=_DEFAULT_STATS_LIMIT, operation=None) async

Per-model counts of call records over a window, keyed by model name. limit caps rows read, not the window: totals summing to it mean the window may be truncated. Never provisions the pool.

Source code in src/llmbroker/broker/broker.py
506
507
508
509
510
511
512
513
514
515
516
async def stats(
    self,
    *,
    since: datetime | None = None,
    limit: int = _DEFAULT_STATS_LIMIT,
    operation: str | None = None,
) -> Mapping[str, LLMStats]:
    """Per-model counts of call records over a window, keyed by model name.
    ``limit`` caps rows read, not the window: totals summing to it mean the
    window may be truncated. Never provisions the pool."""
    return await self.llms.stats(since=since, limit=limit, operation=operation)
stream(prompt, *, operation=None, trace_id=None, wait=None, fastest_of=None, parallel_recovery=True, response_format=None)

Route a completion over the pool as a handle yielding text deltas and naming what answered them. wait bounds the whole answer in provider time; past the first delta a death raises StreamInterruptedError. Async-only.

Source code in src/llmbroker/broker/broker.py
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
def stream(  # noqa: PLR0913 - the call knobs, one keyword each
    self,
    prompt: str,
    *,
    operation: str | None = None,
    trace_id: str | None = None,
    wait: float | None = None,
    fastest_of: int | None = None,
    parallel_recovery: bool = True,
    response_format: dict | None = None,
) -> StreamHandle:
    """Route a completion over the pool as a handle yielding text deltas and naming
    what answered them. ``wait`` bounds the whole answer in provider time; past the
    first delta a death raises ``StreamInterruptedError``. Async-only."""
    return self.llms.stream(
        prompt,
        operation=operation,
        trace_id=trace_id,
        wait=wait,
        fastest_of=fastest_of,
        parallel_recovery=parallel_recovery,
        response_format=response_format,
    )
sync(source=None) async

Merge the curated preset named by source into the registry and return what it did; with no argument, whatever this installation follows — the paid catalog alone has no report. See rules/model-list.md.

Source code in src/llmbroker/broker/broker.py
327
328
329
330
331
async def sync(self, source: str | None = None) -> SyncReport | None:
    """Merge the curated preset named by ``source`` into the registry and return
    what it did; with no argument, whatever this installation follows — the paid
    catalog alone has no report. See ``rules/model-list.md``."""
    return await self._refresher.sync(source)

catalog

Catalog: keep the live pool's membership in sync with the registry. apply is the only registry write path; what it writes is decided in broker.merge.

Catalog

Reconciles the persistent registry into the live pool, and mirrors presets into it.

Source code in src/llmbroker/broker/catalog.py
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
class Catalog:
    """Reconciles the persistent registry into the live pool, and mirrors presets into it."""

    def __init__(  # noqa: PLR0913 - the three ports plus what this pool is allowed to do
        self,
        registry: RegistryProtocol,
        secrets: SecretsProtocol,
        pool: LLMPool,
        ring: KeyRing,
        store: StoreProtocol,
        *,
        overlay: "Callable[[], Awaitable[DeclaredModels]] | None" = None,
        autofill: bool = True,
        relearn: "Callable[[], Awaitable[None]] | None" = None,
    ) -> None:
        self._registry = registry
        self._secrets = secrets
        self._pool = pool
        self._ring = ring
        self._store = store
        self._overlay = overlay
        self._autofill = autofill
        self._relearn = relearn
        self._declared: DeclaredModels | None = None
        self._declared_lock = asyncio.Lock()
        self._health = PoolHealth()
        self._direct_missing_keys: tuple[PendingKey, ...] = ()
        self._key_info: dict[str, KeyInfo] = {}
        self._payable: frozenset[str] = frozenset()
        self._scoped_refs: frozenset[str] = frozenset()
        self._empty = False
        self._reported_state: int | None = None
        self._reported_missing: set[str] = set()

    @property
    def health(self) -> PoolHealth:
        """The pool-wide counts from the last reconcile — the same numbers the
        degradation alarm uses, so log and admin UI cannot diverge."""
        return self._health

    @property
    def payable(self) -> frozenset[str]:
        """The refs this installation holds a key for as of the last rebuild, a
        caller's own included — what an admin read reports and the alarm counts."""
        return self._payable

    @property
    def direct_missing_keys(self) -> tuple[PendingKey, ...]:
        """Refs the host's own ``direct``-reachable entries want and cannot resolve."""
        return self._direct_missing_keys

    def key_help(self, ref: str) -> str:
        """Where this ref's key comes from: the registry's own ``[keys]`` first —
        a host that wrote its own hint means it — then the paid catalog's."""
        stored = self._key_info[ref].help if ref in self._key_info else ""
        if stored:
            return stored
        return self._declared.key_help.get(ref, "") if self._declared is not None else ""

    def invalidate_declared(self) -> None:
        """Drop the resolved overlay so the next read follows the catalog again."""
        self._declared = None

    async def entries(self) -> tuple[list[LLMConfig], list[LLMConfig]]:
        """The stored pool and the models declared in code, apart. This read is not the
        alias clock — ``direct()`` comes through here on every call and must not pay a
        catalog parse."""
        configs = await self._registry.load()
        if self._overlay is None:
            return configs, []
        declared = list((await self._resolve_overlay()).configs)
        check_overlay(configs, declared)
        return configs, declared

    async def _resolve_overlay(self) -> DeclaredModels:
        """Resolve ``direct=`` once, however many callers arrive together: the callers
        that find the resolution dropped are requests, and unserialized each would
        parse — or where nothing is writable, fetch — the catalog for itself."""
        if self._declared is not None:
            return self._declared
        async with self._declared_lock:
            if self._declared is None and self._overlay is not None:
                declared = await self._overlay()
                # The same bootstrap `sync` gives the stored model_list, without which a
                # declared model is dead wherever secrets are not the environment.
                await self.seed_secrets(declared.configs)
                self._declared = declared
            return self._declared if self._declared is not None else DeclaredModels()

    async def rebuild(self, known: frozenset[str] | None = None) -> None:
        """Re-read the registry and re-derive everything learned: pool membership, the
        admin disabled map, and quality from the journal tail. ``known`` is the one
        listing of the secrets store the broker made just before, or ``None``."""
        # A caller's ref is the shared one behind its scope prefix; a ref itself never
        # carries a separator, so the last segment is the ref whatever the scope holds.
        self._scoped_refs = (
            frozenset(r.rsplit("/", 1)[-1] for r in known if "/" in r)
            if known is not None
            else frozenset()
        )
        stored, declared = await self.entries()
        self._empty = not stored and not declared
        await self._reconcile(stored, declared)
        await self._resync_disabled()
        if self._relearn is not None:
            await self._relearn()

    def check_not_empty(self) -> None:
        """Raise when the last rebuild found nothing at all. Only provisioning asks:
        a running pool that empties keeps serving nothing rather than failing a call."""
        if self._empty:
            raise EmptyRegistryError(_EMPTY_NOT_FILLED if self._autofill else _EMPTY_NO_AUTOFILL)

    async def _resync_disabled(self) -> None:
        if not isinstance(self._store, DisabledMapProtocol):
            return
        await self._store.seed_disabled(list(self._pool.configs))
        for name, flag in (await self._store.disabled_map()).items():
            if flag:
                self._pool.set_disabled(name)
            else:
                await self._pool.clear_disabled(name)

    async def _reconcile(self, stored: list[LLMConfig], declared: list[LLMConfig]) -> None:
        """Reconcile the pool with what the registry holds. A declared model is reached
        by name through ``broker.direct`` and never joins it."""
        names = {c.name for c in stored}
        for name in list(self._pool.configs):
            if name not in names:
                await self._pool.drop(name)
        for order, cfg in enumerate(stored):
            await self._pool.add(cfg, order=order)
        await self._measure(stored, declared)
        self._report_health()
        self._report_missing_keys()

    def _held(self, ref: str, shared: frozenset[str]) -> bool:
        """Whether this installation holds a key for ``ref`` at all — the shared value,
        or one belonging to a single caller. Which caller is not the measure's
        business; that a key is here, is."""
        return ref in shared or ref in self._scoped_refs

    async def _measure(self, managed: list[LLMConfig], direct: list[LLMConfig]) -> None:
        shared = await self._ring.payable(c.api_key_ref for c in managed)
        usable: set[str] = set()
        missing: dict[str, list[str]] = {}
        total: set[str] = set()
        for cfg in managed:
            if not cfg.api_key_ref:
                continue
            total.add(cfg.api_key_ref)
            if self._held(cfg.api_key_ref, shared):
                usable.add(cfg.api_key_ref)
            else:
                missing.setdefault(cfg.api_key_ref, []).append(cfg.name)
        self._payable = frozenset(usable)
        held_back = {ref: names for ref, names in missing.items() if ref not in usable}
        direct_held = await self._direct_without_keys(direct)
        # Help text is read only for a ref that is missing, so a fully-keyed
        # installation — the common case — costs no registry read at all.
        if (held_back or direct_held) and isinstance(self._registry, KeyInfoProtocol):
            self._key_info = await self._registry.key_info()
        self._health = PoolHealth(
            providers_usable=len(usable),
            providers_total=len(total),
            missing_keys=self._pending(held_back),
        )
        self._direct_missing_keys = self._pending(direct_held)

    async def _direct_without_keys(self, direct: list[LLMConfig]) -> dict[str, list[str]]:
        """Which refs the host's own entries want and cannot resolve, named by the
        handle ``direct()`` takes — the alias where there is one, since a resolved
        ``name`` carries a version the caller never typed."""
        missing: dict[str, list[str]] = {}
        for cfg in direct:
            if not cfg.api_key_ref:
                continue
            shared = await self._ring.resolve(cfg.api_key_ref) is not None
            if not self._held(cfg.api_key_ref, frozenset({cfg.api_key_ref} if shared else ())):
                missing.setdefault(cfg.api_key_ref, []).append(cfg.alias or cfg.name)
        return missing

    def _pending(self, refs: dict[str, list[str]]) -> tuple[PendingKey, ...]:
        return tuple(
            PendingKey(api_key_ref=ref, help=self.key_help(ref), entry_names=tuple(names))
            for ref, names in refs.items()
        )

    def _report_health(self) -> None:
        """One line per transition only: a healthy log carries none of these, and a
        broken one carries exactly one per change."""
        health = self._health
        # Keyed on the state, not on the log level: 0 and 1 usable providers are
        # both ERROR but different states, and the 1 -> 0 step is the outage.
        if health.providers_total == 0:
            state = _NO_POOL
        else:
            state = health.providers_usable if health.degraded else _HEALTHY
        previous = self._reported_state
        if state == previous:
            return
        self._reported_state = state
        refs = ", ".join(k.api_key_ref for k in health.missing_keys)
        tail = f" — no key for {refs}" if refs else ""
        if state == _NO_POOL:
            # No managed entry names a provider: there is no pool to degrade, and
            # "no provider has a key" would name a cause that is not the case.
            return
        if health.providers_usable == 0:
            logger.error("pool cannot serve any request: no provider has a key%s", tail)
        elif health.degraded:
            logger.error(
                "pool degraded, no failover left: 1 of %d providers usable%s",
                health.providers_total,
                tail,
            )
        elif previous is not None and previous >= 0:
            logger.info(
                "pool recovered: %d of %d providers usable",
                health.providers_usable,
                health.providers_total,
            )

    async def apply(self, configs: list[LLMConfig]) -> None:
        """Mirror an already-merged model list into the registry and seed its keys.

        The merge decision — what the model list should be — belongs to
        ``broker.merge``; this half only writes it.
        """
        registry = self._require_mutable_registry()
        await registry.mirror(configs)
        await self.seed_secrets(configs)

    def _report_missing_keys(self) -> None:
        """One line per ref the first time it turns up missing, carrying where to get
        it. Deduplicated on the set, not on a clock: a reconcile runs on every minute
        of activity and a key that stays missing must not fill the log."""
        pending = (*self._health.missing_keys, *self._direct_missing_keys)
        pooled_refs = {k.api_key_ref for k in self._health.missing_keys}
        for key in pending:
            if key.api_key_ref in self._reported_missing:
                continue
            self._reported_missing.add(key.api_key_ref)
            tail = f" — {key.help}" if key.help else ""
            names = ", ".join(key.entry_names)
            if key.api_key_ref in pooled_refs:
                logger.info(
                    "api_key_ref %r not resolved — %s inactive until the env var / secret"
                    " is set; this is normal, the pool routes over whatever keys are"
                    " present%s",
                    key.api_key_ref,
                    names,
                    tail,
                )
            else:
                logger.info(
                    "api_key_ref %r not resolved — %s is reached by name only, so"
                    " direct() on it fails until the env var / secret is set%s",
                    key.api_key_ref,
                    names,
                    tail,
                )
        self._reported_missing &= {k.api_key_ref for k in pending}

    async def present_refs(self, refs: Iterable[str]) -> frozenset[str]:
        """Which of ``refs`` a key resolves for here — what a sync report describes,
        never what it decides on."""
        return await self._ring.payable(refs)

    async def seed_secrets(self, configs: Sequence[LLMConfig]) -> None:
        """Copy any env-resolvable keys into a mutable secrets backend, preserving existing."""
        if not isinstance(self._secrets, MutableSecretsProtocol):
            return
        bootstrap = Secrets()
        for cfg in configs:
            if await resolve_ref(self._secrets, cfg.api_key_ref) is not None:
                continue  # already resolvable — preserve
            value = await resolve_ref(bootstrap, cfg.api_key_ref)
            if value is not None:
                await self._secrets.set(cfg.api_key_ref, value)

    def _require_mutable_registry(self) -> MutableRegistryProtocol:
        if not isinstance(self._registry, MutableRegistryProtocol):
            raise TypeError(
                f"{type(self._registry).__name__} does not support mutations"
                " (sync requires a mutable registry such as llmbroker.sqlite.Registry)",
            )
        return self._registry
direct_missing_keys property

Refs the host's own direct-reachable entries want and cannot resolve.

health property

The pool-wide counts from the last reconcile — the same numbers the degradation alarm uses, so log and admin UI cannot diverge.

payable property

The refs this installation holds a key for as of the last rebuild, a caller's own included — what an admin read reports and the alarm counts.

apply(configs) async

Mirror an already-merged model list into the registry and seed its keys.

The merge decision — what the model list should be — belongs to broker.merge; this half only writes it.

Source code in src/llmbroker/broker/catalog.py
352
353
354
355
356
357
358
359
360
async def apply(self, configs: list[LLMConfig]) -> None:
    """Mirror an already-merged model list into the registry and seed its keys.

    The merge decision — what the model list should be — belongs to
    ``broker.merge``; this half only writes it.
    """
    registry = self._require_mutable_registry()
    await registry.mirror(configs)
    await self.seed_secrets(configs)
check_not_empty()

Raise when the last rebuild found nothing at all. Only provisioning asks: a running pool that empties keeps serving nothing rather than failing a call.

Source code in src/llmbroker/broker/catalog.py
236
237
238
239
240
def check_not_empty(self) -> None:
    """Raise when the last rebuild found nothing at all. Only provisioning asks:
    a running pool that empties keeps serving nothing rather than failing a call."""
    if self._empty:
        raise EmptyRegistryError(_EMPTY_NOT_FILLED if self._autofill else _EMPTY_NO_AUTOFILL)
entries() async

The stored pool and the models declared in code, apart. This read is not the alias clock — direct() comes through here on every call and must not pay a catalog parse.

Source code in src/llmbroker/broker/catalog.py
192
193
194
195
196
197
198
199
200
201
async def entries(self) -> tuple[list[LLMConfig], list[LLMConfig]]:
    """The stored pool and the models declared in code, apart. This read is not the
    alias clock — ``direct()`` comes through here on every call and must not pay a
    catalog parse."""
    configs = await self._registry.load()
    if self._overlay is None:
        return configs, []
    declared = list((await self._resolve_overlay()).configs)
    check_overlay(configs, declared)
    return configs, declared
invalidate_declared()

Drop the resolved overlay so the next read follows the catalog again.

Source code in src/llmbroker/broker/catalog.py
188
189
190
def invalidate_declared(self) -> None:
    """Drop the resolved overlay so the next read follows the catalog again."""
    self._declared = None
key_help(ref)

Where this ref's key comes from: the registry's own [keys] first — a host that wrote its own hint means it — then the paid catalog's.

Source code in src/llmbroker/broker/catalog.py
180
181
182
183
184
185
186
def key_help(self, ref: str) -> str:
    """Where this ref's key comes from: the registry's own ``[keys]`` first —
    a host that wrote its own hint means it — then the paid catalog's."""
    stored = self._key_info[ref].help if ref in self._key_info else ""
    if stored:
        return stored
    return self._declared.key_help.get(ref, "") if self._declared is not None else ""
present_refs(refs) async

Which of refs a key resolves for here — what a sync report describes, never what it decides on.

Source code in src/llmbroker/broker/catalog.py
393
394
395
396
async def present_refs(self, refs: Iterable[str]) -> frozenset[str]:
    """Which of ``refs`` a key resolves for here — what a sync report describes,
    never what it decides on."""
    return await self._ring.payable(refs)
rebuild(known=None) async

Re-read the registry and re-derive everything learned: pool membership, the admin disabled map, and quality from the journal tail. known is the one listing of the secrets store the broker made just before, or None.

Source code in src/llmbroker/broker/catalog.py
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
async def rebuild(self, known: frozenset[str] | None = None) -> None:
    """Re-read the registry and re-derive everything learned: pool membership, the
    admin disabled map, and quality from the journal tail. ``known`` is the one
    listing of the secrets store the broker made just before, or ``None``."""
    # A caller's ref is the shared one behind its scope prefix; a ref itself never
    # carries a separator, so the last segment is the ref whatever the scope holds.
    self._scoped_refs = (
        frozenset(r.rsplit("/", 1)[-1] for r in known if "/" in r)
        if known is not None
        else frozenset()
    )
    stored, declared = await self.entries()
    self._empty = not stored and not declared
    await self._reconcile(stored, declared)
    await self._resync_disabled()
    if self._relearn is not None:
        await self._relearn()
seed_secrets(configs) async

Copy any env-resolvable keys into a mutable secrets backend, preserving existing.

Source code in src/llmbroker/broker/catalog.py
398
399
400
401
402
403
404
405
406
407
408
async def seed_secrets(self, configs: Sequence[LLMConfig]) -> None:
    """Copy any env-resolvable keys into a mutable secrets backend, preserving existing."""
    if not isinstance(self._secrets, MutableSecretsProtocol):
        return
    bootstrap = Secrets()
    for cfg in configs:
        if await resolve_ref(self._secrets, cfg.api_key_ref) is not None:
            continue  # already resolvable — preserve
        value = await resolve_ref(bootstrap, cfg.api_key_ref)
        if value is not None:
            await self._secrets.set(cfg.api_key_ref, value)
check_overlay(stored, declared)

A model declared in code must not claim a handle the registry already uses.

Two sources for one name is the case a registry's own uniqueness rules cannot see, so it is named here — with both sides, since the only fix is to drop one.

Source code in src/llmbroker/broker/catalog.py
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
def check_overlay(stored: list[LLMConfig], declared: list[LLMConfig]) -> None:
    """A model declared in code must not claim a handle the registry already uses.

    Two sources for one name is the case a registry's own uniqueness rules cannot
    see, so it is named here — with both sides, since the only fix is to drop one.
    """
    names = {c.name for c in stored}
    aliases = {c.alias for c in stored if c.alias is not None}
    seen_names: set[str] = set()
    seen_aliases: set[str] = set()
    for cfg in declared:
        # The alias first: it is the word the caller actually typed, while a name
        # resolved from it carries a model version they never saw.
        if cfg.alias is not None and cfg.alias in seen_aliases:
            raise ValueError(
                f"direct= declares the alias {cfg.alias!r} twice — an alias names"
                " exactly one entry",
            )
        if cfg.name in seen_names:
            raise ValueError(
                f"direct= declares {cfg.name!r} twice — drop one of the two entries",
            )
        if cfg.name in names:
            raise ValueError(
                f"direct= declares {cfg.name!r}, and the registry already carries an"
                " entry of that name — drop one of the two declarations",
            )
        if cfg.alias is not None:
            if cfg.alias in aliases:
                raise ValueError(
                    f"direct= declares the alias {cfg.alias!r}, and the registry already"
                    " carries an entry with it — an alias names exactly one entry",
                )
            seen_aliases.add(cfg.alias)
        seen_names.add(cfg.name)
find_declared(stored, declared, alias, name)

Resolve one model declared with direct= from exactly one of the two keyspaces.

A miss whose string exists in the other keyspace, or in the pool, says so — those are one typo and one wrong expectation apart at a call site.

Source code in src/llmbroker/broker/catalog.py
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
def find_declared(
    stored: list[LLMConfig],
    declared: list[LLMConfig],
    alias: str | None,
    name: str | None,
) -> LLMConfig:
    """Resolve one model declared with ``direct=`` from exactly one of the two keyspaces.

    A miss whose string exists in the *other* keyspace, or in the pool, says so —
    those are one typo and one wrong expectation apart at a call site.
    """
    if alias is not None:
        for cfg in declared:
            if cfg.alias == alias:
                return cfg
        if any(c.name == alias for c in declared):
            raise UnknownModelError(
                f"no declared model with alias {alias!r}; a declared model with this name"
                f" exists — call direct(name={alias!r})",
            )
        if any(c.name == alias for c in stored):
            # The pre-alias call shape: direct() took a name, and a pool name at that.
            # Sending it to direct(name=...) first would only spend an error saying so.
            raise PoolModelError(f"{alias!r} is a preset-managed pool model: {_POOL_MODEL_HINT}")
        raise UnknownModelError(f"no model was declared with alias {alias!r}")
    for cfg in declared:
        if cfg.name == name:
            return cfg
    if any(c.alias == name for c in declared):
        raise UnknownModelError(
            f"no declared model named {name!r}; a declared model with this alias exists"
            f" — call direct({name!r})",
        )
    if any(c.name == name for c in stored):
        raise PoolModelError(f"{name!r} is a preset-managed pool model: {_POOL_MODEL_HINT}")
    raise UnknownModelError(f"no model named {name!r} was declared with direct=")

curated

The curated files, readable as data by a program that has no broker yet.

What this read is not — a registry, a source of pool members, a network call — is in specs/reference/rules/direct-by-name.md.

CuratedModel dataclass

One row of the curated paid catalog: a provider's model, with the alias that keeps it current where the catalog carries one.

Source code in src/llmbroker/broker/curated.py
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
@dataclass(frozen=True, slots=True)
class CuratedModel:
    """One row of the curated paid catalog: a provider's model, with the alias that
    keeps it current where the catalog carries one."""

    provider: CuratedProvider
    model: str
    alias: str | None = None
    label: str = ""

    @property
    def name(self) -> str:
        return f"{self.provider.id}-{self.model}"

    def declare(self) -> LLMConfig:
        """A declaration pinned to this row's model id. The alias rides along as the
        catalog line it came from, and pins no less for being there — what follows an
        alias is a bare string passed to ``direct=``."""
        return LLMConfig(
            name=self.name,
            base_url=self.provider.base_url,
            model=self.model,
            api_key_ref=self.provider.api_key_ref,
            alias=self.alias,
        )
declare()

A declaration pinned to this row's model id. The alias rides along as the catalog line it came from, and pins no less for being there — what follows an alias is a bare string passed to direct=.

Source code in src/llmbroker/broker/curated.py
53
54
55
56
57
58
59
60
61
62
63
def declare(self) -> LLMConfig:
    """A declaration pinned to this row's model id. The alias rides along as the
    catalog line it came from, and pins no less for being there — what follows an
    alias is a bare string passed to ``direct=``."""
    return LLMConfig(
        name=self.name,
        base_url=self.provider.base_url,
        model=self.model,
        api_key_ref=self.provider.api_key_ref,
        alias=self.alias,
    )
CuratedProvider dataclass

One paid provider of the curated catalog: where its endpoint is and which key reference pays for it.

Source code in src/llmbroker/broker/curated.py
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
@dataclass(frozen=True, slots=True)
class CuratedProvider:
    """One paid provider of the curated catalog: where its endpoint is and which key
    reference pays for it."""

    id: str
    base_url: str
    api_key_ref: str
    key_help: str = ""
    label: str = ""

    def declare(self, model: str) -> LLMConfig:
        """A declaration for any model id this provider serves, curated or not — the
        config ``direct=`` takes, pinned to that id and following no alias."""
        return LLMConfig(
            name=f"{self.id}-{model}",
            base_url=self.base_url,
            model=model,
            api_key_ref=self.api_key_ref,
        )
declare(model)

A declaration for any model id this provider serves, curated or not — the config direct= takes, pinned to that id and following no alias.

Source code in src/llmbroker/broker/curated.py
28
29
30
31
32
33
34
35
36
def declare(self, model: str) -> LLMConfig:
    """A declaration for any model id this provider serves, curated or not — the
    config ``direct=`` takes, pinned to that id and following no alias."""
    return LLMConfig(
        name=f"{self.id}-{model}",
        base_url=self.base_url,
        model=model,
        api_key_ref=self.api_key_ref,
    )
curated_paid(*, home=None)

The paid models llmbroker curates, one row per tier.

Source code in src/llmbroker/broker/curated.py
117
118
119
def curated_paid(*, home: str | Path | None = None) -> tuple[CuratedModel, ...]:
    """The paid models llmbroker curates, one row per tier."""
    return models_from(tomllib.loads(_catalog_text(PAID_CATALOG, home)))
curated_pool(*, home=None)

The curated free model list, as a sync would read it.

Source code in src/llmbroker/broker/curated.py
122
123
124
def curated_pool(*, home: str | Path | None = None) -> ModelList:
    """The curated free model list, as a sync would read it."""
    return parse_model_list(tomllib.loads(_catalog_text(POOL_PRESET, home)))
curated_providers(*, home=None)

The paid providers llmbroker curates — base url and key ref for each, so a declaration for a model the catalog does not carry can be built.

Source code in src/llmbroker/broker/curated.py
111
112
113
114
def curated_providers(*, home: str | Path | None = None) -> tuple[CuratedProvider, ...]:
    """The paid providers llmbroker curates — base url and key ref for each, so a
    declaration for a model the catalog does not carry can be built."""
    return providers_from(tomllib.loads(_catalog_text(PAID_CATALOG, home)))
models_from(catalog)

Every model row of the parsed paid catalog, in file order. A row with no model id is not one; a provider missing a field yields its rows with that field empty.

Source code in src/llmbroker/broker/curated.py
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
def models_from(catalog: dict) -> tuple[CuratedModel, ...]:
    """Every model row of the parsed paid catalog, in file order. A row with no model
    id is not one; a provider missing a field yields its rows with that field empty."""
    models: list[CuratedModel] = []
    for entry in catalog.get("provider", []):
        if not isinstance(entry, dict):
            continue
        provider = _provider_from(entry)
        for row in entry.get("models", []):
            if not isinstance(row, dict) or not row.get("model"):
                continue
            models.append(
                CuratedModel(
                    provider=provider,
                    model=str(row["model"]),
                    alias=str(row["alias"]) if row.get("alias") else None,
                    label=str(row.get("label") or ""),
                ),
            )
    return tuple(models)
providers_from(catalog)

Every provider the parsed paid catalog declares, in file order.

Source code in src/llmbroker/broker/curated.py
76
77
78
79
80
def providers_from(catalog: dict) -> tuple[CuratedProvider, ...]:
    """Every provider the parsed paid catalog declares, in file order."""
    return tuple(
        _provider_from(entry) for entry in catalog.get("provider", []) if isinstance(entry, dict)
    )

keyring

The keys one caller may pay with: resolved values for its own scope, over the installation's shared ring. See rules/backends.md for what a scope reaches.

KeyRing

The keys of one scope, read once and held until the pool is rebuilt. A scoped ring answers from its own values first and falls back to the shared ring, which is where a shared key is read and held so every caller shares the one read.

Source code in src/llmbroker/broker/keyring.py
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
class KeyRing:
    """The keys of one scope, read once and held until the pool is rebuilt. A scoped
    ring answers from its own values first and falls back to the shared ring, which is
    where a shared key is read and held so every caller shares the one read."""

    def __init__(
        self,
        secrets: SecretsProtocol,
        *,
        scope: str | None = None,
        shared: "KeyRing | None" = None,
        known: frozenset[str] | None = None,
    ) -> None:
        self._secrets = secrets
        self._scope = scope
        self._shared = shared
        self._values: dict[str, str] = {}
        self._missing: set[str] = set()
        self._rejected: dict[str, str] = {}
        # A ring built between rebuilds inherits the last listing rather than starting
        # blind: without it a first-time caller pays a read per ref nobody holds.
        self._known = known

    @property
    def scope(self) -> str | None:
        """Whose keys these are, and what this caller's journal rows are attributed to."""
        return self._scope

    async def resolve(self, ref: str) -> str | None:
        """The key behind ``ref`` for this caller, or ``None`` where none is set or the
        provider has rejected the one this ring held."""
        if ref in self._rejected:
            return None
        if ref in self._values:
            return self._values[ref]
        if ref and ref not in self._missing:
            own = await self._read(self._own_ref(ref))
            if own is not None:
                self._values[ref] = own
                return own
            self._missing.add(ref)
        return await self._shared.resolve(ref) if self._shared is not None else None

    async def payable(self, refs: Iterable[str]) -> frozenset[str]:
        """Which of ``refs`` this caller can pay for — what the pool filters
        candidates on, and what the health measure counts."""
        payable = set()
        for ref in dict.fromkeys(refs):
            if await self.resolve(ref) is not None:
                payable.add(ref)
        return frozenset(payable)

    def forget(self, ref: str) -> None:
        """Drop a value the provider has just rejected, in the ring that handed it
        over, and remember which value it was — that is what a later rebuild compares
        against to tell a replaced key from the same dead one."""
        rejected = self._values.pop(ref, None)
        if rejected is None:
            if self._shared is not None:
                self._shared.forget(ref)
            return
        self._missing.discard(ref)
        self._rejected[ref] = rejected

    async def refresh(self, known: frozenset[str] | None) -> None:
        """Forget every value read and take the rebuild's one listing of the store. A
        rejection survives only while its value does: an admin who has replaced the
        key gets it used, and one who has not is not charged a call to find out."""
        self._values.clear()
        self._missing.clear()
        self._known = known
        self._rejected = {
            ref: value
            for ref, value in self._rejected.items()
            if await self._read(self._own_ref(ref)) == value
        }

    async def _read(self, ref: str) -> str | None:
        """One lookup, skipped entirely for a ref the listing did not name. Anything it
        raises is logged and read as unset: a key nobody can fetch may not fail a call."""
        if not ref or (self._known is not None and ref not in self._known):
            return None
        try:
            return await resolve_ref(self._secrets, ref)
        except Exception:  # noqa: BLE001 - an unreadable key may not fail a call
            logger.exception("secrets backend could not read %r — treating it as unset", ref)
            return None

    def _own_ref(self, ref: str) -> str:
        return f"{self._scope}/{ref}" if self._scope is not None else ref
scope property

Whose keys these are, and what this caller's journal rows are attributed to.

forget(ref)

Drop a value the provider has just rejected, in the ring that handed it over, and remember which value it was — that is what a later rebuild compares against to tell a replaced key from the same dead one.

Source code in src/llmbroker/broker/keyring.py
89
90
91
92
93
94
95
96
97
98
99
def forget(self, ref: str) -> None:
    """Drop a value the provider has just rejected, in the ring that handed it
    over, and remember which value it was — that is what a later rebuild compares
    against to tell a replaced key from the same dead one."""
    rejected = self._values.pop(ref, None)
    if rejected is None:
        if self._shared is not None:
            self._shared.forget(ref)
        return
    self._missing.discard(ref)
    self._rejected[ref] = rejected
payable(refs) async

Which of refs this caller can pay for — what the pool filters candidates on, and what the health measure counts.

Source code in src/llmbroker/broker/keyring.py
80
81
82
83
84
85
86
87
async def payable(self, refs: Iterable[str]) -> frozenset[str]:
    """Which of ``refs`` this caller can pay for — what the pool filters
    candidates on, and what the health measure counts."""
    payable = set()
    for ref in dict.fromkeys(refs):
        if await self.resolve(ref) is not None:
            payable.add(ref)
    return frozenset(payable)
refresh(known) async

Forget every value read and take the rebuild's one listing of the store. A rejection survives only while its value does: an admin who has replaced the key gets it used, and one who has not is not charged a call to find out.

Source code in src/llmbroker/broker/keyring.py
101
102
103
104
105
106
107
108
109
110
111
112
async def refresh(self, known: frozenset[str] | None) -> None:
    """Forget every value read and take the rebuild's one listing of the store. A
    rejection survives only while its value does: an admin who has replaced the
    key gets it used, and one who has not is not charged a call to find out."""
    self._values.clear()
    self._missing.clear()
    self._known = known
    self._rejected = {
        ref: value
        for ref, value in self._rejected.items()
        if await self._read(self._own_ref(ref)) == value
    }
resolve(ref) async

The key behind ref for this caller, or None where none is set or the provider has rejected the one this ring held.

Source code in src/llmbroker/broker/keyring.py
65
66
67
68
69
70
71
72
73
74
75
76
77
78
async def resolve(self, ref: str) -> str | None:
    """The key behind ``ref`` for this caller, or ``None`` where none is set or the
    provider has rejected the one this ring held."""
    if ref in self._rejected:
        return None
    if ref in self._values:
        return self._values[ref]
    if ref and ref not in self._missing:
        own = await self._read(self._own_ref(ref))
        if own is not None:
            self._values[ref] = own
            return own
        self._missing.add(ref)
    return await self._shared.resolve(ref) if self._shared is not None else None
known_refs(secrets) async

Every ref the store holds, or None where the backend cannot list — then every ref is asked for individually. One listing answers for every caller, so this is asked once per rebuild and never per ring.

Source code in src/llmbroker/broker/keyring.py
24
25
26
27
28
29
30
31
32
33
34
async def known_refs(secrets: SecretsProtocol) -> frozenset[str] | None:
    """Every ref the store holds, or ``None`` where the backend cannot list — then
    every ref is asked for individually. One listing answers for every caller, so
    this is asked once per rebuild and never per ring."""
    if not isinstance(secrets, EnumerableSecretsProtocol):
        return None
    try:
        return await secrets.refs()
    except Exception:  # noqa: BLE001 - a listing may not fail a call
        logger.exception("secrets backend could not list its refs — asking one by one")
        return None
resolve_ref(secrets, ref) async

One backend lookup, with a blank value read as no value at all — any backend can hand one back, and a blank key would route real requests at nothing.

Source code in src/llmbroker/broker/keyring.py
12
13
14
15
16
17
18
19
20
21
async def resolve_ref(secrets: SecretsProtocol, ref: str) -> str | None:
    """One backend lookup, with a blank value read as no value at all — any backend
    can hand one back, and a blank key would route real requests at nothing."""
    if not ref:
        return None
    try:
        value = await secrets.resolve(ref)
    except KeyError:
        return None
    return value if value.strip() else None

learning

Learner: the observer of the journal stream. Drives the optimizer's bookkeeping from live events, and re-derives quality from one read of the journal tail when the pool is rebuilt.

Learner

Observes the journal stream: this process's own cooldown bookkeeping and dead-key drops, and the quality re-derivation a rebuild asks for.

Source code in src/llmbroker/broker/learning.py
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
class Learner:
    """Observes the journal stream: this process's own cooldown bookkeeping and dead-key
    drops, and the quality re-derivation a rebuild asks for."""

    def __init__(
        self,
        optimizer: Optimizer,
        store: StoreProtocol,
        pool: LLMPool,
        *,
        quality_rebuild_limit: int = TAIL_READ_LIMIT,
    ) -> None:
        self._opt = optimizer
        self._store = store
        self._pool = pool
        self._quality_rebuild_limit = quality_rebuild_limit
        self.metrics: dict[str, LLMMetrics] = {}

    def record_quality_observed(
        self,
        llm_name: str,
        operation: str | None,
        call_id: str,
        score: float,
    ) -> None:
        """Fold a rating the caller has already persisted into the live window."""
        self._opt.record_quality(llm_name, operation, call_id, score)

    async def observe(self, call: Call) -> None:
        """Apply what this process just learned, in this process. Nothing here is
        written for a peer and nothing reads a peer's (invariant 11)."""
        name = call.llm_name
        if call.status in (CallStatus.RATE_LIMITED, CallStatus.UNAVAILABLE):
            self._opt.on_rate_limited(name)
        elif call.status == CallStatus.OK:
            self._opt.on_success(name)
        elif call.status == CallStatus.ERROR:
            if call.http_status is not None and is_auth_failure(call.http_status):
                # The withdrawal itself belongs to the ring that paid: a key one
                # caller had revoked says nothing about another caller's.
                cfg = self._pool.configs.get(name)
                ref = cfg.api_key_ref if cfg else "unknown"
                logger.error(
                    "%s: API key appears dead (HTTP %s) — check api_key_ref %r",
                    name,
                    call.http_status,
                    ref,
                )
            elif call.cooldown_until is not None:
                # Only a failure that actually cooled the model feeds its streak: a
                # client-side 4xx and a budget spent mid-answer are not its fault.
                self._opt.on_rate_limited(name)

    async def relearn(self) -> None:
        """Re-derive quality, the budget bounds and the snapshot metrics from one read
        of the journal tail. The rebuild's last step — quality is the only thing the
        journal is read back for (invariant 8)."""
        if not isinstance(self._store, QueryableStoreProtocol):
            return
        rows = await self._store.calls(limit=self._quality_rebuild_limit)
        self._apply_scores_and_metrics(rows)
        window_start = datetime.now(UTC) - timedelta(seconds=BUDGET_BOUND_WINDOW_SEC)
        await self._pool.apply_budget_bounds(budget_bounds_from_calls(rows, since=window_start))

    def _apply_scores_and_metrics(self, rows: list[Call]) -> None:
        """rows are newest-first: keep the newest ``quality_window`` rated calls per
        bucket, handed over oldest-first as the window expects."""
        scores: dict[tuple[str, str | None], list[tuple[str, float]]] = {}
        for row in rows:
            if row.score is None:
                continue
            bucket = scores.setdefault((row.llm_name, row.operation), [])
            if len(bucket) < self._opt.quality_window:
                bucket.append((row.id, row.score))
        self._opt.load_scores({key: bucket[::-1] for key, bucket in scores.items()})
        self.metrics = metrics_from_calls(rows)
observe(call) async

Apply what this process just learned, in this process. Nothing here is written for a peer and nothing reads a peer's (invariant 11).

Source code in src/llmbroker/broker/learning.py
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
async def observe(self, call: Call) -> None:
    """Apply what this process just learned, in this process. Nothing here is
    written for a peer and nothing reads a peer's (invariant 11)."""
    name = call.llm_name
    if call.status in (CallStatus.RATE_LIMITED, CallStatus.UNAVAILABLE):
        self._opt.on_rate_limited(name)
    elif call.status == CallStatus.OK:
        self._opt.on_success(name)
    elif call.status == CallStatus.ERROR:
        if call.http_status is not None and is_auth_failure(call.http_status):
            # The withdrawal itself belongs to the ring that paid: a key one
            # caller had revoked says nothing about another caller's.
            cfg = self._pool.configs.get(name)
            ref = cfg.api_key_ref if cfg else "unknown"
            logger.error(
                "%s: API key appears dead (HTTP %s) — check api_key_ref %r",
                name,
                call.http_status,
                ref,
            )
        elif call.cooldown_until is not None:
            # Only a failure that actually cooled the model feeds its streak: a
            # client-side 4xx and a budget spent mid-answer are not its fault.
            self._opt.on_rate_limited(name)
record_quality_observed(llm_name, operation, call_id, score)

Fold a rating the caller has already persisted into the live window.

Source code in src/llmbroker/broker/learning.py
76
77
78
79
80
81
82
83
84
def record_quality_observed(
    self,
    llm_name: str,
    operation: str | None,
    call_id: str,
    score: float,
) -> None:
    """Fold a rating the caller has already persisted into the live window."""
    self._opt.record_quality(llm_name, operation, call_id, score)
relearn() async

Re-derive quality, the budget bounds and the snapshot metrics from one read of the journal tail. The rebuild's last step — quality is the only thing the journal is read back for (invariant 8).

Source code in src/llmbroker/broker/learning.py
111
112
113
114
115
116
117
118
119
120
async def relearn(self) -> None:
    """Re-derive quality, the budget bounds and the snapshot metrics from one read
    of the journal tail. The rebuild's last step — quality is the only thing the
    journal is read back for (invariant 8)."""
    if not isinstance(self._store, QueryableStoreProtocol):
        return
    rows = await self._store.calls(limit=self._quality_rebuild_limit)
    self._apply_scores_and_metrics(rows)
    window_start = datetime.now(UTC) - timedelta(seconds=BUDGET_BOUND_WINDOW_SEC)
    await self._pool.apply_budget_bounds(budget_bounds_from_calls(rows, since=window_start))
budget_bounds_from_calls(rows, *, since)

Per model, the largest budget it failed to answer within since since, and when. Two things retire a miss and both are needed: the model's own success, and the clock — a model never picked never succeeds, so nothing else would clear it.

Source code in src/llmbroker/broker/learning.py
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
def budget_bounds_from_calls(
    rows: list[Call],
    *,
    since: datetime,
) -> dict[str, tuple[float, datetime]]:
    """Per model, the largest budget it failed to answer within since ``since``, and
    when. Two things retire a miss and both are needed: the model's own success, and
    the clock — a model never picked never succeeds, so nothing else would clear it.
    """
    bounds: dict[str, tuple[float, datetime]] = {}
    answered: set[str] = set()
    for row in rows:  # newest-first
        if row.llm_name in answered:
            continue
        if row.status == CallStatus.OK:
            answered.add(row.llm_name)
            continue
        if row.budget_ms is None or row.ts is None or row.ts < since:
            continue
        seconds = row.budget_ms / 1000
        current = bounds.get(row.llm_name)
        if current is None or seconds > current[0]:
            bounds[row.llm_name] = (seconds, row.ts)
    return bounds
metrics_from_calls(rows)

rows newest-first: the first call row per model is its most recent.

Source code in src/llmbroker/broker/learning.py
20
21
22
23
24
25
26
27
28
29
def metrics_from_calls(rows: list[Call]) -> dict[str, LLMMetrics]:
    """rows newest-first: the first call row per model is its most recent."""
    return {
        name: LLMMetrics(
            call_count=stats.total,
            last_status=stats.last_status,
            last_at=stats.last_at,
        )
        for name, stats in stats_from_calls(rows).items()
    }

llms

AsyncLLMs: one caller's view of the installation's pool — the scope its journal rows carry and the keys it may pay with. Built by the broker only.

AsyncLLMs

Route and rate calls over the broker's one shared pool, paying with this caller's keys and writing its scope on every row it journals.

Source code in src/llmbroker/broker/llms.py
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
class AsyncLLMs:
    """Route and rate calls over the broker's one shared pool, paying with this
    caller's keys and writing its scope on every row it journals."""

    def __init__(  # noqa: PLR0913 - a caller is its ring over the installation's parts
        self,
        ring: KeyRing,
        *,
        router: Router,
        catalog: Catalog,
        pool_view: PoolView,
        store: StoreProtocol,
        learner: Learner | None,
        ensure_pool: Callable[[], Awaitable[None]],
        on_exhausted: Callable[[NoLLMAvailableError, KeyRing], Awaitable[bool]],
    ) -> None:
        self._ring = ring
        self._router = router
        self._catalog = catalog
        self._pool_view = pool_view
        self._store = store
        self._learner = learner
        self._ensure_pool = ensure_pool
        self._on_exhausted = on_exhausted

    @property
    def scope(self) -> str | None:
        """Whose calls these are — the attribution on this caller's journal rows."""
        return self._ring.scope

    # ------------------------------------------------------------------
    # Routing
    # ------------------------------------------------------------------

    async def ask(  # noqa: PLR0913 - the call knobs, one keyword each
        self,
        prompt: str,
        *,
        operation: str | None = None,
        trace_id: str | None = None,
        wait: float | None = None,
        fastest_of: int | None = None,
        parallel_recovery: bool = True,
        response_format: dict | None = None,
    ) -> AsyncResult:
        return await self.chat(
            [{"role": "user", "content": prompt}],
            operation=operation,
            trace_id=trace_id,
            wait=wait,
            fastest_of=fastest_of,
            parallel_recovery=parallel_recovery,
            response_format=response_format,
        )

    async def chat(  # noqa: PLR0913 - what to send, and the call knobs
        self,
        messages: list[dict],
        *,
        tools: list[dict] | None = None,
        operation: str | None = None,
        trace_id: str | None = None,
        wait: float | None = None,
        fastest_of: int | None = None,
        parallel_recovery: bool = True,
        response_format: dict | None = None,
    ) -> AsyncResult:
        await self._ensure_pool()
        try:
            return await self._router.chat(
                self._ring,
                messages,
                tools=tools,
                operation=operation,
                trace_id=trace_id,
                wait=wait,
                fastest_of=fastest_of,
                parallel_recovery=parallel_recovery,
                response_format=response_format,
            )
        except NoLLMAvailableError as exc:
            if not await self._on_exhausted(exc, self._ring):
                raise
        # The ports were re-read inside this request, so it may as well have the
        # answer. ``wait=0``: a second pass may not spend the budget twice.
        return await self._router.chat(
            self._ring,
            messages,
            tools=tools,
            operation=operation,
            trace_id=trace_id,
            wait=0,
            fastest_of=fastest_of,
            parallel_recovery=parallel_recovery,
            response_format=response_format,
        )

    def stream(  # noqa: PLR0913 - the call knobs, one keyword each
        self,
        prompt: str,
        *,
        operation: str | None = None,
        trace_id: str | None = None,
        wait: float | None = None,
        fastest_of: int | None = None,
        parallel_recovery: bool = True,
        response_format: dict | None = None,
    ) -> StreamHandle:
        """Route a completion over the pool as a handle yielding text deltas and naming
        what answered them. ``wait`` bounds the whole answer in provider time; past the
        first delta a death raises ``StreamInterruptedError``. Async-only."""
        receipt = CallReceipt()
        return StreamHandle(
            self._deltas(
                receipt,
                prompt,
                operation=operation,
                trace_id=trace_id,
                wait=wait,
                fastest_of=fastest_of,
                parallel_recovery=parallel_recovery,
                response_format=response_format,
            ),
            receipt,
            operation=operation,
            store=self._store,
            scope=self.scope,
            observe_quality=(
                self._learner.record_quality_observed if self._learner is not None else None
            ),
        )

    async def _deltas(  # noqa: PLR0913 - the call knobs, one keyword each
        self,
        receipt: CallReceipt,
        prompt: str,
        *,
        operation: str | None,
        trace_id: str | None,
        wait: float | None,
        fastest_of: int | None,
        parallel_recovery: bool,
        response_format: dict | None,
    ) -> AsyncGenerator[str, None]:
        await self._ensure_pool()
        messages = [{"role": "user", "content": prompt}]
        produced = False
        try:
            async with aclosing(
                self._router.stream(
                    self._ring,
                    messages,
                    receipt,
                    operation=operation,
                    trace_id=trace_id,
                    wait=wait,
                    fastest_of=fastest_of,
                    parallel_recovery=parallel_recovery,
                    response_format=response_format,
                ),
            ) as deltas:
                async for delta in deltas:
                    produced = True
                    yield delta
            return
        except NoLLMAvailableError as exc:
            # ``produced`` guards invariant 18: past the first delta the answer is
            # already partly the caller's, and a second pass could only splice.
            if produced or not await self._on_exhausted(exc, self._ring):
                raise
        async with aclosing(
            self._router.stream(
                self._ring,
                messages,
                receipt,
                operation=operation,
                trace_id=trace_id,
                wait=0,
                fastest_of=fastest_of,
                parallel_recovery=parallel_recovery,
                response_format=response_format,
            ),
        ) as deltas:
            async for delta in deltas:
                yield delta

    # ------------------------------------------------------------------
    # Direct single-model access (no pool, no failover)
    # ------------------------------------------------------------------

    async def direct(
        self,
        alias: str | None = None,
        *,
        name: str | None = None,
    ) -> AsyncDirectClient:
        """A client for exactly one model of your own — no pool, no failover.

        Takes exactly one of ``alias`` or ``name=``; raises ``PoolModelError``,
        ``UnknownModelError`` or ``MissingKeyError``. See ``docs/`` "Direct model calls".
        """
        cfg, key = await self.resolve_direct(alias, name=name)
        return AsyncDirectClient(
            base_url=cfg.base_url,
            model=cfg.model,
            api_key=key,
            client=self._http(),
        )

    async def resolve_direct(
        self,
        alias: str | None = None,
        *,
        name: str | None = None,
    ) -> tuple[LLMConfig, str]:
        """Look the entry up in its keyspace and resolve it against this caller's ring
        (shared by the synchronous façade)."""
        if (alias is None) == (name is None):
            raise ValueError(
                "direct() takes exactly one of alias (positional) or name= —"
                " they are separate keyspaces",
            )
        stored, declared = await self._catalog.entries()
        cfg = find_declared(stored, declared, alias, name)
        ref = alias if alias is not None else name
        key = await self._ring.resolve(cfg.api_key_ref)
        if key is None:
            hint = self._catalog.key_help(cfg.api_key_ref)
            raise MissingKeyError(
                f"api_key_ref {cfg.api_key_ref!r} for model {ref!r} could not be resolved"
                " — set the env var or configure a secrets backend" + (f". {hint}" if hint else ""),
            )
        return cfg, key

    def _http(self) -> httpx.AsyncClient:
        """The one client of the installation — a caller opens no connection pool of
        its own."""
        return self._router.http

    # ------------------------------------------------------------------
    # Inspection and rating
    # ------------------------------------------------------------------

    async def get(self, name: str) -> AsyncLLM:
        await self._ensure_pool()
        return self._pool_view.get(name)

    async def count(self) -> int:
        await self._ensure_pool()
        return self._pool_view.count()

    async def record_quality(
        self,
        score: float,
        *,
        call_id: str | None = None,
        trace_id: str | None = None,
    ) -> None:
        """Rate a past call, by exactly one key — one rating, on the newest answered
        call the key names within the rating window. Raises ``UnknownCallError`` when
        nothing there answered. See ``docs/`` "Quality rating"."""
        check_score(score)
        if (call_id is None) == (trace_id is None):
            raise ValueError(
                "record_quality() takes exactly one of call_id= or trace_id= —"
                " a rating names the call it rates",
            )
        row = await self._resolve_rated(call_id=call_id, trace_id=trace_id)
        await self._store.record_quality(row.id, score, scope=self.scope)
        if self._learner is not None:
            self._learner.record_quality_observed(row.llm_name, row.operation, row.id, score)

    async def _resolve_rated(self, *, call_id: str | None, trace_id: str | None) -> Call:
        """The one call a rating key names: the newest that answered, inside the window.
        A trace naming more rows than one call's attempts is the host reusing it, which
        is reported — the rating still lands on exactly one call."""
        key = f"call_id={call_id!r}" if call_id is not None else f"trace_id={trace_id!r}"
        # A call id names one row; a trace names one call's attempts.
        limit = 1 if call_id is not None else _RATING_PAGE
        rows = await self.calls(
            limit=limit,
            since=datetime.now(UTC) - _RATING_WINDOW,
            call_id=call_id,
            trace_id=trace_id,
        )
        if trace_id is not None and len(rows) == _RATING_PAGE:
            logger.warning(
                "record_quality: %s matched the %d-row read bound — a trace names one"
                " call, so the rating lands on the newest that answered under it",
                key,
                _RATING_PAGE,
            )
        answered = next((row for row in rows if row.status is CallStatus.OK), None)
        if answered is None:
            raise UnknownCallError(
                f"no answered call found for {key} within the last"
                f" {_RATING_WINDOW.days} days — it may be older than that, purged by"
                " retention, or every attempt under it failed",
            )
        return answered

    # ------------------------------------------------------------------
    # Call journal — the rows this caller's scope is on
    # ------------------------------------------------------------------

    async def calls(
        self,
        *,
        limit: int,
        since: datetime | None = None,
        operation: str | None = None,
        trace_id: str | None = None,
        call_id: str | None = None,
    ) -> list[Call]:
        """Newest-first journal tail for this caller: one row per call attempt, each
        carrying the newest score it was rated with. The unscoped caller sees all.
        Never provisions the pool."""
        check_limit(limit)
        return await self._require_queryable().calls(
            limit=limit,
            scope=self.scope,
            since=to_utc(since, "since") if since is not None else None,
            operation=operation,
            trace_id=trace_id,
            call_id=call_id,
        )

    async def stats(
        self,
        *,
        since: datetime | None = None,
        limit: int = _DEFAULT_STATS_LIMIT,
        operation: str | None = None,
    ) -> Mapping[str, LLMStats]:
        """Per-model counts of this caller's call records over a window, keyed by model
        name. ``limit`` caps rows read, not the window: totals summing to it mean the
        window may be truncated. Never provisions the pool."""
        rows = await self.calls(limit=limit, since=since, operation=operation)
        return stats_from_calls(rows)

    def _require_queryable(self) -> QueryableStoreProtocol:
        if not isinstance(self._store, QueryableStoreProtocol):
            raise TypeError(
                "this store backend is not queryable — use a queryable backend"
                " (e.g. llmbroker.sqlite.Store) for calls()",
            )
        return self._store
scope property

Whose calls these are — the attribution on this caller's journal rows.

calls(*, limit, since=None, operation=None, trace_id=None, call_id=None) async

Newest-first journal tail for this caller: one row per call attempt, each carrying the newest score it was rated with. The unscoped caller sees all. Never provisions the pool.

Source code in src/llmbroker/broker/llms.py
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
async def calls(
    self,
    *,
    limit: int,
    since: datetime | None = None,
    operation: str | None = None,
    trace_id: str | None = None,
    call_id: str | None = None,
) -> list[Call]:
    """Newest-first journal tail for this caller: one row per call attempt, each
    carrying the newest score it was rated with. The unscoped caller sees all.
    Never provisions the pool."""
    check_limit(limit)
    return await self._require_queryable().calls(
        limit=limit,
        scope=self.scope,
        since=to_utc(since, "since") if since is not None else None,
        operation=operation,
        trace_id=trace_id,
        call_id=call_id,
    )
direct(alias=None, *, name=None) async

A client for exactly one model of your own — no pool, no failover.

Takes exactly one of alias or name=; raises PoolModelError, UnknownModelError or MissingKeyError. See docs/ "Direct model calls".

Source code in src/llmbroker/broker/llms.py
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
async def direct(
    self,
    alias: str | None = None,
    *,
    name: str | None = None,
) -> AsyncDirectClient:
    """A client for exactly one model of your own — no pool, no failover.

    Takes exactly one of ``alias`` or ``name=``; raises ``PoolModelError``,
    ``UnknownModelError`` or ``MissingKeyError``. See ``docs/`` "Direct model calls".
    """
    cfg, key = await self.resolve_direct(alias, name=name)
    return AsyncDirectClient(
        base_url=cfg.base_url,
        model=cfg.model,
        api_key=key,
        client=self._http(),
    )
record_quality(score, *, call_id=None, trace_id=None) async

Rate a past call, by exactly one key — one rating, on the newest answered call the key names within the rating window. Raises UnknownCallError when nothing there answered. See docs/ "Quality rating".

Source code in src/llmbroker/broker/llms.py
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
async def record_quality(
    self,
    score: float,
    *,
    call_id: str | None = None,
    trace_id: str | None = None,
) -> None:
    """Rate a past call, by exactly one key — one rating, on the newest answered
    call the key names within the rating window. Raises ``UnknownCallError`` when
    nothing there answered. See ``docs/`` "Quality rating"."""
    check_score(score)
    if (call_id is None) == (trace_id is None):
        raise ValueError(
            "record_quality() takes exactly one of call_id= or trace_id= —"
            " a rating names the call it rates",
        )
    row = await self._resolve_rated(call_id=call_id, trace_id=trace_id)
    await self._store.record_quality(row.id, score, scope=self.scope)
    if self._learner is not None:
        self._learner.record_quality_observed(row.llm_name, row.operation, row.id, score)
resolve_direct(alias=None, *, name=None) async

Look the entry up in its keyspace and resolve it against this caller's ring (shared by the synchronous façade).

Source code in src/llmbroker/broker/llms.py
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
async def resolve_direct(
    self,
    alias: str | None = None,
    *,
    name: str | None = None,
) -> tuple[LLMConfig, str]:
    """Look the entry up in its keyspace and resolve it against this caller's ring
    (shared by the synchronous façade)."""
    if (alias is None) == (name is None):
        raise ValueError(
            "direct() takes exactly one of alias (positional) or name= —"
            " they are separate keyspaces",
        )
    stored, declared = await self._catalog.entries()
    cfg = find_declared(stored, declared, alias, name)
    ref = alias if alias is not None else name
    key = await self._ring.resolve(cfg.api_key_ref)
    if key is None:
        hint = self._catalog.key_help(cfg.api_key_ref)
        raise MissingKeyError(
            f"api_key_ref {cfg.api_key_ref!r} for model {ref!r} could not be resolved"
            " — set the env var or configure a secrets backend" + (f". {hint}" if hint else ""),
        )
    return cfg, key
stats(*, since=None, limit=_DEFAULT_STATS_LIMIT, operation=None) async

Per-model counts of this caller's call records over a window, keyed by model name. limit caps rows read, not the window: totals summing to it mean the window may be truncated. Never provisions the pool.

Source code in src/llmbroker/broker/llms.py
371
372
373
374
375
376
377
378
379
380
381
382
async def stats(
    self,
    *,
    since: datetime | None = None,
    limit: int = _DEFAULT_STATS_LIMIT,
    operation: str | None = None,
) -> Mapping[str, LLMStats]:
    """Per-model counts of this caller's call records over a window, keyed by model
    name. ``limit`` caps rows read, not the window: totals summing to it mean the
    window may be truncated. Never provisions the pool."""
    rows = await self.calls(limit=limit, since=since, operation=operation)
    return stats_from_calls(rows)
stream(prompt, *, operation=None, trace_id=None, wait=None, fastest_of=None, parallel_recovery=True, response_format=None)

Route a completion over the pool as a handle yielding text deltas and naming what answered them. wait bounds the whole answer in provider time; past the first delta a death raises StreamInterruptedError. Async-only.

Source code in src/llmbroker/broker/llms.py
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
def stream(  # noqa: PLR0913 - the call knobs, one keyword each
    self,
    prompt: str,
    *,
    operation: str | None = None,
    trace_id: str | None = None,
    wait: float | None = None,
    fastest_of: int | None = None,
    parallel_recovery: bool = True,
    response_format: dict | None = None,
) -> StreamHandle:
    """Route a completion over the pool as a handle yielding text deltas and naming
    what answered them. ``wait`` bounds the whole answer in provider time; past the
    first delta a death raises ``StreamInterruptedError``. Async-only."""
    receipt = CallReceipt()
    return StreamHandle(
        self._deltas(
            receipt,
            prompt,
            operation=operation,
            trace_id=trace_id,
            wait=wait,
            fastest_of=fastest_of,
            parallel_recovery=parallel_recovery,
            response_format=response_format,
        ),
        receipt,
        operation=operation,
        store=self._store,
        scope=self.scope,
        observe_quality=(
            self._learner.record_quality_observed if self._learner is not None else None
        ),
    )

merge

The merge: an arriving model list becomes the model list this installation follows. One decision site for a file and a database alike; see rules/model-list.md.

MergeOutcome dataclass

What the merge decided, before any writer has seen it.

Source code in src/llmbroker/broker/merge.py
29
30
31
32
33
34
@dataclass(frozen=True, slots=True)
class MergeOutcome:
    """What the merge decided, before any writer has seen it."""

    report: SyncReport
    model_list: ModelList
SyncSource dataclass

An arriving model list and the curated preset name it came from.

Source code in src/llmbroker/broker/merge.py
21
22
23
24
25
26
@dataclass(frozen=True, slots=True)
class SyncSource:
    """An arriving model list and the curated preset name it came from."""

    label: str
    model_list: ModelList
check_not_emptying(merged, current, report)

The one structural guard: never apply an empty model list over a working one.

An empty target accepts anything — that is onboarding, not a loss.

Source code in src/llmbroker/broker/merge.py
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
def check_not_emptying(
    merged: list[LLMConfig],
    current: list[LLMConfig],
    report: SyncReport,
) -> None:
    """The one structural guard: never apply an empty model list over a working one.

    An empty target accepts anything — that is onboarding, not a loss.
    """
    if merged or not current:
        return
    raise SyncRefusedError(
        f"sync {report.source}: refusing to replace {len(current)} entries with an empty"
        " model list — nothing was changed",
        report=replace(report, applied=False),
    )
load_sync_source(source, presets) async

Fetch the named curated preset and parse it into the model list to merge. The fetch is the library's one networked operation, and it runs off the event loop.

Source code in src/llmbroker/broker/merge.py
40
41
42
43
44
45
46
47
48
49
async def load_sync_source(source: str, presets: PresetSource) -> SyncSource:
    """Fetch the named curated preset and parse it into the model list to merge. The
    fetch is the library's one networked operation, and it runs off the event loop."""
    if not PRESET_NAME_RE.match(source):
        raise ValueError(
            f"unrecognized sync source {source!r} — a model list arrives as a curated preset"
            " name (e.g. 'freetier') and nothing else",
        )
    text = await asyncio.to_thread(presets.text, source)
    return SyncSource(label=source, model_list=parse_model_list(tomllib.loads(text)))
merge_model_list(src, current, *, present)

Everything above the writer: merge, and refuse an emptying.

Whoever holds the model_list, the decision is made here; only the write differs.

Source code in src/llmbroker/broker/merge.py
183
184
185
186
187
188
189
190
191
192
193
194
195
def merge_model_list(
    src: SyncSource,
    current: ModelList,
    *,
    present: frozenset[str],
) -> MergeOutcome:
    """Everything above the writer: merge, and refuse an emptying.

    Whoever holds the model_list, the decision is made here; only the write differs.
    """
    merged, report = merge_upstream(src.model_list, current, present, source=src.label)
    check_not_emptying(merged.configs, current.configs, report)
    return MergeOutcome(report=report, model_list=merged)
merge_upstream(new, current, present, *, source)

Merge an arriving model list into the current one. Pure — no I/O, no secrets.

present names the refs a key resolves for; the report describes them and no decision here reads them.

Source code in src/llmbroker/broker/merge.py
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
def merge_upstream(
    new: ModelList,
    current: ModelList,
    present: frozenset[str],
    *,
    source: str,
) -> tuple[ModelList, SyncReport]:
    """Merge an arriving model list into the current one. Pure — no I/O, no secrets.

    ``present`` names the refs a key resolves for; the report describes them and no
    decision here reads them.
    """
    arriving = [c for c in new.configs if c.from_preset]
    stored_curated = [c for c in current.configs if c.from_preset]
    owned = [c for c in current.configs if not c.from_preset]
    current_by_name = {c.name: c for c in stored_curated}

    _check_model_identity(arriving, current_by_name)

    new_names = {c.name for c in arriving}
    removed = [c for c in stored_curated if c.name not in new_names]
    arrived = [c for c in arriving if c.name not in current_by_name]

    merged = [*arriving, *owned]
    _check_name_clash(merged)

    keys = dict(new.keys)
    for cfg in owned:
        if cfg.api_key_ref and cfg.api_key_ref not in keys and cfg.api_key_ref in current.keys:
            keys[cfg.api_key_ref] = current.keys[cfg.api_key_ref]

    report = SyncReport(
        source=source,
        applied=True,
        added=tuple(c.name for c in arrived),
        updated=tuple(
            c.name for c in arriving if c.name in current_by_name and current_by_name[c.name] != c
        ),
        removed=tuple(c.name for c in removed),
        orphan_refs=_orphan_refs(removed, merged, present),
        pending_keys=_pending_keys(merged, keys, present),
        active_before=sum(1 for c in current.configs if c.api_key_ref in present),
        active_after=sum(1 for c in merged if c.api_key_ref in present),
    )
    return ModelList(configs=merged, keys=keys), report

model_list_file

The model list file: rendered wholesale from configs, written atomically.

The file is llmbroker's own — see specs/reference/rules/model-list.md.

FileSyncOutcome dataclass

The result of syncing the file: the report, whether it was rewritten, and the merged model list.

Source code in src/llmbroker/broker/model_list_file.py
 93
 94
 95
 96
 97
 98
 99
100
@dataclass(frozen=True, slots=True)
class FileSyncOutcome:
    """The result of syncing the file: the report, whether it was rewritten, and the
    merged model list."""

    report: SyncReport
    changed: bool
    configs: tuple[LLMConfig, ...] = ()
entry_block(section, entry)

One [[section]] block. The header is written here rather than left to tomli_w, which renders a short array of tables as an inline top-level key — written after a trailing [keys.*] table that parses as a member of that table.

Source code in src/llmbroker/broker/model_list_file.py
23
24
25
26
27
def entry_block(section: str, entry: dict) -> str:
    """One ``[[section]]`` block. The header is written here rather than left to
    ``tomli_w``, which renders a short array of tables as an inline top-level key —
    written after a trailing ``[keys.*]`` table that parses as a member of that table."""
    return f"[[{section}]]\n{tomli_w.dumps(entry).rstrip()}"
key_block(info)

One [keys.REF] table: the help plus whatever else the section carried, which is a documented passthrough and has to survive the round trip.

Source code in src/llmbroker/broker/model_list_file.py
43
44
45
46
47
48
49
def key_block(info: KeyInfo) -> dict:
    """One ``[keys.REF]`` table: the help plus whatever else the section carried,
    which is a documented passthrough and has to survive the round trip."""
    table: dict = {**info.extra}
    if info.help:
        table["help"] = info.help
    return table
render_model_list(model_list)

The whole file: the pool entries, then [keys].

Nothing is carried over from the previous text, so there is nothing to verify the result against.

Source code in src/llmbroker/broker/model_list_file.py
66
67
68
69
70
71
72
73
74
75
76
77
def render_model_list(model_list: ModelList) -> str:
    """The whole file: the pool entries, then ``[keys]``.

    Nothing is carried over from the previous text, so there is nothing to verify
    the result against.
    """
    parts = [_HEADER]
    parts.extend(entry_block("llms", _entry_dict(c)) for c in model_list.configs)
    table = _keys_table(model_list.configs, model_list.keys)
    if table:
        parts.append(tomli_w.dumps({"keys": table}).rstrip("\n"))
    return "\n\n".join(parts) + "\n"
sync_model_list_file(path, src, *, present)

Merge src into the model list file at path and rewrite it.

Any error leaves the file untouched.

Source code in src/llmbroker/broker/model_list_file.py
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
def sync_model_list_file(
    path: Path,
    src: SyncSource,
    *,
    present: frozenset[str],
) -> FileSyncOutcome:
    """Merge ``src`` into the model list file at ``path`` and rewrite it.

    Any error leaves the file untouched.
    """
    if path.suffix.lower() != ".toml":
        raise ValueError(f"sync target must be a .toml file, got {path}")
    outcome = merge_model_list(src, read_model_list(path), present=present)
    return FileSyncOutcome(
        report=outcome.report,
        changed=write_model_list(path, outcome.model_list),
        configs=tuple(outcome.model_list.configs),
    )
write_model_list(path, model_list)

Render and write, and say whether the file changed.

Compared on the exact bytes that would be written, so an upstream change carrying only a key hint still counts: the file's git history is the record.

Source code in src/llmbroker/broker/model_list_file.py
80
81
82
83
84
85
86
87
88
89
90
def write_model_list(path: Path, model_list: ModelList) -> bool:
    """Render and write, and say whether the file changed.

    Compared on the exact bytes that would be written, so an upstream change
    carrying only a key hint still counts: the file's git history is the record.
    """
    rendered = render_model_list(model_list)
    if path.exists() and path.read_text(encoding="utf-8") == rendered:
        return False
    write_atomic(path, rendered)
    return True

pool

LLMPool: live per-LLM slot state (config, cooldown, quality) backing routing.

The pool holds no key: which refs a caller can pay for arrives per acquisition.

LLMPool

The pool of LLM slots and their live cooldown / quality state.

Source code in src/llmbroker/broker/pool.py
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
class LLMPool:
    """The pool of LLM slots and their live cooldown / quality state."""

    def __init__(self, *, optimizer: Optimizer | None = None) -> None:
        self._slots: dict[str, _Slot] = {}
        self._cond = asyncio.Condition()
        self._next_order = 0
        self._optimizer = optimizer
        self._budget_bounds: dict[str, _Bound] = {}

    # ------------------------------------------------------------------
    # Membership / lookup
    # ------------------------------------------------------------------

    def __contains__(self, name: str) -> bool:
        return name in self._slots

    def __len__(self) -> int:
        return len(self._slots)

    @property
    def configs(self) -> dict[str, LLMConfig]:
        return {name: slot.config for name, slot in self._slots.items()}

    def config(self, name: str) -> LLMConfig:
        return self._slots[name].config

    # ------------------------------------------------------------------
    # Membership mutation
    # ------------------------------------------------------------------

    async def add(self, cfg: LLMConfig, order: int | None = None) -> None:
        """Register or refresh a config. Upserts in place, so a slot's live state
        survives; ``order`` defaults to insertion order where the caller asserts no
        curated position."""
        async with self._cond:
            resolved_order = order if order is not None else self._next_order
            self._next_order = max(self._next_order, resolved_order + 1)
            slot = self._slots.get(cfg.name)
            if slot is None:
                self._slots[cfg.name] = _Slot(config=cfg, order=resolved_order)
            else:
                slot.config = cfg
                slot.order = resolved_order
            self._cond.notify_all()

    async def drop(self, name: str) -> None:
        """Remove a slot entirely, so a later re-add under the same name starts clean."""
        async with self._cond:
            self._slots.pop(name, None)
            self._budget_bounds.pop(name, None)
            self._cond.notify_all()

    # ------------------------------------------------------------------
    # Manual disable (hard exclusion)
    # ------------------------------------------------------------------

    def set_disabled(self, name: str) -> None:
        """Withdraw the slot. An in-flight call finishes normally; the flag excludes
        the slot from every acquisition afterward."""
        slot = self._slots.get(name)
        if slot is not None:
            slot.disabled = True

    async def clear_disabled(self, name: str) -> None:
        async with self._cond:
            slot = self._slots.get(name)
            if slot is not None:
                slot.disabled = False
            self._cond.notify_all()

    def is_disabled(self, name: str) -> bool:
        slot = self._slots.get(name)
        return slot is not None and slot.disabled

    # ------------------------------------------------------------------
    # Slot acquisition
    # ------------------------------------------------------------------

    def _is_demoted(self, name: str, operation: str | None) -> bool:
        return self._optimizer is not None and self._optimizer.is_demoted(name, operation)

    def _priority(self, slot: _Slot, operation: str | None) -> float:
        """The curated weight, shrunk toward host ratings as they accumulate. Falls
        back to the raw weight with no optimizer."""
        if self._optimizer is None:
            return slot.config.weight
        return self._optimizer.quality_score(slot.config.name, operation, slot.config.weight)

    async def apply_budget_bounds(self, observed: dict[str, tuple[float, datetime]]) -> None:
        """Replace the map of recently-missed answer budgets — seconds and the instant
        observed. Wholesale like the quality windows: a rebuild derives afresh."""
        async with self._cond:
            self._budget_bounds = {
                name: _Bound(budget, at + timedelta(seconds=BUDGET_BOUND_WINDOW_SEC))
                for name, (budget, at) in observed.items()
            }

    def raise_budget_bound(self, name: str, budget: float, observed_at: datetime) -> None:
        """Record one just-observed miss, so the next caller offering no more than
        ``budget`` seconds is handed a sibling first."""
        current = self._budget_bounds.get(name)
        # A lapsed window is spent evidence: carried over, one small miss would
        # re-arm a far larger bound the window had already retired.
        previous = current.seconds if current is not None and current.until > observed_at else 0.0
        self._budget_bounds[name] = _Bound(
            max(previous, budget),
            observed_at + timedelta(seconds=BUDGET_BOUND_WINDOW_SEC),
        )

    def clear_budget_bound(self, name: str) -> None:
        self._budget_bounds.pop(name, None)

    def _over_budget(self, slot: _Slot, remaining: float | None, now: datetime) -> bool:
        """Whether this LLM has recently failed to answer within a budget as small as
        the one on offer — a reason to prefer a sibling, never to exclude it."""
        bound = self._budget_bounds.get(slot.config.name)
        if remaining is None or bound is None or bound.until <= now:
            return False
        return remaining < bound.seconds + _BUDGET_SLACK_SEC

    def demoted_operations(self, name: str) -> frozenset[str | None]:
        return (
            self._optimizer.demoted_operations(name) if self._optimizer is not None else frozenset()
        )

    def _wake_timeout(
        self,
        now: datetime,
        queue_deadline: float | None,
        candidates: list[_Slot],
    ) -> float | None:
        """Seconds until the nearest event that could make a candidate available, or
        ``None`` when nothing is scheduled (wait solely on notification)."""
        wakeups: list[float] = []
        for slot in candidates:
            cap = slot.config.parallel
            if cap is not None and slot.in_flight >= cap:
                continue
            if slot.cooldown_until is not None and slot.cooldown_until > now:
                wakeups.append((slot.cooldown_until - now).total_seconds())
        if queue_deadline is not None:
            wakeups.append(queue_deadline - time.monotonic())
        return min(wakeups) if wakeups else None

    def _exhaustion_reason(self, exclude: frozenset[str], payable: frozenset[str]) -> str:
        if exclude & self._slots.keys():
            return "excluded"
        if not self._slots:
            return "empty_pool"
        if not any(slot.config.api_key_ref in payable for slot in self._slots.values()):
            return "no_keys"
        return "all_disabled"

    def _raise_exhausted(self, exclude: frozenset[str], payable: frozenset[str]) -> None:
        reason = self._exhaustion_reason(exclude, payable)
        message = {
            "excluded": "every candidate model was excluded for this request",
            "empty_pool": "the LLM pool has no slots",
            "no_keys": (
                "no LLM has a resolved api_key_ref — set at least one env var or configure"
                " a secrets backend"
            ),
            "all_disabled": "every LLM is administratively disabled",
        }[reason]
        raise NoLLMAvailableError(message, reason=reason)

    def _candidates(self, payable: frozenset[str], exclude: frozenset[str]) -> list[_Slot]:
        return [
            s
            for s in self._slots.values()
            if s.config.api_key_ref in payable and not s.disabled and s.config.name not in exclude
        ]

    @staticmethod
    def _is_free(slot: _Slot, now: datetime) -> bool:
        """A recovery already being made is not free whatever the slot's capacity: a
        second caller would make the same unprotected call the claim exists to cover."""
        return (
            not slot.recovery_claimed
            and (slot.config.parallel is None or slot.in_flight < slot.config.parallel)
            and (slot.cooldown_until is None or slot.cooldown_until <= now)
        )

    @staticmethod
    def _earliest_return(candidates: list[_Slot], now: datetime) -> datetime | None:
        cooling = [
            s.cooldown_until
            for s in candidates
            if s.cooldown_until is not None and s.cooldown_until > now
        ]
        return min(cooling) if cooling else None

    def retry_at(
        self,
        payable: frozenset[str],
        *,
        exclude: frozenset[str] = frozenset(),
    ) -> datetime | None:
        """When a candidate comes back on its own, or ``None`` where one can serve
        right now — the same answer a queue that timed out is given, for a caller whose
        own clock ran out instead."""
        now = datetime.now(UTC)
        candidates = self._candidates(payable, exclude)
        if any(self._is_free(s, now) for s in candidates):
            return None
        return self._earliest_return(candidates, now)

    def _rank(
        self,
        slot: _Slot,
        remaining: float | None,
        now: datetime,
        operation: str | None,
    ) -> tuple[bool, bool, float, int]:
        """The one ordering key every acquisition sorts on, so a parallel call cannot
        pick differently from a sequential one."""
        return (
            self._over_budget(slot, remaining, now),
            self._is_demoted(slot.config.name, operation),
            -self._priority(slot, operation),
            slot.order,
        )

    def _reserve(  # noqa: PLR0913 - one selection: from what, how many, and against what
        self,
        candidates: list[_Slot],
        *,
        width: int,
        recovery_width: int,
        remaining: float | None,
        now: datetime,
        operation: str | None,
    ) -> list[LLMConfig]:
        """Take the best free candidates, marked in flight before the condition is
        released so no two callers reserve one slot. Where the best of them is a
        recovery attempt, ``recovery_width`` applies and an ordinary entry covers it."""
        ranked = sorted(
            (s for s in candidates if self._is_free(s, now)),
            key=lambda s: self._rank(s, remaining, now, operation),
        )
        best = ranked[:width]
        if best and best[0].recovery_due:
            best += self._cover(ranked[width:], recovery_width - width)
        for slot in best:
            slot.in_flight += 1
            slot.recovery_claimed = slot.recovery_due
        return [slot.config for slot in best]

    @staticmethod
    def _cover(rest: list[_Slot], count: int) -> list[_Slot]:
        """What runs beside a recovery attempt: an entry the pool has no open question
        about, so one recheck is not covered by another where an ordinary one is free."""
        if count <= 0:
            return []
        ordinary = [s for s in rest if not s.recovery_due]
        return (ordinary + [s for s in rest if s.recovery_due])[:count]

    async def acquire(  # noqa: PLR0913 - one attempt's whole context, all optional
        self,
        queue_deadline: float | None,
        *,
        payable: frozenset[str],
        operation: str | None = None,
        exclude: frozenset[str] = frozenset(),
        answer_deadline: float | None = None,
    ) -> LLMConfig:
        """Take a slot for one attempt. ``payable`` names the refs the calling caller
        holds a key for — a model it cannot pay for is not a candidate."""
        taken = await self.acquire_many(
            queue_deadline,
            payable=payable,
            operation=operation,
            exclude=exclude,
            answer_deadline=answer_deadline,
        )
        return taken[0]

    async def acquire_many(  # noqa: PLR0913 - one call's whole context: who, how many, how long
        self,
        queue_deadline: float | None,
        *,
        payable: frozenset[str],
        width: int = 1,
        recovery_width: int = 1,
        operation: str | None = None,
        exclude: frozenset[str] = frozenset(),
        answer_deadline: float | None = None,
    ) -> list[LLMConfig]:
        """Take up to ``width`` distinct slots for one call, waiting as ``wait`` allows
        for the first of them and taking whatever else is free by then — never fewer
        than one, and never waiting for a second."""
        async with self._cond:
            while True:
                now = datetime.now(UTC)
                # Recomputed per iteration: the longer the queue wait, the less budget is
                # left for the answer, and the stricter the choice below becomes.
                remaining = None if answer_deadline is None else answer_deadline - time.monotonic()
                candidates = self._candidates(payable, exclude)
                taken = self._reserve(
                    candidates,
                    width=width,
                    recovery_width=recovery_width,
                    remaining=remaining,
                    now=now,
                    operation=operation,
                )
                if taken:
                    return taken
                if not candidates:
                    self._raise_exhausted(exclude, payable)
                if queue_deadline is not None and time.monotonic() >= queue_deadline:
                    raise NoLLMAvailableError(
                        "no LLM slot came free within wait",
                        reason="timeout",
                        retry_at=self._earliest_return(candidates, now),
                    )
                timeout = self._wake_timeout(now, queue_deadline, candidates)
                try:
                    await asyncio.wait_for(self._cond.wait(), timeout)
                except TimeoutError:
                    # Re-check: a cooldown may have expired, or the deadline hit (next loop raises).
                    continue

    async def take_free(
        self,
        *,
        payable: frozenset[str],
        width: int,
        operation: str | None = None,
        exclude: frozenset[str] = frozenset(),
        answer_deadline: float | None = None,
    ) -> list[LLMConfig]:
        """Whatever is free this instant, up to ``width``, or nothing: it never waits
        and never raises, because the lanes it tops up are already racing."""
        async with self._cond:
            now = datetime.now(UTC)
            remaining = None if answer_deadline is None else answer_deadline - time.monotonic()
            return self._reserve(
                self._candidates(payable, exclude),
                width=width,
                recovery_width=1,
                remaining=remaining,
                now=now,
                operation=operation,
            )

    async def release(self, config: LLMConfig) -> None:
        """Hand the slot back. A missing name is legal (removed mid-flight) — no-op.
        An unsettled recovery stays due: neither a rejected request nor a lane a
        sibling answered past proves the entry is back."""
        async with self._cond:
            slot = self._slots.get(config.name)
            if slot is not None:
                slot.in_flight = max(0, slot.in_flight - 1)
                slot.recovery_claimed = False
            self._cond.notify_all()

    # ------------------------------------------------------------------
    # Cooldown / state
    # ------------------------------------------------------------------

    def clear_cooling(self, name: str) -> None:
        """The entry answered: it is available again and owes no recovery attempt."""
        slot = self._slots.get(name)
        if slot is not None:
            slot.cooldown_until = None
            slot.recovery_due = False
            slot.recovery_claimed = False

    async def cool_down(self, config: LLMConfig, delay: float) -> None:
        """Withdraw the slot for ``delay`` seconds."""
        cooldown_until = datetime.now(UTC) + timedelta(seconds=delay)
        async with self._cond:
            slot = self._slots.get(config.name)
            if slot is not None:
                slot.cooldown_until = cooldown_until
                slot.fail_count += 1
                slot.in_flight = max(0, slot.in_flight - 1)
                # A new cooldown is new negative state, so the first attempt past it
                # is a recovery again whatever the last one settled.
                slot.recovery_due = True
                slot.recovery_claimed = False
            self._cond.notify_all()
        logger.warning("LLM %s cooling for %ds", config.name, delay)

    def state(self, name: str) -> LLMState:
        slot = self._slots.get(name)
        if slot is None:
            return LLMState()
        now = datetime.now(UTC)
        if slot.cooldown_until is not None and slot.cooldown_until > now:
            return LLMState(
                phase=LifecyclePhase.COOLING,
                cooldown_until=slot.cooldown_until,
                fail_count=slot.fail_count,
            )
        return LLMState(
            phase=LifecyclePhase.AVAILABLE,
            cooldown_until=None,
            fail_count=slot.fail_count,
        )
acquire(queue_deadline, *, payable, operation=None, exclude=frozenset(), answer_deadline=None) async

Take a slot for one attempt. payable names the refs the calling caller holds a key for — a model it cannot pay for is not a candidate.

Source code in src/llmbroker/broker/pool.py
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
async def acquire(  # noqa: PLR0913 - one attempt's whole context, all optional
    self,
    queue_deadline: float | None,
    *,
    payable: frozenset[str],
    operation: str | None = None,
    exclude: frozenset[str] = frozenset(),
    answer_deadline: float | None = None,
) -> LLMConfig:
    """Take a slot for one attempt. ``payable`` names the refs the calling caller
    holds a key for — a model it cannot pay for is not a candidate."""
    taken = await self.acquire_many(
        queue_deadline,
        payable=payable,
        operation=operation,
        exclude=exclude,
        answer_deadline=answer_deadline,
    )
    return taken[0]
acquire_many(queue_deadline, *, payable, width=1, recovery_width=1, operation=None, exclude=frozenset(), answer_deadline=None) async

Take up to width distinct slots for one call, waiting as wait allows for the first of them and taking whatever else is free by then — never fewer than one, and never waiting for a second.

Source code in src/llmbroker/broker/pool.py
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
async def acquire_many(  # noqa: PLR0913 - one call's whole context: who, how many, how long
    self,
    queue_deadline: float | None,
    *,
    payable: frozenset[str],
    width: int = 1,
    recovery_width: int = 1,
    operation: str | None = None,
    exclude: frozenset[str] = frozenset(),
    answer_deadline: float | None = None,
) -> list[LLMConfig]:
    """Take up to ``width`` distinct slots for one call, waiting as ``wait`` allows
    for the first of them and taking whatever else is free by then — never fewer
    than one, and never waiting for a second."""
    async with self._cond:
        while True:
            now = datetime.now(UTC)
            # Recomputed per iteration: the longer the queue wait, the less budget is
            # left for the answer, and the stricter the choice below becomes.
            remaining = None if answer_deadline is None else answer_deadline - time.monotonic()
            candidates = self._candidates(payable, exclude)
            taken = self._reserve(
                candidates,
                width=width,
                recovery_width=recovery_width,
                remaining=remaining,
                now=now,
                operation=operation,
            )
            if taken:
                return taken
            if not candidates:
                self._raise_exhausted(exclude, payable)
            if queue_deadline is not None and time.monotonic() >= queue_deadline:
                raise NoLLMAvailableError(
                    "no LLM slot came free within wait",
                    reason="timeout",
                    retry_at=self._earliest_return(candidates, now),
                )
            timeout = self._wake_timeout(now, queue_deadline, candidates)
            try:
                await asyncio.wait_for(self._cond.wait(), timeout)
            except TimeoutError:
                # Re-check: a cooldown may have expired, or the deadline hit (next loop raises).
                continue
add(cfg, order=None) async

Register or refresh a config. Upserts in place, so a slot's live state survives; order defaults to insertion order where the caller asserts no curated position.

Source code in src/llmbroker/broker/pool.py
81
82
83
84
85
86
87
88
89
90
91
92
93
94
async def add(self, cfg: LLMConfig, order: int | None = None) -> None:
    """Register or refresh a config. Upserts in place, so a slot's live state
    survives; ``order`` defaults to insertion order where the caller asserts no
    curated position."""
    async with self._cond:
        resolved_order = order if order is not None else self._next_order
        self._next_order = max(self._next_order, resolved_order + 1)
        slot = self._slots.get(cfg.name)
        if slot is None:
            self._slots[cfg.name] = _Slot(config=cfg, order=resolved_order)
        else:
            slot.config = cfg
            slot.order = resolved_order
        self._cond.notify_all()
apply_budget_bounds(observed) async

Replace the map of recently-missed answer budgets — seconds and the instant observed. Wholesale like the quality windows: a rebuild derives afresh.

Source code in src/llmbroker/broker/pool.py
139
140
141
142
143
144
145
146
async def apply_budget_bounds(self, observed: dict[str, tuple[float, datetime]]) -> None:
    """Replace the map of recently-missed answer budgets — seconds and the instant
    observed. Wholesale like the quality windows: a rebuild derives afresh."""
    async with self._cond:
        self._budget_bounds = {
            name: _Bound(budget, at + timedelta(seconds=BUDGET_BOUND_WINDOW_SEC))
            for name, (budget, at) in observed.items()
        }
clear_cooling(name)

The entry answered: it is available again and owes no recovery attempt.

Source code in src/llmbroker/broker/pool.py
412
413
414
415
416
417
418
def clear_cooling(self, name: str) -> None:
    """The entry answered: it is available again and owes no recovery attempt."""
    slot = self._slots.get(name)
    if slot is not None:
        slot.cooldown_until = None
        slot.recovery_due = False
        slot.recovery_claimed = False
cool_down(config, delay) async

Withdraw the slot for delay seconds.

Source code in src/llmbroker/broker/pool.py
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
async def cool_down(self, config: LLMConfig, delay: float) -> None:
    """Withdraw the slot for ``delay`` seconds."""
    cooldown_until = datetime.now(UTC) + timedelta(seconds=delay)
    async with self._cond:
        slot = self._slots.get(config.name)
        if slot is not None:
            slot.cooldown_until = cooldown_until
            slot.fail_count += 1
            slot.in_flight = max(0, slot.in_flight - 1)
            # A new cooldown is new negative state, so the first attempt past it
            # is a recovery again whatever the last one settled.
            slot.recovery_due = True
            slot.recovery_claimed = False
        self._cond.notify_all()
    logger.warning("LLM %s cooling for %ds", config.name, delay)
drop(name) async

Remove a slot entirely, so a later re-add under the same name starts clean.

Source code in src/llmbroker/broker/pool.py
 96
 97
 98
 99
100
101
async def drop(self, name: str) -> None:
    """Remove a slot entirely, so a later re-add under the same name starts clean."""
    async with self._cond:
        self._slots.pop(name, None)
        self._budget_bounds.pop(name, None)
        self._cond.notify_all()
raise_budget_bound(name, budget, observed_at)

Record one just-observed miss, so the next caller offering no more than budget seconds is handed a sibling first.

Source code in src/llmbroker/broker/pool.py
148
149
150
151
152
153
154
155
156
157
158
def raise_budget_bound(self, name: str, budget: float, observed_at: datetime) -> None:
    """Record one just-observed miss, so the next caller offering no more than
    ``budget`` seconds is handed a sibling first."""
    current = self._budget_bounds.get(name)
    # A lapsed window is spent evidence: carried over, one small miss would
    # re-arm a far larger bound the window had already retired.
    previous = current.seconds if current is not None and current.until > observed_at else 0.0
    self._budget_bounds[name] = _Bound(
        max(previous, budget),
        observed_at + timedelta(seconds=BUDGET_BOUND_WINDOW_SEC),
    )
release(config) async

Hand the slot back. A missing name is legal (removed mid-flight) — no-op. An unsettled recovery stays due: neither a rejected request nor a lane a sibling answered past proves the entry is back.

Source code in src/llmbroker/broker/pool.py
397
398
399
400
401
402
403
404
405
406
async def release(self, config: LLMConfig) -> None:
    """Hand the slot back. A missing name is legal (removed mid-flight) — no-op.
    An unsettled recovery stays due: neither a rejected request nor a lane a
    sibling answered past proves the entry is back."""
    async with self._cond:
        slot = self._slots.get(config.name)
        if slot is not None:
            slot.in_flight = max(0, slot.in_flight - 1)
            slot.recovery_claimed = False
        self._cond.notify_all()
retry_at(payable, *, exclude=frozenset())

When a candidate comes back on its own, or None where one can serve right now — the same answer a queue that timed out is given, for a caller whose own clock ran out instead.

Source code in src/llmbroker/broker/pool.py
243
244
245
246
247
248
249
250
251
252
253
254
255
256
def retry_at(
    self,
    payable: frozenset[str],
    *,
    exclude: frozenset[str] = frozenset(),
) -> datetime | None:
    """When a candidate comes back on its own, or ``None`` where one can serve
    right now — the same answer a queue that timed out is given, for a caller whose
    own clock ran out instead."""
    now = datetime.now(UTC)
    candidates = self._candidates(payable, exclude)
    if any(self._is_free(s, now) for s in candidates):
        return None
    return self._earliest_return(candidates, now)
set_disabled(name)

Withdraw the slot. An in-flight call finishes normally; the flag excludes the slot from every acquisition afterward.

Source code in src/llmbroker/broker/pool.py
107
108
109
110
111
112
def set_disabled(self, name: str) -> None:
    """Withdraw the slot. An in-flight call finishes normally; the flag excludes
    the slot from every acquisition afterward."""
    slot = self._slots.get(name)
    if slot is not None:
        slot.disabled = True
take_free(*, payable, width, operation=None, exclude=frozenset(), answer_deadline=None) async

Whatever is free this instant, up to width, or nothing: it never waits and never raises, because the lanes it tops up are already racing.

Source code in src/llmbroker/broker/pool.py
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
async def take_free(
    self,
    *,
    payable: frozenset[str],
    width: int,
    operation: str | None = None,
    exclude: frozenset[str] = frozenset(),
    answer_deadline: float | None = None,
) -> list[LLMConfig]:
    """Whatever is free this instant, up to ``width``, or nothing: it never waits
    and never raises, because the lanes it tops up are already racing."""
    async with self._cond:
        now = datetime.now(UTC)
        remaining = None if answer_deadline is None else answer_deadline - time.monotonic()
        return self._reserve(
            self._candidates(payable, exclude),
            width=width,
            recovery_width=1,
            remaining=remaining,
            now=now,
            operation=operation,
        )

pool_view

PoolView: read-only views of the broker's current pool state.

PoolView

Live views over the pool: a single LLM handle, the count, a full snapshot.

has_key says whether the installation holds a key at all, a caller's own included: the snapshot describes the pool rather than one request.

Source code in src/llmbroker/broker/pool_view.py
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
class PoolView:
    """Live views over the pool: a single LLM handle, the count, a full snapshot.

    ``has_key`` says whether the installation holds a key at all, a caller's own
    included: the snapshot describes the pool rather than one request."""

    def __init__(  # noqa: PLR0913 - one read-only view over the parts it reports on
        self,
        pool: LLMPool,
        metrics_source: MetricsSource,
        health: Callable[[], PoolHealth],
        direct_missing_keys: Callable[[], tuple[PendingKey, ...]],
        payable: Callable[[], frozenset[str]],
    ) -> None:
        self._pool = pool
        self._metrics_source = metrics_source
        self._health = health
        self._direct_missing_keys = direct_missing_keys
        self._payable = payable

    def get(self, name: str) -> AsyncLLM:
        if name not in self._pool:
            raise KeyError(name)
        return AsyncLLM(name, self._pool.config(name), self._pool, self._metrics_source)

    def count(self) -> int:
        return len(self._pool)

    async def snapshot(self) -> PoolSnapshot:
        metrics_map = await self._metrics_source()
        payable = self._payable()
        result: dict[str, LLMSnapshot] = {}
        for name, cfg in self._pool.configs.items():
            result[name] = LLMSnapshot(
                config=cfg,
                disabled=self._pool.is_disabled(name),
                has_key=cfg.api_key_ref in payable,
                cooldown_until=self._pool.state(name).cooldown_until,
                demoted_operations=tuple(self._pool.demoted_operations(name)),
                metrics=metrics_map.get(name),
            )
        return PoolSnapshot(result, self._health(), self._direct_missing_keys())

presets

Where a curated model list text comes from: the network, this machine's cache, the wheel.

Every network read in the library goes through here. See specs/reference/rules/model-list.md for the precedence and why it is that way.

PresetSource dataclass

The three copies of a curated text, and one precedence over them.

cache_dir is this installation's home; None means nowhere is writable, and then only the network and the wheel are left.

Source code in src/llmbroker/broker/presets.py
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
@dataclass(frozen=True, slots=True)
class PresetSource:
    """The three copies of a curated text, and one precedence over them.

    ``cache_dir`` is this installation's home; ``None`` means nowhere is writable,
    and then only the network and the wheel are left.
    """

    cache_dir: Path | None = None

    def text(
        self,
        name: str,
        *,
        prefer_cache: bool = False,
        floor: bool = True,
        fetch: bool = True,
    ) -> str:
        """A curated text, by the precedence the caller's decision needs:
        ``prefer_cache`` for a read on the request path, ``floor=False`` to drop the
        wheel's copy, ``fetch=False`` to stay off the network. See ``rules/model-list.md``."""
        if prefer_cache or not fetch:
            cached = self._cached(name)
            if cached is not None:
                return cached
        if not fetch:
            return self._local_only(name, floor=floor)
        try:
            text = fetch_preset_text(name)
        except ValueError as exc:
            cached = self._cached(name)
            if cached is not None:
                logger.debug("preset %s: %s — falling back to the cached copy", name, exc)
                return cached
            bundled = bundled_preset_text(name) if floor else None
            if bundled is None:
                raise
            # Louder than the cached fallback: the wheel's copy is frozen at the
            # installed release, and seeds a model list that runs until the next one.
            logger.warning(
                "preset %s: %s — falling back to the bundled copy, frozen at this"
                " llmbroker release and possibly older than the curated one",
                name,
                exc,
            )
            return bundled
        self._write_cache(name, text)
        return text

    def _local_only(self, name: str, *, floor: bool) -> str:
        """What is on this machine already, for a caller that may not go to the
        network at all."""
        bundled = bundled_preset_text(name) if floor else None
        if bundled is None:
            raise ValueError(
                f"preset '{name}' is not on this machine and this installation fetches"
                " nothing on its own — run `await broker.sync()` where it is deployed",
            )
        logger.warning(
            "preset %s: nothing cached and nothing is fetched automatically — falling"
            " back to the bundled copy, frozen at this llmbroker release",
            name,
        )
        return bundled

    def refresh(self, name: str) -> None:
        """Pull a fresh copy into the cache. Raises when the fetch fails, and before
        it when there is no cache to pull into — the caller decides whether the copy
        already here is good enough."""
        if self.cache_dir is None:
            raise ValueError(
                f"no writable home directory, so a fresh '{name}' has nowhere to be"
                " kept — point $LLMBROKER_HOME (or home=) at a writable directory, or,"
                " where this was the automatic refresh, stop it with sync_interval=None"
                " and keep to the copy already in use",
            )
        self._write_cache(name, fetch_preset_text(name))

    def _path(self, name: str) -> Path | None:
        return None if self.cache_dir is None else self.cache_dir / "presets" / f"{name}.toml"

    def _cached(self, name: str) -> str | None:
        path = self._path(name)
        if path is None:
            return None
        try:
            return path.read_text(encoding="utf-8")
        except OSError:
            return None

    def _write_cache(self, name: str, text: str) -> None:
        path = self._path(name)
        if path is None:
            return
        try:
            path.parent.mkdir(parents=True, exist_ok=True)
            write_atomic(path, text)
        except OSError as exc:
            logger.debug("preset %s: cannot cache into %s (%s)", name, path, exc)
refresh(name)

Pull a fresh copy into the cache. Raises when the fetch fails, and before it when there is no cache to pull into — the caller decides whether the copy already here is good enough.

Source code in src/llmbroker/broker/presets.py
154
155
156
157
158
159
160
161
162
163
164
165
def refresh(self, name: str) -> None:
    """Pull a fresh copy into the cache. Raises when the fetch fails, and before
    it when there is no cache to pull into — the caller decides whether the copy
    already here is good enough."""
    if self.cache_dir is None:
        raise ValueError(
            f"no writable home directory, so a fresh '{name}' has nowhere to be"
            " kept — point $LLMBROKER_HOME (or home=) at a writable directory, or,"
            " where this was the automatic refresh, stop it with sync_interval=None"
            " and keep to the copy already in use",
        )
    self._write_cache(name, fetch_preset_text(name))
text(name, *, prefer_cache=False, floor=True, fetch=True)

A curated text, by the precedence the caller's decision needs: prefer_cache for a read on the request path, floor=False to drop the wheel's copy, fetch=False to stay off the network. See rules/model-list.md.

Source code in src/llmbroker/broker/presets.py
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
def text(
    self,
    name: str,
    *,
    prefer_cache: bool = False,
    floor: bool = True,
    fetch: bool = True,
) -> str:
    """A curated text, by the precedence the caller's decision needs:
    ``prefer_cache`` for a read on the request path, ``floor=False`` to drop the
    wheel's copy, ``fetch=False`` to stay off the network. See ``rules/model-list.md``."""
    if prefer_cache or not fetch:
        cached = self._cached(name)
        if cached is not None:
            return cached
    if not fetch:
        return self._local_only(name, floor=floor)
    try:
        text = fetch_preset_text(name)
    except ValueError as exc:
        cached = self._cached(name)
        if cached is not None:
            logger.debug("preset %s: %s — falling back to the cached copy", name, exc)
            return cached
        bundled = bundled_preset_text(name) if floor else None
        if bundled is None:
            raise
        # Louder than the cached fallback: the wheel's copy is frozen at the
        # installed release, and seeds a model list that runs until the next one.
        logger.warning(
            "preset %s: %s — falling back to the bundled copy, frozen at this"
            " llmbroker release and possibly older than the curated one",
            name,
            exc,
        )
        return bundled
    self._write_cache(name, text)
    return text
bundled_preset_text(name)

The copy shipped in the wheel — the floor under the chain, so a first run with no network and a cold cache still has a model list to start from.

Source code in src/llmbroker/broker/presets.py
79
80
81
82
83
84
85
86
def bundled_preset_text(name: str) -> str | None:
    """The copy shipped in the wheel — the floor under the chain, so a first run with
    no network and a cold cache still has a model list to start from."""
    resource = resources.files("llmbroker").joinpath("presets", f"{name}.toml")
    try:
        return resource.read_text(encoding="utf-8")
    except (OSError, ModuleNotFoundError):
        return None
fetch_preset_text(name)

Download presets/<name>.toml from the catalog.

Every failure is a ValueError carrying an admin-readable message: nothing has been touched yet, so the sync simply does not happen.

Source code in src/llmbroker/broker/presets.py
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
def fetch_preset_text(name: str) -> str:
    """Download ``presets/<name>.toml`` from the catalog.

    Every failure is a ``ValueError`` carrying an admin-readable message: nothing
    has been touched yet, so the sync simply does not happen.
    """
    if not PRESET_NAME_RE.match(name):
        raise ValueError(
            f"invalid preset name '{name}' (use letters, digits, hyphens, underscores)",
        )
    url = _PRESET_URL.format(name=name)
    # Read and decode share the connect phase's try: a body dying mid-stream raises
    # TimeoutError/IncompleteRead, and best-effort callers catch ValueError only.
    try:
        with urllib.request.urlopen(url, timeout=_FETCH_TIMEOUT) as resp:  # noqa: S310 - validated
            text = resp.read().decode()
    except urllib.error.HTTPError as exc:
        if exc.code == HTTPStatus.NOT_FOUND:
            raise ValueError(f"preset '{name}' not found in catalog") from exc
        raise ValueError(f"HTTP {exc.code} fetching {url}") from exc
    except urllib.error.URLError as exc:
        raise ValueError(str(exc.reason)) from exc
    except UnicodeDecodeError as exc:
        raise ValueError(f"downloaded content for '{name}' is not valid UTF-8") from exc
    except (OSError, HTTPException) as exc:
        raise ValueError(f"failed reading {url}: {exc!r}") from exc
    try:
        data = tomllib.loads(text)
    except tomllib.TOMLDecodeError as exc:
        raise ValueError(f"downloaded content for '{name}' is not valid TOML") from exc
    _check_fetched_urls(name, data)
    return text

refresher

Keeping the stored model list following the curated one: its own clock, task, check record and failure policy. Rules in specs/reference/rules/model-list.md.

ModelListRefresher

Merges a model list into the registry, and decides when to go looking for one. source is the preset followed, None for none; interval is None where nothing is fetched on its own; live says whether a pool is running.

Source code in src/llmbroker/broker/refresher.py
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
class ModelListRefresher:
    """Merges a model list into the registry, and decides when to go looking for one.
    ``source`` is the preset followed, ``None`` for none; ``interval`` is ``None``
    where nothing is fetched on its own; ``live`` says whether a pool is running."""

    def __init__(  # noqa: PLR0913 - it assembles a subsystem: the ports plus its clock
        self,
        registry: RegistryProtocol,
        catalog: Catalog,
        store: StoreProtocol,
        presets: PresetSource,
        *,
        source: str | None,
        interval: float | None,
        home: Path | None,
        declared: Sequence[str | LLMConfig] = (),
        target_label: str | None = None,
        live: Callable[[], bool] = lambda: False,
        rebuild: Callable[[], Awaitable[None]] | None = None,
    ) -> None:
        self._registry = registry
        self._catalog = catalog
        self._store = store
        self._presets = presets
        self._source = source
        self._interval = interval
        self._home = home
        self._declared = tuple(declared)
        self._target_label = target_label
        self._live = live
        self._rebuild = rebuild

        self._attempted = False
        # Monotonic deadline for the next check; inf until the first one lands.
        self._next_refresh = float("inf")
        self._task: asyncio.Task[None] | None = None
        self.last_report: SyncReport | None = None

    # ------------------------------------------------------------------
    # The two gates
    # ------------------------------------------------------------------

    async def before_provision(self) -> None:
        """Decide what the model list needs before the pool is provisioned: fill an empty
        registry blocking, otherwise arm the clock and refresh off the request path.
        An installation syncing nothing still arms it for the paid catalog."""
        if self._attempted:
            return
        self._attempted = True
        if self._interval is None:
            # No clock at all: the empty registry is not filled either — an
            # installation that fetches nothing gets the error, not a fetch.
            return
        if self._source is None:
            if self._follows_an_alias():
                self._arm(PAID_CATALOG)
            return
        if not await self._registry.load():
            await self._attempt("start")
            return
        self._arm(self._source)

    def schedule(self) -> None:
        """Fire a background refresh when the interval has elapsed. Synchronous by
        design: the hot path pays one monotonic comparison and nothing else."""
        interval = self._interval
        if interval is None:
            return
        if time.monotonic() < self._next_refresh:
            return
        if self._task is not None and not self._task.done():
            return
        # The deadline moves before the task exists, so a burst of concurrent calls
        # schedules one refresh rather than one per call.
        self._next_refresh = time.monotonic() + interval
        self._task = asyncio.create_task(self._attempt("refresh"))

    def _arm(self, source: str) -> None:
        interval = self._interval
        if interval is None:
            return
        age = stamp_age(self._home, self._stamp_key(source))
        self._next_refresh = (
            time.monotonic() + (interval - age) if age is not None and age < interval else 0.0
        )

    async def aclose(self) -> None:
        """Cancel a refresh in flight. The caller closes it before the ports: a
        refresh still running would otherwise write through a registry whose driver
        is closing."""
        if self._task is not None and not self._task.done():
            self._task.cancel()
            with contextlib.suppress(asyncio.CancelledError):
                await self._task

    # ------------------------------------------------------------------
    # The background path: best-effort, never raising
    # ------------------------------------------------------------------

    async def _attempt(self, reason: str) -> None:
        """Best-effort by construction: neither a start nor a request may fail over a
        model list refresh. The explicit ``sync()`` raises instead."""
        source = self._source
        try:
            if source is None:
                await self._refresh_paid_catalog()
            else:
                await self.sync(source)
        except SyncRefusedError as exc:
            self.last_report = exc.report
            logger.warning("sync %s refused, continuing on the current config: %s", reason, exc)
        except (ValueError, OSError) as exc:
            # What a refresh fails with in normal operation — offline, a throttled
            # CDN, a malformed body, an unwritable target. No traceback to keep.
            logger.warning("sync %s failed, continuing on the current config: %s", reason, exc)
        # A bug here still may not stop the refresh, and a one-line warning off a
        # broad catch is how a bug becomes unreportable — so: traceback.
        except Exception:  # noqa: BLE001 - a refresh may not fail the process
            logger.exception(
                "sync %s failed unexpectedly, continuing on the current config",
                reason,
            )
        finally:
            if self._interval is not None:
                self._next_refresh = time.monotonic() + self._interval
        # The clock is a rebuild trigger in its own right, whatever the merge decided:
        # it is what re-reads a peer's registry edit and re-derives quality daily.
        try:
            await self._rebuild_pool()
        # Inside this task nothing can retrieve an exception, so a re-read that fails
        # has to name the port here or vanish; the pool stays as it is either way.
        except Exception:  # noqa: BLE001 - a detached refresh may not raise
            logger.exception(
                "pool rebuild on the %s check failed, continuing on the current pool",
                reason,
            )

    async def _refresh_paid_catalog(self, *, stamp: bool = True) -> None:
        """Keep the cached paid catalog current on the refresh clock — the only clock
        a declared alias has, and what moves a declared model onto a new version."""
        if not self._follows_an_alias():
            return
        await asyncio.to_thread(self._presets.refresh, PAID_CATALOG)
        if stamp:
            write_stamp(self._home, self._stamp_key(PAID_CATALOG))
        self._catalog.invalidate_declared()

    def _follows_an_alias(self) -> bool:
        return any(isinstance(item, str) for item in self._declared)

    # ------------------------------------------------------------------
    # The check record
    # ------------------------------------------------------------------

    def _stamp_key(self, source: str) -> str:
        """What was checked, and for whom. Keyed by both because two projects on one
        machine have two model lists to keep current, and one project's check must not
        gate the other's."""
        return f"{source} {self._target_identity()}"

    def _target_identity(self) -> str:
        if isinstance(self._registry, Registry):
            return str(self._registry.path.resolve())
        if self._target_label is not None:
            return self._target_label
        # No persistent identity of its own: the home directory is the identity.
        return str(self._home)

    # ------------------------------------------------------------------
    # The explicit sync
    # ------------------------------------------------------------------

    async def sync(self, source: str | None = None) -> SyncReport | None:
        """Merge a model list into the registry and return what it did. With no
        argument: what this installation follows — its preset, or, where it follows
        none, the paid catalog alone, which merges nothing and reports nothing. Raises."""
        target = source if source is not None else self._source
        if target is None:
            await self._refresh_paid_catalog()
            return None
        src = await load_sync_source(target, self._presets)
        current = await self._registry.load()
        # The clock a declared alias rides on, and a catalog nobody can reach may
        # not fail the sync of the model list itself.
        try:
            await self._refresh_paid_catalog(stamp=False)
        except (ValueError, OSError) as exc:
            logger.warning(
                "paid catalog unavailable (%s) — declared models stay on the resolution"
                " already in use",
                exc,
            )
        present = await self._catalog.present_refs(
            [c.api_key_ref for c in (*src.model_list.configs, *current)],
        )
        if isinstance(self._registry, Registry):
            report, changed = await self._file_target(src, self._registry.path, present)
        else:
            report, changed = await self._registry_target(src, current, present)
        if changed and isinstance(self._store, DisabledMapProtocol):
            configs = await self._registry.load()
            await self._store.seed_disabled([c.name for c in configs])
        # Both land before the resync: a resync that raises must not swallow the
        # record of a change already applied.
        if changed:
            logger.info("%s", format_report(report))
        else:
            logger.debug("sync %s: no change", report.source)
        self.last_report = report
        write_stamp(self._home, self._stamp_key(target))
        await self._rebuild_pool()
        return report

    async def _rebuild_pool(self) -> None:
        """A sync is a rebuild trigger, applied or not: a key it has just bootstrapped
        is exactly what a caller is waiting for. Skipped before the pool exists —
        provisioning is its own trigger and runs next."""
        if self._rebuild is not None and self._live():
            await self._rebuild()

    async def _file_target(
        self,
        src: SyncSource,
        target: Path,
        present: frozenset[str],
    ) -> tuple[SyncReport, bool]:
        outcome = sync_model_list_file(target, src, present=present)
        # Outside the identity gate: a key that arrived in the environment is
        # bootstrapped by a sync whether or not the model list itself moved.
        await self._catalog.seed_secrets(list(outcome.configs))
        return outcome.report, outcome.changed

    async def _registry_target(
        self,
        src: SyncSource,
        stored: list[LLMConfig],
        present: frozenset[str],
    ) -> tuple[SyncReport, bool]:
        keys = (
            await self._registry.key_info() if isinstance(self._registry, KeyInfoProtocol) else {}
        )
        outcome = merge_model_list(src, ModelList(configs=stored, keys=keys), present=present)
        merged = outcome.model_list.configs
        # Against what is stored, so a catalog move reaches the registry; keyed by
        # name, since a DB hands rows back in its own order (invariant 3).
        changed = {c.name: c for c in merged} != {c.name: c for c in stored}
        if changed:
            await self._catalog.apply(merged)
        else:
            await self._catalog.seed_secrets(merged)
        return outcome.report, changed
aclose() async

Cancel a refresh in flight. The caller closes it before the ports: a refresh still running would otherwise write through a registry whose driver is closing.

Source code in src/llmbroker/broker/refresher.py
112
113
114
115
116
117
118
119
async def aclose(self) -> None:
    """Cancel a refresh in flight. The caller closes it before the ports: a
    refresh still running would otherwise write through a registry whose driver
    is closing."""
    if self._task is not None and not self._task.done():
        self._task.cancel()
        with contextlib.suppress(asyncio.CancelledError):
            await self._task
before_provision() async

Decide what the model list needs before the pool is provisioned: fill an empty registry blocking, otherwise arm the clock and refresh off the request path. An installation syncing nothing still arms it for the paid catalog.

Source code in src/llmbroker/broker/refresher.py
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
async def before_provision(self) -> None:
    """Decide what the model list needs before the pool is provisioned: fill an empty
    registry blocking, otherwise arm the clock and refresh off the request path.
    An installation syncing nothing still arms it for the paid catalog."""
    if self._attempted:
        return
    self._attempted = True
    if self._interval is None:
        # No clock at all: the empty registry is not filled either — an
        # installation that fetches nothing gets the error, not a fetch.
        return
    if self._source is None:
        if self._follows_an_alias():
            self._arm(PAID_CATALOG)
        return
    if not await self._registry.load():
        await self._attempt("start")
        return
    self._arm(self._source)
schedule()

Fire a background refresh when the interval has elapsed. Synchronous by design: the hot path pays one monotonic comparison and nothing else.

Source code in src/llmbroker/broker/refresher.py
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
def schedule(self) -> None:
    """Fire a background refresh when the interval has elapsed. Synchronous by
    design: the hot path pays one monotonic comparison and nothing else."""
    interval = self._interval
    if interval is None:
        return
    if time.monotonic() < self._next_refresh:
        return
    if self._task is not None and not self._task.done():
        return
    # The deadline moves before the task exists, so a burst of concurrent calls
    # schedules one refresh rather than one per call.
    self._next_refresh = time.monotonic() + interval
    self._task = asyncio.create_task(self._attempt("refresh"))
sync(source=None) async

Merge a model list into the registry and return what it did. With no argument: what this installation follows — its preset, or, where it follows none, the paid catalog alone, which merges nothing and reports nothing. Raises.

Source code in src/llmbroker/broker/refresher.py
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
async def sync(self, source: str | None = None) -> SyncReport | None:
    """Merge a model list into the registry and return what it did. With no
    argument: what this installation follows — its preset, or, where it follows
    none, the paid catalog alone, which merges nothing and reports nothing. Raises."""
    target = source if source is not None else self._source
    if target is None:
        await self._refresh_paid_catalog()
        return None
    src = await load_sync_source(target, self._presets)
    current = await self._registry.load()
    # The clock a declared alias rides on, and a catalog nobody can reach may
    # not fail the sync of the model list itself.
    try:
        await self._refresh_paid_catalog(stamp=False)
    except (ValueError, OSError) as exc:
        logger.warning(
            "paid catalog unavailable (%s) — declared models stay on the resolution"
            " already in use",
            exc,
        )
    present = await self._catalog.present_refs(
        [c.api_key_ref for c in (*src.model_list.configs, *current)],
    )
    if isinstance(self._registry, Registry):
        report, changed = await self._file_target(src, self._registry.path, present)
    else:
        report, changed = await self._registry_target(src, current, present)
    if changed and isinstance(self._store, DisabledMapProtocol):
        configs = await self._registry.load()
        await self._store.seed_disabled([c.name for c in configs])
    # Both land before the resync: a resync that raises must not swallow the
    # record of a change already applied.
    if changed:
        logger.info("%s", format_report(report))
    else:
        logger.debug("sync %s: no change", report.source)
    self.last_report = report
    write_stamp(self._home, self._stamp_key(target))
    await self._rebuild_pool()
    return report

report

Turning what a sync decided into lines for a human: the CLI prints them, the broker logs them.

alias_lines(facts)

One line per fact — a re-spelled key ref is the one a reader must act on, so its line says what to set.

Source code in src/llmbroker/broker/report.py
10
11
12
13
14
15
16
17
18
def alias_lines(facts: Iterable[AliasFact]) -> tuple[str, ...]:
    """One line per fact — a re-spelled key ref is the one a reader must act on, so
    its line says what to set."""
    return tuple(
        f"{fact.alias}: api_key_ref {fact.was} -> {fact.now} — set {fact.now} before the next call"
        if fact.change is AliasChange.KEY_REF
        else f"{fact.alias}: {fact.was} -> {fact.now}"
        for fact in facts
    )
format_report(report)

The whole outcome as text, printed on every run including a no-op.

Source code in src/llmbroker/broker/report.py
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
def format_report(report: SyncReport) -> str:
    """The whole outcome as text, printed on every run including a no-op."""
    verb = "applied" if report.applied else "refused"
    lines = [
        f"sync {report.source}: {verb}"
        f" — {report.active_before} -> {report.active_after} entries with a key",
    ]
    for label, names in (
        ("added", report.added),
        ("updated", report.updated),
        ("removed", report.removed),
    ):
        if names:
            lines.append(f"  {label}: {', '.join(names)}")
    for ref in report.orphan_refs:
        lines.append(
            f"  unused key {ref} — nothing here uses it any more;"
            " revoke it at the provider if you do not need it",
        )
    for pending in report.pending_keys:
        lines.append(
            f"  pending key {pending.api_key_ref} — holds back {', '.join(pending.entry_names)}",
        )
        lines.extend(f"      {line}" for line in pending.help.splitlines() if line.strip())
    if len(lines) == 1:
        lines.append("  no changes")
    return "\n".join(lines)

result

Per-call result handle and the live per-LLM view returned by the broker.

AsyncLLM

Handle returned by AsyncBroker.get(name) — live view into broker internals.

Source code in src/llmbroker/broker/result.py
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
class AsyncLLM:
    """Handle returned by ``AsyncBroker.get(name)`` — live view into broker internals."""

    def __init__(
        self,
        name: str,
        config: LLMConfig,
        pool: LLMPool,
        metrics_source: MetricsSource,
    ) -> None:
        self._name = name
        self._config = config
        self._pool = pool
        self._metrics_source = metrics_source

    @property
    def config(self) -> LLMConfig:
        return self._config

    @property
    def disabled(self) -> bool:
        return self._pool.is_disabled(self._name)

    async def state(self) -> LLMState:
        return self._pool.state(self._name)

    async def metrics(self) -> LLMMetrics:
        all_metrics = await self._metrics_source()
        return all_metrics.get(self._name, LLMMetrics(0, None, None))
AsyncResult

Bases: RoutedCall

Returned by AsyncBroker.ask()/chat() — a call that has already answered.

Source code in src/llmbroker/broker/result.py
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
class AsyncResult(RoutedCall):
    """Returned by AsyncBroker.ask()/chat() — a call that has already answered."""

    def __init__(  # noqa: PLR0913
        self,
        *,
        text: str,
        tool_calls: list[dict] | None,
        usage: Usage | None,
        call_id: str,
        llm_name: str,
        operation: str | None = None,
        store: StoreProtocol,
        scope: str | None = None,
        observe_quality: ObserveQuality | None = None,
    ) -> None:
        super().__init__(
            CallReceipt(llm_name=llm_name, call_id=call_id, usage=usage, settled=True),
            operation=operation,
            store=store,
            scope=scope,
            observe_quality=observe_quality,
        )
        self.text = text
        self.tool_calls = tool_calls

    @property
    def llm_name(self) -> str:
        """Name of the model that answered — persist it to rate the call later."""
        return cast(str, self._receipt.llm_name)

    @property
    def call_id(self) -> str:
        """Opaque id of this call; an optional passthrough for host analytics."""
        return cast(str, self._receipt.call_id)
call_id property

Opaque id of this call; an optional passthrough for host analytics.

llm_name property

Name of the model that answered — persist it to rate the call later.

CallReceipt dataclass

What one routed call has settled so far: which model answered it, under what call id, and what it spent. settled goes up only once the journal row is written — a rating must never precede the row it names.

Source code in src/llmbroker/broker/result.py
23
24
25
26
27
28
29
30
31
32
@dataclass(slots=True)
class CallReceipt:
    """What one routed call has settled so far: which model answered it, under what
    call id, and what it spent. ``settled`` goes up only once the journal row is
    written — a rating must never precede the row it names."""

    llm_name: str | None = None
    call_id: str | None = None
    usage: Usage | None = None
    settled: bool = False
RoutedCall

What every routed call hands back: the model that answered, and the rating that names it without a journal read.

Source code in src/llmbroker/broker/result.py
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
class RoutedCall:
    """What every routed call hands back: the model that answered, and the rating that
    names it without a journal read."""

    def __init__(
        self,
        receipt: CallReceipt,
        *,
        operation: str | None,
        store: StoreProtocol,
        scope: str | None,
        observe_quality: ObserveQuality | None,
    ) -> None:
        self._receipt = receipt
        self._operation = operation
        self._store = store
        self._scope = scope
        self._observe_quality = observe_quality

    @property
    def llm_name(self) -> str | None:
        """Name of the model that answered — persist it to rate the call later."""
        return self._receipt.llm_name

    @property
    def call_id(self) -> str | None:
        """Opaque id of this call; an optional passthrough for host analytics."""
        return self._receipt.call_id

    @property
    def operation(self) -> str | None:
        """Operation label passed to the call, or None."""
        return self._operation

    @property
    def usage(self) -> Usage | None:
        """Token counts the provider reported, where it reported any."""
        return self._receipt.usage

    async def record_quality(self, score: float) -> None:
        """Rate the call this came from — no journal read: the model, the operation
        and the call id are already here. Raises ``ValueError`` until the call has
        settled, so a rating can never reach the store before the row it names."""
        check_score(score)
        receipt = self._receipt
        if not receipt.settled or receipt.llm_name is None or receipt.call_id is None:
            raise ValueError(_UNRATEABLE)
        await self._store.record_quality(receipt.call_id, score, scope=self._scope)
        if self._observe_quality is not None:
            self._observe_quality(receipt.llm_name, self._operation, receipt.call_id, score)
call_id property

Opaque id of this call; an optional passthrough for host analytics.

llm_name property

Name of the model that answered — persist it to rate the call later.

operation property

Operation label passed to the call, or None.

usage property

Token counts the provider reported, where it reported any.

record_quality(score) async

Rate the call this came from — no journal read: the model, the operation and the call id are already here. Raises ValueError until the call has settled, so a rating can never reach the store before the row it names.

Source code in src/llmbroker/broker/result.py
74
75
76
77
78
79
80
81
82
83
84
async def record_quality(self, score: float) -> None:
    """Rate the call this came from — no journal read: the model, the operation
    and the call id are already here. Raises ``ValueError`` until the call has
    settled, so a rating can never reach the store before the row it names."""
    check_score(score)
    receipt = self._receipt
    if not receipt.settled or receipt.llm_name is None or receipt.call_id is None:
        raise ValueError(_UNRATEABLE)
    await self._store.record_quality(receipt.call_id, score, scope=self._scope)
    if self._observe_quality is not None:
        self._observe_quality(receipt.llm_name, self._operation, receipt.call_id, score)
StreamHandle

Bases: RoutedCall

Returned by stream(): an async iterator of text deltas that also names the model answering them, from the first delta on — or, for an answer that had none, once it ends. Closing it is the consumer's move, exactly as for the raw iterator.

Source code in src/llmbroker/broker/result.py
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
class StreamHandle(RoutedCall):
    """Returned by ``stream()``: an async iterator of text deltas that also names the
    model answering them, from the first delta on — or, for an answer that had none,
    once it ends. Closing it is the consumer's move, exactly as for the raw iterator."""

    def __init__(  # noqa: PLR0913
        self,
        deltas: AsyncGenerator[str, None],
        receipt: CallReceipt,
        *,
        operation: str | None,
        store: StoreProtocol,
        scope: str | None,
        observe_quality: ObserveQuality | None,
    ) -> None:
        super().__init__(
            receipt,
            operation=operation,
            store=store,
            scope=scope,
            observe_quality=observe_quality,
        )
        self._deltas = deltas

    def __aiter__(self) -> AsyncIterator[str]:
        return self._deltas

    async def aclose(self) -> None:
        """Close the stream, handing the model's slot back."""
        await self._deltas.aclose()
aclose() async

Close the stream, handing the model's slot back.

Source code in src/llmbroker/broker/result.py
151
152
153
async def aclose(self) -> None:
    """Close the stream, handing the model's slot back."""
    await self._deltas.aclose()

router

Router: route one completion over the pool with per-LLM failover, journaling every attempt. How each failure is disposed of is the contract in call-path.md.

Router

Routes a completion request over the pool, failing over between LLMs.

Source code in src/llmbroker/broker/router.py
 319
 320
 321
 322
 323
 324
 325
 326
 327
 328
 329
 330
 331
 332
 333
 334
 335
 336
 337
 338
 339
 340
 341
 342
 343
 344
 345
 346
 347
 348
 349
 350
 351
 352
 353
 354
 355
 356
 357
 358
 359
 360
 361
 362
 363
 364
 365
 366
 367
 368
 369
 370
 371
 372
 373
 374
 375
 376
 377
 378
 379
 380
 381
 382
 383
 384
 385
 386
 387
 388
 389
 390
 391
 392
 393
 394
 395
 396
 397
 398
 399
 400
 401
 402
 403
 404
 405
 406
 407
 408
 409
 410
 411
 412
 413
 414
 415
 416
 417
 418
 419
 420
 421
 422
 423
 424
 425
 426
 427
 428
 429
 430
 431
 432
 433
 434
 435
 436
 437
 438
 439
 440
 441
 442
 443
 444
 445
 446
 447
 448
 449
 450
 451
 452
 453
 454
 455
 456
 457
 458
 459
 460
 461
 462
 463
 464
 465
 466
 467
 468
 469
 470
 471
 472
 473
 474
 475
 476
 477
 478
 479
 480
 481
 482
 483
 484
 485
 486
 487
 488
 489
 490
 491
 492
 493
 494
 495
 496
 497
 498
 499
 500
 501
 502
 503
 504
 505
 506
 507
 508
 509
 510
 511
 512
 513
 514
 515
 516
 517
 518
 519
 520
 521
 522
 523
 524
 525
 526
 527
 528
 529
 530
 531
 532
 533
 534
 535
 536
 537
 538
 539
 540
 541
 542
 543
 544
 545
 546
 547
 548
 549
 550
 551
 552
 553
 554
 555
 556
 557
 558
 559
 560
 561
 562
 563
 564
 565
 566
 567
 568
 569
 570
 571
 572
 573
 574
 575
 576
 577
 578
 579
 580
 581
 582
 583
 584
 585
 586
 587
 588
 589
 590
 591
 592
 593
 594
 595
 596
 597
 598
 599
 600
 601
 602
 603
 604
 605
 606
 607
 608
 609
 610
 611
 612
 613
 614
 615
 616
 617
 618
 619
 620
 621
 622
 623
 624
 625
 626
 627
 628
 629
 630
 631
 632
 633
 634
 635
 636
 637
 638
 639
 640
 641
 642
 643
 644
 645
 646
 647
 648
 649
 650
 651
 652
 653
 654
 655
 656
 657
 658
 659
 660
 661
 662
 663
 664
 665
 666
 667
 668
 669
 670
 671
 672
 673
 674
 675
 676
 677
 678
 679
 680
 681
 682
 683
 684
 685
 686
 687
 688
 689
 690
 691
 692
 693
 694
 695
 696
 697
 698
 699
 700
 701
 702
 703
 704
 705
 706
 707
 708
 709
 710
 711
 712
 713
 714
 715
 716
 717
 718
 719
 720
 721
 722
 723
 724
 725
 726
 727
 728
 729
 730
 731
 732
 733
 734
 735
 736
 737
 738
 739
 740
 741
 742
 743
 744
 745
 746
 747
 748
 749
 750
 751
 752
 753
 754
 755
 756
 757
 758
 759
 760
 761
 762
 763
 764
 765
 766
 767
 768
 769
 770
 771
 772
 773
 774
 775
 776
 777
 778
 779
 780
 781
 782
 783
 784
 785
 786
 787
 788
 789
 790
 791
 792
 793
 794
 795
 796
 797
 798
 799
 800
 801
 802
 803
 804
 805
 806
 807
 808
 809
 810
 811
 812
 813
 814
 815
 816
 817
 818
 819
 820
 821
 822
 823
 824
 825
 826
 827
 828
 829
 830
 831
 832
 833
 834
 835
 836
 837
 838
 839
 840
 841
 842
 843
 844
 845
 846
 847
 848
 849
 850
 851
 852
 853
 854
 855
 856
 857
 858
 859
 860
 861
 862
 863
 864
 865
 866
 867
 868
 869
 870
 871
 872
 873
 874
 875
 876
 877
 878
 879
 880
 881
 882
 883
 884
 885
 886
 887
 888
 889
 890
 891
 892
 893
 894
 895
 896
 897
 898
 899
 900
 901
 902
 903
 904
 905
 906
 907
 908
 909
 910
 911
 912
 913
 914
 915
 916
 917
 918
 919
 920
 921
 922
 923
 924
 925
 926
 927
 928
 929
 930
 931
 932
 933
 934
 935
 936
 937
 938
 939
 940
 941
 942
 943
 944
 945
 946
 947
 948
 949
 950
 951
 952
 953
 954
 955
 956
 957
 958
 959
 960
 961
 962
 963
 964
 965
 966
 967
 968
 969
 970
 971
 972
 973
 974
 975
 976
 977
 978
 979
 980
 981
 982
 983
 984
 985
 986
 987
 988
 989
 990
 991
 992
 993
 994
 995
 996
 997
 998
 999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
class Router:
    """Routes a completion request over the pool, failing over between LLMs."""

    def __init__(
        self,
        pool: LLMPool,
        store: StoreProtocol,
        *,
        optimizer: Optimizer | None = None,
        learner: Learner | None = None,
    ) -> None:
        self._pool = pool
        self._store = store
        self._optimizer = optimizer
        self._learner = learner
        self._http_client: httpx.AsyncClient | None = None

    @property
    def http(self) -> httpx.AsyncClient:
        """The installation's one HTTP client, opened on first use and shared by every
        caller — routed calls and ``direct`` clients alike."""
        if self._http_client is None:
            self._http_client = chat.make_client()
        return self._http_client

    async def ask(  # noqa: PLR0913 - the call knobs, one keyword each
        self,
        ring: KeyRing,
        prompt: str,
        *,
        operation: str | None = None,
        trace_id: str | None = None,
        wait: float | None = None,
        fastest_of: int | None = None,
        parallel_recovery: bool = True,
        response_format: dict | None = None,
    ) -> AsyncResult:
        return await self.chat(
            ring,
            [{"role": "user", "content": prompt}],
            operation=operation,
            trace_id=trace_id,
            wait=wait,
            fastest_of=fastest_of,
            parallel_recovery=parallel_recovery,
            response_format=response_format,
        )

    async def chat(  # noqa: PLR0913 - who calls, what, and the call knobs
        self,
        ring: KeyRing,
        messages: list[dict],
        *,
        tools: list[dict] | None = None,
        operation: str | None = None,
        trace_id: str | None = None,
        wait: float | None = None,
        fastest_of: int | None = None,
        parallel_recovery: bool = True,
        response_format: dict | None = None,
    ) -> AsyncResult:
        _check_lanes(fastest_of, parallel_recovery)
        routed = self._route(
            partial(
                self._attempt,
                messages=messages,
                tools=tools,
                response_format=response_format,
            ),
            ring=ring,
            operation=operation,
            trace_id=trace_id,
            wait=wait,
            timeout_message="the wait budget ran out while an LLM was answering",
            fastest_of=fastest_of,
            parallel_recovery=parallel_recovery,
        )
        async with aclosing(routed) as results:
            return await anext(results)

    async def _route(  # noqa: PLR0913 - one call's whole context: who, what, how long
        self,
        attempt: Callable[..., AsyncIterator[_Produced]],
        *,
        ring: KeyRing,
        operation: str | None,
        trace_id: str | None,
        wait: float | None,
        timeout_message: str,
        fastest_of: int | None = None,
        parallel_recovery: bool = True,
        receipt: CallReceipt | None = None,
    ) -> AsyncIterator[_Produced]:
        """Run one call over the pool, failing over between LLMs and racing distinct
        candidates where the caller or the pool's own recovery asks for it. The sole
        owner of which candidate comes next and which error the caller finally sees."""
        queue_deadline = None if wait is None else time.monotonic() + wait
        # wait=0 is "do not queue", not "answer instantly": it bounds slot
        # acquisition only, leaving the attempt on the global ceiling.
        answer_deadline = queue_deadline if wait else None
        call = _Call(
            attempt=attempt,
            ring=ring,
            operation=operation,
            trace_id=trace_id,
            answer_deadline=answer_deadline,
            width=fastest_of if fastest_of is not None and fastest_of > 1 else 1,
            recovery_width=2 if parallel_recovery else 1,
        )
        while True:
            payable = await ring.payable(c.api_key_ref for c in self._pool.configs.values())
            try:
                configs = await self._pool.acquire_many(
                    queue_deadline,
                    payable=payable,
                    width=call.width,
                    recovery_width=call.recovery_width,
                    operation=operation,
                    exclude=frozenset(call.client_failed),
                    answer_deadline=answer_deadline,
                )
            except NoLLMAvailableError as exc:
                if exc.reason == "excluded" and call.last_client_error is not None:
                    raise call.last_client_error from None
                raise

            lanes = [self._open(call, config) for config in configs]
            winner = await (
                self._alone(call, lanes[0]) if len(lanes) == 1 else self._race(call, lanes)
            )
            if winner is not None:
                try:
                    async with aclosing(winner.produced) as rest:
                        self._publish(receipt, winner)
                        yield winner.first
                        async for item in rest:
                            yield item
                finally:
                    self._publish(receipt, winner)
                    await self._settled(call)
                return
            if call.expired:
                # A request an earlier LLM already rejected as malformed stays the
                # more useful answer than "the clock ran out" — the caller can act on it.
                if call.last_client_error is not None:
                    raise call.last_client_error from None
                raise NoLLMAvailableError(
                    timeout_message,
                    reason="timeout",
                    retry_at=self._pool.retry_at(payable, exclude=frozenset(call.client_failed)),
                )
            # Every lane failed without answering ⇒ loop to the next free LLM.

    def _open(self, call: _Call, config: LLMConfig) -> _Lane:
        """One candidate's lane, not started until something drives it."""
        outcome = _Outcome()
        return _Lane(
            config=config,
            outcome=outcome,
            produced=call.attempt(
                config,
                outcome,
                call.answer_deadline,
                ring=call.ring,
                operation=call.operation,
                trace_id=call.trace_id,
            ),
        )

    async def _alone(self, call: _Call, lane: _Lane) -> _Lane | None:
        """One candidate on the call path, which is what an ordinary healthy call is:
        no task, no competitor, nothing to settle but this attempt's own verdict."""
        if await lane.open():
            return lane
        call.absorb(lane)
        await lane.produced.aclose()
        return None

    async def _race(self, call: _Call, lanes: list[_Lane]) -> _Lane | None:
        """Run distinct candidates at once and commit to the first that produces
        anything. A lane a real failure emptied is refilled from a model this call has
        not tried; every lane still live once a winner exists is superseded."""
        opened = list(lanes)
        live = list(lanes)
        width = len(lanes)
        for lane in live:
            lane.task = asyncio.create_task(lane.open())
        winner: _Lane | None = None
        try:
            while live and winner is None:
                await asyncio.wait(
                    [lane.task for lane in live if lane.task is not None],
                    return_when=asyncio.FIRST_COMPLETED,
                )
                for lane in [settled for settled in live if settled.done()]:
                    live.remove(lane)
                    if not lane.opened():
                        call.absorb(lane)
                        await lane.produced.aclose()
                    elif winner is None:
                        winner = lane
                    else:
                        self._supersede(call, lane)
                if winner is None and not call.expired:
                    live.extend(await self._refill(call, opened, width - len(live)))
        except BaseException:
            await self._abandon(call, live)
            raise
        for lane in live:
            self._supersede(call, lane)
        return winner

    async def _refill(self, call: _Call, opened: list[_Lane], needed: int) -> list[_Lane]:
        """Reopen an emptied lane on a model this call has not tried. Never waits: the
        lanes still racing must not be stalled to widen the field."""
        if needed <= 0:
            return []
        payable = await call.ring.payable(c.api_key_ref for c in self._pool.configs.values())
        configs = await self._pool.take_free(
            payable=payable,
            width=needed,
            operation=call.operation,
            exclude=frozenset({lane.config.name for lane in opened}) | call.client_failed,
            answer_deadline=call.answer_deadline,
        )
        fresh = [self._open(call, config) for config in configs]
        for lane in fresh:
            lane.task = asyncio.create_task(lane.open())
        opened.extend(fresh)
        return fresh

    async def _abandon(self, call: _Call, live: list[_Lane]) -> None:
        """Nothing answered, so nothing was superseded: every lane is taken off its
        provider first and only then waited on, and whatever was already settling
        beside them is finished too."""
        for lane in live:
            self._cancel(lane)
        for lane in live:
            await self._stop(lane)
        await self._settled(call)

    def _supersede(self, call: _Call, lane: _Lane) -> None:
        """Settle a lane another model has already answered past: cancelled at once, but
        journaled and released beside the answer rather than in front of it — what the
        caller is holding may not wait on a store write it will never read."""
        lane.outcome.superseded = True
        self._cancel(lane)
        call.losers.append(asyncio.create_task(self._stop(lane)))

    @staticmethod
    def _cancel(lane: _Lane) -> None:
        """Take a lane off the provider. One already settling is left to finish: it has
        applied its own verdict to the pool and owes the journal the row for it."""
        if lane.task is not None and not lane.outcome.settling:
            lane.task.cancel()

    async def _settled(self, call: _Call) -> None:
        """Wait for the lanes settling beside the answer, so every attempt this call
        made is journaled and every slot handed back by the time it ends."""
        if not call.losers:
            return
        pending, call.losers = call.losers, []
        for outcome in await asyncio.gather(*pending, return_exceptions=True):
            if isinstance(outcome, Exception):
                logger.warning("llmbroker: settling a superseded attempt failed: %r", outcome)

    async def _stop(self, lane: _Lane) -> None:
        """Let a lane finish whatever it was doing and close it. Cancelling it is the
        caller's to do first, so a lane already settling is never cut short."""
        task = lane.task
        if task is not None:
            await asyncio.wait([task])
            if not task.cancelled() and task.exception() is not None:
                logger.warning(
                    "llmbroker: the cancelled attempt on %s failed: %r",
                    lane.config.name,
                    task.exception(),
                )
        await lane.produced.aclose()

    @staticmethod
    def _publish(receipt: CallReceipt | None, winner: _Lane) -> None:
        """Name the winner on the handle the caller holds. Each lane names itself on
        its own until it has won, so a loser can never claim the answer."""
        if receipt is None:
            return
        won = winner.outcome.receipt
        receipt.llm_name = won.llm_name
        receipt.call_id = won.call_id
        receipt.usage = won.usage
        receipt.settled = won.settled

    def _capped_wait(self, base: float, backoff: float) -> float:
        cap = self._optimizer.max_delay if self._optimizer else base
        return min(base * backoff, cap)

    def _attempt_timeout(self, answer_deadline: float | None) -> tuple[float, bool]:
        """Per-attempt HTTP timeout, and whether the caller's remaining ``wait``
        budget — rather than the global ceiling — is what bounds it."""
        if answer_deadline is None:
            return chat.HTTP_TIMEOUT, False
        remaining = answer_deadline - time.monotonic()
        if remaining >= chat.HTTP_TIMEOUT:
            return chat.HTTP_TIMEOUT, False
        return max(remaining, 0.0), True

    async def _new_attempt(
        self,
        config: LLMConfig,
        ring: KeyRing,
        *,
        operation: str | None,
        trace_id: str | None,
    ) -> "_Attempt | None":
        """``None`` where the caller's key for this model has gone since the slot was
        taken — a 401 in another attempt is enough, and the slot goes straight back."""
        key = await ring.resolve(config.api_key_ref)
        if key is None:
            await self._pool.release(config)
            return None
        return _Attempt(
            config=config,
            call_id=str(uuid.uuid4()),
            t0=time.monotonic(),
            operation=operation,
            trace_id=trace_id,
            ring=ring,
            resolved_key=key,
        )

    def _backoff(self, name: str) -> float:
        # Read before the first record is awaited (which increments rl_fail_count via
        # the learner), so the first failure in a streak always sees exponent 0.
        fails_before = self._optimizer.rl_fail_count(name) if self._optimizer else 0
        return self._optimizer.backoff_factor**fails_before if self._optimizer else 1.0

    async def _record(  # noqa: PLR0913
        self,
        attempt: _Attempt,
        status: CallStatus,
        *,
        http_status: int | None = None,
        error_detail: str | None = None,
        usage: Usage | None = None,
        cooldown_delay: float | None = None,
        budget_ms: int | None = None,
    ) -> None:
        cooldown_until = (
            datetime.now(UTC) + timedelta(seconds=cooldown_delay)
            if cooldown_delay is not None
            else None
        )
        await self._log_call(
            Call(
                id=attempt.call_id,
                llm_name=attempt.config.name,
                operation=attempt.operation,
                trace_id=attempt.trace_id,
                status=status,
                ts=datetime.now(UTC),
                http_status=http_status,
                latency_ms=int((time.monotonic() - attempt.t0) * 1000),
                error_detail=error_detail,
                usage=usage,
                scope=attempt.ring.scope,
                cooldown_until=cooldown_until,
                budget_ms=budget_ms,
            ),
        )

    async def _finish_ok(self, attempt: _Attempt, usage: Usage | None) -> None:
        await self._pool.release(attempt.config)
        self._pool.clear_cooling(attempt.config.name)
        self._pool.clear_budget_bound(attempt.config.name)
        await self._record(attempt, CallStatus.OK, http_status=200, usage=usage)

    async def _dispose(
        self,
        attempt: _Attempt,
        verdict: _Verdict,
        *,
        backoff: float,
        timeout: float,
    ) -> None:
        """Settle one failed attempt: cool the model down or just hand the slot
        back, then journal it. The single failure surface both routing paths use.
        The two facts are independent — a missed budget may also be a cooldown."""
        delay: float | None = None
        budget_ms: int | None = None
        if verdict.http_status is not None and is_auth_failure(verdict.http_status):
            attempt.ring.forget(attempt.config.api_key_ref)
        if isinstance(verdict.outcome, _BudgetExpired):
            # Applied as well as journaled: the next caller on this node must not
            # wait on a rebuild, nor on learning being switched on.
            budget_ms = int(timeout * 1000)
            self._pool.raise_budget_bound(attempt.config.name, timeout, datetime.now(UTC))
        if verdict.cool_base is None:
            await self._pool.release(attempt.config)
        else:
            delay = self._capped_wait(verdict.cool_base, backoff)
            await self._pool.cool_down(attempt.config, delay)
        await self._record(
            attempt,
            verdict.status,
            http_status=verdict.http_status,
            error_detail=verdict.detail,
            cooldown_delay=delay,
            budget_ms=budget_ms,
        )

    async def _settle_superseded(self, attempt: _Attempt, usage: Usage | None) -> None:
        """Settle a lane another model answered past: the slot goes back and one neutral
        row is written. Nothing is cooled, counted, bounded or rated — losing a race
        proves neither availability nor failure."""
        await self._pool.release(attempt.config)
        await self._record(attempt, CallStatus.SUPERSEDED, usage=usage)

    async def _spent_budget(self, attempt: _Attempt, outcome: _Outcome) -> None:
        """The caller's ``wait`` was already gone before a request could be opened:
        hand the slot back and journal it, blaming the clock rather than the LLM."""
        await self._pool.release(attempt.config)
        await self._record(attempt, CallStatus.ERROR, error_detail="wait budget exhausted")
        outcome.verdict = _BudgetExpired()

    async def _attempt(  # noqa: PLR0913
        self,
        config: LLMConfig,
        outcome: _Outcome,
        answer_deadline: float | None,
        *,
        ring: KeyRing,
        messages: list[dict],
        tools: list[dict] | None,
        operation: str | None,
        trace_id: str | None,
        response_format: dict | None = None,
    ) -> AsyncIterator[AsyncResult]:
        """Run one LLM and yield its single result, or leave on ``outcome`` the verdict
        the driver fails over on."""
        attempt = await self._new_attempt(config, ring, operation=operation, trace_id=trace_id)
        if attempt is None:
            outcome.verdict = _Failed(error=None)
            return
        backoff = self._backoff(config.name)

        timeout, budget_bound = self._attempt_timeout(answer_deadline)
        if budget_bound and timeout == 0.0:
            outcome.settling = True
            await self._spent_budget(attempt, outcome)
            return

        try:
            # httpx applies its timeout per operation (connect, write, read), so
            # only this wall-clock bound keeps the whole attempt inside the budget.
            async with asyncio.timeout(timeout):
                content, tool_calls, usage = await call_provider(
                    config,
                    attempt.resolved_key,
                    messages,
                    tools,
                    client=self.http,
                    timeout=timeout,
                    params=_request_params(response_format),
                )
        except _FAILOVER_ERRORS as exc:
            outcome.settling = True
            verdict = _classify(exc, budget_bound=budget_bound)
        except BaseException as exc:
            # A bug, a cancellation, or a sibling that answered first: settling before the
            # awaits, or a race cancels this one mid-release and costs a unit of `parallel`.
            outcome.settling = True
            if outcome.superseded:
                await self._settle_superseded(attempt, None)
            else:
                await self._pool.release(config)
                if isinstance(exc, Exception):
                    await self._record(attempt, CallStatus.ERROR, error_detail=type(exc).__name__)
            raise
        else:
            outcome.settling = True
            await self._finish_ok(attempt, usage)
            outcome.answered = True
            # Outside the `try`: the consumer closes this generator on the yield, and
            # a GeneratorExit caught above would journal the attempt twice.
            yield AsyncResult(
                text=content,
                tool_calls=tool_calls,
                usage=usage,
                call_id=attempt.call_id,
                llm_name=config.name,
                operation=operation,
                store=self._store,
                scope=ring.scope,
                observe_quality=(
                    self._learner.record_quality_observed if self._learner is not None else None
                ),
            )
            return

        await self._dispose(attempt, verdict, backoff=backoff, timeout=timeout)
        outcome.verdict = verdict.outcome

    # ------------------------------------------------------------------
    # Streaming
    # ------------------------------------------------------------------

    async def stream(  # noqa: PLR0913 - the chat knobs plus the caller's receipt
        self,
        ring: KeyRing,
        messages: list[dict],
        receipt: CallReceipt,
        *,
        operation: str | None = None,
        trace_id: str | None = None,
        wait: float | None = None,
        fastest_of: int | None = None,
        parallel_recovery: bool = True,
        response_format: dict | None = None,
    ) -> AsyncIterator[str]:
        """Route a streaming completion over the pool, yielding text deltas and naming
        what answered on ``receipt``. Fails over exactly like ``chat`` up to the first
        delta; past it a death raises ``StreamInterruptedError`` instead."""
        _check_lanes(fastest_of, parallel_recovery)
        routed = self._route(
            partial(
                self._stream_attempt,
                messages=messages,
                response_format=response_format,
            ),
            ring=ring,
            operation=operation,
            trace_id=trace_id,
            wait=wait,
            timeout_message="the wait budget ran out before any LLM produced a delta",
            fastest_of=fastest_of,
            parallel_recovery=parallel_recovery,
            receipt=receipt,
        )
        async with aclosing(routed) as deltas:
            async for delta in deltas:
                yield delta

    async def _stream_attempt(  # noqa: PLR0913
        self,
        config: LLMConfig,
        outcome: _Outcome,
        first_delta_deadline: float | None,
        *,
        ring: KeyRing,
        messages: list[dict],
        operation: str | None,
        trace_id: str | None,
        response_format: dict | None = None,
    ) -> AsyncIterator[str]:
        """Stream one LLM, yielding its deltas. Leaves a verdict on ``outcome`` when it
        died before the first delta; the slot is settled and journaled by then."""
        attempt = await self._new_attempt(config, ring, operation=operation, trace_id=trace_id)
        if attempt is None:
            outcome.verdict = _Failed(error=None)
            return
        backoff = self._backoff(config.name)
        timeout, budget_bound = self._attempt_timeout(first_delta_deadline)
        if budget_bound and timeout == 0.0:
            outcome.settling = True
            await self._spent_budget(attempt, outcome)
            return

        request = build_chat_request(
            config.base_url,
            config.model,
            attempt.resolved_key,
            messages,
            stream=True,
            params=_request_params(response_format),
        )
        progress = _StreamProgress(outcome.receipt, config.name, attempt.call_id)
        try:
            async with aclosing(
                _stream_deltas(
                    self.http,
                    request,
                    model=config.name,
                    timeout=timeout,
                    progress=progress,
                ),
            ) as deltas:
                async for delta in deltas:
                    yield delta
        except _BudgetExhaustedError:
            outcome.settling = True
            await self._exhausted(attempt, timeout)
        except _FAILOVER_ERRORS as exc:
            outcome.settling = True
            await self._fail_stream(
                attempt,
                exc,
                outcome,
                backoff=backoff,
                timeout=timeout,
                budget_bound=budget_bound,
                started=progress.started,
            )
        except GeneratorExit:
            await self._stream_stopped(attempt, progress, outcome)
            raise
        except BaseException as exc:
            outcome.settling = True
            await self._stream_aborted(attempt, progress, outcome, exc)
            raise
        else:
            outcome.settling = True
            await self._settle_stream(
                attempt,
                progress,
                outcome,
                backoff=backoff,
                timeout=timeout,
            )

    async def _stream_stopped(
        self,
        attempt: _Attempt,
        progress: _StreamProgress,
        outcome: _Outcome,
    ) -> None:
        """The consumer stopped pulling, or a sibling answered first: the model answered
        and did nothing wrong either way, so this is a completed attempt rather than a
        failure the pool should learn from."""
        if outcome.superseded:
            await self._settle_superseded(attempt, progress.usage)
            return
        await self._finish_ok(attempt, progress.usage)
        progress.settle()

    async def _stream_aborted(
        self,
        attempt: _Attempt,
        progress: _StreamProgress,
        outcome: _Outcome,
        exc: BaseException,
    ) -> None:
        """A bug, or a cancellation: the slot goes back whatever happened, and only what
        the attempt itself did is journaled."""
        if outcome.superseded:
            await self._settle_superseded(attempt, progress.usage)
            return
        await self._pool.release(attempt.config)
        if isinstance(exc, Exception):
            await self._record(attempt, CallStatus.ERROR, error_detail=type(exc).__name__)

    async def _settle_stream(
        self,
        attempt: _Attempt,
        progress: _StreamProgress,
        outcome: _Outcome,
        *,
        backoff: float,
        timeout: float,
    ) -> None:
        """Settle a streaming attempt whose deltas ran out. One that never produced a
        delta answered nothing, so it fails over through the same surface a malformed
        body does — classified there, so there is one reading of an unusable 200."""
        if not progress.started:
            verdict = _classify(
                empty_answer_error(attempt.config.name, NO_DELTA),
                budget_bound=False,
            )
            await self._dispose(attempt, verdict, backoff=backoff, timeout=timeout)
            outcome.verdict = verdict.outcome
            return
        await self._finish_ok(attempt, progress.usage)
        progress.settle()
        outcome.answered = True

    async def _exhausted(self, attempt: _Attempt, timeout: float) -> NoReturn:
        """Settle a stream that outlived the caller's budget: the model answered and did
        nothing wrong, so it is journaled as a budget it did not finish within rather than
        cooled, and the call ends by raising — nothing is retried past the first delta."""
        elapsed = time.monotonic() - attempt.t0
        await self._dispose(
            attempt,
            _Verdict(
                CallStatus.ERROR,
                f"answer budget exhausted after {elapsed:.1f}s",
                outcome=_BudgetExpired(),
            ),
            backoff=self._backoff(attempt.config.name),
            # The bound this teaches is provider time, which the wall clock overstates
            # by whatever the consumer held between deltas.
            timeout=timeout,
        )
        raise LLMTimeoutError(
            f"{attempt.config.name}: the answer was still arriving {elapsed:.1f}s in, past"
            " the budget, and nothing can be retried once output has reached the caller",
        )

    async def _fail_stream(  # noqa: PLR0913
        self,
        attempt: _Attempt,
        exc: Exception,
        outcome: _Outcome,
        *,
        backoff: float,
        timeout: float,
        budget_bound: bool,
        started: bool,
    ) -> None:
        """Settle a failed streaming attempt through the shared failure surface: the
        same verdict a ``chat`` attempt would give, or ``StreamInterruptedError``
        once deltas have already reached the caller."""
        verdict = _classify(exc, budget_bound=budget_bound and not started)
        await self._dispose(attempt, verdict, backoff=backoff, timeout=timeout)
        if started:
            raise StreamInterruptedError(
                f"{attempt.config.name}: the stream died after it had already emitted"
                " deltas — no failover is possible once output has reached the caller",
                llm_name=attempt.config.name,
            ) from exc
        outcome.verdict = verdict.outcome

    async def _log_call(self, call: Call) -> None:
        try:
            await self._store.record(call)
        except Exception:  # noqa: BLE001
            logger.exception("llmbroker: store.record failed")
        # Guarded separately and reached even when the write failed: a journal nobody
        # can write must not also blind the pool to what just happened.
        if self._learner is not None:
            try:
                await self._learner.observe(call)
            except Exception:  # noqa: BLE001
                logger.exception("llmbroker: learning from the call failed")

    async def aclose(self) -> None:
        if self._http_client is not None:
            await self._http_client.aclose()
            self._http_client = None
http property

The installation's one HTTP client, opened on first use and shared by every caller — routed calls and direct clients alike.

stream(ring, messages, receipt, *, operation=None, trace_id=None, wait=None, fastest_of=None, parallel_recovery=True, response_format=None) async

Route a streaming completion over the pool, yielding text deltas and naming what answered on receipt. Fails over exactly like chat up to the first delta; past it a death raises StreamInterruptedError instead.

Source code in src/llmbroker/broker/router.py
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
async def stream(  # noqa: PLR0913 - the chat knobs plus the caller's receipt
    self,
    ring: KeyRing,
    messages: list[dict],
    receipt: CallReceipt,
    *,
    operation: str | None = None,
    trace_id: str | None = None,
    wait: float | None = None,
    fastest_of: int | None = None,
    parallel_recovery: bool = True,
    response_format: dict | None = None,
) -> AsyncIterator[str]:
    """Route a streaming completion over the pool, yielding text deltas and naming
    what answered on ``receipt``. Fails over exactly like ``chat`` up to the first
    delta; past it a death raises ``StreamInterruptedError`` instead."""
    _check_lanes(fastest_of, parallel_recovery)
    routed = self._route(
        partial(
            self._stream_attempt,
            messages=messages,
            response_format=response_format,
        ),
        ring=ring,
        operation=operation,
        trace_id=trace_id,
        wait=wait,
        timeout_message="the wait budget ran out before any LLM produced a delta",
        fastest_of=fastest_of,
        parallel_recovery=parallel_recovery,
        receipt=receipt,
    )
    async with aclosing(routed) as deltas:
        async for delta in deltas:
            yield delta

source

Which ports a source string becomes, and the defaults each port falls back to when the caller named none. Every backend package is imported lazily here, so a bare import llmbroker never pulls in a driver.

default_secrets()

A registry the host brought itself gets the plain environment resolver.

Source code in src/llmbroker/broker/source.py
67
68
69
def default_secrets() -> SecretsProtocol:
    """A registry the host brought itself gets the plain environment resolver."""
    return EnvSecrets()
default_store()

A registry the host brought itself falls back to ./store under the CWD — not an error, just an unopinionated default.

Source code in src/llmbroker/broker/source.py
72
73
74
75
def default_store() -> StoreProtocol:
    """A registry the host brought itself falls back to ``./store`` under the CWD —
    not an error, just an unopinionated default."""
    return FileStore(Path("store"))
model_list_path(home)

The model list file inside llmbroker's own directory — where a zero-config broker keeps its pool.

Source code in src/llmbroker/broker/source.py
78
79
80
81
def model_list_path(home: Path) -> Path:
    """The model list file inside llmbroker's own directory — where a zero-config broker
    keeps its pool."""
    return home / "model-list.toml"
resolve_source(source)

Returns (registry, secrets, store); a None store means "use the caller's own default".

Source code in src/llmbroker/broker/source.py
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
def resolve_source(
    source: str | Path,
) -> tuple[RegistryProtocol, SecretsProtocol, StoreProtocol | None]:
    """Returns ``(registry, secrets, store)``; a ``None`` store means
    "use the caller's own default"."""
    source = str(source)
    sqlite_path = source.removeprefix("sqlite://")
    if source.startswith("sqlite://") or source.endswith(_SQLITE_SUFFIXES):
        try:
            from llmbroker.sqlite.driver import SqliteDriver  # noqa: PLC0415
        except ImportError as exc:
            raise ImportError(
                f"sqlite source {source!r} requires: pip install llmbroker[sqlite]",
            ) from exc
        driver = SqliteDriver(sqlite_path)
        return DriverRegistry(driver), DriverSecrets(driver), DriverStore(driver)

    if source.startswith("postgresql://"):
        try:
            from llmbroker.postgres.driver import PostgresDriver  # noqa: PLC0415
        except ImportError as exc:
            raise ImportError(
                f"postgres source {source!r} requires: pip install llmbroker[postgres]",
            ) from exc
        driver = PostgresDriver(dsn=source)
        return DriverRegistry(driver), DriverSecrets(driver), DriverStore(driver)

    if source.startswith("mongodb://"):
        try:
            from motor.motor_asyncio import AsyncIOMotorClient  # noqa: PLC0415
        except ImportError as exc:
            raise ImportError(
                f"mongodb source {source!r} requires: pip install llmbroker[mongodb]",
            ) from exc
        from llmbroker.mongodb.driver import MongoDriver  # noqa: PLC0415

        client = AsyncIOMotorClient(source)
        driver = MongoDriver(client.get_default_database(), client=client)
        return DriverRegistry(driver), DriverSecrets(driver), DriverStore(driver)

    raise ValueError(
        f"unrecognized registry source {source!r} — expected a sqlite path/URL"
        " (.db, .sqlite, sqlite://...), or a postgresql://... / mongodb://... URL."
        " A model list is not a file a host names: use Broker() for the curated pool in"
        " llmbroker's own directory, or pass a registry object of your own",
    )
zero_config_ports(home)

The installation a broker builds for itself when given no source at all — see rules/backends.md. Nowhere writable is a supported outcome: everything then lives in memory for that run.

Source code in src/llmbroker/broker/source.py
84
85
86
87
88
89
90
91
92
93
def zero_config_ports(
    home: Path | None,
) -> tuple[RegistryProtocol, SecretsProtocol, StoreProtocol]:
    """The installation a broker builds for itself when given no source at all — see
    ``rules/backends.md``. Nowhere writable is a supported outcome: everything then
    lives in memory for that run."""
    secrets = EnvSecrets(Path(".env"))
    if home is None:
        return DriverRegistry(InMemoryDriver()), secrets, InMemoryStore()
    return FileRegistry(model_list_path(home)), secrets, FileStore(home / "store")

stamps

When this installation last looked upstream, kept across process exits.

Nothing here is authoritative — a stamp only makes checks less frequent — so every failure degrades to "no stamp" instead of surfacing.

stamp_age(home, key)

Seconds since this check last completed, None when there is no usable record. A timestamp in the future — a clock moved back, a stamp copied between hosts — counts as absent rather than as a gate that never expires.

Source code in src/llmbroker/broker/stamps.py
31
32
33
34
35
36
37
38
39
40
41
42
43
44
def stamp_age(home: Path | None, key: str) -> float | None:
    """Seconds since this check last completed, ``None`` when there is no usable
    record. A timestamp in the future — a clock moved back, a stamp copied between
    hosts — counts as absent rather than as a gate that never expires."""
    if home is None:
        return None
    entry = _load(home).get(key)
    if not isinstance(entry, dict):
        return None
    checked_at = entry.get("checked_at")
    if not isinstance(checked_at, (int, float)) or isinstance(checked_at, bool):
        return None
    age = time.time() - float(checked_at)
    return age if age >= 0 else None
write_stamp(home, key)

Record that this check just completed — wall clock, because the whole point is to survive process exit.

Source code in src/llmbroker/broker/stamps.py
47
48
49
50
51
52
53
54
55
56
57
def write_stamp(home: Path | None, key: str) -> None:
    """Record that this check just completed — wall clock, because the whole point
    is to survive process exit."""
    if home is None:
        return
    data = _load(home)
    data[key] = {"checked_at": time.time()}
    try:
        write_atomic(_stamp_path(home), json.dumps(data, indent=2, sort_keys=True) + "\n")
    except OSError as exc:
        logger.debug("sync stamp: cannot write %s (%s)", _stamp_path(home), exc)

stats

Per-model aggregation of journal call records.

stats_from_calls(rows)

Aggregate call rows per model.

rows are newest-first, as everywhere else in this codebase, so the first row seen for a model is its most recent and the last is its oldest.

from datetime import UTC, datetime ts = datetime(2030, 1, 2, tzinfo=UTC) rows = [ ... Call(id="2", llm_name="a", operation=None, trace_id=None, ... status=CallStatus.OK, ts=ts), ... Call(id="1", llm_name="a", operation=None, trace_id=None, ... status=CallStatus.ERROR, ts=datetime(2030, 1, 1, tzinfo=UTC)), ... ] stats = stats_from_calls(rows)["a"] stats.total, stats.last_status (2, ) sorted((s.value, n) for s, n in stats.by_status.items()) [('error', 1), ('ok', 1)]

Source code in src/llmbroker/broker/stats.py
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
def stats_from_calls(rows: list[Call]) -> dict[str, LLMStats]:
    """Aggregate call rows per model.

    ``rows`` are newest-first, as everywhere else in this codebase, so the first
    row seen for a model is its most recent and the last is its oldest.

    >>> from datetime import UTC, datetime
    >>> ts = datetime(2030, 1, 2, tzinfo=UTC)
    >>> rows = [
    ...     Call(id="2", llm_name="a", operation=None, trace_id=None,
    ...          status=CallStatus.OK, ts=ts),
    ...     Call(id="1", llm_name="a", operation=None, trace_id=None,
    ...          status=CallStatus.ERROR, ts=datetime(2030, 1, 1, tzinfo=UTC)),
    ... ]
    >>> stats = stats_from_calls(rows)["a"]
    >>> stats.total, stats.last_status
    (2, <CallStatus.OK: 'ok'>)
    >>> sorted((s.value, n) for s, n in stats.by_status.items())
    [('error', 1), ('ok', 1)]
    """
    totals: dict[str, int] = {}
    by_status: dict[str, dict[CallStatus, int]] = {}
    newest: dict[str, Call] = {}
    oldest: dict[str, Call] = {}
    for row in rows:
        name = row.llm_name
        totals[name] = totals.get(name, 0) + 1
        if row.status is not None:
            counts = by_status.setdefault(name, {})
            counts[row.status] = counts.get(row.status, 0) + 1
        newest.setdefault(name, row)
        oldest[name] = row
    return {
        name: LLMStats(
            total=total,
            by_status=by_status.get(name, {}),
            first_at=oldest[name].ts,
            last_at=newest[name].ts,
            last_status=newest[name].status,
        )
        for name, total in totals.items()
    }

chat

OpenAI-compatible chat primitives. Request building, response parsing and retry parsing live here once; the resolved key is passed in, never read off the config.

aiter_chat_chunks(resp, model) async

Yield the decoded chunks of a chat-completion SSE body. choices is what makes a chunk one, so a body decoding none raises on exhaustion; an answer with no delta is a separate verdict its consumer reaches, not a body that is not a stream.

Source code in src/llmbroker/chat.py
291
292
293
294
295
296
297
298
299
300
301
302
303
304
async def aiter_chat_chunks(resp: httpx.Response, model: str) -> AsyncIterator[dict]:
    """Yield the decoded chunks of a chat-completion SSE body. ``choices`` is what
    makes a chunk one, so a body decoding none raises on exhaustion; an answer with no
    delta is a separate verdict its consumer reaches, not a body that is not a stream."""
    completions = 0
    async for chunk in aiter_sse_chunks(resp):
        completions += "choices" in chunk
        yield chunk
    if not completions:
        raise _invalid_stream(
            model,
            f"content-type={resp.headers.get('content-type', '')!r},"
            " no chat-completion chunks decoded",
        )

aiter_sse_chunks(response) async

Yield decoded JSON objects from an OpenAI-compatible SSE stream body.

data: [DONE] ends the stream; a payload that does not decode, or decodes to a scalar or array, is skipped — every consumer here may assume an object.

Source code in src/llmbroker/chat.py
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
async def aiter_sse_chunks(response: httpx.Response) -> AsyncIterator[dict]:
    """Yield decoded JSON objects from an OpenAI-compatible SSE stream body.

    ``data: [DONE]`` ends the stream; a payload that does not decode, or decodes to
    a scalar or array, is skipped — every consumer here may assume an object.
    """
    async for raw in response.aiter_lines():
        line = raw.strip()
        if not line.startswith(_SSE_DATA_PREFIX):
            continue
        payload = line[len(_SSE_DATA_PREFIX) :].strip()
        if payload == _SSE_DONE:
            return
        try:
            decoded = json.loads(payload)
        except json.JSONDecodeError:
            continue
        if isinstance(decoded, dict):
            yield decoded

build_chat_request(base_url, model, api_key, messages, tools=None, *, stream=False, params=None)

Return (url, headers, json_body) for an OpenAI-compatible chat completion.

params is the caller's own provider parameters, merged in last and never inspected; a key this builder owns is refused rather than overwritten.

url, headers, body = build_chat_request("https://x/v1", "m", "k", [], stream=True) url 'https://x/v1/chat/completions' body["stream"], body["stream_options"] (True, {'include_usage': True}) build_chat_request("https://x/v1", "m", "k", [], params={"model": "other"}) Traceback (most recent call last): ValueError: request parameter 'model' is built by llmbroker and cannot be passed

Source code in src/llmbroker/chat.py
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
def build_chat_request(  # noqa: PLR0913
    base_url: str,
    model: str,
    api_key: str,
    messages: list[dict],
    tools: list[dict] | None = None,
    *,
    stream: bool = False,
    params: Mapping[str, object] | None = None,
) -> tuple[str, dict[str, str], dict[str, Any]]:
    """Return (url, headers, json_body) for an OpenAI-compatible chat completion.

    ``params`` is the caller's own provider parameters, merged in last and never
    inspected; a key this builder owns is refused rather than overwritten.

    >>> url, headers, body = build_chat_request("https://x/v1", "m", "k", [], stream=True)
    >>> url
    'https://x/v1/chat/completions'
    >>> body["stream"], body["stream_options"]
    (True, {'include_usage': True})
    >>> build_chat_request("https://x/v1", "m", "k", [], params={"model": "other"})
    Traceback (most recent call last):
    ValueError: request parameter 'model' is built by llmbroker and cannot be passed
    """
    body: dict[str, Any] = {"model": model, "messages": messages}
    if tools:
        body["tools"] = tools
        body["tool_choice"] = "auto"
    if stream:
        body["stream"] = True
        body["stream_options"] = {"include_usage": True}
    for key, value in (params or {}).items():
        if key in RESERVED_BODY_KEYS:
            raise ValueError(
                f"request parameter {key!r} is built by llmbroker and cannot be passed",
            )
        body[key] = value
    return (
        f"{base_url}{_CHAT_PATH}",
        {"Authorization": f"Bearer {api_key}"},
        body,
    )

call_provider(config, api_key, messages, tools, *, client=None, timeout=None, params=None) async

POST an OpenAI-compatible completion and return (content, tool_calls, usage). A passed client is reused and never closed here; timeout bounds this one request, overriding the client's own.

Source code in src/llmbroker/chat.py
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
async def call_provider(  # noqa: PLR0913
    config: LLMConfig,
    api_key: str,
    messages: list[dict],
    tools: list[dict] | None,
    *,
    client: httpx.AsyncClient | None = None,
    timeout: float | None = None,
    params: Mapping[str, object] | None = None,
) -> tuple[str, list[dict] | None, Usage | None]:
    """POST an OpenAI-compatible completion and return (content, tool_calls, usage).
    A passed ``client`` is reused and never closed here; ``timeout`` bounds this one
    request, overriding the client's own."""
    url, headers, body = build_chat_request(
        config.base_url,
        config.model,
        api_key,
        messages,
        tools,
        params=params,
    )
    async with _resolve_client(client) as active:
        if timeout is None:
            resp = await active.post(url, headers=headers, json=body)
        else:
            resp = await active.post(url, headers=headers, json=body, timeout=timeout)
        resp.raise_for_status()
    return completion_from_response(resp, config.name)

completion_from_response(resp, model)

Decode a successful chat-completion response and pull its parts out. Anything the schema does not admit raises InvalidProviderResponseError, so no caller of a 200 sees a raw decoding error.

Source code in src/llmbroker/chat.py
214
215
216
217
218
219
220
221
222
223
224
225
def completion_from_response(
    resp: httpx.Response,
    model: str,
) -> tuple[str, list[dict] | None, Usage | None]:
    """Decode a successful chat-completion response and pull its parts out. Anything
    the schema does not admit raises ``InvalidProviderResponseError``, so no caller of
    a 200 sees a raw decoding error."""
    try:
        data = resp.json()
    except ValueError as exc:
        raise _invalid_body(model, resp.text) from exc
    return _parse_completion(data, model)

empty_answer_error(model, detail)

A 200 that parses as a chat completion and carries no answer at all. The same type an unusable body raises, because the disposal is the same: fail over, or raise where there is nothing to fail over to.

Source code in src/llmbroker/chat.py
187
188
189
190
191
192
193
194
195
def empty_answer_error(model: str, detail: str) -> InvalidProviderResponseError:
    """A 200 that parses as a chat completion and carries no answer at all. The same
    type an unusable body raises, because the disposal is the same: fail over, or
    raise where there is nothing to fail over to."""
    return InvalidProviderResponseError(
        f"{model}: HTTP 200 chat completion carried no text and no tool calls",
        model=model,
        detail=detail[:_BODY_SNIPPET],
    )

message_from_response(data)

Extract the assistant message object from a chat-completion response body.

Source code in src/llmbroker/chat.py
112
113
114
def message_from_response(data: dict) -> dict:
    """Extract the assistant message object from a chat-completion response body."""
    return data["choices"][0]["message"]

parse_stream_chunk(chunk, model)

Pull (delta, usage) out of one stream chunk. The caught set is deliberately broad: an escape here reaches the caller raw and skips failover.

Source code in src/llmbroker/chat.py
321
322
323
324
325
326
327
def parse_stream_chunk(chunk: dict, model: str) -> tuple[str, Usage | None]:
    """Pull (delta, usage) out of one stream chunk. The caught set is deliberately
    broad: an escape here reaches the caller raw and skips failover."""
    try:
        return _stream_delta(chunk), parse_usage(chunk)
    except (ArithmeticError, AttributeError, KeyError, IndexError, TypeError, ValueError) as exc:
        raise _invalid_stream(model, str(chunk)) from exc

parse_tool_calls(message)

Extract the raw tool_calls list from an assistant message, verbatim.

Source code in src/llmbroker/chat.py
258
259
260
261
262
263
def parse_tool_calls(message: dict) -> list[dict] | None:
    """Extract the raw ``tool_calls`` list from an assistant message, verbatim."""
    tool_calls = message.get("tool_calls")
    if not tool_calls:
        return None
    return tool_calls

parse_usage(data)

Extract token counts from a chat-completion response body, if present.

Source code in src/llmbroker/chat.py
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
def parse_usage(data: dict) -> Usage | None:
    """Extract token counts from a chat-completion response body, if present."""
    raw = data.get("usage")
    if not isinstance(raw, dict):
        return None
    known = {"prompt_tokens", "completion_tokens", "total_tokens"}
    extra = {
        k: count
        for k, v in raw.items()
        if k not in known and (count := _token_count(v)) is not None
    }
    return Usage(
        prompt_tokens=_token_count(raw.get("prompt_tokens")),
        completion_tokens=_token_count(raw.get("completion_tokens")),
        total_tokens=_token_count(raw.get("total_tokens")),
        extra=extra or None,
    )

provider_error(status, detail, headers)

Map an error HTTP status onto the provider-error hierarchy — the one mapping both call paths use, so a pooled call and a direct one report the same failure as the same type.

Source code in src/llmbroker/chat.py
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
def provider_error(status: int, detail: str, headers: Mapping[str, str]) -> ProviderError:
    """Map an error HTTP status onto the provider-error hierarchy — the one mapping
    both call paths use, so a pooled call and a direct one report the same failure
    as the same type."""
    if is_auth_failure(status):
        return AuthError(f"provider rejected the key (HTTP {status})", status=status, detail=detail)
    if is_rate_limit(status):
        retry_after = None
        if headers.get("Retry-After") is not None:
            retry_after = retry_after_seconds(headers, 0)
        return RateLimitError(
            f"provider rate-limited or unavailable (HTTP {status})",
            status=status,
            detail=detail,
            retry_after=retry_after,
        )
    return ProviderError(f"provider returned HTTP {status}", status=status, detail=detail)

retry_after_seconds(headers, default_sec)

Parse Retry-After as either delay-seconds or an HTTP-date, per RFC 9110.

Source code in src/llmbroker/chat.py
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
def retry_after_seconds(headers: Mapping[str, str], default_sec: int) -> int:
    """Parse ``Retry-After`` as either delay-seconds or an HTTP-date, per RFC 9110."""
    raw = headers.get("Retry-After")
    if raw is None:
        return default_sec
    try:
        return int(raw)
    except ValueError:
        pass
    try:
        when = parsedate_to_datetime(raw)
    except (TypeError, ValueError):
        return default_sec
    if when.tzinfo is None:
        when = when.replace(tzinfo=UTC)
    return max(0, int((when - datetime.now(UTC)).total_seconds()))

cli

python -m llmbroker env and list. The CLI reads: a model list is filled by a sync, and a model reached by name is declared in the host's own code.

direct

Direct single-model client: no pool, no failover, no journal. Reuses the request/response primitives in chat.py.

AsyncDirectClient

Async direct client for one named model — stream() and ask(). Pass an httpx.AsyncClient to share a connection pool, or let it open and close its own.

Source code in src/llmbroker/direct.py
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
class AsyncDirectClient:
    """Async direct client for one named model — ``stream()`` and ``ask()``. Pass an
    ``httpx.AsyncClient`` to share a connection pool, or let it open and close its
    own."""

    def __init__(
        self,
        *,
        base_url: str,
        model: str,
        api_key: str,
        timeout: float = _DEFAULT_TIMEOUT,
        client: httpx.AsyncClient | None = None,
    ) -> None:
        self._base_url = base_url
        self._model = model
        self._api_key = api_key
        self._timeout = timeout
        self._http = client
        self._owns_http = client is None

    def _ensure_http(self) -> httpx.AsyncClient:
        if self._http is None:
            self._http = make_client(self._timeout)
        return self._http

    def _request(
        self,
        prompt: str | None,
        messages: list[dict] | None,
        *,
        stream: bool = False,
        params: Mapping[str, object] | None = None,
    ) -> tuple[str, dict[str, str], dict]:
        return build_chat_request(
            self._base_url,
            self._model,
            self._api_key,
            _messages(prompt, messages),
            stream=stream,
            params=params,
        )

    async def ask(
        self,
        prompt: str | None = None,
        *,
        messages: list[dict] | None = None,
        timeout: float | None = None,
        params: Mapping[str, object] | None = None,
    ) -> DirectResult:
        url, headers, body = self._request(prompt, messages, params=params)
        try:
            resp = await self._ensure_http().post(
                url,
                headers=headers,
                json=body,
                timeout=timeout or self._timeout,
            )
        except httpx.TimeoutException as exc:
            raise LLMTimeoutError("direct call timed out") from exc
        return _result(resp, self._model)

    async def stream(
        self,
        prompt: str | None = None,
        *,
        messages: list[dict] | None = None,
        timeout: float | None = None,
        params: Mapping[str, object] | None = None,
    ) -> AsyncIterator[str]:
        url, headers, body = self._request(prompt, messages, stream=True, params=params)
        try:
            async with self._ensure_http().stream(
                "POST",
                url,
                headers=headers,
                json=body,
                timeout=timeout or self._timeout,
            ) as resp:
                if resp.status_code >= ERROR_FLOOR:
                    detail = (await resp.aread()).decode(errors="replace")[:DETAIL_SNIPPET]
                    raise provider_error(resp.status_code, detail, resp.headers)
                produced = False
                async for chunk in aiter_chat_chunks(resp, self._model):
                    delta, _ = parse_stream_chunk(chunk, self._model)
                    if delta:
                        produced = True
                        yield delta
                if not produced:
                    raise empty_answer_error(self._model, NO_DELTA)
        except httpx.TimeoutException as exc:
            raise LLMTimeoutError("direct stream timed out") from exc

    async def aclose(self) -> None:
        if self._owns_http and self._http is not None:
            await self._http.aclose()
            self._http = None

    async def __aenter__(self) -> "AsyncDirectClient":
        return self

    async def __aexit__(self, *exc: object) -> None:
        await self.aclose()

DirectClient

Synchronous direct client for one named model — ask() only, since it is a single POST and needs no event loop. Pass an httpx.Client to share a connection pool, or let it open and close its own.

Source code in src/llmbroker/direct.py
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
class DirectClient:
    """Synchronous direct client for one named model — ``ask()`` only, since it is a
    single ``POST`` and needs no event loop. Pass an ``httpx.Client`` to share a
    connection pool, or let it open and close its own."""

    def __init__(
        self,
        *,
        base_url: str,
        model: str,
        api_key: str,
        timeout: float = _DEFAULT_TIMEOUT,
        client: httpx.Client | None = None,
    ) -> None:
        self._base_url = base_url
        self._model = model
        self._api_key = api_key
        self._timeout = timeout
        self._http = client
        self._owns_http = client is None

    def _ensure_http(self) -> httpx.Client:
        if self._http is None:
            self._http = httpx.Client(timeout=self._timeout)
        return self._http

    def ask(
        self,
        prompt: str | None = None,
        *,
        messages: list[dict] | None = None,
        timeout: float | None = None,
        params: Mapping[str, object] | None = None,
    ) -> DirectResult:
        url, headers, body = build_chat_request(
            self._base_url,
            self._model,
            self._api_key,
            _messages(prompt, messages),
            params=params,
        )
        try:
            resp = self._ensure_http().post(
                url,
                headers=headers,
                json=body,
                timeout=timeout or self._timeout,
            )
        except httpx.TimeoutException as exc:
            raise LLMTimeoutError("direct call timed out") from exc
        return _result(resp, self._model)

    def close(self) -> None:
        if self._owns_http and self._http is not None:
            self._http.close()
            self._http = None

    def __enter__(self) -> "DirectClient":
        return self

    def __exit__(self, *exc: object) -> None:
        self.close()

DirectResult dataclass

The full, non-streaming reply from a direct call.

Source code in src/llmbroker/direct.py
26
27
28
29
30
31
@dataclass(frozen=True, slots=True)
class DirectResult:
    """The full, non-streaming reply from a direct call."""

    text: str
    usage: Usage | None = None

exceptions

All exceptions a caller of llmbroker may catch, in one place.

AuthError

Bases: ProviderError

The key was missing, malformed, or rejected (HTTP 401/403).

Source code in src/llmbroker/exceptions.py
130
131
class AuthError(ProviderError):
    """The key was missing, malformed, or rejected (HTTP 401/403)."""

EmptyRegistryError

Bases: LLMBrokerError

The registry holds no configs — nothing has been synced into it yet.

The request-time sibling is NoLLMAvailableError(reason="empty_pool"): this one says nothing is configured, that one says nothing is usable now.

Source code in src/llmbroker/exceptions.py
16
17
18
19
20
21
class EmptyRegistryError(LLMBrokerError):
    """The registry holds no configs — nothing has been synced into it yet.

    The request-time sibling is ``NoLLMAvailableError(reason="empty_pool")``:
    this one says nothing is configured, that one says nothing is usable now.
    """

InvalidProviderResponseError

Bases: LLMRequestError

The provider answered 200 with a body that is not a chat completion, or with one carrying no text and no tool calls — a provider-side failure like a 5xx, so the router cools the model and fails over.

Source code in src/llmbroker/exceptions.py
109
110
111
112
113
114
115
116
117
class InvalidProviderResponseError(LLMRequestError):
    """The provider answered 200 with a body that is not a chat completion, or with one
    carrying no text and no tool calls — a provider-side failure like a 5xx, so the
    router cools the model and fails over."""

    def __init__(self, message: str, *, model: str, detail: str | None = None) -> None:
        super().__init__(message)
        self.model = model
        self.detail = detail

LLMBrokerError

Bases: RuntimeError

Base: a lifecycle failure — provisioning or storage, not one request.

Subclasses RuntimeError so a host that already catches RuntimeError around provisioning keeps working.

Source code in src/llmbroker/exceptions.py
 8
 9
10
11
12
13
class LLMBrokerError(RuntimeError):
    """Base: a lifecycle failure — provisioning or storage, not one request.

    Subclasses ``RuntimeError`` so a host that already catches ``RuntimeError``
    around provisioning keeps working.
    """

LLMRequestError

Bases: Exception

Base: this request could not be completed.

Source code in src/llmbroker/exceptions.py
50
51
class LLMRequestError(Exception):
    """Base: this request could not be completed."""

LLMTimeoutError

Bases: LLMRequestError

The request did not complete within its timeout.

Source code in src/llmbroker/exceptions.py
88
89
class LLMTimeoutError(LLMRequestError):
    """The request did not complete within its timeout."""

MissingKeyError

Bases: LLMRequestError

The model's api_key_ref could not be resolved, so nothing was sent. Distinct from AuthError, which means a key was sent and rejected.

Source code in src/llmbroker/exceptions.py
83
84
85
class MissingKeyError(LLMRequestError):
    """The model's ``api_key_ref`` could not be resolved, so nothing was sent. Distinct
    from ``AuthError``, which means a key *was* sent and rejected."""

NoLLMAvailableError

Bases: LLMRequestError

No LLM slot was available for this request. reason is one of empty_pool, no_keys, all_disabled, excluded or timeout, the last carrying retry_at where a return time is known.

Source code in src/llmbroker/exceptions.py
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
class NoLLMAvailableError(LLMRequestError):
    """No LLM slot was available for this request. ``reason`` is one of
    ``empty_pool``, ``no_keys``, ``all_disabled``, ``excluded`` or ``timeout``, the
    last carrying ``retry_at`` where a return time is known."""

    def __init__(
        self,
        message: str,
        *,
        reason: str,
        retry_at: datetime | None = None,
    ) -> None:
        super().__init__(message)
        self.reason = reason
        self.retry_at = retry_at

PoolModelError

Bases: LLMRequestError

direct() was pointed at a preset-managed pool entry.

Pool models are anonymous: reach them through ask/chat/stream, which route and learn. A model you want to name is declared with direct=.

Source code in src/llmbroker/exceptions.py
75
76
77
78
79
80
class PoolModelError(LLMRequestError):
    """``direct()`` was pointed at a preset-managed pool entry.

    Pool models are anonymous: reach them through ``ask``/``chat``/``stream``,
    which route and learn. A model you want to name is declared with ``direct=``.
    """

ProviderError

Bases: LLMRequestError

The provider returned an error response: status is the HTTP code, detail a short snippet of the body. Catch this for any provider failure, or a subclass for one kind.

Source code in src/llmbroker/exceptions.py
 98
 99
100
101
102
103
104
105
106
class ProviderError(LLMRequestError):
    """The provider returned an error response: ``status`` is the HTTP code, ``detail``
    a short snippet of the body. Catch this for any provider failure, or a subclass
    for one kind."""

    def __init__(self, message: str, *, status: int, detail: str | None = None) -> None:
        super().__init__(message)
        self.status = status
        self.detail = detail

RateLimitError

Bases: ProviderError

The provider rate-limited or was temporarily unavailable (HTTP 429/503).

retry_after is the server-advised wait in seconds, when the response carried a parseable Retry-After header.

Source code in src/llmbroker/exceptions.py
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
class RateLimitError(ProviderError):
    """The provider rate-limited or was temporarily unavailable (HTTP 429/503).

    ``retry_after`` is the server-advised wait in seconds, when the response
    carried a parseable ``Retry-After`` header.
    """

    def __init__(
        self,
        message: str,
        *,
        status: int,
        detail: str | None = None,
        retry_after: int | None = None,
    ) -> None:
        super().__init__(message, status=status, detail=detail)
        self.retry_after = retry_after

SchemaVersionError

Bases: LLMBrokerError

The store holds a schema version this release cannot use.

Source code in src/llmbroker/exceptions.py
41
42
43
44
45
46
47
class SchemaVersionError(LLMBrokerError):
    """The store holds a schema version this release cannot use."""

    def __init__(self, message: str, *, found: int, expected: int) -> None:
        super().__init__(message)
        self.found = found
        self.expected = expected

StreamInterruptedError

Bases: LLMRequestError

A pooled stream died after it had already emitted deltas. Failover is impossible once output has reached the caller, so the deltas already yielded stand and the rest of the answer is lost.

Source code in src/llmbroker/exceptions.py
120
121
122
123
124
125
126
127
class StreamInterruptedError(LLMRequestError):
    """A pooled stream died after it had already emitted deltas. Failover is
    impossible once output has reached the caller, so the deltas already yielded
    stand and the rest of the answer is lost."""

    def __init__(self, message: str, *, llm_name: str) -> None:
        super().__init__(message)
        self.llm_name = llm_name

SyncRefusedError

Bases: LLMBrokerError

A sync result was not applied — it would have emptied a working registry.

report carries what the merge would have done, so a caller can log or forward the facts that led to the refusal.

Source code in src/llmbroker/exceptions.py
24
25
26
27
28
29
30
31
32
33
class SyncRefusedError(LLMBrokerError):
    """A sync result was not applied — it would have emptied a working registry.

    ``report`` carries what the merge would have done, so a caller can log or
    forward the facts that led to the refusal.
    """

    def __init__(self, message: str, *, report: SyncReport) -> None:
        super().__init__(message)
        self.report = report

ToolLoopLimitError

Bases: LLMRequestError

The tool loop hit max_steps without a tool-call-free reply. Raised rather than returning empty: the contract admits a result or an exception, never silence.

Source code in src/llmbroker/exceptions.py
92
93
94
95
class ToolLoopLimitError(LLMRequestError):
    """The tool loop hit ``max_steps`` without a tool-call-free reply. Raised rather
    than returning empty: the contract admits a result or an exception, never
    silence."""

UnknownCallError

Bases: LLMBrokerError

The key a rating named matched no answered call inside the rating window — it is older than that, was purged by retention, never existed, or never answered.

Source code in src/llmbroker/exceptions.py
36
37
38
class UnknownCallError(LLMBrokerError):
    """The key a rating named matched no answered call inside the rating window — it
    is older than that, was purged by retention, never existed, or never answered."""

UnknownModelError

Bases: LLMRequestError

No registry entry matched the requested model name.

Source code in src/llmbroker/exceptions.py
71
72
class UnknownModelError(LLMRequestError):
    """No registry entry matched the requested model name."""

home

The one directory llmbroker keeps its own cached state in. Nothing here is authoritative, so no step may raise: an unwritable candidate falls through to the next, and nowhere writable is a supported outcome.

home_dir(override=None)

First writable of override, $LLMBROKER_HOME, the platform cache dir, a per-user temp dir. None when nowhere is — every caller then degrades to memory rather than failing.

Source code in src/llmbroker/home.py
76
77
78
79
80
81
82
83
def home_dir(override: str | Path | None = None) -> Path | None:
    """First writable of ``override``, ``$LLMBROKER_HOME``, the platform cache dir, a
    per-user temp dir. ``None`` when nowhere is — every caller then degrades to
    memory rather than failing."""
    for candidate in _candidates(override):
        if _is_writable(candidate):
            return candidate
    return None

home_dir_for_read(override=None)

The directory a read of already-cached state takes: the one named, unprobed. A read writes nothing, so a read-only directory is a perfectly good answer; with nothing named it is home_dir(), where a write would have put the copy.

Source code in src/llmbroker/home.py
86
87
88
89
90
def home_dir_for_read(override: str | Path | None = None) -> Path | None:
    """The directory a read of already-cached state takes: the one named, unprobed.
    A read writes nothing, so a read-only directory is a perfectly good answer; with
    nothing named it is ``home_dir()``, where a write would have put the copy."""
    return Path(override).expanduser() if override is not None else home_dir()

http_status

What a provider's HTTP status means, decided once: pure predicates over a status code, with no package imports, so nothing compares numbers of its own.

is_auth_failure(code)

The key is rejected — drop the model and report the ref as dead.

[is_auth_failure(c) for c in (400, 401, 403, 404, 429, 500)][False, True, True, False, False, False]

Source code in src/llmbroker/http_status.py
33
34
35
36
37
38
39
def is_auth_failure(code: int) -> bool:
    """The key is rejected — drop the model and report the ref as dead.

    >>> [is_auth_failure(c) for c in (400, 401, 403, 404, 429, 500)]
    [False, True, True, False, False, False]
    """
    return code in (_UNAUTHORIZED, _FORBIDDEN)

is_client_error(code)

The request's own fault — fail over without cooling, and surface it once every candidate has said the same.

[is_client_error(c) for c in (399, 400, 401, 403, 404, 418, 429, 499, 500)][False, True, False, False, True, True, False, True, False]

Source code in src/llmbroker/http_status.py
42
43
44
45
46
47
48
49
50
51
def is_client_error(code: int) -> bool:
    """The request's own fault — fail over without cooling, and surface it once
    every candidate has said the same.

    >>> [is_client_error(c) for c in (399, 400, 401, 403, 404, 418, 429, 499, 500)]
    [False, True, False, False, True, True, False, True, False]
    """
    return ERROR_FLOOR <= code < _SERVER_ERROR_FLOOR and not (
        is_rate_limit(code) or is_auth_failure(code)
    )

is_rate_limit(code)

Out of quota or the provider is down — cool the model down and fail over.

[is_rate_limit(c) for c in (200, 399, 429, 499, 500, 503)][False, False, True, False, False, True]

Source code in src/llmbroker/http_status.py
14
15
16
17
18
19
20
def is_rate_limit(code: int) -> bool:
    """Out of quota or the provider is down — cool the model down and fail over.

    >>> [is_rate_limit(c) for c in (200, 399, 429, 499, 500, 503)]
    [False, False, True, False, False, True]
    """
    return code in (_TOO_MANY_REQUESTS, _UNAVAILABLE)

is_unavailable(code)

Of the two rate-limit codes, the one that is the provider being down rather than this key's quota being spent — same cooldown, different journalled status.

[is_unavailable(c) for c in (429, 500, 502, 503)][False, False, False, True]

Source code in src/llmbroker/http_status.py
23
24
25
26
27
28
29
30
def is_unavailable(code: int) -> bool:
    """Of the two rate-limit codes, the one that is the provider being down rather
    than this key's quota being spent — same cooldown, different journalled status.

    >>> [is_unavailable(c) for c in (429, 500, 502, 503)]
    [False, False, False, True]
    """
    return code == _UNAVAILABLE

integrations

Host-side hooks for coexisting with third-party tools (e.g. Alembic).

These modules carry no backend logic and import no drivers; they are thin shims a host wires into another tool's configuration.

alembic

Alembic coexistence hook — autogenerate ignores every llmbroker_* object. Wiring is in docs/ "Servers & clusters"; nothing is imported from Alembic, the hook only inspects the object name.

journal_policy

Journal policy shared by every store: the retention horizon, the purge debounce, and the one shape of a quality record.

PurgeClock

Debounce for the retention purge: due() is true at most once per interval.

Source code in src/llmbroker/journal_policy.py
27
28
29
30
31
32
33
34
35
36
37
38
class PurgeClock:
    """Debounce for the retention purge: ``due()`` is true at most once per interval."""

    def __init__(self) -> None:
        self._last = float("-inf")

    def due(self) -> bool:
        now = time.monotonic()
        if now - self._last < PURGE_INTERVAL:
            return False
        self._last = now
        return True

quality_row(call_id, score, scope)

The appended rating: names the call, carries no model and no operation.

Source code in src/llmbroker/journal_policy.py
15
16
17
18
19
20
21
22
23
24
def quality_row(call_id: str, score: float, scope: str | None) -> dict[str, object]:
    """The appended rating: names the call, carries no model and no operation."""
    return {
        "id": str(uuid.uuid4()),
        "kind": KIND_QUALITY,
        "call_id": call_id,
        "quality_score": score,
        "scope": scope,
        "called_at": datetime.now(UTC),
    }

models

DTOs, enums, and the shared resource-lifecycle protocol for llmbroker.

Pure data and the one cross-cutting capability protocol. No I/O, no driver imports — safe to import from anywhere in the package.

AsyncResourceProtocol

Bases: Protocol

Lifecycle capability for any backend that holds an open resource.

Orthogonal to a backend's data contract. aclose() is idempotent.

Source code in src/llmbroker/models.py
393
394
395
396
397
398
399
400
@runtime_checkable
class AsyncResourceProtocol(Protocol):
    """Lifecycle capability for any backend that holds an open resource.

    Orthogonal to a backend's data contract. ``aclose()`` is idempotent.
    """

    async def aclose(self) -> None: ...

Call dataclass

One call attempt, as the journal holds it. score is the newest host rating of this attempt and is filled on reads only — a rating is its own appended row, never a field the record was written with.

Source code in src/llmbroker/models.py
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
@dataclass(frozen=True, slots=True)
class Call:
    """One call attempt, as the journal holds it. ``score`` is the newest host rating
    of this attempt and is filled on reads only — a rating is its own appended row,
    never a field the record was written with."""

    id: str
    llm_name: str
    operation: str | None
    trace_id: str | None
    status: CallStatus | None
    ts: datetime | None = None
    http_status: int | None = None
    latency_ms: int | None = None
    error_detail: str | None = None
    usage: Usage | None = None
    score: float | None = None
    scope: str | None = None
    cooldown_until: datetime | None = None
    # Set only where the caller's own budget ran out mid-attempt: the bound the
    # model failed to answer within, which is the only latency evidence it left.
    budget_ms: int | None = None

CallStatus

Bases: Enum

How one attempt ended. SUPERSEDED is neutral: a sibling answered first, so it proves neither success nor failure and feeds no routing signal.

Source code in src/llmbroker/models.py
184
185
186
187
188
189
190
191
192
class CallStatus(Enum):
    """How one attempt ended. ``SUPERSEDED`` is neutral: a sibling answered first, so
    it proves neither success nor failure and feeds no routing signal."""

    OK = "ok"
    RATE_LIMITED = "rate_limited"
    UNAVAILABLE = "unavailable"
    ERROR = "error"
    SUPERSEDED = "superseded"

DeclaredModels dataclass

What direct= resolved to: the entries, and where to get the keys they want.

The help travels with the entries because nothing stores a declared model — there is no registry row for a later read to recover it from.

Source code in src/llmbroker/models.py
155
156
157
158
159
160
161
162
163
164
165
166
@dataclass(frozen=True, slots=True)
class DeclaredModels:
    """What ``direct=`` resolved to: the entries, and where to get the keys they want.

    The help travels with the entries because nothing stores a declared model —
    there is no registry row for a later read to recover it from.
    """

    configs: tuple[LLMConfig, ...] = ()
    # Factory, not a plain default: mappingproxy is unhashable before 3.12, and
    # dataclasses reject an unhashable default outright.
    key_help: Mapping[str, str] = field(default_factory=lambda: _NO_KEY_HELP)

KeyInfo dataclass

Per-provider onboarding metadata for one api_key_ref: a help blurb plus a free-form passthrough of whatever else the TOML [keys.REF] section holds — llmbroker has no taxonomy opinion on it.

Source code in src/llmbroker/models.py
40
41
42
43
44
45
46
47
48
@dataclass(frozen=True, slots=True)
class KeyInfo:
    """Per-provider onboarding metadata for one ``api_key_ref``: a help blurb plus
    a free-form passthrough of whatever else the TOML ``[keys.REF]`` section holds —
    llmbroker has no taxonomy opinion on it."""

    api_key_ref: str
    help: str
    extra: dict[str, str]

LLMConfig dataclass

Pure config for one LLM — no secret, safe to expose. from_preset says our curated preset supplied these parameters, and so is what a sync may replace.

Source code in src/llmbroker/models.py
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
@dataclass(frozen=True, slots=True)
class LLMConfig:
    """Pure config for one LLM — no secret, safe to expose. ``from_preset`` says our
    curated preset supplied these parameters, and so is what a sync may replace."""

    name: str
    base_url: str
    model: str
    api_key_ref: str
    parallel: int | None = None
    from_preset: bool = False
    alias: str | None = None
    weight: float = 0.0

    def to_metadata(self) -> dict[str, object]:
        """Structured optional config, serialized for the registry's JSON column.

        Only non-default values are stored, so a plain pool config stays empty.

        >>> LLMConfig(name="g", base_url="https://x/v1", model="m", api_key_ref="K").to_metadata()
        {}
        >>> followed = LLMConfig(name="g", base_url="u", model="m", api_key_ref="K", alias="opus")
        >>> followed.to_metadata()
        {'alias': 'opus'}
        >>> curated = LLMConfig(name="g", base_url="u", model="m", api_key_ref="K")
        >>> replace(curated, from_preset=True).to_metadata()
        {'from_preset': True}
        >>> LLMConfig(name="g", base_url="u", model="m", api_key_ref="K", weight=0.7).to_metadata()
        {'weight': 0.7}
        """
        metadata: dict[str, object] = {}
        if self.parallel is not None:
            metadata["parallel"] = self.parallel
        if self.from_preset:
            metadata["from_preset"] = True
        if self.alias is not None:
            metadata["alias"] = self.alias
        if self.weight:
            metadata["weight"] = self.weight
        return metadata

    @classmethod
    def from_metadata(
        cls,
        *,
        name: str,
        base_url: str,
        model: str,
        api_key_ref: str,
        metadata: dict[str, object] | None,
    ) -> "LLMConfig":
        """Reconstruct from the core columns plus the JSON ``metadata`` blob."""
        metadata = metadata or {}
        raw_parallel = metadata.get("parallel")
        parallel = raw_parallel if isinstance(raw_parallel, int) else None
        raw_from_preset = metadata.get("from_preset")
        from_preset = raw_from_preset if isinstance(raw_from_preset, bool) else False
        raw_alias = metadata.get("alias")
        alias = raw_alias if isinstance(raw_alias, str) else None
        return cls(
            name=name,
            base_url=base_url,
            model=model,
            api_key_ref=api_key_ref,
            parallel=parallel,
            from_preset=from_preset,
            alias=alias,
            weight=_weight_from_metadata(metadata.get("weight"), name),
        )
from_metadata(*, name, base_url, model, api_key_ref, metadata) classmethod

Reconstruct from the core columns plus the JSON metadata blob.

Source code in src/llmbroker/models.py
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
@classmethod
def from_metadata(
    cls,
    *,
    name: str,
    base_url: str,
    model: str,
    api_key_ref: str,
    metadata: dict[str, object] | None,
) -> "LLMConfig":
    """Reconstruct from the core columns plus the JSON ``metadata`` blob."""
    metadata = metadata or {}
    raw_parallel = metadata.get("parallel")
    parallel = raw_parallel if isinstance(raw_parallel, int) else None
    raw_from_preset = metadata.get("from_preset")
    from_preset = raw_from_preset if isinstance(raw_from_preset, bool) else False
    raw_alias = metadata.get("alias")
    alias = raw_alias if isinstance(raw_alias, str) else None
    return cls(
        name=name,
        base_url=base_url,
        model=model,
        api_key_ref=api_key_ref,
        parallel=parallel,
        from_preset=from_preset,
        alias=alias,
        weight=_weight_from_metadata(metadata.get("weight"), name),
    )
to_metadata()

Structured optional config, serialized for the registry's JSON column.

Only non-default values are stored, so a plain pool config stays empty.

LLMConfig(name="g", base_url="https://x/v1", model="m", api_key_ref="K").to_metadata() {} followed = LLMConfig(name="g", base_url="u", model="m", api_key_ref="K", alias="opus") followed.to_metadata() {'alias': 'opus'} curated = LLMConfig(name="g", base_url="u", model="m", api_key_ref="K") replace(curated, from_preset=True).to_metadata() {'from_preset': True} LLMConfig(name="g", base_url="u", model="m", api_key_ref="K", weight=0.7).to_metadata()

Source code in src/llmbroker/models.py
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
def to_metadata(self) -> dict[str, object]:
    """Structured optional config, serialized for the registry's JSON column.

    Only non-default values are stored, so a plain pool config stays empty.

    >>> LLMConfig(name="g", base_url="https://x/v1", model="m", api_key_ref="K").to_metadata()
    {}
    >>> followed = LLMConfig(name="g", base_url="u", model="m", api_key_ref="K", alias="opus")
    >>> followed.to_metadata()
    {'alias': 'opus'}
    >>> curated = LLMConfig(name="g", base_url="u", model="m", api_key_ref="K")
    >>> replace(curated, from_preset=True).to_metadata()
    {'from_preset': True}
    >>> LLMConfig(name="g", base_url="u", model="m", api_key_ref="K", weight=0.7).to_metadata()
    {'weight': 0.7}
    """
    metadata: dict[str, object] = {}
    if self.parallel is not None:
        metadata["parallel"] = self.parallel
    if self.from_preset:
        metadata["from_preset"] = True
    if self.alias is not None:
        metadata["alias"] = self.alias
    if self.weight:
        metadata["weight"] = self.weight
    return metadata

LLMMetrics dataclass

Per-LLM admin read-model derived from Call rows.

Source code in src/llmbroker/models.py
293
294
295
296
297
298
299
@dataclass(frozen=True, slots=True)
class LLMMetrics:
    """Per-LLM admin read-model derived from Call rows."""

    call_count: int
    last_status: CallStatus | None
    last_at: datetime | None

LLMSnapshot dataclass

Frozen point-in-time materialization of one LLM: raw facts, no status enum. demoted_operations may contain None — the bucket for calls made with no operation= label.

Source code in src/llmbroker/models.py
317
318
319
320
321
322
323
324
325
326
327
328
@dataclass(frozen=True, slots=True)
class LLMSnapshot:
    """Frozen point-in-time materialization of one LLM: raw facts, no status enum.
    ``demoted_operations`` may contain ``None`` — the bucket for calls made with no
    ``operation=`` label."""

    config: LLMConfig
    disabled: bool
    has_key: bool
    cooldown_until: datetime | None
    demoted_operations: tuple[str | None, ...]
    metrics: LLMMetrics | None

LLMState dataclass

Snapshot of one LLM's live runtime state, built fresh on each read.

Source code in src/llmbroker/models.py
31
32
33
34
35
36
37
@dataclass(frozen=True, slots=True)
class LLMState:
    """Snapshot of one LLM's live runtime state, built fresh on each read."""

    phase: LifecyclePhase = LifecyclePhase.AVAILABLE
    cooldown_until: datetime | None = None
    fail_count: int = 0

LLMStats dataclass

Per-LLM aggregate of call records over a time window.

by_status holds only statuses actually seen, so "how many were not OK" is a subtraction from total, not an assumption about the enum's shape.

Source code in src/llmbroker/models.py
302
303
304
305
306
307
308
309
310
311
312
313
314
@dataclass(frozen=True, slots=True)
class LLMStats:
    """Per-LLM aggregate of call records over a time window.

    ``by_status`` holds only statuses actually seen, so "how many were not OK" is
    a subtraction from ``total``, not an assumption about the enum's shape.
    """

    total: int
    by_status: Mapping[CallStatus, int]
    first_at: datetime | None
    last_at: datetime | None
    last_status: CallStatus | None

LifecyclePhase

Bases: Enum

The FSM label for one LLM's lifecycle, always derived from cooldown_until vs now.

Source code in src/llmbroker/models.py
24
25
26
27
28
class LifecyclePhase(Enum):
    """The FSM label for one LLM's lifecycle, always derived from cooldown_until vs now."""

    AVAILABLE = "available"
    COOLING = "cooling"

ModelList dataclass

A set of entries and the key help that goes with them — read from a source, or produced by a merge. keys is keyed by api_key_ref.

Source code in src/llmbroker/models.py
136
137
138
139
140
141
142
@dataclass(frozen=True, slots=True)
class ModelList:
    """A set of entries and the key help that goes with them — read from a source,
    or produced by a merge. ``keys`` is keyed by ``api_key_ref``."""

    configs: list[LLMConfig] = field(default_factory=list)
    keys: dict[str, KeyInfo] = field(default_factory=dict)

PendingKey dataclass

One api_key_ref a synced model list wants and the secrets store does not have, with the entries it holds back inactive until it resolves.

Source code in src/llmbroker/models.py
145
146
147
148
149
150
151
152
@dataclass(frozen=True, slots=True)
class PendingKey:
    """One ``api_key_ref`` a synced model list wants and the secrets store does not have,
    with the entries it holds back inactive until it resolves."""

    api_key_ref: str
    help: str
    entry_names: tuple[str, ...]

PoolHealth dataclass

How much of the pool can actually serve a request, by provider.

The unit is the api_key_ref: two entries on one ref are one quota and one failure domain. missing_keys names only refs no managed entry can use.

Source code in src/llmbroker/models.py
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
@dataclass(frozen=True, slots=True)
class PoolHealth:
    """How much of the pool can actually serve a request, by provider.

    The unit is the ``api_key_ref``: two entries on one ref are one quota and one
    failure domain. ``missing_keys`` names only refs no managed entry can use.
    """

    providers_usable: int = 0
    providers_total: int = 0
    missing_keys: tuple[PendingKey, ...] = ()

    @property
    def degraded(self) -> bool:
        """One usable provider is a single quota with nothing to fail over to. A
        registry that pools nothing has no pool to degrade."""
        return bool(self.providers_total) and self.providers_usable < _MIN_USABLE_PROVIDERS
degraded property

One usable provider is a single quota with nothing to fail over to. A registry that pools nothing has no pool to degrade.

PoolSnapshot dataclass

Bases: Mapping[str, LLMSnapshot]

Point-in-time view of the whole pool. Iterate it like a dict of name -> LLMSnapshot; the properties describe the pool as a whole and come from the same measurement the degradation alarm uses.

Source code in src/llmbroker/models.py
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
@dataclass(frozen=True, slots=True)
class PoolSnapshot(Mapping[str, LLMSnapshot]):
    """Point-in-time view of the whole pool. Iterate it like a dict of
    ``name -> LLMSnapshot``; the properties describe the pool as a whole and come
    from the same measurement the degradation alarm uses."""

    _llms: Mapping[str, LLMSnapshot]
    _health: PoolHealth
    _direct_missing_keys: tuple[PendingKey, ...] = ()

    @property
    def providers_usable(self) -> int:
        return self._health.providers_usable

    @property
    def providers_total(self) -> int:
        return self._health.providers_total

    @property
    def missing_keys(self) -> tuple[PendingKey, ...]:
        return self._health.missing_keys

    @property
    def direct_missing_keys(self) -> tuple[PendingKey, ...]:
        """Refs the host's own ``direct``-reachable models want and cannot resolve.
        Kept apart from ``missing_keys``, which counts the pool's failover capacity —
        a model that is never routed can neither degrade nor repair it."""
        return self._direct_missing_keys

    @property
    def degraded(self) -> bool:
        return self._health.degraded

    def __getitem__(self, name: str) -> LLMSnapshot:
        return self._llms[name]

    def __iter__(self) -> Iterator[str]:
        return iter(self._llms)

    def __len__(self) -> int:
        return len(self._llms)
direct_missing_keys property

Refs the host's own direct-reachable models want and cannot resolve. Kept apart from missing_keys, which counts the pool's failover capacity — a model that is never routed can neither degrade nor repair it.

SyncReport dataclass

What one sync did, as raw facts — no severity verdict, the host derives that.

Source code in src/llmbroker/models.py
169
170
171
172
173
174
175
176
177
178
179
180
181
@dataclass(frozen=True, slots=True)
class SyncReport:
    """What one sync did, as raw facts — no severity verdict, the host derives that."""

    source: str
    applied: bool
    added: tuple[str, ...] = ()
    updated: tuple[str, ...] = ()
    removed: tuple[str, ...] = ()
    orphan_refs: tuple[str, ...] = ()
    pending_keys: tuple[PendingKey, ...] = ()
    active_before: int = 0
    active_after: int = 0

Usage dataclass

Resource use the provider reported for one call.

Source code in src/llmbroker/models.py
195
196
197
198
199
200
201
202
@dataclass(frozen=True, slots=True)
class Usage:
    """Resource use the provider reported for one call."""

    prompt_tokens: int | None = None
    completion_tokens: int | None = None
    total_tokens: int | None = None
    extra: dict[str, int] | None = None

check_aliases(configs)

Reject a registry carrying one alias twice. Enforced on every read, not just a file parse: a lookup returns the first match.

Source code in src/llmbroker/models.py
279
280
281
282
283
284
285
286
287
288
289
290
def check_aliases(configs: "list[LLMConfig]") -> None:
    """Reject a registry carrying one alias twice. Enforced on every read, not just a
    file parse: a lookup returns the first match."""
    seen: set[str] = set()
    for cfg in configs:
        if cfg.alias is None:
            continue
        if cfg.alias in seen:
            raise ValueError(
                f"Registry: duplicate alias {cfg.alias!r} — an alias names exactly one entry",
            )
        seen.add(cfg.alias)

check_limit(limit)

Reject a non-positive journal read limit — backends disagree on what one means, and pymongo reads limit=0 as no limit.

Source code in src/llmbroker/models.py
258
259
260
261
262
def check_limit(limit: int) -> None:
    """Reject a non-positive journal read limit — backends disagree on what one
    means, and pymongo reads ``limit=0`` as *no limit*."""
    if limit < 1:
        raise ValueError(f"limit must be >= 1, got {limit}")

check_score(score)

Reject a quality score outside [0, 1] — the Wilson bound the optimizer derives from the window is only defined on that interval.

Source code in src/llmbroker/models.py
265
266
267
268
269
def check_score(score: float) -> None:
    """Reject a quality score outside ``[0, 1]`` — the Wilson bound the optimizer
    derives from the window is only defined on that interval."""
    if not 0.0 <= score <= 1.0:
        raise ValueError(f"quality score must be within [0.0, 1.0], got {score}")

check_weight(weight)

Reject a curated weight outside [0, 1] — it is a prior on the same scale as a host quality rating, and blends with one.

Source code in src/llmbroker/models.py
272
273
274
275
276
def check_weight(weight: float) -> None:
    """Reject a curated weight outside ``[0, 1]`` — it is a prior on the same scale as
    a host quality rating, and blends with one."""
    if not 0.0 <= weight <= 1.0:
        raise ValueError(f"weight must be within [0.0, 1.0], got {weight}")

to_utc(value, field)

Pin an instant to UTC; refuse a naive one rather than guess its zone.

from datetime import timedelta, timezone to_utc(datetime(2030, 1, 1, 5, tzinfo=timezone(timedelta(hours=5))), "since") datetime.datetime(2030, 1, 1, 0, 0, tzinfo=datetime.timezone.utc)

Source code in src/llmbroker/models.py
229
230
231
232
233
234
235
236
237
238
239
240
def to_utc(value: datetime, field: str) -> datetime:
    """Pin an instant to UTC; refuse a naive one rather than guess its zone.

    >>> from datetime import timedelta, timezone
    >>> to_utc(datetime(2030, 1, 1, 5, tzinfo=timezone(timedelta(hours=5))), "since")
    datetime.datetime(2030, 1, 1, 0, 0, tzinfo=datetime.timezone.utc)
    """
    if not isinstance(value, datetime):
        raise TypeError(f"{field} must be a datetime, got {type(value).__name__}")
    if value.tzinfo is None:
        raise ValueError(f"{field} must be timezone-aware, e.g. datetime.now(UTC)")
    return value.astimezone(UTC)

with_utc_timestamps(call)

Stamp an unset ts and pin both journal instants to UTC.

The write side of the same rule the read bound follows: these two fields are what the journal orders, windows, and expires by.

Source code in src/llmbroker/models.py
243
244
245
246
247
248
249
250
251
252
253
254
255
def with_utc_timestamps(call: "Call") -> "Call":
    """Stamp an unset ``ts`` and pin both journal instants to UTC.

    The write side of the same rule the read bound follows: these two fields are
    what the journal orders, windows, and expires by.
    """
    ts = to_utc(call.ts, "Call.ts") if call.ts is not None else datetime.now(UTC)
    cooldown = (
        to_utc(call.cooldown_until, "Call.cooldown_until")
        if call.cooldown_until is not None
        else None
    )
    return replace(call, ts=ts, cooldown_until=cooldown)

mongodb

MongoDB backend: registry, store and secrets. Needs motor (llmbroker[mongodb]); importing this package is how a host declares that dependency.

Registry

Bases: DriverRegistry

MongoDB-backed mutable registry over llmbroker_registry — a pure preset mirror.

Source code in src/llmbroker/mongodb/registry.py
 9
10
11
12
13
class Registry(DriverRegistry):
    """MongoDB-backed mutable registry over ``llmbroker_registry`` — a pure preset mirror."""

    def __init__(self, db: AsyncIOMotorDatabase) -> None:
        super().__init__(MongoDriver(db))

Secrets

Bases: DriverSecrets

MongoDB-backed mutable secrets store over llmbroker_secrets.

Source code in src/llmbroker/mongodb/secrets.py
 9
10
11
12
13
class Secrets(DriverSecrets):
    """MongoDB-backed mutable secrets store over ``llmbroker_secrets``."""

    def __init__(self, db: AsyncIOMotorDatabase) -> None:
        super().__init__(MongoDriver(db))

Store

Bases: DriverStore

MongoDB-backed queryable store over llmbroker_calls + the llmbroker_disabled admin verdict map.

Source code in src/llmbroker/mongodb/store.py
13
14
15
16
17
18
19
20
21
22
23
class Store(DriverStore):
    """MongoDB-backed queryable store over ``llmbroker_calls`` + the
    ``llmbroker_disabled`` admin verdict map."""

    def __init__(
        self,
        db: AsyncIOMotorDatabase,
        *,
        retention: timedelta = RETENTION_DEFAULT,
    ) -> None:
        super().__init__(MongoDriver(db), retention=retention)

driver

MongoDB Driver: renders indexes from backends.spec.TABLES.

One known installation, upgraded manually — on a version-marker mismatch ensure_schema fails fast instead of attempting an in-place migration.

MongoDriver

One Mongo database backing every llmbroker_* collection.

Source code in src/llmbroker/mongodb/driver.py
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
class MongoDriver:
    """One Mongo database backing every ``llmbroker_*`` collection."""

    def __init__(
        self,
        db: AsyncIOMotorDatabase,
        *,
        client: AsyncIOMotorClient | None = None,
    ) -> None:
        """Pass ``client=`` only when the driver should own (and close) it — the
        explicit-``db`` facade use case does not own the caller's client."""
        self._db = db
        self._client = client
        self._schema_ready = False
        self._schema_lock = asyncio.Lock()

    async def ensure_schema(self) -> None:
        """A plain per-instance flag: one driver per stack is app-lifetime, so this
        gives one check per process under intended usage. Not ``id(db)``-keyed —
        after GC an id can be reused by a different database and falsely skip creation."""
        if self._schema_ready:
            return
        async with self._schema_lock:
            if self._schema_ready:
                return
            version_doc = await self._db["llmbroker_schema_version"].find_one({})
            current = int(version_doc["version"]) if version_doc else 0
            if current not in (0, SCHEMA_VERSION):
                raise SchemaVersionError(
                    f"llmbroker schema version {current} found, this release expects"
                    f" {SCHEMA_VERSION} — drop the llmbroker_* collections and restart"
                    " (export registry/secrets/calls first if you need them)",
                    found=current,
                    expected=SCHEMA_VERSION,
                )
            for spec in TABLES.values():
                if spec.key:
                    await self._db[spec.name].create_index(
                        [(k, 1) for k in spec.key],
                        unique=True,
                        name=f"{spec.name}_unique",
                    )
                for idx_cols in spec.indexes:
                    await self._db[spec.name].create_index(
                        [(k, 1) for k in idx_cols],
                        name=f"{spec.name}_idx_{'_'.join(idx_cols)}",
                    )
            if current == 0:
                await self._db["llmbroker_schema_version"].replace_one(
                    {},
                    {"version": SCHEMA_VERSION},
                    upsert=True,
                )
            self._schema_ready = True

    async def fetch(self, table: str) -> list[Row]:
        spec = TABLES[table]
        await self.ensure_schema()
        sort = [(k, 1) for k in spec.key] if spec.key else None
        cursor = self._db[spec.name].find({})
        if sort:
            cursor = cursor.sort(sort)
        docs = await cursor.to_list(length=None)
        return [_decode_doc(d, spec) for d in docs]

    async def get(self, table: str, key: Key) -> Row | None:
        spec = TABLES[table]
        await self.ensure_schema()
        doc = await self._db[spec.name].find_one(_key_filter(spec, key))
        return _decode_doc(doc, spec) if doc is not None else None

    async def upsert(self, table: str, key: Key, row: Row) -> None:
        spec = TABLES[table]
        await self.ensure_schema()
        await self._db[spec.name].update_one(
            _key_filter(spec, key),
            {"$set": row},
            upsert=True,
        )

    async def delete(self, table: str, key: Key) -> bool:
        spec = TABLES[table]
        await self.ensure_schema()
        result = await self._db[spec.name].delete_one(_key_filter(spec, key))
        return result.deleted_count > 0

    async def append(self, table: str, row: Row) -> None:
        spec = TABLES[table]
        await self.ensure_schema()
        await self._db[spec.name].insert_one(dict(row))

    async def journal_view(
        self,
        limit: int,
        match: Row | None = None,
        since: datetime | None = None,
    ) -> list[Row]:
        spec = TABLES["calls"]
        await self.ensure_schema()
        query: dict[str, object] = dict(match) if match else {}
        query["kind"] = KIND_CALL
        if since is not None:
            query["called_at"] = {"$gte": since}
        # The lookup runs on the limited page, so it keeps the page at exactly
        # ``limit`` calls. ``$arrayElemAt``, not ``$first``: the latter is 4.4+.
        pipeline: list[dict[str, object]] = [
            {"$match": query},
            {"$sort": {"called_at": -1}},
            {"$limit": limit},
            {
                "$lookup": {
                    "from": spec.name,
                    "let": {"cid": "$id"},
                    "pipeline": [
                        {
                            "$match": {
                                "$expr": {
                                    "$and": [
                                        {"$eq": ["$kind", KIND_QUALITY]},
                                        {"$eq": ["$call_id", "$$cid"]},
                                    ],
                                },
                            },
                        },
                        {"$sort": {"called_at": -1}},
                        {"$limit": 1},
                    ],
                    "as": "_rating",
                },
            },
            {"$addFields": {"score": {"$arrayElemAt": ["$_rating.quality_score", 0]}}},
        ]
        docs = await self._db[spec.name].aggregate(pipeline).to_list(length=None)
        return [{**_decode_doc(d, spec), "score": d.get("score")} for d in docs]

    async def purge(self, table: str, before: datetime) -> int:
        spec = TABLES[table]
        await self.ensure_schema()
        result = await self._db[spec.name].delete_many({"called_at": {"$lt": before}})
        return int(result.deleted_count)

    async def aclose(self) -> None:
        if self._client is not None:
            client, self._client = self._client, None
            client.close()
__init__(db, *, client=None)

Pass client= only when the driver should own (and close) it — the explicit-db facade use case does not own the caller's client.

Source code in src/llmbroker/mongodb/driver.py
43
44
45
46
47
48
49
50
51
52
53
54
def __init__(
    self,
    db: AsyncIOMotorDatabase,
    *,
    client: AsyncIOMotorClient | None = None,
) -> None:
    """Pass ``client=`` only when the driver should own (and close) it — the
    explicit-``db`` facade use case does not own the caller's client."""
    self._db = db
    self._client = client
    self._schema_ready = False
    self._schema_lock = asyncio.Lock()
ensure_schema() async

A plain per-instance flag: one driver per stack is app-lifetime, so this gives one check per process under intended usage. Not id(db)-keyed — after GC an id can be reused by a different database and falsely skip creation.

Source code in src/llmbroker/mongodb/driver.py
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
async def ensure_schema(self) -> None:
    """A plain per-instance flag: one driver per stack is app-lifetime, so this
    gives one check per process under intended usage. Not ``id(db)``-keyed —
    after GC an id can be reused by a different database and falsely skip creation."""
    if self._schema_ready:
        return
    async with self._schema_lock:
        if self._schema_ready:
            return
        version_doc = await self._db["llmbroker_schema_version"].find_one({})
        current = int(version_doc["version"]) if version_doc else 0
        if current not in (0, SCHEMA_VERSION):
            raise SchemaVersionError(
                f"llmbroker schema version {current} found, this release expects"
                f" {SCHEMA_VERSION} — drop the llmbroker_* collections and restart"
                " (export registry/secrets/calls first if you need them)",
                found=current,
                expected=SCHEMA_VERSION,
            )
        for spec in TABLES.values():
            if spec.key:
                await self._db[spec.name].create_index(
                    [(k, 1) for k in spec.key],
                    unique=True,
                    name=f"{spec.name}_unique",
                )
            for idx_cols in spec.indexes:
                await self._db[spec.name].create_index(
                    [(k, 1) for k in idx_cols],
                    name=f"{spec.name}_idx_{'_'.join(idx_cols)}",
                )
        if current == 0:
            await self._db["llmbroker_schema_version"].replace_one(
                {},
                {"version": SCHEMA_VERSION},
                upsert=True,
            )
        self._schema_ready = True

registry

MongoDB-backed mutable registry over llmbroker_registry — a pure preset mirror.

Registry

Bases: DriverRegistry

MongoDB-backed mutable registry over llmbroker_registry — a pure preset mirror.

Source code in src/llmbroker/mongodb/registry.py
 9
10
11
12
13
class Registry(DriverRegistry):
    """MongoDB-backed mutable registry over ``llmbroker_registry`` — a pure preset mirror."""

    def __init__(self, db: AsyncIOMotorDatabase) -> None:
        super().__init__(MongoDriver(db))

secrets

MongoDB-backed mutable secrets store over llmbroker_secrets.

Secrets

Bases: DriverSecrets

MongoDB-backed mutable secrets store over llmbroker_secrets.

Source code in src/llmbroker/mongodb/secrets.py
 9
10
11
12
13
class Secrets(DriverSecrets):
    """MongoDB-backed mutable secrets store over ``llmbroker_secrets``."""

    def __init__(self, db: AsyncIOMotorDatabase) -> None:
        super().__init__(MongoDriver(db))

store

MongoDB-backed queryable store over llmbroker_calls + the llmbroker_disabled admin verdict map.

Store

Bases: DriverStore

MongoDB-backed queryable store over llmbroker_calls + the llmbroker_disabled admin verdict map.

Source code in src/llmbroker/mongodb/store.py
13
14
15
16
17
18
19
20
21
22
23
class Store(DriverStore):
    """MongoDB-backed queryable store over ``llmbroker_calls`` + the
    ``llmbroker_disabled`` admin verdict map."""

    def __init__(
        self,
        db: AsyncIOMotorDatabase,
        *,
        retention: timedelta = RETENTION_DEFAULT,
    ) -> None:
        super().__init__(MongoDriver(db), retention=retention)

optimizer

The Optimizer knob — per-LLM failure bookkeeping, and per-(model, operation) quality-window demotion verdicts feeding the pool's demoted-last selection order.

Optimizer dataclass

Consecutive-failure counter for backoff, plus per-(model, operation) sliding windows of raw quality ratings backing the demoted-last selection order.

Source code in src/llmbroker/optimizer.py
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
@dataclass
class Optimizer:
    """Consecutive-failure counter for backoff, plus per-(model, operation) sliding
    windows of raw quality ratings backing the demoted-last selection order."""

    max_delay: float = 3600.0
    backoff_factor: float = 2.0
    quality_floor: float = 0.3
    quality_confidence: float = 0.95  # z for the Wilson upper bound
    quality_window: int = 30  # ratings kept per (model, operation)
    quality_min_count: int = 10  # verdicts need at least this many
    # Pseudo-ratings the curated weight is worth on an empty window; it loses its
    # majority about where a demotion verdict becomes expressible at all.
    prior_strength: float = 10.0

    _rl_fail_count: dict[str, int] = field(default_factory=dict, init=False, repr=False)
    # Oldest first, one entry per rated call: the id is what keeps a re-rating from
    # counting as a second observation.
    _scores: dict[tuple[str, str | None], deque[tuple[str, float]]] = field(
        default_factory=dict,
        init=False,
        repr=False,
    )

    def rl_fail_count(self, llm_name: str) -> int:
        return self._rl_fail_count.get(llm_name, 0)

    def on_rate_limited(self, llm_name: str) -> None:
        """Increment the consecutive-failure count the router reads for its backoff exponent."""
        self._rl_fail_count[llm_name] = self._rl_fail_count.get(llm_name, 0) + 1

    def on_success(self, llm_name: str) -> None:
        self._rl_fail_count[llm_name] = 0

    # ------------------------------------------------------------------
    # Quality windows + derived per-operation demotion verdicts
    # ------------------------------------------------------------------

    def wilson_bound(self, llm_name: str, operation: str | None) -> float | None:
        """The Wilson-score upper bound backing ``is_demoted``, for diagnostics."""
        window = self._scores.get((llm_name, operation))
        if not window:
            return None
        return wilson_upper([score for _, score in window], _z_score(self.quality_confidence))

    def quality_score(self, llm_name: str, operation: str | None, weight: float) -> float:
        """The curated weight, displaced by the observed window as that window fills.
        Not the Wilson bound, whose optimism on thin evidence would make the
        best-sampled model yield to a barely-tried one.

        >>> Optimizer().quality_score("m", None, 0.8)  # nothing observed yet
        0.8
        """
        window = self._scores.get((llm_name, operation))
        if not window:
            return weight
        n = len(window)
        mean = sum(score for _, score in window) / n
        strength = self.prior_strength * max(0.0, 1.0 - n / self.quality_window)
        if not strength:
            return mean
        return (n * mean + strength * weight) / (n + strength)

    def is_demoted(self, llm_name: str, operation: str | None) -> bool:
        """True iff the window holds at least ``quality_min_count`` ratings and their
        Wilson-score upper bound sits below ``quality_floor``."""
        window = self._scores.get((llm_name, operation))
        if window is None or len(window) < self.quality_min_count:
            return False
        bound = wilson_upper([score for _, score in window], _z_score(self.quality_confidence))
        return bound < self.quality_floor

    def demoted_operations(self, llm_name: str) -> frozenset[str | None]:
        return frozenset(
            operation
            for name, operation in self._scores
            if name == llm_name and self.is_demoted(name, operation)
        )

    def record_quality(
        self,
        llm_name: str,
        operation: str | None,
        call_id: str,
        score: float,
    ) -> None:
        """Fold one call's rating into the (model, operation) window, oldest evicted
        first. A call already in the window keeps its place and takes the new value:
        one rated call is one observation however often the host changes its mind."""
        before = self.is_demoted(llm_name, operation)
        window = self._scores.setdefault(
            (llm_name, operation),
            deque(maxlen=self.quality_window),
        )
        for i, (rated, _) in enumerate(window):
            if rated == call_id:
                window[i] = (call_id, score)
                break
        else:
            window.append((call_id, score))
        self._log_flip(llm_name, operation, before, self.is_demoted(llm_name, operation))

    def load_scores(self, scores: dict[tuple[str, str | None], list[tuple[str, float]]]) -> None:
        """Replace every window wholesale — used by the journal rebuild. Values come
        oldest first, so the next rating evicts the oldest rated call and not the
        newest one the rebuild just put in."""
        keys = set(self._scores) | set(scores)
        before = {key: self.is_demoted(*key) for key in keys}
        self._scores = {
            key: deque(values[-self.quality_window :], maxlen=self.quality_window)
            for key, values in scores.items()
        }
        for key in keys:
            self._log_flip(key[0], key[1], before[key], self.is_demoted(*key))

    def _log_flip(
        self,
        llm_name: str,
        operation: str | None,
        before: bool,  # noqa: FBT001
        after: bool,  # noqa: FBT001
    ) -> None:
        if before == after:
            return
        bound = self.wilson_bound(llm_name, operation)
        if after:
            if bound is not None:
                logger.warning(
                    "%s: quality-demoted for operation=%r (wilson upper %.3f < floor %.2f)",
                    llm_name,
                    operation,
                    bound,
                    self.quality_floor,
                )
            else:
                logger.warning("%s: quality-demoted for operation=%r", llm_name, operation)
        elif bound is not None:
            logger.info(
                "%s: quality demotion cleared for operation=%r (wilson upper %.3f)",
                llm_name,
                operation,
                bound,
            )
        else:
            logger.info("%s: quality demotion cleared for operation=%r", llm_name, operation)
is_demoted(llm_name, operation)

True iff the window holds at least quality_min_count ratings and their Wilson-score upper bound sits below quality_floor.

Source code in src/llmbroker/optimizer.py
 95
 96
 97
 98
 99
100
101
102
def is_demoted(self, llm_name: str, operation: str | None) -> bool:
    """True iff the window holds at least ``quality_min_count`` ratings and their
    Wilson-score upper bound sits below ``quality_floor``."""
    window = self._scores.get((llm_name, operation))
    if window is None or len(window) < self.quality_min_count:
        return False
    bound = wilson_upper([score for _, score in window], _z_score(self.quality_confidence))
    return bound < self.quality_floor
load_scores(scores)

Replace every window wholesale — used by the journal rebuild. Values come oldest first, so the next rating evicts the oldest rated call and not the newest one the rebuild just put in.

Source code in src/llmbroker/optimizer.py
134
135
136
137
138
139
140
141
142
143
144
145
def load_scores(self, scores: dict[tuple[str, str | None], list[tuple[str, float]]]) -> None:
    """Replace every window wholesale — used by the journal rebuild. Values come
    oldest first, so the next rating evicts the oldest rated call and not the
    newest one the rebuild just put in."""
    keys = set(self._scores) | set(scores)
    before = {key: self.is_demoted(*key) for key in keys}
    self._scores = {
        key: deque(values[-self.quality_window :], maxlen=self.quality_window)
        for key, values in scores.items()
    }
    for key in keys:
        self._log_flip(key[0], key[1], before[key], self.is_demoted(*key))
on_rate_limited(llm_name)

Increment the consecutive-failure count the router reads for its backoff exponent.

Source code in src/llmbroker/optimizer.py
59
60
61
def on_rate_limited(self, llm_name: str) -> None:
    """Increment the consecutive-failure count the router reads for its backoff exponent."""
    self._rl_fail_count[llm_name] = self._rl_fail_count.get(llm_name, 0) + 1
quality_score(llm_name, operation, weight)

The curated weight, displaced by the observed window as that window fills. Not the Wilson bound, whose optimism on thin evidence would make the best-sampled model yield to a barely-tried one.

Optimizer().quality_score("m", None, 0.8) # nothing observed yet 0.8

Source code in src/llmbroker/optimizer.py
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
def quality_score(self, llm_name: str, operation: str | None, weight: float) -> float:
    """The curated weight, displaced by the observed window as that window fills.
    Not the Wilson bound, whose optimism on thin evidence would make the
    best-sampled model yield to a barely-tried one.

    >>> Optimizer().quality_score("m", None, 0.8)  # nothing observed yet
    0.8
    """
    window = self._scores.get((llm_name, operation))
    if not window:
        return weight
    n = len(window)
    mean = sum(score for _, score in window) / n
    strength = self.prior_strength * max(0.0, 1.0 - n / self.quality_window)
    if not strength:
        return mean
    return (n * mean + strength * weight) / (n + strength)
record_quality(llm_name, operation, call_id, score)

Fold one call's rating into the (model, operation) window, oldest evicted first. A call already in the window keeps its place and takes the new value: one rated call is one observation however often the host changes its mind.

Source code in src/llmbroker/optimizer.py
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
def record_quality(
    self,
    llm_name: str,
    operation: str | None,
    call_id: str,
    score: float,
) -> None:
    """Fold one call's rating into the (model, operation) window, oldest evicted
    first. A call already in the window keeps its place and takes the new value:
    one rated call is one observation however often the host changes its mind."""
    before = self.is_demoted(llm_name, operation)
    window = self._scores.setdefault(
        (llm_name, operation),
        deque(maxlen=self.quality_window),
    )
    for i, (rated, _) in enumerate(window):
        if rated == call_id:
            window[i] = (call_id, score)
            break
    else:
        window.append((call_id, score))
    self._log_flip(llm_name, operation, before, self.is_demoted(llm_name, operation))
wilson_bound(llm_name, operation)

The Wilson-score upper bound backing is_demoted, for diagnostics.

Source code in src/llmbroker/optimizer.py
70
71
72
73
74
75
def wilson_bound(self, llm_name: str, operation: str | None) -> float | None:
    """The Wilson-score upper bound backing ``is_demoted``, for diagnostics."""
    window = self._scores.get((llm_name, operation))
    if not window:
        return None
    return wilson_upper([score for _, score in window], _z_score(self.quality_confidence))

wilson_upper(scores, z)

Wilson-score upper bound of the mean of scores at confidence z.

round(wilson_upper([1.0] * 25 + [0.0] * 5, 1.96), 4) 0.9266

Source code in src/llmbroker/optimizer.py
17
18
19
20
21
22
23
24
25
26
27
28
29
def wilson_upper(scores: list[float], z: float) -> float:
    """Wilson-score upper bound of the mean of ``scores`` at confidence ``z``.

    >>> round(wilson_upper([1.0] * 25 + [0.0] * 5, 1.96), 4)
    0.9266
    """
    n = len(scores)
    p = sum(scores) / n
    z2 = z * z
    center = p + z2 / (2 * n)
    margin = z * ((p * (1 - p) / n) + z2 / (4 * n * n)) ** 0.5
    denom = 1 + z2 / n
    return (center + margin) / denom

postgres

Postgres backend: registry, store and secrets. Needs asyncpg (llmbroker[postgres]); importing this package is how a host declares that dependency.

Registry

Bases: DriverRegistry

Postgres-backed mutable registry over llmbroker_registry — a pure preset mirror.

Source code in src/llmbroker/postgres/registry.py
 9
10
11
12
13
class Registry(DriverRegistry):
    """Postgres-backed mutable registry over ``llmbroker_registry`` — a pure preset mirror."""

    def __init__(self, pool: asyncpg.Pool) -> None:
        super().__init__(PostgresDriver(pool))

Secrets

Bases: DriverSecrets

Postgres-backed mutable secrets store over llmbroker_secrets.

Source code in src/llmbroker/postgres/secrets.py
 9
10
11
12
13
class Secrets(DriverSecrets):
    """Postgres-backed mutable secrets store over ``llmbroker_secrets``."""

    def __init__(self, pool: asyncpg.Pool) -> None:
        super().__init__(PostgresDriver(pool))

Store

Bases: DriverStore

Postgres-backed queryable store over llmbroker_calls + the llmbroker_disabled admin verdict map.

Source code in src/llmbroker/postgres/store.py
13
14
15
16
17
18
class Store(DriverStore):
    """Postgres-backed queryable store over ``llmbroker_calls`` + the
    ``llmbroker_disabled`` admin verdict map."""

    def __init__(self, pool: asyncpg.Pool, *, retention: timedelta = RETENTION_DEFAULT) -> None:
        super().__init__(PostgresDriver(pool), retention=retention)

driver

Postgres Driver: renders DDL and DML from backends.spec.TABLES. Schema policy — create-if-missing, fail fast on a version-marker mismatch — is in rules/backends.md.

PostgresDriver

One asyncpg pool backing every llmbroker_* table. A pre-built pool stays the caller's and aclose() is a no-op; with dsn= the driver creates its own lazily — pool creation is async and this constructor is not — and closes it.

Source code in src/llmbroker/postgres/driver.py
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
class PostgresDriver:
    """One asyncpg pool backing every ``llmbroker_*`` table. A pre-built ``pool`` stays
    the caller's and ``aclose()`` is a no-op; with ``dsn=`` the driver creates its own
    lazily — pool creation is async and this constructor is not — and closes it."""

    def __init__(self, pool: asyncpg.Pool | None = None, *, dsn: str | None = None) -> None:
        if pool is None and dsn is None:
            raise ValueError("PostgresDriver requires either pool= or dsn=")
        self._pool = pool
        self._dsn = dsn
        self._owns_pool = pool is None
        self._schema_ready = False
        self._schema_lock = asyncio.Lock()

    async def ensure_schema(self) -> None:
        """A plain per-instance flag: one driver per stack is app-lifetime, so this
        gives one check per process under intended usage. Not ``id(pool)``-keyed —
        after GC an id can be reused by a different pool and falsely skip creation."""
        if self._schema_ready:
            return
        async with self._schema_lock:
            if self._schema_ready:
                return
            if self._pool is None:
                self._pool = await asyncpg.create_pool(self._dsn)
            async with self._pool.acquire() as conn, conn.transaction():
                await _apply_ddl(conn)
            self._schema_ready = True

    async def fetch(self, table: str) -> list[Row]:
        spec = TABLES[table]
        await self.ensure_schema()
        cols = ", ".join(spec.columns)
        order = ", ".join(spec.key) if spec.key else cols
        async with self._pool.acquire() as conn:
            records = await conn.fetch(f"SELECT {cols} FROM {spec.name} ORDER BY {order}")  # noqa: S608
        return [_decode_row(r, spec) for r in records]

    async def get(self, table: str, key: Key) -> Row | None:
        spec = TABLES[table]
        await self.ensure_schema()
        cols = ", ".join(spec.columns)
        where = " AND ".join(f"{k} = ${i}" for i, k in enumerate(spec.key, start=1))
        async with self._pool.acquire() as conn:
            record = await conn.fetchrow(f"SELECT {cols} FROM {spec.name} WHERE {where}", *key)  # noqa: S608
        return _decode_row(record, spec) if record is not None else None

    async def upsert(self, table: str, key: Key, row: Row) -> None:  # noqa: ARG002
        """``key`` is part of the Driver contract but unused here: the row already
        carries its own key-column values, which ON CONFLICT matches against."""
        spec = TABLES[table]
        await self.ensure_schema()
        encoded = _encode_row(row, spec)
        cols = list(spec.columns)
        placeholders = ", ".join(_placeholder(i, spec.columns[c]) for i, c in enumerate(cols, 1))
        conflict = ", ".join(spec.key)
        updates = ", ".join(f"{c} = EXCLUDED.{c}" for c in cols if c not in spec.key)
        # Identifiers below come from the fixed internal TableSpec, not user input.
        query = (
            f"INSERT INTO {spec.name} ({', '.join(cols)}) VALUES ({placeholders})"  # noqa: S608
            f" ON CONFLICT ({conflict}) DO UPDATE SET {updates}"
        )
        async with self._pool.acquire() as conn:
            await conn.execute(query, *(encoded[c] for c in cols))

    async def delete(self, table: str, key: Key) -> bool:
        spec = TABLES[table]
        await self.ensure_schema()
        where = " AND ".join(f"{k} = ${i}" for i, k in enumerate(spec.key, start=1))
        async with self._pool.acquire() as conn:
            result = await conn.execute(f"DELETE FROM {spec.name} WHERE {where}", *key)  # noqa: S608
        return result.split()[-1] != "0"

    async def append(self, table: str, row: Row) -> None:
        spec = TABLES[table]
        await self.ensure_schema()
        encoded = _encode_row(row, spec)
        cols = list(spec.columns)
        placeholders = ", ".join(_placeholder(i, spec.columns[c]) for i, c in enumerate(cols, 1))
        query = f"INSERT INTO {spec.name} ({', '.join(cols)}) VALUES ({placeholders})"  # noqa: S608
        async with self._pool.acquire() as conn:
            await conn.execute(query, *(encoded[c] for c in cols))

    async def journal_view(
        self,
        limit: int,
        match: Row | None = None,
        since: datetime | None = None,
    ) -> list[Row]:
        spec = TABLES["calls"]
        await self.ensure_schema()
        cols = ", ".join(f"c.{col}" for col in spec.columns)
        # The correlated subquery runs once per returned row, so it keeps the page at
        # exactly ``limit`` calls and makes "newest rating wins" free.
        params: list[object] = [KIND_QUALITY]
        score = (
            f"(SELECT q.quality_score FROM {spec.name} q"  # noqa: S608
            f" WHERE q.kind = ${len(params)} AND q.call_id = c.id"
            " ORDER BY q.called_at DESC LIMIT 1) AS score"
        )
        params.append(KIND_CALL)
        conditions = [f"c.kind = ${len(params)}"]
        if match:
            for k, v in match.items():
                if v is None:
                    conditions.append(f"c.{k} IS NULL")
                else:
                    params.append(v)
                    conditions.append(f"c.{k} = ${len(params)}")
        if since is not None:
            params.append(since)
            conditions.append(f"c.called_at >= ${len(params)}")
        where = " WHERE " + " AND ".join(conditions)
        params.append(limit)
        async with self._pool.acquire() as conn:
            records = await conn.fetch(
                f"SELECT {cols}, {score} FROM {spec.name} c{where}"  # noqa: S608
                f" ORDER BY c.called_at DESC LIMIT ${len(params)}",
                *params,
            )
        return [{**_decode_row(r, spec), "score": r["score"]} for r in records]

    async def purge(self, table: str, before: datetime) -> int:
        spec = TABLES[table]
        await self.ensure_schema()
        async with self._pool.acquire() as conn:
            result = await conn.execute(f"DELETE FROM {spec.name} WHERE called_at < $1", before)  # noqa: S608
        return int(result.split()[-1])

    async def aclose(self) -> None:
        if self._owns_pool and self._pool is not None:
            pool, self._pool = self._pool, None
            await pool.close()
ensure_schema() async

A plain per-instance flag: one driver per stack is app-lifetime, so this gives one check per process under intended usage. Not id(pool)-keyed — after GC an id can be reused by a different pool and falsely skip creation.

Source code in src/llmbroker/postgres/driver.py
113
114
115
116
117
118
119
120
121
122
123
124
125
126
async def ensure_schema(self) -> None:
    """A plain per-instance flag: one driver per stack is app-lifetime, so this
    gives one check per process under intended usage. Not ``id(pool)``-keyed —
    after GC an id can be reused by a different pool and falsely skip creation."""
    if self._schema_ready:
        return
    async with self._schema_lock:
        if self._schema_ready:
            return
        if self._pool is None:
            self._pool = await asyncpg.create_pool(self._dsn)
        async with self._pool.acquire() as conn, conn.transaction():
            await _apply_ddl(conn)
        self._schema_ready = True
upsert(table, key, row) async

key is part of the Driver contract but unused here: the row already carries its own key-column values, which ON CONFLICT matches against.

Source code in src/llmbroker/postgres/driver.py
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
async def upsert(self, table: str, key: Key, row: Row) -> None:  # noqa: ARG002
    """``key`` is part of the Driver contract but unused here: the row already
    carries its own key-column values, which ON CONFLICT matches against."""
    spec = TABLES[table]
    await self.ensure_schema()
    encoded = _encode_row(row, spec)
    cols = list(spec.columns)
    placeholders = ", ".join(_placeholder(i, spec.columns[c]) for i, c in enumerate(cols, 1))
    conflict = ", ".join(spec.key)
    updates = ", ".join(f"{c} = EXCLUDED.{c}" for c in cols if c not in spec.key)
    # Identifiers below come from the fixed internal TableSpec, not user input.
    query = (
        f"INSERT INTO {spec.name} ({', '.join(cols)}) VALUES ({placeholders})"  # noqa: S608
        f" ON CONFLICT ({conflict}) DO UPDATE SET {updates}"
    )
    async with self._pool.acquire() as conn:
        await conn.execute(query, *(encoded[c] for c in cols))

registry

Postgres-backed mutable registry over llmbroker_registry — a pure preset mirror.

Registry

Bases: DriverRegistry

Postgres-backed mutable registry over llmbroker_registry — a pure preset mirror.

Source code in src/llmbroker/postgres/registry.py
 9
10
11
12
13
class Registry(DriverRegistry):
    """Postgres-backed mutable registry over ``llmbroker_registry`` — a pure preset mirror."""

    def __init__(self, pool: asyncpg.Pool) -> None:
        super().__init__(PostgresDriver(pool))

secrets

Postgres-backed mutable secrets store over llmbroker_secrets.

Secrets

Bases: DriverSecrets

Postgres-backed mutable secrets store over llmbroker_secrets.

Source code in src/llmbroker/postgres/secrets.py
 9
10
11
12
13
class Secrets(DriverSecrets):
    """Postgres-backed mutable secrets store over ``llmbroker_secrets``."""

    def __init__(self, pool: asyncpg.Pool) -> None:
        super().__init__(PostgresDriver(pool))

store

Postgres-backed queryable store over llmbroker_calls + the llmbroker_disabled admin verdict map.

Store

Bases: DriverStore

Postgres-backed queryable store over llmbroker_calls + the llmbroker_disabled admin verdict map.

Source code in src/llmbroker/postgres/store.py
13
14
15
16
17
18
class Store(DriverStore):
    """Postgres-backed queryable store over ``llmbroker_calls`` + the
    ``llmbroker_disabled`` admin verdict map."""

    def __init__(self, pool: asyncpg.Pool, *, retention: timedelta = RETENTION_DEFAULT) -> None:
        super().__init__(PostgresDriver(pool), retention=retention)

protocols

Backend contracts: what a registry / secrets / store must do. Implement these to add one of your own; the shipped implementations live in llmbroker.standalone and the per-driver packages.

registry

Registry contract: load LLM configs; mutable backends also mirror a preset into them.

KeyInfoProtocol

Bases: Protocol

Optional capability: per-key onboarding metadata, keyed by api_key_ref because one key is usually shared by several LLMs. A source without it simply does not implement this protocol, and callers probe with isinstance.

Source code in src/llmbroker/protocols/registry.py
20
21
22
23
24
25
26
@runtime_checkable
class KeyInfoProtocol(Protocol):
    """Optional capability: per-key onboarding metadata, keyed by ``api_key_ref``
    because one key is usually shared by several LLMs. A source without it simply
    does not implement this protocol, and callers probe with ``isinstance``."""

    async def key_info(self) -> dict[str, KeyInfo]: ...
MutableRegistryProtocol

Bases: RegistryProtocol, Protocol

Source code in src/llmbroker/protocols/registry.py
12
13
14
15
16
17
@runtime_checkable
class MutableRegistryProtocol(RegistryProtocol, Protocol):
    async def mirror(self, configs: list[LLMConfig]) -> None:
        """Total mirror: add entries absent from the store, update existing ones,
        delete stored entries absent from ``configs``. The only registry write path."""
        ...
mirror(configs) async

Total mirror: add entries absent from the store, update existing ones, delete stored entries absent from configs. The only registry write path.

Source code in src/llmbroker/protocols/registry.py
14
15
16
17
async def mirror(self, configs: list[LLMConfig]) -> None:
    """Total mirror: add entries absent from the store, update existing ones,
    delete stored entries absent from ``configs``. The only registry write path."""
    ...

secrets

Secrets contract: resolve api_key_ref to a key; mutable backends also set, and a backend that can list its own refs answers the pool's key question in one call instead of one per ref.

EnumerableSecretsProtocol

Bases: SecretsProtocol, Protocol

Optional: the refs this store holds under a prefix, the prefix included.

A backend without it is asked ref by ref, which suits a store whose reads are free; what it gives up is in rules/backends.md.

Source code in src/llmbroker/protocols/secrets.py
18
19
20
21
22
23
24
25
26
@runtime_checkable
class EnumerableSecretsProtocol(SecretsProtocol, Protocol):
    """Optional: the refs this store holds under a prefix, the prefix included.

    A backend without it is asked ref by ref, which suits a store whose reads are
    free; what it gives up is in ``rules/backends.md``.
    """

    async def refs(self, prefix: str = "") -> frozenset[str]: ...

store

Store contract: record calls; queryable backends also read the journal.

DisabledMapProtocol is the optional admin-verdict half: a tiny mutable name -> disabled document a backend may additionally implement.

DisabledMapProtocol

Bases: Protocol

Optional capability: the admin disabled-verdict map (name -> bool).

Source code in src/llmbroker/protocols/store.py
42
43
44
45
46
47
48
49
@runtime_checkable
class DisabledMapProtocol(Protocol):
    """Optional capability: the admin disabled-verdict map (``name -> bool``)."""

    async def get_disabled(self, name: str) -> bool: ...
    async def set_disabled(self, name: str, flag: bool) -> None: ...  # noqa: FBT001
    async def seed_disabled(self, names: list[str]) -> None: ...
    async def disabled_map(self) -> dict[str, bool]: ...
QueryableStoreProtocol

Bases: StoreProtocol, Protocol

One row per call attempt, carrying the newest score it was rated with. since must be timezone-aware and bounds the journal inclusively; limit must be >= 1. Every filter narrows the call rows only, never the ratings folded onto them.

Source code in src/llmbroker/protocols/store.py
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
@runtime_checkable
class QueryableStoreProtocol(StoreProtocol, Protocol):
    """One row per call attempt, carrying the newest score it was rated with. ``since``
    must be timezone-aware and bounds the journal inclusively; ``limit`` must be >= 1.
    Every filter narrows the call rows only, never the ratings folded onto them."""

    async def calls(  # noqa: PLR0913 - one narrowing dimension per parameter
        self,
        *,
        limit: int,
        scope: str | None = None,
        since: datetime | None = None,
        operation: str | None = None,
        trace_id: str | None = None,
        call_id: str | None = None,
    ) -> list[Call]: ...

sqlite

SQLite backend: registry, store and secrets over one DB file. Needs aiosqlite (llmbroker[sqlite]); importing this package is how a host declares that dependency.

Registry

Bases: DriverRegistry

SQLite-backed mutable registry over llmbroker_registry — a pure preset mirror.

Source code in src/llmbroker/sqlite/registry.py
 9
10
11
12
13
class Registry(DriverRegistry):
    """SQLite-backed mutable registry over ``llmbroker_registry`` — a pure preset mirror."""

    def __init__(self, db_path: str | Path) -> None:
        super().__init__(SqliteDriver(db_path))

Secrets

Bases: DriverSecrets

SQLite-backed mutable secrets store over llmbroker_secrets.

Source code in src/llmbroker/sqlite/secrets.py
 9
10
11
12
13
class Secrets(DriverSecrets):
    """SQLite-backed mutable secrets store over ``llmbroker_secrets``."""

    def __init__(self, db_path: str | Path) -> None:
        super().__init__(SqliteDriver(db_path))

Store

Bases: DriverStore

SQLite-backed queryable store over llmbroker_calls + the llmbroker_disabled admin verdict map.

Source code in src/llmbroker/sqlite/store.py
12
13
14
15
16
17
class Store(DriverStore):
    """SQLite-backed queryable store over ``llmbroker_calls`` + the
    ``llmbroker_disabled`` admin verdict map."""

    def __init__(self, db_path: str | Path, *, retention: timedelta = RETENTION_DEFAULT) -> None:
        super().__init__(SqliteDriver(db_path), retention=retention)

driver

SQLite Driver: renders DDL and DML from backends.spec.TABLES. Schema policy — create-if-missing, fail fast on a version-marker mismatch — is in rules/backends.md.

SqliteDriver

One SQLite file (or one process-local :memory: database) backing every llmbroker_* table.

Source code in src/llmbroker/sqlite/driver.py
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
class SqliteDriver:
    """One SQLite file (or one process-local ``:memory:`` database) backing
    every ``llmbroker_*`` table."""

    def __init__(self, db_path: str | Path) -> None:
        self._db_path = str(db_path)
        # A ":memory:" database lives only as long as one connection, so the
        # file-path pattern would give every call its own empty database.
        self._memory_conn: aiosqlite.Connection | None = None

    @asynccontextmanager
    async def _connection(self) -> AsyncIterator[aiosqlite.Connection]:
        if self._db_path == ":memory:":
            if self._memory_conn is None:
                self._memory_conn = await aiosqlite.connect(":memory:")
            yield self._memory_conn
        else:
            async with aiosqlite.connect(self._db_path) as db:
                yield db

    async def ensure_schema(self) -> None:
        """A real file path migrates under its own ``isolation_level=None`` connection
        and a ``BEGIN IMMEDIATE`` transaction, so concurrent OS processes serialize on
        sqlite's file lock instead of racing. ``:memory:`` has no cross-process concern."""
        if self._db_path and self._db_path != ":memory:":
            if _schema_ready.get(self._db_path) == SCHEMA_VERSION:
                return
            async with aiosqlite.connect(self._db_path, isolation_level=None) as db:
                await db.execute("BEGIN IMMEDIATE")
                try:
                    await _apply_ddl(db)
                    await db.execute("COMMIT")
                except BaseException:
                    await db.execute("ROLLBACK")
                    raise
            _schema_ready[self._db_path] = SCHEMA_VERSION
        else:
            async with self._connection() as db:
                await _apply_ddl(db)
                await db.commit()

    async def fetch(self, table: str) -> list[Row]:
        spec = TABLES[table]
        await self.ensure_schema()
        cols = ", ".join(spec.columns)
        order = ", ".join(spec.key) if spec.key else cols
        query = f"SELECT {cols} FROM {spec.name} ORDER BY {order}"  # noqa: S608
        async with self._connection() as db:
            rows = await (await db.execute(query)).fetchall()
        return [_decode_row(r, spec) for r in rows]

    async def get(self, table: str, key: Key) -> Row | None:
        spec = TABLES[table]
        await self.ensure_schema()
        cols = ", ".join(spec.columns)
        where = " AND ".join(f"{k} = ?" for k in spec.key)
        async with self._connection() as db:
            row = await (
                await db.execute(f"SELECT {cols} FROM {spec.name} WHERE {where}", key)  # noqa: S608
            ).fetchone()
        return _decode_row(row, spec) if row is not None else None

    async def upsert(self, table: str, key: Key, row: Row) -> None:  # noqa: ARG002
        """``key`` is part of the Driver contract but unused here: the row already
        carries its own key-column values, which ON CONFLICT matches against."""
        spec = TABLES[table]
        await self.ensure_schema()
        encoded = _encode_row(row, spec)
        cols = list(spec.columns)
        placeholders = ", ".join("?" for _ in cols)
        conflict = ", ".join(spec.key)
        updates = ", ".join(f"{c} = excluded.{c}" for c in cols if c not in spec.key)
        # Identifiers below come from the fixed internal TableSpec, not user input.
        query = (
            f"INSERT INTO {spec.name} ({', '.join(cols)}) VALUES ({placeholders})"  # noqa: S608
            f" ON CONFLICT({conflict}) DO UPDATE SET {updates}"
        )
        async with self._connection() as db:
            await db.execute(query, [encoded[c] for c in cols])
            await db.commit()

    async def delete(self, table: str, key: Key) -> bool:
        spec = TABLES[table]
        await self.ensure_schema()
        where = " AND ".join(f"{k} = ?" for k in spec.key)
        async with self._connection() as db:
            cursor = await db.execute(f"DELETE FROM {spec.name} WHERE {where}", key)  # noqa: S608
            await db.commit()
            return cursor.rowcount > 0

    async def append(self, table: str, row: Row) -> None:
        spec = TABLES[table]
        await self.ensure_schema()
        encoded = _encode_row(row, spec)
        cols = list(spec.columns)
        placeholders = ", ".join("?" for _ in cols)
        query = f"INSERT INTO {spec.name} ({', '.join(cols)}) VALUES ({placeholders})"  # noqa: S608
        async with self._connection() as db:
            await db.execute(query, [encoded[c] for c in cols])
            await db.commit()

    async def journal_view(
        self,
        limit: int,
        match: Row | None = None,
        since: datetime | None = None,
    ) -> list[Row]:
        spec = TABLES["calls"]
        await self.ensure_schema()
        cols = ", ".join(f"c.{col}" for col in spec.columns)
        # The correlated subquery runs once per returned row, so it keeps the page at
        # exactly ``limit`` calls and makes "newest rating wins" free.
        score = (
            f"(SELECT q.quality_score FROM {spec.name} q"  # noqa: S608
            " WHERE q.kind = ? AND q.call_id = c.id"
            " ORDER BY q.called_at DESC LIMIT 1) AS score"
        )
        params: list[object] = [KIND_QUALITY, KIND_CALL]
        conditions = ["c.kind = ?"]
        if match:
            # "=" never matches NULL in SQL; a None filter value means "IS NULL".
            for k, v in match.items():
                if v is None:
                    conditions.append(f"c.{k} IS NULL")
                else:
                    conditions.append(f"c.{k} = ?")
                    params.append(v)
        if since is not None:
            conditions.append("c.called_at >= ?")
            params.append(_iso(since))
        where = " WHERE " + " AND ".join(conditions)
        params.append(limit)
        async with self._connection() as db:
            rows = await (
                await db.execute(
                    f"SELECT {cols}, {score} FROM {spec.name} c{where}"  # noqa: S608
                    " ORDER BY c.called_at DESC LIMIT ?",
                    params,
                )
            ).fetchall()
        return [{**_decode_row(r[:-1], spec), "score": r[-1]} for r in rows]

    async def purge(self, table: str, before: datetime) -> int:
        spec = TABLES[table]
        await self.ensure_schema()
        async with self._connection() as db:
            cursor = await db.execute(
                f"DELETE FROM {spec.name} WHERE called_at < ?",  # noqa: S608
                [_iso(before)],
            )
            await db.commit()
            return cursor.rowcount

    async def aclose(self) -> None:
        if self._memory_conn is not None:
            conn, self._memory_conn = self._memory_conn, None
            await conn.close()
ensure_schema() async

A real file path migrates under its own isolation_level=None connection and a BEGIN IMMEDIATE transaction, so concurrent OS processes serialize on sqlite's file lock instead of racing. :memory: has no cross-process concern.

Source code in src/llmbroker/sqlite/driver.py
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
async def ensure_schema(self) -> None:
    """A real file path migrates under its own ``isolation_level=None`` connection
    and a ``BEGIN IMMEDIATE`` transaction, so concurrent OS processes serialize on
    sqlite's file lock instead of racing. ``:memory:`` has no cross-process concern."""
    if self._db_path and self._db_path != ":memory:":
        if _schema_ready.get(self._db_path) == SCHEMA_VERSION:
            return
        async with aiosqlite.connect(self._db_path, isolation_level=None) as db:
            await db.execute("BEGIN IMMEDIATE")
            try:
                await _apply_ddl(db)
                await db.execute("COMMIT")
            except BaseException:
                await db.execute("ROLLBACK")
                raise
        _schema_ready[self._db_path] = SCHEMA_VERSION
    else:
        async with self._connection() as db:
            await _apply_ddl(db)
            await db.commit()
upsert(table, key, row) async

key is part of the Driver contract but unused here: the row already carries its own key-column values, which ON CONFLICT matches against.

Source code in src/llmbroker/sqlite/driver.py
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
async def upsert(self, table: str, key: Key, row: Row) -> None:  # noqa: ARG002
    """``key`` is part of the Driver contract but unused here: the row already
    carries its own key-column values, which ON CONFLICT matches against."""
    spec = TABLES[table]
    await self.ensure_schema()
    encoded = _encode_row(row, spec)
    cols = list(spec.columns)
    placeholders = ", ".join("?" for _ in cols)
    conflict = ", ".join(spec.key)
    updates = ", ".join(f"{c} = excluded.{c}" for c in cols if c not in spec.key)
    # Identifiers below come from the fixed internal TableSpec, not user input.
    query = (
        f"INSERT INTO {spec.name} ({', '.join(cols)}) VALUES ({placeholders})"  # noqa: S608
        f" ON CONFLICT({conflict}) DO UPDATE SET {updates}"
    )
    async with self._connection() as db:
        await db.execute(query, [encoded[c] for c in cols])
        await db.commit()

registry

SQLite-backed mutable registry over llmbroker_registry — a pure preset mirror.

Registry

Bases: DriverRegistry

SQLite-backed mutable registry over llmbroker_registry — a pure preset mirror.

Source code in src/llmbroker/sqlite/registry.py
 9
10
11
12
13
class Registry(DriverRegistry):
    """SQLite-backed mutable registry over ``llmbroker_registry`` — a pure preset mirror."""

    def __init__(self, db_path: str | Path) -> None:
        super().__init__(SqliteDriver(db_path))

secrets

SQLite-backed mutable secrets store over llmbroker_secrets.

Secrets

Bases: DriverSecrets

SQLite-backed mutable secrets store over llmbroker_secrets.

Source code in src/llmbroker/sqlite/secrets.py
 9
10
11
12
13
class Secrets(DriverSecrets):
    """SQLite-backed mutable secrets store over ``llmbroker_secrets``."""

    def __init__(self, db_path: str | Path) -> None:
        super().__init__(SqliteDriver(db_path))

store

SQLite-backed queryable store over llmbroker_calls + the llmbroker_disabled admin verdict map.

Store

Bases: DriverStore

SQLite-backed queryable store over llmbroker_calls + the llmbroker_disabled admin verdict map.

Source code in src/llmbroker/sqlite/store.py
12
13
14
15
16
17
class Store(DriverStore):
    """SQLite-backed queryable store over ``llmbroker_calls`` + the
    ``llmbroker_disabled`` admin verdict map."""

    def __init__(self, db_path: str | Path, *, retention: timedelta = RETENTION_DEFAULT) -> None:
        super().__init__(SqliteDriver(db_path), retention=retention)

standalone

Zero-dependency implementations: a model list file, env-var secrets, a file-backed store. Available with a bare import llmbroker; dependency-carrying backends live in per-driver packages instead.

registry

File-backed registry: a .toml file of [[llms]] rows, no secrets.

Registry

File-backed read-only registry over a TOML model list.

The file is llmbroker's own output, rewritten in full by a sync; see specs/reference/rules/model-list.md.

Source code in src/llmbroker/standalone/registry.py
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
class Registry:
    """File-backed read-only registry over a TOML model list.

    The file is llmbroker's own output, rewritten in full by a sync; see
    ``specs/reference/rules/model-list.md``.
    """

    def __init__(self, path: str | Path) -> None:
        self._path = Path(path)

    @property
    def path(self) -> Path:
        return self._path

    async def load(self) -> list[LLMConfig]:
        """The ``[[llms]]`` entries of the file, validated."""
        return read_model_list(self._path).configs

    async def key_info(self) -> dict[str, KeyInfo]:
        """Per-provider onboarding metadata from the ``[keys]`` table, keyed by ``api_key_ref``."""
        return read_model_list(self._path).keys
key_info() async

Per-provider onboarding metadata from the [keys] table, keyed by api_key_ref.

Source code in src/llmbroker/standalone/registry.py
143
144
145
async def key_info(self) -> dict[str, KeyInfo]:
    """Per-provider onboarding metadata from the ``[keys]`` table, keyed by ``api_key_ref``."""
    return read_model_list(self._path).keys
load() async

The [[llms]] entries of the file, validated.

Source code in src/llmbroker/standalone/registry.py
139
140
141
async def load(self) -> list[LLMConfig]:
    """The ``[[llms]]`` entries of the file, validated."""
    return read_model_list(self._path).configs
key_info_from_entry(ref, raw)

Parse one [keys.REF] entry; a bare string is the help-only form.

Source code in src/llmbroker/standalone/registry.py
56
57
58
59
60
61
62
63
64
65
66
67
68
def key_info_from_entry(ref: str, raw: object) -> KeyInfo:
    """Parse one ``[keys.REF]`` entry; a bare string is the help-only form."""
    if isinstance(raw, str):
        return KeyInfo(api_key_ref=ref, help=raw, extra={})
    if not isinstance(raw, dict):
        return KeyInfo(api_key_ref=ref, help="", extra={})
    help_text = raw.get("help")
    extra = {str(k): str(v) for k, v in raw.items() if k != "help"}
    return KeyInfo(
        api_key_ref=ref,
        help=help_text if isinstance(help_text, str) else "",
        extra=extra,
    )
parse_model_list(data)

The one reader of a model list: the [[llms]] entries in file order plus the [keys] metadata. Whether a list is valid is decided only here.

Source code in src/llmbroker/standalone/registry.py
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
def parse_model_list(data: dict) -> ModelList:
    """The one reader of a model list: the ``[[llms]]`` entries in file order plus the
    ``[keys]`` metadata. Whether a list is valid is decided only here."""
    _check_no_declared_entries(data)
    configs: list[LLMConfig] = []
    for position, entry in enumerate(data.get("llms", []), start=1):
        if not isinstance(entry, dict):
            # ValueError, not TypeError: a malformed model list must stay inside the
            # error type a background refresh catches rather than kill the process.
            raise ValueError(  # noqa: TRY004
                f"Registry: [[llms]] entry {position} is {type(entry).__name__}, not a table",
            )
        cfg = config_from_entry(entry)
        if cfg is not None:
            configs.append(cfg)
    _check_unique_names(configs)
    return ModelList(configs=configs, keys=_key_infos(data))
read_model_list(path)

The model list this installation follows: its entries and its [keys] help.

Source code in src/llmbroker/standalone/registry.py
120
121
122
def read_model_list(path: Path) -> ModelList:
    """The model list this installation follows: its entries and its ``[keys]`` help."""
    return parse_model_list(_read_data(path))

secrets

Read-only secrets resolvers with no external backend: Secrets over os.environ with an optional .env behind it, DictSecrets over a mapping. A plain callable is accepted and adapted.

DictSecrets

Read-only secrets resolver backed by an in-memory mapping (tests / preloaded keys).

Source code in src/llmbroker/standalone/secrets.py
85
86
87
88
89
90
91
92
93
94
class DictSecrets:
    """Read-only secrets resolver backed by an in-memory mapping (tests / preloaded keys)."""

    def __init__(self, mapping: dict[str, str]) -> None:
        self._mapping = dict(mapping)

    async def resolve(self, ref: str) -> str:
        if ref not in self._mapping:
            raise KeyError(f"DictSecrets: ref {ref!r} not found")
        return self._mapping[ref]
Secrets

Read-only env-backed secrets resolver (the default battery). env_file is consulted only where the real environment has no such variable, and a blank value counts as absent either way.

Source code in src/llmbroker/standalone/secrets.py
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
class Secrets:
    """Read-only env-backed secrets resolver (the default battery). ``env_file`` is
    consulted only where the real environment has no such variable, and a blank value
    counts as absent either way."""

    def __init__(self, env_file: str | Path | None = None) -> None:
        self._env_file = Path(env_file) if env_file is not None else None
        self._file_values: dict[str, str] | None = None
        self._file_stamp: tuple[int, int] | None = None

    def _file_mapping(self, path: Path) -> dict[str, str]:
        """Re-parse whenever the file changes, so a key filled in on a running
        broker takes effect on the next resync exactly as an exported one would."""
        try:
            info = path.stat()
            stamp = (info.st_mtime_ns, info.st_size)
        except OSError:
            stamp = None
        if self._file_values is None or stamp != self._file_stamp:
            self._file_stamp = stamp
            try:
                self._file_values = parse_env_file(path.read_text(encoding="utf-8"))
            except OSError:
                self._file_values = {}
        return self._file_values

    def _from_file(self, ref: str) -> str | None:
        if self._env_file is None:
            return None
        # The skeleton `llmbroker env` writes is all `KEY=` lines: an unfilled one
        # must leave the model keyless, not hand the provider an empty credential.
        return self._file_mapping(self._env_file).get(ref) or None

    async def resolve(self, ref: str) -> str:
        # A blank export is as unset as no export at all: whitespace admitted here
        # would put a model with no credential into the pool (invariant 21).
        value = os.environ.get(ref)
        if value is None or not value.strip():
            value = self._from_file(ref)
        if value is None or not value.strip():
            raise KeyError(f"Secrets: env var {ref!r} is not set")
        return value
as_secrets(secrets)

Return a SecretsProtocol, wrapping a bare callable if needed.

Source code in src/llmbroker/standalone/secrets.py
110
111
112
113
114
115
116
117
118
def as_secrets(secrets: object) -> SecretsProtocol:
    """Return a SecretsProtocol, wrapping a bare callable if needed."""
    if secrets is None:
        return Secrets()
    if isinstance(secrets, SecretsProtocol):
        return secrets
    if callable(secrets):
        return _CallableSecrets(cast(Callable[[str], str | Awaitable[str]], secrets))
    raise TypeError(f"secrets must be a SecretsProtocol or callable, got {type(secrets)!r}")
parse_env_file(text)

Parse KEY=VALUE lines, skipping blanks, # comments, and anything else — a malformed line is not worth failing a whole deployment over.

parse_env_file('# a comment\nA=1\nexport B="two"\nnonsense\n')

Source code in src/llmbroker/standalone/secrets.py
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
def parse_env_file(text: str) -> dict[str, str]:
    """Parse ``KEY=VALUE`` lines, skipping blanks, ``#`` comments, and anything
    else — a malformed line is not worth failing a whole deployment over.

    >>> parse_env_file('# a comment\\nA=1\\nexport B="two"\\nnonsense\\n')
    {'A': '1', 'B': 'two'}
    """
    values: dict[str, str] = {}
    for raw in text.splitlines():
        line = raw.strip().removeprefix("export ").strip()
        if not line or line.startswith("#") or "=" not in line:
            continue
        key, _, value = line.partition("=")
        key = key.strip()
        if not key:
            continue
        value = value.strip()
        if len(value) >= 2 and value[0] == value[-1] and value[0] in ("'", '"'):  # noqa: PLR2004
            value = value[1:-1]
        else:
            # An unquoted value ends at an inline comment; a key silently carrying
            # a trailing "# note" would be rejected as a dead one by the provider.
            value = value.partition(" #")[0].rstrip()
        values[key] = value
    return values

store

File-backed and in-memory stores, no external backend. FileStore keeps a day-split JSON-lines journal beside a hand-editable disabled map, and purges by unlinking whole expired day files — no rewrite, no race with a concurrent append.

FileStore

Day-split JSONL call journal plus a YAML disabled-verdict map, under one directory.

Source code in src/llmbroker/standalone/store.py
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
class FileStore:
    """Day-split JSONL call journal plus a YAML disabled-verdict map, under one directory."""

    def __init__(self, directory: str | Path, *, retention: timedelta = RETENTION_DEFAULT) -> None:
        self._dir = Path(directory)
        self._calls_dir = self._dir / "calls"
        self._disabled_path = self._dir / "disabled.yml"
        self._retention = retention
        self._purge_clock = PurgeClock()

    def _day_path(self, ts: datetime) -> Path:
        """UTC date, not the record's own offset: the ``since`` bound skips whole
        files by name, so a file must never hold a row outside its named UTC day."""
        return self._calls_dir / f"{ts.astimezone(UTC).date().isoformat()}.jsonl"

    def _append_line(self, ts: datetime, payload: dict) -> None:
        path = self._day_path(ts)
        path.parent.mkdir(parents=True, exist_ok=True)
        line = json.dumps(payload)
        with path.open("a", encoding="utf-8") as fh:
            fh.write(line + "\n")

    async def record(self, call: Call) -> None:
        stamped = with_utc_timestamps(call)
        await asyncio.to_thread(self._append_line, stamped.ts, _call_to_jsonable(stamped))
        await self._maybe_purge()

    async def record_quality(
        self,
        call_id: str,
        score: float,
        *,
        scope: str | None = None,
    ) -> None:
        row = quality_row(call_id, score, scope)
        called_at: datetime = row["called_at"]  # type: ignore[assignment]
        payload = {k: v for k, v in row.items() if k != "called_at" and v is not None}
        payload["ts"] = called_at.isoformat()
        await asyncio.to_thread(self._append_line, called_at, payload)
        await self._maybe_purge()

    def _day_files_newest_first(self) -> list[Path]:
        if not self._calls_dir.exists():
            return []
        return sorted(self._calls_dir.glob("*.jsonl"), reverse=True)

    def _read_tail(
        self,
        limit: int,
        match: dict[str, object],
        since: datetime | None,
    ) -> list[Call]:
        """``match`` maps a ``Call`` attribute name to the value it must equal — the
        file counterpart of the driver stores' column match. One reverse pass: a rating
        is newer than the call it names, so it is always met before that call."""
        result: list[Call] = []
        pending: dict[str, float] = {}
        for path in self._day_files_newest_first():
            if since is not None and self._file_is_wholly_before(path, since):
                continue
            lines = path.read_text(encoding="utf-8").splitlines()
            for raw_line in reversed(lines):
                stripped = raw_line.strip()
                if not stripped:
                    continue
                raw = json.loads(stripped)
                if raw.get("kind") == KIND_QUALITY:
                    rated, value = raw.get("call_id"), raw.get("quality_score")
                    if rated is not None and value is not None:
                        pending.setdefault(rated, value)
                    continue
                call = _call_from_jsonable(raw)
                if any(getattr(call, attr) != want for attr, want in match.items()):
                    continue
                if since is not None and (call.ts is None or call.ts < since):
                    continue
                result.append(replace(call, score=pending.pop(call.id, None)))
                if len(result) >= limit:
                    return result
        return result

    @staticmethod
    def _file_is_wholly_before(path: Path, since: datetime) -> bool:
        """A day file's newest possible record is the last instant of its UTC date, so
        a file whose whole day precedes ``since`` is skipped without being read."""
        try:
            file_date = date.fromisoformat(path.stem)
        except ValueError:
            return False
        return file_date < since.date()

    async def calls(  # noqa: PLR0913 - one narrowing dimension per parameter
        self,
        *,
        limit: int,
        scope: str | None = None,
        since: datetime | None = None,
        operation: str | None = None,
        trace_id: str | None = None,
        call_id: str | None = None,
    ) -> list[Call]:
        """Newest-first tail of the journal, one row per call attempt carrying the
        newest score it was rated with. ``since`` must be timezone-aware and bounds the
        timestamp inclusively; the filters narrow the calls, never the ratings."""
        check_limit(limit)
        bound = to_utc(since, "since") if since is not None else None
        wanted = {
            "scope": scope,
            "operation": operation,
            "trace_id": trace_id,
            "id": call_id,
        }
        match = {attr: want for attr, want in wanted.items() if want is not None}
        return await asyncio.to_thread(self._read_tail, limit, match, bound)

    def _purge_old_day_files(self) -> None:
        cutoff = (datetime.now(UTC) - self._retention).date()
        for path in self._day_files_newest_first():
            try:
                file_date = date.fromisoformat(path.stem)
            except ValueError:
                continue
            if file_date < cutoff:
                path.unlink(missing_ok=True)

    async def _maybe_purge(self) -> None:
        if not self._purge_clock.due():
            return
        await asyncio.to_thread(self._purge_old_day_files)

    def _read_disabled(self) -> dict[str, bool]:
        if not self._disabled_path.exists():
            return {}
        data = yaml.safe_load(self._disabled_path.read_text(encoding="utf-8"))
        return dict(data) if data else {}

    def _write_disabled(self, data: dict[str, bool]) -> None:
        self._disabled_path.parent.mkdir(parents=True, exist_ok=True)
        body = yaml.safe_dump(data, sort_keys=True)
        self._disabled_path.write_text(_DISABLED_HEADER + body, encoding="utf-8")

    async def get_disabled(self, name: str) -> bool:
        data = await asyncio.to_thread(self._read_disabled)
        return bool(data.get(name, False))

    async def set_disabled(self, name: str, flag: bool) -> None:  # noqa: FBT001
        data = await asyncio.to_thread(self._read_disabled)
        data[name] = flag
        await asyncio.to_thread(self._write_disabled, data)

    async def seed_disabled(self, names: list[str]) -> None:
        data = await asyncio.to_thread(self._read_disabled)
        changed = False
        for name in names:
            if name not in data:
                data[name] = False
                changed = True
        if changed:
            await asyncio.to_thread(self._write_disabled, data)

    async def disabled_map(self) -> dict[str, bool]:
        return await asyncio.to_thread(self._read_disabled)
calls(*, limit, scope=None, since=None, operation=None, trace_id=None, call_id=None) async

Newest-first tail of the journal, one row per call attempt carrying the newest score it was rated with. since must be timezone-aware and bounds the timestamp inclusively; the filters narrow the calls, never the ratings.

Source code in src/llmbroker/standalone/store.py
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
async def calls(  # noqa: PLR0913 - one narrowing dimension per parameter
    self,
    *,
    limit: int,
    scope: str | None = None,
    since: datetime | None = None,
    operation: str | None = None,
    trace_id: str | None = None,
    call_id: str | None = None,
) -> list[Call]:
    """Newest-first tail of the journal, one row per call attempt carrying the
    newest score it was rated with. ``since`` must be timezone-aware and bounds the
    timestamp inclusively; the filters narrow the calls, never the ratings."""
    check_limit(limit)
    bound = to_utc(since, "since") if since is not None else None
    wanted = {
        "scope": scope,
        "operation": operation,
        "trace_id": trace_id,
        "id": call_id,
    }
    match = {attr: want for attr, want in wanted.items() if want is not None}
    return await asyncio.to_thread(self._read_tail, limit, match, bound)
InMemoryStore

Explicit in-memory opt-out — no persistence, session-scoped learning; disabled verdicts live only in process memory.

Source code in src/llmbroker/standalone/store.py
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
class InMemoryStore:
    """Explicit in-memory opt-out — no persistence, session-scoped learning;
    disabled verdicts live only in process memory."""

    def __init__(self) -> None:
        self._disabled: dict[str, bool] = {}

    async def record(self, _call: Call) -> None:
        return

    async def record_quality(
        self,
        _call_id: str,
        _score: float,
        *,
        scope: str | None = None,  # noqa: ARG002
    ) -> None:
        return

    async def get_disabled(self, name: str) -> bool:
        return self._disabled.get(name, False)

    async def set_disabled(self, name: str, flag: bool) -> None:  # noqa: FBT001
        self._disabled[name] = flag

    async def seed_disabled(self, names: list[str]) -> None:
        for name in names:
            self._disabled.setdefault(name, False)

    async def disabled_map(self) -> dict[str, bool]:
        return dict(self._disabled)

sync

Synchronous Broker / LLM / Result: blocking proxies that submit coroutines to an AsyncBroker on a dedicated background event-loop thread.

Broker

Synchronous client over an AsyncBroker on a background loop thread.

Source code in src/llmbroker/sync.py
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
class Broker:
    """Synchronous client over an AsyncBroker on a background loop thread."""

    def __init__(  # noqa: PLR0913
        self,
        registry: RegistryProtocol | str | Path | None = None,
        *,
        secrets: SecretsProtocol | None = None,
        store: StoreProtocol | None = None,
        optimize: bool | Optimizer = True,
        sync: str | None | _SyncDefault = _SYNC_DEFAULT,
        sync_interval: float | None = _DEFAULT_SYNC_INTERVAL,
        home: str | Path | None = None,
        direct: Sequence[str | LLMConfig] = (),
    ) -> None:
        self._async = AsyncBroker(
            registry,
            secrets=secrets,
            store=store,
            optimize=optimize,
            sync=sync,
            sync_interval=sync_interval,
            home=home,
            direct=direct,
        )
        self._loop = asyncio.new_event_loop()
        self._thread = threading.Thread(
            target=_run_loop,
            args=(self._loop,),
            daemon=True,
            name="llmbroker-loop",
        )
        self._thread.start()
        # Backstop for a Broker nobody closes. The callback holds only loop + thread,
        # never self, so it does not pin the instance it is registered on.
        self._finalizer = weakref.finalize(self, _shutdown, self._loop, self._thread)
        self.llms = LLMs(self._run, self._async.llms)

    def _run(self, coro: Coroutine[Any, Any, Any]) -> Any:
        return asyncio.run_coroutine_threadsafe(coro, self._loop).result()

    def for_scope(self, scope: str) -> "LLMs":
        """A caller that pays with ``scope``\'s own keys and writes ``scope`` on every
        row it journals. Costs no I/O."""
        return LLMs(self._run, self._async.for_scope(scope))

    def _ensure_pool(self) -> None:
        self._run(self._async.ensure_pool())

    # ── The unscoped caller, delegated ──
    def get(self, name: str) -> LLM:
        return self.llms.get(name)

    def count(self) -> int:
        return self.llms.count()

    def ask(  # noqa: PLR0913 - the call knobs, one keyword each
        self,
        prompt: str,
        *,
        operation: str | None = None,
        trace_id: str | None = None,
        wait: float | None = None,
        fastest_of: int | None = None,
        parallel_recovery: bool = True,
        response_format: dict | None = None,
    ) -> Result:
        return self.llms.ask(
            prompt,
            operation=operation,
            trace_id=trace_id,
            wait=wait,
            fastest_of=fastest_of,
            parallel_recovery=parallel_recovery,
            response_format=response_format,
        )

    def chat(  # noqa: PLR0913 - what to send, and the call knobs
        self,
        messages: list[dict],
        *,
        tools: list[dict] | None = None,
        operation: str | None = None,
        trace_id: str | None = None,
        wait: float | None = None,
        fastest_of: int | None = None,
        parallel_recovery: bool = True,
        response_format: dict | None = None,
    ) -> Result:
        return self.llms.chat(
            messages,
            tools=tools,
            operation=operation,
            trace_id=trace_id,
            wait=wait,
            fastest_of=fastest_of,
            parallel_recovery=parallel_recovery,
            response_format=response_format,
        )

    def direct(self, alias: str | None = None, *, name: str | None = None) -> DirectClient:
        return self.llms.direct(alias, name=name)

    def record_quality(
        self,
        score: float,
        *,
        call_id: str | None = None,
        trace_id: str | None = None,
    ) -> None:
        self.llms.record_quality(score, call_id=call_id, trace_id=trace_id)

    def snapshot(self) -> PoolSnapshot:
        return self._run(self._async.snapshot())

    def sync(self, source: str | None = None) -> SyncReport | None:
        return self._run(self._async.sync(source))

    @property
    def last_sync_report(self) -> SyncReport | None:
        return self._async.last_sync_report

    def disable_llm(self, name: str) -> None:
        self._run(self._async.disable_llm(name))

    def enable_llm(self, name: str) -> None:
        self._run(self._async.enable_llm(name))

    def calls(
        self,
        *,
        limit: int,
        since: datetime | None = None,
        operation: str | None = None,
        trace_id: str | None = None,
        call_id: str | None = None,
    ) -> list[Call]:
        return self.llms.calls(
            limit=limit,
            since=since,
            operation=operation,
            trace_id=trace_id,
            call_id=call_id,
        )

    def stats(
        self,
        *,
        since: datetime | None = None,
        limit: int = _DEFAULT_STATS_LIMIT,
        operation: str | None = None,
    ) -> Mapping[str, LLMStats]:
        return self.llms.stats(since=since, limit=limit, operation=operation)

    # ── lifecycle ──
    def close(self) -> None:
        if not self._finalizer.alive:
            return
        self._run(self._async.aclose())
        # Run the same teardown the GC backstop would, and mark it done so the
        # finalizer does not repeat it later.
        self._finalizer()

    def __enter__(self) -> "Broker":
        self._ensure_pool()
        return self

    def __exit__(self, *exc: object) -> None:
        self.close()
for_scope(scope)

A caller that pays with scope's own keys and writes scope on every row it journals. Costs no I/O.

Source code in src/llmbroker/sync.py
258
259
260
261
def for_scope(self, scope: str) -> "LLMs":
    """A caller that pays with ``scope``\'s own keys and writes ``scope`` on every
    row it journals. Costs no I/O."""
    return LLMs(self._run, self._async.for_scope(scope))

LLM

Synchronous analogue of AsyncLLM.

Source code in src/llmbroker/sync.py
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
class LLM:
    """Synchronous analogue of AsyncLLM."""

    def __init__(self, run_fn: "Callable[[Any], Any]", async_llm: AsyncLLM) -> None:
        self._run = run_fn
        self._async = async_llm

    @property
    def config(self) -> LLMConfig:
        return self._async.config

    @property
    def disabled(self) -> bool:
        return self._async.disabled

    def state(self) -> LLMState:
        return self._run(self._async.state())

    def metrics(self) -> LLMMetrics:
        return self._run(self._async.metrics())

LLMs

Synchronous analogue of AsyncLLMs — one caller over the shared pool.

Source code in src/llmbroker/sync.py
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
class LLMs:
    """Synchronous analogue of AsyncLLMs — one caller over the shared pool."""

    def __init__(self, run_fn: "Callable[[Any], Any]", async_llms: AsyncLLMs) -> None:
        self._run = run_fn
        self._async = async_llms

    @property
    def scope(self) -> str | None:
        return self._async.scope

    def ask(  # noqa: PLR0913 - the call knobs, one keyword each
        self,
        prompt: str,
        *,
        operation: str | None = None,
        trace_id: str | None = None,
        wait: float | None = None,
        fastest_of: int | None = None,
        parallel_recovery: bool = True,
        response_format: dict | None = None,
    ) -> Result:
        return Result(
            self._run,
            self._run(
                self._async.ask(
                    prompt,
                    operation=operation,
                    trace_id=trace_id,
                    wait=wait,
                    fastest_of=fastest_of,
                    parallel_recovery=parallel_recovery,
                    response_format=response_format,
                ),
            ),
        )

    def chat(  # noqa: PLR0913 - what to send, and the call knobs
        self,
        messages: list[dict],
        *,
        tools: list[dict] | None = None,
        operation: str | None = None,
        trace_id: str | None = None,
        wait: float | None = None,
        fastest_of: int | None = None,
        parallel_recovery: bool = True,
        response_format: dict | None = None,
    ) -> Result:
        return Result(
            self._run,
            self._run(
                self._async.chat(
                    messages,
                    tools=tools,
                    operation=operation,
                    trace_id=trace_id,
                    wait=wait,
                    fastest_of=fastest_of,
                    parallel_recovery=parallel_recovery,
                    response_format=response_format,
                ),
            ),
        )

    def direct(self, alias: str | None = None, *, name: str | None = None) -> DirectClient:
        """Return a synchronous direct client (``ask()`` only) for a declared model.

        Streaming is async-only; use the async caller for deltas. Same alias/name
        keyspaces and errors as the async counterpart.
        """
        cfg, key = self._run(self._async.resolve_direct(alias, name=name))
        return DirectClient(base_url=cfg.base_url, model=cfg.model, api_key=key)

    def get(self, name: str) -> LLM:
        return LLM(self._run, self._run(self._async.get(name)))

    def count(self) -> int:
        return self._run(self._async.count())

    def record_quality(
        self,
        score: float,
        *,
        call_id: str | None = None,
        trace_id: str | None = None,
    ) -> None:
        self._run(self._async.record_quality(score, call_id=call_id, trace_id=trace_id))

    def calls(
        self,
        *,
        limit: int,
        since: datetime | None = None,
        operation: str | None = None,
        trace_id: str | None = None,
        call_id: str | None = None,
    ) -> list[Call]:
        return self._run(
            self._async.calls(
                limit=limit,
                since=since,
                operation=operation,
                trace_id=trace_id,
                call_id=call_id,
            ),
        )

    def stats(
        self,
        *,
        since: datetime | None = None,
        limit: int = _DEFAULT_STATS_LIMIT,
        operation: str | None = None,
    ) -> Mapping[str, LLMStats]:
        return self._run(self._async.stats(since=since, limit=limit, operation=operation))
direct(alias=None, *, name=None)

Return a synchronous direct client (ask() only) for a declared model.

Streaming is async-only; use the async caller for deltas. Same alias/name keyspaces and errors as the async counterpart.

Source code in src/llmbroker/sync.py
164
165
166
167
168
169
170
171
def direct(self, alias: str | None = None, *, name: str | None = None) -> DirectClient:
    """Return a synchronous direct client (``ask()`` only) for a declared model.

    Streaming is async-only; use the async caller for deltas. Same alias/name
    keyspaces and errors as the async counterpart.
    """
    cfg, key = self._run(self._async.resolve_direct(alias, name=name))
    return DirectClient(base_url=cfg.base_url, model=cfg.model, api_key=key)

Result

Synchronous analogue of AsyncResult.

Source code in src/llmbroker/sync.py
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
class Result:
    """Synchronous analogue of AsyncResult."""

    def __init__(self, run_fn: "Callable[[Any], Any]", async_result: AsyncResult) -> None:
        self._run = run_fn
        self._async = async_result
        self.text = async_result.text
        self.tool_calls = async_result.tool_calls
        self.usage = async_result.usage

    @property
    def llm_name(self) -> str:
        return self._async.llm_name

    @property
    def operation(self) -> str | None:
        return self._async.operation

    @property
    def call_id(self) -> str:
        return self._async.call_id

    def record_quality(self, score: float) -> None:
        self._run(self._async.record_quality(score))

tool_loop

The tool loop, async and blocking: drive a broker's chat until it stops asking for tools, running each requested tool through the host's dispatch. It sits above the broker, not beside the HTTP primitives it never touches.

arun_tool_loop(llms, messages, *, tools=None, dispatch=None, max_steps=8, **chat_kwargs) async

Drive broker.chat until a tool-call-free reply; execute tools via dispatch. Returns that last round's result: earlier rounds are routed calls of their own, each with its own journal row, so usage is the final round's alone.

Source code in src/llmbroker/tool_loop.py
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
async def arun_tool_loop(
    llms: AsyncBroker,
    messages: list[dict],
    *,
    tools: list[dict] | None = None,
    dispatch: Mapping[str, Callable[..., object]] | None = None,
    max_steps: int = 8,
    **chat_kwargs,
) -> AsyncResult:
    """Drive ``broker.chat`` until a tool-call-free reply; execute tools via dispatch.
    Returns that last round's result: earlier rounds are routed calls of their own,
    each with its own journal row, so ``usage`` is the final round's alone."""
    convo = list(messages)
    dispatch = dispatch or {}
    for _ in range(max_steps):
        result = await llms.chat(convo, tools=tools, **chat_kwargs)
        if _advance_tool_loop(convo, result, dispatch):
            return result
    raise ToolLoopLimitError(_TOOL_LOOP_EXHAUSTED.format(max_steps=max_steps))

execute_tool_calls(tool_calls, dispatch)

Run each tool call via dispatch; return the tool-result messages to append.

Source code in src/llmbroker/tool_loop.py
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
def execute_tool_calls(
    tool_calls: list[dict],
    dispatch: Mapping[str, Callable[..., object]],
) -> list[dict]:
    """Run each tool call via dispatch; return the tool-result messages to append."""
    results: list[dict] = []
    for call in tool_calls:
        name = call["function"]["name"]
        try:
            args = json.loads(call["function"].get("arguments") or "{}")
        except json.JSONDecodeError:
            args = {}
        if not isinstance(args, dict):
            args = {}
        fn = dispatch.get(name)
        if fn is None:
            output: object = f"Unknown tool {name}"
        else:
            try:
                output = fn(**args)
            except Exception as exc:  # noqa: BLE001 - report back to the model so it can retry
                output = f"Tool {name} failed: {exc}"
        results.append({"role": "tool", "tool_call_id": call.get("id"), "content": str(output)})
    return results

run_tool_loop(llms, messages, *, tools=None, dispatch=None, max_steps=8, **chat_kwargs)

Synchronous tool loop over a sync Broker, returning the final round's result.

Mirrors arun_tool_loop but calls the blocking Broker.chat; it does not use the async engine directly so it is safe to call from any thread.

Source code in src/llmbroker/tool_loop.py
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
def run_tool_loop(
    llms: Broker,
    messages: list[dict],
    *,
    tools: list[dict] | None = None,
    dispatch: Mapping[str, Callable[..., object]] | None = None,
    max_steps: int = 8,
    **chat_kwargs,
) -> Result:
    """Synchronous tool loop over a sync ``Broker``, returning the final round's result.

    Mirrors ``arun_tool_loop`` but calls the blocking ``Broker.chat``; it does
    not use the async engine directly so it is safe to call from any thread.
    """
    convo = list(messages)
    dispatch = dispatch or {}
    for _ in range(max_steps):
        result = llms.chat(convo, tools=tools, **chat_kwargs)
        if _advance_tool_loop(convo, result, dispatch):
            return result
    raise ToolLoopLimitError(_TOOL_LOOP_EXHAUSTED.format(max_steps=max_steps))

util

Small helpers with no llmbroker domain in them.

atomic

Replacing a file's contents without a window where it is half-written.

write_atomic(target, text)

Write through a sibling temp file and rename, so a crash mid-write cannot truncate the config the user already has.

Source code in src/llmbroker/util/atomic.py
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
def write_atomic(target: Path, text: str) -> None:
    """Write through a sibling temp file and rename, so a crash mid-write cannot
    truncate the config the user already has."""
    # NamedTemporaryFile creates at 0600 and os.replace carries that onto the target,
    # locking everyone but one user out of the config a sync just rewrote.
    mode = target.stat().st_mode & 0o777 if target.exists() else None
    with tempfile.NamedTemporaryFile(
        "w",
        encoding="utf-8",
        dir=target.parent,
        prefix=f".{target.name}.",
        delete=False,
    ) as fh:
        fh.write(text)
        fh.flush()
        os.fsync(fh.fileno())
        tmp = Path(fh.name)
    try:
        if mode is not None:
            tmp.chmod(mode)
        os.replace(tmp, target)
    except OSError:
        tmp.unlink(missing_ok=True)
        raise

vault

HashiCorp Vault (KV v2) backend. Needs hvac (llmbroker[vault]); importing this package is how a host declares that dependency.

Secrets

HashiCorp Vault KV v2-backed mutable secrets store.

hvac is sync-only; all calls run inside asyncio.to_thread. aclose is a no-op.

Source code in src/llmbroker/vault/secrets.py
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
class Secrets:
    """HashiCorp Vault KV v2-backed mutable secrets store.

    ``hvac`` is sync-only; all calls run inside ``asyncio.to_thread``.
    ``aclose`` is a no-op.
    """

    def __init__(self, url: str, token: str, *, mount_point: str = "secret") -> None:
        self._client = hvac.Client(url=url, token=token)
        self._mount_point = mount_point

    def _path(self, ref: str) -> str:
        if _SEPARATOR in ref:
            raise ValueError(
                f"vault.Secrets: ref {ref!r} contains {_SEPARATOR!r}, which this backend"
                " uses to keep a scoped ref inside one KV path segment — rename the ref"
                " or the scope",
            )
        return f"llmbroker/{ref.replace('/', _SEPARATOR)}"

    async def resolve(self, ref: str) -> str:
        try:
            response = await asyncio.to_thread(
                self._client.secrets.kv.v2.read_secret_version,
                path=self._path(ref),
                mount_point=self._mount_point,
            )
            return response["data"]["data"]["value"]
        except hvac.exceptions.InvalidPath as exc:
            raise KeyError(f"vault.Secrets: ref {ref!r} not found") from exc

    async def set(self, ref: str, value: str) -> None:
        await asyncio.to_thread(
            self._client.secrets.kv.v2.create_or_update_secret,
            path=self._path(ref),
            secret={"value": value},
            mount_point=self._mount_point,
        )

    async def refs(self, prefix: str = "") -> frozenset[str]:
        """Every ref under llmbroker's own path, narrowed by ``prefix``. An empty
        path is not an error here — nothing has been stored yet."""
        try:
            response = await asyncio.to_thread(
                self._client.secrets.kv.v2.list_secrets,
                path="llmbroker",
                mount_point=self._mount_point,
            )
        except hvac.exceptions.InvalidPath:
            return frozenset()
        stored = (str(key).replace(_SEPARATOR, "/") for key in response["data"]["keys"])
        return frozenset(ref for ref in stored if ref.startswith(prefix))

    async def aclose(self) -> None:
        return
refs(prefix='') async

Every ref under llmbroker's own path, narrowed by prefix. An empty path is not an error here — nothing has been stored yet.

Source code in src/llmbroker/vault/secrets.py
52
53
54
55
56
57
58
59
60
61
62
63
64
async def refs(self, prefix: str = "") -> frozenset[str]:
    """Every ref under llmbroker's own path, narrowed by ``prefix``. An empty
    path is not an error here — nothing has been stored yet."""
    try:
        response = await asyncio.to_thread(
            self._client.secrets.kv.v2.list_secrets,
            path="llmbroker",
            mount_point=self._mount_point,
        )
    except hvac.exceptions.InvalidPath:
        return frozenset()
    stored = (str(key).replace(_SEPARATOR, "/") for key in response["data"]["keys"])
    return frozenset(ref for ref in stored if ref.startswith(prefix))

secrets

HashiCorp Vault KV v2-backed mutable secrets store.

Secrets

HashiCorp Vault KV v2-backed mutable secrets store.

hvac is sync-only; all calls run inside asyncio.to_thread. aclose is a no-op.

Source code in src/llmbroker/vault/secrets.py
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
class Secrets:
    """HashiCorp Vault KV v2-backed mutable secrets store.

    ``hvac`` is sync-only; all calls run inside ``asyncio.to_thread``.
    ``aclose`` is a no-op.
    """

    def __init__(self, url: str, token: str, *, mount_point: str = "secret") -> None:
        self._client = hvac.Client(url=url, token=token)
        self._mount_point = mount_point

    def _path(self, ref: str) -> str:
        if _SEPARATOR in ref:
            raise ValueError(
                f"vault.Secrets: ref {ref!r} contains {_SEPARATOR!r}, which this backend"
                " uses to keep a scoped ref inside one KV path segment — rename the ref"
                " or the scope",
            )
        return f"llmbroker/{ref.replace('/', _SEPARATOR)}"

    async def resolve(self, ref: str) -> str:
        try:
            response = await asyncio.to_thread(
                self._client.secrets.kv.v2.read_secret_version,
                path=self._path(ref),
                mount_point=self._mount_point,
            )
            return response["data"]["data"]["value"]
        except hvac.exceptions.InvalidPath as exc:
            raise KeyError(f"vault.Secrets: ref {ref!r} not found") from exc

    async def set(self, ref: str, value: str) -> None:
        await asyncio.to_thread(
            self._client.secrets.kv.v2.create_or_update_secret,
            path=self._path(ref),
            secret={"value": value},
            mount_point=self._mount_point,
        )

    async def refs(self, prefix: str = "") -> frozenset[str]:
        """Every ref under llmbroker's own path, narrowed by ``prefix``. An empty
        path is not an error here — nothing has been stored yet."""
        try:
            response = await asyncio.to_thread(
                self._client.secrets.kv.v2.list_secrets,
                path="llmbroker",
                mount_point=self._mount_point,
            )
        except hvac.exceptions.InvalidPath:
            return frozenset()
        stored = (str(key).replace(_SEPARATOR, "/") for key in response["data"]["keys"])
        return frozenset(ref for ref in stored if ref.startswith(prefix))

    async def aclose(self) -> None:
        return
refs(prefix='') async

Every ref under llmbroker's own path, narrowed by prefix. An empty path is not an error here — nothing has been stored yet.

Source code in src/llmbroker/vault/secrets.py
52
53
54
55
56
57
58
59
60
61
62
63
64
async def refs(self, prefix: str = "") -> frozenset[str]:
    """Every ref under llmbroker's own path, narrowed by ``prefix``. An empty
    path is not an error here — nothing has been stored yet."""
    try:
        response = await asyncio.to_thread(
            self._client.secrets.kv.v2.list_secrets,
            path="llmbroker",
            mount_point=self._mount_point,
        )
    except hvac.exceptions.InvalidPath:
        return frozenset()
    stored = (str(key).replace(_SEPARATOR, "/") for key in response["data"]["keys"])
    return frozenset(ref for ref in stored if ref.startswith(prefix))