Observed since 20 Oct, 22:17 UTC
No superseded metadata states collected. Unchanged observations do not create new revisions.
Cost-aware multi-provider LLM router with role tiers (cheap/mid/chief/audit).
Ecosystem role and current integration status: component roadmap. The public cross-repository plan is owned by the Agentic Security Harness ecosystem roadmap.
A tiny, dependency-light async LLM router with role tiers and per-call cost logging.
One call() interface for the tested OpenAI-compatible request shape and a
separate Yandex AI Studio path. Provider compatibility depends on each endpoint's
current API contract and must be verified before use.
The public library demonstrates a cheap-to-chief routing pattern with offline tests. It does not publish or verify a production deployment claim. No SDKs or models are hardcoded in the routing logic.
The repository now publishes a source-owned, offline invocation-receipt contract and is
therefore contract_only in the ecosystem. Its source tree builds the unique distribution
agentic-llm-router==0.2.1, imported as llm_router. Exact publication is established by
PyPI release history, not source
metadata alone. It is not automatically activated or invoked by Harness.
Supply-chain boundary: the generic PyPI name
llm-routerbelongs to another project. Do not install or declare that coordinate for this repository. The only planned public distribution coordinate isagentic-llm-router.
In agentic systems most LLM calls are cheap bulk work (extract, classify, filter) and a few are high-stakes (the final decision). Paying flagship prices for everything is wasteful; juggling provider SDKs is annoying. llm-router gives you:
cheap / mid / chief / audit, each mapped to a model via env. Route volume to cheap, escalate only candidates to chief.OPENAI_BASE_URL can target endpoints that
implement the tested request/response contract; Yandex AI Studio has a separate
path. Provider identity, terms, availability, and exact compatibility are external
gates.LLM_FX / LLM_CCY). Aggregate the dicts to a budget log.chief model.429 / 5xx with exponential backoff.async call(role, system, user) -> (text | None, usage) interface.json_mode=True → adds response_format={"type":"json_object"} (OpenAI-compatible).LLM_PRICE_<MODEL>_IN/OUT_USD_PER_1M).summarize_usage, budget_status, and
build_savings_report.llm_router, including from llm_router import call, performs no
provider I/O and starts no child process. The aiohttp network dependency is
loaded only when the caller explicitly invokes call() with provider
configuration present.aiohttp).python -m pip install agentic-llm-router==0.2.1
For editable development use python -m pip install -e .[dev]. Requires Python 3.9+.
CI builds and audits the exact installed wheel on Linux and Windows. Historical
Harness v1.4.0 and v1.5.0 use Router 0.2.0 for their passive router extra;
they do not include the passive-import repair. A new Harness release must update that
pin after Router 0.2.1 is published. Installation does not configure credentials,
choose a provider, or make a provider call.
Version 0.2.1 packages the passive-import repair from PR #12: ordinary package import
does not load aiohttp or start a child process. Source and installed-wheel tests install
process/network audit denial before import. Provider calls and production compatibility
are not exercised or certified by those tests. Published 0.2.0 bytes are unchanged.
import asyncio
from llm_router import call
async def main():
text, usage = await call("cheap", "You are concise.", "Name 3 primary colors.")
print(text)
print(usage) # {provider, model, role, input_tokens, output_tokens,
# total_tokens, cost_usd, cost_local, currency}
asyncio.run(main())
Set at least a provider + key first (see Configuration). For OpenAI:
export OPENAI_API_KEY=sk-...
| Provider | Set | Auth |
|---|---|---|
| OpenAI | LLM_PROVIDER=openai (default), OPENAI_API_KEY | Bearer |
| Alibaba Qwen | OPENAI_BASE_URL=<dashscope compatible-mode/v1> + OPENAI_API_KEY | Bearer |
| OpenRouter / Together / Ollama / vLLM | OPENAI_BASE_URL=<their /v1> + OPENAI_API_KEY | Bearer |
| Yandex AI Studio | LLM_PROVIDER=yandex, YANDEX_API_KEY, YANDEX_FOLDER_ID | Api-Key |
The base URL must NOT include
/chat/completions— the router appends it. Yandex requiresYANDEX_FOLDER_ID(or an explicitYANDEX_<ROLE>_MODEL); otherwisemodel_forraises a clear configuration error (fail-fast) instead of sending an empty model.
from llm_router import call, model_for
model_for("cheap") # -> e.g. "gpt-4o-mini" (or your LLM_CHEAP_MODEL)
model_for("chief") # -> e.g. "gpt-4o"
# pattern: cheap for volume, chief only when it matters
facts, u1 = await call("cheap", EXTRACT_PROMPT, raw_text)
if looks_important(facts):
verdict, u2 = await call("chief", DECIDE_PROMPT, facts, json_mode=True)
text, usage = await call("cheap", sys, user)
# usage["cost_usd"] -> e.g. 0.0001
# usage["cost_local"] -> cost_usd * LLM_FX
# usage["currency"] -> LLM_CCY (e.g. "RUB")
Append each usage to a JSONL file and you have a per-call budget log. Prices come from a small built-in table and are illustrative — override per model:
export LLM_PRICE_GPT_4O_MINI_IN_USD_PER_1M=0.15
export LLM_PRICE_GPT_4O_MINI_OUT_USD_PER_1M=0.60
Summarize a batch of usage records:
from llm_router import summarize_usage, budget_status, build_savings_report
usages = [u1, u2] # dicts returned by call()
print(summarize_usage(usages).as_dict())
print(budget_status(usages, limit_usd=1.00).as_dict())
print(build_savings_report(usages, counterfactual_role="chief").as_dict())
LLM_BUDGET_USD_DAY can be used as a default budget cap for budget_status(...).
The router stays stateless; you decide where the JSONL budget log lives.
router-invocation-receipt-v1.0 is a separate offline
interchange surface for already-observed sanitized values. It uses canonical UTF-8 JSON,
domain-separated content identities, contiguous attempt accounting, strict token totals,
and integer nano-unit cost arithmetic. Pricing and FX inputs bind caller-supplied source
artifact digests; those digests are evidence references, not authenticity proofs.
The receipt builder never calls a provider and the existing call() return value is
unchanged. A receipt contains digests of request, response, output, model, and producer
identity—not their raw bytes. It always declares invoice_authoritative=false and
operational_authority=none.
Deterministic hashes are content-minimizing, not anonymizing: they remain linkable and can be guessed when the source space is small. A receipt is not automatically safe to publish.
from llm_router import InvocationAttemptV1
attempt = InvocationAttemptV1(
attempt_index=1,
outcome="network_error",
http_status=None,
reason_code="provider.network_error",
response_payload_sha256=None,
)
# Supply only already-observed sanitized values; see docs/invocation-receipt.md.
| Variable | Default | Purpose |
|---|---|---|
LLM_PROVIDER | openai | openai (compatible) or yandex |
OPENAI_API_KEY | — | key for the OpenAI-compatible endpoint |
OPENAI_BASE_URL | https://api.openai.com/v1 | point at Alibaba/OpenRouter/Ollama/... |
LLM_CHEAP_MODEL / LLM_MID_MODEL / LLM_CHIEF_MODEL / LLM_AUDIT_MODEL | gpt-4o-mini / gpt-4o-mini / gpt-4o / gpt-4o | role → model |
YANDEX_API_KEY, YANDEX_FOLDER_ID | — | Yandex AI Studio |
YANDEX_<ROLE>_MODEL | wraps gpt://<folder>/<name>/latest | override a Yandex role model URI |
LLM_FX | 1.0 | USD → local currency multiplier |
LLM_CCY | USD | local currency label |
LLM_DEFAULT_TIMEOUT | 60 | per-call timeout (s) |
LLM_MAX_RETRIES | 2 | retries on 429/5xx |
LLM_PRICE_<MODEL>_IN/OUT_USD_PER_1M | from table | override price per model |
LLM_BUDGET_USD_DAY | unset | optional cap used by budget helpers |
See .env.example.
python examples/basic.py # one call
python examples/role_tiers.py # cheap vs chief + cost
python -m pytest -q # offline unit tests (no network)
What each example shows and what it does not prove: examples/README.md.
MIT — see LICENSE.
Download counts and popularity will appear here as the collector receives them.
| Version | Published | Direct dependencies | Open version |
|---|---|---|---|
| 0.2.1 | 20 Sept 2026 | 6 |
This describes our collected data; it is not a security assessment.
Observed since 20 Oct, 22:17 UTC
No superseded metadata states collected. Unchanged observations do not create new revisions.
package:5c17547712bdbfd375ba5a3a63cd3dbf2f3fa2b0765a8ec15ec7cd2272f253f4