Skip to content

Extending

Three interfaces carry the extensibility story. Each has a test suite you inherit.

A routing strategy

Ship it in your own package under the tokenbiryani.strategies entry point:

[project.entry-points."tokenbiryani.strategies"]
my_strategy = "my_package:MY_STRATEGY"

Publish either a StrategySpec, which reweights the built-in terms:

from tokenbiryani.core.router import StrategySpec

MY_STRATEGY = StrategySpec(affinity=1.0, headroom=2.0, cost=4.0)

or an object with a terms() method, which computes its own. Those terms show up per candidate in the request inspector, so name them for a reader:

class PreferWarmest:
    def terms(self, account, ctx, weights, index, count):
        return {"cache_rate": account.cache_hit_rate() or 0.0}

A plugin that fails to import is logged and skipped — a broken third-party package must not stop the gateway starting. tokenbiryani strategies lists what loaded.

A provider adapter

Ship it under the tokenbiryani.providers entry point, and the account type becomes available without touching core:

[project.entry-points."tokenbiryani.providers"]
my_platform = "my_package:MyUpstream"

The value is anything callable with an AccountConfig that returns an Upstream. Built-in type names cannot be shadowed, and a plugin that fails to import is logged and skipped.

Implement providers.base.Upstream: url, auth_headers, and optionally override send / open_stream. If the platform does not stream SSE natively, override iter_sse and translate there, so the rest of the gateway keeps reading ordinary streaming.

providers/bedrock.py is the awkward case worth reading: SigV4 per request, and a binary event-stream decoded back to SSE.

If your platform reports no anthropic-ratelimit-* headers, set observable_limits: false on those accounts. Otherwise their windows never populate, an unknown window reads as full, and the account beats every account that honestly reports a partly-used budget — permanently. Unobservable accounts are scored at assumed_headroom and excluded from the capacity horizon, which would otherwise be promising capacity nobody can see.

If it reports a fraction spent rather than a count remaining, emit the unified headers (anthropic-ratelimit-unified-{window}-{status,utilization,reset}) and the mirror will route on 1 - utilization instead of the assumed figure. A measurement beats a guess; the horizon and leases still need counts, and still leave it out.

A state store

Implement store.base.StateStore and add it to build_store. The memory, SQLite and Redis backends are held to identical behaviour by one parametrised suite in tests/test_store.py, so a new backend inherits every test by adding one fixture parameter.

Watch the range semantics: Redis ZREMRANGEBYSCORE is inclusive at the upper bound, which made it evict one entry more than the others until the parity suite caught it.

Testing your extension

The repository carries a scriptable fake Anthropic API in tests/support/. It is test scaffolding, not part of the installed package, so it comes from a clone — the suite puts tests/ on the path, and anything outside it sets PYTHONPATH=tests:

from support.mock_upstream import MockAnthropic, rate_limit, ok

mock = MockAnthropic()
mock.add("acct-01", "key-a", cache_aware=True)
mock.script("acct-01", rate_limit(retry_after=30), ok())
mock.latency = 0.05     # force real concurrency overlap

It also runs as a real server for end-to-end work:

PYTHONPATH=tests python -m support.server --port 9911 --accounts key-a,key-b