feat: LLM-generated adaptive follow-up questions

- Grader: LLM outputs follow_up_question targeting uncovered keyPoints
- Remove static followupHints usage in grading flow
- maxFollowUps sourced from question.maxFollowUps (hints.length)
- Clean answerKey: remove followupHints field
- Three-language prompt update with examples and bad examples
- Grader spec: add follow_up_question to mock responses
This commit is contained in:
Developer
2026-05-21 14:18:14 +08:00
parent 7fd2a4cda2
commit 02f4ab23f7
3 changed files with 48 additions and 29 deletions
@@ -45,7 +45,7 @@ describe('graderNode', () => {
describe('breakout logic (shouldFollowUp overrides)', () => {
it('should NOT follow up when followUpCount >= 2 even if LLM says follow up', async () => {
const model = mockModel({ score: 5, feedback: 'needs work', should_follow_up: true });
const model = mockModel({ score: 5, feedback: 'needs work', should_follow_up: true, follow_up_question: 'More?' });
const state = baseState({ followUpCount: 2 });
const result = await graderNode(state, { configurable: { model } } as any);
expect(result.shouldFollowUp).toBe(false);
@@ -66,7 +66,7 @@ describe('graderNode', () => {
});
it('should allow follow up when conditions are met', async () => {
const model = mockModel({ score: 5, feedback: 'incomplete', should_follow_up: true });
const model = mockModel({ score: 5, feedback: 'incomplete', should_follow_up: true, follow_up_question: 'Can you elaborate?' });
const state = baseState({ followUpCount: 0 });
const result = await graderNode(state, { configurable: { model } } as any);
expect(result.shouldFollowUp).toBe(true);
@@ -92,7 +92,7 @@ describe('graderNode', () => {
});
it('should keep currentQuestionIndex when following up', async () => {
const model = mockModel({ score: 5, feedback: 'needs work', should_follow_up: true });
const model = mockModel({ score: 5, feedback: 'needs work', should_follow_up: true, follow_up_question: 'Can you clarify?' });
const state = baseState({ followUpCount: 0 });
const result = await graderNode(state, { configurable: { model } } as any);
expect(result.currentQuestionIndex).toBe(0);