Skip to content

Commit c7e74f5

Browse files
authored
Merge branch 'main' into anx/streaming_query
2 parents abccf07 + 310a8f2 commit c7e74f5

12 files changed

Lines changed: 380 additions & 47 deletions

File tree

build-args-konflux.conf

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
BUILDER_BASE_IMAGE=registry.redhat.io/rhai/base-image-cpu-rhel9:3.2
2+
BUILDER_DNF_COMMAND=dnf
3+
RUNTIME_BASE_IMAGE=registry.redhat.io/rhai/base-image-cpu-rhel9:3.2
4+
RUNTIME_DNF_COMMAND=dnf

redhat.repo

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
[codeready-builder-for-rhel-9-$basearch-eus-rpms]
2+
name = Red Hat CodeReady Linux Builder for RHEL 9 $basearch - Extended Update Support (RPMs)
3+
baseurl = https://cdn.redhat.com/content/eus/rhel9/9.6/$basearch/codeready-builder/os
4+
enabled = 1
5+
gpgcheck = 1
6+
gpgkey = file:///etc/pki/rpm-gpg/RPM-GPG-KEY-redhat-release
7+
sslverify = 1
8+
sslcacert = /etc/rhsm/ca/redhat-uep.pem
9+
sslverifystatus = 1
10+
metadata_expire = 86400
11+
enabled_metadata = 0
12+
sslclientkey = $SSL_CLIENT_KEY
13+
sslclientcert = $SSL_CLIENT_CERT
14+
15+
[rhel-9-for-$basearch-appstream-eus-rpms]
16+
name = Red Hat Enterprise Linux 9 for $basearch - AppStream - Extended Update Support (RPMs)
17+
baseurl = https://cdn.redhat.com/content/eus/rhel9/9.6/$basearch/appstream/os
18+
enabled = 1
19+
gpgcheck = 1
20+
gpgkey = file:///etc/pki/rpm-gpg/RPM-GPG-KEY-redhat-release
21+
sslverify = 1
22+
sslcacert = /etc/rhsm/ca/redhat-uep.pem
23+
sslverifystatus = 1
24+
metadata_expire = 86400
25+
enabled_metadata = 0
26+
sslclientkey = $SSL_CLIENT_KEY
27+
sslclientcert = $SSL_CLIENT_CERT
28+
29+
[rhel-9-for-$basearch-baseos-eus-rpms]
30+
name = Red Hat Enterprise Linux 9 for $basearch - BaseOS - Extended Update Support (RPMs)
31+
baseurl = https://cdn.redhat.com/content/eus/rhel9/9.6/$basearch/baseos/os
32+
enabled = 1
33+
gpgcheck = 1
34+
gpgkey = file:///etc/pki/rpm-gpg/RPM-GPG-KEY-redhat-release
35+
sslverify = 1
36+
sslcacert = /etc/rhsm/ca/redhat-uep.pem
37+
sslverifystatus = 1
38+
metadata_expire = 86400
39+
enabled_metadata = 0
40+
sslclientkey = $SSL_CLIENT_KEY
41+
sslclientcert = $SSL_CLIENT_CERT
42+
43+
[rhocp-4.17-for-rhel-9-$basearch-rpms]
44+
name = Red Hat OpenShift Container Platform 4.17 for RHEL 9 $basearch (RPMs)
45+
baseurl = https://cdn.redhat.com/content/dist/layered/rhel9/$basearch/rhocp/4.17/os
46+
enabled = 0
47+
gpgcheck = 1
48+
gpgkey = file:///etc/pki/rpm-gpg/RPM-GPG-KEY-redhat-release
49+
sslverify = 1
50+
sslcacert = /etc/rhsm/ca/redhat-uep.pem
51+
sslverifystatus = 1
52+
metadata_expire = 86400
53+
enabled_metadata = 0
54+
sslclientkey = $SSL_CLIENT_KEY
55+
sslclientcert = $SSL_CLIENT_CERT
56+
57+
[rhocp-4.17-for-rhel-9-$basearch-source-rpms]
58+
name = Red Hat OpenShift Container Platform 4.17 for RHEL 9 $basearch (Source RPMs)
59+
baseurl = https://cdn.redhat.com/content/dist/layered/rhel9/$basearch/rhocp/4.17/source/SRPMS
60+
enabled = 0
61+
gpgcheck = 1
62+
gpgkey = file:///etc/pki/rpm-gpg/RPM-GPG-KEY-redhat-release
63+
sslverify = 1
64+
sslcacert = /etc/rhsm/ca/redhat-uep.pem
65+
sslverifystatus = 1
66+
metadata_expire = 86400
67+
enabled_metadata = 0
68+
sslclientkey = $SSL_CLIENT_KEY
69+
sslclientcert = $SSL_CLIENT_CERT

src/app/endpoints/query.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@
22

33
"""Handler for REST API call to provide answer to query using Response API."""
44

5-
"""Handler for REST API call to provide answer to query using Response API."""
6-
75
import datetime
86
import logging
97
from typing import Annotated, Any, cast
@@ -51,6 +49,7 @@
5149
store_query_results,
5250
update_azure_token,
5351
validate_attachments_metadata,
52+
validate_model_provider_override,
5453
)
5554
from utils.quota import check_tokens_available, get_available_quotas
5655
from utils.responses import (
@@ -124,13 +123,14 @@ async def query_endpoint_handler(
124123
"""
125124
check_configuration_loaded(configuration)
126125

127-
started_at = datetime.datetime.now(datetime.timezone.utc).strftime(
128-
"%Y-%m-%dT%H:%M:%SZ"
129-
)
126+
started_at = datetime.now(UTC).strftime("%Y-%m-%dT%H:%M:%SZ")
130127
user_id, _, _skip_userid_check, token = auth
131128
# Check token availability
132129
check_tokens_available(configuration.quota_limiters, user_id)
133130

131+
# Enforce RBAC: optionally disallow overriding model/provider in requests
132+
validate_model_provider_override(query_request, request.state.authorized_actions)
133+
134134
# Validate attachments if provided
135135
if query_request.attachments:
136136
validate_attachments_metadata(query_request.attachments)

src/app/endpoints/streaming_query.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,12 @@ async def streaming_query_endpoint_handler( # pylint: disable=too-many-locals
226226
doc_ids_from_chunks=doc_ids_from_chunks,
227227
)
228228

229+
response_media_type = (
230+
MEDIA_TYPE_TEXT
231+
if query_request.media_type == MEDIA_TYPE_TEXT
232+
else MEDIA_TYPE_EVENT_STREAM
233+
)
234+
229235
return StreamingResponse(
230236
generate_response(
231237
generator=generator,

src/app/main.py

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -74,12 +74,6 @@ async def lifespan(_app: FastAPI) -> AsyncIterator[None]:
7474
)
7575
raise
7676

77-
# try:
78-
# await client.vector_stores.delete(vector_store_id="portal-rag")
79-
# logger.info("Successfully deregistered vector store: portal-rag")
80-
# except Exception as e:
81-
# logger.warning("Failed to deregister vector store 'portal-rag': %s", e)
82-
8377
logger.info("Registering MCP servers")
8478
await register_mcp_servers_async(logger, configuration.configuration)
8579
get_logger("app.endpoints.handlers")

src/authentication/rh_identity.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@
1111

1212
from fastapi import HTTPException, Request
1313

14-
from authentication.interface import AuthInterface, AuthTuple
14+
from authentication.interface import NO_AUTH_TUPLE, AuthInterface, AuthTuple
15+
from configuration import configuration
1516
from constants import DEFAULT_VIRTUAL_PATH, NO_USER_TOKEN
1617

1718
logger = logging.getLogger(__name__)
@@ -215,6 +216,10 @@ async def __call__(self, request: Request) -> AuthTuple:
215216
# Extract header
216217
identity_header = request.headers.get("x-rh-identity")
217218
if not identity_header:
219+
# Skip auth for health probes when configured
220+
if request.url.path.endswith(("/readiness", "/liveness")):
221+
if configuration.authentication_configuration.skip_for_health_probes:
222+
return NO_AUTH_TUPLE
218223
logger.warning("Missing x-rh-identity header")
219224
raise HTTPException(status_code=401, detail="Missing x-rh-identity header")
220225

src/models/responses.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from pydantic import BaseModel, Field
99
from pydantic_core import SchemaError
1010

11+
from constants import MEDIA_TYPE_EVENT_STREAM
1112
from models.config import Action, Configuration
1213
from quota.quota_exceed_error import QuotaExceedError
1314
from utils.types import RAGChunk, ReferencedDocument, ToolCallSummary, ToolResultSummary
@@ -485,8 +486,8 @@ def openapi_response(cls) -> dict[str, Any]:
485486
raise SchemaError(f"Examples not found in {cls.__name__}")
486487
example_value = model_examples[0]
487488
content = {
488-
"text/event-stream": {
489-
"schema": {"type": "string", "format": "text/event-stream"},
489+
MEDIA_TYPE_EVENT_STREAM: {
490+
"schema": {"type": "string", "format": MEDIA_TYPE_EVENT_STREAM},
490491
"example": example_value,
491492
}
492493
}

tests/benchmarks/test_app_database.py

Lines changed: 77 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,11 @@
88
from pytest_benchmark.fixture import BenchmarkFixture
99
from datetime import UTC, datetime
1010

11+
import psycopg2
12+
1113
from app import database
1214
from app.database import get_session
13-
from configuration import configuration
15+
from configuration import configuration, AppConfig
1416
from utils.suid import get_suid
1517
from models.database.conversations import UserConversation
1618

@@ -20,8 +22,8 @@
2022
LARGE_DB_RECORDS_COUNT = 10000
2123

2224

23-
@pytest.fixture(name="configuration_filename")
24-
def configuration_filename_fixture() -> str:
25+
@pytest.fixture(name="configuration_filename_sqlite")
26+
def configuration_filename_sqlite_fixture() -> str:
2527
"""Retrieve configuration file name to be used by benchmarks.
2628
2729
Parameters:
@@ -33,8 +35,21 @@ def configuration_filename_fixture() -> str:
3335
return "tests/configuration/benchmarks-sqlite.yaml"
3436

3537

38+
@pytest.fixture(name="configuration_filename_postgres")
39+
def configuration_filename_postgres_fixture() -> str:
40+
"""Retrieve configuration file name to be used by benchmarks.
41+
42+
Parameters:
43+
None
44+
45+
Returns:
46+
str: Path to the benchmark configuration file to load.
47+
"""
48+
return "tests/configuration/benchmarks-postgres.yaml"
49+
50+
3651
@pytest.fixture(name="sqlite_database")
37-
def sqlite_database_fixture(configuration_filename: str, tmp_path: Path) -> None:
52+
def sqlite_database_fixture(configuration_filename_sqlite: str, tmp_path: Path) -> None:
3853
"""Initialize a temporary SQLite database for benchmarking.
3954
4055
This fixture:
@@ -44,14 +59,14 @@ def sqlite_database_fixture(configuration_filename: str, tmp_path: Path) -> None
4459
- Initializes the DB engine and creates required tables.
4560
4661
Parameters:
47-
configuration_filename (str): Path to the YAML configuration file to load.
62+
configuration_filename_sqlite (str): Path to the YAML configuration file to load.
4863
tmp_path (Path): pytest-provided temporary directory for creating the DB file.
4964
5065
Raises:
5166
AssertionError: If the configuration does not include an sqlite configuration.
5267
"""
5368
# try to load the configuration containing SQLite database setup
54-
configuration.load_configuration(configuration_filename)
69+
configuration.load_configuration(configuration_filename_sqlite)
5570
assert configuration.database_configuration.sqlite is not None
5671

5772
# we need to start each benchmark with empty database
@@ -62,6 +77,62 @@ def sqlite_database_fixture(configuration_filename: str, tmp_path: Path) -> None
6277
database.create_tables()
6378

6479

80+
def drop_postgres_tables(configuration: AppConfig) -> None:
81+
"""Drop postgres tables used by benchmarks.
82+
83+
The tables will be re-created so every benchmark start with fresh DB.
84+
"""
85+
86+
pgconfig = configuration.database_configuration.postgres
87+
assert pgconfig is not None
88+
89+
# try to connect to Postgres
90+
conn = psycopg2.connect(
91+
database=pgconfig.db,
92+
user=pgconfig.user,
93+
password=pgconfig.password.get_secret_value(),
94+
host=pgconfig.host,
95+
port=pgconfig.port,
96+
)
97+
98+
# try to drop tables used by benchmarks
99+
try:
100+
with conn.cursor() as cursor:
101+
cursor.execute("DROP TABLE IF EXISTS user_turn;")
102+
cursor.execute("DROP TABLE IF EXISTS user_conversation;")
103+
conn.commit()
104+
finally:
105+
# closing the connection
106+
conn.close()
107+
108+
109+
@pytest.fixture(name="postgres_database")
110+
def postgres_database_fixture(configuration_filename_postgres: str) -> None:
111+
"""Initialize a temporary postgres database for benchmarking.
112+
113+
This fixture:
114+
- Loads the provided configuration file.
115+
- Ensures an Postgres configuration is present.
116+
- Initializes the DB engine and creates required tables.
117+
118+
Parameters:
119+
configuration_filename_postgres (str): Path to the YAML configuration file to load.
120+
121+
Raises:
122+
AssertionError: If the configuration does not include an postgres configuration.
123+
"""
124+
# try to load the configuration containing postgres database setup
125+
configuration.load_configuration(configuration_filename_postgres)
126+
assert configuration.database_configuration.postgres is not None
127+
128+
# make sure all tables will be re-initialized
129+
drop_postgres_tables(configuration)
130+
131+
# initialize database session and create tables
132+
database.initialize_database()
133+
database.create_tables()
134+
135+
65136
def generate_provider() -> str:
66137
"""Return a randomly chosen provider name.
67138
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
name: Lightspeed Core Service (LCS)
2+
service:
3+
host: 0.0.0.0
4+
port: 8080
5+
base_url: http://localhost:8080
6+
auth_enabled: false
7+
workers: 1
8+
color_log: true
9+
access_log: true
10+
llama_stack:
11+
# Uses a remote llama-stack service
12+
# The instance would have already been started with a llama-stack-run.yaml file
13+
use_as_library_client: false
14+
# Alternative for "as library use"
15+
# use_as_library_client: true
16+
# library_client_config_path: <path-to-llama-stack-run.yaml-file>
17+
url: http://llama-stack:8321
18+
api_key: xyzzy
19+
user_data_collection:
20+
feedback_enabled: true
21+
feedback_storage: "/tmp/data/feedback"
22+
transcripts_enabled: true
23+
transcripts_storage: "/tmp/data/transcripts"
24+
25+
database:
26+
postgres:
27+
host: 127.0.0.1
28+
db: test
29+
user: tester
30+
password: 123qwe
31+
ssl_mode: disable
32+
gss_encmode: disable
33+
34+
# Conversation cache for storing Q&A history
35+
conversation_cache:
36+
type: "sqlite"
37+
sqlite:
38+
db_path: "/tmp/benchmarks-conversation-cache.db" # Persistent across requests, can be deleted between test runs
39+
# host="localhost",
40+
# port=5432,
41+
# db="test",
42+
# user="testur",
43+
# password="123qwe",
44+
# namespace="public",
45+
46+
authentication:
47+
module: "noop"

0 commit comments

Comments
 (0)