costplan 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.
costplan-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Meenal Asesh
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,229 @@
1
+ Metadata-Version: 2.4
2
+ Name: costplan
3
+ Version: 0.1.0
4
+ Summary: How much will my agent cost? Plan AI agent budgets, caps and spend from a handful of pilot runs
5
+ Author: Meenal Asesh
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/ai-with-md/costplan
8
+ Project-URL: Calculator, https://ai-with-md.github.io/costplan/
9
+ Project-URL: Issues, https://github.com/ai-with-md/costplan/issues
10
+ Keywords: llm,ai agents,cost,budget,token cost,llm cost,cost per task,statistics,forecasting
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Operating System :: OS Independent
13
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
14
+ Requires-Python: >=3.9
15
+ Description-Content-Type: text/markdown
16
+ License-File: LICENSE
17
+ Provides-Extra: dev
18
+ Requires-Dist: pytest; extra == "dev"
19
+ Dynamic: license-file
20
+
21
+ # costplan
22
+
23
+ **How much will my agent cost?** Plan AI agent budgets, caps and spend from a handful of pilot runs, with a [calculator that runs in your browser](https://ai-with-md.github.io/costplan/).
24
+
25
+ [![PyPI](https://img.shields.io/pypi/v/costplan)](https://pypi.org/project/costplan/)
26
+ [![tests](https://github.com/ai-with-md/costplan/actions/workflows/tests.yml/badge.svg)](https://github.com/ai-with-md/costplan/actions/workflows/tests.yml)
27
+ ![Python 3.9+](https://img.shields.io/badge/python-3.9%2B-blue)
28
+ ![License: MIT](https://img.shields.io/badge/license-MIT-green)
29
+
30
+ [![The costplan calculator: run costs, a budget cap and the long tail of expensive runs](https://raw.githubusercontent.com/ai-with-md/costplan/main/docs/calculator.png)](https://ai-with-md.github.io/costplan/)
31
+
32
+ Agent costs are lopsided. Most runs take a few tool calls and cost a few cents. A few get stuck in a loop, retry, or read a long document, and cost ten times more. In the example pilot above, the typical run costs $0.11 and the most expensive one $1.43. That makes averages slow to settle, and it makes "what budget cap should I set?" a question about the rare expensive runs, not the typical ones.
33
+
34
+ costplan answers the planning questions from a pilot:
35
+
36
+ 1. What does a run really cost, and what does a **finished task** cost?
37
+ 2. What per-run budget cap should I set, so it almost never stops a normal run?
38
+ 3. How many pilot runs do I need before I can trust those numbers?
39
+ 4. What will it cost at 10,000 runs a day?
40
+ 5. Is the cheaper model actually cheaper per finished task?
41
+
42
+ ## The number most people get wrong
43
+
44
+ To set a budget cap that stops at most 2% of normal runs, and be 95% sure of it, you need **149 pilot runs**. Fewer runs simply cannot rule out a rare expensive run that a tighter cap would cut off. Set the cap at the most expensive pilot run and this is the best you can promise:
45
+
46
+ | Pilot runs | Cap stops at most |
47
+ |---|---|
48
+ | 20 | 13.9% of normal runs |
49
+ | 30 | 9.5% |
50
+ | 50 | 5.8% |
51
+ | 100 | 3.0% |
52
+ | 149 | 2.0% |
53
+ | 300 | 1.0% |
54
+
55
+ These numbers hold whatever the shape of your costs. costplan tells you what your current pilot can promise, and how many more runs the promise you want would take.
56
+
57
+ ## Try it
58
+
59
+ **In the browser:** [ai-with-md.github.io/costplan](https://ai-with-md.github.io/costplan/). Paste your run costs, nothing to install.
60
+
61
+ **In Python:**
62
+
63
+ ```bash
64
+ pip install costplan
65
+ ```
66
+
67
+ No dependencies beyond the standard library. Python 3.9 or newer.
68
+
69
+ ## Use
70
+
71
+ ### Reading your exports
72
+
73
+ `load_runs` reads a CSV, JSON or JSON Lines export from your tracing tool or provider and finds the cost, token, success and run columns by their common names (`cost`, `total_cost`, `totalCost`, `calculatedTotalCost`, `prompt_tokens`, `input_tokens`, `usage.input`, `status`, `error`, `trace_id`, `traceId` and others).
74
+
75
+ ```python
76
+ import costplan as cp
77
+
78
+ runs = cp.load_runs("calls_export.jsonl", group_by="trace_id")
79
+ print(runs.report())
80
+ # Read 318 rows into 40 runs (cost from 'cost', status from 'status', runs from 'trace_id').
81
+
82
+ costs, successes = runs.costs, runs.successes
83
+ ```
84
+
85
+ - **One row per model call?** Pass `group_by` with the column that names the run (a trace or session id). Calls are added up into runs, and a run counts as failed if any of its calls failed.
86
+ - **One row per run, already totalled?** Load it as it is. Grouping it again would count costs twice.
87
+ - **No cost column, or calls your tool could not price?** Pass `input_price` and `output_price` in dollars per million tokens and costplan prices them from the token columns. costplan ships no price table, because prices change; take them from your provider's pricing page or a list such as [tokencost](https://github.com/AgentOps-AI/tokencost).
88
+ - **Names not found?** Pass them yourself: `cost="spend"`, `success="passed"`, and dotted names for nested JSON such as `"usage.prompt_tokens"`.
89
+
90
+ The column guesses are tested on files shaped like LangSmith run exports and Langfuse observation exports, using the field names in their documentation. Check `runs.report()` the first time you load a new kind of file.
91
+
92
+ `run_cost` does the same pricing for a single call:
93
+
94
+ ```python
95
+ cp.run_cost(input_tokens=12_000, output_tokens=800, input_price=3.0, output_price=15.0)
96
+ # 0.048
97
+ ```
98
+
99
+ ### 1. What does a run cost?
100
+
101
+ ```python
102
+ print(cp.summarize(costs, successes))
103
+ # 60 runs.
104
+ # Average cost per run: $0.1577 (95% interval $0.1160 to $0.2167)
105
+ # Typical run (median): $0.1100 (95% interval $0.0920 to $0.1371)
106
+ # 1 in 20 runs costs more than: $0.3272 (95% interval needs more runs)
107
+ # Most expensive run: $1.43
108
+ # Success rate: 85.0% (95% interval 73.9% to 91.9%)
109
+ # Cost per successful task: $0.1855 (95% interval $0.1308 to $0.2677)
110
+ ```
111
+
112
+ The cost per successful task is the number that matters for a product: failed runs cost money too.
113
+
114
+ ### 2. What cap should I set?
115
+
116
+ ```python
117
+ print(cp.recommend_cap(costs, stop_rate=0.02))
118
+ # Goal: a cap that stops at most 2.0% of normal runs (95% confidence), from 60 pilot runs.
119
+ # Not enough runs to promise that yet: it takes 149.
120
+ # With 60 runs, a cap at the most expensive run ($1.43) can only promise to stop at most 4.9% of runs.
121
+ # Best single guess for the 98th percentile: $0.7737 (a guess, not a promise).
122
+ ```
123
+
124
+ With enough runs, it returns a recommended cap. That cap is an upper confidence bound on the cost percentile, so it holds even if the pilot happened to miss some expensive runs. Enforce it with your agent framework, your own code, or a library such as [AgentBudget](https://github.com/AgentBudget/agentbudget).
125
+
126
+ ### 3. How many pilot runs do I need?
127
+
128
+ ```python
129
+ cp.runs_needed_for_cap(stop_rate=0.02) # 149
130
+ cp.guaranteed_stop_rate(n_runs=30) # 0.095
131
+ cp.pilot_runs_for_average(spread=1.0, margin=0.10) # 385 runs to pin the average within 10%
132
+ ```
133
+
134
+ `spread` is the standard deviation divided by the average. Agent costs often have a spread near 1 or above because of the runaway runs, which is why averages need hundreds of runs to settle.
135
+
136
+ ### 4. What will it cost at scale?
137
+
138
+ ```python
139
+ print(cp.project_spend(costs, runs=300_000))
140
+ # 300,000 runs: about $47,301.75 (95% range $31,765.55 to $62,837.95).
141
+ ```
142
+
143
+ At high volume the run to run variation averages out, and nearly all of the range comes from how well the pilot pinned down the average. A bigger pilot narrows it more than anything else.
144
+
145
+ ### 5. Is the cheaper model cheaper per finished task?
146
+
147
+ ```python
148
+ print(cp.compare_cost_per_success(large_costs, large_ok, small_costs, small_ok, names=("large model", "small model")))
149
+ # Cost per successful task: large model $0.1855, small model $0.2197.
150
+ # small model costs 1.18 times as much as large model per success (95% interval 0.71 to 2.02, separate pilots). The difference could be noise.
151
+ ```
152
+
153
+ In this example the small model is about 30% cheaper per run, but it fails and loops more often, so it probably costs more per finished task. With 60 runs each, costplan correctly says the difference is not yet certain.
154
+
155
+ **Run both models on the same tasks if you can.** Hard tasks are expensive for every model, so comparing task by task removes that variation. With `paired=True` the same 60 tasks give an answer that separate pilots cannot:
156
+
157
+ ```python
158
+ cp.compare_cost_per_success(large_costs, large_ok, small_costs, small_ok, names=("large model", "small model"))
159
+ # small model costs 0.73 times as much as large model per success (95% interval 0.49 to 1.08, separate pilots). The difference could be noise.
160
+
161
+ cp.compare_cost_per_success(large_costs, large_ok, small_costs, small_ok, names=("large model", "small model"), paired=True)
162
+ # small model costs 0.73 times as much as large model per success (95% interval 0.61 to 0.88, same tasks, compared task by task). small model is reliably cheaper per success.
163
+ ```
164
+
165
+ In simulation, pairing gives intervals about half as wide from the same number of runs.
166
+
167
+ ## Command line
168
+
169
+ ```bash
170
+ costplan summary runs.csv
171
+ costplan cap runs.csv --stop-rate 0.02
172
+ costplan project runs.csv --runs 300000
173
+ costplan compare large.csv small.csv --names "large model" "small model"
174
+ costplan compare large.csv small.csv --paired --task-column task_id
175
+ costplan summary calls_export.jsonl --group-by trace_id -v
176
+ costplan pilot --stop-rate 0.02 --spread 1.0
177
+ ```
178
+
179
+ Files can be CSV, JSON or JSON Lines, with the same automatic column finding as `load_runs`. Use `--cost-column`, `--success-column` and similar options when the guess is wrong, `--input-price` and `--output-price` for files with tokens but no costs, and `-v` to see which columns were used. Try it on the simulated files in [`examples/`](https://github.com/ai-with-md/costplan/tree/main/examples).
180
+
181
+ ## How the numbers are checked
182
+
183
+ The test suite simulates thousands of pilots with lopsided costs where the truth is known, and checks each promise:
184
+
185
+ | Promise | Held in simulation |
186
+ |---|---|
187
+ | The recommended cap stops at most 2% of runs | 98.5% of pilots (95% promised) |
188
+ | A cap at the most expensive of 149 runs stops at most 2% | 94.8% |
189
+ | The interval for cost per successful task covers the truth | 93% |
190
+ | The spend projection covers the real bill | 92% |
191
+ | The interval for the average cost covers the truth | 92% |
192
+ | The paired comparison interval covers the true ratio | 96% |
193
+ | Paired vs separate pilots, same number of runs | interval about half as wide |
194
+
195
+ The caps and quantiles make no assumption about the shape of costs and keep their promise. The intervals for averages run slightly short of 95% on lopsided costs, which is typical for the bootstrap; treat them as a little optimistic, especially below about 30 runs. See [`tests/test_simulation.py`](https://github.com/ai-with-md/costplan/blob/main/tests/test_simulation.py).
196
+
197
+ ## How it compares
198
+
199
+ - **[AgentBudget](https://github.com/AgentBudget/agentbudget)** and similar tools enforce a dollar limit while an agent runs. costplan tells you what limit to set, and what you will spend. They work well together.
200
+ - **Price calculators and price lists** such as [tokencost](https://github.com/AgentOps-AI/tokencost) cost out a single request. costplan works on whole runs, including the expensive tail, retries and failures.
201
+ - **Tracing tools** (Langfuse, LangSmith and others) record what runs cost. costplan turns those records into planning numbers with honest uncertainty.
202
+
203
+ ## Limits
204
+
205
+ - The pilot must look like real traffic: the same prompts, tools, models and mix of requests. Change any of them and run a new pilot.
206
+ - Caps and projections assume future runs behave like pilot runs. A new kind of request that triggers long loops will not show up in a pilot that never saw it.
207
+ - The spend projection uses a normal approximation, which is a little narrow for very lopsided costs.
208
+
209
+ ## More tools
210
+
211
+ Small, free tools for building and checking AI systems:
212
+
213
+ - **[sturdytools](https://github.com/ai-with-md/sturdytools)**: retries, timeouts, rate limits and circuit breakers for the tools an AI agent calls.
214
+ - **[evalsize](https://github.com/ai-with-md/evalsize)**: how many eval examples you need before a difference between prompts or models means anything.
215
+
216
+ ## Development
217
+
218
+ ```bash
219
+ python -m venv .venv
220
+ source .venv/bin/activate
221
+ pip install -e ".[dev]"
222
+ pytest
223
+ ```
224
+
225
+ The calculator is a [marimo](https://marimo.io) notebook in [`app/calculator.py`](https://github.com/ai-with-md/costplan/blob/main/app/calculator.py), published to GitHub Pages as a WebAssembly app on every push to `main`.
226
+
227
+ ## License
228
+
229
+ MIT
@@ -0,0 +1,209 @@
1
+ # costplan
2
+
3
+ **How much will my agent cost?** Plan AI agent budgets, caps and spend from a handful of pilot runs, with a [calculator that runs in your browser](https://ai-with-md.github.io/costplan/).
4
+
5
+ [![PyPI](https://img.shields.io/pypi/v/costplan)](https://pypi.org/project/costplan/)
6
+ [![tests](https://github.com/ai-with-md/costplan/actions/workflows/tests.yml/badge.svg)](https://github.com/ai-with-md/costplan/actions/workflows/tests.yml)
7
+ ![Python 3.9+](https://img.shields.io/badge/python-3.9%2B-blue)
8
+ ![License: MIT](https://img.shields.io/badge/license-MIT-green)
9
+
10
+ [![The costplan calculator: run costs, a budget cap and the long tail of expensive runs](https://raw.githubusercontent.com/ai-with-md/costplan/main/docs/calculator.png)](https://ai-with-md.github.io/costplan/)
11
+
12
+ Agent costs are lopsided. Most runs take a few tool calls and cost a few cents. A few get stuck in a loop, retry, or read a long document, and cost ten times more. In the example pilot above, the typical run costs $0.11 and the most expensive one $1.43. That makes averages slow to settle, and it makes "what budget cap should I set?" a question about the rare expensive runs, not the typical ones.
13
+
14
+ costplan answers the planning questions from a pilot:
15
+
16
+ 1. What does a run really cost, and what does a **finished task** cost?
17
+ 2. What per-run budget cap should I set, so it almost never stops a normal run?
18
+ 3. How many pilot runs do I need before I can trust those numbers?
19
+ 4. What will it cost at 10,000 runs a day?
20
+ 5. Is the cheaper model actually cheaper per finished task?
21
+
22
+ ## The number most people get wrong
23
+
24
+ To set a budget cap that stops at most 2% of normal runs, and be 95% sure of it, you need **149 pilot runs**. Fewer runs simply cannot rule out a rare expensive run that a tighter cap would cut off. Set the cap at the most expensive pilot run and this is the best you can promise:
25
+
26
+ | Pilot runs | Cap stops at most |
27
+ |---|---|
28
+ | 20 | 13.9% of normal runs |
29
+ | 30 | 9.5% |
30
+ | 50 | 5.8% |
31
+ | 100 | 3.0% |
32
+ | 149 | 2.0% |
33
+ | 300 | 1.0% |
34
+
35
+ These numbers hold whatever the shape of your costs. costplan tells you what your current pilot can promise, and how many more runs the promise you want would take.
36
+
37
+ ## Try it
38
+
39
+ **In the browser:** [ai-with-md.github.io/costplan](https://ai-with-md.github.io/costplan/). Paste your run costs, nothing to install.
40
+
41
+ **In Python:**
42
+
43
+ ```bash
44
+ pip install costplan
45
+ ```
46
+
47
+ No dependencies beyond the standard library. Python 3.9 or newer.
48
+
49
+ ## Use
50
+
51
+ ### Reading your exports
52
+
53
+ `load_runs` reads a CSV, JSON or JSON Lines export from your tracing tool or provider and finds the cost, token, success and run columns by their common names (`cost`, `total_cost`, `totalCost`, `calculatedTotalCost`, `prompt_tokens`, `input_tokens`, `usage.input`, `status`, `error`, `trace_id`, `traceId` and others).
54
+
55
+ ```python
56
+ import costplan as cp
57
+
58
+ runs = cp.load_runs("calls_export.jsonl", group_by="trace_id")
59
+ print(runs.report())
60
+ # Read 318 rows into 40 runs (cost from 'cost', status from 'status', runs from 'trace_id').
61
+
62
+ costs, successes = runs.costs, runs.successes
63
+ ```
64
+
65
+ - **One row per model call?** Pass `group_by` with the column that names the run (a trace or session id). Calls are added up into runs, and a run counts as failed if any of its calls failed.
66
+ - **One row per run, already totalled?** Load it as it is. Grouping it again would count costs twice.
67
+ - **No cost column, or calls your tool could not price?** Pass `input_price` and `output_price` in dollars per million tokens and costplan prices them from the token columns. costplan ships no price table, because prices change; take them from your provider's pricing page or a list such as [tokencost](https://github.com/AgentOps-AI/tokencost).
68
+ - **Names not found?** Pass them yourself: `cost="spend"`, `success="passed"`, and dotted names for nested JSON such as `"usage.prompt_tokens"`.
69
+
70
+ The column guesses are tested on files shaped like LangSmith run exports and Langfuse observation exports, using the field names in their documentation. Check `runs.report()` the first time you load a new kind of file.
71
+
72
+ `run_cost` does the same pricing for a single call:
73
+
74
+ ```python
75
+ cp.run_cost(input_tokens=12_000, output_tokens=800, input_price=3.0, output_price=15.0)
76
+ # 0.048
77
+ ```
78
+
79
+ ### 1. What does a run cost?
80
+
81
+ ```python
82
+ print(cp.summarize(costs, successes))
83
+ # 60 runs.
84
+ # Average cost per run: $0.1577 (95% interval $0.1160 to $0.2167)
85
+ # Typical run (median): $0.1100 (95% interval $0.0920 to $0.1371)
86
+ # 1 in 20 runs costs more than: $0.3272 (95% interval needs more runs)
87
+ # Most expensive run: $1.43
88
+ # Success rate: 85.0% (95% interval 73.9% to 91.9%)
89
+ # Cost per successful task: $0.1855 (95% interval $0.1308 to $0.2677)
90
+ ```
91
+
92
+ The cost per successful task is the number that matters for a product: failed runs cost money too.
93
+
94
+ ### 2. What cap should I set?
95
+
96
+ ```python
97
+ print(cp.recommend_cap(costs, stop_rate=0.02))
98
+ # Goal: a cap that stops at most 2.0% of normal runs (95% confidence), from 60 pilot runs.
99
+ # Not enough runs to promise that yet: it takes 149.
100
+ # With 60 runs, a cap at the most expensive run ($1.43) can only promise to stop at most 4.9% of runs.
101
+ # Best single guess for the 98th percentile: $0.7737 (a guess, not a promise).
102
+ ```
103
+
104
+ With enough runs, it returns a recommended cap. That cap is an upper confidence bound on the cost percentile, so it holds even if the pilot happened to miss some expensive runs. Enforce it with your agent framework, your own code, or a library such as [AgentBudget](https://github.com/AgentBudget/agentbudget).
105
+
106
+ ### 3. How many pilot runs do I need?
107
+
108
+ ```python
109
+ cp.runs_needed_for_cap(stop_rate=0.02) # 149
110
+ cp.guaranteed_stop_rate(n_runs=30) # 0.095
111
+ cp.pilot_runs_for_average(spread=1.0, margin=0.10) # 385 runs to pin the average within 10%
112
+ ```
113
+
114
+ `spread` is the standard deviation divided by the average. Agent costs often have a spread near 1 or above because of the runaway runs, which is why averages need hundreds of runs to settle.
115
+
116
+ ### 4. What will it cost at scale?
117
+
118
+ ```python
119
+ print(cp.project_spend(costs, runs=300_000))
120
+ # 300,000 runs: about $47,301.75 (95% range $31,765.55 to $62,837.95).
121
+ ```
122
+
123
+ At high volume the run to run variation averages out, and nearly all of the range comes from how well the pilot pinned down the average. A bigger pilot narrows it more than anything else.
124
+
125
+ ### 5. Is the cheaper model cheaper per finished task?
126
+
127
+ ```python
128
+ print(cp.compare_cost_per_success(large_costs, large_ok, small_costs, small_ok, names=("large model", "small model")))
129
+ # Cost per successful task: large model $0.1855, small model $0.2197.
130
+ # small model costs 1.18 times as much as large model per success (95% interval 0.71 to 2.02, separate pilots). The difference could be noise.
131
+ ```
132
+
133
+ In this example the small model is about 30% cheaper per run, but it fails and loops more often, so it probably costs more per finished task. With 60 runs each, costplan correctly says the difference is not yet certain.
134
+
135
+ **Run both models on the same tasks if you can.** Hard tasks are expensive for every model, so comparing task by task removes that variation. With `paired=True` the same 60 tasks give an answer that separate pilots cannot:
136
+
137
+ ```python
138
+ cp.compare_cost_per_success(large_costs, large_ok, small_costs, small_ok, names=("large model", "small model"))
139
+ # small model costs 0.73 times as much as large model per success (95% interval 0.49 to 1.08, separate pilots). The difference could be noise.
140
+
141
+ cp.compare_cost_per_success(large_costs, large_ok, small_costs, small_ok, names=("large model", "small model"), paired=True)
142
+ # small model costs 0.73 times as much as large model per success (95% interval 0.61 to 0.88, same tasks, compared task by task). small model is reliably cheaper per success.
143
+ ```
144
+
145
+ In simulation, pairing gives intervals about half as wide from the same number of runs.
146
+
147
+ ## Command line
148
+
149
+ ```bash
150
+ costplan summary runs.csv
151
+ costplan cap runs.csv --stop-rate 0.02
152
+ costplan project runs.csv --runs 300000
153
+ costplan compare large.csv small.csv --names "large model" "small model"
154
+ costplan compare large.csv small.csv --paired --task-column task_id
155
+ costplan summary calls_export.jsonl --group-by trace_id -v
156
+ costplan pilot --stop-rate 0.02 --spread 1.0
157
+ ```
158
+
159
+ Files can be CSV, JSON or JSON Lines, with the same automatic column finding as `load_runs`. Use `--cost-column`, `--success-column` and similar options when the guess is wrong, `--input-price` and `--output-price` for files with tokens but no costs, and `-v` to see which columns were used. Try it on the simulated files in [`examples/`](https://github.com/ai-with-md/costplan/tree/main/examples).
160
+
161
+ ## How the numbers are checked
162
+
163
+ The test suite simulates thousands of pilots with lopsided costs where the truth is known, and checks each promise:
164
+
165
+ | Promise | Held in simulation |
166
+ |---|---|
167
+ | The recommended cap stops at most 2% of runs | 98.5% of pilots (95% promised) |
168
+ | A cap at the most expensive of 149 runs stops at most 2% | 94.8% |
169
+ | The interval for cost per successful task covers the truth | 93% |
170
+ | The spend projection covers the real bill | 92% |
171
+ | The interval for the average cost covers the truth | 92% |
172
+ | The paired comparison interval covers the true ratio | 96% |
173
+ | Paired vs separate pilots, same number of runs | interval about half as wide |
174
+
175
+ The caps and quantiles make no assumption about the shape of costs and keep their promise. The intervals for averages run slightly short of 95% on lopsided costs, which is typical for the bootstrap; treat them as a little optimistic, especially below about 30 runs. See [`tests/test_simulation.py`](https://github.com/ai-with-md/costplan/blob/main/tests/test_simulation.py).
176
+
177
+ ## How it compares
178
+
179
+ - **[AgentBudget](https://github.com/AgentBudget/agentbudget)** and similar tools enforce a dollar limit while an agent runs. costplan tells you what limit to set, and what you will spend. They work well together.
180
+ - **Price calculators and price lists** such as [tokencost](https://github.com/AgentOps-AI/tokencost) cost out a single request. costplan works on whole runs, including the expensive tail, retries and failures.
181
+ - **Tracing tools** (Langfuse, LangSmith and others) record what runs cost. costplan turns those records into planning numbers with honest uncertainty.
182
+
183
+ ## Limits
184
+
185
+ - The pilot must look like real traffic: the same prompts, tools, models and mix of requests. Change any of them and run a new pilot.
186
+ - Caps and projections assume future runs behave like pilot runs. A new kind of request that triggers long loops will not show up in a pilot that never saw it.
187
+ - The spend projection uses a normal approximation, which is a little narrow for very lopsided costs.
188
+
189
+ ## More tools
190
+
191
+ Small, free tools for building and checking AI systems:
192
+
193
+ - **[sturdytools](https://github.com/ai-with-md/sturdytools)**: retries, timeouts, rate limits and circuit breakers for the tools an AI agent calls.
194
+ - **[evalsize](https://github.com/ai-with-md/evalsize)**: how many eval examples you need before a difference between prompts or models means anything.
195
+
196
+ ## Development
197
+
198
+ ```bash
199
+ python -m venv .venv
200
+ source .venv/bin/activate
201
+ pip install -e ".[dev]"
202
+ pytest
203
+ ```
204
+
205
+ The calculator is a [marimo](https://marimo.io) notebook in [`app/calculator.py`](https://github.com/ai-with-md/costplan/blob/main/app/calculator.py), published to GitHub Pages as a WebAssembly app on every push to `main`.
206
+
207
+ ## License
208
+
209
+ MIT
@@ -0,0 +1,37 @@
1
+ [build-system]
2
+ requires = ["setuptools>=77"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "costplan"
7
+ version = "0.1.0"
8
+ description = "How much will my agent cost? Plan AI agent budgets, caps and spend from a handful of pilot runs"
9
+ readme = "README.md"
10
+ license = "MIT"
11
+ authors = [{name = "Meenal Asesh"}]
12
+ requires-python = ">=3.9"
13
+ dependencies = []
14
+ keywords = ["llm", "ai agents", "cost", "budget", "token cost", "llm cost", "cost per task", "statistics", "forecasting"]
15
+ classifiers = [
16
+ "Programming Language :: Python :: 3",
17
+ "Operating System :: OS Independent",
18
+ "Topic :: Scientific/Engineering :: Artificial Intelligence",
19
+ ]
20
+
21
+ [project.optional-dependencies]
22
+ dev = ["pytest"]
23
+
24
+ [project.scripts]
25
+ costplan = "costplan.cli:main"
26
+
27
+ [project.urls]
28
+ Homepage = "https://github.com/ai-with-md/costplan"
29
+ Calculator = "https://ai-with-md.github.io/costplan/"
30
+ Issues = "https://github.com/ai-with-md/costplan/issues"
31
+
32
+ [tool.setuptools.packages.find]
33
+ where = ["src"]
34
+
35
+ [tool.pytest.ini_options]
36
+ testpaths = ["tests", "src"]
37
+ addopts = "--doctest-modules"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,36 @@
1
+ """How much will my agent cost? Plan agent budgets from a handful of pilot runs."""
2
+
3
+ from .loading import LoadedRuns, load_runs
4
+ from .planning import (
5
+ CapAdvice,
6
+ CostComparison,
7
+ CostSummary,
8
+ SpendProjection,
9
+ compare_cost_per_success,
10
+ guaranteed_stop_rate,
11
+ pilot_runs_for_average,
12
+ project_spend,
13
+ recommend_cap,
14
+ run_cost,
15
+ runs_needed_for_cap,
16
+ summarize,
17
+ )
18
+
19
+ __all__ = [
20
+ "load_runs",
21
+ "LoadedRuns",
22
+ "run_cost",
23
+ "summarize",
24
+ "recommend_cap",
25
+ "runs_needed_for_cap",
26
+ "guaranteed_stop_rate",
27
+ "pilot_runs_for_average",
28
+ "project_spend",
29
+ "compare_cost_per_success",
30
+ "CostSummary",
31
+ "CapAdvice",
32
+ "SpendProjection",
33
+ "CostComparison",
34
+ ]
35
+
36
+ __version__ = "0.1.0"
@@ -0,0 +1,109 @@
1
+ """Small statistics helpers built on the standard library only."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import math
6
+ import random
7
+ from statistics import NormalDist
8
+ from typing import Callable, List, Optional, Sequence, Tuple
9
+
10
+ _NORMAL = NormalDist()
11
+
12
+
13
+ def check_level(value: float, name: str) -> None:
14
+ if not 0 < value < 1:
15
+ raise ValueError(f"{name} must be strictly between 0 and 1, got {value!r}")
16
+
17
+
18
+ def z_two_sided(confidence: float) -> float:
19
+ return _NORMAL.inv_cdf(1 - (1 - confidence) / 2)
20
+
21
+
22
+ def wilson(successes: int, n: int, confidence: float) -> Tuple[float, float]:
23
+ z = z_two_sided(confidence)
24
+ p = successes / n
25
+ denom = 1 + z * z / n
26
+ centre = (p + z * z / (2 * n)) / denom
27
+ half = z / denom * math.sqrt(p * (1 - p) / n + z * z / (4 * n * n))
28
+ return max(0.0, centre - half), min(1.0, centre + half)
29
+
30
+
31
+ def binom_cdf_table(n: int, p: float) -> List[float]:
32
+ """[P(X <= k) for k = 0..n] for X ~ Binomial(n, p), computed in log space."""
33
+ if p <= 0:
34
+ return [1.0] * (n + 1)
35
+ if p >= 1:
36
+ return [0.0] * n + [1.0]
37
+ log_p, log_q = math.log(p), math.log1p(-p)
38
+ log_n = math.lgamma(n + 1)
39
+ table, total = [], 0.0
40
+ for i in range(n + 1):
41
+ total += math.exp(log_n - math.lgamma(i + 1) - math.lgamma(n - i + 1) + i * log_p + (n - i) * log_q)
42
+ table.append(min(1.0, total))
43
+ return table
44
+
45
+
46
+ def quantile(sorted_values: Sequence[float], p: float) -> float:
47
+ """Linear interpolation quantile (the usual default in numpy and pandas)."""
48
+ n = len(sorted_values)
49
+ if n == 1:
50
+ return sorted_values[0]
51
+ pos = p * (n - 1)
52
+ lo = math.floor(pos)
53
+ hi = min(lo + 1, n - 1)
54
+ return sorted_values[lo] + (sorted_values[hi] - sorted_values[lo]) * (pos - lo)
55
+
56
+
57
+ def quantile_interval(
58
+ sorted_values: Sequence[float], p: float, confidence: float
59
+ ) -> Tuple[Optional[float], Optional[float]]:
60
+ """Distribution-free interval for the p-th quantile from order statistics.
61
+
62
+ Makes no assumption about the shape of the cost distribution. Either end
63
+ is None when there are too few runs to bound it at this confidence.
64
+ """
65
+ n = len(sorted_values)
66
+ cdf = binom_cdf_table(n, p)
67
+ tail = (1 - confidence) / 2
68
+ low = None
69
+ for k in range(n, 0, -1): # largest k with P(X_(k) > q) at most the tail
70
+ if cdf[k - 1] <= tail:
71
+ low = sorted_values[k - 1]
72
+ break
73
+ high = upper_quantile_bound(sorted_values, p, 1 - tail, cdf)
74
+ return low, high
75
+
76
+
77
+ def upper_quantile_bound(
78
+ sorted_values: Sequence[float], p: float, confidence: float, cdf: Optional[List[float]] = None
79
+ ) -> Optional[float]:
80
+ """Smallest order statistic that is above the p-th quantile with the given confidence."""
81
+ n = len(sorted_values)
82
+ cdf = cdf or binom_cdf_table(n, p)
83
+ for k in range(1, n + 1):
84
+ if cdf[k - 1] >= confidence:
85
+ return sorted_values[k - 1]
86
+ return None
87
+
88
+
89
+ def bootstrap_interval(
90
+ columns: Sequence[Sequence],
91
+ statistic: Callable[..., Optional[float]],
92
+ confidence: float,
93
+ resamples: int,
94
+ seed: int,
95
+ ) -> Tuple[float, float]:
96
+ """Percentile bootstrap, resampling rows of the given columns together."""
97
+ rng = random.Random(seed)
98
+ n = len(columns[0])
99
+ values: List[float] = []
100
+ for _ in range(resamples):
101
+ idx = [rng.randrange(n) for _ in range(n)]
102
+ value = statistic(*[[col[i] for i in idx] for col in columns])
103
+ if value is not None and math.isfinite(value):
104
+ values.append(value)
105
+ if not values:
106
+ return float("nan"), float("nan")
107
+ values.sort()
108
+ tail = (1 - confidence) / 2
109
+ return quantile(values, tail), quantile(values, 1 - tail)