Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified docs/config.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
19 changes: 16 additions & 3 deletions docs/config.puml
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ class "Configuration" as src.models.config.Configuration {
llama_stack
mcp_servers : Optional[list[ModelContextProtocolServer]]
name : str
quota_handlers : Optional[QuotaHandlersConfig]
quota_handlers : Optional[QuotaHandlersConfiguration]
service
user_data_collection
dump(filename: str) -> None
Expand Down Expand Up @@ -135,11 +135,23 @@ class "PostgreSQLDatabaseConfiguration" as src.models.config.PostgreSQLDatabaseC
user : str
check_postgres_configuration() -> Self
}
class "QuotaHandlersConfig" as src.models.config.QuotaHandlersConfig {
class "QuotaHandlersConfiguration" as src.models.config.QuotaHandlersConfiguration {
enable_token_history : bool
limiters : Optional[list[QuotaLimiterConfiguration]]
postgres : Optional[PostgreSQLDatabaseConfiguration]
scheduler : Optional[QuotaSchedulerConfiguration]
sqlite : Optional[SQLiteDatabaseConfiguration]
}
Comment on lines +138 to 144
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Missing composition declarations for limiters and scheduler fields.

The QuotaHandlersConfiguration class contains two composition fields (limiters and scheduler), but the corresponding composition relationship declarations are missing from the diagram.

Add the missing composition declarations after line 210:

 src.models.config.UserDataCollection --* src.models.config.Configuration : user_data_collection
+src.models.config.QuotaLimiterConfiguration --* src.models.config.QuotaHandlersConfiguration : limiters
+src.models.config.QuotaSchedulerConfiguration --* src.models.config.QuotaHandlersConfiguration : scheduler
 @enduml

Also applies to: 205-210

🤖 Prompt for AI Agents
In docs/config.puml around lines 205-210 (and ensure coverage for the
QuotaHandlersConfiguration declaration at ~138-144), add missing composition
relationships for the two composed fields: create composition edges from
src.models.config.QuotaHandlersConfiguration to
src.models.config.QuotaLimiterConfiguration for the "limiters" field, and from
src.models.config.QuotaHandlersConfiguration to
src.models.config.QuotaSchedulerConfiguration for the "scheduler" field; place
these declarations immediately after line 210 so the diagram shows that
QuotaHandlersConfiguration composes Limiters and Scheduler using the appropriate
fully-qualified class names and relationship labels.

class "QuotaLimiterConfiguration" as src.models.config.QuotaLimiterConfiguration {
initial_quota : Annotated
name : str
period : str
quota_increase : Annotated
type : Literal['user_limiter', 'cluster_limiter']
}
class "QuotaSchedulerConfiguration" as src.models.config.QuotaSchedulerConfiguration {
period : Annotated
}
Comment on lines +152 to +154
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Missing inheritance declaration for QuotaSchedulerConfiguration.

The QuotaSchedulerConfiguration class is defined but lacks an inheritance declaration. According to the codebase patterns and learnings, all configuration classes must extend ConfigurationBase.

Add the missing inheritance declaration after line 200:

 src.models.config.QuotaLimiterConfiguration --|> src.models.config.ConfigurationBase
+src.models.config.QuotaSchedulerConfiguration --|> src.models.config.ConfigurationBase
 src.models.config.SQLiteDatabaseConfiguration --|> src.models.config.ConfigurationBase

Based on learnings.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In docs/config.puml around lines 152 to 154, the QuotaSchedulerConfiguration
class is missing its inheritance; update the class declaration to extend
ConfigurationBase (i.e., add "extends ConfigurationBase" to the class line) and
ensure any necessary stereotype/namespace references match the rest of the
diagram; place the inheritance declaration as instructed after line 200 if that
aligns with the file's ordering conventions.

class "SQLiteDatabaseConfiguration" as src.models.config.SQLiteDatabaseConfiguration {
db_path : str
}
Expand Down Expand Up @@ -184,7 +196,8 @@ src.models.config.JwtRoleRule --|> src.models.config.ConfigurationBase
src.models.config.LlamaStackConfiguration --|> src.models.config.ConfigurationBase
src.models.config.ModelContextProtocolServer --|> src.models.config.ConfigurationBase
src.models.config.PostgreSQLDatabaseConfiguration --|> src.models.config.ConfigurationBase
src.models.config.QuotaHandlersConfig --|> src.models.config.ConfigurationBase
src.models.config.QuotaHandlersConfiguration --|> src.models.config.ConfigurationBase
src.models.config.QuotaLimiterConfiguration --|> src.models.config.ConfigurationBase
src.models.config.SQLiteDatabaseConfiguration --|> src.models.config.ConfigurationBase
src.models.config.ServiceConfiguration --|> src.models.config.ConfigurationBase
src.models.config.TLSConfiguration --|> src.models.config.ConfigurationBase
Expand Down
303 changes: 167 additions & 136 deletions docs/config.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
27 changes: 25 additions & 2 deletions src/models/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
FilePath,
AnyHttpUrl,
PositiveInt,
NonNegativeInt,
SecretStr,
)

Expand Down Expand Up @@ -564,11 +565,31 @@ class ByokRag(ConfigurationBase):
db_path: FilePath


class QuotaHandlersConfig(ConfigurationBase):
class QuotaLimiterConfiguration(ConfigurationBase):
"""Configuration for one quota limiter."""

type: Literal["user_limiter", "cluster_limiter"]
name: str
initial_quota: NonNegativeInt
quota_increase: NonNegativeInt
period: str


class QuotaSchedulerConfiguration(BaseModel):
"""Quota scheduler configuration."""

period: PositiveInt = 1


class QuotaHandlersConfiguration(ConfigurationBase):
"""Quota limiter configuration."""

sqlite: Optional[SQLiteDatabaseConfiguration] = None
postgres: Optional[PostgreSQLDatabaseConfiguration] = None
limiters: list[QuotaLimiterConfiguration] = Field(default_factory=list)
scheduler: QuotaSchedulerConfiguration = Field(
default_factory=QuotaSchedulerConfiguration
)
enable_token_history: bool = False


Expand All @@ -591,7 +612,9 @@ class Configuration(ConfigurationBase):
default_factory=ConversationCacheConfiguration
)
byok_rag: list[ByokRag] = Field(default_factory=list)
quota_handlers: QuotaHandlersConfig = Field(default_factory=QuotaHandlersConfig)
quota_handlers: QuotaHandlersConfiguration = Field(
default_factory=QuotaHandlersConfiguration
)

def dump(self, filename: str = "configuration.json") -> None:
"""Dump actual configuration into JSON file."""
Expand Down
203 changes: 203 additions & 0 deletions tests/unit/models/config/test_dump_configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@
PostgreSQLDatabaseConfiguration,
CORSConfiguration,
Configuration,
QuotaHandlersConfiguration,
QuotaLimiterConfiguration,
QuotaSchedulerConfiguration,
ServiceConfiguration,
InferenceConfiguration,
TLSConfiguration,
Expand Down Expand Up @@ -175,6 +178,8 @@ def test_dump_configuration(tmp_path) -> None:
"quota_handlers": {
"sqlite": None,
"postgres": None,
"limiters": [],
"scheduler": {"period": 1},
"enable_token_history": False,
},
}
Expand Down Expand Up @@ -293,3 +298,201 @@ def test_dump_configuration_with_more_mcp_servers(tmp_path) -> None:
"url": "http://localhost:8083",
},
]


def test_dump_configuration_with_quota_limiters(tmp_path) -> None:
"""
Test that the Configuration object can be serialized to a JSON file and
that the resulting file contains all expected sections and values.

Please note that redaction process is not in place.
"""
cfg = Configuration(
name="test_name",
service=ServiceConfiguration(
tls_config=TLSConfiguration(
tls_certificate_path=Path("tests/configuration/server.crt"),
tls_key_path=Path("tests/configuration/server.key"),
tls_key_password=Path("tests/configuration/password"),
),
cors=CORSConfiguration(
allow_origins=["foo_origin", "bar_origin", "baz_origin"],
allow_credentials=False,
allow_methods=["foo_method", "bar_method", "baz_method"],
allow_headers=["foo_header", "bar_header", "baz_header"],
),
),
llama_stack=LlamaStackConfiguration(
use_as_library_client=True,
library_client_config_path="tests/configuration/run.yaml",
api_key="whatever",
),
user_data_collection=UserDataCollection(
feedback_enabled=False, feedback_storage=None
),
database=DatabaseConfiguration(
sqlite=None,
postgres=PostgreSQLDatabaseConfiguration(
db="lightspeed_stack",
user="ls_user",
password="ls_password",
port=5432,
ca_cert_path=None,
ssl_mode="require",
gss_encmode="disable",
),
),
mcp_servers=[],
customization=None,
inference=InferenceConfiguration(
default_provider="default_provider",
default_model="default_model",
),
quota_handlers=QuotaHandlersConfiguration(
limiters=[
QuotaLimiterConfiguration(
type="user_limiter",
name="user_monthly_limits",
initial_quota=1,
quota_increase=10,
period="2 seconds",
),
QuotaLimiterConfiguration(
type="cluster_limiter",
name="cluster_monthly_limits",
initial_quota=2,
quota_increase=20,
period="1 month",
),
],
scheduler=QuotaSchedulerConfiguration(period=10),
enable_token_history=True,
),
)
assert cfg is not None
dump_file = tmp_path / "test.json"
cfg.dump(dump_file)

with open(dump_file, "r", encoding="utf-8") as fin:
content = json.load(fin)
# content should be loaded
assert content is not None

# all sections must exists
assert "name" in content
assert "service" in content
assert "llama_stack" in content
assert "user_data_collection" in content
assert "mcp_servers" in content
assert "authentication" in content
assert "authorization" in content
assert "customization" in content
assert "inference" in content
assert "database" in content
assert "byok_rag" in content
assert "quota_handlers" in content

# check the whole deserialized JSON file content
assert content == {
"name": "test_name",
"service": {
"host": "localhost",
"port": 8080,
"auth_enabled": False,
"workers": 1,
"color_log": True,
"access_log": True,
"tls_config": {
"tls_certificate_path": "tests/configuration/server.crt",
"tls_key_password": "tests/configuration/password",
"tls_key_path": "tests/configuration/server.key",
},
"cors": {
"allow_credentials": False,
"allow_headers": [
"foo_header",
"bar_header",
"baz_header",
],
"allow_methods": [
"foo_method",
"bar_method",
"baz_method",
],
"allow_origins": [
"foo_origin",
"bar_origin",
"baz_origin",
],
},
},
"llama_stack": {
"url": None,
"use_as_library_client": True,
"api_key": "**********",
"library_client_config_path": "tests/configuration/run.yaml",
},
"user_data_collection": {
"feedback_enabled": False,
"feedback_storage": None,
"transcripts_enabled": False,
"transcripts_storage": None,
},
"mcp_servers": [],
"authentication": {
"module": "noop",
"skip_tls_verification": False,
"k8s_ca_cert_path": None,
"k8s_cluster_api": None,
"jwk_config": None,
},
"customization": None,
"inference": {
"default_provider": "default_provider",
"default_model": "default_model",
},
"database": {
"sqlite": None,
"postgres": {
"host": "localhost",
"port": 5432,
"db": "lightspeed_stack",
"user": "ls_user",
"password": "**********",
"ssl_mode": "require",
"gss_encmode": "disable",
"namespace": "lightspeed-stack",
"ca_cert_path": None,
},
},
"authorization": None,
"conversation_cache": {
"memory": None,
"postgres": None,
"sqlite": None,
"type": None,
},
"byok_rag": [],
"quota_handlers": {
"sqlite": None,
"postgres": None,
"limiters": [
{
"initial_quota": 1,
"name": "user_monthly_limits",
"period": "2 seconds",
"quota_increase": 10,
"type": "user_limiter",
},
{
"initial_quota": 2,
"name": "cluster_monthly_limits",
"period": "1 month",
"quota_increase": 20,
"type": "cluster_limiter",
},
],
"scheduler": {"period": 10},
"enable_token_history": True,
},
}
20 changes: 20 additions & 0 deletions tests/unit/models/config/test_quota_handlers_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
"""Unit tests for QuotaHandlersConfiguration model."""

from models.config import QuotaHandlersConfiguration, QuotaSchedulerConfiguration


def test_quota_handlers_configuration() -> None:
"""Test the quota handlers configuration."""
cfg = QuotaHandlersConfiguration(
sqlite=None,
postgres=None,
limiters=[],
scheduler=QuotaSchedulerConfiguration(period=10),
enable_token_history=False,
)
assert cfg is not None
assert cfg.sqlite is None
assert cfg.postgres is None
assert cfg.limiters == []
assert cfg.scheduler is not None
assert not cfg.enable_token_history
60 changes: 60 additions & 0 deletions tests/unit/models/config/test_quota_limiter_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
"""Unit tests for QuotaLimiterConfig model."""

import pytest

from models.config import QuotaLimiterConfiguration


def test_quota_limiter_configuration() -> None:
"""Test the default configuration."""
cfg = QuotaLimiterConfiguration(
type="cluster_limiter",
name="cluster_monthly_limits",
initial_quota=0,
quota_increase=10,
period="3 seconds",
)
assert cfg is not None
assert cfg.type == "cluster_limiter"
assert cfg.name == "cluster_monthly_limits"
assert cfg.initial_quota == 0
assert cfg.quota_increase == 10
assert cfg.period == "3 seconds"


def test_quota_limiter_configuration_improper_value_1() -> None:
"""Test the default configuration."""
with pytest.raises(ValueError, match="Input should be greater than or equal to 0"):
_ = QuotaLimiterConfiguration(
type="cluster_limiter",
name="cluster_monthly_limits",
initial_quota=-1,
quota_increase=10,
period="3 seconds",
)


def test_quota_limiter_configuration_improper_value_2() -> None:
"""Test the default configuration."""
with pytest.raises(ValueError, match="Input should be greater than or equal to 0"):
_ = QuotaLimiterConfiguration(
type="cluster_limiter",
name="cluster_monthly_limits",
initial_quota=1,
quota_increase=-10,
period="3 seconds",
)


def test_quota_limiter_configuration_improper_value_3() -> None:
"""Test the default configuration."""
with pytest.raises(
ValueError, match="Input should be 'user_limiter' or 'cluster_limiter'"
):
_ = QuotaLimiterConfiguration(
type="unknown_limiter",
name="cluster_monthly_limits",
initial_quota=1,
quota_increase=10,
period="3 seconds",
)
Loading
Loading