-
Notifications
You must be signed in to change notification settings - Fork 0
Make TokenUsage.total_tokens a computed field #109
Copy link
Copy link
Closed
Labels
prio:mediumShould do, but not blockingShould do, but not blockingscope:smallLess than 1 day of workLess than 1 day of workspec:providersDESIGN_SPEC Section 9 - Model Provider LayerDESIGN_SPEC Section 9 - Model Provider Layertype:refactorCode restructuring, cleanupCode restructuring, cleanup
Milestone
Description
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 selfThis 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_tokensBenefits:
- Eliminates the redundant validator
- Call sites stop passing
total_tokens=... model_dump()and JSON serialization still includetotal_tokens- Cannot be out of sync by definition
Acceptance Criteria
-
total_tokensis a@computed_fieldproperty -
_validate_totalvalidator removed - All call sites updated to stop passing
total_tokens=... -
model_dump()and JSON serialization still includetotal_tokens - All existing tests pass
- Verify DESIGN_SPEC.md §10.2 and §15.5 remain consistent after implementation
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
prio:mediumShould do, but not blockingShould do, but not blockingscope:smallLess than 1 day of workLess than 1 day of workspec:providersDESIGN_SPEC Section 9 - Model Provider LayerDESIGN_SPEC Section 9 - Model Provider Layertype:refactorCode restructuring, cleanupCode restructuring, cleanup