Skip to content

Commit 42bf733

Browse files
committed
feat(rlsapi): include system info context in LLM instructions
Enhance the LLM instructions with the user's RHEL system information (OS, version, architecture) when available. This gives the LLM better context about the environment the user is asking questions about, enabling more relevant and accurate responses. Signed-off-by: Major Hayden <major@redhat.com>
1 parent 67dbfee commit 42bf733

2 files changed

Lines changed: 72 additions & 8 deletions

File tree

src/app/endpoints/rlsapi_v1.py

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
UnauthorizedResponse,
2828
UnprocessableEntityResponse,
2929
)
30-
from models.rlsapi.requests import RlsapiV1InferRequest
30+
from models.rlsapi.requests import RlsapiV1InferRequest, RlsapiV1SystemInfo
3131
from models.rlsapi.responses import RlsapiV1InferData, RlsapiV1InferResponse
3232
from utils.responses import extract_text_from_response_output_item
3333
from utils.suid import get_suid
@@ -49,6 +49,35 @@
4949
}
5050

5151

52+
def _build_instructions(systeminfo: RlsapiV1SystemInfo) -> str:
53+
"""Build LLM instructions incorporating system context when available.
54+
55+
Enhances the default system prompt with RHEL system information to provide
56+
the LLM with relevant context about the user's environment.
57+
58+
Args:
59+
systeminfo: System information from the client (OS, version, arch).
60+
61+
Returns:
62+
Instructions string for the LLM, with system context if available.
63+
"""
64+
base_prompt = constants.DEFAULT_SYSTEM_PROMPT
65+
66+
context_parts = []
67+
if systeminfo.os:
68+
context_parts.append(f"OS: {systeminfo.os}")
69+
if systeminfo.version:
70+
context_parts.append(f"Version: {systeminfo.version}")
71+
if systeminfo.arch:
72+
context_parts.append(f"Architecture: {systeminfo.arch}")
73+
74+
if not context_parts:
75+
return base_prompt
76+
77+
system_context = ", ".join(context_parts)
78+
return f"{base_prompt}\n\nUser's system: {system_context}"
79+
80+
5281
def _get_default_model_id() -> str:
5382
"""Get the default model ID from configuration.
5483
@@ -82,14 +111,15 @@ def _get_default_model_id() -> str:
82111
)
83112

84113

85-
async def retrieve_simple_response(question: str) -> str:
114+
async def retrieve_simple_response(question: str, instructions: str) -> str:
86115
"""Retrieve a simple response from the LLM for a stateless query.
87116
88117
Uses the Responses API for simple stateless inference, consistent with
89118
other endpoints (query_v2, streaming_query_v2).
90119
91120
Args:
92121
question: The combined user input (question + context).
122+
instructions: System instructions for the LLM.
93123
94124
Returns:
95125
The LLM-generated response text.
@@ -106,7 +136,7 @@ async def retrieve_simple_response(question: str) -> str:
106136
response = await client.responses.create(
107137
input=question,
108138
model=model_id,
109-
instructions=constants.DEFAULT_SYSTEM_PROMPT,
139+
instructions=instructions,
110140
stream=False,
111141
store=False,
112142
)
@@ -149,14 +179,14 @@ async def infer_endpoint(
149179

150180
logger.info("Processing rlsapi v1 /infer request %s", request_id)
151181

152-
# Combine all input sources (question, stdin, attachments, terminal)
153182
input_source = infer_request.get_input_source()
183+
instructions = _build_instructions(infer_request.context.systeminfo)
154184
logger.debug(
155185
"Request %s: Combined input source length: %d", request_id, len(input_source)
156186
)
157187

158188
try:
159-
response_text = await retrieve_simple_response(input_source)
189+
response_text = await retrieve_simple_response(input_source, instructions)
160190
except APIConnectionError as e:
161191
metrics.llm_calls_failures_total.inc()
162192
logger.error(

tests/unit/app/endpoints/test_rlsapi_v1.py

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
import constants
1515
from app.endpoints.rlsapi_v1 import (
16+
_build_instructions,
1617
_get_default_model_id,
1718
infer_endpoint,
1819
retrieve_simple_response,
@@ -96,6 +97,35 @@ def mock_api_connection_error_fixture(mocker: MockerFixture) -> None:
9697
)
9798

9899

100+
# --- Test _build_instructions ---
101+
102+
103+
def test_build_instructions_with_full_systeminfo() -> None:
104+
"""Test _build_instructions includes all system info fields."""
105+
systeminfo = RlsapiV1SystemInfo(os="RHEL", version="9.3", arch="x86_64")
106+
result = _build_instructions(systeminfo)
107+
assert constants.DEFAULT_SYSTEM_PROMPT in result
108+
assert "OS: RHEL" in result
109+
assert "Version: 9.3" in result
110+
assert "Architecture: x86_64" in result
111+
112+
113+
def test_build_instructions_with_partial_systeminfo() -> None:
114+
"""Test _build_instructions handles partial system info."""
115+
systeminfo = RlsapiV1SystemInfo(os="RHEL", version="", arch="")
116+
result = _build_instructions(systeminfo)
117+
assert "OS: RHEL" in result
118+
assert "Version:" not in result
119+
assert "Architecture:" not in result
120+
121+
122+
def test_build_instructions_with_empty_systeminfo() -> None:
123+
"""Test _build_instructions returns base prompt when no system info."""
124+
systeminfo = RlsapiV1SystemInfo()
125+
result = _build_instructions(systeminfo)
126+
assert result == constants.DEFAULT_SYSTEM_PROMPT
127+
128+
99129
# --- Test _get_default_model_id ---
100130

101131

@@ -151,7 +181,9 @@ async def test_retrieve_simple_response_success(
151181
mock_configuration: AppConfig, mock_llm_response: None
152182
) -> None:
153183
"""Test retrieve_simple_response returns LLM response text."""
154-
response = await retrieve_simple_response("How do I list files?")
184+
response = await retrieve_simple_response(
185+
"How do I list files?", constants.DEFAULT_SYSTEM_PROMPT
186+
)
155187
assert response == "This is a test LLM response."
156188

157189

@@ -160,7 +192,9 @@ async def test_retrieve_simple_response_empty_output(
160192
mock_configuration: AppConfig, mock_empty_llm_response: None
161193
) -> None:
162194
"""Test retrieve_simple_response handles empty LLM output."""
163-
response = await retrieve_simple_response("Test question")
195+
response = await retrieve_simple_response(
196+
"Test question", constants.DEFAULT_SYSTEM_PROMPT
197+
)
164198
assert response == ""
165199

166200

@@ -170,7 +204,7 @@ async def test_retrieve_simple_response_api_connection_error(
170204
) -> None:
171205
"""Test retrieve_simple_response propagates APIConnectionError."""
172206
with pytest.raises(APIConnectionError):
173-
await retrieve_simple_response("Test question")
207+
await retrieve_simple_response("Test question", constants.DEFAULT_SYSTEM_PROMPT)
174208

175209

176210
# --- Test infer_endpoint ---

0 commit comments

Comments
 (0)