tokenbill 0.1.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.
- tokenbill-0.1.0/.github/ISSUE_TEMPLATE/bug_report.yml +58 -0
- tokenbill-0.1.0/.github/ISSUE_TEMPLATE/config.yml +5 -0
- tokenbill-0.1.0/.github/ISSUE_TEMPLATE/feature_request.yml +40 -0
- tokenbill-0.1.0/.github/pull_request_template.md +25 -0
- tokenbill-0.1.0/.github/workflows/ci.yml +52 -0
- tokenbill-0.1.0/.github/workflows/release.yml +88 -0
- tokenbill-0.1.0/.gitignore +23 -0
- tokenbill-0.1.0/CHANGELOG.md +52 -0
- tokenbill-0.1.0/CODE_OF_CONDUCT.md +52 -0
- tokenbill-0.1.0/CONTRIBUTING.md +93 -0
- tokenbill-0.1.0/DESIGN.md +338 -0
- tokenbill-0.1.0/LICENSE +21 -0
- tokenbill-0.1.0/Makefile +17 -0
- tokenbill-0.1.0/PKG-INFO +286 -0
- tokenbill-0.1.0/README.md +258 -0
- tokenbill-0.1.0/SECURITY.md +70 -0
- tokenbill-0.1.0/docs/SPEC.md +471 -0
- tokenbill-0.1.0/pyproject.toml +67 -0
- tokenbill-0.1.0/tests/test_analyzer.py +200 -0
- tokenbill-0.1.0/tests/test_breakers.py +398 -0
- tokenbill-0.1.0/tests/test_cli.py +247 -0
- tokenbill-0.1.0/tests/test_demo_recovers_planted_waste.py +388 -0
- tokenbill-0.1.0/tests/test_demo_traces.py +205 -0
- tokenbill-0.1.0/tests/test_instrument.py +470 -0
- tokenbill-0.1.0/tests/test_performance.py +91 -0
- tokenbill-0.1.0/tests/test_pricing.py +137 -0
- tokenbill-0.1.0/tests/test_report.py +400 -0
- tokenbill-0.1.0/tests/test_simulator.py +321 -0
- tokenbill-0.1.0/tests/test_trace.py +439 -0
- tokenbill-0.1.0/tokenbill/__init__.py +12 -0
- tokenbill-0.1.0/tokenbill/__main__.py +6 -0
- tokenbill-0.1.0/tokenbill/analyzer.py +187 -0
- tokenbill-0.1.0/tokenbill/breakers.py +369 -0
- tokenbill-0.1.0/tokenbill/cli.py +255 -0
- tokenbill-0.1.0/tokenbill/common.py +46 -0
- tokenbill-0.1.0/tokenbill/demo_traces.py +558 -0
- tokenbill-0.1.0/tokenbill/instrument.py +310 -0
- tokenbill-0.1.0/tokenbill/pricing.py +139 -0
- tokenbill-0.1.0/tokenbill/py.typed +0 -0
- tokenbill-0.1.0/tokenbill/report.py +788 -0
- tokenbill-0.1.0/tokenbill/simulator.py +313 -0
- tokenbill-0.1.0/tokenbill/trace.py +455 -0
- tokenbill-0.1.0/uv.lock +183 -0
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
name: Bug report
|
|
2
|
+
description: Something is wrong, or a number in the report looks off
|
|
3
|
+
labels: ["bug"]
|
|
4
|
+
body:
|
|
5
|
+
- type: input
|
|
6
|
+
id: version
|
|
7
|
+
attributes:
|
|
8
|
+
label: Token Bill version
|
|
9
|
+
description: Output of `tokenbill --version`.
|
|
10
|
+
placeholder: "0.1.0"
|
|
11
|
+
validations:
|
|
12
|
+
required: true
|
|
13
|
+
- type: input
|
|
14
|
+
id: environment
|
|
15
|
+
attributes:
|
|
16
|
+
label: Python version and OS
|
|
17
|
+
placeholder: "Python 3.12.4, macOS 15"
|
|
18
|
+
validations:
|
|
19
|
+
required: true
|
|
20
|
+
- type: textarea
|
|
21
|
+
id: command
|
|
22
|
+
attributes:
|
|
23
|
+
label: Exact command
|
|
24
|
+
description: The full command line you ran, including flags and seed.
|
|
25
|
+
render: shell
|
|
26
|
+
placeholder: tokenbill analyze trace.jsonl -o report.html
|
|
27
|
+
validations:
|
|
28
|
+
required: true
|
|
29
|
+
- type: dropdown
|
|
30
|
+
id: trace-source
|
|
31
|
+
attributes:
|
|
32
|
+
label: Trace source
|
|
33
|
+
options:
|
|
34
|
+
- Bundled demo scenario (synthetic)
|
|
35
|
+
- Recorded with tokenbill.instrument
|
|
36
|
+
- Converted or hand-written JSONL
|
|
37
|
+
validations:
|
|
38
|
+
required: true
|
|
39
|
+
- type: textarea
|
|
40
|
+
id: trace-line
|
|
41
|
+
attributes:
|
|
42
|
+
label: One redacted trace line (if not the bundled demo)
|
|
43
|
+
description: >
|
|
44
|
+
One representative line of your trace JSONL. IMPORTANT — trace lines
|
|
45
|
+
contain your prompts, tool definitions, and conversation history:
|
|
46
|
+
redact all text content before pasting. Field names, shapes, and the
|
|
47
|
+
`usage` numbers are what matter for debugging; keep those intact.
|
|
48
|
+
render: json
|
|
49
|
+
- type: textarea
|
|
50
|
+
id: what-happened
|
|
51
|
+
attributes:
|
|
52
|
+
label: What happened vs. what you expected
|
|
53
|
+
description: >
|
|
54
|
+
Include the full traceback or the wrong output verbatim. If a dollar
|
|
55
|
+
or token number looks wrong, say which number and what you expected
|
|
56
|
+
it to be (and why).
|
|
57
|
+
validations:
|
|
58
|
+
required: true
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
name: Feature request
|
|
2
|
+
description: Propose a new breaker kind, scenario, adapter, or capability
|
|
3
|
+
labels: ["enhancement"]
|
|
4
|
+
body:
|
|
5
|
+
- type: textarea
|
|
6
|
+
id: problem
|
|
7
|
+
attributes:
|
|
8
|
+
label: What waste are you trying to find, or what workflow are you unblocking?
|
|
9
|
+
description: >
|
|
10
|
+
The underlying need, not the mechanism — e.g. "my agent alternates
|
|
11
|
+
between two models and I can't see which one burns the cache", not
|
|
12
|
+
just "add flag X".
|
|
13
|
+
validations:
|
|
14
|
+
required: true
|
|
15
|
+
- type: textarea
|
|
16
|
+
id: proposal
|
|
17
|
+
attributes:
|
|
18
|
+
label: Proposed solution
|
|
19
|
+
description: >
|
|
20
|
+
Sketch the interface (CLI flag, function signature, report section).
|
|
21
|
+
For a new breaker kind: give the detection rule precisely enough to
|
|
22
|
+
unit-test, plus the one-sentence fix it would attach. For a new
|
|
23
|
+
simulator scenario: state its assumptions the way DESIGN.md's
|
|
24
|
+
assumption table does. Say how a planted-waste demo scenario could
|
|
25
|
+
validate it end to end.
|
|
26
|
+
validations:
|
|
27
|
+
required: true
|
|
28
|
+
- type: textarea
|
|
29
|
+
id: alternatives
|
|
30
|
+
attributes:
|
|
31
|
+
label: Alternatives considered
|
|
32
|
+
- type: checkboxes
|
|
33
|
+
id: constraints
|
|
34
|
+
attributes:
|
|
35
|
+
label: Project constraints
|
|
36
|
+
options:
|
|
37
|
+
- label: This can be built with the Python standard library only (no new runtime dependencies; the recorder keeps duck-typing provider SDKs, never importing them).
|
|
38
|
+
required: true
|
|
39
|
+
- label: Every dollar figure this produces is either derived from real billed usage (exact) or clearly labeled approximate — never an approximation presented as billed.
|
|
40
|
+
required: true
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
## What & why
|
|
2
|
+
|
|
3
|
+
<!-- One or two sentences. Link the issue if there is one. -->
|
|
4
|
+
|
|
5
|
+
## Checklist
|
|
6
|
+
|
|
7
|
+
- [ ] `uv run --python 3.12 --extra dev pytest -q` passes locally (includes the
|
|
8
|
+
flagship planted-waste recovery test).
|
|
9
|
+
- [ ] `uv run --python 3.12 --extra dev ruff check .` is clean.
|
|
10
|
+
- [ ] No new runtime dependencies (stdlib only; `instrument.py` duck-types the
|
|
11
|
+
SDK client and never imports `anthropic`).
|
|
12
|
+
- [ ] Tests added/updated for the change; nothing here hits the network or
|
|
13
|
+
needs an API key — CI has no secrets and never will.
|
|
14
|
+
- [ ] Every new number surfaced to users is either derived from billed `usage`
|
|
15
|
+
(exact) or labeled approximate; no approximation is presented as billed.
|
|
16
|
+
- [ ] `CHANGELOG.md` updated under `[Unreleased]` for user-visible changes.
|
|
17
|
+
- [ ] If a metric definition, cache-rule constant, or pricing entry changed:
|
|
18
|
+
`DESIGN.md`, the report's methodology footnotes, and the flagship test's
|
|
19
|
+
derived expectations are updated together — and pricing changes cite the
|
|
20
|
+
published pricing doc in the source comment.
|
|
21
|
+
|
|
22
|
+
## Anything reviewers should focus on?
|
|
23
|
+
|
|
24
|
+
<!-- Subtle spots, e.g. canonical-rendering byte stability, approx-vs-billed
|
|
25
|
+
labeling, TTL edge cases in the simulator, seed scoping. -->
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# CI: lint + unit tests + the offline demo as pipeline proof.
|
|
2
|
+
#
|
|
3
|
+
# Deliberately NO live-API job: Token Bill needs no API keys at all. The
|
|
4
|
+
# `demo` job runs the entire real pipeline (synthetic planted-waste traces ->
|
|
5
|
+
# analyzer -> cache simulator -> breaker detection -> HTML report) offline,
|
|
6
|
+
# and the unit matrix includes tests/test_demo_recovers_planted_waste.py,
|
|
7
|
+
# which asserts the analyzer recovers the exact waste the demo traces plant.
|
|
8
|
+
# That is the correctness proof, and it needs zero keys and zero network.
|
|
9
|
+
name: CI
|
|
10
|
+
|
|
11
|
+
on:
|
|
12
|
+
push:
|
|
13
|
+
branches: [main]
|
|
14
|
+
pull_request:
|
|
15
|
+
|
|
16
|
+
jobs:
|
|
17
|
+
lint:
|
|
18
|
+
name: lint (ruff)
|
|
19
|
+
runs-on: ubuntu-latest
|
|
20
|
+
steps:
|
|
21
|
+
- uses: actions/checkout@v4
|
|
22
|
+
- uses: astral-sh/setup-uv@v5
|
|
23
|
+
with:
|
|
24
|
+
python-version: "3.12"
|
|
25
|
+
- name: ruff check
|
|
26
|
+
run: uv run --extra dev ruff check .
|
|
27
|
+
|
|
28
|
+
unit:
|
|
29
|
+
name: unit (py ${{ matrix.python }})
|
|
30
|
+
runs-on: ubuntu-latest
|
|
31
|
+
strategy:
|
|
32
|
+
fail-fast: false
|
|
33
|
+
matrix:
|
|
34
|
+
python: ["3.10", "3.13"]
|
|
35
|
+
steps:
|
|
36
|
+
- uses: actions/checkout@v4
|
|
37
|
+
- uses: astral-sh/setup-uv@v5
|
|
38
|
+
- name: pytest
|
|
39
|
+
run: uv run --python ${{ matrix.python }} --extra dev pytest -q
|
|
40
|
+
|
|
41
|
+
demo:
|
|
42
|
+
name: offline demo (no keys, no network)
|
|
43
|
+
runs-on: ubuntu-latest
|
|
44
|
+
steps:
|
|
45
|
+
- uses: actions/checkout@v4
|
|
46
|
+
- uses: astral-sh/setup-uv@v5
|
|
47
|
+
with:
|
|
48
|
+
python-version: "3.12"
|
|
49
|
+
- name: Run the full pipeline offline
|
|
50
|
+
run: uv run tokenbill demo -o /tmp/report.html
|
|
51
|
+
- name: Report must exist and be non-empty
|
|
52
|
+
run: test -s /tmp/report.html
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# Release pipeline, triggered by pushing a v* tag (e.g. v0.1.0).
|
|
2
|
+
#
|
|
3
|
+
# No API tokens anywhere: PyPI uses Trusted Publishing (OIDC).
|
|
4
|
+
# One-time setup by the repo owner:
|
|
5
|
+
# 1. pypi.org -> account -> Publishing -> add a (pending) trusted publisher:
|
|
6
|
+
# project "tokenbill", owner "sedai77",
|
|
7
|
+
# repository "tokenbill-llm-agent-cost-profiler",
|
|
8
|
+
# workflow "release.yml", environment "pypi".
|
|
9
|
+
# 2. GitHub repo Settings -> Environments -> create "pypi".
|
|
10
|
+
# Then: bump __version__ in tokenbill/__init__.py, move the CHANGELOG.md
|
|
11
|
+
# Unreleased entries into a dated section, and push the matching tag.
|
|
12
|
+
name: Release
|
|
13
|
+
|
|
14
|
+
on:
|
|
15
|
+
push:
|
|
16
|
+
tags: ["v*"]
|
|
17
|
+
|
|
18
|
+
jobs:
|
|
19
|
+
build:
|
|
20
|
+
name: build (wheel + sdist)
|
|
21
|
+
runs-on: ubuntu-latest
|
|
22
|
+
steps:
|
|
23
|
+
- uses: actions/checkout@v4
|
|
24
|
+
- uses: astral-sh/setup-uv@v5
|
|
25
|
+
with:
|
|
26
|
+
python-version: "3.12"
|
|
27
|
+
- name: Build
|
|
28
|
+
run: uv build
|
|
29
|
+
- name: Check metadata
|
|
30
|
+
run: uvx twine check dist/*
|
|
31
|
+
- name: Tag must match the package version
|
|
32
|
+
run: |
|
|
33
|
+
version="${GITHUB_REF_NAME#v}"
|
|
34
|
+
if [ ! -f "dist/tokenbill-${version}.tar.gz" ]; then
|
|
35
|
+
echo "tag ${GITHUB_REF_NAME} does not match the built version:" >&2
|
|
36
|
+
ls dist/ >&2
|
|
37
|
+
echo "bump __version__ in tokenbill/__init__.py before tagging" >&2
|
|
38
|
+
exit 1
|
|
39
|
+
fi
|
|
40
|
+
- uses: actions/upload-artifact@v4
|
|
41
|
+
with:
|
|
42
|
+
name: dist
|
|
43
|
+
path: dist/
|
|
44
|
+
|
|
45
|
+
pypi:
|
|
46
|
+
name: publish to PyPI (trusted publishing)
|
|
47
|
+
needs: build
|
|
48
|
+
runs-on: ubuntu-latest
|
|
49
|
+
environment: pypi
|
|
50
|
+
permissions:
|
|
51
|
+
id-token: write # OIDC is the whole credential — no token, no secret
|
|
52
|
+
steps:
|
|
53
|
+
- uses: actions/download-artifact@v4
|
|
54
|
+
with:
|
|
55
|
+
name: dist
|
|
56
|
+
path: dist/
|
|
57
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
58
|
+
|
|
59
|
+
github-release:
|
|
60
|
+
name: GitHub release
|
|
61
|
+
needs: [build, pypi]
|
|
62
|
+
runs-on: ubuntu-latest
|
|
63
|
+
permissions:
|
|
64
|
+
contents: write
|
|
65
|
+
steps:
|
|
66
|
+
- uses: actions/checkout@v4
|
|
67
|
+
- uses: actions/download-artifact@v4
|
|
68
|
+
with:
|
|
69
|
+
name: dist
|
|
70
|
+
path: dist/
|
|
71
|
+
- name: Extract this version's notes from CHANGELOG.md
|
|
72
|
+
run: |
|
|
73
|
+
awk -v heading="## [${GITHUB_REF_NAME#v}]" '
|
|
74
|
+
index($0, heading) == 1 { found = 1; next }
|
|
75
|
+
found && (/^## / || /^\[.*\]: /) { exit }
|
|
76
|
+
found { print }
|
|
77
|
+
' CHANGELOG.md > notes.md
|
|
78
|
+
if [ ! -s notes.md ]; then
|
|
79
|
+
echo "no CHANGELOG.md section for ${GITHUB_REF_NAME#v}" >&2
|
|
80
|
+
echo "See CHANGELOG.md." > notes.md
|
|
81
|
+
fi
|
|
82
|
+
- name: Create the release
|
|
83
|
+
env:
|
|
84
|
+
GH_TOKEN: ${{ github.token }}
|
|
85
|
+
run: |
|
|
86
|
+
gh release create "$GITHUB_REF_NAME" dist/* \
|
|
87
|
+
--title "Token Bill ${GITHUB_REF_NAME#v}" \
|
|
88
|
+
--notes-file notes.md
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*.egg-info/
|
|
5
|
+
dist/
|
|
6
|
+
build/
|
|
7
|
+
.venv/
|
|
8
|
+
venv/
|
|
9
|
+
.pytest_cache/
|
|
10
|
+
.ruff_cache/
|
|
11
|
+
.coverage
|
|
12
|
+
htmlcov/
|
|
13
|
+
|
|
14
|
+
# Token Bill artifacts (root-anchored — never swallow tests/data/)
|
|
15
|
+
/report.html
|
|
16
|
+
/trace*.jsonl
|
|
17
|
+
/data/
|
|
18
|
+
|
|
19
|
+
# Environment / OS / editor
|
|
20
|
+
.env
|
|
21
|
+
.DS_Store
|
|
22
|
+
.idea/
|
|
23
|
+
.vscode/
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to Token Bill are documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
## [0.1.0] - 2026-07-26
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- Trace schema `tokenbill/trace@1`: JSONL, one API call per line (full request
|
|
15
|
+
payload + real billed usage), with strict validation and precise
|
|
16
|
+
`TraceError` messages, plus canonical rendering (tools → system → messages)
|
|
17
|
+
as the byte-level substrate for prefix comparison.
|
|
18
|
+
- Per-call and per-run **token waterfalls** computed from real billed `usage`:
|
|
19
|
+
cache reads / cache writes / uncached input / output, in tokens and dollars.
|
|
20
|
+
- **Redundancy analysis**: the share of cumulative billed input spend that was
|
|
21
|
+
re-sending byte-identical prefix the model had already seen and that was
|
|
22
|
+
*not* served from cache — labeled approximate, since it splits exact billed
|
|
23
|
+
totals by character fractions.
|
|
24
|
+
- **Cache simulator** replaying each run under the provider's documented
|
|
25
|
+
prompt-caching rules in four priced scenarios: as-billed, no-cache,
|
|
26
|
+
optimal-cache, and fixed-cache (after repairing detected breakers), with a
|
|
27
|
+
validation hook comparing predicted vs. billed cache reads whenever the
|
|
28
|
+
trace shows real cache activity.
|
|
29
|
+
- **Cache-breaker detection** with classified causes — volatile system prompt,
|
|
30
|
+
tool churn, history rewrite, model switch, missing breakpoint — each with
|
|
31
|
+
the evidence span, a one-sentence concrete fix, and the estimated dollars
|
|
32
|
+
recovered.
|
|
33
|
+
- `tokenbill demo`: four deterministic synthetic coding-agent scenarios with
|
|
34
|
+
*planted* waste patterns — zero keys, zero network. The flagship test
|
|
35
|
+
(`tests/test_demo_recovers_planted_waste.py`) asserts the analyzer recovers
|
|
36
|
+
exactly the waste the traces plant, so the demo doubles as the correctness
|
|
37
|
+
certificate.
|
|
38
|
+
- `tokenbill analyze`: aligned terminal summary plus a single self-contained
|
|
39
|
+
HTML report (inline CSS, inline SVG waterfalls and scenario bars,
|
|
40
|
+
dark/light via `prefers-color-scheme`, zero external resources);
|
|
41
|
+
`--model-price MODEL=IN,OUT` for pricing unknown or self-hosted models.
|
|
42
|
+
- `tokenbill.instrument.Recorder`: records real traces by duck-type-wrapping
|
|
43
|
+
an Anthropic-SDK-shaped client (`messages.create` and `messages.stream`,
|
|
44
|
+
streaming via `get_final_message()`), appending one crash-safe JSONL line
|
|
45
|
+
per completed call — without ever importing the SDK.
|
|
46
|
+
- Versioned pricing and cache-rule tables with per-entry source comments
|
|
47
|
+
(verified 2026-07 against the published pricing doc).
|
|
48
|
+
- Zero runtime dependencies (pure standard library); Python 3.10+; typed
|
|
49
|
+
(PEP 561 `py.typed`).
|
|
50
|
+
|
|
51
|
+
[Unreleased]: https://github.com/sedai77/tokenbill-llm-agent-cost-profiler/compare/v0.1.0...HEAD
|
|
52
|
+
[0.1.0]: https://github.com/sedai77/tokenbill-llm-agent-cost-profiler/releases/tag/v0.1.0
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# Code of Conduct
|
|
2
|
+
|
|
3
|
+
This project adopts the spirit and structure of the
|
|
4
|
+
[Contributor Covenant, version 2.1](https://www.contributor-covenant.org/version/2/1/code_of_conduct/).
|
|
5
|
+
The short version follows; the linked document governs anything this summary
|
|
6
|
+
leaves ambiguous.
|
|
7
|
+
|
|
8
|
+
## Our pledge
|
|
9
|
+
|
|
10
|
+
We as members, contributors, and maintainers pledge to make participation in
|
|
11
|
+
Token Bill a harassment-free experience for everyone, regardless of age, body
|
|
12
|
+
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
|
13
|
+
identity and expression, level of experience, education, socio-economic status,
|
|
14
|
+
nationality, personal appearance, race, caste, religion, or sexual identity
|
|
15
|
+
and orientation.
|
|
16
|
+
|
|
17
|
+
## Our standards
|
|
18
|
+
|
|
19
|
+
Behavior that keeps this a good place to work:
|
|
20
|
+
|
|
21
|
+
- Being kind and welcoming to newcomers and disagreeing with ideas, not people.
|
|
22
|
+
- Giving and gracefully accepting constructive review feedback.
|
|
23
|
+
- Owning mistakes, apologizing to those affected, and learning from them.
|
|
24
|
+
- Focusing on what is best for the project and its users.
|
|
25
|
+
|
|
26
|
+
Behavior that is not acceptable:
|
|
27
|
+
|
|
28
|
+
- Sexualized language or imagery, and sexual attention of any kind.
|
|
29
|
+
- Trolling, insults, personal or political attacks, and sustained disruption.
|
|
30
|
+
- Public or private harassment.
|
|
31
|
+
- Publishing others' private information (physical or email address, etc.)
|
|
32
|
+
without their explicit permission.
|
|
33
|
+
- Any other conduct that would reasonably be considered inappropriate in a
|
|
34
|
+
professional setting.
|
|
35
|
+
|
|
36
|
+
## Scope and enforcement
|
|
37
|
+
|
|
38
|
+
This code applies in all project spaces (issues, pull requests, discussions)
|
|
39
|
+
and whenever someone is representing the project in public.
|
|
40
|
+
|
|
41
|
+
Report violations privately to the maintainers via
|
|
42
|
+
[GitHub's report-abuse flow](https://github.com/contact/report-abuse) or by
|
|
43
|
+
opening a [private security advisory](https://github.com/sedai77/tokenbill-llm-agent-cost-profiler/security/advisories/new)
|
|
44
|
+
marked "conduct". Maintainers will review every report, keep reporters'
|
|
45
|
+
identities confidential, and respond with actions proportionate to the
|
|
46
|
+
violation — from a warning, to temporary interaction limits, to a permanent
|
|
47
|
+
ban — following the Contributor Covenant's enforcement guidelines.
|
|
48
|
+
|
|
49
|
+
## Attribution
|
|
50
|
+
|
|
51
|
+
Adapted from the [Contributor Covenant](https://www.contributor-covenant.org),
|
|
52
|
+
version 2.1, available under CC BY 4.0.
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# Contributing to Token Bill
|
|
2
|
+
|
|
3
|
+
Thanks for helping make agent bills explainable. This document covers the
|
|
4
|
+
mechanics; [docs/SPEC.md](docs/SPEC.md) is the authoritative contract for the
|
|
5
|
+
internals — when this file and the spec disagree, the spec wins.
|
|
6
|
+
|
|
7
|
+
## Ground rules
|
|
8
|
+
|
|
9
|
+
- **Zero runtime dependencies is a feature, not an accident.** The package
|
|
10
|
+
must import and run on a bare Python 3.10+ install — no exceptions, not
|
|
11
|
+
even optional extras. The recorder (`tokenbill/instrument.py`) duck-types
|
|
12
|
+
the Anthropic SDK client and must never import `anthropic`. PRs that add a
|
|
13
|
+
runtime dependency will be declined.
|
|
14
|
+
- **The honesty rules are product law.** Every dollar total comes from real
|
|
15
|
+
billed `usage` fields — exact. Character-based tokenization
|
|
16
|
+
(`approx_tokens`, chars ÷ 3.7) is used *only* for proportional attribution
|
|
17
|
+
and divergence localization, is always rescaled so segments sum to the
|
|
18
|
+
billed total, and is labeled "approx" everywhere it surfaces. A PR that
|
|
19
|
+
presents an approximate number as billed will be declined regardless of how
|
|
20
|
+
useful the number is.
|
|
21
|
+
- **Determinism is load-bearing.** Every stochastic step takes an explicit
|
|
22
|
+
`seed` and derives its stream via `common.rng(seed, *scope)`. Never call
|
|
23
|
+
`random` module-level functions or `hash()` for anything reproducible.
|
|
24
|
+
- **The flagship test is the contract.**
|
|
25
|
+
`tests/test_demo_recovers_planted_waste.py` asserts the analyzer, simulator,
|
|
26
|
+
and breaker detector recover the demo traces' planted waste, with
|
|
27
|
+
expectations derived from the documented usage construction. If your change
|
|
28
|
+
breaks it, either the instruments are wrong or the traces changed — either
|
|
29
|
+
way, that is a design conversation, not a tolerance bump.
|
|
30
|
+
|
|
31
|
+
## Development setup
|
|
32
|
+
|
|
33
|
+
We use [uv](https://docs.astral.sh/uv/). No install step is needed beyond
|
|
34
|
+
cloning:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
git clone https://github.com/sedai77/tokenbill-llm-agent-cost-profiler
|
|
38
|
+
cd tokenbill-llm-agent-cost-profiler
|
|
39
|
+
|
|
40
|
+
# Run the full offline pipeline
|
|
41
|
+
uv run tokenbill demo -o report.html
|
|
42
|
+
|
|
43
|
+
# Tests (any supported interpreter; CI runs 3.10 and 3.13)
|
|
44
|
+
uv run --python 3.12 --extra dev pytest -q
|
|
45
|
+
|
|
46
|
+
# Lint
|
|
47
|
+
uv run --python 3.12 --extra dev ruff check .
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Or via the Makefile: `make demo`, `make test`, `make lint`.
|
|
51
|
+
|
|
52
|
+
## Tests
|
|
53
|
+
|
|
54
|
+
Everything passes offline — no network, no API keys, ever. CI has no secrets
|
|
55
|
+
and never will. Tests for the recorder use a tiny fake client double (a few
|
|
56
|
+
lines), never the real SDK. Tests that need trace input generate it from
|
|
57
|
+
`tokenbill/demo_traces.py` or build `Call` objects inline — do not add tests
|
|
58
|
+
that download anything or check in large trace fixtures containing real
|
|
59
|
+
prompts.
|
|
60
|
+
|
|
61
|
+
## Style
|
|
62
|
+
|
|
63
|
+
- `from __future__ import annotations`, full type hints, frozen dataclasses
|
|
64
|
+
for value types.
|
|
65
|
+
- Loggers are named `logging.getLogger("tokenbill.<module>")`.
|
|
66
|
+
- Docstrings on public functions; comments only for invariants the code
|
|
67
|
+
cannot express.
|
|
68
|
+
- `ruff check .` must pass (config in `pyproject.toml`; line length 100).
|
|
69
|
+
|
|
70
|
+
## Submitting changes
|
|
71
|
+
|
|
72
|
+
1. Fork, branch, and keep the change focused.
|
|
73
|
+
2. Add or update tests next to the code you touched.
|
|
74
|
+
3. Update `CHANGELOG.md` under `[Unreleased]` for user-visible changes.
|
|
75
|
+
4. Confirm `pytest -q` and `ruff check .` are clean.
|
|
76
|
+
5. Open the PR — the template walks through the checklist.
|
|
77
|
+
|
|
78
|
+
Metric definitions, cache-rule constants, and pricing entries are public API:
|
|
79
|
+
changing one changes what a dollar figure means. Such changes need a matching
|
|
80
|
+
update to [DESIGN.md](DESIGN.md), the report's methodology footnotes, and the
|
|
81
|
+
flagship test's derived expectations — together, in the same PR. Pricing and
|
|
82
|
+
cache-rule edits must cite the published pricing doc in the entry's source
|
|
83
|
+
comment; the values are re-verified before each release.
|
|
84
|
+
|
|
85
|
+
## Releasing (maintainers)
|
|
86
|
+
|
|
87
|
+
1. Re-verify the pricing table against the published pricing doc (see the
|
|
88
|
+
comment in `tokenbill/pricing.py`).
|
|
89
|
+
2. Move `[Unreleased]` entries into a new dated section in `CHANGELOG.md`.
|
|
90
|
+
3. Bump `__version__` in `tokenbill/__init__.py`.
|
|
91
|
+
4. Tag `v<version>` and push the tag. `release.yml` builds, verifies the tag
|
|
92
|
+
matches the package version, publishes to PyPI via Trusted Publishing (no
|
|
93
|
+
tokens), and creates a GitHub release with the changelog section as notes.
|