tokenaudit 0.2.0__tar.gz → 0.4.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.
- {tokenaudit-0.2.0 → tokenaudit-0.4.0}/CHANGELOG.md +52 -1
- {tokenaudit-0.2.0 → tokenaudit-0.4.0}/DECISIONS.md +17 -0
- {tokenaudit-0.2.0 → tokenaudit-0.4.0}/PKG-INFO +10 -7
- {tokenaudit-0.2.0 → tokenaudit-0.4.0}/README.md +9 -6
- {tokenaudit-0.2.0 → tokenaudit-0.4.0}/corpus/captured/runner.py +29 -13
- {tokenaudit-0.2.0 → tokenaudit-0.4.0}/corpus/synthetic/fixtures/breakpoint_limit.label.json +4 -0
- {tokenaudit-0.2.0 → tokenaudit-0.4.0}/corpus/synthetic/fixtures/context_bloat.label.json +4 -0
- tokenaudit-0.4.0/corpus/synthetic/fixtures/cross_session_distinct.label.json +36 -0
- tokenaudit-0.4.0/corpus/synthetic/fixtures/cross_session_distinct.trace.json +749 -0
- tokenaudit-0.4.0/corpus/synthetic/fixtures/cross_session_drift.label.json +39 -0
- tokenaudit-0.4.0/corpus/synthetic/fixtures/cross_session_drift.trace.json +986 -0
- tokenaudit-0.4.0/corpus/synthetic/fixtures/cross_session_stable.label.json +36 -0
- tokenaudit-0.4.0/corpus/synthetic/fixtures/cross_session_stable.trace.json +986 -0
- {tokenaudit-0.2.0 → tokenaudit-0.4.0}/corpus/synthetic/fixtures/healthy_cached.label.json +4 -0
- {tokenaudit-0.2.0 → tokenaudit-0.4.0}/corpus/synthetic/fixtures/healthy_incremental.label.json +4 -0
- {tokenaudit-0.2.0 → tokenaudit-0.4.0}/corpus/synthetic/fixtures/healthy_small.label.json +4 -0
- {tokenaudit-0.2.0 → tokenaudit-0.4.0}/corpus/synthetic/fixtures/lookback_overflow.label.json +4 -0
- {tokenaudit-0.2.0 → tokenaudit-0.4.0}/corpus/synthetic/fixtures/manifest.json +112 -0
- tokenaudit-0.4.0/corpus/synthetic/fixtures/misordered_shared_context.label.json +39 -0
- tokenaudit-0.4.0/corpus/synthetic/fixtures/misordered_shared_context.trace.json +239 -0
- tokenaudit-0.4.0/corpus/synthetic/fixtures/misordered_shared_context_provider_log.label.json +39 -0
- tokenaudit-0.4.0/corpus/synthetic/fixtures/misordered_shared_context_provider_log.trace.json +104 -0
- tokenaudit-0.4.0/corpus/synthetic/fixtures/missing_cache_markers.label.json +39 -0
- tokenaudit-0.4.0/corpus/synthetic/fixtures/missing_cache_markers.trace.json +373 -0
- {tokenaudit-0.2.0 → tokenaudit-0.4.0}/corpus/synthetic/fixtures/model_switch.label.json +4 -0
- {tokenaudit-0.2.0 → tokenaudit-0.4.0}/corpus/synthetic/fixtures/openai_healthy_cached.label.json +4 -0
- {tokenaudit-0.2.0 → tokenaudit-0.4.0}/corpus/synthetic/fixtures/prefix_drift.label.json +4 -0
- {tokenaudit-0.2.0 → tokenaudit-0.4.0}/corpus/synthetic/fixtures/prefix_drift_legacy_flat.label.json +4 -0
- {tokenaudit-0.2.0 → tokenaudit-0.4.0}/corpus/synthetic/fixtures/prefix_drift_no_content.label.json +4 -0
- {tokenaudit-0.2.0 → tokenaudit-0.4.0}/corpus/synthetic/fixtures/prefix_drift_openinference.label.json +4 -0
- tokenaudit-0.4.0/corpus/synthetic/fixtures/prefix_drift_provider_log.label.json +39 -0
- tokenaudit-0.4.0/corpus/synthetic/fixtures/prefix_drift_provider_log.trace.json +284 -0
- {tokenaudit-0.2.0 → tokenaudit-0.4.0}/corpus/synthetic/fixtures/retry_storm.label.json +4 -0
- tokenaudit-0.4.0/corpus/synthetic/fixtures/shared_context_first.label.json +37 -0
- tokenaudit-0.4.0/corpus/synthetic/fixtures/shared_context_first.trace.json +275 -0
- tokenaudit-0.4.0/corpus/synthetic/fixtures/shared_context_small.label.json +37 -0
- tokenaudit-0.4.0/corpus/synthetic/fixtures/shared_context_small.trace.json +239 -0
- {tokenaudit-0.2.0 → tokenaudit-0.4.0}/corpus/synthetic/fixtures/sub_minimum.label.json +4 -0
- tokenaudit-0.4.0/corpus/synthetic/fixtures/tool_schema_bloat.label.json +39 -0
- tokenaudit-0.4.0/corpus/synthetic/fixtures/tool_schema_bloat.trace.json +1160 -0
- tokenaudit-0.4.0/corpus/synthetic/fixtures/tool_schema_bloat_provider_log.label.json +39 -0
- tokenaudit-0.4.0/corpus/synthetic/fixtures/tool_schema_bloat_provider_log.trace.json +1412 -0
- {tokenaudit-0.2.0 → tokenaudit-0.4.0}/corpus/synthetic/fixtures/tool_schema_churn.label.json +4 -0
- tokenaudit-0.4.0/corpus/synthetic/fixtures/tool_schema_lean.label.json +38 -0
- tokenaudit-0.4.0/corpus/synthetic/fixtures/tool_schema_lean.trace.json +836 -0
- tokenaudit-0.4.0/corpus/synthetic/fixtures/tool_schema_rotating.label.json +38 -0
- tokenaudit-0.4.0/corpus/synthetic/fixtures/tool_schema_rotating.trace.json +1342 -0
- {tokenaudit-0.2.0 → tokenaudit-0.4.0}/corpus/synthetic/fixtures/ttl_expiry.label.json +4 -0
- tokenaudit-0.4.0/corpus/synthetic/fixtures/unmarked_far_apart.label.json +37 -0
- tokenaudit-0.4.0/corpus/synthetic/fixtures/unmarked_far_apart.trace.json +373 -0
- {tokenaudit-0.2.0 → tokenaudit-0.4.0}/corpus/synthetic/generate.py +494 -14
- {tokenaudit-0.2.0 → tokenaudit-0.4.0}/demo/README.md +4 -0
- {tokenaudit-0.2.0 → tokenaudit-0.4.0}/demo/app.py +5 -6
- {tokenaudit-0.2.0 → tokenaudit-0.4.0}/demo/static/index.html +1 -1
- {tokenaudit-0.2.0 → tokenaudit-0.4.0}/docs/case-studies/tradingagents.md +26 -1
- tokenaudit-0.4.0/eval/results.json +1692 -0
- {tokenaudit-0.2.0 → tokenaudit-0.4.0}/eval/results.md +8 -4
- {tokenaudit-0.2.0 → tokenaudit-0.4.0}/eval/run_eval.py +14 -11
- {tokenaudit-0.2.0 → tokenaudit-0.4.0}/pyproject.toml +1 -1
- tokenaudit-0.4.0/render.yaml +10 -0
- {tokenaudit-0.2.0 → tokenaudit-0.4.0}/src/tokenaudit/__init__.py +1 -1
- {tokenaudit-0.2.0 → tokenaudit-0.4.0}/src/tokenaudit/__main__.py +32 -5
- {tokenaudit-0.2.0 → tokenaudit-0.4.0}/src/tokenaudit/ci/baseline.py +13 -2
- tokenaudit-0.4.0/src/tokenaudit/counterfactual/__init__.py +88 -0
- {tokenaudit-0.2.0 → tokenaudit-0.4.0}/src/tokenaudit/detect/__init__.py +9 -1
- {tokenaudit-0.2.0 → tokenaudit-0.4.0}/src/tokenaudit/detect/base.py +68 -2
- tokenaudit-0.4.0/src/tokenaudit/detect/corpus_record.json +80 -0
- {tokenaudit-0.2.0 → tokenaudit-0.4.0}/src/tokenaudit/detect/engine.py +50 -1
- {tokenaudit-0.2.0 → tokenaudit-0.4.0}/src/tokenaudit/detect/rules/breakpoint_limit.py +12 -15
- {tokenaudit-0.2.0 → tokenaudit-0.4.0}/src/tokenaudit/detect/rules/context_bloat.py +14 -14
- tokenaudit-0.4.0/src/tokenaudit/detect/rules/cross_session_drift.py +226 -0
- {tokenaudit-0.2.0 → tokenaudit-0.4.0}/src/tokenaudit/detect/rules/lookback_overflow.py +15 -15
- tokenaudit-0.4.0/src/tokenaudit/detect/rules/misordered_shared_context.py +180 -0
- tokenaudit-0.4.0/src/tokenaudit/detect/rules/missing_cache_markers.py +149 -0
- {tokenaudit-0.2.0 → tokenaudit-0.4.0}/src/tokenaudit/detect/rules/model_switch.py +11 -12
- {tokenaudit-0.2.0 → tokenaudit-0.4.0}/src/tokenaudit/detect/rules/prefix_drift.py +15 -18
- {tokenaudit-0.2.0 → tokenaudit-0.4.0}/src/tokenaudit/detect/rules/retry_storm.py +13 -14
- {tokenaudit-0.2.0 → tokenaudit-0.4.0}/src/tokenaudit/detect/rules/sub_minimum.py +16 -18
- tokenaudit-0.4.0/src/tokenaudit/detect/rules/tool_schema_bloat.py +122 -0
- {tokenaudit-0.2.0 → tokenaudit-0.4.0}/src/tokenaudit/detect/rules/tool_schema_churn.py +13 -18
- {tokenaudit-0.2.0 → tokenaudit-0.4.0}/src/tokenaudit/detect/rules/ttl_expiry.py +16 -19
- {tokenaudit-0.2.0 → tokenaudit-0.4.0}/src/tokenaudit/ingest/__init__.py +20 -12
- {tokenaudit-0.2.0 → tokenaudit-0.4.0}/src/tokenaudit/ingest/dialects.py +100 -5
- tokenaudit-0.4.0/src/tokenaudit/ingest/provider_logs.py +267 -0
- {tokenaudit-0.2.0 → tokenaudit-0.4.0}/src/tokenaudit/model/records.py +27 -3
- {tokenaudit-0.2.0 → tokenaudit-0.4.0}/src/tokenaudit/report/html.py +20 -2
- {tokenaudit-0.2.0 → tokenaudit-0.4.0}/tests/test_corpus.py +15 -2
- tokenaudit-0.4.0/tests/test_counterfactual.py +65 -0
- {tokenaudit-0.2.0 → tokenaudit-0.4.0}/tests/test_demo.py +9 -0
- tokenaudit-0.4.0/tests/test_detect.py +408 -0
- {tokenaudit-0.2.0 → tokenaudit-0.4.0}/tests/test_eval.py +1 -1
- {tokenaudit-0.2.0 → tokenaudit-0.4.0}/tests/test_ingest.py +78 -1
- {tokenaudit-0.2.0 → tokenaudit-0.4.0}/tests/test_report.py +1 -1
- {tokenaudit-0.2.0 → tokenaudit-0.4.0}/tokenaudit-baseline.json +5 -2
- tokenaudit-0.2.0/eval/results.json +0 -668
- tokenaudit-0.2.0/src/tokenaudit/counterfactual/__init__.py +0 -1
- tokenaudit-0.2.0/src/tokenaudit/detect/corpus_record.json +0 -56
- tokenaudit-0.2.0/tests/test_detect.py +0 -201
- {tokenaudit-0.2.0 → tokenaudit-0.4.0}/.dockerignore +0 -0
- {tokenaudit-0.2.0 → tokenaudit-0.4.0}/.github/workflows/ci.yml +0 -0
- {tokenaudit-0.2.0 → tokenaudit-0.4.0}/.github/workflows/release.yml +0 -0
- {tokenaudit-0.2.0 → tokenaudit-0.4.0}/.gitignore +0 -0
- {tokenaudit-0.2.0 → tokenaudit-0.4.0}/.pre-commit-config.yaml +0 -0
- {tokenaudit-0.2.0 → tokenaudit-0.4.0}/LICENSE +0 -0
- {tokenaudit-0.2.0 → tokenaudit-0.4.0}/Makefile +0 -0
- {tokenaudit-0.2.0 → tokenaudit-0.4.0}/action.yml +0 -0
- {tokenaudit-0.2.0 → tokenaudit-0.4.0}/corpus/README.md +0 -0
- {tokenaudit-0.2.0 → tokenaudit-0.4.0}/corpus/captured/README.md +0 -0
- {tokenaudit-0.2.0 → tokenaudit-0.4.0}/corpus/captured/capture_tradingagents.sh +0 -0
- {tokenaudit-0.2.0 → tokenaudit-0.4.0}/corpus/captured/redact.py +0 -0
- {tokenaudit-0.2.0 → tokenaudit-0.4.0}/corpus/captured/runs/tradingagents/run-01-NVDA-2026-08-14/decision.txt +0 -0
- {tokenaudit-0.2.0 → tokenaudit-0.4.0}/corpus/captured/runs/tradingagents/run-01-NVDA-2026-08-14/manifest.json +0 -0
- {tokenaudit-0.2.0 → tokenaudit-0.4.0}/corpus/captured/runs/tradingagents/run-01-NVDA-2026-08-14/trace.jsonl +0 -0
- {tokenaudit-0.2.0 → tokenaudit-0.4.0}/corpus/captured/runs/tradingagents/run-02-AAPL-2026-08-14/decision.txt +0 -0
- {tokenaudit-0.2.0 → tokenaudit-0.4.0}/corpus/captured/runs/tradingagents/run-02-AAPL-2026-08-14/manifest.json +0 -0
- {tokenaudit-0.2.0 → tokenaudit-0.4.0}/corpus/captured/runs/tradingagents/run-02-AAPL-2026-08-14/trace.jsonl +0 -0
- {tokenaudit-0.2.0 → tokenaudit-0.4.0}/corpus/captured/runs/tradingagents/run-03-NVDA-2026-08-21/decision.txt +0 -0
- {tokenaudit-0.2.0 → tokenaudit-0.4.0}/corpus/captured/runs/tradingagents/run-03-NVDA-2026-08-21/manifest.json +0 -0
- {tokenaudit-0.2.0 → tokenaudit-0.4.0}/corpus/captured/runs/tradingagents/run-03-NVDA-2026-08-21/trace.jsonl +0 -0
- {tokenaudit-0.2.0 → tokenaudit-0.4.0}/corpus/synthetic/fixtures/breakpoint_limit.trace.json +0 -0
- {tokenaudit-0.2.0 → tokenaudit-0.4.0}/corpus/synthetic/fixtures/context_bloat.trace.json +0 -0
- {tokenaudit-0.2.0 → tokenaudit-0.4.0}/corpus/synthetic/fixtures/healthy_cached.trace.json +0 -0
- {tokenaudit-0.2.0 → tokenaudit-0.4.0}/corpus/synthetic/fixtures/healthy_incremental.trace.json +0 -0
- {tokenaudit-0.2.0 → tokenaudit-0.4.0}/corpus/synthetic/fixtures/healthy_small.trace.json +0 -0
- {tokenaudit-0.2.0 → tokenaudit-0.4.0}/corpus/synthetic/fixtures/lookback_overflow.trace.json +0 -0
- {tokenaudit-0.2.0 → tokenaudit-0.4.0}/corpus/synthetic/fixtures/model_switch.trace.json +0 -0
- {tokenaudit-0.2.0 → tokenaudit-0.4.0}/corpus/synthetic/fixtures/openai_healthy_cached.trace.json +0 -0
- {tokenaudit-0.2.0 → tokenaudit-0.4.0}/corpus/synthetic/fixtures/prefix_drift.trace.json +0 -0
- {tokenaudit-0.2.0 → tokenaudit-0.4.0}/corpus/synthetic/fixtures/prefix_drift_legacy_flat.trace.json +0 -0
- {tokenaudit-0.2.0 → tokenaudit-0.4.0}/corpus/synthetic/fixtures/prefix_drift_no_content.trace.json +0 -0
- {tokenaudit-0.2.0 → tokenaudit-0.4.0}/corpus/synthetic/fixtures/prefix_drift_openinference.trace.json +0 -0
- {tokenaudit-0.2.0 → tokenaudit-0.4.0}/corpus/synthetic/fixtures/retry_storm.trace.json +0 -0
- {tokenaudit-0.2.0 → tokenaudit-0.4.0}/corpus/synthetic/fixtures/sub_minimum.trace.json +0 -0
- {tokenaudit-0.2.0 → tokenaudit-0.4.0}/corpus/synthetic/fixtures/tool_schema_churn.trace.json +0 -0
- {tokenaudit-0.2.0 → tokenaudit-0.4.0}/corpus/synthetic/fixtures/ttl_expiry.trace.json +0 -0
- {tokenaudit-0.2.0 → tokenaudit-0.4.0}/demo/Dockerfile +0 -0
- {tokenaudit-0.2.0 → tokenaudit-0.4.0}/demo/__init__.py +0 -0
- {tokenaudit-0.2.0 → tokenaudit-0.4.0}/demo/requirements.txt +0 -0
- {tokenaudit-0.2.0 → tokenaudit-0.4.0}/docs/ci.md +0 -0
- {tokenaudit-0.2.0 → tokenaudit-0.4.0}/docs/rulebook.md +0 -0
- {tokenaudit-0.2.0 → tokenaudit-0.4.0}/src/tokenaudit/ci/__init__.py +0 -0
- {tokenaudit-0.2.0 → tokenaudit-0.4.0}/src/tokenaudit/ci/check.py +0 -0
- {tokenaudit-0.2.0 → tokenaudit-0.4.0}/src/tokenaudit/detect/findings.py +0 -0
- {tokenaudit-0.2.0 → tokenaudit-0.4.0}/src/tokenaudit/detect/rules/__init__.py +0 -0
- {tokenaudit-0.2.0 → tokenaudit-0.4.0}/src/tokenaudit/examples/sample.trace.json +0 -0
- {tokenaudit-0.2.0 → tokenaudit-0.4.0}/src/tokenaudit/ingest/containers.py +0 -0
- {tokenaudit-0.2.0 → tokenaudit-0.4.0}/src/tokenaudit/ingest/dedup.py +0 -0
- {tokenaudit-0.2.0 → tokenaudit-0.4.0}/src/tokenaudit/ingest/quality.py +0 -0
- {tokenaudit-0.2.0 → tokenaudit-0.4.0}/src/tokenaudit/ingest/sessionize.py +0 -0
- {tokenaudit-0.2.0 → tokenaudit-0.4.0}/src/tokenaudit/model/__init__.py +0 -0
- {tokenaudit-0.2.0 → tokenaudit-0.4.0}/src/tokenaudit/py.typed +0 -0
- {tokenaudit-0.2.0 → tokenaudit-0.4.0}/src/tokenaudit/report/__init__.py +0 -0
- {tokenaudit-0.2.0 → tokenaudit-0.4.0}/src/tokenaudit/report/corpus_record.py +0 -0
- {tokenaudit-0.2.0 → tokenaudit-0.4.0}/src/tokenaudit/report/health.py +0 -0
- {tokenaudit-0.2.0 → tokenaudit-0.4.0}/src/tokenaudit/rulebook/__init__.py +0 -0
- {tokenaudit-0.2.0 → tokenaudit-0.4.0}/src/tokenaudit/rulebook/anthropic.json +0 -0
- {tokenaudit-0.2.0 → tokenaudit-0.4.0}/src/tokenaudit/rulebook/google.json +0 -0
- {tokenaudit-0.2.0 → tokenaudit-0.4.0}/src/tokenaudit/rulebook/openai.json +0 -0
- {tokenaudit-0.2.0 → tokenaudit-0.4.0}/tests/test_ci.py +0 -0
- {tokenaudit-0.2.0 → tokenaudit-0.4.0}/tests/test_package.py +0 -0
- {tokenaudit-0.2.0 → tokenaudit-0.4.0}/tests/test_redact.py +0 -0
- {tokenaudit-0.2.0 → tokenaudit-0.4.0}/tests/test_rulebook.py +0 -0
|
@@ -1,6 +1,57 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
##
|
|
3
|
+
## 0.4.0 — 2026-11-12
|
|
4
|
+
|
|
5
|
+
Upgrading adds findings to traces with these patterns, and changes the
|
|
6
|
+
impact of some existing ones: re-baseline `tokenaudit check` after upgrading.
|
|
7
|
+
|
|
8
|
+
New detectors, each with a positive corpus fixture and near-miss negatives:
|
|
9
|
+
|
|
10
|
+
- `misordered_shared_context`: the same reports or documents sent to several
|
|
11
|
+
agents, each behind its own role text, so the prefix cache never reuses
|
|
12
|
+
them.
|
|
13
|
+
- `cross_session_drift`: a date, user or ticker near the start of a long
|
|
14
|
+
prompt, which keeps it from being reused across sessions.
|
|
15
|
+
- `tool_schema_bloat`: tool definitions sent on every request and never
|
|
16
|
+
called by any request carrying the same tool set.
|
|
17
|
+
- `missing_cache_markers`: a provider that only caches on request
|
|
18
|
+
(Anthropic), a session with no markers and no cache activity, and requests
|
|
19
|
+
re-sending a prefix an earlier one sent within the cache lifetime.
|
|
20
|
+
|
|
21
|
+
The first two find the TradingAgents problems the case study diagnosed by
|
|
22
|
+
hand. The captured runs, previously reported as clean, now show both
|
|
23
|
+
findings and a 25% health floor (was 100%).
|
|
24
|
+
|
|
25
|
+
Pricing:
|
|
26
|
+
|
|
27
|
+
- Every finding is priced with its model's read and write rates from the
|
|
28
|
+
rulebook. A request that wrote the cache is charged the write premium
|
|
29
|
+
(1.25x on Anthropic's 5-minute cache) instead of full price, and the
|
|
30
|
+
assumption line names the rates used.
|
|
31
|
+
- `diagnose` and the HTML report show a dollar range when the rulebook has a
|
|
32
|
+
verified input price for the model.
|
|
33
|
+
|
|
34
|
+
Ingest:
|
|
35
|
+
|
|
36
|
+
- Raw provider logs: JSONL or a JSON array of logged request/response pairs
|
|
37
|
+
from the OpenAI Responses, OpenAI Chat Completions and Anthropic Messages
|
|
38
|
+
APIs. Anthropic's exclusive token counts are converted; records without
|
|
39
|
+
timestamps are flagged and time-based detectors abstain.
|
|
40
|
+
- Tool definitions (name and size) and the tools each response called, from
|
|
41
|
+
OpenInference, `gen_ai.tool.definitions` in current OTel GenAI, and raw
|
|
42
|
+
logs.
|
|
43
|
+
- Per-line digests kept in memory for the new detectors; `ingest` output
|
|
44
|
+
leaves them out.
|
|
45
|
+
|
|
46
|
+
Also:
|
|
47
|
+
|
|
48
|
+
- `diagnose` and `check` report a cross-session section when given more
|
|
49
|
+
than one session.
|
|
50
|
+
- Demo: a `render.yaml` blueprint for one-click deploys, and raw provider
|
|
51
|
+
logs are accepted as uploads.
|
|
52
|
+
- The capture runner records Anthropic runs (`--provider anthropic`);
|
|
53
|
+
`experiments/tradingagents-claude/` is ready to run with an API key.
|
|
54
|
+
- The corpus has 30 fixtures (was 16).
|
|
4
55
|
|
|
5
56
|
## 0.2.0 — 2026-11-10
|
|
6
57
|
|
|
@@ -39,3 +39,20 @@ Three calls made together for the 0.1.0 release, all in service of the same prin
|
|
|
39
39
|
**The cache health score is `realized / achievable` savings** — realized cache savings over realized-plus-what-the-findings-quantify. Both halves are computed the same way, so there are no weights to defend; overlapping findings make the denominator conservative, so the score is presented as a floor and says so.
|
|
40
40
|
|
|
41
41
|
**Findings carry a corpus record, not a confidence number.** A precision float computed from a 16-fixture corpus would be theater. The package ships the raw record instead — "3/4 planted cases found, 0 false alarms in N negatives" — generated by the eval harness and drift-tested, so what appears next to a finding is exactly what was measured.
|
|
42
|
+
|
|
43
|
+
## 007 — Line-level digests, and detectors that look across sessions (2026-11-12)
|
|
44
|
+
|
|
45
|
+
**Decision:** ingest keeps a truncated SHA-256 and length for every line of every message, in memory only, next to the existing whole-message digest. Two detectors use them: `misordered_shared_context` within a session and `cross_session_drift` across sessions.
|
|
46
|
+
|
|
47
|
+
**Why:** the TradingAgents diagnosis was done by hand, because whole-message hashes can't see the same reports inside two different messages. The tool called those runs clean while the case study showed a 4x improvement. Lines are fine enough to find shared content and to pin a date to one line, and coarse enough to stay fast.
|
|
48
|
+
|
|
49
|
+
**Boundaries:** line digests never leave the process (`tokenaudit ingest` drops them), because a hash of a short line is easy to guess. Cross-session findings count toward a CI gate's findings but not toward the health floor, since whether sessions share a cache depends on when they ran; the low impact bound only counts sessions that started within the model's cache lifetime of each other. Each new detector defers to an existing one where they overlap: a short edit near the start of a prompt is prefix drift, and a value that changes on every call within a session is prefix drift too.
|
|
50
|
+
|
|
51
|
+
## 008 — Price against the fix, per model; new detectors yield to older ones (2026-11-12)
|
|
52
|
+
|
|
53
|
+
**Decision:** a finding's impact is what the affected tokens cost minus what they would have cost under the fix, using the model's read and write multipliers from the rulebook. A request that wrote the cache is charged the write premium, not full price. Dollars appear only when the rulebook has a verified input price; tiered prices (Haiku 5.5) stay in tokens.
|
|
54
|
+
|
|
55
|
+
**Why:** a flat discount overstated waste on models with cheaper reads and ignored that a miss on Anthropic often costs more than a plain request, because it writes the cache. Keeping the rates in one module means a rulebook update reprices every detector at once.
|
|
56
|
+
|
|
57
|
+
**Boundaries:** `tool_schema_bloat` prints tool names, which are identifiers a developer chose rather than prompt text. It counts a tool as used if any request with the same tool set called it, so a shared registry where every tool is used by some agent is not flagged; telling agents apart needs more than a trace carries. `missing_cache_markers` only counts block-aligned prefixes repeated within the cache lifetime, since a breakpoint sits at a block's end and a later repeat would have missed anyway. It leaves growing conversations to `context_bloat` and byte-identical re-sends to `retry_storm`, whose fixes fit those cases better.
|
|
58
|
+
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: tokenaudit
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: Root-cause diagnosis of wasted LLM tokens, from the traces your agents already emit
|
|
5
5
|
Project-URL: Repository, https://github.com/yashmyeole/tokenaudit
|
|
6
6
|
Project-URL: Issues, https://github.com/yashmyeole/tokenaudit/issues
|
|
@@ -43,7 +43,8 @@ It works: diagnosing one of those documented cases produced a 9-file patch that
|
|
|
43
43
|
tokenaudit diagnose --sample # diagnose the bundled demo trace
|
|
44
44
|
tokenaudit diagnose mytraces/ --html report.html
|
|
45
45
|
|
|
46
|
-
Zero runtime dependencies; reads OTLP/JSON files
|
|
46
|
+
Zero runtime dependencies; reads OTLP/JSON files, OTel SDK JSONL, and raw
|
|
47
|
+
OpenAI or Anthropic request/response logs. Try it
|
|
47
48
|
on this repo's real captured runs: `tokenaudit diagnose corpus/captured/runs`.
|
|
48
49
|
Prefer a browser? The [demo app](demo/) diagnoses a dropped trace; uploads
|
|
49
50
|
are parsed in memory and never stored.
|
|
@@ -51,7 +52,7 @@ are parsed in memory and never stored.
|
|
|
51
52
|
The plan, in one line each:
|
|
52
53
|
|
|
53
54
|
- Ingest standard traces — OpenTelemetry GenAI in all three of its cache-field naming generations, OpenInference, raw provider SDK logs — into one normalized accounting model.
|
|
54
|
-
- Run a library of deterministic detectors for the silent failure modes: prefix drift, sub-minimum cache blocks, model switches, TTL expiry, breakpoint and lookback limits, tool-schema churn and bloat, context bloat, retry storms.
|
|
55
|
+
- Run a library of deterministic detectors for the silent failure modes: prefix drift, sub-minimum cache blocks, model switches, TTL expiry, breakpoint and lookback limits, tool-schema churn and bloat, context bloat, retry storms, shared content sent behind different openings, per-session values that stop a prompt being reused across sessions, and prompts that never ask a cache-on-request provider to cache them.
|
|
55
56
|
- Price each finding against a counterfactual — what the bill would have been under the fix — as a bounded range, never a point guess.
|
|
56
57
|
- Publish per-detector precision and recall against a labeled corpus, so you know how much to trust each finding.
|
|
57
58
|
|
|
@@ -119,10 +120,12 @@ If you know a tool that already does this, please open an issue — I would genu
|
|
|
119
120
|
- [x] CI cost-regression mode: fail a build that tanks the cache hit rate ([docs/ci.md](docs/ci.md))
|
|
120
121
|
- [x] Demo app: drag a trace in, get the report ([demo/](demo/))
|
|
121
122
|
- [x] Case studies on public agents, with reproduction commands ([TradingAgents: 4.3× cache hit rate](docs/case-studies/tradingagents.md))
|
|
122
|
-
- [
|
|
123
|
-
- [
|
|
124
|
-
- [
|
|
125
|
-
- [
|
|
123
|
+
- [x] Detectors for misordered shared context and cross-session drift: they now find the TradingAgents problems on their own ([case study, section 6](docs/case-studies/tradingagents.md#6-what-tokenaudit-reports-now))
|
|
124
|
+
- [x] Counterfactual pricing with per-model read and write rates and dollar ranges
|
|
125
|
+
- [x] Raw provider SDK log ingest (OpenAI Responses, Chat Completions, Anthropic Messages), tool-schema bloat detector
|
|
126
|
+
- [x] A detector for prompts that never ask Anthropic to cache them
|
|
127
|
+
- [ ] Public demo deployment (`render.yaml` is ready; see [demo/](demo/))
|
|
128
|
+
- [ ] Case study on an Anthropic-model agent (the harness is ready: [experiments/tradingagents-claude](experiments/tradingagents-claude/))
|
|
126
129
|
|
|
127
130
|
## Development
|
|
128
131
|
|
|
@@ -16,7 +16,8 @@ It works: diagnosing one of those documented cases produced a 9-file patch that
|
|
|
16
16
|
tokenaudit diagnose --sample # diagnose the bundled demo trace
|
|
17
17
|
tokenaudit diagnose mytraces/ --html report.html
|
|
18
18
|
|
|
19
|
-
Zero runtime dependencies; reads OTLP/JSON files
|
|
19
|
+
Zero runtime dependencies; reads OTLP/JSON files, OTel SDK JSONL, and raw
|
|
20
|
+
OpenAI or Anthropic request/response logs. Try it
|
|
20
21
|
on this repo's real captured runs: `tokenaudit diagnose corpus/captured/runs`.
|
|
21
22
|
Prefer a browser? The [demo app](demo/) diagnoses a dropped trace; uploads
|
|
22
23
|
are parsed in memory and never stored.
|
|
@@ -24,7 +25,7 @@ are parsed in memory and never stored.
|
|
|
24
25
|
The plan, in one line each:
|
|
25
26
|
|
|
26
27
|
- Ingest standard traces — OpenTelemetry GenAI in all three of its cache-field naming generations, OpenInference, raw provider SDK logs — into one normalized accounting model.
|
|
27
|
-
- Run a library of deterministic detectors for the silent failure modes: prefix drift, sub-minimum cache blocks, model switches, TTL expiry, breakpoint and lookback limits, tool-schema churn and bloat, context bloat, retry storms.
|
|
28
|
+
- Run a library of deterministic detectors for the silent failure modes: prefix drift, sub-minimum cache blocks, model switches, TTL expiry, breakpoint and lookback limits, tool-schema churn and bloat, context bloat, retry storms, shared content sent behind different openings, per-session values that stop a prompt being reused across sessions, and prompts that never ask a cache-on-request provider to cache them.
|
|
28
29
|
- Price each finding against a counterfactual — what the bill would have been under the fix — as a bounded range, never a point guess.
|
|
29
30
|
- Publish per-detector precision and recall against a labeled corpus, so you know how much to trust each finding.
|
|
30
31
|
|
|
@@ -92,10 +93,12 @@ If you know a tool that already does this, please open an issue — I would genu
|
|
|
92
93
|
- [x] CI cost-regression mode: fail a build that tanks the cache hit rate ([docs/ci.md](docs/ci.md))
|
|
93
94
|
- [x] Demo app: drag a trace in, get the report ([demo/](demo/))
|
|
94
95
|
- [x] Case studies on public agents, with reproduction commands ([TradingAgents: 4.3× cache hit rate](docs/case-studies/tradingagents.md))
|
|
95
|
-
- [
|
|
96
|
-
- [
|
|
97
|
-
- [
|
|
98
|
-
- [
|
|
96
|
+
- [x] Detectors for misordered shared context and cross-session drift: they now find the TradingAgents problems on their own ([case study, section 6](docs/case-studies/tradingagents.md#6-what-tokenaudit-reports-now))
|
|
97
|
+
- [x] Counterfactual pricing with per-model read and write rates and dollar ranges
|
|
98
|
+
- [x] Raw provider SDK log ingest (OpenAI Responses, Chat Completions, Anthropic Messages), tool-schema bloat detector
|
|
99
|
+
- [x] A detector for prompts that never ask Anthropic to cache them
|
|
100
|
+
- [ ] Public demo deployment (`render.yaml` is ready; see [demo/](demo/))
|
|
101
|
+
- [ ] Case study on an Anthropic-model agent (the harness is ready: [experiments/tradingagents-claude](experiments/tradingagents-claude/))
|
|
99
102
|
|
|
100
103
|
## Development
|
|
101
104
|
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
"""Capture one real TradingAgents run as OpenTelemetry spans in a JSONL file.
|
|
2
2
|
|
|
3
3
|
Zero-modification capture: TradingAgents' code is untouched. Instrumentation
|
|
4
|
-
wraps the
|
|
5
|
-
posture tokenaudit itself takes. Spans are written
|
|
6
|
-
Python SDK's own ``ReadableSpan.to_json()``
|
|
7
|
-
so the captured dialect is exactly what
|
|
4
|
+
wraps the provider SDK (OpenAI or Anthropic) and LangChain at process level,
|
|
5
|
+
the same zero-integration posture tokenaudit itself takes. Spans are written
|
|
6
|
+
line-by-line using the OTel Python SDK's own ``ReadableSpan.to_json()``
|
|
7
|
+
rendering, with no custom mapping, so the captured dialect is exactly what
|
|
8
|
+
the SDK produces in the wild.
|
|
8
9
|
|
|
9
10
|
This script is executed by ``capture_tradingagents.sh`` inside a throwaway
|
|
10
11
|
virtualenv on the machine that holds the API key. It is not part of the
|
|
@@ -66,16 +67,22 @@ def build_provider(trace_file: Path, run_id: str, ta_commit: str) -> TracerProvi
|
|
|
66
67
|
return provider
|
|
67
68
|
|
|
68
69
|
|
|
69
|
-
def instrument(provider: TracerProvider) -> list[str]:
|
|
70
|
+
def instrument(provider: TracerProvider, llm_provider: str = "openai") -> list[str]:
|
|
70
71
|
"""Attach OpenInference instrumentors; return the ones that activated."""
|
|
71
72
|
active: list[str] = []
|
|
72
73
|
try:
|
|
73
|
-
|
|
74
|
+
if llm_provider == "anthropic":
|
|
75
|
+
from openinference.instrumentation.anthropic import AnthropicInstrumentor
|
|
74
76
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
+
AnthropicInstrumentor().instrument(tracer_provider=provider)
|
|
78
|
+
active.append("openinference-instrumentation-anthropic")
|
|
79
|
+
else:
|
|
80
|
+
from openinference.instrumentation.openai import OpenAIInstrumentor
|
|
81
|
+
|
|
82
|
+
OpenAIInstrumentor().instrument(tracer_provider=provider)
|
|
83
|
+
active.append("openinference-instrumentation-openai")
|
|
77
84
|
except Exception as exc:
|
|
78
|
-
print(f"warning:
|
|
85
|
+
print(f"warning: {llm_provider} SDK instrumentor not active: {exc}", file=sys.stderr)
|
|
79
86
|
try:
|
|
80
87
|
from openinference.instrumentation.langchain import LangChainInstrumentor
|
|
81
88
|
|
|
@@ -88,12 +95,14 @@ def instrument(provider: TracerProvider) -> list[str]:
|
|
|
88
95
|
return active
|
|
89
96
|
|
|
90
97
|
|
|
91
|
-
def run_analysis(
|
|
98
|
+
def run_analysis(
|
|
99
|
+
ticker: str, date: str, deep: str, quick: str, rounds: int, llm_provider: str = "openai"
|
|
100
|
+
) -> str:
|
|
92
101
|
from tradingagents.default_config import DEFAULT_CONFIG
|
|
93
102
|
from tradingagents.graph.trading_graph import TradingAgentsGraph
|
|
94
103
|
|
|
95
104
|
config = dict(DEFAULT_CONFIG)
|
|
96
|
-
config["llm_provider"] =
|
|
105
|
+
config["llm_provider"] = llm_provider
|
|
97
106
|
config["deep_think_llm"] = deep
|
|
98
107
|
config["quick_think_llm"] = quick
|
|
99
108
|
config["max_debate_rounds"] = rounds
|
|
@@ -119,6 +128,7 @@ def main() -> None:
|
|
|
119
128
|
parser.add_argument("--deep", default="gpt-5.4-mini")
|
|
120
129
|
parser.add_argument("--quick", default="gpt-5.4-mini")
|
|
121
130
|
parser.add_argument("--rounds", type=int, default=1)
|
|
131
|
+
parser.add_argument("--provider", choices=["openai", "anthropic"], default="openai")
|
|
122
132
|
parser.add_argument("--ta-commit", default="unknown")
|
|
123
133
|
parser.add_argument("--smoke", action="store_true", help="plumbing test, no API calls")
|
|
124
134
|
args = parser.parse_args()
|
|
@@ -134,6 +144,7 @@ def main() -> None:
|
|
|
134
144
|
"smoke": args.smoke,
|
|
135
145
|
"ticker": args.ticker,
|
|
136
146
|
"trade_date": args.date,
|
|
147
|
+
"llm_provider": args.provider,
|
|
137
148
|
"models": {"deep_think": args.deep, "quick_think": args.quick},
|
|
138
149
|
"max_debate_rounds": args.rounds,
|
|
139
150
|
"tradingagents_commit": args.ta_commit,
|
|
@@ -146,8 +157,11 @@ def main() -> None:
|
|
|
146
157
|
"tradingagents",
|
|
147
158
|
"opentelemetry-sdk",
|
|
148
159
|
"openinference-instrumentation-openai",
|
|
160
|
+
"openinference-instrumentation-anthropic",
|
|
149
161
|
"openinference-instrumentation-langchain",
|
|
150
162
|
"openai",
|
|
163
|
+
"anthropic",
|
|
164
|
+
"langchain-anthropic",
|
|
151
165
|
"langchain-core",
|
|
152
166
|
)
|
|
153
167
|
},
|
|
@@ -164,8 +178,10 @@ def main() -> None:
|
|
|
164
178
|
manifest["instrumentors"] = []
|
|
165
179
|
decision = "smoke"
|
|
166
180
|
else:
|
|
167
|
-
manifest["instrumentors"] = instrument(provider)
|
|
168
|
-
decision = run_analysis(
|
|
181
|
+
manifest["instrumentors"] = instrument(provider, args.provider)
|
|
182
|
+
decision = run_analysis(
|
|
183
|
+
args.ticker, args.date, args.deep, args.quick, args.rounds, args.provider
|
|
184
|
+
)
|
|
169
185
|
(args.out / "decision.txt").write_text(decision + "\n", encoding="utf-8")
|
|
170
186
|
|
|
171
187
|
provider.force_flush()
|
|
@@ -11,11 +11,15 @@
|
|
|
11
11
|
],
|
|
12
12
|
"must_not_contain": [
|
|
13
13
|
"context_bloat",
|
|
14
|
+
"cross_session_drift",
|
|
14
15
|
"lookback_overflow",
|
|
16
|
+
"misordered_shared_context",
|
|
17
|
+
"missing_cache_markers",
|
|
15
18
|
"model_switch",
|
|
16
19
|
"prefix_drift",
|
|
17
20
|
"retry_storm",
|
|
18
21
|
"sub_minimum",
|
|
22
|
+
"tool_schema_bloat",
|
|
19
23
|
"tool_schema_churn",
|
|
20
24
|
"ttl_expiry"
|
|
21
25
|
],
|
|
@@ -11,11 +11,15 @@
|
|
|
11
11
|
],
|
|
12
12
|
"must_not_contain": [
|
|
13
13
|
"breakpoint_limit",
|
|
14
|
+
"cross_session_drift",
|
|
14
15
|
"lookback_overflow",
|
|
16
|
+
"misordered_shared_context",
|
|
17
|
+
"missing_cache_markers",
|
|
15
18
|
"model_switch",
|
|
16
19
|
"prefix_drift",
|
|
17
20
|
"retry_storm",
|
|
18
21
|
"sub_minimum",
|
|
22
|
+
"tool_schema_bloat",
|
|
19
23
|
"tool_schema_churn",
|
|
20
24
|
"ttl_expiry"
|
|
21
25
|
],
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"fixture": "cross_session_distinct",
|
|
3
|
+
"schema": "otel-genai-current",
|
|
4
|
+
"provider_shape": "openai",
|
|
5
|
+
"models": [
|
|
6
|
+
"gpt-5.6"
|
|
7
|
+
],
|
|
8
|
+
"content_captured": true,
|
|
9
|
+
"contains": [],
|
|
10
|
+
"must_not_contain": [
|
|
11
|
+
"breakpoint_limit",
|
|
12
|
+
"context_bloat",
|
|
13
|
+
"cross_session_drift",
|
|
14
|
+
"lookback_overflow",
|
|
15
|
+
"misordered_shared_context",
|
|
16
|
+
"missing_cache_markers",
|
|
17
|
+
"model_switch",
|
|
18
|
+
"prefix_drift",
|
|
19
|
+
"retry_storm",
|
|
20
|
+
"sub_minimum",
|
|
21
|
+
"tool_schema_bloat",
|
|
22
|
+
"tool_schema_churn",
|
|
23
|
+
"ttl_expiry"
|
|
24
|
+
],
|
|
25
|
+
"evidence": {
|
|
26
|
+
"sessions": 3
|
|
27
|
+
},
|
|
28
|
+
"notes": "Three sessions run three different agents whose long system prompts share only their last line. Different prompts, not one prompt with a changing value: near-miss negative for cross_session_drift.",
|
|
29
|
+
"totals": {
|
|
30
|
+
"calls": 9,
|
|
31
|
+
"input_tokens": 13713,
|
|
32
|
+
"cache_read_tokens": 9094,
|
|
33
|
+
"cache_creation_tokens": 0,
|
|
34
|
+
"output_tokens": 1440
|
|
35
|
+
}
|
|
36
|
+
}
|