77 lines
2.4 KiB
Python
77 lines
2.4 KiB
Python
"""Repository contract for model-stream truncation events."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from abc import ABC, abstractmethod
|
|
from dataclasses import dataclass
|
|
from datetime import datetime
|
|
from typing import Any
|
|
|
|
# Hard retention cap. The user-facing promise is "最多存一个月" of truncation
|
|
# records; the SQL repository enforces it on every insert so no scheduler is
|
|
# required for the cap to hold.
|
|
RETENTION_DAYS = 30
|
|
|
|
|
|
@dataclass
|
|
class StreamTruncationEvent:
|
|
"""In-memory shape of a truncation event before persistence."""
|
|
|
|
id: str
|
|
created_at: datetime
|
|
model_name: str | None = None
|
|
provider_completion_id: str | None = None
|
|
run_id: str | None = None
|
|
# silent_no_terminal / incomplete_stream_error / length_truncated /
|
|
# content_filter / midstream_error
|
|
event_type: str = "silent_no_terminal"
|
|
strict_mode: bool = False
|
|
finish_reason: str = "none"
|
|
status: str = "success"
|
|
error_type: str | None = None
|
|
chunk_count: int = 0
|
|
duration_ms: int = 0
|
|
first_chunk_ms: int = -1
|
|
detail: str | None = None
|
|
|
|
|
|
@dataclass
|
|
class StreamTruncationQuery:
|
|
"""Filter set for the admin list endpoint."""
|
|
|
|
model_name: str | None = None
|
|
event_type: str | None = None
|
|
since: datetime | None = None
|
|
until: datetime | None = None
|
|
limit: int = 100
|
|
offset: int = 0
|
|
|
|
|
|
class StreamTruncationStore(ABC):
|
|
"""Persistence contract for the model-layer recorder + admin API."""
|
|
|
|
@abstractmethod
|
|
async def record(self, event: StreamTruncationEvent) -> None:
|
|
"""Persist one event and enforce the retention cap. Must never raise."""
|
|
raise NotImplementedError
|
|
|
|
@abstractmethod
|
|
async def list(self, query: StreamTruncationQuery) -> list[dict[str, Any]]:
|
|
"""Return matching rows (newest first), paginated by ``query``."""
|
|
raise NotImplementedError
|
|
|
|
@abstractmethod
|
|
async def count(self, query: StreamTruncationQuery) -> int:
|
|
"""Return the unpaginated row count for ``query``."""
|
|
raise NotImplementedError
|
|
|
|
@abstractmethod
|
|
async def model_breakdown(self, query: StreamTruncationQuery) -> list[dict[str, Any]]:
|
|
"""Per-model aggregate: ``[{model_name, event_type counts..., total, last_at}]``."""
|
|
raise NotImplementedError
|
|
|
|
@abstractmethod
|
|
async def purge_before(self, cutoff: datetime) -> int:
|
|
"""Delete rows older than ``cutoff``; return the deleted row count."""
|
|
raise NotImplementedError
|