Skip to content

Commit b5daef0

Browse files
authored
Merge pull request #680 from radofuchs/LCORE_760_hardcoded_model_fix
LCORE-760: fix hardcoded model and provider in e2e tests
2 parents 629b8cf + 3df76b4 commit b5daef0

File tree

10 files changed

+117
-44
lines changed

10 files changed

+117
-44
lines changed

tests/e2e/features/conversations.feature

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ Feature: conversations endpoint API tests
2222
And The conversation with conversation_id from above is returned
2323
And The conversation details are following
2424
"""
25-
{"last_used_model": "gpt-4-turbo", "last_used_provider": "openai", "message_count": 1}
25+
{"last_used_model": "{MODEL}", "last_used_provider": "{PROVIDER}", "message_count": 1}
2626
"""
2727

2828
Scenario: Check if conversations endpoint fails when the auth header is not present

tests/e2e/features/environment.py

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,47 @@
2020
create_config_backup,
2121
)
2222

23-
try:
24-
import os # noqa: F401
25-
except ImportError as e:
26-
print("Warning: unable to import module:", e)
23+
24+
def _fetch_models_from_service(hostname: str = "localhost", port: int = 8080) -> dict:
25+
"""Query /v1/models endpoint and return first LLM model.
26+
27+
Returns:
28+
Dict with model_id and provider_id, or empty dict if unavailable
29+
"""
30+
try:
31+
url = f"http://{hostname}:{port}/v1/models"
32+
response = requests.get(url, timeout=5)
33+
response.raise_for_status()
34+
data = response.json()
35+
36+
# Find first LLM model
37+
for model in data.get("models", []):
38+
if model.get("api_model_type") == "llm":
39+
provider_id = model.get("provider_id")
40+
model_id = model.get("provider_resource_id")
41+
if provider_id and model_id:
42+
return {"model_id": model_id, "provider_id": provider_id}
43+
return {}
44+
except (requests.RequestException, ValueError, KeyError):
45+
return {}
2746

2847

2948
def before_all(context: Context) -> None:
3049
"""Run before and after the whole shooting match."""
50+
# Get first LLM model from running service
51+
llm_model = _fetch_models_from_service()
52+
53+
if llm_model:
54+
context.default_model = llm_model["model_id"]
55+
context.default_provider = llm_model["provider_id"]
56+
print(
57+
f"Detected LLM: {context.default_model} (provider: {context.default_provider})"
58+
)
59+
else:
60+
# Fallback for development
61+
context.default_model = "gpt-4-turbo"
62+
context.default_provider = "openai"
63+
print("⚠ Could not detect models, using fallback: gpt-4-turbo/openai")
3164

3265

3366
def before_scenario(context: Context, scenario: Scenario) -> None:

tests/e2e/features/info.feature

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ Feature: Info tests
3434
Given The system is in default state
3535
When I access REST API endpoint "models" using HTTP GET method
3636
Then The status code of the response is 200
37-
And The body of the response for model gpt-4o-mini has proper structure
37+
And The body of the response has proper model structure
3838

3939

4040
Scenario: Check if models endpoint is working

tests/e2e/features/query.feature

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ Feature: Query endpoint API tests
4242
And I store conversation details
4343
And I use "query" to ask question with same conversation_id
4444
"""
45-
{"query": "Write a simple code for reversing string", "system_prompt": "provide coding assistance", "model": "gpt-4-turbo", "provider": "openai"}
45+
{"query": "Write a simple code for reversing string", "system_prompt": "provide coding assistance", "model": "{MODEL}", "provider": "{PROVIDER}"}
4646
"""
4747
Then The status code of the response is 200
4848
And The response should contain following fragments
@@ -76,20 +76,20 @@ Scenario: Check if LLM responds for query request with error for missing query
7676
And I set the Authorization header to Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6Ikpva
7777
When I use "query" to ask question with authorization header
7878
"""
79-
{"provider": "openai"}
79+
{"provider": "{PROVIDER}"}
8080
"""
8181
Then The status code of the response is 422
8282
And The body of the response is the following
8383
"""
84-
{ "detail": [{"type": "missing", "loc": [ "body", "query" ], "msg": "Field required", "input": {"provider": "openai"}}] }
84+
{ "detail": [{"type": "missing", "loc": [ "body", "query" ], "msg": "Field required", "input": {"provider": "{PROVIDER}"}}] }
8585
"""
8686

8787
Scenario: Check if LLM responds for query request with error for missing model
8888
Given The system is in default state
8989
And I set the Authorization header to Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6Ikpva
9090
When I use "query" to ask question with authorization header
9191
"""
92-
{"query": "Say hello", "provider": "openai"}
92+
{"query": "Say hello", "provider": "{PROVIDER}"}
9393
"""
9494
Then The status code of the response is 422
9595
And The body of the response contains Value error, Model must be specified if provider is specified
@@ -99,7 +99,7 @@ Scenario: Check if LLM responds for query request with error for missing query
9999
And I set the Authorization header to Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6Ikpva
100100
When I use "query" to ask question with authorization header
101101
"""
102-
{"query": "Say hello", "model": "gpt-4-turbo"}
102+
{"query": "Say hello", "model": "{MODEL}"}
103103
"""
104104
Then The status code of the response is 422
105105
And The body of the response contains Value error, Provider must be specified if model is specified

tests/e2e/features/steps/common_http.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from behave.runner import Context
88
from tests.e2e.utils.utils import (
99
normalize_endpoint,
10+
replace_placeholders,
1011
validate_json,
1112
validate_json_partially,
1213
)
@@ -170,7 +171,10 @@ def check_prediction_result(context: Context) -> None:
170171
assert context.response is not None, "Request needs to be performed first"
171172
assert context.text is not None, "Response does not contain any payload"
172173

173-
expected_body = json.loads(context.text)
174+
# Replace {MODEL} and {PROVIDER} placeholders with actual values
175+
json_str = replace_placeholders(context, context.text or "{}")
176+
177+
expected_body = json.loads(json_str)
174178
result = context.response.json()
175179

176180
# compare both JSONs and print actual result in case of any difference

tests/e2e/features/steps/conversation.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from behave import step, when, then # pyright: ignore[reportAttributeAccessIssue]
55
from behave.runner import Context
66
import requests
7+
from tests.e2e.utils.utils import replace_placeholders
78

89
# default timeout for HTTP operations
910
DEFAULT_TIMEOUT = 10
@@ -109,7 +110,10 @@ def check_returned_conversation_id(context: Context) -> None:
109110
@then("The conversation details are following")
110111
def check_returned_conversation_content(context: Context) -> None:
111112
"""Check the conversation content in response."""
112-
expected_data = json.loads(context.text)
113+
# Replace {MODEL} and {PROVIDER} placeholders with actual values
114+
json_str = replace_placeholders(context, context.text or "{}")
115+
116+
expected_data = json.loads(json_str)
113117
found_conversation = context.found_conversation
114118

115119
assert (

tests/e2e/features/steps/info.py

Lines changed: 25 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -27,31 +27,39 @@ def check_llama_version(context: Context, llama_version: str) -> None:
2727
), f"llama-stack version is {response_json["llama_stack_version"]}"
2828

2929

30-
@then("The body of the response for model {model} has proper structure")
31-
def check_model_structure(context: Context, model: str) -> None:
32-
"""Check that the gpt-4o-mini model has the correct structure and required fields."""
30+
@then("The body of the response has proper model structure")
31+
def check_model_structure(context: Context) -> None:
32+
"""Check that the first LLM model has the correct structure and required fields."""
3333
response_json = context.response.json()
3434
assert response_json is not None, "Response is not valid JSON"
3535

3636
assert "models" in response_json, "Response missing 'models' field"
3737
models = response_json["models"]
38-
assert len(models) > 0, "Models list should not be empty"
38+
assert len(models) > 0, "Response has empty list of models"
3939

40-
gpt_model = None
41-
for model_id in models:
42-
if "gpt-4o-mini" in model_id.get("identifier", ""):
43-
gpt_model = model_id
40+
# Find first LLM model (same logic as environment.py)
41+
llm_model = None
42+
for model in models:
43+
if model.get("api_model_type") == "llm":
44+
llm_model = model
4445
break
4546

46-
assert gpt_model is not None
47+
assert llm_model is not None, "No LLM model found in response"
4748

48-
assert gpt_model["type"] == "model", "type should be 'model'"
49-
assert gpt_model["api_model_type"] == "llm", "api_model_type should be 'llm'"
50-
assert gpt_model["model_type"] == "llm", "model_type should be 'llm'"
51-
assert gpt_model["provider_id"] == "openai", "provider_id should be 'openai'"
49+
# Get expected values from context
50+
expected_model = context.default_model
51+
expected_provider = context.default_provider
52+
53+
# Validate structure and values
54+
assert llm_model["type"] == "model", "type should be 'model'"
55+
assert llm_model["api_model_type"] == "llm", "api_model_type should be 'llm'"
56+
assert llm_model["model_type"] == "llm", "model_type should be 'llm'"
57+
assert (
58+
llm_model["provider_id"] == expected_provider
59+
), f"provider_id should be '{expected_provider}'"
5260
assert (
53-
gpt_model["provider_resource_id"] == model
54-
), "provider_resource_id should be 'gpt-4o-mini'"
61+
llm_model["provider_resource_id"] == expected_model
62+
), f"provider_resource_id should be '{expected_model}'"
5563
assert (
56-
gpt_model["identifier"] == f"openai/{model}"
57-
), "identifier should be 'openai/gpt-4o-mini'"
64+
llm_model["identifier"] == f"{expected_provider}/{expected_model}"
65+
), f"identifier should be '{expected_provider}/{expected_model}'"

tests/e2e/features/steps/llm_query_response.py

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import requests
55
from behave import then, step # pyright: ignore[reportAttributeAccessIssue]
66
from behave.runner import Context
7+
from tests.e2e.utils.utils import replace_placeholders
78

89

910
DEFAULT_LLM_TIMEOUT = 60
@@ -24,9 +25,11 @@ def ask_question(context: Context, endpoint: str) -> None:
2425
path = f"{context.api_prefix}/{endpoint}".replace("//", "/")
2526
url = base + path
2627

27-
# Use context.text if available, otherwise use empty query
28-
data = json.loads(context.text or "{}")
29-
print(data)
28+
# Replace {MODEL} and {PROVIDER} placeholders with actual values
29+
json_str = replace_placeholders(context, context.text or "{}")
30+
31+
data = json.loads(json_str)
32+
print(f"Request data: {data}")
3033
context.response = requests.post(url, json=data, timeout=DEFAULT_LLM_TIMEOUT)
3134

3235

@@ -37,9 +40,11 @@ def ask_question_authorized(context: Context, endpoint: str) -> None:
3740
path = f"{context.api_prefix}/{endpoint}".replace("//", "/")
3841
url = base + path
3942

40-
# Use context.text if available, otherwise use empty query
41-
data = json.loads(context.text or "{}")
42-
print(data)
43+
# Replace {MODEL} and {PROVIDER} placeholders with actual values
44+
json_str = replace_placeholders(context, context.text or "{}")
45+
46+
data = json.loads(json_str)
47+
print(f"Request data: {data}")
4348
context.response = requests.post(
4449
url, json=data, headers=context.auth_headers, timeout=DEFAULT_LLM_TIMEOUT
4550
)
@@ -58,12 +63,14 @@ def ask_question_in_same_conversation(context: Context, endpoint: str) -> None:
5863
path = f"{context.api_prefix}/{endpoint}".replace("//", "/")
5964
url = base + path
6065

61-
# Use context.text if available, otherwise use empty query
62-
data = json.loads(context.text or "{}")
66+
# Replace {MODEL} and {PROVIDER} placeholders with actual values
67+
json_str = replace_placeholders(context, context.text or "{}")
68+
69+
data = json.loads(json_str)
6370
headers = context.auth_headers if hasattr(context, "auth_headers") else {}
6471
data["conversation_id"] = context.response_data["conversation_id"]
6572

66-
print(data)
73+
print(f"Request data: {data}")
6774
context.response = requests.post(
6875
url, json=data, headers=headers, timeout=DEFAULT_LLM_TIMEOUT
6976
)

tests/e2e/features/streaming_query.feature

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ Feature: streaming_query endpoint API tests
5151
Then The status code of the response is 200
5252
And I use "streaming_query" to ask question with same conversation_id
5353
"""
54-
{"query": "Write a simple code for reversing string", "system_prompt": "provide coding assistance", "model": "gpt-4-turbo", "provider": "openai"}
54+
{"query": "Write a simple code for reversing string", "system_prompt": "provide coding assistance", "model": "{MODEL}", "provider": "{PROVIDER}"}
5555
"""
5656
Then The status code of the response is 200
5757
When I wait for the response to be completed
@@ -64,19 +64,19 @@ Feature: streaming_query endpoint API tests
6464
Given The system is in default state
6565
When I use "streaming_query" to ask question
6666
"""
67-
{"provider": "openai"}
67+
{"provider": "{PROVIDER}"}
6868
"""
6969
Then The status code of the response is 422
7070
And The body of the response is the following
7171
"""
72-
{ "detail": [{"type": "missing", "loc": [ "body", "query" ], "msg": "Field required", "input": {"provider": "openai"}}] }
72+
{ "detail": [{"type": "missing", "loc": [ "body", "query" ], "msg": "Field required", "input": {"provider": "{PROVIDER}"}}] }
7373
"""
7474

7575
Scenario: Check if LLM responds for streaming_query request with error for missing model
7676
Given The system is in default state
7777
When I use "streaming_query" to ask question
7878
"""
79-
{"query": "Say hello", "provider": "openai"}
79+
{"query": "Say hello", "provider": "{PROVIDER}"}
8080
"""
8181
Then The status code of the response is 422
8282
And The body of the response contains Value error, Model must be specified if provider is specified
@@ -85,7 +85,7 @@ Feature: streaming_query endpoint API tests
8585
Given The system is in default state
8686
When I use "streaming_query" to ask question
8787
"""
88-
{"query": "Say hello", "model": "gpt-4-turbo"}
88+
{"query": "Say hello", "model": "{MODEL}"}
8989
"""
9090
Then The status code of the response is 422
9191
And The body of the response contains Value error, Provider must be specified if model is specified

tests/e2e/utils/utils.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import time
77
import jsonschema
88
from typing import Any
9+
from behave.runner import Context
910

1011

1112
def normalize_endpoint(endpoint: str) -> str:
@@ -141,3 +142,19 @@ def restart_container(container_name: str) -> None:
141142

142143
# Wait for container to be healthy
143144
wait_for_container_health(container_name)
145+
146+
147+
def replace_placeholders(context: Context, text: str) -> str:
148+
"""Replace {MODEL} and {PROVIDER} placeholders with actual values from context.
149+
150+
Args:
151+
context: Behave context containing default_model and default_provider
152+
text: String that may contain {MODEL} and {PROVIDER} placeholders
153+
154+
Returns:
155+
String with placeholders replaced by actual values
156+
157+
"""
158+
result = text.replace("{MODEL}", context.default_model)
159+
result = result.replace("{PROVIDER}", context.default_provider)
160+
return result

0 commit comments

Comments
 (0)