Skip to content

Round cost_usd at the pricing boundary to avoid float-representation noise in persisted manifests and agent totals #63

Description

@goanpeca

Summary

cost_usd and all packaged pricing strategies compute and store USD as Python
float. Per-unit-times-count, per-1000-character division, and the agent batch sum
accumulate binary float representation error, so user-facing and analytics values
land in Parquet rows, manifest steps, and AgentResult.total_cost_usd as values
like 0.30000000000000004.

Affected files

  • libs/core/genblaze_core/providers/pricing.py:61-207 (all strategies multiply/divide raw float: per_unit, per_input_chars, per_output_second, tiered, by_param, by_model_and_param, bucketed_by_duration)
  • libs/core/genblaze_core/providers/model_registry.py:750 (compute_cost returns spec.pricing(ctx) with no boundary rounding)
  • libs/core/genblaze_core/providers/base.py:1051 (step.cost_usd = cost), also :1652, :1822
  • libs/core/genblaze_core/agents/loop.py:300 (total_cost = sum(step.cost_usd or 0.0 ...), stored at loop.py:84)
  • libs/core/genblaze_core/sinks/parquet.py:133 (writes cost_usd raw)
  • cost_usd: float declared at libs/core/genblaze_core/models/step.py:61 and libs/core/genblaze_core/models/chat.py:227

Evidence

Runtime demo with the actual functions:

per_unit(0.10) summed over 10 assets    = 0.9999999999999999   (== 1.0? False)
by_param size a=0.1 b=0.2 -> a+b         = 0.30000000000000004  (== 0.3? False)
per_input_chars(0.002, per=1000) x1000   = 0.0020000000000000304 (== 0.002? False)

These values are assigned straight to step.cost_usd and persisted/summed
unrounded. Scope note (verified, to avoid over-claiming): the canonical hash is NOT
affected, because canonical/_normalize.py:34 rounds every float to 10 decimals
before hashing, so determinism holds. The defect is the unrounded values exposed to
consumers (Parquet sink, AgentResult.total_cost_usd, AgentCompletedEvent / tracer
cost attributes). There is no display-side rounding anywhere in the CLI or runtime.

Why it matters

USD amounts shown to users and written to analytics tables carry float noise like
0.30000000000000004, which looks wrong in cost reports and complicates downstream
aggregation.

Suggested fix

Round at the single boundary where cost is finalized, for example in compute_cost
(model_registry.py:750) return round(result, 6) (micro-dollar precision), and
round the agents/loop.py:300 sum the same way. This is the cheapest, lowest-risk
option. Switching strategies and cost_usd to Decimal would be exact but is a
larger change and likely overkill for a field documented as "Estimated cost".

Acceptance criteria

  • Persisted and aggregated cost_usd values are free of float-representation noise.
  • Existing pricing-strategy behavior is otherwise unchanged; canonical hashing is
    unaffected.

Metadata

Metadata

Assignees

No one assigned

    Type

    Fields

    No fields configured for Bug.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions