Skip to content

Commit 9b8378d

Browse files
authored
Merge pull request lightspeed-core#2071 from Jdubrick/add-saved-prompt-config
RHIDP-14303: add config fields for saved prompts
1 parent 7bad760 commit 9b8378d

5 files changed

Lines changed: 393 additions & 0 deletions

File tree

docs/openapi.json

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12223,6 +12223,11 @@
1222312223
],
1222412224
"title": "Agent skills",
1222512225
"description": "Agent skills configuration. Specifies paths to skill directories."
12226+
},
12227+
"saved_prompts": {
12228+
"$ref": "#/components/schemas/SavedPromptsConfiguration",
12229+
"title": "Saved prompts configuration",
12230+
"description": "Configuration for saved prompts feature limits including maximum prompts per user, display name length, and content length."
1222612231
}
1222712232
},
1222812233
"additionalProperties": false,
@@ -19266,6 +19271,53 @@
1926619271
"title": "SQLiteDatabaseConfiguration",
1926719272
"description": "SQLite database configuration."
1926819273
},
19274+
"SavedPromptsConfiguration": {
19275+
"properties": {
19276+
"max_prompts_per_user": {
19277+
"anyOf": [
19278+
{
19279+
"type": "integer",
19280+
"exclusiveMinimum": 0.0
19281+
},
19282+
{
19283+
"type": "null"
19284+
}
19285+
],
19286+
"title": "Max prompts per user",
19287+
"description": "Maximum number of saved prompts a user can create. Defaults to 50. Cannot exceed 200."
19288+
},
19289+
"max_display_name_length": {
19290+
"anyOf": [
19291+
{
19292+
"type": "integer",
19293+
"exclusiveMinimum": 0.0
19294+
},
19295+
{
19296+
"type": "null"
19297+
}
19298+
],
19299+
"title": "Max display name length",
19300+
"description": "Maximum character length for prompt display name (title). Defaults to 255. Cannot exceed 255."
19301+
},
19302+
"max_content_length": {
19303+
"anyOf": [
19304+
{
19305+
"type": "integer",
19306+
"exclusiveMinimum": 0.0
19307+
},
19308+
{
19309+
"type": "null"
19310+
}
19311+
],
19312+
"title": "Max content length",
19313+
"description": "Maximum character length for the prompt content body. Defaults to 10000. Cannot exceed 30000."
19314+
}
19315+
},
19316+
"additionalProperties": false,
19317+
"type": "object",
19318+
"title": "SavedPromptsConfiguration",
19319+
"description": "Configuration for saved prompts feature limits.\n\nControls the maximum number of prompts a user can save, the maximum\ndisplay name (title) length, and the maximum prompt content length.\nAll fields are optional and default to values defined in constants.\n\nAttributes:\n max_prompts_per_user: Maximum number of saved prompts allowed per user.\n max_display_name_length: Maximum character length for the prompt display name.\n max_content_length: Maximum character length for the prompt content body."
19320+
},
1926919321
"SearchRankingOptions": {
1927019322
"properties": {
1927119323
"ranker": {

src/constants.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -351,3 +351,15 @@
351351
# first version check.
352352
DEFAULT_MAX_RETRIES: Final[int] = 5
353353
DEFAULT_RETRY_DELAY: Final[int] = 2
354+
355+
# Saved prompts configuration defaults and upper bounds.
356+
# Defaults are used when a field is omitted from lightspeed-stack.yaml.
357+
# Upper bounds prevent operators from exceeding DB or application limits.
358+
SAVED_PROMPTS_DEFAULT_MAX_PER_USER: Final[int] = 50
359+
SAVED_PROMPTS_MAX_PER_USER_UPPER_BOUND: Final[int] = 200
360+
SAVED_PROMPTS_DEFAULT_MAX_DISPLAY_NAME_LENGTH: Final[int] = 255 # database column limit
361+
SAVED_PROMPTS_MAX_DISPLAY_NAME_LENGTH_UPPER_BOUND: Final[int] = (
362+
255 # database column limit
363+
)
364+
SAVED_PROMPTS_DEFAULT_MAX_CONTENT_LENGTH: Final[int] = 10_000
365+
SAVED_PROMPTS_MAX_CONTENT_LENGTH_UPPER_BOUND: Final[int] = 30_000

src/models/config.py

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2330,6 +2330,108 @@ class SkillsConfiguration(ConfigurationBase):
23302330
)
23312331

23322332

2333+
class SavedPromptsConfiguration(ConfigurationBase):
2334+
"""Configuration for saved prompts feature limits.
2335+
2336+
Controls the maximum number of prompts a user can save, the maximum
2337+
display name (title) length, and the maximum prompt content length.
2338+
All fields are optional and default to values defined in constants.
2339+
2340+
Attributes:
2341+
max_prompts_per_user: Maximum number of saved prompts allowed per user.
2342+
max_display_name_length: Maximum character length for the prompt display name.
2343+
max_content_length: Maximum character length for the prompt content body.
2344+
"""
2345+
2346+
max_prompts_per_user: Optional[PositiveInt] = Field(
2347+
default=None,
2348+
title="Max prompts per user",
2349+
description="Maximum number of saved prompts a user can create. "
2350+
f"Defaults to {constants.SAVED_PROMPTS_DEFAULT_MAX_PER_USER}. "
2351+
f"Cannot exceed {constants.SAVED_PROMPTS_MAX_PER_USER_UPPER_BOUND}.",
2352+
)
2353+
2354+
max_display_name_length: Optional[PositiveInt] = Field(
2355+
default=None,
2356+
title="Max display name length",
2357+
description="Maximum character length for prompt display name (title). "
2358+
f"Defaults to {constants.SAVED_PROMPTS_DEFAULT_MAX_DISPLAY_NAME_LENGTH}. "
2359+
f"Cannot exceed {constants.SAVED_PROMPTS_MAX_DISPLAY_NAME_LENGTH_UPPER_BOUND}.",
2360+
)
2361+
2362+
max_content_length: Optional[PositiveInt] = Field(
2363+
default=None,
2364+
title="Max content length",
2365+
description="Maximum character length for the prompt content body. "
2366+
f"Defaults to {constants.SAVED_PROMPTS_DEFAULT_MAX_CONTENT_LENGTH}. "
2367+
f"Cannot exceed {constants.SAVED_PROMPTS_MAX_CONTENT_LENGTH_UPPER_BOUND}.",
2368+
)
2369+
2370+
@model_validator(mode="after")
2371+
def apply_defaults_and_validate_bounds(self) -> Self:
2372+
"""Apply default values for None fields and validate upper bounds.
2373+
2374+
Logs an info message for each field that falls back to its default.
2375+
2376+
Returns:
2377+
Self: The validated model instance with defaults applied.
2378+
2379+
Raises:
2380+
ValueError: If any value exceeds its upper bound.
2381+
"""
2382+
if self.max_prompts_per_user is None:
2383+
self.max_prompts_per_user = constants.SAVED_PROMPTS_DEFAULT_MAX_PER_USER
2384+
logger.info(
2385+
"saved_prompts.max_prompts_per_user not configured, "
2386+
"using default: %d",
2387+
constants.SAVED_PROMPTS_DEFAULT_MAX_PER_USER,
2388+
)
2389+
elif (
2390+
self.max_prompts_per_user > constants.SAVED_PROMPTS_MAX_PER_USER_UPPER_BOUND
2391+
):
2392+
raise ValueError(
2393+
f"max_prompts_per_user ({self.max_prompts_per_user}) exceeds "
2394+
f"upper bound ({constants.SAVED_PROMPTS_MAX_PER_USER_UPPER_BOUND})."
2395+
)
2396+
2397+
if self.max_display_name_length is None:
2398+
self.max_display_name_length = (
2399+
constants.SAVED_PROMPTS_DEFAULT_MAX_DISPLAY_NAME_LENGTH
2400+
)
2401+
logger.info(
2402+
"saved_prompts.max_display_name_length not configured, "
2403+
"using default: %d",
2404+
constants.SAVED_PROMPTS_DEFAULT_MAX_DISPLAY_NAME_LENGTH,
2405+
)
2406+
elif (
2407+
self.max_display_name_length
2408+
> constants.SAVED_PROMPTS_MAX_DISPLAY_NAME_LENGTH_UPPER_BOUND
2409+
):
2410+
raise ValueError(
2411+
f"max_display_name_length ({self.max_display_name_length}) exceeds "
2412+
f"database column limit "
2413+
f"({constants.SAVED_PROMPTS_MAX_DISPLAY_NAME_LENGTH_UPPER_BOUND})."
2414+
)
2415+
2416+
if self.max_content_length is None:
2417+
self.max_content_length = constants.SAVED_PROMPTS_DEFAULT_MAX_CONTENT_LENGTH
2418+
logger.info(
2419+
"saved_prompts.max_content_length not configured, "
2420+
+ "using default: %d",
2421+
constants.SAVED_PROMPTS_DEFAULT_MAX_CONTENT_LENGTH,
2422+
)
2423+
elif (
2424+
self.max_content_length
2425+
> constants.SAVED_PROMPTS_MAX_CONTENT_LENGTH_UPPER_BOUND
2426+
):
2427+
raise ValueError(
2428+
f"max_content_length ({self.max_content_length}) exceeds "
2429+
f"upper bound ({constants.SAVED_PROMPTS_MAX_CONTENT_LENGTH_UPPER_BOUND})."
2430+
)
2431+
2432+
return self
2433+
2434+
23332435
class QuestionValidityConfig(ConfigurationBase):
23342436
"""Configuration for the question validity guardrail."""
23352437

@@ -2622,6 +2724,13 @@ class Configuration(ConfigurationBase):
26222724
description="Agent skills configuration. Specifies paths to skill directories.",
26232725
)
26242726

2727+
saved_prompts: SavedPromptsConfiguration = Field(
2728+
default_factory=SavedPromptsConfiguration,
2729+
title="Saved prompts configuration",
2730+
description="Configuration for saved prompts feature limits including "
2731+
"maximum prompts per user, display name length, and content length.",
2732+
)
2733+
26252734
@model_validator(mode="after")
26262735
def validate_mcp_auth_headers(self) -> Self:
26272736
"""

tests/unit/models/config/test_dump_configuration.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,12 @@
3434
"approval_retention_days": 30,
3535
}
3636

37+
_DEFAULT_SAVED_PROMPTS_DUMP: dict[str, int] = {
38+
"max_prompts_per_user": constants.SAVED_PROMPTS_DEFAULT_MAX_PER_USER,
39+
"max_display_name_length": constants.SAVED_PROMPTS_DEFAULT_MAX_DISPLAY_NAME_LENGTH,
40+
"max_content_length": constants.SAVED_PROMPTS_DEFAULT_MAX_CONTENT_LENGTH,
41+
}
42+
3743
_MCP_SERVER_DUMP_DEFAULTS: dict[str, Any] = {
3844
"authorization_headers": {},
3945
"headers": [],
@@ -223,6 +229,7 @@ def test_dump_configuration_minimal_cfg(tmp_path: Path) -> None:
223229
"enabled": False,
224230
"model": "cross-encoder/ms-marco-MiniLM-L6-v2",
225231
},
232+
"saved_prompts": _DEFAULT_SAVED_PROMPTS_DUMP,
226233
"skills": None,
227234
}
228235

@@ -445,6 +452,7 @@ def test_dump_configuration_valid_values(tmp_path: Path) -> None:
445452
"enabled": False,
446453
"model": "cross-encoder/ms-marco-MiniLM-L6-v2",
447454
},
455+
"saved_prompts": _DEFAULT_SAVED_PROMPTS_DUMP,
448456
"skills": None,
449457
}
450458

@@ -818,6 +826,7 @@ def test_dump_configuration_with_quota_limiters(tmp_path: Path) -> None:
818826
"enabled": False,
819827
"model": "cross-encoder/ms-marco-MiniLM-L6-v2",
820828
},
829+
"saved_prompts": _DEFAULT_SAVED_PROMPTS_DUMP,
821830
"skills": None,
822831
}
823832

@@ -1075,6 +1084,7 @@ def test_dump_configuration_with_quota_limiters_different_values(
10751084
"enabled": False,
10761085
"model": "cross-encoder/ms-marco-MiniLM-L6-v2",
10771086
},
1087+
"saved_prompts": _DEFAULT_SAVED_PROMPTS_DUMP,
10781088
"skills": None,
10791089
}
10801090

@@ -1312,6 +1322,7 @@ def test_dump_configuration_byok(tmp_path: Path) -> None:
13121322
"enabled": False,
13131323
"model": "cross-encoder/ms-marco-MiniLM-L6-v2",
13141324
},
1325+
"saved_prompts": _DEFAULT_SAVED_PROMPTS_DUMP,
13151326
"skills": None,
13161327
}
13171328

@@ -1529,6 +1540,7 @@ def test_dump_configuration_pg_namespace(tmp_path: Path) -> None:
15291540
"enabled": False,
15301541
"model": "cross-encoder/ms-marco-MiniLM-L6-v2",
15311542
},
1543+
"saved_prompts": _DEFAULT_SAVED_PROMPTS_DUMP,
15321544
"skills": None,
15331545
}
15341546

@@ -1906,6 +1918,7 @@ def test_dump_configuration_allow_degraded_mode(tmp_path: Path) -> None:
19061918
"enabled": False,
19071919
"model": "cross-encoder/ms-marco-MiniLM-L6-v2",
19081920
},
1921+
"saved_prompts": _DEFAULT_SAVED_PROMPTS_DUMP,
19091922
"skills": None,
19101923
}
19111924

@@ -2129,6 +2142,7 @@ def test_dump_configuration_max_retries_settings(tmp_path: Path) -> None:
21292142
"enabled": False,
21302143
"model": "cross-encoder/ms-marco-MiniLM-L6-v2",
21312144
},
2145+
"saved_prompts": _DEFAULT_SAVED_PROMPTS_DUMP,
21322146
"skills": None,
21332147
}
21342148

@@ -2352,6 +2366,7 @@ def test_dump_configuration_retry_count_settings(tmp_path: Path) -> None:
23522366
"enabled": False,
23532367
"model": "cross-encoder/ms-marco-MiniLM-L6-v2",
23542368
},
2369+
"saved_prompts": _DEFAULT_SAVED_PROMPTS_DUMP,
23552370
"skills": None,
23562371
}
23572372

@@ -2582,5 +2597,6 @@ def test_dump_configuration_specific_compaction_values(tmp_path: Path) -> None:
25822597
"enabled": False,
25832598
"model": "cross-encoder/ms-marco-MiniLM-L6-v2",
25842599
},
2600+
"saved_prompts": _DEFAULT_SAVED_PROMPTS_DUMP,
25852601
"skills": None,
25862602
}

0 commit comments

Comments
 (0)