Skip to content

Make TokenUsage.total_tokens a computed field #109

@Aureliolo

Description

@Aureliolo

Problem

TokenUsage in providers/models.py stores total_tokens as a field with a validator that checks total_tokens == input_tokens + output_tokens:

total_tokens: int = Field(ge=0)

@model_validator(mode="after")
def _validate_total(self) -> Self:
    expected = self.input_tokens + self.output_tokens
    if self.total_tokens != expected:
        raise ValueError(...)
    return self

This is a derived value that can never differ from its inputs — the validator exists solely to catch construction bugs. Every call site must pass total_tokens=input + output, which is redundant boilerplate.

Proposed Solution

Use Pydantic v2's @computed_field:

class TokenUsage(BaseModel):
    model_config = ConfigDict(frozen=True)

    input_tokens: int = Field(ge=0, description="Input token count")
    output_tokens: int = Field(ge=0, description="Output token count")
    cost_usd: Decimal | None = Field(default=None, ge=0, description="Cost in USD")

    @computed_field  # type: ignore[prop-decorator]
    @property
    def total_tokens(self) -> int:
        return self.input_tokens + self.output_tokens

Benefits:

  • Eliminates the redundant validator
  • Call sites stop passing total_tokens=...
  • model_dump() and JSON serialization still include total_tokens
  • Cannot be out of sync by definition

Acceptance Criteria

  • total_tokens is a @computed_field property
  • _validate_total validator removed
  • All call sites updated to stop passing total_tokens=...
  • model_dump() and JSON serialization still include total_tokens
  • All existing tests pass
  • Verify DESIGN_SPEC.md §10.2 and §15.5 remain consistent after implementation

Metadata

Metadata

Assignees

No one assigned

    Labels

    prio:mediumShould do, but not blockingscope:smallLess than 1 day of workspec:providersDESIGN_SPEC Section 9 - Model Provider Layertype:refactorCode restructuring, cleanup

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions