model_usage_details.py 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127
  1. from datetime import date, datetime
  2. from typing import ClassVar, Optional
  3. from pydantic import ConfigDict
  4. from sqlalchemy import BigInteger, Column, Integer
  5. from sqlmodel import Field, SQLModel
  6. from gpustack.mixins import BaseModelMixin
  7. from gpustack.schemas.common import UTCDateTime
  8. from gpustack.schemas.model_usage import OperationEnum
  9. class ModelUsageDetails(SQLModel, BaseModelMixin, table=True):
  10. """
  11. Per-request inference usage audit row.
  12. Reference id columns (``user_id`` / ``model_id`` / ``model_route_id`` /
  13. ``provider_id`` / ``cluster_id`` / ``api_key_id``) are plain integers,
  14. not foreign keys. Audit rows must outlive the entities they describe;
  15. losing the historical id (which ``SET NULL`` would do on parent delete)
  16. is a worse audit outcome than losing the live join, so ids stay as
  17. reported and ``*_name`` columns hold mutable display snapshots
  18. alongside.
  19. Relationship to ``ModelUsage``: details and rollup are NOT 1:1 — the
  20. rollup aggregates many requests per (model, user, key, operation, day)
  21. into one row, while details preserves every report. They are populated
  22. from the same ingest path but serve different read patterns:
  23. * ``ModelUsage`` — dashboard / per-day analytics, FK-friendly
  24. * ``ModelUsageDetails`` — quota reconciliation / per-request audit,
  25. FK-less so historical ids survive deletes
  26. Both rows are constructed with the same ``build_model_usage_snapshot``
  27. keys plus table-specific extras; see that helper's docstring for the
  28. shared-snapshot contract.
  29. """
  30. __tablename__: ClassVar[str] = "model_usage_details"
  31. id: Optional[int] = Field(default=None, primary_key=True)
  32. user_id: Optional[int] = Field(default=None, sa_column=Column(Integer))
  33. user_name: Optional[str] = Field(default=None)
  34. model_id: Optional[int] = Field(default=None, sa_column=Column(Integer))
  35. model_name: str = Field(default=...)
  36. model_route_id: Optional[int] = Field(default=None, sa_column=Column(Integer))
  37. model_route_name: Optional[str] = Field(default=None)
  38. provider_id: Optional[int] = Field(default=None, sa_column=Column(Integer))
  39. provider_name: Optional[str] = Field(default=None)
  40. provider_type: Optional[str] = Field(default=None)
  41. cluster_id: Optional[int] = Field(default=None, sa_column=Column(Integer))
  42. cluster_name: Optional[str] = Field(default=None)
  43. api_key_id: Optional[int] = Field(default=None, sa_column=Column(Integer))
  44. api_key_name: Optional[str] = Field(default=None)
  45. access_key: Optional[str] = Field(default=None)
  46. api_key_is_custom: Optional[bool] = Field(default=None)
  47. date: date
  48. prompt_token_count: int = Field(
  49. default=..., sa_column=Column(BigInteger, nullable=False)
  50. )
  51. completion_token_count: int = Field(
  52. default=..., sa_column=Column(BigInteger, nullable=False)
  53. )
  54. prompt_cached_token_count: int = Field(
  55. default=0, sa_column=Column(BigInteger, nullable=False, default=0)
  56. )
  57. operation: Optional[OperationEnum] = Field(default=None)
  58. # Wall-clock anchors reported by the proxy (UnixMilli on the wire,
  59. # stored as naive UTC). Distinct from ``created_at`` so quota
  60. # reconciliation / cache rebuild can key off the request's actual
  61. # completion time even after rows are archived or migrated.
  62. started_at: Optional[datetime] = Field(
  63. default=None, sa_column=Column(UTCDateTime(), nullable=True)
  64. )
  65. completed_at: Optional[datetime] = Field(
  66. default=None, sa_column=Column(UTCDateTime(), nullable=True)
  67. )
  68. model_config = ConfigDict(protected_namespaces=())
  69. class ModelUsageDetailsArchive(SQLModel, BaseModelMixin, table=True):
  70. """
  71. Cold-storage archive for ``model_usage_details``.
  72. Same column layout as the hot table; ``id`` is a plain primary key with
  73. no sequence/autoincrement — rows are archived from
  74. ``model_usage_details`` and reuse the source ``id``.
  75. """
  76. __tablename__: ClassVar[str] = "model_usage_details_archive"
  77. id: Optional[int] = Field(
  78. default=None,
  79. sa_column=Column(Integer, primary_key=True, autoincrement=False),
  80. )
  81. user_id: Optional[int] = Field(default=None, sa_column=Column(Integer))
  82. user_name: Optional[str] = Field(default=None)
  83. model_id: Optional[int] = Field(default=None, sa_column=Column(Integer))
  84. model_name: str = Field(default=...)
  85. model_route_id: Optional[int] = Field(default=None, sa_column=Column(Integer))
  86. model_route_name: Optional[str] = Field(default=None)
  87. provider_id: Optional[int] = Field(default=None, sa_column=Column(Integer))
  88. provider_name: Optional[str] = Field(default=None)
  89. provider_type: Optional[str] = Field(default=None)
  90. cluster_id: Optional[int] = Field(default=None, sa_column=Column(Integer))
  91. cluster_name: Optional[str] = Field(default=None)
  92. api_key_id: Optional[int] = Field(default=None, sa_column=Column(Integer))
  93. api_key_name: Optional[str] = Field(default=None)
  94. access_key: Optional[str] = Field(default=None)
  95. api_key_is_custom: Optional[bool] = Field(default=None)
  96. date: date
  97. prompt_token_count: int = Field(
  98. default=..., sa_column=Column(BigInteger, nullable=False)
  99. )
  100. completion_token_count: int = Field(
  101. default=..., sa_column=Column(BigInteger, nullable=False)
  102. )
  103. prompt_cached_token_count: int = Field(
  104. default=0, sa_column=Column(BigInteger, nullable=False, default=0)
  105. )
  106. operation: Optional[OperationEnum] = Field(default=None)
  107. started_at: Optional[datetime] = Field(
  108. default=None, sa_column=Column(UTCDateTime(), nullable=True)
  109. )
  110. completed_at: Optional[datetime] = Field(
  111. default=None, sa_column=Column(UTCDateTime(), nullable=True)
  112. )
  113. model_config = ConfigDict(protected_namespaces=())