Skip to main content

PricingEngine

The PricingEngine is the pure calculation core. It evaluates pricing expressions against usage metrics. No database dependency — can be used standalone.

Creating an Engine

from ducto import PricingEngine

# From a dict (testing, stateless)
engine = PricingEngine.from_dict({
"version": 1,
"models": {"_default": "input_tokens * 0.001 + output_tokens * 0.003"},
})

# From a config file
engine = PricingEngine.from_yaml("pricing.yaml")

Methods

calculate(metrics: UsageMetrics) -> CostBreakdown

Evaluate pricing expressions against usage metrics.

result = engine.calculate(
UsageMetrics(model="gpt-4", input_tokens=500, output_tokens=200)
)
print(f"Total: {result.total}")
print(f"Model credits: {result.model_credits}")

calculate_batch(metrics: list[UsageMetrics]) -> list[CostBreakdown]

Evaluate multiple metrics at once.

resolve_model(model_version: str) -> str | None

Resolve a model version string against configured model names.

has_model(model_name: str) -> bool

Check if a specific model is configured.

get_fixed_cost(job_name: str) -> int | None

Get the fixed cost for a named job.

pricing_schema() -> PricingConfigData

Get the raw pricing config dict that the engine was initialized with.

CostBreakdown

Returned by calculate():

FieldTypeDescription
totalfloatTotal credits
model_creditsfloatCredits from model expression
tool_creditsfloatCredits from tool expressions
search_creditsfloatCredits from search expressions
cache_creditsfloatCredits from cache expression (negative = savings)
fixed_creditsint ` None`
metadatadictDebug info: expressions evaluated, values

UsageMetrics

Input to calculate():

FieldTypeDefault
modelstr"_default"
input_tokensint0
output_tokensint0
cache_read_tokensint0
cache_write_tokensint0
tool_callslist[ToolCall][]
search_queriesint0
search_resultsint0
web_search_callsint0
code_exec_callsint0
fixed_jobstr ` None`

Expression Safety

The engine uses Python's ast module with a strict allowlist:

  1. Parses ast.parse(expr, mode="eval")
  2. Walks the AST — only ~25 node types allowed (binary ops, comparisons, conditionals, etc.)
  3. Function calls whitelisted: ceil, floor, min, max, round, if, tier, clamp, percentile
  4. Rejects: attributes (x.__class__), subscripts (x[0]), lambdas, comprehensions, imports
  5. Evaluation namespace has __builtins__ emptied
  6. All expressions validated at config load time