diff --git a/GUI/src/pages/TestModel/TestLLM.scss b/GUI/src/pages/TestModel/TestLLM.scss
index 2dd2b4e..833690d 100644
--- a/GUI/src/pages/TestModel/TestLLM.scss
+++ b/GUI/src/pages/TestModel/TestLLM.scss
@@ -41,6 +41,44 @@
line-height: 1.5;
color: #555;
}
+
+ .context-section {
+ margin-top: 20px;
+
+ .context-list {
+ display: flex;
+ flex-direction: column;
+ gap: 12px;
+ margin-top: 8px;
+ }
+
+ .context-item {
+ padding: 12px;
+ background-color: #ffffff;
+ border: 1px solid #e0e0e0;
+ border-radius: 6px;
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
+
+ .context-rank {
+ margin-bottom: 8px;
+ padding-bottom: 4px;
+ border-bottom: 1px solid #f0f0f0;
+
+ strong {
+ color: #2563eb;
+ font-size: 0.875rem;
+ font-weight: 600;
+ }
+ }
+
+ .context-content {
+ color: #374151;
+ line-height: 1.5;
+ font-size: 0.9rem;
+ white-space: pre-wrap;
+ }
+ }
+ }
}
.testModalList {
diff --git a/GUI/src/pages/TestModel/index.tsx b/GUI/src/pages/TestModel/index.tsx
index 4b16522..b6e66e7 100644
--- a/GUI/src/pages/TestModel/index.tsx
+++ b/GUI/src/pages/TestModel/index.tsx
@@ -1,5 +1,5 @@
import { useMutation, useQuery } from '@tanstack/react-query';
-import { Button, FormSelect, FormTextarea } from 'components';
+import { Button, FormSelect, FormTextarea, Collapsible } from 'components';
import CircularSpinner from 'components/molecules/CircularSpinner/CircularSpinner';
import { FC, useState } from 'react';
import { useTranslation } from 'react-i18next';
@@ -19,6 +19,9 @@ const TestLLM: FC = () => {
text: '',
});
+ // Sort context by rank
+ const sortedContext = inferenceResult?.chunks?.toSorted((a, b) => a.rank - b.rank) ?? [];
+
// Fetch LLM connections for dropdown - using the working legacy endpoint for now
const { data: connections, isLoading: isLoadingConnections } = useQuery({
queryKey: llmConnectionsQueryKeys.list({
@@ -99,7 +102,7 @@ const TestLLM: FC = () => {
onSelectionChange={(selection) => {
handleChange('connectionId', selection?.value as string);
}}
- value={testLLM?.connectionId === null ? t('testModels.connectionNotExist') || 'Connection does not exist' : undefined}
+ value={testLLM?.connectionId === null ? t('testModels.connectionNotExist') || 'Connection does not exist' : undefined}
defaultValue={testLLM?.connectionId ?? undefined}
/>
@@ -126,15 +129,38 @@ const TestLLM: FC = () => {
{/* Inference Result */}
- {inferenceResult && (
+ {inferenceResult && !inferenceMutation.isLoading && (
-
-
{t('testModels.responseLabel') || 'Response:'}
-
- {inferenceResult.content}
+
+
Response:
+
+ {inferenceResult.content}
+
+
+ {/* Context Section */}
+ {
+ sortedContext && sortedContext?.length > 0 && (
+
+
+
+ {sortedContext?.map((contextItem, index) => (
+
+
+ Rank {contextItem.rank}
+
+
+ {contextItem.chunkRetrieved}
+
+
+ ))}
+
+
+
+ )
+ }
+
-
)}
{/* Error State */}
diff --git a/GUI/src/services/inference.ts b/GUI/src/services/inference.ts
index 691522c..44baf69 100644
--- a/GUI/src/services/inference.ts
+++ b/GUI/src/services/inference.ts
@@ -25,6 +25,10 @@ export interface InferenceResponse {
llmServiceActive: boolean;
questionOutOfLlmScope: boolean;
content: string;
+ chunks?: {
+ rank: number,
+ chunkRetrieved: string
+ }[]
};
}
diff --git a/src/llm_orchestration_service.py b/src/llm_orchestration_service.py
index 26c4b7d..a7de4c6 100644
--- a/src/llm_orchestration_service.py
+++ b/src/llm_orchestration_service.py
@@ -18,6 +18,7 @@
PromptRefinerOutput,
ContextGenerationRequest,
TestOrchestrationResponse,
+ ChunkInfo,
)
from prompt_refine_manager.prompt_refiner import PromptRefinerAgent
from src.response_generator.response_generate import ResponseGeneratorAgent
@@ -922,6 +923,7 @@ def handle_input_guardrails(
questionOutOfLLMScope=False,
inputGuardFailed=True,
content=INPUT_GUARDRAIL_VIOLATION_MESSAGE,
+ chunks=None,
)
else:
return OrchestrationResponse(
@@ -1606,6 +1608,31 @@ def _initialize_response_generator(
logger.error(f"Failed to initialize response generator: {str(e)}")
raise
+ @staticmethod
+ def _format_chunks_for_test_response(
+ relevant_chunks: Optional[List[Dict[str, Union[str, float, Dict[str, Any]]]]],
+ ) -> Optional[List[ChunkInfo]]:
+ """
+ Format retrieved chunks for test response.
+
+ Args:
+ relevant_chunks: List of retrieved chunks with metadata
+
+ Returns:
+ List of ChunkInfo objects with rank and content, or None if no chunks
+ """
+ if not relevant_chunks:
+ return None
+
+ formatted_chunks = []
+ for rank, chunk in enumerate(relevant_chunks, start=1):
+ # Extract text content - prefer "text" key, fallback to "content"
+ chunk_text = chunk.get("text", chunk.get("content", ""))
+ if isinstance(chunk_text, str) and chunk_text.strip():
+ formatted_chunks.append(ChunkInfo(rank=rank, chunkRetrieved=chunk_text))
+
+ return formatted_chunks if formatted_chunks else None
+
@observe(name="generate_rag_response", as_type="generation")
def _generate_rag_response(
self,
@@ -1639,6 +1666,7 @@ def _generate_rag_response(
questionOutOfLLMScope=False,
inputGuardFailed=False,
content=TECHNICAL_ISSUE_MESSAGE,
+ chunks=self._format_chunks_for_test_response(relevant_chunks),
)
else:
return OrchestrationResponse(
@@ -1706,6 +1734,7 @@ def _generate_rag_response(
questionOutOfLLMScope=True,
inputGuardFailed=False,
content=OUT_OF_SCOPE_MESSAGE,
+ chunks=self._format_chunks_for_test_response(relevant_chunks),
)
else:
return OrchestrationResponse(
@@ -1725,6 +1754,7 @@ def _generate_rag_response(
questionOutOfLLMScope=False,
inputGuardFailed=False,
content=answer,
+ chunks=self._format_chunks_for_test_response(relevant_chunks),
)
else:
return OrchestrationResponse(
@@ -1765,6 +1795,7 @@ def _generate_rag_response(
questionOutOfLLMScope=False,
inputGuardFailed=False,
content=TECHNICAL_ISSUE_MESSAGE,
+ chunks=self._format_chunks_for_test_response(relevant_chunks),
)
else:
return OrchestrationResponse(
diff --git a/src/llm_orchestration_service_api.py b/src/llm_orchestration_service_api.py
index df2fa21..b58eac9 100644
--- a/src/llm_orchestration_service_api.py
+++ b/src/llm_orchestration_service_api.py
@@ -332,7 +332,9 @@ def test_orchestrate_llm_request(
conversationHistory=[],
url="test-context",
environment=request.environment,
- connection_id=str(request.connectionId),
+ connection_id=str(request.connectionId)
+ if request.connectionId is not None
+ else None,
)
logger.info(f"This is full request constructed for testing: {full_request}")
@@ -340,12 +342,20 @@ def test_orchestrate_llm_request(
# Process the request using the same logic
response = orchestration_service.process_orchestration_request(full_request)
- # Convert to TestOrchestrationResponse (exclude chatId)
+ # If response is already TestOrchestrationResponse (when environment is testing), return it directly
+ if isinstance(response, TestOrchestrationResponse):
+ logger.info(
+ f"Successfully processed test request for environment: {request.environment}"
+ )
+ return response
+
+ # Convert to TestOrchestrationResponse (exclude chatId) for other cases
test_response = TestOrchestrationResponse(
llmServiceActive=response.llmServiceActive,
questionOutOfLLMScope=response.questionOutOfLLMScope,
inputGuardFailed=response.inputGuardFailed,
content=response.content,
+ chunks=None, # OrchestrationResponse doesn't have chunks
)
logger.info(
diff --git a/src/models/request_models.py b/src/models/request_models.py
index e31eec4..2239425 100644
--- a/src/models/request_models.py
+++ b/src/models/request_models.py
@@ -230,10 +230,17 @@ class TestOrchestrationRequest(BaseModel):
..., description="Environment context"
)
connectionId: Optional[int] = Field(
- ..., description="Optional connection identifier"
+ None, description="Optional connection identifier"
)
+class ChunkInfo(BaseModel):
+ """Model for chunk information in test response."""
+
+ rank: int = Field(..., description="Rank of the retrieved chunk")
+ chunkRetrieved: str = Field(..., description="Content of the retrieved chunk")
+
+
class TestOrchestrationResponse(BaseModel):
"""Model for test orchestration response (without chatId)."""
@@ -245,3 +252,6 @@ class TestOrchestrationResponse(BaseModel):
..., description="Whether input guard validation failed"
)
content: str = Field(..., description="Response content with citations")
+ chunks: Optional[List[ChunkInfo]] = Field(
+ default=None, description="Retrieved chunks with rank and content"
+ )