agentic-llm-router 0.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 krivonosoff161
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,277 @@
1
+ Metadata-Version: 2.4
2
+ Name: agentic-llm-router
3
+ Version: 0.2.0
4
+ Summary: Cost-aware multi-provider LLM router with role tiers (cheap/mid/chief/audit).
5
+ Author: krivonosoff161
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/krivonosoff161/llm-router
8
+ Project-URL: Repository, https://github.com/krivonosoff161/llm-router
9
+ Keywords: llm,openai,router,agents,cost,qwen,yandex
10
+ Requires-Python: >=3.9
11
+ Description-Content-Type: text/markdown
12
+ License-File: LICENSE
13
+ Requires-Dist: aiohttp>=3.9
14
+ Provides-Extra: dev
15
+ Requires-Dist: build>=1.2.2; extra == "dev"
16
+ Requires-Dist: pytest>=7; extra == "dev"
17
+ Requires-Dist: ruff>=0.6; extra == "dev"
18
+ Requires-Dist: setuptools>=77; extra == "dev"
19
+ Requires-Dist: wheel>=0.44; extra == "dev"
20
+ Dynamic: license-file
21
+
22
+ # llm-router
23
+
24
+ Ecosystem role and current integration status: [component roadmap](docs/component-roadmap.md).
25
+ The public cross-repository plan is owned by the
26
+ [Agentic Security Harness ecosystem roadmap](https://github.com/krivonosoff161/agentic-security-harness/blob/main/docs/ecosystem-roadmap.md).
27
+
28
+ [![Tests](https://github.com/krivonosoff161/llm-router/actions/workflows/tests.yml/badge.svg)](https://github.com/krivonosoff161/llm-router/actions/workflows/tests.yml)
29
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
30
+ [![Python 3.9+](https://img.shields.io/badge/python-3.9%2B-blue.svg)](https://www.python.org/)
31
+
32
+ **A tiny, dependency-light async LLM router with role tiers and per-call cost logging.**
33
+ One `call()` interface for the tested OpenAI-compatible request shape and a
34
+ separate Yandex AI Studio path. Provider compatibility depends on each endpoint's
35
+ current API contract and must be verified before use.
36
+
37
+ > The public library demonstrates a cheap-to-chief routing pattern with offline
38
+ > tests. It does not publish or verify a production deployment claim. No SDKs or
39
+ > models are hardcoded in the routing logic.
40
+
41
+ The repository now publishes a source-owned, offline invocation-receipt contract and is
42
+ therefore `contract_only` in the ecosystem. Its source tree builds the unique distribution
43
+ candidate `agentic-llm-router==0.2.0`, imported as `llm_router`. It is not yet published or
44
+ automatically activated by Harness.
45
+
46
+ > Supply-chain boundary: the generic PyPI name `llm-router` belongs to another project.
47
+ > Do not install or declare that coordinate for this repository. The only planned public
48
+ > distribution coordinate is `agentic-llm-router`.
49
+
50
+ ---
51
+
52
+ ## Why
53
+
54
+ 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:
55
+
56
+ - **Role tiers** — `cheap` / `mid` / `chief` / `audit`, each mapped to a model via env. Route volume to `cheap`, escalate only candidates to `chief`.
57
+ - **Provider flexibility** — a custom `OPENAI_BASE_URL` can target endpoints that
58
+ implement the tested request/response contract; Yandex AI Studio has a separate
59
+ path. Provider identity, terms, availability, and exact compatibility are external
60
+ gates.
61
+ - **Per-call cost** — every call returns token counts and cost in **USD + a configurable local currency** (set `LLM_FX` / `LLM_CCY`). Aggregate the dicts to a budget log.
62
+ - **Budget helpers** — aggregate usage records, check a daily cap, and estimate savings
63
+ versus sending the same tokens to the `chief` model.
64
+ - **Resilience** — retries on `429` / `5xx` with exponential backoff.
65
+ - **Canonical receipt contract** — strict, digest-bound attempt, usage, pricing, and FX
66
+ evidence without credentials, endpoints, prompts, output text, response bodies, or
67
+ exception messages.
68
+
69
+ ---
70
+
71
+ ## Features
72
+
73
+ - Single `async call(role, system, user) -> (text | None, usage)` interface.
74
+ - Four configurable role tiers, models set per provider via env.
75
+ - OpenAI-compatible **and** Yandex AI Studio providers.
76
+ - `json_mode=True` → adds `response_format={"type":"json_object"}` (OpenAI-compatible).
77
+ - Cost estimation from an override-able price table (`LLM_PRICE_<MODEL>_IN/OUT_USD_PER_1M`).
78
+ - Budget helpers for logs you own: `summarize_usage`, `budget_status`, and
79
+ `build_savings_report`.
80
+ - Zero secrets cached at import — all config read live from env.
81
+ - ~150 LOC, one runtime dependency (`aiohttp`).
82
+
83
+ ---
84
+
85
+ ## Install
86
+
87
+ ```bash
88
+ git clone https://github.com/krivonosoff161/llm-router
89
+ cd llm-router
90
+ python -m build
91
+ python -m pip install dist/agentic_llm_router-0.2.0-py3-none-any.whl
92
+ ```
93
+
94
+ For editable development use `python -m pip install -e .[dev]`. Requires **Python 3.9+**.
95
+ CI builds and installs the exact wheel on Linux and Windows. Harness `main` declares a
96
+ source-only `router` extra using the unique `agentic-llm-router` distribution name, but
97
+ this package is not on PyPI and published Harness `v1.3.0` metadata does not contain that
98
+ extra. Public `pip install agentic-security-harness[router]` support is therefore
99
+ unavailable; package publication and newer Harness package metadata remain separate release gates.
100
+
101
+ ---
102
+
103
+ ## Quickstart
104
+
105
+ ```python
106
+ import asyncio
107
+ from llm_router import call
108
+
109
+ async def main():
110
+ text, usage = await call("cheap", "You are concise.", "Name 3 primary colors.")
111
+ print(text)
112
+ print(usage) # {provider, model, role, input_tokens, output_tokens,
113
+ # total_tokens, cost_usd, cost_local, currency}
114
+
115
+ asyncio.run(main())
116
+ ```
117
+
118
+ Set at least a provider + key first (see **Configuration**). For OpenAI:
119
+
120
+ ```bash
121
+ export OPENAI_API_KEY=sk-...
122
+ ```
123
+
124
+ ---
125
+
126
+ ## Providers
127
+
128
+ | Provider | Set | Auth |
129
+ |---|---|---|
130
+ | **OpenAI** | `LLM_PROVIDER=openai` (default), `OPENAI_API_KEY` | `Bearer` |
131
+ | **Alibaba Qwen** | `OPENAI_BASE_URL=<dashscope compatible-mode/v1>` + `OPENAI_API_KEY` | `Bearer` |
132
+ | **OpenRouter / Together / Ollama / vLLM** | `OPENAI_BASE_URL=<their /v1>` + `OPENAI_API_KEY` | `Bearer` |
133
+ | **Yandex AI Studio** | `LLM_PROVIDER=yandex`, `YANDEX_API_KEY`, `YANDEX_FOLDER_ID` | `Api-Key` |
134
+
135
+ > The base URL must NOT include `/chat/completions` — the router appends it.
136
+ > **Yandex** requires `YANDEX_FOLDER_ID` (or an explicit `YANDEX_<ROLE>_MODEL`); otherwise `model_for` raises a clear configuration error (fail-fast) instead of sending an empty model.
137
+
138
+ ---
139
+
140
+ ## Roles
141
+
142
+ ```python
143
+ from llm_router import call, model_for
144
+
145
+ model_for("cheap") # -> e.g. "gpt-4o-mini" (or your LLM_CHEAP_MODEL)
146
+ model_for("chief") # -> e.g. "gpt-4o"
147
+
148
+ # pattern: cheap for volume, chief only when it matters
149
+ facts, u1 = await call("cheap", EXTRACT_PROMPT, raw_text)
150
+ if looks_important(facts):
151
+ verdict, u2 = await call("chief", DECIDE_PROMPT, facts, json_mode=True)
152
+ ```
153
+
154
+ ---
155
+
156
+ ## Cost logging
157
+
158
+ ```python
159
+ text, usage = await call("cheap", sys, user)
160
+ # usage["cost_usd"] -> e.g. 0.0001
161
+ # usage["cost_local"] -> cost_usd * LLM_FX
162
+ # usage["currency"] -> LLM_CCY (e.g. "RUB")
163
+ ```
164
+
165
+ 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:
166
+
167
+ ```bash
168
+ export LLM_PRICE_GPT_4O_MINI_IN_USD_PER_1M=0.15
169
+ export LLM_PRICE_GPT_4O_MINI_OUT_USD_PER_1M=0.60
170
+ ```
171
+
172
+ Summarize a batch of usage records:
173
+
174
+ ```python
175
+ from llm_router import summarize_usage, budget_status, build_savings_report
176
+
177
+ usages = [u1, u2] # dicts returned by call()
178
+ print(summarize_usage(usages).as_dict())
179
+ print(budget_status(usages, limit_usd=1.00).as_dict())
180
+ print(build_savings_report(usages, counterfactual_role="chief").as_dict())
181
+ ```
182
+
183
+ `LLM_BUDGET_USD_DAY` can be used as a default budget cap for `budget_status(...)`.
184
+ The router stays stateless; you decide where the JSONL budget log lives.
185
+
186
+ ## Canonical invocation receipts
187
+
188
+ [`router-invocation-receipt-v1.0`](docs/invocation-receipt.md) is a separate offline
189
+ interchange surface for already-observed sanitized values. It uses canonical UTF-8 JSON,
190
+ domain-separated content identities, contiguous attempt accounting, strict token totals,
191
+ and integer nano-unit cost arithmetic. Pricing and FX inputs bind caller-supplied source
192
+ artifact digests; those digests are evidence references, not authenticity proofs.
193
+
194
+ The receipt builder never calls a provider and the existing `call()` return value is
195
+ unchanged. A receipt contains digests of request, response, output, model, and producer
196
+ identity—not their raw bytes. It always declares `invoice_authoritative=false` and
197
+ `operational_authority=none`.
198
+
199
+ Deterministic hashes are content-minimizing, not anonymizing: they remain linkable and can
200
+ be guessed when the source space is small. A receipt is not automatically safe to publish.
201
+
202
+ ```python
203
+ from llm_router import InvocationAttemptV1
204
+
205
+ attempt = InvocationAttemptV1(
206
+ attempt_index=1,
207
+ outcome="network_error",
208
+ http_status=None,
209
+ reason_code="provider.network_error",
210
+ response_payload_sha256=None,
211
+ )
212
+ # Supply only already-observed sanitized values; see docs/invocation-receipt.md.
213
+ ```
214
+
215
+ ---
216
+
217
+ ## Configuration (env)
218
+
219
+ | Variable | Default | Purpose |
220
+ |---|---|---|
221
+ | `LLM_PROVIDER` | `openai` | `openai` (compatible) or `yandex` |
222
+ | `OPENAI_API_KEY` | — | key for the OpenAI-compatible endpoint |
223
+ | `OPENAI_BASE_URL` | `https://api.openai.com/v1` | point at Alibaba/OpenRouter/Ollama/... |
224
+ | `LLM_CHEAP_MODEL` / `LLM_MID_MODEL` / `LLM_CHIEF_MODEL` / `LLM_AUDIT_MODEL` | gpt-4o-mini / gpt-4o-mini / gpt-4o / gpt-4o | role → model |
225
+ | `YANDEX_API_KEY`, `YANDEX_FOLDER_ID` | — | Yandex AI Studio |
226
+ | `YANDEX_<ROLE>_MODEL` | wraps `gpt://<folder>/<name>/latest` | override a Yandex role model URI |
227
+ | `LLM_FX` | `1.0` | USD → local currency multiplier |
228
+ | `LLM_CCY` | `USD` | local currency label |
229
+ | `LLM_DEFAULT_TIMEOUT` | `60` | per-call timeout (s) |
230
+ | `LLM_MAX_RETRIES` | `2` | retries on 429/5xx |
231
+ | `LLM_PRICE_<MODEL>_IN/OUT_USD_PER_1M` | from table | override price per model |
232
+ | `LLM_BUDGET_USD_DAY` | unset | optional cap used by budget helpers |
233
+
234
+ See [.env.example](.env.example).
235
+
236
+ ---
237
+
238
+ ## Examples & tests
239
+
240
+ ```bash
241
+ python examples/basic.py # one call
242
+ python examples/role_tiers.py # cheap vs chief + cost
243
+ python -m pytest -q # offline unit tests (no network)
244
+ ```
245
+
246
+ What each example shows and what it does *not* prove: [examples/README.md](examples/README.md).
247
+
248
+ ---
249
+
250
+ ## Docs
251
+
252
+ - [Component roadmap](docs/component-roadmap.md) — source-owned ecosystem role,
253
+ platform evidence, historical projections, and integration gates.
254
+ - [Project map](docs/project-map.md) — modules, what exists today vs not included, reviewer checklist.
255
+ - [Use cases](docs/use-cases.md) — who this is for, practical workflows, limitations.
256
+ - [Operating model](docs/operating-model.md) — role budgets, usage records, escalation gates, and residual risk.
257
+ - [Invocation receipt V1](docs/invocation-receipt.md) — canonical codec, attempt state
258
+ machine, fixed-point arithmetic, privacy boundary, and non-claims.
259
+ - [Harness ecosystem roadmap](https://github.com/krivonosoff161/agentic-security-harness/blob/main/docs/ecosystem-roadmap.md)
260
+ — the canonical public ordering for cross-repository integration work.
261
+
262
+ ---
263
+
264
+ ## Limitations / non-goals
265
+
266
+ - Chat completions only (no streaming, embeddings, tools/function-calling, vision — kept intentionally small).
267
+ - One system + one user message per call (no multi-turn history helper).
268
+ - The price table is illustrative; confirm real prices with your provider.
269
+ - Not a full framework — it's a focused routing + cost-logging utility you drop into your own agent loop.
270
+ - Not the portfolio flagship, policy authority, or security boundary. Larger
271
+ systems own their own validation, authorization, storage, and safety rules.
272
+
273
+ ---
274
+
275
+ ## License
276
+
277
+ MIT — see [LICENSE](LICENSE).
@@ -0,0 +1,256 @@
1
+ # llm-router
2
+
3
+ Ecosystem role and current integration status: [component roadmap](docs/component-roadmap.md).
4
+ The public cross-repository plan is owned by the
5
+ [Agentic Security Harness ecosystem roadmap](https://github.com/krivonosoff161/agentic-security-harness/blob/main/docs/ecosystem-roadmap.md).
6
+
7
+ [![Tests](https://github.com/krivonosoff161/llm-router/actions/workflows/tests.yml/badge.svg)](https://github.com/krivonosoff161/llm-router/actions/workflows/tests.yml)
8
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
9
+ [![Python 3.9+](https://img.shields.io/badge/python-3.9%2B-blue.svg)](https://www.python.org/)
10
+
11
+ **A tiny, dependency-light async LLM router with role tiers and per-call cost logging.**
12
+ One `call()` interface for the tested OpenAI-compatible request shape and a
13
+ separate Yandex AI Studio path. Provider compatibility depends on each endpoint's
14
+ current API contract and must be verified before use.
15
+
16
+ > The public library demonstrates a cheap-to-chief routing pattern with offline
17
+ > tests. It does not publish or verify a production deployment claim. No SDKs or
18
+ > models are hardcoded in the routing logic.
19
+
20
+ The repository now publishes a source-owned, offline invocation-receipt contract and is
21
+ therefore `contract_only` in the ecosystem. Its source tree builds the unique distribution
22
+ candidate `agentic-llm-router==0.2.0`, imported as `llm_router`. It is not yet published or
23
+ automatically activated by Harness.
24
+
25
+ > Supply-chain boundary: the generic PyPI name `llm-router` belongs to another project.
26
+ > Do not install or declare that coordinate for this repository. The only planned public
27
+ > distribution coordinate is `agentic-llm-router`.
28
+
29
+ ---
30
+
31
+ ## Why
32
+
33
+ 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:
34
+
35
+ - **Role tiers** — `cheap` / `mid` / `chief` / `audit`, each mapped to a model via env. Route volume to `cheap`, escalate only candidates to `chief`.
36
+ - **Provider flexibility** — a custom `OPENAI_BASE_URL` can target endpoints that
37
+ implement the tested request/response contract; Yandex AI Studio has a separate
38
+ path. Provider identity, terms, availability, and exact compatibility are external
39
+ gates.
40
+ - **Per-call cost** — every call returns token counts and cost in **USD + a configurable local currency** (set `LLM_FX` / `LLM_CCY`). Aggregate the dicts to a budget log.
41
+ - **Budget helpers** — aggregate usage records, check a daily cap, and estimate savings
42
+ versus sending the same tokens to the `chief` model.
43
+ - **Resilience** — retries on `429` / `5xx` with exponential backoff.
44
+ - **Canonical receipt contract** — strict, digest-bound attempt, usage, pricing, and FX
45
+ evidence without credentials, endpoints, prompts, output text, response bodies, or
46
+ exception messages.
47
+
48
+ ---
49
+
50
+ ## Features
51
+
52
+ - Single `async call(role, system, user) -> (text | None, usage)` interface.
53
+ - Four configurable role tiers, models set per provider via env.
54
+ - OpenAI-compatible **and** Yandex AI Studio providers.
55
+ - `json_mode=True` → adds `response_format={"type":"json_object"}` (OpenAI-compatible).
56
+ - Cost estimation from an override-able price table (`LLM_PRICE_<MODEL>_IN/OUT_USD_PER_1M`).
57
+ - Budget helpers for logs you own: `summarize_usage`, `budget_status`, and
58
+ `build_savings_report`.
59
+ - Zero secrets cached at import — all config read live from env.
60
+ - ~150 LOC, one runtime dependency (`aiohttp`).
61
+
62
+ ---
63
+
64
+ ## Install
65
+
66
+ ```bash
67
+ git clone https://github.com/krivonosoff161/llm-router
68
+ cd llm-router
69
+ python -m build
70
+ python -m pip install dist/agentic_llm_router-0.2.0-py3-none-any.whl
71
+ ```
72
+
73
+ For editable development use `python -m pip install -e .[dev]`. Requires **Python 3.9+**.
74
+ CI builds and installs the exact wheel on Linux and Windows. Harness `main` declares a
75
+ source-only `router` extra using the unique `agentic-llm-router` distribution name, but
76
+ this package is not on PyPI and published Harness `v1.3.0` metadata does not contain that
77
+ extra. Public `pip install agentic-security-harness[router]` support is therefore
78
+ unavailable; package publication and newer Harness package metadata remain separate release gates.
79
+
80
+ ---
81
+
82
+ ## Quickstart
83
+
84
+ ```python
85
+ import asyncio
86
+ from llm_router import call
87
+
88
+ async def main():
89
+ text, usage = await call("cheap", "You are concise.", "Name 3 primary colors.")
90
+ print(text)
91
+ print(usage) # {provider, model, role, input_tokens, output_tokens,
92
+ # total_tokens, cost_usd, cost_local, currency}
93
+
94
+ asyncio.run(main())
95
+ ```
96
+
97
+ Set at least a provider + key first (see **Configuration**). For OpenAI:
98
+
99
+ ```bash
100
+ export OPENAI_API_KEY=sk-...
101
+ ```
102
+
103
+ ---
104
+
105
+ ## Providers
106
+
107
+ | Provider | Set | Auth |
108
+ |---|---|---|
109
+ | **OpenAI** | `LLM_PROVIDER=openai` (default), `OPENAI_API_KEY` | `Bearer` |
110
+ | **Alibaba Qwen** | `OPENAI_BASE_URL=<dashscope compatible-mode/v1>` + `OPENAI_API_KEY` | `Bearer` |
111
+ | **OpenRouter / Together / Ollama / vLLM** | `OPENAI_BASE_URL=<their /v1>` + `OPENAI_API_KEY` | `Bearer` |
112
+ | **Yandex AI Studio** | `LLM_PROVIDER=yandex`, `YANDEX_API_KEY`, `YANDEX_FOLDER_ID` | `Api-Key` |
113
+
114
+ > The base URL must NOT include `/chat/completions` — the router appends it.
115
+ > **Yandex** requires `YANDEX_FOLDER_ID` (or an explicit `YANDEX_<ROLE>_MODEL`); otherwise `model_for` raises a clear configuration error (fail-fast) instead of sending an empty model.
116
+
117
+ ---
118
+
119
+ ## Roles
120
+
121
+ ```python
122
+ from llm_router import call, model_for
123
+
124
+ model_for("cheap") # -> e.g. "gpt-4o-mini" (or your LLM_CHEAP_MODEL)
125
+ model_for("chief") # -> e.g. "gpt-4o"
126
+
127
+ # pattern: cheap for volume, chief only when it matters
128
+ facts, u1 = await call("cheap", EXTRACT_PROMPT, raw_text)
129
+ if looks_important(facts):
130
+ verdict, u2 = await call("chief", DECIDE_PROMPT, facts, json_mode=True)
131
+ ```
132
+
133
+ ---
134
+
135
+ ## Cost logging
136
+
137
+ ```python
138
+ text, usage = await call("cheap", sys, user)
139
+ # usage["cost_usd"] -> e.g. 0.0001
140
+ # usage["cost_local"] -> cost_usd * LLM_FX
141
+ # usage["currency"] -> LLM_CCY (e.g. "RUB")
142
+ ```
143
+
144
+ 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:
145
+
146
+ ```bash
147
+ export LLM_PRICE_GPT_4O_MINI_IN_USD_PER_1M=0.15
148
+ export LLM_PRICE_GPT_4O_MINI_OUT_USD_PER_1M=0.60
149
+ ```
150
+
151
+ Summarize a batch of usage records:
152
+
153
+ ```python
154
+ from llm_router import summarize_usage, budget_status, build_savings_report
155
+
156
+ usages = [u1, u2] # dicts returned by call()
157
+ print(summarize_usage(usages).as_dict())
158
+ print(budget_status(usages, limit_usd=1.00).as_dict())
159
+ print(build_savings_report(usages, counterfactual_role="chief").as_dict())
160
+ ```
161
+
162
+ `LLM_BUDGET_USD_DAY` can be used as a default budget cap for `budget_status(...)`.
163
+ The router stays stateless; you decide where the JSONL budget log lives.
164
+
165
+ ## Canonical invocation receipts
166
+
167
+ [`router-invocation-receipt-v1.0`](docs/invocation-receipt.md) is a separate offline
168
+ interchange surface for already-observed sanitized values. It uses canonical UTF-8 JSON,
169
+ domain-separated content identities, contiguous attempt accounting, strict token totals,
170
+ and integer nano-unit cost arithmetic. Pricing and FX inputs bind caller-supplied source
171
+ artifact digests; those digests are evidence references, not authenticity proofs.
172
+
173
+ The receipt builder never calls a provider and the existing `call()` return value is
174
+ unchanged. A receipt contains digests of request, response, output, model, and producer
175
+ identity—not their raw bytes. It always declares `invoice_authoritative=false` and
176
+ `operational_authority=none`.
177
+
178
+ Deterministic hashes are content-minimizing, not anonymizing: they remain linkable and can
179
+ be guessed when the source space is small. A receipt is not automatically safe to publish.
180
+
181
+ ```python
182
+ from llm_router import InvocationAttemptV1
183
+
184
+ attempt = InvocationAttemptV1(
185
+ attempt_index=1,
186
+ outcome="network_error",
187
+ http_status=None,
188
+ reason_code="provider.network_error",
189
+ response_payload_sha256=None,
190
+ )
191
+ # Supply only already-observed sanitized values; see docs/invocation-receipt.md.
192
+ ```
193
+
194
+ ---
195
+
196
+ ## Configuration (env)
197
+
198
+ | Variable | Default | Purpose |
199
+ |---|---|---|
200
+ | `LLM_PROVIDER` | `openai` | `openai` (compatible) or `yandex` |
201
+ | `OPENAI_API_KEY` | — | key for the OpenAI-compatible endpoint |
202
+ | `OPENAI_BASE_URL` | `https://api.openai.com/v1` | point at Alibaba/OpenRouter/Ollama/... |
203
+ | `LLM_CHEAP_MODEL` / `LLM_MID_MODEL` / `LLM_CHIEF_MODEL` / `LLM_AUDIT_MODEL` | gpt-4o-mini / gpt-4o-mini / gpt-4o / gpt-4o | role → model |
204
+ | `YANDEX_API_KEY`, `YANDEX_FOLDER_ID` | — | Yandex AI Studio |
205
+ | `YANDEX_<ROLE>_MODEL` | wraps `gpt://<folder>/<name>/latest` | override a Yandex role model URI |
206
+ | `LLM_FX` | `1.0` | USD → local currency multiplier |
207
+ | `LLM_CCY` | `USD` | local currency label |
208
+ | `LLM_DEFAULT_TIMEOUT` | `60` | per-call timeout (s) |
209
+ | `LLM_MAX_RETRIES` | `2` | retries on 429/5xx |
210
+ | `LLM_PRICE_<MODEL>_IN/OUT_USD_PER_1M` | from table | override price per model |
211
+ | `LLM_BUDGET_USD_DAY` | unset | optional cap used by budget helpers |
212
+
213
+ See [.env.example](.env.example).
214
+
215
+ ---
216
+
217
+ ## Examples & tests
218
+
219
+ ```bash
220
+ python examples/basic.py # one call
221
+ python examples/role_tiers.py # cheap vs chief + cost
222
+ python -m pytest -q # offline unit tests (no network)
223
+ ```
224
+
225
+ What each example shows and what it does *not* prove: [examples/README.md](examples/README.md).
226
+
227
+ ---
228
+
229
+ ## Docs
230
+
231
+ - [Component roadmap](docs/component-roadmap.md) — source-owned ecosystem role,
232
+ platform evidence, historical projections, and integration gates.
233
+ - [Project map](docs/project-map.md) — modules, what exists today vs not included, reviewer checklist.
234
+ - [Use cases](docs/use-cases.md) — who this is for, practical workflows, limitations.
235
+ - [Operating model](docs/operating-model.md) — role budgets, usage records, escalation gates, and residual risk.
236
+ - [Invocation receipt V1](docs/invocation-receipt.md) — canonical codec, attempt state
237
+ machine, fixed-point arithmetic, privacy boundary, and non-claims.
238
+ - [Harness ecosystem roadmap](https://github.com/krivonosoff161/agentic-security-harness/blob/main/docs/ecosystem-roadmap.md)
239
+ — the canonical public ordering for cross-repository integration work.
240
+
241
+ ---
242
+
243
+ ## Limitations / non-goals
244
+
245
+ - Chat completions only (no streaming, embeddings, tools/function-calling, vision — kept intentionally small).
246
+ - One system + one user message per call (no multi-turn history helper).
247
+ - The price table is illustrative; confirm real prices with your provider.
248
+ - Not a full framework — it's a focused routing + cost-logging utility you drop into your own agent loop.
249
+ - Not the portfolio flagship, policy authority, or security boundary. Larger
250
+ systems own their own validation, authorization, storage, and safety rules.
251
+
252
+ ---
253
+
254
+ ## License
255
+
256
+ MIT — see [LICENSE](LICENSE).
@@ -0,0 +1,30 @@
1
+ [build-system]
2
+ requires = ["setuptools>=77", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "agentic-llm-router"
7
+ version = "0.2.0"
8
+ description = "Cost-aware multi-provider LLM router with role tiers (cheap/mid/chief/audit)."
9
+ readme = "README.md"
10
+ requires-python = ">=3.9"
11
+ license = "MIT"
12
+ authors = [{ name = "krivonosoff161" }]
13
+ keywords = ["llm", "openai", "router", "agents", "cost", "qwen", "yandex"]
14
+ dependencies = ["aiohttp>=3.9"]
15
+
16
+ [project.optional-dependencies]
17
+ dev = ["build>=1.2.2", "pytest>=7", "ruff>=0.6", "setuptools>=77", "wheel>=0.44"]
18
+
19
+ [project.urls]
20
+ Homepage = "https://github.com/krivonosoff161/llm-router"
21
+ Repository = "https://github.com/krivonosoff161/llm-router"
22
+
23
+ [tool.setuptools.packages.find]
24
+ where = ["src"]
25
+
26
+ [tool.pytest.ini_options]
27
+ testpaths = ["tests"]
28
+
29
+ [tool.ruff.lint]
30
+ select = ["E4", "E7", "E9", "F"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+