Skip to content

Commit 7606aeb

Browse files
committed
Feature: add a qna evaluation for post training checkpoint evals
- Add endpoints for a checkpoint listing and the execution of a qna eval. - The backend go api will call a container that loads the requested checkpoint and qna.yaml into vLLM and queries the checkpoint with the seed questions from the QnA to get a sense of the fidelity of the checkpoint from the orignal seed examples. Signed-off-by: Brent Salisbury <[email protected]>
1 parent a788e86 commit 7606aeb

File tree

3 files changed

+269
-0
lines changed

3 files changed

+269
-0
lines changed

Diff for: src/app/api/native/eval/checkpoints/route.ts

+43
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
// app/api/native/eval/checkpoints/route.ts
2+
'use server';
3+
4+
import type { NextRequest } from 'next/server';
5+
import { NextResponse } from 'next/server';
6+
7+
export async function GET(request: NextRequest) {
8+
console.log('Received GET request for /api/native/eval/checkpoints');
9+
10+
try {
11+
const apiUrl = 'http://localhost:8080/checkpoints';
12+
console.log(`Fetching checkpoints from external API: ${apiUrl}`);
13+
14+
const res = await fetch(apiUrl, {
15+
method: 'GET',
16+
headers: {
17+
'Content-Type': 'application/json'
18+
}
19+
});
20+
21+
console.log(`External API response status: ${res.status}`);
22+
23+
if (!res.ok) {
24+
const errorData = await res.json();
25+
console.error('Error from external API:', errorData);
26+
return NextResponse.json({ error: errorData.error || 'Failed to fetch checkpoints' }, { status: res.status });
27+
}
28+
29+
const data = await res.json();
30+
console.log('Checkpoints data fetched successfully:', data);
31+
32+
// Validate that data is an array
33+
if (!Array.isArray(data)) {
34+
console.warn('Unexpected data format from external API:', data);
35+
return NextResponse.json({ error: 'Invalid data format received from checkpoints API.' }, { status: 500 });
36+
}
37+
38+
return NextResponse.json(data);
39+
} catch (error) {
40+
console.error('Error fetching checkpoints:', error);
41+
return NextResponse.json({ error: 'Unable to reach the checkpoints endpoint' }, { status: 500 });
42+
}
43+
}

Diff for: src/app/api/native/eval/qna/route.ts

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// src/app/api/native/eval/qna/route.ts
2+
import { NextResponse, NextRequest } from 'next/server';
3+
4+
const BACKEND_URL = process.env.BACKEND_URL || 'http://localhost:8080';
5+
const HARD_CODED_QNA_PATH = '/var/home/cloud-user/.local/share/instructlab/taxonomy/knowledge/history/amazon/qna.yaml';
6+
7+
export async function POST(request: NextRequest) {
8+
try {
9+
const body = await request.json();
10+
console.log('[SERVER] Body received:', body);
11+
12+
const { selectedModelDir } = body;
13+
if (!selectedModelDir) {
14+
console.error('[SERVER] Missing selectedModelDir in request body!');
15+
return NextResponse.json({ error: 'Missing required field: selectedModelDir' }, { status: 400 });
16+
}
17+
18+
console.log('[SERVER] selectedModelDir:', selectedModelDir);
19+
20+
// Forward to Go backend
21+
const response = await fetch(`${BACKEND_URL}/qna-eval`, {
22+
method: 'POST',
23+
headers: { 'Content-Type': 'application/json' },
24+
body: JSON.stringify({
25+
model_path: selectedModelDir,
26+
yaml_file: HARD_CODED_QNA_PATH
27+
})
28+
});
29+
30+
const data = await response.json();
31+
console.log('[SERVER] Response from Go backend:', data);
32+
33+
if (!response.ok) {
34+
return NextResponse.json({ error: data.error || 'Failed to initiate QnA evaluation' }, { status: response.status });
35+
}
36+
37+
return NextResponse.json(data, { status: 200 });
38+
} catch (error) {
39+
console.error('Error in /api/native/eval/qna route:', error);
40+
return NextResponse.json({ error: 'Internal Server Error' }, { status: 500 });
41+
}
42+
}

Diff for: src/components/Dashboard/Native/dashboard.tsx

+184
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,18 @@ const DashboardNative: React.FunctionComponent = () => {
7575
const [isPublishing, setIsPublishing] = React.useState(false);
7676
const [expandedFiles, setExpandedFiles] = React.useState<Record<string, boolean>>({});
7777

78+
// State Variables for Evaluate Checkpoint
79+
const [isEvalModalOpen, setIsEvalModalOpen] = React.useState<boolean>(false);
80+
const [checkpoints, setCheckpoints] = React.useState<string[]>([]);
81+
const [isCheckpointsLoading, setIsCheckpointsLoading] = React.useState<boolean>(false);
82+
const [checkpointsError, setCheckpointsError] = React.useState<string | null>(null);
83+
const [isDropdownOpen, setIsDropdownOpen] = React.useState<boolean>(false);
84+
const [selectedCheckpoint, setSelectedCheckpoint] = React.useState<string | null>(null);
85+
86+
// QnA eval result
87+
const [qnaEvalResult, setQnaEvalResult] = React.useState<string>('');
88+
const [isQnaLoading, setIsQnaLoading] = React.useState<boolean>(false);
89+
7890
const router = useRouter();
7991

8092
// Fetch branches from the API route
@@ -299,6 +311,100 @@ const DashboardNative: React.FunctionComponent = () => {
299311
}));
300312
};
301313

314+
const handleOpenEvalModal = () => {
315+
setIsEvalModalOpen(true);
316+
fetchCheckpoints();
317+
};
318+
319+
const handleCloseEvalModal = () => {
320+
setIsEvalModalOpen(false);
321+
setCheckpoints([]);
322+
setCheckpointsError(null);
323+
setSelectedCheckpoint(null);
324+
setQnaEvalResult('');
325+
setIsQnaLoading(false);
326+
};
327+
328+
// **New Function to Fetch Checkpoints from API Route**
329+
const fetchCheckpoints = async () => {
330+
setIsCheckpointsLoading(true);
331+
setCheckpointsError(null);
332+
try {
333+
const response = await fetch('/api/native/eval/checkpoints');
334+
console.log('Response status:', response.status);
335+
const data = await response.json();
336+
console.log('Checkpoints data:', data);
337+
338+
if (response.ok) {
339+
// Assuming the API returns an array of checkpoints
340+
if (Array.isArray(data) && data.length > 0) {
341+
setCheckpoints(data);
342+
console.log('Checkpoints set successfully:', data);
343+
} else {
344+
setCheckpoints([]);
345+
console.log('No checkpoints returned from API.');
346+
}
347+
} else {
348+
setCheckpointsError(data.error || 'Failed to fetch checkpoints.');
349+
console.error('Error fetching checkpoints:', data.error || 'Failed to fetch checkpoints.');
350+
}
351+
} catch (error) {
352+
console.error('Error fetching checkpoints:', error);
353+
setCheckpointsError('Unable to reach the checkpoints endpoint.');
354+
} finally {
355+
setIsCheckpointsLoading(false);
356+
}
357+
};
358+
359+
// Checkpoint select dropdown
360+
const onDropdownToggle = (isOpen: boolean) => setIsDropdownOpen(isOpen);
361+
const onSelectCheckpoint = (event: React.MouseEvent<Element, MouseEvent>, selection: string) => {
362+
setSelectedCheckpoint(selection);
363+
setIsDropdownOpen(false);
364+
};
365+
366+
const handleEvaluateQnA = async () => {
367+
if (!selectedCheckpoint) {
368+
addDangerAlert('Please select a checkpoint to evaluate.');
369+
return;
370+
}
371+
372+
setIsQnaLoading(true);
373+
setQnaEvalResult('');
374+
375+
// TODO: dynamically prepend the checkpoint path
376+
const selectedModelDir = '/var/home/cloud-user/.local/share/instructlab/checkpoints/hf_format/' + selectedCheckpoint;
377+
378+
console.log('[CLIENT] Sending to /api/native/eval/qna:', selectedModelDir);
379+
380+
try {
381+
const res = await fetch('/api/native/eval/qna', {
382+
method: 'POST',
383+
headers: { 'Content-Type': 'application/json' },
384+
body: JSON.stringify({ selectedModelDir })
385+
});
386+
387+
const data = await res.json();
388+
console.log('[CLIENT] Response from /api/native/eval/qna:', data);
389+
390+
if (!res.ok) {
391+
addDangerAlert(data.error || 'Failed to evaluate QnA.');
392+
} else {
393+
if (data.result) {
394+
setQnaEvalResult(data.result);
395+
addSuccessAlert('QnA Evaluation succeeded!');
396+
} else {
397+
setQnaEvalResult('Evaluation completed (no result field).');
398+
}
399+
}
400+
} catch (error) {
401+
console.error('Error evaluating QnA:', error);
402+
addDangerAlert('Error evaluating QnA.');
403+
} finally {
404+
setIsQnaLoading(false);
405+
}
406+
};
407+
302408
return (
303409
<div>
304410
<PageBreadcrumb hasBodyWrapper={false}>
@@ -459,6 +565,84 @@ const DashboardNative: React.FunctionComponent = () => {
459565
</PageSection>
460566
)}
461567

568+
{/* Evaluate Checkpoint Modal */}
569+
<Modal
570+
variant={ModalVariant.medium}
571+
title="Evaluate Checkpoint"
572+
isOpen={isEvalModalOpen}
573+
onClose={handleCloseEvalModal}
574+
aria-labelledby="evaluate-checkpoint-modal-title"
575+
aria-describedby="evaluate-checkpoint-modal-body"
576+
>
577+
<ModalHeader title="Evaluate Checkpoint" />
578+
<ModalBody id="evaluate-checkpoint-modal-body">
579+
{isCheckpointsLoading ? (
580+
<Spinner size="lg" aria-label="Loading checkpoints" />
581+
) : checkpointsError ? (
582+
<Alert variant="danger" title={checkpointsError} isInline />
583+
) : (
584+
<>
585+
<div style={{ marginBottom: '1rem' }}>
586+
<label style={{ display: 'block', marginBottom: '0.4rem' }}>Select a Checkpoint:</label>
587+
<Dropdown
588+
isOpen={isDropdownOpen}
589+
onSelect={onSelectCheckpoint}
590+
onOpenChange={onDropdownToggle}
591+
toggle={(toggleRef: React.Ref<MenuToggleElement>) => (
592+
<MenuToggle ref={toggleRef} onClick={() => onDropdownToggle(!isDropdownOpen)} isExpanded={isDropdownOpen}>
593+
{selectedCheckpoint || 'Select a Checkpoint'}
594+
</MenuToggle>
595+
)}
596+
ouiaId="EvaluateCheckpointDropdown"
597+
shouldFocusToggleOnSelect
598+
>
599+
<DropdownList>
600+
{checkpoints.length > 0 ? (
601+
checkpoints.map((checkpoint) => (
602+
<DropdownItem key={checkpoint} value={checkpoint}>
603+
{checkpoint}
604+
</DropdownItem>
605+
))
606+
) : (
607+
<DropdownItem key="no-checkpoints" isDisabled>
608+
No checkpoints available
609+
</DropdownItem>
610+
)}
611+
</DropdownList>
612+
</Dropdown>
613+
</div>
614+
615+
{/* Display the evaluation result */}
616+
{qnaEvalResult && (
617+
<div style={{ marginTop: '1rem' }}>
618+
<b>Evaluation Output:</b>
619+
<pre
620+
style={{
621+
marginTop: '0.5rem',
622+
backgroundColor: '#f5f5f5',
623+
padding: '1rem',
624+
borderRadius: '4px',
625+
maxHeight: '300px',
626+
overflowY: 'auto'
627+
}}
628+
>
629+
{qnaEvalResult}
630+
</pre>
631+
</div>
632+
)}
633+
</>
634+
)}
635+
</ModalBody>
636+
<ModalFooter>
637+
<Button key="evaluateQnA" variant="primary" onClick={handleEvaluateQnA} isDisabled={!selectedCheckpoint || isQnaLoading}>
638+
{isQnaLoading ? 'Evaluating...' : 'Evaluate'}
639+
</Button>
640+
<Button key="cancel" variant="secondary" onClick={handleCloseEvalModal}>
641+
Cancel
642+
</Button>
643+
</ModalFooter>
644+
</Modal>
645+
462646
<Modal
463647
variant={ModalVariant.medium}
464648
title={`Files Contained in Branch: ${diffData?.branch}`}

0 commit comments

Comments
 (0)