diff --git a/llama_stack/apis/telemetry/telemetry.py b/llama_stack/apis/telemetry/telemetry.py index af0469d2b..6d30d96e0 100644 --- a/llama_stack/apis/telemetry/telemetry.py +++ b/llama_stack/apis/telemetry/telemetry.py @@ -203,6 +203,45 @@ class QuerySpanTreeResponse(BaseModel): data: dict[str, SpanWithStatus] +@json_schema_type +class MetricQueryType(Enum): + RANGE = "range" + INSTANT = "instant" + + +@json_schema_type +class MetricLabelOperator(Enum): + EQUALS = "=" + NOT_EQUALS = "!=" + REGEX_MATCH = "=~" + REGEX_NOT_MATCH = "!~" + + +@json_schema_type +class MetricLabelMatcher(BaseModel): + name: str + value: str + operator: MetricLabelOperator = MetricLabelOperator.EQUALS + + +@json_schema_type +class MetricDataPoint(BaseModel): + timestamp: datetime + value: float + + +@json_schema_type +class MetricSeries(BaseModel): + metric: str + labels: Dict[str, str] + values: List[MetricDataPoint] + + +@json_schema_type +class GetMetricsResponse(BaseModel): + data: List[MetricSeries] + + @runtime_checkable class Telemetry(Protocol): @webmethod(route="/telemetry/events", method="POST") @@ -247,3 +286,14 @@ class Telemetry(Protocol): dataset_id: str, max_depth: int | None = None, ) -> None: ... + + @webmethod(route="/telemetry/metrics/{metric_name}", method="POST") + async def get_metrics( + self, + metric_name: str, + start_time: int, + end_time: Optional[int] = None, + step: Optional[str] = "1d", + query_type: MetricQueryType = MetricQueryType.RANGE, + label_matchers: Optional[List[MetricLabelMatcher]] = None, + ) -> GetMetricsResponse: ... diff --git a/llama_stack/providers/inline/telemetry/meta_reference/telemetry.py b/llama_stack/providers/inline/telemetry/meta_reference/telemetry.py index 9295d5cab..1e7f9a40e 100644 --- a/llama_stack/providers/inline/telemetry/meta_reference/telemetry.py +++ b/llama_stack/providers/inline/telemetry/meta_reference/telemetry.py @@ -19,7 +19,10 @@ from opentelemetry.semconv.resource import ResourceAttributes from llama_stack.apis.telemetry import ( Event, + GetMetricsResponse, MetricEvent, + MetricLabelMatcher, + MetricQueryType, QueryCondition, QuerySpanTreeResponse, QueryTracesResponse, @@ -123,6 +126,17 @@ class TelemetryAdapter(TelemetryDatasetMixin, Telemetry): else: raise ValueError(f"Unknown event type: {event}") + async def get_metrics( + self, + metric_name: str, + start_time: int, + end_time: Optional[int] = None, + step: Optional[str] = "1d", + query_type: MetricQueryType = MetricQueryType.RANGE, + label_matchers: Optional[List[MetricLabelMatcher]] = None, + ) -> GetMetricsResponse: + pass + def _log_unstructured(self, event: UnstructuredLogEvent, ttl_seconds: int) -> None: with self._lock: # Use global storage instead of instance storage