Skip to content

Commit 35dac4d

Browse files
committed
feat: add tests for ragas remote provider
1 parent 0a99948 commit 35dac4d

File tree

4 files changed

+308
-9
lines changed

4 files changed

+308
-9
lines changed

tests/llama_stack/conftest.py

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@
3030

3131
LOGGER = get_logger(name=__name__)
3232

33+
distribution_name = generate_random_name(prefix="llama-stack-distribution")
34+
3335

3436
@pytest.fixture(scope="class")
3537
def enabled_llama_stack_operator(dsc_resource: DataScienceCluster) -> Generator[DataScienceCluster, Any, Any]:
@@ -87,6 +89,14 @@ def llama_stack_server_config(
8789
- fms_orchestrator_url_fixture: Fixture name to get FMS orchestrator URL from
8890
- vector_io_provider: Vector I/O provider type ("milvus" or "milvus-remote")
8991
- llama_stack_storage_size: Storage size for the deployment
92+
- embedding_model: Embedding model identifier for inference
93+
- kubeflow_llama_stack_url: LlamaStack service URL for Kubeflow
94+
- kubeflow_pipelines_endpoint: Kubeflow Pipelines API endpoint URL
95+
- kubeflow_namespace: Namespace for Kubeflow resources
96+
- kubeflow_base_image: Base container image for Kubeflow pipelines
97+
- kubeflow_results_s3_prefix: S3 prefix for storing Kubeflow results
98+
- kubeflow_s3_credentials_secret_name: Secret name for S3 credentials
99+
- kubeflow_pipelines_token: Authentication token for Kubeflow Pipelines
90100
91101
Example:
92102
@pytest.mark.parametrize("llama_stack_server_config",
@@ -136,6 +146,44 @@ def test_with_remote_milvus(llama_stack_server_config):
136146
if embedding_model:
137147
env_vars.append({"name": "EMBEDDING_MODEL", "value": embedding_model})
138148

149+
# Kubeflow-related environment variables
150+
if params.get("enable_ragas_remote"):
151+
# Get fixtures only when Ragas Remote/Kubeflow is enabled
152+
model_namespace = request.getfixturevalue(argname="model_namespace")
153+
current_client_token = request.getfixturevalue(argname="current_client_token")
154+
dspa_route = request.getfixturevalue(argname="dspa_route")
155+
dspa_s3_secret = request.getfixturevalue(argname="dspa_s3_secret")
156+
157+
# KUBEFLOW_LLAMA_STACK_URL: Build from LlamaStackDistribution service
158+
env_vars.append({
159+
"name": "KUBEFLOW_LLAMA_STACK_URL",
160+
"value": f"http://{distribution_name}-service.{model_namespace.name}.svc.cluster.local:8321",
161+
})
162+
163+
# KUBEFLOW_PIPELINES_ENDPOINT: Get from DSPA route
164+
env_vars.append({"name": "KUBEFLOW_PIPELINES_ENDPOINT", "value": f"https://{dspa_route.instance.spec.host}"})
165+
166+
# KUBEFLOW_NAMESPACE: Use model namespace
167+
env_vars.append({"name": "KUBEFLOW_NAMESPACE", "value": model_namespace.name})
168+
169+
# KUBEFLOW_BASE_IMAGE: Use default or override from params
170+
env_vars.append({
171+
"name": "KUBEFLOW_BASE_IMAGE",
172+
"value": params.get("kubeflow_base_image", "quay.io/diegosquayorg/my-ragas-provider-image:latest"),
173+
})
174+
175+
# KUBEFLOW_RESULTS_S3_PREFIX: Build from MinIO bucket
176+
env_vars.append({
177+
"name": "KUBEFLOW_RESULTS_S3_PREFIX",
178+
"value": params.get("kubeflow_results_s3_prefix", "s3://llms/ragas-results"),
179+
})
180+
181+
# KUBEFLOW_S3_CREDENTIALS_SECRET_NAME: Use DSPA secret name
182+
env_vars.append({"name": "KUBEFLOW_S3_CREDENTIALS_SECRET_NAME", "value": dspa_s3_secret.name})
183+
184+
# KUBEFLOW_PIPELINES_TOKEN: Get from current client token
185+
env_vars.append({"name": "KUBEFLOW_PIPELINES_TOKEN", "value": str(current_client_token)})
186+
139187
# Depending on parameter vector_io_provider, deploy vector_io provider and obtain required env_vars
140188
vector_io_provider = params.get("vector_io_provider") or "milvus"
141189
env_vars_vector_io = vector_io_provider_deployment_config_factory(provider_name=vector_io_provider)
@@ -189,7 +237,6 @@ def llama_stack_distribution(
189237
llama_stack_server_config: Dict[str, Any],
190238
) -> Generator[LlamaStackDistribution, None, None]:
191239
# Distribution name needs a random substring due to bug RHAIENG-999 / RHAIENG-1139
192-
distribution_name = generate_random_name(prefix="llama-stack-distribution")
193240
with create_llama_stack_distribution(
194241
client=admin_client,
195242
name=distribution_name,

tests/llama_stack/constants.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ class Safety(str, Enum):
1818
class Eval(str, Enum):
1919
TRUSTYAI_LMEVAL = "trustyai_lmeval"
2020
TRUSTYAI_RAGAS_INLINE = "trustyai_ragas_inline"
21+
TRUSTYAI_RAGAS_REMOTE = "trustyai_ragas_remote"
2122

2223

2324
class ModelInfo(NamedTuple):

tests/llama_stack/eval/conftest.py

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,17 @@
22

33
import pytest
44
from kubernetes.dynamic import DynamicClient
5+
from ocp_resources.data_science_pipelines_application import DataSciencePipelinesApplication
56
from ocp_resources.namespace import Namespace
67
from ocp_resources.persistent_volume_claim import PersistentVolumeClaim
78
from ocp_resources.pod import Pod
9+
from ocp_resources.route import Route
10+
from ocp_resources.secret import Secret
11+
from ocp_resources.service import Service
12+
from timeout_sampler import TimeoutExpiredError, TimeoutSampler
813

914
from tests.llama_stack.eval.constants import DK_CUSTOM_DATASET_IMAGE
15+
from utilities.constants import MinIo
1016

1117

1218
@pytest.fixture(scope="class")
@@ -87,3 +93,160 @@ def teardown_lmeval_job_pod(admin_client, model_namespace) -> None:
8793
]:
8894
for pod in pods:
8995
pod.delete()
96+
97+
98+
@pytest.fixture(scope="class")
99+
def dspa(
100+
admin_client: DynamicClient,
101+
model_namespace: Namespace,
102+
minio_pod: Pod,
103+
minio_service: Service,
104+
dspa_s3_secret: Secret,
105+
) -> Generator[DataSciencePipelinesApplication, Any, Any]:
106+
"""
107+
Creates a DataSciencePipelinesApplication with MinIO object storage.
108+
"""
109+
110+
with DataSciencePipelinesApplication(
111+
client=admin_client,
112+
name="dspa",
113+
namespace=model_namespace.name,
114+
dsp_version="v2",
115+
pod_to_pod_tls=True,
116+
api_server={
117+
"deploy": True,
118+
"enableOauth": True,
119+
"enableSamplePipeline": False,
120+
"cacheEnabled": True,
121+
"artifactSignedURLExpirySeconds": 60,
122+
"pipelineStore": "kubernetes",
123+
},
124+
database={
125+
"disableHealthCheck": False,
126+
"mariaDB": {
127+
"deploy": True,
128+
"pipelineDBName": "mlpipeline",
129+
"pvcSize": "10Gi",
130+
"username": "mlpipeline",
131+
},
132+
},
133+
object_storage={
134+
"disableHealthCheck": False,
135+
"enableExternalRoute": False,
136+
"externalStorage": {
137+
"bucket": "ods-ci-ds-pipelines",
138+
"host": f"{minio_service.instance.spec.clusterIP}:{MinIo.Metadata.DEFAULT_PORT}",
139+
"region": "us-east-1",
140+
"scheme": "http",
141+
"s3CredentialsSecret": {
142+
"accessKey": "AWS_ACCESS_KEY_ID", # pragma: allowlist secret
143+
"secretKey": "AWS_SECRET_ACCESS_KEY", # pragma: allowlist secret
144+
"secretName": dspa_s3_secret.name,
145+
},
146+
},
147+
},
148+
persistence_agent={
149+
"deploy": True,
150+
"numWorkers": 2,
151+
},
152+
scheduled_workflow={
153+
"deploy": True,
154+
"cronScheduleTimezone": "UTC",
155+
},
156+
) as dspa_resource:
157+
wait_for_dspa_pods(
158+
admin_client=admin_client,
159+
namespace=model_namespace.name,
160+
dspa_name=dspa_resource.name,
161+
)
162+
yield dspa_resource
163+
164+
165+
def wait_for_dspa_pods(admin_client: DynamicClient, namespace: str, dspa_name: str, timeout: int = 300) -> None:
166+
"""
167+
Wait for all DataSciencePipelinesApplication pods to be running.
168+
169+
Args:
170+
admin_client: The admin client to use for pod retrieval
171+
namespace: The namespace where DSPA is deployed
172+
dspa_name: The name of the DSPA resource
173+
timeout: Timeout in seconds
174+
"""
175+
176+
label_selector = f"dspa={dspa_name}"
177+
178+
def _all_dspa_pods_running() -> bool:
179+
pods = list(Pod.get(dyn_client=admin_client, namespace=namespace, label_selector=label_selector))
180+
if not pods:
181+
return False
182+
return all(pod.instance.status.phase == Pod.Status.RUNNING for pod in pods)
183+
184+
sampler = TimeoutSampler(
185+
wait_timeout=timeout,
186+
sleep=10,
187+
func=_all_dspa_pods_running,
188+
)
189+
190+
for is_ready in sampler:
191+
if is_ready:
192+
return
193+
194+
195+
@pytest.fixture(scope="class")
196+
def dspa_s3_secret(
197+
admin_client: DynamicClient,
198+
model_namespace: Namespace,
199+
minio_service: Service,
200+
) -> Generator[Secret, Any, Any]:
201+
"""
202+
Creates a secret for DSPA S3 credentials using MinIO.
203+
"""
204+
with Secret(
205+
client=admin_client,
206+
name="dashboard-dspa-secret",
207+
namespace=model_namespace.name,
208+
string_data={
209+
"AWS_ACCESS_KEY_ID": MinIo.Credentials.ACCESS_KEY_VALUE,
210+
"AWS_SECRET_ACCESS_KEY": MinIo.Credentials.SECRET_KEY_VALUE,
211+
"AWS_DEFAULT_REGION": "us-east-1",
212+
},
213+
) as secret:
214+
yield secret
215+
216+
217+
@pytest.fixture(scope="class")
218+
def dspa_route(
219+
admin_client: DynamicClient,
220+
model_namespace: Namespace,
221+
dspa: DataSciencePipelinesApplication,
222+
) -> Generator[Route, Any, Any]:
223+
"""
224+
Retrieves the Route for the DSPA API server.
225+
"""
226+
227+
def _get_dspa_route() -> Route | None:
228+
routes = list(
229+
Route.get(
230+
dyn_client=admin_client,
231+
namespace=model_namespace.name,
232+
name="ds-pipeline-dspa",
233+
)
234+
)
235+
return routes[0] if routes else None
236+
237+
sampler = TimeoutSampler(
238+
wait_timeout=120,
239+
sleep=5,
240+
func=_get_dspa_route,
241+
)
242+
243+
route = None
244+
for result in sampler:
245+
if result:
246+
route = result
247+
break
248+
249+
if not route:
250+
raise TimeoutExpiredError(f"DSPA route {dspa.name} not found in namespace {model_namespace.name}")
251+
252+
yield route

0 commit comments

Comments
 (0)