costtrace 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.
- costtrace-0.1.0/LICENSE +21 -0
- costtrace-0.1.0/PKG-INFO +422 -0
- costtrace-0.1.0/README.md +393 -0
- costtrace-0.1.0/costtrace.egg-info/PKG-INFO +422 -0
- costtrace-0.1.0/costtrace.egg-info/SOURCES.txt +35 -0
- costtrace-0.1.0/costtrace.egg-info/dependency_links.txt +1 -0
- costtrace-0.1.0/costtrace.egg-info/entry_points.txt +2 -0
- costtrace-0.1.0/costtrace.egg-info/requires.txt +9 -0
- costtrace-0.1.0/costtrace.egg-info/top_level.txt +1 -0
- costtrace-0.1.0/pyproject.toml +48 -0
- costtrace-0.1.0/setup.cfg +4 -0
- costtrace-0.1.0/src/__init__.py +60 -0
- costtrace-0.1.0/src/analysis/__init__.py +42 -0
- costtrace-0.1.0/src/analysis/aggregate.py +149 -0
- costtrace-0.1.0/src/analysis/detect.py +245 -0
- costtrace-0.1.0/src/analysis/judge.py +141 -0
- costtrace-0.1.0/src/analysis/metrics.py +169 -0
- costtrace-0.1.0/src/analysis/store.py +89 -0
- costtrace-0.1.0/src/capture.py +119 -0
- costtrace-0.1.0/src/capture_schema.py +244 -0
- costtrace-0.1.0/src/cli.py +315 -0
- costtrace-0.1.0/src/fetch.py +69 -0
- costtrace-0.1.0/src/ingest.py +165 -0
- costtrace-0.1.0/src/paths.py +49 -0
- costtrace-0.1.0/src/pricing.py +208 -0
- costtrace-0.1.0/src/pricing.yaml +49 -0
- costtrace-0.1.0/src/providers/__init__.py +39 -0
- costtrace-0.1.0/src/providers/base.py +68 -0
- costtrace-0.1.0/src/providers/openai_agents.py +221 -0
- costtrace-0.1.0/src/schema.py +31 -0
- costtrace-0.1.0/src/trace.py +87 -0
- costtrace-0.1.0/tests/test_analysis.py +176 -0
- costtrace-0.1.0/tests/test_capture.py +125 -0
- costtrace-0.1.0/tests/test_cli.py +77 -0
- costtrace-0.1.0/tests/test_fetch.py +179 -0
- costtrace-0.1.0/tests/test_ingest.py +75 -0
- costtrace-0.1.0/tests/test_pricing.py +77 -0
costtrace-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Sanat Bhalla
|
|
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.
|
costtrace-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,422 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: costtrace
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A deterministic cost-attribution profiler for LLM traces — an X-ray for where your token spend goes.
|
|
5
|
+
Author: Sanat Bhalla
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/sanat77/CostTrace
|
|
8
|
+
Project-URL: Repository, https://github.com/sanat77/CostTrace
|
|
9
|
+
Project-URL: Issues, https://github.com/sanat77/CostTrace/issues
|
|
10
|
+
Keywords: llm,cost,finops,tokens,profiler,observability
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
16
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
17
|
+
Classifier: Topic :: System :: Monitoring
|
|
18
|
+
Requires-Python: >=3.9
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
License-File: LICENSE
|
|
21
|
+
Requires-Dist: PyYAML>=6.0
|
|
22
|
+
Requires-Dist: tiktoken>=0.7
|
|
23
|
+
Provides-Extra: dev
|
|
24
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
25
|
+
Requires-Dist: pyflakes>=3.0; extra == "dev"
|
|
26
|
+
Provides-Extra: examples
|
|
27
|
+
Requires-Dist: openai-agents>=0.18; extra == "examples"
|
|
28
|
+
Dynamic: license-file
|
|
29
|
+
|
|
30
|
+
# CostTrace
|
|
31
|
+
|
|
32
|
+
**An X-ray for your LLM token spend.**
|
|
33
|
+
|
|
34
|
+
CostTrace is a deterministic cost-attribution profiler for LLM applications. You
|
|
35
|
+
add **one line** after each agent run to capture the durable identity of your
|
|
36
|
+
billed calls. Later, CostTrace re-fetches the exact usage from the provider,
|
|
37
|
+
prices it, and tells you three things most dashboards can't:
|
|
38
|
+
|
|
39
|
+
1. **Where** your money goes — split across system prompt, conversation history,
|
|
40
|
+
retrieved context, the user's turn, and the model's output.
|
|
41
|
+
2. **What changed** over time — cost-per-turn jumps, model switches, history
|
|
42
|
+
bloat, longer outputs, cache regressions, or system-prompt edits.
|
|
43
|
+
3. **Why** — an LLM-as-judge explains the most likely driver in plain English,
|
|
44
|
+
grounded strictly in your real numbers.
|
|
45
|
+
|
|
46
|
+
> **Integrations:** today CostTrace supports the **OpenAI Agents SDK (Python)**
|
|
47
|
+
> only. The design is provider-tagged, and **support for more SDKs and providers
|
|
48
|
+
> is planned** (see [Roadmap](#roadmap)).
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## Table of contents
|
|
53
|
+
|
|
54
|
+
- [Why CostTrace](#why-costtrace)
|
|
55
|
+
- [How it's different](#how-its-different)
|
|
56
|
+
- [Install](#install)
|
|
57
|
+
- [Quickstart](#quickstart)
|
|
58
|
+
- [Core concepts](#core-concepts)
|
|
59
|
+
- [Providers](#providers)
|
|
60
|
+
- [CLI reference](#cli-reference)
|
|
61
|
+
- [Programmatic API](#programmatic-api)
|
|
62
|
+
- [How it works](#how-it-works)
|
|
63
|
+
- [The cost model](#the-cost-model)
|
|
64
|
+
- [Data, privacy & retention](#data-privacy--retention)
|
|
65
|
+
- [Configuration](#configuration)
|
|
66
|
+
- [Development](#development)
|
|
67
|
+
- [Roadmap](#roadmap)
|
|
68
|
+
- [FAQ](#faq)
|
|
69
|
+
- [License](#license)
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
## Why CostTrace
|
|
74
|
+
|
|
75
|
+
LLM bills are opaque. A provider hands you a token count per call, not dollars,
|
|
76
|
+
and never tells you *which part* of the prompt cost the money. In a multi-turn
|
|
77
|
+
agent, the conversation history is re-sent on every turn, a verbose system
|
|
78
|
+
prompt is billed on every call, and a single model swap can 100× your cost — but
|
|
79
|
+
the invoice just says "tokens."
|
|
80
|
+
|
|
81
|
+
CostTrace turns that opaque number into an **itemized, timestamped, queryable**
|
|
82
|
+
picture of your spend, so you can answer questions like:
|
|
83
|
+
|
|
84
|
+
- "What fraction of my bill is just re-sending history?"
|
|
85
|
+
- "Our costs doubled last week — what changed?"
|
|
86
|
+
- "How much cheaper is `nano` than `pro` for the *same* workload?"
|
|
87
|
+
- "Did that system-prompt edit break prompt caching?"
|
|
88
|
+
|
|
89
|
+
## How it's different
|
|
90
|
+
|
|
91
|
+
- **Capture is tiny and cheap.** We store only `{provider, conversation_id,
|
|
92
|
+
response_ids}` — no usage numbers, no message content. Everything else is
|
|
93
|
+
re-fetched on demand (provider GET reads are **not** billed).
|
|
94
|
+
- **Accounting is deterministic.** Costs are computed by us as `rate × tokens`
|
|
95
|
+
using live [LiteLLM](https://github.com/BerriAI/litellm) pricing (with a
|
|
96
|
+
bundled offline fallback) — reproducible, auditable, no guessing.
|
|
97
|
+
- **Detection is general, not single-cause.** Rather than blaming one thing,
|
|
98
|
+
CostTrace compares a recent window against a prior one across *every*
|
|
99
|
+
dimension and surfaces each signal that moved.
|
|
100
|
+
- **The LLM only explains, never computes.** The judge receives a compact,
|
|
101
|
+
number-grounded digest and is instructed to reason over it — it never invents
|
|
102
|
+
figures.
|
|
103
|
+
- **It's a profiler, not a proxy.** No gateway to route traffic through, no
|
|
104
|
+
latency added to your requests. You capture ids; analysis happens offline,
|
|
105
|
+
on demand.
|
|
106
|
+
|
|
107
|
+
## Install
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
pip install costtrace
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
This installs the importable **`CostTrace`** package and the **`costtrace`** CLI.
|
|
114
|
+
|
|
115
|
+
Set your OpenAI key via the environment or a `.env` in your project root:
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
export OPENAI_API_KEY=sk-...
|
|
119
|
+
# or, in ./.env
|
|
120
|
+
# OPENAI_API_KEY=sk-...
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Requires Python 3.9+.
|
|
124
|
+
|
|
125
|
+
## Quickstart
|
|
126
|
+
|
|
127
|
+
### 1. Capture — one line after each agent run
|
|
128
|
+
|
|
129
|
+
```python
|
|
130
|
+
from agents import Agent, OpenAIConversationsSession, Runner
|
|
131
|
+
from CostTrace import capture
|
|
132
|
+
|
|
133
|
+
session = OpenAIConversationsSession()
|
|
134
|
+
result = await Runner.run(agent, user_input, session=session)
|
|
135
|
+
|
|
136
|
+
capture(result, conversation_id, provider="openai_agents") # -> ./.costtrace/captures.json
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
`capture()` extracts the response ids from the run, validates them, de-dupes,
|
|
140
|
+
timestamps them, and appends them under the conversation id. It's idempotent and
|
|
141
|
+
prunes anything older than the retention window on every write.
|
|
142
|
+
|
|
143
|
+
`provider` is **required** — it records which backend the ids came from so they
|
|
144
|
+
can be re-fetched later. Passing an unknown/missing provider raises an error that
|
|
145
|
+
lists the [available providers](#providers).
|
|
146
|
+
|
|
147
|
+
You can also pass a plain list of ids instead of a `RunResult`:
|
|
148
|
+
|
|
149
|
+
```python
|
|
150
|
+
capture(["resp_abc", "resp_def"], conversation_id, provider="openai_agents")
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### 2. Analyze — from the CLI
|
|
154
|
+
|
|
155
|
+
```bash
|
|
156
|
+
costtrace analyze # everything captured
|
|
157
|
+
costtrace analyze --limit 5 # 5 most recent conversations
|
|
158
|
+
costtrace analyze --date 2026/07/20 # a single day
|
|
159
|
+
costtrace analyze --starting-date 2026/07/01 # from that day until today
|
|
160
|
+
costtrace analyze --conversationId conv_abc # one conversation
|
|
161
|
+
costtrace analyze --no-judge # skip the LLM explanation
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
Example output:
|
|
165
|
+
|
|
166
|
+
```
|
|
167
|
+
======================================================================
|
|
168
|
+
WHERE THE MONEY GOES
|
|
169
|
+
======================================================================
|
|
170
|
+
sessions: 3 total: $6.143908 avg cache-hit: 53%
|
|
171
|
+
output 85.9% $5.275134
|
|
172
|
+
history 14.0% $0.857593
|
|
173
|
+
system 0.1% $0.009336
|
|
174
|
+
user 0.0% $0.001846
|
|
175
|
+
retrieved 0.0% $0.000000
|
|
176
|
+
|
|
177
|
+
======================================================================
|
|
178
|
+
PER-MODEL COMPARISON
|
|
179
|
+
======================================================================
|
|
180
|
+
model cost/turn cache out tok/turn turns
|
|
181
|
+
gpt-5.4-pro $ 1.118778 0% 5376 5
|
|
182
|
+
gpt-5.4 $ 0.043458 80% 2305 12
|
|
183
|
+
gpt-5.4-nano $ 0.002377 78% 1472 12
|
|
184
|
+
|
|
185
|
+
gpt-5.4-pro costs 470.6x more per turn than gpt-5.4-nano
|
|
186
|
+
|
|
187
|
+
======================================================================
|
|
188
|
+
WHAT CHANGED (deterministic signals)
|
|
189
|
+
======================================================================
|
|
190
|
+
[1] (medium) dominant_bucket: 'output' is 86% of total spend ($5.275134)
|
|
191
|
+
|
|
192
|
+
======================================================================
|
|
193
|
+
JUDGE (LLM explanation)
|
|
194
|
+
======================================================================
|
|
195
|
+
Output dominates spend; the cost gap tracks the switch to gpt-5.4-pro
|
|
196
|
+
(higher rate + 3.6x longer outputs + 0% cache vs ~80%). Consider routing
|
|
197
|
+
routine turns to gpt-5.4-nano and capping output length.
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
## Core concepts
|
|
201
|
+
|
|
202
|
+
| Term | Meaning |
|
|
203
|
+
|------|---------|
|
|
204
|
+
| **capture** | The dev-side one-liner. Persists `{provider, conversation_id, response_ids}` — the durable *identity* of billed calls, nothing else. |
|
|
205
|
+
| **conversation** | One session, grouped by the provider's conversation id. |
|
|
206
|
+
| **call** | One billed model request == one `response_id`. |
|
|
207
|
+
| **message kind** | Each item in a call is classified as `system`, `history`, `retrieved`, `user`, or `output`. |
|
|
208
|
+
| **bucket** | Cost aggregated by message kind — the "where the money goes" split. |
|
|
209
|
+
| **session metric** | One durable, timestamped row per conversation (cost, buckets, cache rate, cost/turn, model, system-prompt fingerprint). |
|
|
210
|
+
| **provider tag** | Routes fetching to the right backend. Currently only `openai_agents`. |
|
|
211
|
+
|
|
212
|
+
## Providers
|
|
213
|
+
|
|
214
|
+
`provider` is a **required** argument to `capture()` — it records which backend
|
|
215
|
+
the response ids belong to, so CostTrace knows where to re-fetch usage from.
|
|
216
|
+
|
|
217
|
+
| Provider tag | Backend | Status |
|
|
218
|
+
|--------------|---------|--------|
|
|
219
|
+
| `openai_agents` | OpenAI Agents SDK (Python) | ✅ Available |
|
|
220
|
+
| _(more)_ | OpenAI Python SDK, Anthropic, Langfuse/Helicone exports, … | 🚧 Planned ([roadmap](#roadmap)) |
|
|
221
|
+
|
|
222
|
+
Passing an unknown or missing provider fails fast with an error that lists the
|
|
223
|
+
currently supported tags:
|
|
224
|
+
|
|
225
|
+
```python
|
|
226
|
+
>>> capture(result, conversation_id) # missing
|
|
227
|
+
CaptureSchemaError: provider is required — pass one of the supported providers:
|
|
228
|
+
['openai_agents'] (e.g. capture(result, conversation_id, provider='openai_agents'))
|
|
229
|
+
|
|
230
|
+
>>> capture(result, conversation_id, provider="anthropic") # unknown
|
|
231
|
+
CaptureSchemaError: unknown provider 'anthropic'; supported providers: ['openai_agents']
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
The available tags are also exposed programmatically:
|
|
235
|
+
|
|
236
|
+
```python
|
|
237
|
+
from CostTrace import Provider
|
|
238
|
+
Provider.values() # ['openai_agents']
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
## CLI reference
|
|
242
|
+
|
|
243
|
+
```
|
|
244
|
+
costtrace analyze [options]
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
| Option | Description |
|
|
248
|
+
|--------|-------------|
|
|
249
|
+
| `--limit N` | Analyze the N most recent conversations. |
|
|
250
|
+
| `--conversationId conv_xxx` | Analyze a single conversation (alias: `--conversation-id`). |
|
|
251
|
+
| `--date yyyy/mm/dd` | Only conversations from that day. |
|
|
252
|
+
| `--starting-date yyyy/mm/dd` | Conversations from that day until today. |
|
|
253
|
+
| `--no-judge` | Skip the LLM explanation (fully offline). |
|
|
254
|
+
|
|
255
|
+
`--date` and `--starting-date` are mutually exclusive. Run `costtrace --help` or
|
|
256
|
+
`costtrace analyze --help` for the full, self-documenting listing.
|
|
257
|
+
|
|
258
|
+
## Programmatic API
|
|
259
|
+
|
|
260
|
+
```python
|
|
261
|
+
from CostTrace import load_captures, fetch_captures, Pricer
|
|
262
|
+
|
|
263
|
+
captures = load_captures(".costtrace/captures.json") # validated ids
|
|
264
|
+
report = fetch_captures(captures) # validated traces
|
|
265
|
+
|
|
266
|
+
report.conversations # list[ConversationTrace] — ready to analyze
|
|
267
|
+
report.rejections # ids dropped (expired/invalid), each with a reason
|
|
268
|
+
report.fetched_calls # count of successfully fetched calls
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
Build your own analysis with the `analysis` module:
|
|
272
|
+
|
|
273
|
+
```python
|
|
274
|
+
from CostTrace.analysis import (
|
|
275
|
+
compute_session_metric, upsert_metrics, load_metrics,
|
|
276
|
+
summarize_overall, summarize_by_model, detect_all, judge,
|
|
277
|
+
)
|
|
278
|
+
|
|
279
|
+
pricer = Pricer.default() # live LiteLLM pricing, cached, yaml fallback
|
|
280
|
+
metrics = [compute_session_metric(c, pricer) for c in report.conversations]
|
|
281
|
+
upsert_metrics(metrics) # persist to .costtrace/metrics.jsonl
|
|
282
|
+
|
|
283
|
+
rows = load_metrics()
|
|
284
|
+
overall = summarize_overall(rows)
|
|
285
|
+
findings = detect_all(overall, rows)
|
|
286
|
+
verdict = judge(overall, findings, by_model=summarize_by_model(rows))
|
|
287
|
+
print(verdict.verdict)
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
## How it works
|
|
291
|
+
|
|
292
|
+
```
|
|
293
|
+
your app
|
|
294
|
+
│ capture(result, conversation_id)
|
|
295
|
+
▼
|
|
296
|
+
.costtrace/captures.json ── just ids, 15-day retention, auto-pruned
|
|
297
|
+
│
|
|
298
|
+
costtrace analyze:
|
|
299
|
+
▼
|
|
300
|
+
ingest ──► fetch ──► metrics ──► store ──► aggregate ──► detect ──► judge
|
|
301
|
+
(validate (re-fetch (bucket (durable (overall + (what (LLM
|
|
302
|
+
ids) usage & split + JSONL) per-model + changed) explains
|
|
303
|
+
content) cost) per-prompt) why)
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
1. **Ingest** — strict schema + id-format validation; clear errors; duplicate
|
|
307
|
+
conversations merged.
|
|
308
|
+
2. **Fetch** — for each id, pull the response (model + usage) and its input
|
|
309
|
+
items from the provider, then classify each message (system/history/
|
|
310
|
+
retrieved/user/output). **Resilient:** expired or invalid ids are recorded as
|
|
311
|
+
rejections and skipped; only auth failures are fatal.
|
|
312
|
+
3. **Metrics** — per conversation, compute total cost and split the input cost
|
|
313
|
+
across buckets by token share (estimated with a tokenizer, then scaled so the
|
|
314
|
+
parts sum to the provider's real `input_tokens`). Output cost is exact.
|
|
315
|
+
4. **Store** — append/upsert one row per conversation to
|
|
316
|
+
`.costtrace/metrics.jsonl`; refresh rows that gained turns; prune expired
|
|
317
|
+
rows. This history survives the id-retention window.
|
|
318
|
+
5. **Aggregate** — overall spend split, per-model comparison, per-prompt-version
|
|
319
|
+
grouping.
|
|
320
|
+
6. **Detect** — compare a recent window vs a prior one and flag every dimension
|
|
321
|
+
that moved: `cost_increase`, `model_shift`, `bucket_shift`, `cache_drop`,
|
|
322
|
+
`output_growth`, `prompt_change`, plus a static `dominant_bucket`.
|
|
323
|
+
7. **Judge** — assemble a compact, grounded digest and ask an LLM to name the
|
|
324
|
+
likely cause and a fix. It reasons over the signals; it does not compute
|
|
325
|
+
numbers.
|
|
326
|
+
|
|
327
|
+
## The cost model
|
|
328
|
+
|
|
329
|
+
Providers return **tokens, not dollars** — and never a per-call price. CostTrace
|
|
330
|
+
computes cost itself:
|
|
331
|
+
|
|
332
|
+
```
|
|
333
|
+
cost = uncached_input_tokens × input_rate
|
|
334
|
+
+ cached_input_tokens × cache_read_rate
|
|
335
|
+
+ output_tokens × output_rate
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
Rates come from LiteLLM's community-maintained pricing table (≈3,000 models),
|
|
339
|
+
fetched and cached locally, with a small bundled `pricing.yaml` as an offline
|
|
340
|
+
fallback. Because accounting is pure arithmetic on real token counts, results
|
|
341
|
+
are reproducible and auditable.
|
|
342
|
+
|
|
343
|
+
The **bucket split** for the input side is an approximation: the provider bills
|
|
344
|
+
one `input_tokens` number per call, so CostTrace estimates each message's share
|
|
345
|
+
with a tokenizer and scales those shares to sum exactly to the real input cost.
|
|
346
|
+
The **output** bucket is exact.
|
|
347
|
+
|
|
348
|
+
## Data, privacy & retention
|
|
349
|
+
|
|
350
|
+
- **What's stored on disk:** only response/conversation **ids** in
|
|
351
|
+
`.costtrace/captures.json`, plus computed **metrics** (numbers, model names,
|
|
352
|
+
and a hash + copy of the system prompt) in `.costtrace/metrics.jsonl`, plus a
|
|
353
|
+
cached pricing table. No user/assistant message content is persisted.
|
|
354
|
+
- **Where:** a project-local `.costtrace/` directory, discovered by walking up
|
|
355
|
+
to the nearest `.git` / `pyproject.toml` (like git finds its root), so
|
|
356
|
+
`capture()` and the CLI always agree regardless of your working directory.
|
|
357
|
+
Override with `COSTTRACE_HOME`.
|
|
358
|
+
- **Retention:** ids are only re-fetchable from the provider for a limited
|
|
359
|
+
window, so captures older than **15 days** are pruned automatically on write —
|
|
360
|
+
you lose nothing that could still be fetched.
|
|
361
|
+
- **The judge digest** contains aggregate numbers and (only when a prompt change
|
|
362
|
+
is detected) the system-prompt text — never raw conversation content.
|
|
363
|
+
- **Secrets:** your API key is read from the environment or a `.env`; it is
|
|
364
|
+
never written to the data directory.
|
|
365
|
+
|
|
366
|
+
## Configuration
|
|
367
|
+
|
|
368
|
+
| Variable | Purpose |
|
|
369
|
+
|----------|---------|
|
|
370
|
+
| `OPENAI_API_KEY` | Required for fetching usage and for the LLM judge. |
|
|
371
|
+
| `COSTTRACE_HOME` | Override the `.costtrace/` data directory location. |
|
|
372
|
+
|
|
373
|
+
## Development
|
|
374
|
+
|
|
375
|
+
```bash
|
|
376
|
+
git clone https://github.com/sanat77/CostTrace.git
|
|
377
|
+
cd CostTrace
|
|
378
|
+
python -m venv .venv && source .venv/bin/activate
|
|
379
|
+
pip install -e .[dev]
|
|
380
|
+
|
|
381
|
+
python -m pytest -q # run the test suite
|
|
382
|
+
python -m pyflakes src tests conftest.py # lint
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
The package source lives in `src/` and is mapped to the import name `CostTrace`
|
|
386
|
+
via `package-dir` in `pyproject.toml`, so the built wheel ships `CostTrace/` and
|
|
387
|
+
`pip install` / `import CostTrace` are unaffected by the on-disk layout. A
|
|
388
|
+
`conftest.py` replicates that mapping so tests import from the source tree
|
|
389
|
+
without installing.
|
|
390
|
+
|
|
391
|
+
## Roadmap
|
|
392
|
+
|
|
393
|
+
CostTrace v1 targets the **OpenAI Agents SDK (Python)** (`provider =
|
|
394
|
+
"openai_agents"`). Planned next:
|
|
395
|
+
|
|
396
|
+
- **More Python LLM SDKs / providers** (e.g. the OpenAI Python SDK directly,
|
|
397
|
+
Anthropic, and others) behind the same provider-tagged architecture.
|
|
398
|
+
- **Ingesting exports** from existing observability tools (e.g. Langfuse /
|
|
399
|
+
Helicone) as additional provider tags.
|
|
400
|
+
- **Longer retention & trend history** as an opt-in tier.
|
|
401
|
+
- **Richer detectors** and per-tenant / per-feature attribution.
|
|
402
|
+
|
|
403
|
+
Contributions and provider requests are welcome — open an issue.
|
|
404
|
+
|
|
405
|
+
## FAQ
|
|
406
|
+
|
|
407
|
+
**Does this add latency to my app?** No. You only save ids at runtime; all
|
|
408
|
+
fetching and analysis happens later, offline.
|
|
409
|
+
|
|
410
|
+
**Do the re-fetches cost money?** No — provider GET reads of past responses are
|
|
411
|
+
not billed. Only your original generations were.
|
|
412
|
+
|
|
413
|
+
**Why can't I analyze conversations from last month?** Ids expire at the
|
|
414
|
+
provider (~retention window), so they can't be re-fetched. The computed metrics
|
|
415
|
+
you already stored remain, but new deep analysis needs live ids.
|
|
416
|
+
|
|
417
|
+
**Distribution name vs import name?** You `pip install costtrace` (lowercase)
|
|
418
|
+
and `import CostTrace` (capitalized) — standard PyPI convention.
|
|
419
|
+
|
|
420
|
+
## License
|
|
421
|
+
|
|
422
|
+
MIT — see [LICENSE](LICENSE).
|