fix(major::pr): re-architect instrumentation library

This commit is contained in:
Emilio Garcia 2025-10-06 17:54:05 -04:00
parent 7e3cf1fb20
commit 8fe3a25158
21 changed files with 422 additions and 462 deletions

View file

@ -9,7 +9,7 @@ from pathlib import Path
from typing import Annotated, Any, Literal, Self
from urllib.parse import urlparse
from pydantic import BaseModel, Field, field_validator, model_validator
from pydantic import BaseModel, Field, TypeAdapter, field_validator, model_validator
from llama_stack.apis.benchmarks import Benchmark, BenchmarkInput
from llama_stack.apis.datasetio import DatasetIO
@ -26,7 +26,10 @@ from llama_stack.apis.tools import ToolGroup, ToolGroupInput, ToolRuntime
from llama_stack.apis.vector_dbs import VectorDB, VectorDBInput
from llama_stack.apis.vector_io import VectorIO
from llama_stack.core.access_control.datatypes import AccessRule
from llama_stack.core.instrumentation import InstrumentationProvider
from llama_stack.core.utils.dynamic import instantiate_class_type
from llama_stack.providers.datatypes import Api, ProviderSpec
from llama_stack.providers.registry.instrumentation import instrumentation_registry
from llama_stack.providers.utils.kvstore.config import KVStoreConfig, SqliteKVStoreConfig
from llama_stack.providers.utils.sqlstore.sqlstore import SqlStoreConfig
@ -493,6 +496,12 @@ If not specified, a default SQLite store will be used.""",
logging: LoggingConfig | None = Field(default=None, description="Configuration for Llama Stack Logging")
# Middleware/instrumentation providers (not full APIs)
instrumentation: InstrumentationProvider | None = Field(
default=None,
description="Instrumentation provider for observability",
)
server: ServerConfig = Field(
default_factory=ServerConfig,
description="Configuration for the HTTP(S) server",
@ -517,11 +526,31 @@ If not specified, a default SQLite store will be used.""",
return Path(v)
return v
@field_validator("instrumentation", mode="before")
@classmethod
def load_instrumentation(cls, v: InstrumentationProvider | dict[str, Any] | None):
if v is None or isinstance(v, InstrumentationProvider):
return v
provider_type = v.get("provider")
if not isinstance(provider_type, str):
raise ValueError("instrumentation.provider must be a string")
entry = instrumentation_registry.get(provider_type)
if entry is None:
raise ValueError(f"Unknown instrumentation provider: {provider_type}")
cfg_cls = instantiate_class_type(entry.config_class)
prv_cls = instantiate_class_type(entry.provider_class)
cfg_data = v.get("config") or {}
cfg = TypeAdapter(cfg_cls).validate_python(cfg_data)
return prv_cls(provider=provider_type, config=cfg)
class BuildConfig(BaseModel):
version: int = LLAMA_STACK_BUILD_CONFIG_VERSION
distribution_spec: DistributionSpec = Field(description="The distribution spec to build including API providers. ")
distribution_spec: DistributionSpec = Field(description="The distribution spec to build including API providers.")
image_type: str = Field(
default="venv",
description="Type of package to build (container | venv)",

View file

@ -0,0 +1,33 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.
# All rights reserved.
#
# This source code is licensed under the terms described in the LICENSE file in
# the root directory of this source tree.
"""Protocol for instrumentation providers."""
from abc import abstractmethod
from fastapi import FastAPI
from pydantic import BaseModel, Field
class InstrumentationProvider(BaseModel):
"""
Base class for instrumentation providers.
Instrumentation providers add observability (tracing, metrics, logs) to the
application but don't expose API endpoints.
"""
provider: str = Field(description="Provider identifier for discriminated unions")
config: BaseModel
@abstractmethod
def fastapi_middleware(self, app: FastAPI) -> None:
"""
Inject middleware into the FastAPI application.
:param app: The FastAPI application to instrument
"""
...

View file

@ -400,9 +400,9 @@ def create_app() -> StackApp:
if cors_config:
app.add_middleware(CORSMiddleware, **cors_config.model_dump())
if Api.telemetry in impls:
impls[Api.telemetry].fastapi_middleware(app)
impls[Api.telemetry].sqlalchemy_instrumentation()
# Apply instrumentation provider (e.g., OpenTelemetry)
if config.instrumentation:
config.instrumentation.fastapi_middleware(app)
# Load external APIs if configured
external_apis = load_external_apis(config)

View file

@ -1,10 +0,0 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.
# All rights reserved.
#
# This source code is licensed under the terms described in the LICENSE file in
# the root directory of this source tree.
# All rights reserved.
#
# This source code is licensed under the terms described in the LICENSE file in
# the root directory of this source tree.

View file

@ -1,22 +0,0 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.
# All rights reserved.
#
# This source code is licensed under the terms described in the LICENSE file in
# the root directory of this source tree.
from abc import abstractmethod
from fastapi import FastAPI
from pydantic import BaseModel
class TelemetryProvider(BaseModel):
"""
TelemetryProvider standardizes how telemetry is provided to the application.
"""
@abstractmethod
def fastapi_middleware(self, app: FastAPI, *args, **kwargs):
"""
Injects FastAPI middleware that instruments the application for telemetry.
"""
...