Files
aurak/server/src/assessment/graph/builder.ts
T
Developer 68371922ca P0-1/P0-2/P1-1: dimensions form + E2E tests + PDF export
P0-1 Backend: dimensions column on template entity + validation
P0-1 Frontend: dimensions edit UI in TemplateManager
P0-2: routeAfterGrading unit tests (10 cases), service spec fix + certificate tests, jest-e2e.json
P1-1: proper PDF generation with embedded CJK font via pdf-lib low-level API
2026-05-19 08:42:03 +08:00

79 lines
2.5 KiB
TypeScript

import { StateGraph, MemorySaver } from '@langchain/langgraph';
import { EvaluationAnnotation } from './state';
import { questionGeneratorNode } from './nodes/generator.node';
import { interviewerNode } from './nodes/interviewer.node';
import { graderNode } from './nodes/grader.node';
import { reportAnalyzerNode } from './nodes/analyzer.node';
/**
* Conditional routing logic for the Grader node.
*/
export const routeAfterGrading = (state: typeof EvaluationAnnotation.State) => {
const targetCount = state.questionCount || 5;
const questionsLen = state.questions?.length || 0;
const currentIndex = Math.max(0, state.currentQuestionIndex || 0);
console.log('[Router] Evaluation Result:', {
currentIndex,
shouldFollowUp: state.shouldFollowUp,
numQuestions: questionsLen,
targetCount,
});
if (state.shouldFollowUp) {
console.log('[Router] Routing to follow-up interviewer');
return 'interviewer';
}
if (currentIndex < targetCount) {
// If the next question isn't generated yet, go back to generator
if (currentIndex >= questionsLen) {
console.log('[Router] Index >= Questions, routing to generator');
return 'generator';
}
// If it is generated, go to interviewer
console.log('[Router] Index < Questions, routing to interviewer');
return 'interviewer';
}
console.log('[Router] Assessment complete, routing to analyzer');
return 'analyzer';
};
/**
* Builds and compiles the Evaluation Graph.
*/
export const createEvaluationGraph = () => {
const workflow = new StateGraph(EvaluationAnnotation)
.addNode('generator', questionGeneratorNode)
.addNode('interviewer', interviewerNode)
.addNode('grader', graderNode)
.addNode('analyzer', reportAnalyzerNode)
// Flow definition
.addEdge('__start__', 'generator')
.addEdge('generator', 'interviewer')
// After interviewer, the graph will naturally pause for user input
// if we use it in a thread-safe way with interrupts or simple invocation.
.addEdge('interviewer', 'grader')
// After grading, decide where to go
.addConditionalEdges('grader', routeAfterGrading, {
interviewer: 'interviewer',
generator: 'generator',
analyzer: 'analyzer',
})
.addEdge('analyzer', '__end__');
// Using MemorySaver for thread-based persistence
const checkpointer = new MemorySaver();
return workflow.compile({
checkpointer,
// We want the graph to stop after the interviewer presents the question
interruptAfter: ['interviewer'],
});
};