12 KiB
Cost Estimation Feature - Implementation Checklist (REVISED)
⚠️ IMPORTANT CHANGE
Costs are integrated into existing -u/--usage flag, NOT separate commands
Quick Start
-
Review the pricing data structure
cat pricing_data.json | python3 -c "import json, sys; d=json.load(sys.stdin); print(json.dumps(d['prices'][0], indent=2))" -
Set up development environment
pip install -e '.[test]' pytest tests/
Phase 1: Core Functionality ✓
1.1 Create llm/costs.py
-
Define
PriceInfodataclass- Fields: id, vendor, name, input_price, output_price, cached_input_price, from_date, to_date
-
Define
Costdataclass- Fields: input_cost, output_cost, cached_cost, total_cost, currency, model_id, price_info
-
Implement
CostEstimatorclass__init__()- Load pricing data from file_load_pricing_data()- Parse JSON, validate structure_find_price()- Exact model ID match_find_price_fuzzy()- Fuzzy model ID matchget_price()- Public method, handles historical datescalculate_cost()- Main cost calculationlist_models()- Return all models with pricing (for debugging)
-
Implement helper functions
get_default_estimator()- Singleton pattern_normalize_model_id()- Model ID cleanup_match_historical_price()- Date range matching
1.2 Bundle pricing data
-
Download and save to
llm/pricing_data.jsoncurl -s https://www.llm-prices.com/historical-v1.json -o llm/pricing_data.json -
Update
MANIFEST.into include pricing_data.jsoninclude llm/pricing_data.json
1.3 Unit Tests
- Create
tests/test_costs.py - Create
tests/fixtures/pricing_data.json(subset for testing) - Test cases:
test_load_pricing_data_success()test_load_pricing_data_missing_file()test_load_pricing_data_invalid_json()test_exact_model_match()test_fuzzy_model_match_gpt4()test_fuzzy_model_match_claude()test_model_not_found()test_calculate_cost_basic()test_calculate_cost_with_cached()test_calculate_cost_no_pricing()test_calculate_cost_zero_tokens()test_historical_pricing_in_range()test_historical_pricing_before_range()test_historical_pricing_after_range()test_list_models()test_list_models_by_vendor()
Phase 2: Integration ✓
2.1 Modify llm/models.py
-
Add to
Responseclass:def cost(self, estimator: Optional[CostEstimator] = None) -> Optional[Cost]: """Calculate cost for this response""" -
Add helper method:
def _get_cached_tokens(self) -> Optional[int]: """Extract cached token count from token_details""" -
Import CostEstimator in models.py
from .costs import CostEstimator, Cost
2.2 Update exports
-
Add to
llm/__init__.py:from .costs import CostEstimator, Cost, PriceInfo -
Add to
__all__list
2.3 Integration Tests
- Test cases in
tests/test_costs.py:test_response_cost_method()test_response_cost_with_custom_estimator()test_response_cost_no_tokens()test_response_cost_unknown_model()test_response_cached_tokens_anthropic()
Phase 3: CLI Integration ✓
3.1 Enhance token_usage_string() in llm/utils.py
Current signature:
def token_usage_string(input_tokens, output_tokens, token_details) -> str:
New signature (backward compatible):
def token_usage_string(
input_tokens,
output_tokens,
token_details,
model_id: Optional[str] = None,
datetime_utc: Optional[datetime] = None,
show_cost: bool = True
) -> str:
Implementation tasks:
- Add new parameters with defaults
- Add cost calculation logic:
- Import
get_default_estimatorfrom.costs - Extract cached tokens from
token_detailsif present - Call
estimator.calculate_cost() - Format cost string with breakdown
- Import
- Maintain backward compatibility (all new params optional)
- Handle errors gracefully (if cost calculation fails, just skip it)
Example output format:
"1,000 input, 500 output, Cost: $0.004500 ($0.003000 input, $0.001500 output)"
3.2 Update llm prompt command in llm/cli.py
Location: Around line 901
Current code:
if usage:
for response_object in responses:
click.echo(
click.style(
"Token usage: {}".format(response_object.token_usage()),
fg="yellow",
bold=True,
),
err=True,
)
Modify to:
- Pass model_id to token_usage_string
- Pass datetime_utc to token_usage_string
- Access via
response_object.resolved_modelorresponse_object.model.model_id - Access via
response_object.datetime_utc()
Suggested change:
if usage:
for response_object in responses:
usage_str = token_usage_string(
response_object.input_tokens,
response_object.output_tokens,
response_object.token_details,
model_id=response_object.resolved_model or response_object.model.model_id,
datetime_utc=response_object.datetime_utc()
)
click.echo(
click.style(
"Token usage: {}".format(usage_str),
fg="yellow",
bold=True,
),
err=True,
)
3.3 Update llm logs command in llm/cli.py
Location: Around line 2182
Current code:
if usage:
token_usage = token_usage_string(
row["input_tokens"],
row["output_tokens"],
json.loads(row["token_details"]) if row["token_details"] else None,
)
if token_usage:
click.echo("## Token usage\n\n{}\n".format(token_usage))
Modify to:
- Pass model_id from
row["model"] - Parse and pass datetime from
row["datetime_utc"]
Suggested change:
if usage:
from datetime import datetime as dt
token_usage = token_usage_string(
row["input_tokens"],
row["output_tokens"],
json.loads(row["token_details"]) if row["token_details"] else None,
model_id=row.get("resolved_model") or row["model"],
datetime_utc=dt.fromisoformat(row["datetime_utc"]) if row.get("datetime_utc") else None
)
if token_usage:
click.echo("## Token usage\n\n{}\n".format(token_usage))
3.4 CLI Tests
- Test cases in
tests/test_cli_costs.py(ortests/test_costs.py):test_prompt_usage_shows_cost()- Verify cost appears with -u flagtest_prompt_usage_unknown_model()- Verify graceful handlingtest_logs_usage_shows_cost()- Verify cost in logs -u outputtest_logs_usage_with_cached_tokens()- Verify cached token coststest_usage_backward_compatibility()- Old calls still worktest_usage_without_model_id()- Verify no crash if model_id missing
Phase 4: Documentation ✓
4.1 API Documentation
- Create
docs/costs.md- Overview: costs integrated into -u/--usage
- How pricing data works
- Python API examples (Response.cost())
- CLI examples (with -u flag)
- Model ID matching explained
- Historical pricing explained
- Limitations and caveats
4.2 Update existing docs
-
docs/logging.md- Add section on cost estimation with -u flag
- Link to costs.md
-
docs/usage.mdordocs/cli-reference.md- Update
-u/--usagedocumentation - Show examples with cost output
- Explain what costs mean
- Update
-
docs/python-api.md- Document
Response.cost()method - Document
CostEstimatorclass - Document
CostandPriceInfodataclasses
- Document
-
README.md- Add cost estimation to feature list
- Add quick example with -u flag
4.3 Docstrings
- Complete docstrings for all public classes/methods
- Include examples in docstrings
- Type hints everywhere
- Explain what happens when pricing unavailable
Phase 5: Polish ✓
5.1 Error Handling
- Graceful handling of missing pricing data
- Clear error messages for network failures (future feature)
- Warnings for model ID mismatches (debug level)
- Handle missing token counts (just don't show cost)
- Handle circular import issues (costs importing from models)
5.2 Logging
- Debug logs for model matching
- Info logs for using bundled vs custom pricing
- Warning logs for missing pricing (debug only, not user-facing)
5.3 Performance
- Cache parsed pricing data in memory (singleton pattern)
- Lazy load pricing data (only when needed)
- Efficient model lookup (dict, not list iteration)
- Don't slow down non-usage queries
5.4 Code Quality
- Run mypy:
mypy llm/costs.py llm/utils.py - Run ruff:
ruff llm/costs.py llm/utils.py - Run pytest with coverage:
pytest --cov=llm.costs --cov=llm.utils - Aim for >90% test coverage
Testing Checklist
Run all tests
# Unit tests
pytest tests/test_costs.py -v
# All tests with coverage
pytest --cov=llm.costs --cov=llm.utils --cov-report=html
# Type checking
mypy llm/costs.py llm/utils.py
# Linting
ruff llm/costs.py llm/utils.py
Manual testing
# Test Python API
python -c "
import llm
model = llm.get_model('gpt-3.5-turbo')
response = model.prompt('Hello')
cost = response.cost()
print(f'Cost: \${cost.total_cost:.6f}' if cost else 'No pricing')
"
# Test CLI with usage flag
llm "Hello world" -m gpt-3.5-turbo -u
# Should show: Token usage: X input, Y output, Cost: $Z
# Test logs with usage flag
llm logs -1 -u
# Should show token usage and cost
# Test with unknown model
llm "Hello" -m some-unknown-model -u
# Should show tokens but no cost
Example Code Snippets
Basic usage with -u flag
llm "Explain quantum computing" -m gpt-4 -u
# Output includes:
# Token usage: 15 input, 127 output, Cost: $0.004110 ($0.000450 input, $0.003660 output)
Python API
import llm
# Get response
model = llm.get_model("gpt-4")
response = model.prompt("Explain quantum computing in one sentence")
# Get cost
cost = response.cost()
if cost:
print(f"Cost: ${cost.total_cost:.6f}")
print(f" Input: ${cost.input_cost:.6f} ({response.input_tokens} tokens)")
print(f" Output: ${cost.output_cost:.6f} ({response.output_tokens} tokens)")
Custom estimator
from llm.costs import CostEstimator
# Use custom pricing data
estimator = CostEstimator("/path/to/custom/pricing.json")
cost = response.cost(estimator)
Key Differences from Original Plan
❌ Removed Features
llm logs costcommand - NOT implementingllm cost-updatecommand - NOT implementingllm cost-modelscommand - NOT implementing- Separate cost reporting interface
✅ Simplified Approach
- Cost estimates appear automatically when using
-u/--usage - Integration into existing workflow
- Less code to maintain
- More intuitive for users
🎯 Core Feature Remains
- Python API:
response.cost()- YES, still implementing - Cost calculation engine - YES, core functionality
- Pricing data management - YES, bundled with package
- Model ID matching - YES, fuzzy matching included
- Historical pricing - YES, date-based pricing
Deployment Checklist
- All tests passing
- Coverage >90%
- Documentation complete and accurate
- CHANGELOG.md updated
- Version bumped in pyproject.toml
- pricing_data.json bundled with package
- README.md examples tested
-uflag behavior documented- Clean git history
Success Metrics
- Cost appears correctly with
-uflag for known models - Cost calculation is accurate
- No cost shown (gracefully) for unknown models
- Response.cost() Python API works
- No breaking changes to existing code
- Performance impact is negligible
- Tests achieve >90% coverage
- User experience is seamless