Перейти к содержанию

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
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
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
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,
            followed_key_info=(
                partial(_followed_key_info, self._presets, source) if source is not None else None
            ),
        )

        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,
            health=lambda: self._catalog.health,
            direct_missing_keys=lambda: self._catalog.direct_missing_keys,
            direct_unresolved=lambda: self._catalog.direct_unresolved,
            payable=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,
            rebuild=self._rebuild_after_sync,
        )

        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._streams: set[StreamHandle] = set()
        self._closing: asyncio.Task[None] | None = None
        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,
            tick=self._tick,
            on_exhausted=self._on_exhausted,
            own_stream=self._own_stream,
            release_stream=self._streams.discard,
        )

    def _own_stream(self, stream: StreamHandle) -> None:
        if self._closing is not None:
            raise RuntimeError("the broker is closed")
        self._streams.add(stream)

    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 _rebuild_after_sync(self) -> None:
        """Rebuild a running pool once a provisioning in progress is done, since it may
        have read the registry before this write; with no pool there is nothing to rebuild."""
        async with self._provision_lock:
            live = self._provisioned
        if live:
            await self.rebuild()

    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=``: a catalog that cannot be read keeps the whole
        resolution in use, an alias it dropped keeps its own entry, and a handle that
        never resolved is carried unresolved — 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 (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 fact, line in zip(moved, alias_lines(moved), strict=True):
            level = logging.WARNING if fact.change is AliasChange.DROPPED else logging.INFO
            logger.log(level, "direct=: %s", line)
        self._last_declared = resolved
        return resolved

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

    async def ensure_pool(self) -> None:
        """Provision lazily and schedule a due model list refresh.
        Raises if the broker is closed or its registry is empty and unfilled."""
        if self._closing is not None:
            raise RuntimeError("the broker is closed")
        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()

    def _tick(self) -> None:
        """The refresh clock for a call that provisions nothing — ``direct()``, whose
        declared alias has no other clock. Refuses a closed broker, as provisioning does."""
        if self._closing is not None:
            raise RuntimeError("the broker is closed")
        self._refresher.tick()

    @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:
        """Settle owned streams before closing the installation's shared resources."""
        if self._closing is None:
            self._closing = asyncio.create_task(self._close())
        await asyncio.shield(self._closing)

    async def _close(self) -> None:
        results = await asyncio.gather(
            *(stream.aclose() for stream in tuple(self._streams)),
            return_exceptions=True,
        )
        # 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()
        for result in results:
            if isinstance(result, BaseException):
                raise result

    async def __aenter__(self) -> "AsyncBroker":
        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,
        stream_selection_window: float = 1.0,
    ) -> StreamHandle:
        """Return an owned stream that can supply another complete pool answer.
        The broker closes it; use ``async with`` for earlier release."""
        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,
            stream_selection_window=stream_selection_window,
        )

    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.

aclose() async

Settle owned streams before closing the installation's shared resources.

Source code in src/llmbroker/broker/broker.py
373
374
375
376
377
async def aclose(self) -> None:
    """Settle owned streams before closing the installation's shared resources."""
    if self._closing is None:
        self._closing = asyncio.create_task(self._close())
    await asyncio.shield(self._closing)

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
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
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
474
475
476
477
478
479
480
481
482
483
484
485
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
519
520
521
522
523
524
525
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
527
528
529
530
531
532
533
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

Provision lazily and schedule a due model list refresh. Raises if the broker is closed or its registry is empty and unfilled.

Source code in src/llmbroker/broker/broker.py
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
async def ensure_pool(self) -> None:
    """Provision lazily and schedule a due model list refresh.
    Raises if the broker is closed or its registry is empty and unfilled."""
    if self._closing is not None:
        raise RuntimeError("the broker is closed")
    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
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
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
267
268
269
270
271
272
273
274
275
276
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
487
488
489
490
491
492
493
494
495
496
497
498
499
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
559
560
561
562
563
564
565
566
567
568
569
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, stream_selection_window=1.0)

Return an owned stream that can supply another complete pool answer. The broker closes it; use async with for earlier release.

Source code in src/llmbroker/broker/broker.py
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
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,
    stream_selection_window: float = 1.0,
) -> StreamHandle:
    """Return an owned stream that can supply another complete pool answer.
    The broker closes it; use ``async with`` for earlier release."""
    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,
        stream_selection_window=stream_selection_window,
    )

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
367
368
369
370
371
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(), ask() and chat(). Pass an httpx.AsyncClient to share a connection pool, or let it open and close its own; tool_params ride every chat that carries tools, under params.

Source code in src/llmbroker/direct.py
 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
class AsyncDirectClient:
    """Async direct client for one named model — ``stream()``, ``ask()`` and ``chat()``.
    Pass an ``httpx.AsyncClient`` to share a connection pool, or let it open and close
    its own; ``tool_params`` ride every ``chat`` that carries tools, under ``params``."""

    def __init__(  # noqa: PLR0913 - where the model is, and how to reach it
        self,
        *,
        base_url: str,
        model: str,
        api_key: str,
        timeout: float = _DEFAULT_TIMEOUT,
        client: httpx.AsyncClient | None = None,
        tool_params: Mapping[str, object] | None = None,
    ) -> None:
        self._model = model
        self._timeout = timeout
        self._http = client
        self._owns_http = client is None
        self._request = partial(
            build_chat_request,
            base_url,
            model,
            api_key,
            tool_params=tool_params,
        )

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

    async def ask(
        self,
        prompt: str | None = None,
        *,
        messages: list[dict] | None = None,
        timeout: float | None = None,
        params: Mapping[str, object] | None = None,
    ) -> DirectResult:
        return await self.chat(_messages(prompt, messages), timeout=timeout, params=params)

    async def chat(
        self,
        messages: list[dict],
        *,
        tools: list[dict] | None = None,
        timeout: float | None = None,
        params: Mapping[str, object] | None = None,
    ) -> DirectResult:
        """One complete reply, which may be tool calls and no text."""
        url, headers, body = self._request(messages, tools, 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(
            _messages(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()

chat(messages, *, tools=None, timeout=None, params=None) async

One complete reply, which may be tool calls and no text.

Source code in src/llmbroker/direct.py
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
async def chat(
    self,
    messages: list[dict],
    *,
    tools: list[dict] | None = None,
    timeout: float | None = None,
    params: Mapping[str, object] | None = None,
) -> DirectResult:
    """One complete reply, which may be tool calls and no text."""
    url, headers, body = self._request(messages, tools, 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)

AsyncLLM

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

Source code in src/llmbroker/broker/result.py
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
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
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]],
        tick: Callable[[], None],
        on_exhausted: Callable[[NoLLMAvailableError, KeyRing], Awaitable[bool]],
        own_stream: Callable[[StreamHandle], None],
        release_stream: Callable[[StreamHandle], None],
    ) -> 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._tick = tick
        self._on_exhausted = on_exhausted
        self._own_stream = own_stream
        self._release_stream = release_stream

    @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,
        stream_selection_window: float = 1.0,
    ) -> StreamHandle:
        """Return an owned stream that can supply another complete pool answer.
        The broker closes it; use ``async with`` for earlier release."""
        receipt = CallReceipt()
        handle = StreamHandle(
            partial(
                self._start_stream,
                receipt,
                prompt,
                operation=operation,
                trace_id=trace_id,
                wait=wait,
                fastest_of=fastest_of,
                parallel_recovery=parallel_recovery,
                response_format=response_format,
                stream_selection_window=stream_selection_window,
            ),
            receipt,
            operation=operation,
            store=self._store,
            scope=self.scope,
            observe_quality=(
                self._learner.record_quality_observed if self._learner is not None else None
            ),
            on_close=self._release_stream,
        )
        self._own_stream(handle)
        return handle

    async def _start_stream(  # 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,
        stream_selection_window: float,
    ) -> _RoutedStream:
        await self._ensure_pool()
        messages = [{"role": "user", "content": prompt}]
        return 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,
            stream_selection_window=stream_selection_window,
            _on_exhausted=lambda exc: self._on_exhausted(exc, self._ring),
        )

    # ------------------------------------------------------------------
    # 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(),
            tool_params=cfg.tool_params,
        )

    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",
            )
        self._tick()
        stored, declared = await self._catalog.entries()
        cfg = find_declared(stored, declared, self._catalog.direct_unresolved, 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
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
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
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
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(),
        tool_params=cfg.tool_params,
    )

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
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
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
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
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",
        )
    self._tick()
    stored, declared = await self._catalog.entries()
    cfg = find_declared(stored, declared, self._catalog.direct_unresolved, 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
359
360
361
362
363
364
365
366
367
368
369
370
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, stream_selection_window=1.0)

Return an owned stream that can supply another complete pool answer. The broker closes it; use async with for earlier release.

Source code in src/llmbroker/broker/llms.py
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
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,
    stream_selection_window: float = 1.0,
) -> StreamHandle:
    """Return an owned stream that can supply another complete pool answer.
    The broker closes it; use ``async with`` for earlier release."""
    receipt = CallReceipt()
    handle = StreamHandle(
        partial(
            self._start_stream,
            receipt,
            prompt,
            operation=operation,
            trace_id=trace_id,
            wait=wait,
            fastest_of=fastest_of,
            parallel_recovery=parallel_recovery,
            response_format=response_format,
            stream_selection_window=stream_selection_window,
        ),
        receipt,
        operation=operation,
        store=self._store,
        scope=self.scope,
        observe_quality=(
            self._learner.record_quality_observed if self._learner is not None else None
        ),
        on_close=self._release_stream,
    )
    self._own_stream(handle)
    return handle

AsyncResult

Bases: RoutedCall

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

Source code in src/llmbroker/broker/result.py
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
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
151
152
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
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
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, self._async),
            daemon=True,
            name="llmbroker-loop",
        )
        self._thread.start()
        # Backstop for a Broker nobody closes. The callback never holds self, so it does
        # not pin the instance it is registered on.
        self._finalizer = weakref.finalize(self, _shutdown, self._loop, self._async)
        # At exit the executors are already shut, so a teardown could journal nothing.
        self._finalizer.atexit = False
        self._run = partial(_run_on, self._loop)

    @property
    def llms(self) -> "LLMs":
        # Made on each access: stored, it would hold self in a cycle only the cycle
        # collector could break, and the teardown would then run on whatever thread it hits.
        return LLMs(self, self._loop, self._async.llms)

    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, self._loop, self._async.for_scope(scope))

    def ensure_pool(self) -> None:
        """Provision now rather than on the first call that routes — the eager fail-fast."""
        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 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,
        stream_selection_window: float = 1.0,
    ) -> Stream:
        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,
            stream_selection_window=stream_selection_window,
        )

    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:
        """Close every owned stream and wait until each is settled and journaled."""
        if self._finalizer.detach() is None:
            return
        try:
            self._run(_teardown(self._async))
        finally:
            self._loop.call_soon_threadsafe(self._loop.stop)
            self._thread.join()

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

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

close()

Close every owned stream and wait until each is settled and journaled.

Source code in src/llmbroker/sync.py
567
568
569
570
571
572
573
574
575
def close(self) -> None:
    """Close every owned stream and wait until each is settled and journaled."""
    if self._finalizer.detach() is None:
        return
    try:
        self._run(_teardown(self._async))
    finally:
        self._loop.call_soon_threadsafe(self._loop.stop)
        self._thread.join()

ensure_pool()

Provision now rather than on the first call that routes — the eager fail-fast.

Source code in src/llmbroker/sync.py
434
435
436
def ensure_pool(self) -> None:
    """Provision now rather than on the first call that routes — the eager fail-fast."""
    self._run(self._async.ensure_pool())

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
429
430
431
432
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, self._loop, 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
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
@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
229
230
231
232
233
234
235
236
237
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
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
@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 = ""
    tool_params: Mapping[str, object] = field(default_factory=dict, hash=False)

    @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,
            tool_params=self.tool_params,
        )

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
56
57
58
59
60
61
62
63
64
65
66
67
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,
        tool_params=self.tool_params,
    )

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
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
@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. It carries no
        tool parameters: the catalog vouches only for the lines it lists."""
        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. It carries no tool parameters: the catalog vouches only for the lines it lists.

Source code in src/llmbroker/broker/curated.py
29
30
31
32
33
34
35
36
37
38
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. It carries no
    tool parameters: the catalog vouches only for the lines it lists."""
    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 — stream(), ask() and chat(), none needing an event loop. client and tool_params mean what they do on AsyncDirectClient.

Source code in src/llmbroker/direct.py
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
class DirectClient:
    """Synchronous direct client for one named model — ``stream()``, ``ask()`` and
    ``chat()``, none needing an event loop. ``client`` and ``tool_params`` mean what
    they do on ``AsyncDirectClient``."""

    def __init__(  # noqa: PLR0913 - where the model is, and how to reach it
        self,
        *,
        base_url: str,
        model: str,
        api_key: str,
        timeout: float = _DEFAULT_TIMEOUT,
        client: httpx.Client | None = None,
        tool_params: Mapping[str, object] | None = None,
    ) -> None:
        self._model = model
        self._timeout = timeout
        self._http = client
        self._owns_http = client is None
        self._request = partial(
            build_chat_request,
            base_url,
            model,
            api_key,
            tool_params=tool_params,
        )

    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:
        return self.chat(_messages(prompt, messages), timeout=timeout, params=params)

    def chat(
        self,
        messages: list[dict],
        *,
        tools: list[dict] | None = None,
        timeout: float | None = None,
        params: Mapping[str, object] | None = None,
    ) -> DirectResult:
        """One complete reply, which may be tool calls and no text."""
        url, headers, body = self._request(messages, tools, 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 stream(
        self,
        prompt: str | None = None,
        *,
        messages: list[dict] | None = None,
        timeout: float | None = None,
        params: Mapping[str, object] | None = None,
    ) -> Iterator[str]:
        """Text deltas as they arrive. Closing the iterator early closes the response,
        and with it the connection the provider is writing to."""
        url, headers, body = self._request(
            _messages(prompt, messages),
            stream=True,
            params=params,
        )
        try:
            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 = resp.read().decode(errors="replace")[:DETAIL_SNIPPET]
                    raise provider_error(resp.status_code, detail, resp.headers)
                produced = False
                for chunk in iter_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

    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()

chat(messages, *, tools=None, timeout=None, params=None)

One complete reply, which may be tool calls and no text.

Source code in src/llmbroker/direct.py
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
def chat(
    self,
    messages: list[dict],
    *,
    tools: list[dict] | None = None,
    timeout: float | None = None,
    params: Mapping[str, object] | None = None,
) -> DirectResult:
    """One complete reply, which may be tool calls and no text."""
    url, headers, body = self._request(messages, tools, 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)

stream(prompt=None, *, messages=None, timeout=None, params=None)

Text deltas as they arrive. Closing the iterator early closes the response, and with it the connection the provider is writing to.

Source code in src/llmbroker/direct.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
256
257
258
259
260
261
262
263
def stream(
    self,
    prompt: str | None = None,
    *,
    messages: list[dict] | None = None,
    timeout: float | None = None,
    params: Mapping[str, object] | None = None,
) -> Iterator[str]:
    """Text deltas as they arrive. Closing the iterator early closes the response,
    and with it the connection the provider is writing to."""
    url, headers, body = self._request(
        _messages(prompt, messages),
        stream=True,
        params=params,
    )
    try:
        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 = resp.read().decode(errors="replace")[:DETAIL_SNIPPET]
                raise provider_error(resp.status_code, detail, resp.headers)
            produced = False
            for chunk in iter_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

DirectResult dataclass

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

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

    text: str
    usage: Usage | None = None
    tool_calls: list[dict] | 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
20
21
22
23
24
25
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:  # noqa: ARG002
        return

    async def record_quality(
        self,
        call_id: str,  # noqa: ARG002
        score: float,  # noqa: ARG002
        *,
        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
113
114
115
116
117
118
119
120
121
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
57
58
59
60
61
62
63
64
65
@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
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
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
12
13
14
15
16
17
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
 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
@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
    # What the model needs on a request that carries tools, applied under the caller's
    # own parameters. Out of the hash: a mapping is not hashable, and identity is the name.
    tool_params: Mapping[str, object] = field(default_factory=dict, hash=False)

    def __post_init__(self) -> None:
        check_request_params(self.tool_params)
        object.__setattr__(self, "tool_params", dict(self.tool_params))

    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}
        >>> replace(curated, tool_params={"reasoning_effort": "none"}).to_metadata()
        {'tool_params': {'reasoning_effort': 'none'}}
        """
        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
        if self.tool_params:
            metadata["tool_params"] = dict(self.tool_params)
        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),
            tool_params=_tool_params_from_metadata(metadata.get("tool_params"), 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
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
@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),
        tool_params=_tool_params_from_metadata(metadata.get("tool_params"), 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() {'weight': 0.7} replace(curated, tool_params={"reasoning_effort": "none"}).to_metadata() {'tool_params': {'reasoning_effort': 'none'}}

Source code in src/llmbroker/models.py
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
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}
    >>> replace(curated, tool_params={"reasoning_effort": "none"}).to_metadata()
    {'tool_params': {'reasoning_effort': 'none'}}
    """
    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
    if self.tool_params:
        metadata["tool_params"] = dict(self.tool_params)
    return metadata

LLMMetrics dataclass

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

Source code in src/llmbroker/models.py
338
339
340
341
342
343
344
@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
54
55
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
362
363
364
365
366
367
368
369
370
371
372
373
@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
48
49
50
51
52
53
54
@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
347
348
349
350
351
352
353
354
355
356
357
358
359
@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
92
93
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
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
class LLMs:
    """Synchronous analogue of AsyncLLMs — one caller over the shared pool."""

    def __init__(
        self,
        broker: "Broker",
        loop: asyncio.AbstractEventLoop,
        async_llms: AsyncLLMs,
    ) -> None:
        self._broker = broker  # a live caller keeps its broker, and so its loop, alive
        self._loop = loop
        self._run = partial(_run_on, loop)
        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 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,
        stream_selection_window: float = 1.0,
    ) -> Stream:
        """Return an owned stream that can supply another complete pool answer. The
        broker closes it; ``with`` or ``close()`` releases it earlier."""
        return Stream(
            self._broker,
            self._loop,
            self._async.stream(
                prompt,
                operation=operation,
                trace_id=trace_id,
                wait=wait,
                fastest_of=fastest_of,
                parallel_recovery=parallel_recovery,
                response_format=response_format,
                stream_selection_window=stream_selection_window,
            ),
        )

    def direct(self, alias: str | None = None, *, name: str | None = None) -> DirectClient:
        """Return a synchronous direct client for a declared model — 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,
            tool_params=cfg.tool_params,
        )

    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 for a declared model — same alias/name keyspaces and errors as the async counterpart.

Source code in src/llmbroker/sync.py
328
329
330
331
332
333
334
335
336
337
def direct(self, alias: str | None = None, *, name: str | None = None) -> DirectClient:
    """Return a synchronous direct client for a declared model — 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,
        tool_params=cfg.tool_params,
    )

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

Return an owned stream that can supply another complete pool answer. The broker closes it; with or close() releases it earlier.

Source code in src/llmbroker/sync.py
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
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,
    stream_selection_window: float = 1.0,
) -> Stream:
    """Return an owned stream that can supply another complete pool answer. The
    broker closes it; ``with`` or ``close()`` releases it earlier."""
    return Stream(
        self._broker,
        self._loop,
        self._async.stream(
            prompt,
            operation=operation,
            trace_id=trace_id,
            wait=wait,
            fastest_of=fastest_of,
            parallel_recovery=parallel_recovery,
            response_format=response_format,
            stream_selection_window=stream_selection_window,
        ),
    )

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
41
42
43
44
45
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
87
88
89
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
180
181
182
183
184
185
186
@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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
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
189
190
191
192
193
194
195
196
@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
79
80
81
82
83
84
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
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
@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, ...] = ()
    _direct_unresolved: Mapping[str, str] = field(default_factory=lambda: _EMPTY_MAP)

    @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 direct_unresolved(self) -> Mapping[str, str]:
        """Handles ``direct=`` declared and could not resolve, each with why. A typo in
        one alias is visible here without making a call, and fails nothing else."""
        return self._direct_unresolved

    @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.

direct_unresolved property

Handles direct= declared and could not resolve, each with why. A typo in one alias is visible here without making a call, and fails nothing else.

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
102
103
104
105
106
107
108
109
110
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
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
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
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
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
45
46
47
48
49
50
51
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

Stream

Synchronous analogue of StreamHandle. Each pull runs on the broker's loop, so it raises what the async stream raises at that pull; closing it, or dropping it unclosed, closes the async stream and so cancels the provider request.

Source code in src/llmbroker/sync.py
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
class Stream:
    """Synchronous analogue of StreamHandle. Each pull runs on the broker's loop, so it
    raises what the async stream raises at that pull; closing it, or dropping it
    unclosed, closes the async stream and so cancels the provider request."""

    def __init__(
        self,
        broker: "Broker",
        loop: asyncio.AbstractEventLoop,
        handle: StreamHandle,
    ) -> None:
        self._broker = broker  # a live stream keeps its broker, and so its loop, alive
        self._run = partial(_run_on, loop)
        self._async = handle
        self._finalizer = weakref.finalize(self, _abandon, loop, handle)
        # At exit the loop thread dies with the process; a close scheduled then never runs.
        self._finalizer.atexit = False

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

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

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

    @property
    def usage(self) -> Usage | None:
        return self._async.usage

    def __iter__(self) -> "Stream":
        return self

    def __next__(self) -> str:
        if not self._finalizer.alive or self._async.closed:
            raise StopIteration
        try:
            delta = self._run(_pull(self._async))
        except CancelledError:
            # A close of this stream or of the broker, made from another thread, ends it.
            if self._async.closed:
                raise StopIteration from None
            raise
        if delta is None:
            raise StopIteration
        return delta

    def another(self) -> Result | None:
        answer = self._run(self._async.another())
        return None if answer is None else Result(self._run, answer)

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

    def close(self) -> None:
        """Close all provider work and wait for its settlement."""
        if self._finalizer.detach() is None or self._async.closed:
            return
        self._run(self._async.aclose())

    def __enter__(self) -> "Stream":
        if not self._finalizer.alive or self._async.closed:
            raise RuntimeError("the stream is closed")
        return self

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

close()

Close all provider work and wait for its settlement.

Source code in src/llmbroker/sync.py
212
213
214
215
216
def close(self) -> None:
    """Close all provider work and wait for its settlement."""
    if self._finalizer.detach() is None or self._async.closed:
        return
    self._run(self._async.aclose())

StreamHandle

Bases: RoutedCall

A streamed answer whose owner can supply further complete pool answers. The broker closes it; async with releases its resources earlier.

Source code in src/llmbroker/broker/result.py
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
class StreamHandle(RoutedCall):
    """A streamed answer whose owner can supply further complete pool answers.
    The broker closes it; ``async with`` releases its resources earlier."""

    def __init__(  # noqa: PLR0913
        self,
        start: StreamStart,
        receipt: CallReceipt,
        *,
        operation: str | None,
        store: StoreProtocol,
        scope: str | None,
        observe_quality: ObserveQuality | None,
        on_close: Callable[["StreamHandle"], None] | None = None,
    ) -> None:
        super().__init__(
            receipt,
            operation=operation,
            store=store,
            scope=scope,
            observe_quality=observe_quality,
        )
        self._start = start
        self._source: _StreamSource | None = None
        self._active: asyncio.Task[object] | None = None
        self._closed = False
        self._cleanup: asyncio.Task[None] | None = None
        self._on_close = on_close

    @property
    def closed(self) -> bool:
        """Whether this stream was closed, by its owner or by the broker."""
        return self._closed

    async def __aenter__(self) -> "StreamHandle":
        if self._closed:
            raise RuntimeError("the stream is closed")
        return self

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

    def __aiter__(self) -> "StreamHandle":
        return self

    async def __anext__(self) -> str:
        if self._closed:
            raise StopAsyncIteration
        if self._active is not None:
            raise RuntimeError("the stream already has an active pull or continuation")
        task = cast("asyncio.Task[object]", asyncio.current_task())
        self._active = task
        try:
            if self._source is None:
                self._source = await self._start()
            return await anext(self._source)
        finally:
            if self._active is task:
                self._active = None

    async def another(self) -> AsyncResult | None:
        """Return the next complete answer from this routed call, or ``None``."""
        if self._closed:
            raise RuntimeError("the stream is closed")
        if self._active is not None:
            raise RuntimeError("the stream already has an active pull or continuation")
        if self._source is None:
            raise RuntimeError("another answer requires a complete initial answer")
        task = cast("asyncio.Task[object]", asyncio.current_task())
        self._active = task
        try:
            return await self._source.another()
        finally:
            if self._active is task:
                self._active = None

    async def aclose(self) -> None:
        """Close all provider work and await its settlement."""
        if self._cleanup is None:
            self._closed = True
            self._cleanup = asyncio.create_task(self._close())
        await asyncio.shield(self._cleanup)

    async def _close(self) -> None:
        active = self._active
        if active is not None:
            active.cancel()
            await asyncio.gather(active, return_exceptions=True)
        try:
            if self._source is not None:
                await self._source.aclose()
                self._source = None
        finally:
            if self._on_close is not None:
                self._on_close(self)
                self._on_close = None

closed property

Whether this stream was closed, by its owner or by the broker.

aclose() async

Close all provider work and await its settlement.

Source code in src/llmbroker/broker/result.py
214
215
216
217
218
219
async def aclose(self) -> None:
    """Close all provider work and await its settlement."""
    if self._cleanup is None:
        self._closed = True
        self._cleanup = asyncio.create_task(self._close())
    await asyncio.shield(self._cleanup)

another() async

Return the next complete answer from this routed call, or None.

Source code in src/llmbroker/broker/result.py
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
async def another(self) -> AsyncResult | None:
    """Return the next complete answer from this routed call, or ``None``."""
    if self._closed:
        raise RuntimeError("the stream is closed")
    if self._active is not None:
        raise RuntimeError("the stream already has an active pull or continuation")
    if self._source is None:
        raise RuntimeError("another answer requires a complete initial answer")
    task = cast("asyncio.Task[object]", asyncio.current_task())
    self._active = task
    try:
        return await self._source.another()
    finally:
        if self._active is task:
            self._active = None

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
124
125
126
127
128
129
130
131
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

StreamReplacementError

Bases: LLMRequestError

A raced stream's provisional deltas lost to a complete answer from another lane: discard every delta received and use replacement instead. Terminal for that iterator — see docs/ "Racing a stream".

Source code in src/llmbroker/exceptions.py
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
class StreamReplacementError(LLMRequestError):
    """A raced stream's provisional deltas lost to a complete answer from another lane:
    discard every delta received and use ``replacement`` instead. Terminal for that
    iterator — see ``docs/`` "Racing a stream"."""

    def __init__(
        self,
        message: str,
        *,
        replacement: "AsyncResult",
        streamed_llm_name: str,
    ) -> None:
        super().__init__(message)
        self.replacement = replacement
        self.streamed_llm_name = streamed_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
28
29
30
31
32
33
34
35
36
37
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
214
215
216
217
218
219
220
221
222
223
224
225
226
@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
96
97
98
99
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
40
41
42
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
75
76
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
240
241
242
243
244
245
246
247
@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 llms.chat until a tool-call-free reply; execute tools via dispatch. Returns that last round's result: earlier rounds are calls of their own, so usage is the final round's alone.

Source code in src/llmbroker/tool_loop.py
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
async def arun_tool_loop(
    llms: ChatProtocol[Awaitable[ReplyT]],
    messages: list[dict],
    *,
    tools: list[dict] | None = None,
    dispatch: Mapping[str, Callable[..., object]] | None = None,
    max_steps: int = 8,
    **chat_kwargs,
) -> ReplyT:
    """Drive ``llms.chat`` until a tool-call-free reply; execute tools via dispatch.
    Returns that last round's result: earlier rounds are calls of their own, 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
137
138
139
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
142
143
144
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
131
132
133
134
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
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
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)

arun_tool_loop over a blocking chat, returning the final round's result. It never touches the async engine itself, so it is safe to call from any thread.

Source code in src/llmbroker/tool_loop.py
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
def run_tool_loop(
    llms: ChatProtocol[ReplyT],
    messages: list[dict],
    *,
    tools: list[dict] | None = None,
    dispatch: Mapping[str, Callable[..., object]] | None = None,
    max_steps: int = 8,
    **chat_kwargs,
) -> ReplyT:
    """``arun_tool_loop`` over a blocking ``chat``, returning the final round's result.
    It never touches the async engine itself, 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
19
20
21
22
23
24
class AliasChange(Enum):
    """What a re-resolution found for one declared alias."""

    MODEL = "model"
    KEY_REF = "key_ref"
    DROPPED = "dropped"
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
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, unreadable = await _catalog_targets(declared, presets, previous=previous, fetch=fetch)
    configs, unresolved, dropped = _each_declaration(declared, targets, unreadable, previous)
    resolved = DeclaredModels(
        configs=configs,
        key_help=_key_help(
            targets,
            configs,
            kept={fact.alias for fact in dropped},
            previous=previous,
        ),
        unresolved=MappingProxyType(unresolved),
    )
    return resolved, _moved(previous, resolved) + dropped

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
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
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
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,
            followed_key_info=(
                partial(_followed_key_info, self._presets, source) if source is not None else None
            ),
        )

        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,
            health=lambda: self._catalog.health,
            direct_missing_keys=lambda: self._catalog.direct_missing_keys,
            direct_unresolved=lambda: self._catalog.direct_unresolved,
            payable=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,
            rebuild=self._rebuild_after_sync,
        )

        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._streams: set[StreamHandle] = set()
        self._closing: asyncio.Task[None] | None = None
        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,
            tick=self._tick,
            on_exhausted=self._on_exhausted,
            own_stream=self._own_stream,
            release_stream=self._streams.discard,
        )

    def _own_stream(self, stream: StreamHandle) -> None:
        if self._closing is not None:
            raise RuntimeError("the broker is closed")
        self._streams.add(stream)

    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 _rebuild_after_sync(self) -> None:
        """Rebuild a running pool once a provisioning in progress is done, since it may
        have read the registry before this write; with no pool there is nothing to rebuild."""
        async with self._provision_lock:
            live = self._provisioned
        if live:
            await self.rebuild()

    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=``: a catalog that cannot be read keeps the whole
        resolution in use, an alias it dropped keeps its own entry, and a handle that
        never resolved is carried unresolved — 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 (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 fact, line in zip(moved, alias_lines(moved), strict=True):
            level = logging.WARNING if fact.change is AliasChange.DROPPED else logging.INFO
            logger.log(level, "direct=: %s", line)
        self._last_declared = resolved
        return resolved

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

    async def ensure_pool(self) -> None:
        """Provision lazily and schedule a due model list refresh.
        Raises if the broker is closed or its registry is empty and unfilled."""
        if self._closing is not None:
            raise RuntimeError("the broker is closed")
        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()

    def _tick(self) -> None:
        """The refresh clock for a call that provisions nothing — ``direct()``, whose
        declared alias has no other clock. Refuses a closed broker, as provisioning does."""
        if self._closing is not None:
            raise RuntimeError("the broker is closed")
        self._refresher.tick()

    @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:
        """Settle owned streams before closing the installation's shared resources."""
        if self._closing is None:
            self._closing = asyncio.create_task(self._close())
        await asyncio.shield(self._closing)

    async def _close(self) -> None:
        results = await asyncio.gather(
            *(stream.aclose() for stream in tuple(self._streams)),
            return_exceptions=True,
        )
        # 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()
        for result in results:
            if isinstance(result, BaseException):
                raise result

    async def __aenter__(self) -> "AsyncBroker":
        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,
        stream_selection_window: float = 1.0,
    ) -> StreamHandle:
        """Return an owned stream that can supply another complete pool answer.
        The broker closes it; use ``async with`` for earlier release."""
        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,
            stream_selection_window=stream_selection_window,
        )

    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.

aclose() async

Settle owned streams before closing the installation's shared resources.

Source code in src/llmbroker/broker/broker.py
373
374
375
376
377
async def aclose(self) -> None:
    """Settle owned streams before closing the installation's shared resources."""
    if self._closing is None:
        self._closing = asyncio.create_task(self._close())
    await asyncio.shield(self._closing)
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
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
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
474
475
476
477
478
479
480
481
482
483
484
485
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
519
520
521
522
523
524
525
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
527
528
529
530
531
532
533
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

Provision lazily and schedule a due model list refresh. Raises if the broker is closed or its registry is empty and unfilled.

Source code in src/llmbroker/broker/broker.py
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
async def ensure_pool(self) -> None:
    """Provision lazily and schedule a due model list refresh.
    Raises if the broker is closed or its registry is empty and unfilled."""
    if self._closing is not None:
        raise RuntimeError("the broker is closed")
    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
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
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
267
268
269
270
271
272
273
274
275
276
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
487
488
489
490
491
492
493
494
495
496
497
498
499
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
559
560
561
562
563
564
565
566
567
568
569
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, stream_selection_window=1.0)

Return an owned stream that can supply another complete pool answer. The broker closes it; use async with for earlier release.

Source code in src/llmbroker/broker/broker.py
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
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,
    stream_selection_window: float = 1.0,
) -> StreamHandle:
    """Return an owned stream that can supply another complete pool answer.
    The broker closes it; use ``async with`` for earlier release."""
    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,
        stream_selection_window=stream_selection_window,
    )
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
367
368
369
370
371
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
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
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,
        followed_key_info: "Callable[[], Mapping[str, KeyInfo]] | 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._followed_key_info = followed_key_info
        self._declared: DeclaredModels | None = None
        self._declared_lock = asyncio.Lock()
        self._reported_unresolved: set[str] = set()
        self._health = PoolHealth()
        self._direct_missing_keys: tuple[PendingKey, ...] = ()
        self._key_info: Mapping[str, KeyInfo] = {}
        self._followed_help: Mapping[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

    @property
    def direct_unresolved(self) -> Mapping[str, str]:
        """Handles ``direct=`` declared that the paid catalog could not resolve, each
        with why. Only a call naming one fails; nothing else here knows about them."""
        return self._declared.unresolved if self._declared is not None else _NO_UNRESOLVED

    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 list followed, then the paid catalog."""
        for known in (self._key_info, self._followed_help):
            if ref in known and known[ref].help:
                return known[ref].help
        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
                self._report_unresolved(declared)
            return self._declared if self._declared is not None else DeclaredModels()

    def _report_unresolved(self, declared: DeclaredModels) -> None:
        """One line the first time a handle cannot be resolved. Deduplicated on the set,
        like the missing-key lines: nothing else fails, so a handle that stays unresolved
        would otherwise say so on every refresh."""
        for handle, message in declared.unresolved.items():
            if handle not in self._reported_unresolved:
                logger.error("%s", message)
        self._reported_unresolved = set(declared.unresolved)

    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()
        # What the host declared, not what resolved: a typo is the one thing the
        # registry-is-empty message would send a reader to the wrong place over.
        self._empty = not stored and not declared and not self.direct_unresolved
        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 read at all.
        if held_back or direct_held:
            await self._read_key_help({*held_back, *direct_held})
        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 _read_key_help(self, missing: set[str]) -> None:
        """The followed list is read only for a ref the registry gave no help for, so a
        registry carrying its own reads nothing more."""
        if isinstance(self._registry, KeyInfoProtocol):
            self._key_info = await self._registry.key_info()
        stored = self._key_info
        if self._followed_key_info is not None and any(
            not (ref in stored and stored[ref].help) for ref in missing
        ):
            self._followed_help = self._followed_key_info()

    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.

direct_unresolved property

Handles direct= declared that the paid catalog could not resolve, each with why. Only a call naming one fails; nothing else here knows about them.

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
402
403
404
405
406
407
408
409
410
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
275
276
277
278
279
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
219
220
221
222
223
224
225
226
227
228
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
215
216
217
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 list followed, then the paid catalog.

Source code in src/llmbroker/broker/catalog.py
207
208
209
210
211
212
213
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 list followed, then the paid catalog."""
    for known in (self._key_info, self._followed_help):
        if ref in known and known[ref].help:
            return known[ref].help
    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
443
444
445
446
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
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
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()
    # What the host declared, not what resolved: a typo is the one thing the
    # registry-is-empty message would send a reader to the wrong place over.
    self._empty = not stored and not declared and not self.direct_unresolved
    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
448
449
450
451
452
453
454
455
456
457
458
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
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
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, unresolved, 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
 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
def find_declared(
    stored: list[LLMConfig],
    declared: list[LLMConfig],
    unresolved: Mapping[str, str],
    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:
        return _by_alias(stored, declared, unresolved, alias)
    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
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
@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 = ""
    tool_params: Mapping[str, object] = field(default_factory=dict, hash=False)

    @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,
            tool_params=self.tool_params,
        )
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
56
57
58
59
60
61
62
63
64
65
66
67
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,
        tool_params=self.tool_params,
    )
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
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
@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. It carries no
        tool parameters: the catalog vouches only for the lines it lists."""
        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. It carries no tool parameters: the catalog vouches only for the lines it lists.

Source code in src/llmbroker/broker/curated.py
29
30
31
32
33
34
35
36
37
38
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. It carries no
    tool parameters: the catalog vouches only for the lines it lists."""
    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
137
138
139
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
142
143
144
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
131
132
133
134
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. Raises on a row whose tool_params no request could carry.

Source code in src/llmbroker/broker/curated.py
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
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.
    Raises on a row whose ``tool_params`` no request could carry."""
    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 ""),
                    tool_params=_tool_params_from(row),
                ),
            )
    return tuple(models)
providers_from(catalog)

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

Source code in src/llmbroker/broker/curated.py
94
95
96
97
98
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
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]],
        tick: Callable[[], None],
        on_exhausted: Callable[[NoLLMAvailableError, KeyRing], Awaitable[bool]],
        own_stream: Callable[[StreamHandle], None],
        release_stream: Callable[[StreamHandle], None],
    ) -> 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._tick = tick
        self._on_exhausted = on_exhausted
        self._own_stream = own_stream
        self._release_stream = release_stream

    @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,
        stream_selection_window: float = 1.0,
    ) -> StreamHandle:
        """Return an owned stream that can supply another complete pool answer.
        The broker closes it; use ``async with`` for earlier release."""
        receipt = CallReceipt()
        handle = StreamHandle(
            partial(
                self._start_stream,
                receipt,
                prompt,
                operation=operation,
                trace_id=trace_id,
                wait=wait,
                fastest_of=fastest_of,
                parallel_recovery=parallel_recovery,
                response_format=response_format,
                stream_selection_window=stream_selection_window,
            ),
            receipt,
            operation=operation,
            store=self._store,
            scope=self.scope,
            observe_quality=(
                self._learner.record_quality_observed if self._learner is not None else None
            ),
            on_close=self._release_stream,
        )
        self._own_stream(handle)
        return handle

    async def _start_stream(  # 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,
        stream_selection_window: float,
    ) -> _RoutedStream:
        await self._ensure_pool()
        messages = [{"role": "user", "content": prompt}]
        return 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,
            stream_selection_window=stream_selection_window,
            _on_exhausted=lambda exc: self._on_exhausted(exc, self._ring),
        )

    # ------------------------------------------------------------------
    # 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(),
            tool_params=cfg.tool_params,
        )

    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",
            )
        self._tick()
        stored, declared = await self._catalog.entries()
        cfg = find_declared(stored, declared, self._catalog.direct_unresolved, 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
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
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
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
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(),
        tool_params=cfg.tool_params,
    )
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
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
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
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
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",
        )
    self._tick()
    stored, declared = await self._catalog.entries()
    cfg = find_declared(stored, declared, self._catalog.direct_unresolved, 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
359
360
361
362
363
364
365
366
367
368
369
370
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, stream_selection_window=1.0)

Return an owned stream that can supply another complete pool answer. The broker closes it; use async with for earlier release.

Source code in src/llmbroker/broker/llms.py
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
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,
    stream_selection_window: float = 1.0,
) -> StreamHandle:
    """Return an owned stream that can supply another complete pool answer.
    The broker closes it; use ``async with`` for earlier release."""
    receipt = CallReceipt()
    handle = StreamHandle(
        partial(
            self._start_stream,
            receipt,
            prompt,
            operation=operation,
            trace_id=trace_id,
            wait=wait,
            fastest_of=fastest_of,
            parallel_recovery=parallel_recovery,
            response_format=response_format,
            stream_selection_window=stream_selection_window,
        ),
        receipt,
        operation=operation,
        store=self._store,
        scope=self.scope,
        observe_quality=(
            self._learner.record_quality_observed if self._learner is not None else None
        ),
        on_close=self._release_stream,
    )
    self._own_stream(handle)
    return handle

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
 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
452
453
454
455
456
457
458
459
460
461
462
463
464
465
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],
        eligible_names: frozenset[str] | None = None,
    ) -> list[_Slot]:
        return [
            s
            for s in self._slots.values()
            if (eligible_names is None or s.config.name in eligible_names)
            and 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,
        eligible_names: frozenset[str] | 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, eligible_names)
                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(  # noqa: PLR0913 - one call's candidate boundary
        self,
        *,
        payable: frozenset[str],
        width: int,
        operation: str | None = None,
        exclude: frozenset[str] = frozenset(),
        answer_deadline: float | None = None,
        eligible_names: frozenset[str] | None = None,
        still_needed: Callable[[], bool] | 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:
            if still_needed is not None and not still_needed():
                return []
            now = datetime.now(UTC)
            remaining = None if answer_deadline is None else answer_deadline - time.monotonic()
            return self._reserve(
                self._candidates(payable, exclude, eligible_names),
                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
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
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, eligible_names=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
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
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,
    eligible_names: frozenset[str] | 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, eligible_names)
            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
82
83
84
85
86
87
88
89
90
91
92
93
94
95
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
140
141
142
143
144
145
146
147
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
426
427
428
429
430
431
432
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
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
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
 97
 98
 99
100
101
102
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
149
150
151
152
153
154
155
156
157
158
159
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
411
412
413
414
415
416
417
418
419
420
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
252
253
254
255
256
257
258
259
260
261
262
263
264
265
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
108
109
110
111
112
113
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, eligible_names=None, still_needed=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
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
async def take_free(  # noqa: PLR0913 - one call's candidate boundary
    self,
    *,
    payable: frozenset[str],
    width: int,
    operation: str | None = None,
    exclude: frozenset[str] = frozenset(),
    answer_deadline: float | None = None,
    eligible_names: frozenset[str] | None = None,
    still_needed: Callable[[], bool] | 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:
        if still_needed is not None and not still_needed():
            return []
        now = datetime.now(UTC)
        remaining = None if answer_deadline is None else answer_deadline - time.monotonic()
        return self._reserve(
            self._candidates(payable, exclude, eligible_names),
            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
52
53
54
55
56
57
58
59
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, ...]],
        direct_unresolved: Callable[[], Mapping[str, str]],
        payable: Callable[[], frozenset[str]],
    ) -> None:
        self._pool = pool
        self._metrics_source = metrics_source
        self._health = health
        self._direct_missing_keys = direct_missing_keys
        self._direct_unresolved = direct_unresolved
        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(),
            self._direct_unresolved(),
        )

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
188
189
190
191
192
193
194
@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 text_for_help(self, name: str) -> str | None:
        """The copy on this machine, the wheel's under it, or ``None``. Key help decides
        nothing an installation runs on, so this read never fetches and never reports
        the floor — it repeats on every rebuild that finds a key missing."""
        cached = self._cached(name)
        return cached if cached is not None else bundled_preset_text(name)

    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
161
162
163
164
165
166
167
168
169
170
171
172
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
text_for_help(name)

The copy on this machine, the wheel's under it, or None. Key help decides nothing an installation runs on, so this read never fetches and never reports the floor — it repeats on every rebuild that finds a key missing.

Source code in src/llmbroker/broker/presets.py
138
139
140
141
142
143
def text_for_help(self, name: str) -> str | None:
    """The copy on this machine, the wheel's under it, or ``None``. Key help decides
    nothing an installation runs on, so this read never fetches and never reports
    the floor — it repeats on every rebuild that finds a key missing."""
    cached = self._cached(name)
    return cached if cached is not None else bundled_preset_text(name)
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; rebuild brings a running pool up to date.

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
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
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; ``rebuild`` brings a running pool up to date."""

    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,
        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._rebuild = rebuild

        self._attempted = False
        self._armed = False
        self._filling = 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()
            return
        if not await self._registry.load():
            self._armed = True  # the start fill sets the first deadline itself
            self._filling = True
            try:
                await self._attempt("start")
            finally:
                self._filling = False
            return
        self._arm()

    def tick(self) -> None:
        """The clock as ``direct()`` reaches it: armed on first use where an alias is
        followed, then any due refresh fired. Never the blocking fill of an empty registry."""
        if not self._armed and self._follows_an_alias():
            self._arm()
        self.schedule()

    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) -> None:
        """Set the first deadline from the check record, once: arming again after a refresh
        that failed, and so left no record, would fire it straight back."""
        if self._armed:
            return
        self._armed = True
        interval = self._interval
        if interval is None:
            return
        source = self._source if self._source is not None else PAID_CATALOG
        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 while the start fill runs —
        provisioning reads the registry right after it."""
        if self._rebuild is not None and not self._filling:
            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
130
131
132
133
134
135
136
137
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
87
88
89
90
91
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()
        return
    if not await self._registry.load():
        self._armed = True  # the start fill sets the first deadline itself
        self._filling = True
        try:
            await self._attempt("start")
        finally:
            self._filling = False
        return
    self._arm()
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
100
101
102
103
104
105
106
107
108
109
110
111
112
113
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
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
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
tick()

The clock as direct() reaches it: armed on first use where an alias is followed, then any due refresh fired. Never the blocking fill of an empty registry.

Source code in src/llmbroker/broker/refresher.py
93
94
95
96
97
98
def tick(self) -> None:
    """The clock as ``direct()`` reaches it: armed on first use where an alias is
    followed, then any due refresh fired. Never the blocking fill of an empty registry."""
    if not self._armed and self._follows_an_alias():
        self._arm()
    self.schedule()

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
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(_alias_line(fact) 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
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
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
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
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
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
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
37
38
39
40
41
42
43
44
45
46
@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
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
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
88
89
90
91
92
93
94
95
96
97
98
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

A streamed answer whose owner can supply further complete pool answers. The broker closes it; async with releases its resources earlier.

Source code in src/llmbroker/broker/result.py
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
class StreamHandle(RoutedCall):
    """A streamed answer whose owner can supply further complete pool answers.
    The broker closes it; ``async with`` releases its resources earlier."""

    def __init__(  # noqa: PLR0913
        self,
        start: StreamStart,
        receipt: CallReceipt,
        *,
        operation: str | None,
        store: StoreProtocol,
        scope: str | None,
        observe_quality: ObserveQuality | None,
        on_close: Callable[["StreamHandle"], None] | None = None,
    ) -> None:
        super().__init__(
            receipt,
            operation=operation,
            store=store,
            scope=scope,
            observe_quality=observe_quality,
        )
        self._start = start
        self._source: _StreamSource | None = None
        self._active: asyncio.Task[object] | None = None
        self._closed = False
        self._cleanup: asyncio.Task[None] | None = None
        self._on_close = on_close

    @property
    def closed(self) -> bool:
        """Whether this stream was closed, by its owner or by the broker."""
        return self._closed

    async def __aenter__(self) -> "StreamHandle":
        if self._closed:
            raise RuntimeError("the stream is closed")
        return self

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

    def __aiter__(self) -> "StreamHandle":
        return self

    async def __anext__(self) -> str:
        if self._closed:
            raise StopAsyncIteration
        if self._active is not None:
            raise RuntimeError("the stream already has an active pull or continuation")
        task = cast("asyncio.Task[object]", asyncio.current_task())
        self._active = task
        try:
            if self._source is None:
                self._source = await self._start()
            return await anext(self._source)
        finally:
            if self._active is task:
                self._active = None

    async def another(self) -> AsyncResult | None:
        """Return the next complete answer from this routed call, or ``None``."""
        if self._closed:
            raise RuntimeError("the stream is closed")
        if self._active is not None:
            raise RuntimeError("the stream already has an active pull or continuation")
        if self._source is None:
            raise RuntimeError("another answer requires a complete initial answer")
        task = cast("asyncio.Task[object]", asyncio.current_task())
        self._active = task
        try:
            return await self._source.another()
        finally:
            if self._active is task:
                self._active = None

    async def aclose(self) -> None:
        """Close all provider work and await its settlement."""
        if self._cleanup is None:
            self._closed = True
            self._cleanup = asyncio.create_task(self._close())
        await asyncio.shield(self._cleanup)

    async def _close(self) -> None:
        active = self._active
        if active is not None:
            active.cancel()
            await asyncio.gather(active, return_exceptions=True)
        try:
            if self._source is not None:
                await self._source.aclose()
                self._source = None
        finally:
            if self._on_close is not None:
                self._on_close(self)
                self._on_close = None
closed property

Whether this stream was closed, by its owner or by the broker.

aclose() async

Close all provider work and await its settlement.

Source code in src/llmbroker/broker/result.py
214
215
216
217
218
219
async def aclose(self) -> None:
    """Close all provider work and await its settlement."""
    if self._cleanup is None:
        self._closed = True
        self._cleanup = asyncio.create_task(self._close())
    await asyncio.shield(self._cleanup)
another() async

Return the next complete answer from this routed call, or None.

Source code in src/llmbroker/broker/result.py
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
async def another(self) -> AsyncResult | None:
    """Return the next complete answer from this routed call, or ``None``."""
    if self._closed:
        raise RuntimeError("the stream is closed")
    if self._active is not None:
        raise RuntimeError("the stream already has an active pull or continuation")
    if self._source is None:
        raise RuntimeError("another answer requires a complete initial answer")
    task = cast("asyncio.Task[object]", asyncio.current_task())
    self._active = task
    try:
        return await self._source.another()
    finally:
        if self._active is task:
            self._active = None

route_state

Mutable state shared by atomic and streaming pool routes.

Attempt dataclass

Identity and payer of one in-flight provider attempt.

Source code in src/llmbroker/broker/route_state.py
15
16
17
18
19
20
21
22
23
24
25
@dataclass(frozen=True, slots=True)
class Attempt:
    """Identity and payer of one in-flight provider attempt."""

    config: LLMConfig
    call_id: str
    t0: float
    operation: str | None
    trace_id: str | None
    ring: KeyRing
    resolved_key: str
Lane dataclass

One atomic candidate held at its first produced result.

Source code in src/llmbroker/broker/route_state.py
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
@dataclass(slots=True)
class Lane:
    """One atomic candidate held at its first produced result."""

    config: LLMConfig
    outcome: Outcome
    produced: AsyncGenerator[Any, None]
    task: asyncio.Task[bool] | None = None
    first: Any = None

    async def open(self) -> bool:
        try:
            self.first = await anext(self.produced)
        except StopAsyncIteration:
            return False
        return True

    def done(self) -> bool:
        return self.task is not None and self.task.done()

    def opened(self) -> bool:
        return self.task is not None and self.task.result()
Outcome dataclass

Settlement facts reported by one provider attempt.

Source code in src/llmbroker/broker/route_state.py
28
29
30
31
32
33
34
35
36
37
38
39
40
@dataclass(slots=True)
class Outcome:
    """Settlement facts reported by one provider attempt."""

    receipt: CallReceipt = field(default_factory=CallReceipt)
    answered: bool = False
    verdict: Failed | BudgetExpired | None = None
    superseded: bool = False
    stopped: bool = False
    completed_at: float | None = None
    settling: bool = False
    holds_slot: bool = False
    crashed: Exception | None = None
RouteCall dataclass

Candidate history, clocks, and settlement tasks for one routed call.

Source code in src/llmbroker/broker/route_state.py
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
@dataclass(slots=True)
class RouteCall:
    """Candidate history, clocks, and settlement tasks for one routed call."""

    attempt: Callable[..., AsyncGenerator[Any, None]]
    ring: KeyRing
    operation: str | None
    trace_id: str | None
    answer_deadline: float | None
    width: int
    recovery_width: int
    client_failed: set[str] = field(default_factory=set)
    attempted: set[str] = field(default_factory=set)
    last_client_error: ProviderError | None = None
    expired: bool = False
    losers: list[asyncio.Task[None]] = field(default_factory=list)

    def absorb(self, lane: Any) -> None:
        """Fold one settled attempt into call-wide selection state."""
        verdict = lane.outcome.verdict
        if isinstance(verdict, BudgetExpired):
            self.expired = True
        elif isinstance(verdict, Failed):
            self.client_failed.add(lane.config.name)
            if verdict.error is not None:
                self.last_client_error = verdict.error
absorb(lane)

Fold one settled attempt into call-wide selection state.

Source code in src/llmbroker/broker/route_state.py
84
85
86
87
88
89
90
91
92
def absorb(self, lane: Any) -> None:
    """Fold one settled attempt into call-wide selection state."""
    verdict = lane.outcome.verdict
    if isinstance(verdict, BudgetExpired):
        self.expired = True
    elif isinstance(verdict, Failed):
        self.client_failed.add(lane.config.name)
        if verdict.error is not None:
            self.last_client_error = verdict.error

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
 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
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
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[..., AsyncGenerator[_Produced, None]],
        *,
        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,
    ) -> AsyncGenerator[_Produced, None]:
        """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 self._payable(ring)
            configs = await self._acquire(call, queue_deadline, payable)
            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:
                self._expired(call, payable, timeout_message)
            # Every lane failed without answering ⇒ loop to the next free LLM.

    async def _payable(self, ring: KeyRing) -> frozenset[str]:
        return await ring.payable(c.api_key_ref for c in self._pool.configs.values())

    async def _acquire(
        self,
        call: _Call,
        queue_deadline: float | None,
        payable: frozenset[str],
        eligible_names: frozenset[str] | None = None,
    ) -> list[LLMConfig]:
        """Reserve this call's next candidates, or raise what the caller can act on."""
        try:
            configs = await self._pool.acquire_many(
                queue_deadline,
                payable=payable,
                width=call.width,
                recovery_width=call.recovery_width,
                operation=call.operation,
                exclude=frozenset(
                    call.client_failed | (call.attempted if eligible_names is not None else set()),
                ),
                answer_deadline=call.answer_deadline,
                eligible_names=eligible_names,
            )
        except NoLLMAvailableError as exc:
            if exc.reason == "excluded" and call.last_client_error is not None:
                raise call.last_client_error from None
            raise
        if eligible_names is not None:
            call.attempted.update(config.name for config in configs)
        return configs

    def _expired(self, call: _Call, payable: frozenset[str], message: str) -> NoReturn:
        """The budget ran out with nothing answered. A request an earlier LLM already
        rejected as malformed stays the more useful answer than "the clock ran out"."""
        if call.last_client_error is not None:
            raise call.last_client_error from None
        raise NoLLMAvailableError(
            message,
            reason="timeout",
            retry_at=self._pool.retry_at(payable, exclude=frozenset(call.client_failed)),
        )

    def _produce(
        self,
        call: _Call,
        config: LLMConfig,
        outcome: _Outcome,
    ) -> AsyncGenerator[Any, None]:
        return call.attempt(
            config,
            outcome,
            call.answer_deadline,
            ring=call.ring,
            operation=call.operation,
            trace_id=call.trace_id,
        )

    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=self._produce(call, config, outcome))

    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 _untried(
        self,
        call: _Call,
        tried: frozenset[str],
        needed: int,
        eligible_names: frozenset[str] | None = None,
        *,
        still_needed: Callable[[], bool] | None = None,
    ) -> list[LLMConfig]:
        """Whatever is free this instant among the models this call has not tried. Never
        waits: the lanes still racing must not be stalled to widen the field."""
        if needed <= 0:
            return []
        configs = await self._pool.take_free(
            payable=await self._payable(call.ring),
            width=needed,
            operation=call.operation,
            exclude=tried
            | call.client_failed
            | (call.attempted if eligible_names is not None else set()),
            answer_deadline=call.answer_deadline,
            eligible_names=eligible_names,
            still_needed=still_needed,
        )
        if eligible_names is not None:
            call.attempted.update(config.name for config in configs)
        return configs

    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."""
        configs = await self._untried(
            call,
            frozenset(lane.config.name for lane in opened),
            needed,
        )
        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 | _StreamLane") -> 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 | _StreamLane") -> 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 | _StreamLane") -> 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; one cut
        before its attempt began ran no code at all, so its slot is handed back here."""
        task = lane.task
        if task is not None:
            await asyncio.wait([task])
            if task.cancelled():
                if not lane.outcome.holds_slot:
                    await self._pool.release(lane.config)
            elif 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 | _StreamLane") -> 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,
        outcome: _Outcome,
        *,
        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
        outcome.holds_slot = True
        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,
    ) -> AsyncGenerator[AsyncResult, None]:
        """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,
            outcome,
            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
    # ------------------------------------------------------------------

    def _stream_backend(self) -> StreamBackend:
        """Expose the small service boundary used by the streaming modules."""
        return StreamBackend(
            configs=lambda: self._pool.configs,
            payable=self._payable,
            acquire=self._acquire,
            retry_at=self._pool.retry_at,
            produce=self._produce,
            untried=self._untried,
            publish=self._publish,
            cancel=self._cancel,
            stop=self._stop,
            settled=self._settled,
            new_attempt=self._new_attempt,
            backoff=self._backoff,
            attempt_timeout=self._attempt_timeout,
            spent_budget=self._spent_budget,
            settle_superseded=self._settle_superseded,
            finish_ok=self._finish_ok,
            release=self._pool.release,
            record=self._record,
            dispose=self._dispose,
            http=lambda: self.http,
            store=self._store,
            observe_quality=(
                self._learner.record_quality_observed if self._learner is not None else None
            ),
        )

    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,
        stream_selection_window: float = 1.0,
        _on_exhausted: Callable[[NoLLMAvailableError], Awaitable[bool]] | None = None,
    ) -> "_RoutedStream":
        """Return the lazy owner of one routed stream and its complete alternatives."""
        _check_lanes(fastest_of, parallel_recovery)
        _check_window(stream_selection_window)
        return _RoutedStream(
            self._stream_backend(),
            ring,
            messages,
            receipt,
            operation=operation,
            trace_id=trace_id,
            wait=wait,
            fastest_of=fastest_of,
            parallel_recovery=parallel_recovery,
            response_format=response_format,
            stream_selection_window=stream_selection_window,
            on_exhausted=_on_exhausted,
        )

    _drain_lane = staticmethod(_drain_stream_lane)

    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, stream_selection_window=1.0, _on_exhausted=None)

Return the lazy owner of one routed stream and its complete alternatives.

Source code in src/llmbroker/broker/router.py
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
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,
    stream_selection_window: float = 1.0,
    _on_exhausted: Callable[[NoLLMAvailableError], Awaitable[bool]] | None = None,
) -> "_RoutedStream":
    """Return the lazy owner of one routed stream and its complete alternatives."""
    _check_lanes(fastest_of, parallel_recovery)
    _check_window(stream_selection_window)
    return _RoutedStream(
        self._stream_backend(),
        ring,
        messages,
        receipt,
        operation=operation,
        trace_id=trace_id,
        wait=wait,
        fastest_of=fastest_of,
        parallel_recovery=parallel_recovery,
        response_format=response_format,
        stream_selection_window=stream_selection_window,
        on_exhausted=_on_exhausted,
    )

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()
    }

stream_owner

Lifetime owner for a routed stream and its retained alternative answers.

RoutedStream

Own one streaming route until its public handle is explicitly closed.

Source code in src/llmbroker/broker/stream_owner.py
 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
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
565
566
567
568
569
class RoutedStream:
    """Own one streaming route until its public handle is explicitly closed."""

    def __init__(  # noqa: PLR0913
        self,
        backend: StreamBackend,
        ring: KeyRing,
        messages: list[dict],
        receipt: CallReceipt,
        *,
        operation: str | None,
        trace_id: str | None,
        wait: float | None,
        fastest_of: int | None,
        parallel_recovery: bool,
        response_format: dict | None,
        stream_selection_window: float,
        on_exhausted: Callable[[NoLLMAvailableError], Awaitable[bool]] | None,
    ) -> None:
        self._backend = backend
        self._ring = ring
        self._messages = messages
        self._receipt = receipt
        self._operation = operation
        self._trace_id = trace_id
        self._wait = wait
        self._fastest_of = fastest_of
        self._parallel_recovery = parallel_recovery
        self._response_format = response_format
        self._window = stream_selection_window
        self._on_exhausted = on_exhausted
        self._call: RouteCall | None = None
        self._race: StreamRace | None = None
        self._budget: StreamBudget | None = None
        self._queue_deadline: float | None = None
        self._eligible_names: set[str] = set()
        self._refresh_used = False
        self._initial: AsyncGenerator[str, None] | None = None
        self._initial_complete = False
        self._authoritative: StreamLane | None = None
        self._delivered: set[str] = set()
        self._terminal = False
        self._closed = False
        self._active: asyncio.Task[Any] | None = None
        self._cleanup: asyncio.Task[None] | None = None
        self._opening_order = 0
        self._wave = -1
        self._wave_width: dict[int, int] = {}

    def __aiter__(self) -> "RoutedStream":
        return self

    async def __anext__(self) -> str:
        if self._closed:
            raise StopAsyncIteration
        if self._active is not None:
            raise RuntimeError("the stream already has an active pull or continuation")
        if self._initial is None:
            self._initial = self._initial_deltas()
        task = asyncio.create_task(self._pull_initial(self._initial))
        self._active = task
        try:
            return await task
        except StopAsyncIteration:
            if not self._initial_complete:
                await self._close_after_fault()
            raise
        except StreamReplacementError:
            raise
        except BaseException:
            await self._close_after_fault()
            raise
        finally:
            if self._active is task:
                self._active = None

    @staticmethod
    async def _pull_initial(initial: AsyncIterator[str]) -> str:
        return await anext(initial)

    async def another(self) -> AsyncResult | None:
        if self._closed:
            raise RuntimeError("the stream is closed")
        if not self._initial_complete:
            raise RuntimeError("another answer requires a complete initial answer")
        if self._active is not None:
            raise RuntimeError("the stream already has an active pull or continuation")
        if self._terminal:
            return None
        task = asyncio.create_task(self._next_answer())
        self._active = task
        try:
            return await task
        except BaseException:
            await self._close_after_fault()
            raise
        finally:
            if self._active is task:
                self._active = None

    async def aclose(self) -> None:
        if self._cleanup is None:
            self._closed = True
            self._cleanup = asyncio.create_task(self._close())
        await asyncio.shield(self._cleanup)

    async def _close_after_fault(self) -> None:
        if self._cleanup is None:
            self._closed = True
            self._active = None
            self._cleanup = asyncio.create_task(self._close())
        with suppress(asyncio.CancelledError):
            await asyncio.shield(self._cleanup)

    def _start(self) -> None:
        now = time.monotonic()
        self._queue_deadline = None if self._wait is None else now + self._wait
        answer_deadline = self._queue_deadline if self._wait else None
        self._budget = StreamBudget(answer_deadline)
        attempt = partial(
            stream_attempt,
            self._backend,
            messages=self._messages,
            response_format=self._response_format,
            budget=self._budget,
        )
        self._call = RouteCall(
            attempt=attempt,
            ring=self._ring,
            operation=self._operation,
            trace_id=self._trace_id,
            answer_deadline=answer_deadline,
            width=self._fastest_of if self._fastest_of is not None and self._fastest_of > 1 else 1,
            recovery_width=2 if self._parallel_recovery else 1,
        )
        self._race = StreamRace(lanes=[], width=self._call.width)
        self._eligible_names.update(self._backend.configs())

    @property
    def _current_call(self) -> RouteCall:
        return cast(RouteCall, self._call)

    @property
    def _current_race(self) -> StreamRace:
        return cast(StreamRace, self._race)

    async def _initial_deltas(self) -> AsyncGenerator[str, None]:
        self._start()
        if self._fastest_of is not None and self._fastest_of > 1:
            async for delta in self._explicit_initial():
                yield delta
            return
        async for delta in self._ordinary_initial():
            yield delta

    async def _acquire(self, *, initial: bool) -> list[LLMConfig]:
        call = self._current_call
        while True:
            payable = await self._backend.payable(self._ring)
            try:
                return await self._backend.acquire(
                    call,
                    self._queue_deadline,
                    payable,
                    frozenset(self._eligible_names),
                )
            except NoLLMAvailableError as exc:
                if (
                    initial
                    and not self._refresh_used
                    and self._on_exhausted is not None
                    and await self._on_exhausted(exc)
                ):
                    self._refresh_used = True
                    self._eligible_names.update(self._backend.configs())
                    continue
                raise

    async def _reported(self, exc: NoLLMAvailableError) -> NoLLMAvailableError:
        """What an initial acquisition raises. Where the candidates it may not reopen
        come back by themselves, that is the pool being busy and not a fault nothing
        resolves, so it is reported as a routed call reports the same pool state."""
        if exc.reason != "excluded":
            return exc
        retry_at = await self._retry_at()
        if retry_at is None:
            return exc
        return NoLLMAvailableError(
            "every LLM this stream could use is cooling down",
            reason="timeout",
            retry_at=retry_at,
        )

    async def _retry_at(self) -> datetime | None:
        """When a model this caller can pay for comes back, or ``None`` where one can
        serve right now."""
        payable = await self._backend.payable(self._ring)
        return self._backend.retry_at(
            payable,
            exclude=frozenset(self._current_call.client_failed),
        )

    def _open(
        self,
        configs: list[LLMConfig],
        *,
        hold_first: bool,
        new_wave: bool = False,
    ) -> None:
        call = self._current_call
        race = self._current_race
        if new_wave:
            self._wave += 1
            self._wave_width[self._wave] = max(call.width, len(configs))
        for config in configs:
            outcome = Outcome()
            lane = StreamLane(
                config=config,
                outcome=outcome,
                produced=self._backend.produce(call, config, outcome),
                opening_order=self._opening_order,
                hold_first=hold_first,
                wave=self._wave,
            )
            self._opening_order += 1
            race.lanes.append(lane)
            lane.task = asyncio.create_task(self._drain(lane))

    async def _drain(self, lane: StreamLane) -> None:
        race = self._current_race
        try:
            async for delta in lane.produced:
                if lane.first_delta_at is None:
                    lane.first_delta_at = time.monotonic()
                lane.deltas.append(delta)
                race.wake.set()
                if lane.hold_first and len(lane.deltas) == 1:
                    await lane.release_first.wait()
                if lane.consumer_driven:
                    await lane.pulled.wait()
                    lane.pulled.clear()
        except asyncio.CancelledError:
            raise
        except BaseException as exc:  # noqa: BLE001
            lane.failure = exc
        finally:
            lane.finished = True
            race.wake.set()

    def _absorb_finished(self) -> None:
        call = self._current_call
        for lane in self._current_race.lanes:
            if lane.finished and not lane.absorbed and lane.outcome.completed_at is None:
                lane.absorbed = True
                call.absorb(lane)

    async def _pause(self) -> None:
        await self._current_race.wake.wait()

    def _resume_budget(self) -> None:
        elapsed = self._budget.resume() if self._budget is not None else 0.0
        call = self._current_call
        if call.answer_deadline is not None:
            call.answer_deadline += elapsed
        if self._queue_deadline is not None:
            self._queue_deadline += elapsed

    async def _yield_visible(self, lane: StreamLane) -> AsyncIterator[str]:
        race = self._current_race
        while True:
            race.wake.clear()
            if lane.sent < len(lane.deltas):
                delta = lane.deltas[lane.sent]
                lane.sent += 1
                if self._budget is not None:
                    self._budget.pause()
                yield delta
                self._resume_budget()
                lane.pulled.set()
                continue
            if lane.finished:
                return
            await self._refill_free()
            await race.wake.wait()

    def _first_delta(self) -> StreamLane | None:
        candidates = [lane for lane in self._current_race.lanes if lane.first_delta_at is not None]
        return min(
            candidates,
            key=lambda lane: (cast(float, lane.first_delta_at), lane.opening_order),
            default=None,
        )

    async def _ordinary_initial(self) -> AsyncIterator[str]:  # noqa: C901
        while self._authoritative is None:
            self._current_race.wake.clear()
            self._absorb_finished()
            if self._current_race.lanes:
                await self._refill_free()
            if not self._current_race.live():
                if self._current_call.expired:
                    await self._raise_expired()
                try:
                    self._open(await self._acquire(initial=True), hold_first=True, new_wave=True)
                except NoLLMAvailableError as exc:
                    self._raise_initial_fault(await self._reported(exc))
            selected = self._first_delta()
            if selected is None:
                if not self._current_race.live():
                    continue
                await self._pause()
                continue
            self._authoritative = selected
            self._current_race.exposed = selected
            selected.consumer_driven = True
            name(self._receipt, selected)
            for lane in self._current_race.lanes:
                lane.release_first.set()
        selected = self._authoritative
        async for delta in self._yield_visible(selected):
            await self._refill_free()
            yield delta
        if selected.failure is not None:
            raise selected.failure
        if not selected.outcome.answered:
            self._raise_initial_fault(
                NoLLMAvailableError("no LLM in the pool produced an answer", reason="excluded"),
            )
        self._complete_initial(selected)

    async def _explicit_initial(self) -> AsyncIterator[str]:  # noqa: C901, PLR0912
        race = self._current_race
        while True:
            race.wake.clear()
            self._absorb_finished()
            winner = race.winner()
            if winner is not None and winner.finished:
                replaced = race.exposed if race.exposed is not winner else None
                if replaced is None:
                    if race.exposed is None:
                        race.exposed = winner
                        name(self._receipt, winner)
                    while winner.sent < len(winner.deltas):
                        delta = winner.deltas[winner.sent]
                        winner.sent += 1
                        yield delta
                self._complete_initial(winner)
                if replaced is None:
                    return
                raise StreamReplacementError(
                    f"{replaced.config.name}: its provisional deltas lost the race to a"
                    f" complete answer from {winner.config.name} — discard them and use"
                    " the replacement",
                    replacement=replacement(self._backend, self._current_call, winner),
                    streamed_llm_name=replaced.config.name,
                )
            if not race.lanes or not race.live():
                if self._current_call.expired:
                    await self._raise_expired()
                try:
                    self._open(await self._acquire(initial=True), hold_first=False, new_wave=True)
                except NoLLMAvailableError as exc:
                    self._raise_initial_fault(await self._reported(exc))
                race.deadline = time.monotonic() + self._window
                if self._current_call.answer_deadline is not None:
                    race.deadline = min(race.deadline, self._current_call.answer_deadline)
                continue
            if race.exposed is None:
                race.exposed = race.select()
                if race.exposed is not None:
                    name(self._receipt, race.exposed)
            exposed = race.exposed
            if exposed is not None and exposed.sent < len(exposed.deltas):
                delta = exposed.deltas[exposed.sent]
                exposed.sent += 1
                yield delta
                continue
            await self._refill_free()
            await self._pause_explicit()

    async def _pause_explicit(self) -> None:
        race = self._current_race
        with suppress(TimeoutError):
            await asyncio.wait_for(race.wake.wait(), race.timeout())

    async def _refill_free(self) -> None:
        call = self._current_call
        race = self._current_race
        if call.expired or self._buffered_lane() is not None:
            return
        wave = self._wave
        delivered = sum(
            lane.outcome.receipt.call_id in self._delivered
            for lane in race.lanes
            if lane.wave == wave
        )
        pending = sum(
            (not lane.finished or lane.outcome.completed_at is not None)
            and lane.outcome.receipt.call_id not in self._delivered
            for lane in race.lanes
            if lane.wave == wave
        )
        target = max(self._wave_width.get(wave, call.width) - delivered, 0)
        needed = target - pending
        configs = await self._backend.untried(
            call,
            frozenset(),
            needed,
            frozenset(self._eligible_names),
            still_needed=lambda: self._buffered_lane() is None,
        )
        self._open(configs, hold_first=False)

    def _complete_initial(self, winner: StreamLane) -> None:
        self._authoritative = winner
        self._initial_complete = True
        self._current_race.done = True
        self._backend.publish(self._receipt, winner)
        if winner.outcome.receipt.call_id is not None:
            self._delivered.add(winner.outcome.receipt.call_id)

    def _buffered_lane(self) -> StreamLane | None:
        candidates = [
            lane
            for lane in self._current_race.lanes
            if lane.outcome.completed_at is not None
            and lane.outcome.receipt.call_id not in self._delivered
        ]
        return min(
            candidates,
            key=lambda lane: (cast(float, lane.outcome.completed_at), lane.opening_order),
            default=None,
        )

    async def _next_answer(self) -> AsyncResult | None:  # noqa: C901
        call = self._current_call
        race = self._current_race
        while True:
            race.wake.clear()
            self._absorb_finished()
            lane = self._buffered_lane()
            if lane is not None:
                if lane.task is not None and not lane.task.done():
                    await asyncio.shield(lane.task)
                call_id = lane.outcome.receipt.call_id
                if call_id is not None:
                    self._delivered.add(call_id)
                return replacement(self._backend, call, lane)
            await self._refill_free()
            lane = self._buffered_lane()
            if lane is not None:
                continue
            if race.live():
                await self._pause()
                continue
            unexpected = self._unexpected_fault()
            if call.expired or (
                self._budget is not None
                and self._budget.deadline is not None
                and time.monotonic() >= self._budget.deadline
            ):
                if call.last_client_error is not None:
                    raise call.last_client_error
                if unexpected is not None:
                    raise unexpected
                self._terminal = True
                return None
            try:
                configs = await self._acquire(initial=False)
            except NoLLMAvailableError:
                if call.last_client_error is not None:
                    raise call.last_client_error from None
                if unexpected is not None:
                    raise unexpected from None
                self._terminal = True
                return None
            self._open(configs, hold_first=False, new_wave=True)

    def _unexpected_fault(self) -> Exception | None:
        crashed = [
            (lane.opening_order, lane.outcome.crashed)
            for lane in self._current_race.lanes
            if lane.outcome.crashed is not None
        ]
        return min(crashed, default=(0, None))[1]

    async def _raise_expired(self) -> NoReturn:
        """The budget ran out before any delta: the timeout a routed call raises there,
        naming when the pool is back where nothing can serve this caller now."""
        self._raise_initial_fault(
            NoLLMAvailableError(
                "the wait budget ran out before any LLM produced a delta",
                reason="timeout",
                retry_at=await self._retry_at(),
            ),
        )

    def _raise_initial_fault(self, fallback: NoLLMAvailableError) -> NoReturn:
        exposed = self._current_race.exposed
        if exposed is not None and isinstance(exposed.failure, Exception):
            raise exposed.failure
        unexpected = self._unexpected_fault()
        if unexpected is not None:
            raise unexpected
        call = self._current_call
        if call.last_client_error is not None:
            raise call.last_client_error
        raise fallback

    async def _close(self) -> None:
        active = self._active
        if active is not None and not active.done():
            active.cancel()
            await asyncio.gather(active, return_exceptions=True)
        initial = self._initial
        if initial is not None:
            with suppress(RuntimeError):
                await initial.aclose()
        race = self._race
        if race is None:
            return
        completed = race.winner()
        settled = completed or self._authoritative or race.exposed
        stops: list[Awaitable[None]] = []
        for lane in race.lanes:
            if lane.retired:
                continue
            lane.retired = True
            if not lane.finished:
                if not self._initial_complete and completed is None and lane is settled:
                    lane.outcome.stopped = True
                else:
                    lane.outcome.superseded = True
                self._backend.cancel(lane)
            stops.append(self._backend.stop(lane))
        if stops:
            await asyncio.gather(*stops, return_exceptions=True)
        await self._backend.settled(self._current_call)
        if not self._initial_complete and settled is not None:
            self._backend.publish(self._receipt, settled)
        self._race = None
        self._authoritative = None
        self._initial = None

streaming

Provider-facing streaming attempts and their per-lane state.

BudgetExhaustedError

Bases: Exception

The caller's budget ran out after a streamed answer had started.

Source code in src/llmbroker/broker/streaming.py
62
63
class BudgetExhaustedError(Exception):
    """The caller's budget ran out after a streamed answer had started."""
StreamBackend dataclass

The narrow router services needed by streamed calls.

Source code in src/llmbroker/broker/streaming.py
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
@dataclass(frozen=True, slots=True)
class StreamBackend:
    """The narrow router services needed by streamed calls."""

    configs: Callable[[], Iterable[str]]
    payable: Callable[[KeyRing], Awaitable[frozenset[str]]]
    acquire: Callable[..., Awaitable[list[LLMConfig]]]
    retry_at: Callable[..., datetime | None]
    produce: Callable[[RouteCall, LLMConfig, Outcome], AsyncGenerator[Any, None]]
    untried: Callable[..., Awaitable[list[LLMConfig]]]
    publish: Callable[[CallReceipt | None, Any], None]
    cancel: Callable[[Any], None]
    stop: Callable[[Any], Awaitable[None]]
    settled: Callable[[RouteCall], Awaitable[None]]
    new_attempt: Callable[..., Awaitable[Attempt | None]]
    backoff: Callable[[str], float]
    attempt_timeout: Callable[[float | None], tuple[float, bool]]
    spent_budget: Callable[[Attempt, Outcome], Awaitable[None]]
    settle_superseded: Callable[[Attempt, Usage | None], Awaitable[None]]
    finish_ok: Callable[[Attempt, Usage | None], Awaitable[None]]
    release: Callable[[LLMConfig], Awaitable[None]]
    record: Callable[..., Awaitable[None]]
    dispose: Callable[..., Awaitable[None]]
    http: Callable[[], httpx.AsyncClient]
    store: StoreProtocol
    observe_quality: ObserveQuality | None
StreamBudget dataclass

The mutable deadline shared by every attempt owned by one stream.

Source code in src/llmbroker/broker/streaming.py
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
@dataclass(slots=True)
class StreamBudget:
    """The mutable deadline shared by every attempt owned by one stream."""

    deadline: float | None
    changed: asyncio.Event = field(default_factory=asyncio.Event)
    paused_at: float | None = None

    def pause(self) -> None:
        if self.deadline is not None and self.paused_at is None:
            self.paused_at = time.monotonic()
            self.changed.set()

    def resume(self) -> float:
        if self.paused_at is None:
            return 0.0
        elapsed = time.monotonic() - self.paused_at
        self.paused_at = None
        self.deadline = cast(float, self.deadline) + elapsed
        self.changed.set()
        return elapsed
StreamLane dataclass

One provider attempt retained by a streamed call.

Source code in src/llmbroker/broker/streaming.py
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
@dataclass(slots=True)
class StreamLane:
    """One provider attempt retained by a streamed call."""

    config: LLMConfig
    outcome: Outcome
    produced: AsyncGenerator[str, None]
    task: asyncio.Task[None] | None = None
    deltas: list[str] = field(default_factory=list)
    sent: int = 0
    first_delta_at: float | None = None
    failure: BaseException | None = None
    finished: bool = False
    absorbed: bool = False
    retired: bool = False
    opening_order: int = 0
    hold_first: bool = False
    consumer_driven: bool = False
    release_first: asyncio.Event = field(default_factory=asyncio.Event)
    pulled: asyncio.Event = field(default_factory=asyncio.Event)
    wave: int = 0
StreamProgress dataclass

Identity and usage observed during one streaming attempt.

Source code in src/llmbroker/broker/streaming.py
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
@dataclass(slots=True)
class StreamProgress:
    """Identity and usage observed during one streaming attempt."""

    receipt: CallReceipt
    llm_name: str
    call_id: str
    started: bool = False
    usage: Usage | None = None

    def opened(self) -> None:
        self.started = True
        self.receipt.llm_name = self.llm_name
        self.receipt.call_id = self.call_id

    def settle(self) -> None:
        self.receipt.llm_name = self.llm_name
        self.receipt.call_id = self.call_id
        self.receipt.usage = self.usage
        self.receipt.settled = True
StreamRace dataclass

Retained lanes and the provisional lane selected for initial deltas.

Source code in src/llmbroker/broker/streaming.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
@dataclass(slots=True)
class StreamRace:
    """Retained lanes and the provisional lane selected for initial deltas."""

    lanes: list[StreamLane]
    width: int
    deadline: float = 0.0
    wake: asyncio.Event = field(default_factory=asyncio.Event)
    exposed: StreamLane | None = None
    done: bool = False

    @property
    def preferred(self) -> StreamLane:
        return self.lanes[0]

    def live(self) -> list[StreamLane]:
        return [lane for lane in self.lanes if not lane.finished]

    def winner(self) -> StreamLane | None:
        """Return the lane whose provider completed a valid answer first. Rank already
        decided who is shown, so a tie the clock cannot split keeps that lane instead of
        withdrawing text the caller has read."""
        best: StreamLane | None = None
        best_key = (0.0, False, 0)
        for lane in self.lanes:
            at = lane.outcome.completed_at
            key = (at, lane is not self.exposed, lane.opening_order) if at is not None else None
            if key is not None and (best is None or key < best_key):
                best, best_key = lane, key
        return best

    def select(self) -> StreamLane | None:
        """Choose whose deltas become the provisional initial stream."""
        preferred = self.preferred
        if preferred.first_delta_at is not None and preferred.first_delta_at < self.deadline:
            return preferred
        if not preferred.finished and time.monotonic() < self.deadline:
            return None
        best: StreamLane | None = None
        best_at = 0.0
        for lane in self.lanes:
            at = lane.first_delta_at
            if at is not None and (best is None or at < best_at):
                best, best_at = lane, at
        return best

    def timeout(self) -> float | None:
        if self.exposed is not None or self.preferred.finished:
            return None
        remaining = self.deadline - time.monotonic()
        return remaining if remaining > 0 else None
select()

Choose whose deltas become the provisional initial stream.

Source code in src/llmbroker/broker/streaming.py
120
121
122
123
124
125
126
127
128
129
130
131
132
133
def select(self) -> StreamLane | None:
    """Choose whose deltas become the provisional initial stream."""
    preferred = self.preferred
    if preferred.first_delta_at is not None and preferred.first_delta_at < self.deadline:
        return preferred
    if not preferred.finished and time.monotonic() < self.deadline:
        return None
    best: StreamLane | None = None
    best_at = 0.0
    for lane in self.lanes:
        at = lane.first_delta_at
        if at is not None and (best is None or at < best_at):
            best, best_at = lane, at
    return best
winner()

Return the lane whose provider completed a valid answer first. Rank already decided who is shown, so a tie the clock cannot split keeps that lane instead of withdrawing text the caller has read.

Source code in src/llmbroker/broker/streaming.py
107
108
109
110
111
112
113
114
115
116
117
118
def winner(self) -> StreamLane | None:
    """Return the lane whose provider completed a valid answer first. Rank already
    decided who is shown, so a tie the clock cannot split keeps that lane instead of
    withdrawing text the caller has read."""
    best: StreamLane | None = None
    best_key = (0.0, False, 0)
    for lane in self.lanes:
        at = lane.outcome.completed_at
        key = (at, lane is not self.exposed, lane.opening_order) if at is not None else None
        if key is not None and (best is None or key < best_key):
            best, best_key = lane, key
    return best
budgeted_await(awaited, provider_deadline, budget) async

Await provider I/O against a deadline an ordinary reader may pause.

Source code in src/llmbroker/broker/streaming.py
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
async def budgeted_await(
    awaited: Awaitable[Produced],
    provider_deadline: float,
    budget: StreamBudget | None,
) -> Produced:
    """Await provider I/O against a deadline an ordinary reader may pause."""
    task = asyncio.ensure_future(awaited)
    try:
        while True:
            if budget is None:
                async with asyncio.timeout_at(provider_deadline):
                    return await task
            budget.changed.clear()
            deadline = provider_deadline
            if budget.deadline is not None and budget.paused_at is None:
                deadline = min(deadline, budget.deadline)
            changed = asyncio.create_task(budget.changed.wait())
            try:
                done, _ = await asyncio.wait(
                    (task, changed),
                    timeout=max(deadline - time.monotonic(), 0.0),
                    return_when=asyncio.FIRST_COMPLETED,
                )
            finally:
                changed.cancel()
                await asyncio.gather(changed, return_exceptions=True)
            if task in done:
                return task.result()
            if changed in done:
                continue
            raise TimeoutError
    except BaseException:
        if not task.done():
            task.cancel()
            await asyncio.gather(task, return_exceptions=True)
        raise
drain_lane(lane, wake) async

Read one lane fully so consumer pacing cannot decide its completion time.

Source code in src/llmbroker/broker/streaming.py
269
270
271
272
273
274
275
276
277
278
279
280
281
async def drain_lane(lane: StreamLane, wake: asyncio.Event) -> None:
    """Read one lane fully so consumer pacing cannot decide its completion time."""
    try:
        async for delta in lane.produced:
            if lane.first_delta_at is None:
                lane.first_delta_at = time.monotonic()
            lane.deltas.append(delta)
            wake.set()
    except Exception as exc:  # noqa: BLE001
        lane.failure = exc
    finally:
        lane.finished = True
        wake.set()
name(receipt, lane)

Name the provisional lane without marking the handle settled.

Source code in src/llmbroker/broker/streaming.py
284
285
286
287
def name(receipt: CallReceipt, lane: StreamLane) -> None:
    """Name the provisional lane without marking the handle settled."""
    receipt.llm_name = lane.outcome.receipt.llm_name
    receipt.call_id = lane.outcome.receipt.call_id
replacement(backend, call, winner)

Build the complete result belonging to a retained lane.

Source code in src/llmbroker/broker/streaming.py
290
291
292
293
294
295
296
297
298
299
300
301
302
def replacement(backend: StreamBackend, call: RouteCall, winner: StreamLane) -> AsyncResult:
    """Build the complete result belonging to a retained lane."""
    return AsyncResult(
        text="".join(winner.deltas),
        tool_calls=None,
        usage=winner.outcome.receipt.usage,
        call_id=cast(str, winner.outcome.receipt.call_id),
        llm_name=winner.config.name,
        operation=call.operation,
        store=backend.store,
        scope=call.ring.scope,
        observe_quality=backend.observe_quality,
    )
stream_attempt(backend, config, outcome, first_delta_deadline, *, ring, messages, operation, trace_id, response_format=None, budget=None) async

Stream one provider and settle its slot, row, and outcome.

Source code in src/llmbroker/broker/streaming.py
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
async def stream_attempt(  # noqa: PLR0913
    backend: StreamBackend,
    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,
    budget: StreamBudget | None = None,
) -> AsyncGenerator[str, None]:
    """Stream one provider and settle its slot, row, and outcome."""
    attempt = await backend.new_attempt(
        config,
        ring,
        outcome,
        operation=operation,
        trace_id=trace_id,
    )
    if attempt is None:
        outcome.verdict = Failed(error=None)
        return
    backoff = backend.backoff(config.name)
    timeout, budget_bound = backend.attempt_timeout(first_delta_deadline)
    if budget_bound and timeout == 0.0:
        outcome.settling = True
        await backend.spent_budget(attempt, outcome)
        return

    request = build_chat_request(
        config.base_url,
        config.model,
        attempt.resolved_key,
        messages,
        stream=True,
        params=None if response_format is None else {"response_format": response_format},
    )
    progress = StreamProgress(outcome.receipt, config.name, attempt.call_id)
    try:
        async with aclosing(
            stream_deltas(
                backend.http(),
                request,
                model=config.name,
                timeout=chat.HTTP_TIMEOUT if budget is not None else timeout,
                progress=progress,
                budget=budget,
            ),
        ) as deltas:
            async for delta in deltas:
                yield delta
    except BudgetExhaustedError:
        outcome.settling = True
        outcome.verdict = BudgetExpired()
        await exhausted(backend, attempt, timeout)
    except FAILOVER_ERRORS as exc:
        outcome.settling = True
        await fail_stream(
            backend,
            attempt,
            exc,
            outcome,
            backoff=backoff,
            timeout=timeout,
            budget_bound=budget_bound,
            started=progress.started,
        )
    except GeneratorExit:
        await stream_stopped(backend, attempt, progress, outcome)
        raise
    except BaseException as exc:
        outcome.settling = True
        await stream_aborted(backend, attempt, progress, outcome, exc)
        raise
    else:
        outcome.settling = True
        await settle_stream(
            backend,
            attempt,
            progress,
            outcome,
            backoff=backoff,
            timeout=timeout,
        )
stream_deltas(client, request, *, model, timeout, progress, budget=None) async

Open one streaming request and yield its text deltas.

Source code in src/llmbroker/broker/streaming.py
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
async def stream_deltas(  # noqa: PLR0913
    client: httpx.AsyncClient,
    request: tuple[str, dict[str, str], dict],
    *,
    model: str,
    timeout: float,
    progress: StreamProgress,
    budget: StreamBudget | None = None,
) -> AsyncGenerator[str, None]:
    """Open one streaming request and yield its text deltas."""
    url, headers, body = request
    loop = asyncio.get_running_loop()
    provider_deadline = loop.time() + timeout
    try:
        async with AsyncExitStack() as opened:
            resp = await budgeted_await(
                opened.enter_async_context(client.stream("POST", url, headers=headers, json=body)),
                provider_deadline,
                budget,
            )
            if resp.status_code >= ERROR_FLOOR:
                await resp.aread()
                resp.raise_for_status()
            chunks = await opened.enter_async_context(aclosing(aiter_chat_chunks(resp, model)))
            while True:
                try:
                    chunk = await budgeted_await(anext(chunks), provider_deadline, budget)
                except StopAsyncIteration:
                    break
                delta, usage = parse_stream_chunk(chunk, model)
                progress.usage = usage or progress.usage
                if not delta:
                    continue
                if not progress.started:
                    progress.opened()
                held = loop.time()
                yield delta
                provider_deadline += loop.time() - held
    except TimeoutError:
        if progress.started:
            raise BudgetExhaustedError from None
        raise

verdict

Failure classifications shared by atomic and streaming routed attempts.

BudgetExpired dataclass

The caller's own wait budget ran out during an attempt.

Source code in src/llmbroker/broker/verdict.py
38
39
40
@dataclass(frozen=True)
class BudgetExpired:
    """The caller's own wait budget ran out during an attempt."""
Failed dataclass

One candidate is done for this request.

error is set only for a genuine client error, so the router can decide whether to surface it after every candidate is exhausted.

Source code in src/llmbroker/broker/verdict.py
27
28
29
30
31
32
33
34
35
@dataclass(frozen=True)
class Failed:
    """One candidate is done for this request.

    ``error`` is set only for a genuine client error, so the router can decide
    whether to surface it after every candidate is exhausted.
    """

    error: ProviderError | None
Verdict dataclass

How one failed attempt is disposed of and journaled.

Source code in src/llmbroker/broker/verdict.py
43
44
45
46
47
48
49
50
51
@dataclass(frozen=True)
class Verdict:
    """How one failed attempt is disposed of and journaled."""

    status: CallStatus
    detail: str | None
    http_status: int | None = None
    cool_base: float | None = None
    outcome: Failed | BudgetExpired | None = None
classify(exc, *, budget_bound)

Map a failed attempt onto cooldown, retry, or caller-budget expiry.

Source code in src/llmbroker/broker/verdict.py
73
74
75
76
77
78
79
80
81
82
83
84
85
86
def classify(exc: Exception, *, budget_bound: bool) -> Verdict:
    """Map a failed attempt onto cooldown, retry, or caller-budget expiry."""
    if isinstance(exc, httpx.HTTPStatusError):
        return classify_status(exc)
    if isinstance(exc, InvalidProviderResponseError):
        return Verdict(CallStatus.ERROR, exc.detail, cool_base=DEFAULT_RATE_LIMIT_SEC)
    if budget_bound and isinstance(exc, (httpx.TimeoutException, TimeoutError)):
        return Verdict(
            CallStatus.ERROR,
            f"wait budget exhausted: {type(exc).__name__}",
            cool_base=DEFAULT_RATE_LIMIT_SEC,
            outcome=BudgetExpired(),
        )
    return Verdict(CallStatus.ERROR, type(exc).__name__, cool_base=DEFAULT_RATE_LIMIT_SEC)

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
323
324
325
326
327
328
329
330
331
332
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 _no_chat_chunks(resp, model)

aiter_sse_chunks(response) async

Yield decoded JSON objects from an OpenAI-compatible SSE stream body; data: [DONE] ends it.

Source code in src/llmbroker/chat.py
302
303
304
305
306
307
308
309
310
async def aiter_sse_chunks(response: httpx.Response) -> AsyncIterator[dict]:
    """Yield decoded JSON objects from an OpenAI-compatible SSE stream body;
    ``data: [DONE]`` ends it."""
    async for raw in response.aiter_lines():
        payload = _sse_payload(raw)
        if payload == _SSE_DONE:
            return
        if payload is not None and (chunk := _sse_object(payload)) is not None:
            yield chunk

build_chat_request(base_url, model, api_key, messages, tools=None, *, stream=False, params=None, tool_params=None)

Return (url, headers, json_body) for an OpenAI-compatible chat completion.

tool_params (what the model needs with tools) land only when tools do, and the caller's own params land last; a key this builder owns is refused in either.

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
 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
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,
    tool_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.

    ``tool_params`` (what the model needs with tools) land only when ``tools`` do, and the
    caller's own ``params`` land last; a key this builder owns is refused in either.

    >>> 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}
    check_request_params(tool_params or {})
    check_request_params(params or {})
    if tools:
        body.update(tool_params or {})
    body.update(params or {})
    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
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
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,
        tool_params=config.tool_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
212
213
214
215
216
217
218
219
220
221
222
223
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
185
186
187
188
189
190
191
192
193
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],
    )

iter_chat_chunks(resp, model)

The synchronous twin of aiter_chat_chunks.

Source code in src/llmbroker/chat.py
335
336
337
338
339
340
341
342
def iter_chat_chunks(resp: httpx.Response, model: str) -> Iterator[dict]:
    """The synchronous twin of ``aiter_chat_chunks``."""
    completions = 0
    for chunk in iter_sse_chunks(resp):
        completions += "choices" in chunk
        yield chunk
    if not completions:
        raise _no_chat_chunks(resp, model)

iter_sse_chunks(response)

The synchronous twin of aiter_sse_chunks.

Source code in src/llmbroker/chat.py
313
314
315
316
317
318
319
320
def iter_sse_chunks(response: httpx.Response) -> Iterator[dict]:
    """The synchronous twin of ``aiter_sse_chunks``."""
    for raw in response.iter_lines():
        payload = _sse_payload(raw)
        if payload == _SSE_DONE:
            return
        if payload is not None and (chunk := _sse_object(payload)) is not None:
            yield chunk

message_from_response(data)

Extract the assistant message object from a chat-completion response body.

Source code in src/llmbroker/chat.py
110
111
112
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
359
360
361
362
363
364
365
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
257
258
259
260
261
262
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
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
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(), ask() and chat(). Pass an httpx.AsyncClient to share a connection pool, or let it open and close its own; tool_params ride every chat that carries tools, under params.

Source code in src/llmbroker/direct.py
 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
class AsyncDirectClient:
    """Async direct client for one named model — ``stream()``, ``ask()`` and ``chat()``.
    Pass an ``httpx.AsyncClient`` to share a connection pool, or let it open and close
    its own; ``tool_params`` ride every ``chat`` that carries tools, under ``params``."""

    def __init__(  # noqa: PLR0913 - where the model is, and how to reach it
        self,
        *,
        base_url: str,
        model: str,
        api_key: str,
        timeout: float = _DEFAULT_TIMEOUT,
        client: httpx.AsyncClient | None = None,
        tool_params: Mapping[str, object] | None = None,
    ) -> None:
        self._model = model
        self._timeout = timeout
        self._http = client
        self._owns_http = client is None
        self._request = partial(
            build_chat_request,
            base_url,
            model,
            api_key,
            tool_params=tool_params,
        )

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

    async def ask(
        self,
        prompt: str | None = None,
        *,
        messages: list[dict] | None = None,
        timeout: float | None = None,
        params: Mapping[str, object] | None = None,
    ) -> DirectResult:
        return await self.chat(_messages(prompt, messages), timeout=timeout, params=params)

    async def chat(
        self,
        messages: list[dict],
        *,
        tools: list[dict] | None = None,
        timeout: float | None = None,
        params: Mapping[str, object] | None = None,
    ) -> DirectResult:
        """One complete reply, which may be tool calls and no text."""
        url, headers, body = self._request(messages, tools, 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(
            _messages(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()
chat(messages, *, tools=None, timeout=None, params=None) async

One complete reply, which may be tool calls and no text.

Source code in src/llmbroker/direct.py
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
async def chat(
    self,
    messages: list[dict],
    *,
    tools: list[dict] | None = None,
    timeout: float | None = None,
    params: Mapping[str, object] | None = None,
) -> DirectResult:
    """One complete reply, which may be tool calls and no text."""
    url, headers, body = self._request(messages, tools, 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)

DirectClient

Synchronous direct client for one named model — stream(), ask() and chat(), none needing an event loop. client and tool_params mean what they do on AsyncDirectClient.

Source code in src/llmbroker/direct.py
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
class DirectClient:
    """Synchronous direct client for one named model — ``stream()``, ``ask()`` and
    ``chat()``, none needing an event loop. ``client`` and ``tool_params`` mean what
    they do on ``AsyncDirectClient``."""

    def __init__(  # noqa: PLR0913 - where the model is, and how to reach it
        self,
        *,
        base_url: str,
        model: str,
        api_key: str,
        timeout: float = _DEFAULT_TIMEOUT,
        client: httpx.Client | None = None,
        tool_params: Mapping[str, object] | None = None,
    ) -> None:
        self._model = model
        self._timeout = timeout
        self._http = client
        self._owns_http = client is None
        self._request = partial(
            build_chat_request,
            base_url,
            model,
            api_key,
            tool_params=tool_params,
        )

    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:
        return self.chat(_messages(prompt, messages), timeout=timeout, params=params)

    def chat(
        self,
        messages: list[dict],
        *,
        tools: list[dict] | None = None,
        timeout: float | None = None,
        params: Mapping[str, object] | None = None,
    ) -> DirectResult:
        """One complete reply, which may be tool calls and no text."""
        url, headers, body = self._request(messages, tools, 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 stream(
        self,
        prompt: str | None = None,
        *,
        messages: list[dict] | None = None,
        timeout: float | None = None,
        params: Mapping[str, object] | None = None,
    ) -> Iterator[str]:
        """Text deltas as they arrive. Closing the iterator early closes the response,
        and with it the connection the provider is writing to."""
        url, headers, body = self._request(
            _messages(prompt, messages),
            stream=True,
            params=params,
        )
        try:
            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 = resp.read().decode(errors="replace")[:DETAIL_SNIPPET]
                    raise provider_error(resp.status_code, detail, resp.headers)
                produced = False
                for chunk in iter_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

    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()
chat(messages, *, tools=None, timeout=None, params=None)

One complete reply, which may be tool calls and no text.

Source code in src/llmbroker/direct.py
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
def chat(
    self,
    messages: list[dict],
    *,
    tools: list[dict] | None = None,
    timeout: float | None = None,
    params: Mapping[str, object] | None = None,
) -> DirectResult:
    """One complete reply, which may be tool calls and no text."""
    url, headers, body = self._request(messages, tools, 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)
stream(prompt=None, *, messages=None, timeout=None, params=None)

Text deltas as they arrive. Closing the iterator early closes the response, and with it the connection the provider is writing to.

Source code in src/llmbroker/direct.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
256
257
258
259
260
261
262
263
def stream(
    self,
    prompt: str | None = None,
    *,
    messages: list[dict] | None = None,
    timeout: float | None = None,
    params: Mapping[str, object] | None = None,
) -> Iterator[str]:
    """Text deltas as they arrive. Closing the iterator early closes the response,
    and with it the connection the provider is writing to."""
    url, headers, body = self._request(
        _messages(prompt, messages),
        stream=True,
        params=params,
    )
    try:
        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 = resp.read().decode(errors="replace")[:DETAIL_SNIPPET]
                raise provider_error(resp.status_code, detail, resp.headers)
            produced = False
            for chunk in iter_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

DirectResult dataclass

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

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

    text: str
    usage: Usage | None = None
    tool_calls: list[dict] | 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
151
152
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
20
21
22
23
24
25
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
113
114
115
116
117
118
119
120
121
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
12
13
14
15
16
17
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
54
55
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
92
93
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
87
88
89
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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
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
79
80
81
82
83
84
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
102
103
104
105
106
107
108
109
110
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
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
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
45
46
47
48
49
50
51
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
124
125
126
127
128
129
130
131
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

StreamReplacementError

Bases: LLMRequestError

A raced stream's provisional deltas lost to a complete answer from another lane: discard every delta received and use replacement instead. Terminal for that iterator — see docs/ "Racing a stream".

Source code in src/llmbroker/exceptions.py
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
class StreamReplacementError(LLMRequestError):
    """A raced stream's provisional deltas lost to a complete answer from another lane:
    discard every delta received and use ``replacement`` instead. Terminal for that
    iterator — see ``docs/`` "Racing a stream"."""

    def __init__(
        self,
        message: str,
        *,
        replacement: "AsyncResult",
        streamed_llm_name: str,
    ) -> None:
        super().__init__(message)
        self.replacement = replacement
        self.streamed_llm_name = streamed_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
28
29
30
31
32
33
34
35
36
37
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
96
97
98
99
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
40
41
42
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
75
76
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
445
446
447
448
449
450
451
452
@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
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
@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
229
230
231
232
233
234
235
236
237
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, what it could not, and where to get the keys.

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
199
200
201
202
203
204
205
206
207
208
209
210
211
@dataclass(frozen=True, slots=True)
class DeclaredModels:
    """What ``direct=`` resolved to, what it could not, and where to get the keys.

    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: _EMPTY_MAP)
    unresolved: Mapping[str, str] = field(default_factory=lambda: _EMPTY_MAP)

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
57
58
59
60
61
62
63
64
65
@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
 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
@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
    # What the model needs on a request that carries tools, applied under the caller's
    # own parameters. Out of the hash: a mapping is not hashable, and identity is the name.
    tool_params: Mapping[str, object] = field(default_factory=dict, hash=False)

    def __post_init__(self) -> None:
        check_request_params(self.tool_params)
        object.__setattr__(self, "tool_params", dict(self.tool_params))

    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}
        >>> replace(curated, tool_params={"reasoning_effort": "none"}).to_metadata()
        {'tool_params': {'reasoning_effort': 'none'}}
        """
        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
        if self.tool_params:
            metadata["tool_params"] = dict(self.tool_params)
        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),
            tool_params=_tool_params_from_metadata(metadata.get("tool_params"), 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
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
@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),
        tool_params=_tool_params_from_metadata(metadata.get("tool_params"), 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() {'weight': 0.7} replace(curated, tool_params={"reasoning_effort": "none"}).to_metadata() {'tool_params': {'reasoning_effort': 'none'}}

Source code in src/llmbroker/models.py
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
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}
    >>> replace(curated, tool_params={"reasoning_effort": "none"}).to_metadata()
    {'tool_params': {'reasoning_effort': 'none'}}
    """
    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
    if self.tool_params:
        metadata["tool_params"] = dict(self.tool_params)
    return metadata

LLMMetrics dataclass

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

Source code in src/llmbroker/models.py
338
339
340
341
342
343
344
@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
362
363
364
365
366
367
368
369
370
371
372
373
@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
48
49
50
51
52
53
54
@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
347
348
349
350
351
352
353
354
355
356
357
358
359
@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
41
42
43
44
45
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
180
181
182
183
184
185
186
@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
189
190
191
192
193
194
195
196
@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
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
@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
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
@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, ...] = ()
    _direct_unresolved: Mapping[str, str] = field(default_factory=lambda: _EMPTY_MAP)

    @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 direct_unresolved(self) -> Mapping[str, str]:
        """Handles ``direct=`` declared and could not resolve, each with why. A typo in
        one alias is visible here without making a call, and fails nothing else."""
        return self._direct_unresolved

    @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.

direct_unresolved property

Handles direct= declared and could not resolve, each with why. A typo in one alias is visible here without making a call, and fails nothing else.

SyncReport dataclass

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

Source code in src/llmbroker/models.py
214
215
216
217
218
219
220
221
222
223
224
225
226
@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
240
241
242
243
244
245
246
247
@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
324
325
326
327
328
329
330
331
332
333
334
335
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
303
304
305
306
307
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_request_params(params)

Refuse a request key llmbroker builds itself: merged, it would silently break the call — a moved model, a flipped streaming switch.

check_request_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/models.py
26
27
28
29
30
31
32
33
34
35
36
37
38
def check_request_params(params: Mapping[str, object]) -> None:
    """Refuse a request key llmbroker builds itself: merged, it would silently break the
    call — a moved model, a flipped streaming switch.

    >>> check_request_params({"model": "other"})
    Traceback (most recent call last):
    ValueError: request parameter 'model' is built by llmbroker and cannot be passed
    """
    for key in params:
        if key in RESERVED_BODY_KEYS:
            raise ValueError(
                f"request parameter {key!r} is built by llmbroker and cannot be passed",
            )

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
310
311
312
313
314
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
317
318
319
320
321
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
274
275
276
277
278
279
280
281
282
283
284
285
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
288
289
290
291
292
293
294
295
296
297
298
299
300
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

Contracts: what a registry / secrets / store must do, and what the tool loop drives. Implement these to add one of your own; the shipped implementations live in llmbroker.standalone and the per-driver packages.

chat

Chat contract: what the tool loop drives — a routed caller and a direct client alike.

ChatProtocol

Bases: Protocol[ReplyT_co]

A chat taking tools; an async one is the same port over an awaitable reply.

Source code in src/llmbroker/protocols/chat.py
19
20
21
22
class ChatProtocol(Protocol[ReplyT_co]):
    """A ``chat`` taking tools; an async one is the same port over an awaitable reply."""

    def chat(self, messages: list[dict], *, tools: list[dict] | None = None) -> ReplyT_co: ...
ToolReply

Bases: Protocol

One round's reply: the prose, and the tool calls it asks for.

Source code in src/llmbroker/protocols/chat.py
 6
 7
 8
 9
10
11
12
13
class ToolReply(Protocol):
    """One round's reply: the prose, and the tool calls it asks for."""

    @property
    def text(self) -> str: ...

    @property
    def tool_calls(self) -> list[dict] | None: ...

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:  # noqa: ARG002
        return

    async def record_quality(
        self,
        call_id: str,  # noqa: ARG002
        score: float,  # noqa: ARG002
        *,
        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 / Stream: 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
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
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, self._async),
            daemon=True,
            name="llmbroker-loop",
        )
        self._thread.start()
        # Backstop for a Broker nobody closes. The callback never holds self, so it does
        # not pin the instance it is registered on.
        self._finalizer = weakref.finalize(self, _shutdown, self._loop, self._async)
        # At exit the executors are already shut, so a teardown could journal nothing.
        self._finalizer.atexit = False
        self._run = partial(_run_on, self._loop)

    @property
    def llms(self) -> "LLMs":
        # Made on each access: stored, it would hold self in a cycle only the cycle
        # collector could break, and the teardown would then run on whatever thread it hits.
        return LLMs(self, self._loop, self._async.llms)

    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, self._loop, self._async.for_scope(scope))

    def ensure_pool(self) -> None:
        """Provision now rather than on the first call that routes — the eager fail-fast."""
        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 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,
        stream_selection_window: float = 1.0,
    ) -> Stream:
        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,
            stream_selection_window=stream_selection_window,
        )

    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:
        """Close every owned stream and wait until each is settled and journaled."""
        if self._finalizer.detach() is None:
            return
        try:
            self._run(_teardown(self._async))
        finally:
            self._loop.call_soon_threadsafe(self._loop.stop)
            self._thread.join()

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

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

Close every owned stream and wait until each is settled and journaled.

Source code in src/llmbroker/sync.py
567
568
569
570
571
572
573
574
575
def close(self) -> None:
    """Close every owned stream and wait until each is settled and journaled."""
    if self._finalizer.detach() is None:
        return
    try:
        self._run(_teardown(self._async))
    finally:
        self._loop.call_soon_threadsafe(self._loop.stop)
        self._thread.join()
ensure_pool()

Provision now rather than on the first call that routes — the eager fail-fast.

Source code in src/llmbroker/sync.py
434
435
436
def ensure_pool(self) -> None:
    """Provision now rather than on the first call that routes — the eager fail-fast."""
    self._run(self._async.ensure_pool())
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
429
430
431
432
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, self._loop, self._async.for_scope(scope))

LLM

Synchronous analogue of AsyncLLM.

Source code in src/llmbroker/sync.py
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
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
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
class LLMs:
    """Synchronous analogue of AsyncLLMs — one caller over the shared pool."""

    def __init__(
        self,
        broker: "Broker",
        loop: asyncio.AbstractEventLoop,
        async_llms: AsyncLLMs,
    ) -> None:
        self._broker = broker  # a live caller keeps its broker, and so its loop, alive
        self._loop = loop
        self._run = partial(_run_on, loop)
        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 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,
        stream_selection_window: float = 1.0,
    ) -> Stream:
        """Return an owned stream that can supply another complete pool answer. The
        broker closes it; ``with`` or ``close()`` releases it earlier."""
        return Stream(
            self._broker,
            self._loop,
            self._async.stream(
                prompt,
                operation=operation,
                trace_id=trace_id,
                wait=wait,
                fastest_of=fastest_of,
                parallel_recovery=parallel_recovery,
                response_format=response_format,
                stream_selection_window=stream_selection_window,
            ),
        )

    def direct(self, alias: str | None = None, *, name: str | None = None) -> DirectClient:
        """Return a synchronous direct client for a declared model — 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,
            tool_params=cfg.tool_params,
        )

    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 for a declared model — same alias/name keyspaces and errors as the async counterpart.

Source code in src/llmbroker/sync.py
328
329
330
331
332
333
334
335
336
337
def direct(self, alias: str | None = None, *, name: str | None = None) -> DirectClient:
    """Return a synchronous direct client for a declared model — 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,
        tool_params=cfg.tool_params,
    )
stream(prompt, *, operation=None, trace_id=None, wait=None, fastest_of=None, parallel_recovery=True, response_format=None, stream_selection_window=1.0)

Return an owned stream that can supply another complete pool answer. The broker closes it; with or close() releases it earlier.

Source code in src/llmbroker/sync.py
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
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,
    stream_selection_window: float = 1.0,
) -> Stream:
    """Return an owned stream that can supply another complete pool answer. The
    broker closes it; ``with`` or ``close()`` releases it earlier."""
    return Stream(
        self._broker,
        self._loop,
        self._async.stream(
            prompt,
            operation=operation,
            trace_id=trace_id,
            wait=wait,
            fastest_of=fastest_of,
            parallel_recovery=parallel_recovery,
            response_format=response_format,
            stream_selection_window=stream_selection_window,
        ),
    )

Result

Synchronous analogue of AsyncResult.

Source code in src/llmbroker/sync.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
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))

Stream

Synchronous analogue of StreamHandle. Each pull runs on the broker's loop, so it raises what the async stream raises at that pull; closing it, or dropping it unclosed, closes the async stream and so cancels the provider request.

Source code in src/llmbroker/sync.py
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
class Stream:
    """Synchronous analogue of StreamHandle. Each pull runs on the broker's loop, so it
    raises what the async stream raises at that pull; closing it, or dropping it
    unclosed, closes the async stream and so cancels the provider request."""

    def __init__(
        self,
        broker: "Broker",
        loop: asyncio.AbstractEventLoop,
        handle: StreamHandle,
    ) -> None:
        self._broker = broker  # a live stream keeps its broker, and so its loop, alive
        self._run = partial(_run_on, loop)
        self._async = handle
        self._finalizer = weakref.finalize(self, _abandon, loop, handle)
        # At exit the loop thread dies with the process; a close scheduled then never runs.
        self._finalizer.atexit = False

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

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

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

    @property
    def usage(self) -> Usage | None:
        return self._async.usage

    def __iter__(self) -> "Stream":
        return self

    def __next__(self) -> str:
        if not self._finalizer.alive or self._async.closed:
            raise StopIteration
        try:
            delta = self._run(_pull(self._async))
        except CancelledError:
            # A close of this stream or of the broker, made from another thread, ends it.
            if self._async.closed:
                raise StopIteration from None
            raise
        if delta is None:
            raise StopIteration
        return delta

    def another(self) -> Result | None:
        answer = self._run(self._async.another())
        return None if answer is None else Result(self._run, answer)

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

    def close(self) -> None:
        """Close all provider work and wait for its settlement."""
        if self._finalizer.detach() is None or self._async.closed:
            return
        self._run(self._async.aclose())

    def __enter__(self) -> "Stream":
        if not self._finalizer.alive or self._async.closed:
            raise RuntimeError("the stream is closed")
        return self

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

Close all provider work and wait for its settlement.

Source code in src/llmbroker/sync.py
212
213
214
215
216
def close(self) -> None:
    """Close all provider work and wait for its settlement."""
    if self._finalizer.detach() is None or self._async.closed:
        return
    self._run(self._async.aclose())

tool_loop

The tool loop, async and blocking: drive a chat until it stops asking for tools, running each requested tool through the host's dispatch. It sits above a caller or a direct client, not beside the HTTP primitives it never touches.

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

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

Source code in src/llmbroker/tool_loop.py
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
async def arun_tool_loop(
    llms: ChatProtocol[Awaitable[ReplyT]],
    messages: list[dict],
    *,
    tools: list[dict] | None = None,
    dispatch: Mapping[str, Callable[..., object]] | None = None,
    max_steps: int = 8,
    **chat_kwargs,
) -> ReplyT:
    """Drive ``llms.chat`` until a tool-call-free reply; execute tools via dispatch.
    Returns that last round's result: earlier rounds are calls of their own, 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
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
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)

arun_tool_loop over a blocking chat, returning the final round's result. It never touches the async engine itself, so it is safe to call from any thread.

Source code in src/llmbroker/tool_loop.py
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
def run_tool_loop(
    llms: ChatProtocol[ReplyT],
    messages: list[dict],
    *,
    tools: list[dict] | None = None,
    dispatch: Mapping[str, Callable[..., object]] | None = None,
    max_steps: int = 8,
    **chat_kwargs,
) -> ReplyT:
    """``arun_tool_loop`` over a blocking ``chat``, returning the final round's result.
    It never touches the async engine itself, 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
68
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,
                raise_on_deleted_version=True,
            )
            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
53
54
55
56
57
58
59
60
61
62
63
64
65
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
68
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,
                raise_on_deleted_version=True,
            )
            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
53
54
55
56
57
58
59
60
61
62
63
64
65
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))