Skip to content

Commit 93e6dc2

Browse files
authored
Merge pull request #2093 from Jdubrick/add-saved-prompts-config-get
RHIDP-14304: add GET endpoint for saved prompts config
2 parents 1aea36a + daa700b commit 93e6dc2

11 files changed

Lines changed: 621 additions & 5 deletions

File tree

docs/openapi.json

Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6931,6 +6931,169 @@
69316931
}
69326932
}
69336933
},
6934+
"/v1/saved-prompts/config": {
6935+
"get": {
6936+
"tags": [
6937+
"saved-prompts"
6938+
],
6939+
"summary": "Get Saved Prompts Config Handler",
6940+
"description": "Handle requests to the GET /saved-prompts/config endpoint.\n\nProcess GET requests that return saved prompts configuration limits so\nconsuming services can provide limits consistent with what the server\nwill enforce. For example:\n\n curl http://localhost:8080/v1/saved-prompts/config\n\n### Parameters:\n- request: The incoming HTTP request (used by middleware).\n- auth: Authentication tuple from the auth dependency (used by middleware).\n\n### Raises:\n- HTTPException: with status 401 for unauthorized access.\n- HTTPException: with status 403 if permission is denied.\n- HTTPException: with status 500 and a detail object containing `response`\n and `cause` when service configuration is wrong or incomplete.\n- HTTPException: with status 503 and a detail object containing `response`\n and `cause` when unable to connect to backend services.\n\n### Returns:\n- SavedPromptsConfigResponse: Saved prompts configuration limits.",
6941+
"operationId": "get_saved_prompts_config_handler_v1_saved_prompts_config_get",
6942+
"responses": {
6943+
"200": {
6944+
"description": "Successful response",
6945+
"content": {
6946+
"application/json": {
6947+
"schema": {
6948+
"$ref": "#/components/schemas/SavedPromptsConfigResponse"
6949+
},
6950+
"example": {
6951+
"max_content_length": 10000,
6952+
"max_display_name_length": 255,
6953+
"max_prompts_per_user": 50
6954+
}
6955+
}
6956+
}
6957+
},
6958+
"401": {
6959+
"description": "Unauthorized",
6960+
"content": {
6961+
"application/json": {
6962+
"schema": {
6963+
"$ref": "#/components/schemas/UnauthorizedResponse"
6964+
},
6965+
"examples": {
6966+
"missing header": {
6967+
"value": {
6968+
"detail": {
6969+
"cause": "No Authorization header found",
6970+
"response": "Missing or invalid credentials provided by client"
6971+
}
6972+
}
6973+
},
6974+
"missing token": {
6975+
"value": {
6976+
"detail": {
6977+
"cause": "No token found in Authorization header",
6978+
"response": "Missing or invalid credentials provided by client"
6979+
}
6980+
}
6981+
},
6982+
"expired token": {
6983+
"value": {
6984+
"detail": {
6985+
"cause": "Token has expired",
6986+
"response": "Missing or invalid credentials provided by client"
6987+
}
6988+
}
6989+
},
6990+
"invalid signature": {
6991+
"value": {
6992+
"detail": {
6993+
"cause": "Invalid token signature",
6994+
"response": "Missing or invalid credentials provided by client"
6995+
}
6996+
}
6997+
},
6998+
"invalid key": {
6999+
"value": {
7000+
"detail": {
7001+
"cause": "Token signed by unknown key",
7002+
"response": "Missing or invalid credentials provided by client"
7003+
}
7004+
}
7005+
},
7006+
"missing claim": {
7007+
"value": {
7008+
"detail": {
7009+
"cause": "Token missing claim: user_id",
7010+
"response": "Missing or invalid credentials provided by client"
7011+
}
7012+
}
7013+
},
7014+
"invalid k8s token": {
7015+
"value": {
7016+
"detail": {
7017+
"cause": "Invalid or expired Kubernetes token",
7018+
"response": "Missing or invalid credentials provided by client"
7019+
}
7020+
}
7021+
},
7022+
"invalid jwk token": {
7023+
"value": {
7024+
"detail": {
7025+
"cause": "Authentication key server returned invalid data",
7026+
"response": "Missing or invalid credentials provided by client"
7027+
}
7028+
}
7029+
}
7030+
}
7031+
}
7032+
}
7033+
},
7034+
"403": {
7035+
"description": "Permission denied",
7036+
"content": {
7037+
"application/json": {
7038+
"schema": {
7039+
"$ref": "#/components/schemas/ForbiddenResponse"
7040+
},
7041+
"examples": {
7042+
"endpoint": {
7043+
"value": {
7044+
"detail": {
7045+
"cause": "User 6789 is not authorized to access this endpoint.",
7046+
"response": "User does not have permission to access this endpoint"
7047+
}
7048+
}
7049+
}
7050+
}
7051+
}
7052+
}
7053+
},
7054+
"500": {
7055+
"description": "Internal server error",
7056+
"content": {
7057+
"application/json": {
7058+
"schema": {
7059+
"$ref": "#/components/schemas/InternalServerErrorResponse"
7060+
},
7061+
"examples": {
7062+
"configuration": {
7063+
"value": {
7064+
"detail": {
7065+
"cause": "Lightspeed Stack configuration has not been initialized.",
7066+
"response": "Configuration is not loaded"
7067+
}
7068+
}
7069+
}
7070+
}
7071+
}
7072+
}
7073+
},
7074+
"503": {
7075+
"description": "Service unavailable",
7076+
"content": {
7077+
"application/json": {
7078+
"schema": {
7079+
"$ref": "#/components/schemas/ServiceUnavailableResponse"
7080+
},
7081+
"examples": {
7082+
"kubernetes api": {
7083+
"value": {
7084+
"detail": {
7085+
"cause": "Failed to connect to Kubernetes API: Service Unavailable (status 503)",
7086+
"response": "Unable to connect to Kubernetes API"
7087+
}
7088+
}
7089+
}
7090+
}
7091+
}
7092+
}
7093+
}
7094+
}
7095+
}
7096+
},
69347097
"/v1/feedback": {
69357098
"post": {
69367099
"tags": [
@@ -19271,6 +19434,50 @@
1927119434
"title": "SQLiteDatabaseConfiguration",
1927219435
"description": "SQLite database configuration."
1927319436
},
19437+
"SavedPromptsConfigResponse": {
19438+
"properties": {
19439+
"max_prompts_per_user": {
19440+
"type": "integer",
19441+
"title": "Max Prompts Per User",
19442+
"description": "Maximum number of saved prompts allowed per user",
19443+
"examples": [
19444+
50
19445+
]
19446+
},
19447+
"max_display_name_length": {
19448+
"type": "integer",
19449+
"title": "Max Display Name Length",
19450+
"description": "Maximum character length for prompt display name",
19451+
"examples": [
19452+
255
19453+
]
19454+
},
19455+
"max_content_length": {
19456+
"type": "integer",
19457+
"title": "Max Content Length",
19458+
"description": "Maximum character length for prompt content body",
19459+
"examples": [
19460+
10000
19461+
]
19462+
}
19463+
},
19464+
"additionalProperties": false,
19465+
"type": "object",
19466+
"required": [
19467+
"max_prompts_per_user",
19468+
"max_display_name_length",
19469+
"max_content_length"
19470+
],
19471+
"title": "SavedPromptsConfigResponse",
19472+
"description": "Saved prompts configuration limits returned to consuming services.\n\nAttributes:\n max_prompts_per_user: Maximum number of saved prompts allowed per user.\n max_display_name_length: Maximum character length for prompt display name.\n max_content_length: Maximum character length for prompt content body.",
19473+
"examples": [
19474+
{
19475+
"max_content_length": 10000,
19476+
"max_display_name_length": 255,
19477+
"max_prompts_per_user": 50
19478+
}
19479+
]
19480+
},
1927419481
"SavedPromptsConfiguration": {
1927519482
"properties": {
1927619483
"max_prompts_per_user": {
@@ -21085,6 +21292,10 @@
2108521292
"name": "root",
2108621293
"description": "Service root."
2108721294
},
21295+
{
21296+
"name": "saved-prompts",
21297+
"description": "Saved prompts configuration and management."
21298+
},
2108821299
{
2108921300
"name": "shields",
2109021301
"description": "Safety shields."

src/app/endpoints/saved_prompts.py

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
"""Handler for REST API calls to manage saved prompts."""
2+
3+
from typing import Annotated, Any
4+
5+
from fastapi import APIRouter, Depends, HTTPException, Request
6+
7+
from authentication import get_auth_dependency
8+
from authentication.interface import AuthTuple
9+
from authorization.middleware import authorize
10+
from configuration import configuration
11+
from log import get_logger
12+
from models.api.responses.constants import UNAUTHORIZED_OPENAPI_EXAMPLES
13+
from models.api.responses.error import (
14+
ForbiddenResponse,
15+
InternalServerErrorResponse,
16+
ServiceUnavailableResponse,
17+
UnauthorizedResponse,
18+
)
19+
from models.api.responses.successful import SavedPromptsConfigResponse
20+
from models.config import Action
21+
from utils.endpoints import check_configuration_loaded
22+
23+
logger = get_logger(__name__)
24+
router = APIRouter(tags=["saved-prompts"])
25+
26+
27+
get_saved_prompts_config_responses: dict[int | str, dict[str, Any]] = {
28+
200: SavedPromptsConfigResponse.openapi_response(),
29+
401: UnauthorizedResponse.openapi_response(examples=UNAUTHORIZED_OPENAPI_EXAMPLES),
30+
403: ForbiddenResponse.openapi_response(examples=["endpoint"]),
31+
500: InternalServerErrorResponse.openapi_response(examples=["configuration"]),
32+
503: ServiceUnavailableResponse.openapi_response(examples=["kubernetes api"]),
33+
}
34+
35+
36+
@router.get("/saved-prompts/config", responses=get_saved_prompts_config_responses)
37+
@authorize(Action.GET_CONFIG)
38+
async def get_saved_prompts_config_handler(
39+
auth: Annotated[AuthTuple, Depends(get_auth_dependency())],
40+
request: Request,
41+
) -> SavedPromptsConfigResponse:
42+
"""
43+
Handle requests to the GET /saved-prompts/config endpoint.
44+
45+
Process GET requests that return saved prompts configuration limits so
46+
consuming services can provide limits consistent with what the server
47+
will enforce. For example:
48+
49+
curl http://localhost:8080/v1/saved-prompts/config
50+
51+
### Parameters:
52+
- request: The incoming HTTP request (used by middleware).
53+
- auth: Authentication tuple from the auth dependency (used by middleware).
54+
55+
### Raises:
56+
- HTTPException: with status 401 for unauthorized access.
57+
- HTTPException: with status 403 if permission is denied.
58+
- HTTPException: with status 500 and a detail object containing `response`
59+
and `cause` when service configuration is wrong or incomplete.
60+
- HTTPException: with status 503 and a detail object containing `response`
61+
and `cause` when unable to connect to backend services.
62+
63+
### Returns:
64+
- SavedPromptsConfigResponse: Saved prompts configuration limits.
65+
"""
66+
_ = auth
67+
_ = request
68+
69+
check_configuration_loaded(configuration)
70+
71+
saved_prompts_config = configuration.configuration.saved_prompts
72+
max_prompts_per_user = saved_prompts_config.max_prompts_per_user
73+
max_display_name_length = saved_prompts_config.max_display_name_length
74+
max_content_length = saved_prompts_config.max_content_length
75+
if (
76+
max_prompts_per_user is None
77+
or max_display_name_length is None
78+
or max_content_length is None
79+
):
80+
logger.error("Saved prompts configuration limits are not set")
81+
error_response = InternalServerErrorResponse.generic()
82+
raise HTTPException(**error_response.model_dump())
83+
84+
return SavedPromptsConfigResponse(
85+
max_prompts_per_user=max_prompts_per_user,
86+
max_display_name_length=max_display_name_length,
87+
max_content_length=max_content_length,
88+
)

src/app/main.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,10 @@
5858
{"name": "responses", "description": "OpenAI-compatible Responses API."},
5959
{"name": "rlsapi-v1", "description": "RLS API v1 (inference)."},
6060
{"name": "root", "description": "Service root."},
61+
{
62+
"name": "saved-prompts",
63+
"description": "Saved prompts configuration and management.",
64+
},
6165
{"name": "shields", "description": "Safety shields."},
6266
{"name": "streaming_query", "description": "Streaming query (SSE)."},
6367
{"name": "streaming_query_interrupt", "description": "Streaming interrupt."},

src/app/routers.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
# RHEL Lightspeed rlsapi v1 compatibility
2626
rlsapi_v1,
2727
root,
28+
saved_prompts,
2829
shields,
2930
stream_interrupt,
3031
streaming_query,
@@ -63,6 +64,7 @@ def include_routers(app: FastAPI) -> None:
6364
app.include_router(streaming_query.router, prefix="/v1")
6465
app.include_router(stream_interrupt.router, prefix="/v1")
6566
app.include_router(config.router, prefix="/v1")
67+
app.include_router(saved_prompts.router, prefix="/v1")
6668
app.include_router(feedback.router, prefix="/v1")
6769
app.include_router(conversations_v1.router, prefix="/v1")
6870
app.include_router(conversations_v2.router, prefix="/v2")

src/models/api/responses/successful/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
RlsapiV1InferData,
5050
RlsapiV1InferResponse,
5151
)
52+
from models.api.responses.successful.saved_prompts import SavedPromptsConfigResponse
5253
from models.api.responses.successful.vector_stores import (
5354
FileResponse,
5455
VectorStoreDeleteResponse,
@@ -89,6 +90,7 @@
8990
"ResponsesResponse",
9091
"RlsapiV1InferData",
9192
"RlsapiV1InferResponse",
93+
"SavedPromptsConfigResponse",
9294
"ShieldsResponse",
9395
"StatusResponse",
9496
"StreamingInterruptResponse",

0 commit comments

Comments
 (0)