fix
This commit is contained in:
@@ -135,7 +135,7 @@ describe('quizQuestionsSchema', () => {
|
|||||||
).toThrow();
|
).toThrow();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('rejects a missing or unknown difficulty', () => {
|
it('falls back to medium for a missing or unknown difficulty', () => {
|
||||||
const base = {
|
const base = {
|
||||||
id: 'q',
|
id: 'q',
|
||||||
question: 'q',
|
question: 'q',
|
||||||
@@ -144,10 +144,12 @@ describe('quizQuestionsSchema', () => {
|
|||||||
correctIndex: 0,
|
correctIndex: 0,
|
||||||
explanation: 'because',
|
explanation: 'because',
|
||||||
};
|
};
|
||||||
expect(() => quizQuestionsSchema.parse({ questions: [base] })).toThrow();
|
// Missing difficulty should default to 'medium'
|
||||||
expect(() =>
|
const parsed1 = quizQuestionsSchema.parse({ questions: [base] });
|
||||||
quizQuestionsSchema.parse({ questions: [{ ...base, difficulty: 'trivial' }] }),
|
expect(parsed1.questions[0].difficulty).toBe('medium');
|
||||||
).toThrow();
|
// Unknown difficulty should also default to 'medium'
|
||||||
|
const parsed2 = quizQuestionsSchema.parse({ questions: [{ ...base, difficulty: 'trivial' }] });
|
||||||
|
expect(parsed2.questions[0].difficulty).toBe('medium');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -112,20 +112,32 @@ export const learningAllSchema = z.object({
|
|||||||
infographic: infographicBodySchema,
|
infographic: infographicBodySchema,
|
||||||
});
|
});
|
||||||
|
|
||||||
const quizDifficultyEnum = z.enum(['easy', 'medium', 'hard']);
|
const quizDifficultyEnum = z.enum(['easy', 'medium', 'hard']).catch('medium');
|
||||||
|
|
||||||
const quizQuestionSchema = z.object({
|
const quizQuestionSchema = z.object({
|
||||||
id: z.string().min(1),
|
id: z.string().min(1).catch(`gen-${Math.random().toString(36).slice(2, 8)}`),
|
||||||
question: z.string().min(1),
|
question: z.string().min(1),
|
||||||
topicLabel: z.string().min(1),
|
topicLabel: z.string().min(1).catch('General'),
|
||||||
options: z.array(z.string().min(1)).length(4),
|
options: z.array(z.string().min(1)).length(4),
|
||||||
correctIndex: z.number().int().min(0).max(3),
|
correctIndex: z.preprocess(
|
||||||
explanation: z.string().min(1),
|
(v) => (typeof v === 'number' ? Math.round(v) : v),
|
||||||
|
z.number().int().min(0).max(3),
|
||||||
|
),
|
||||||
|
explanation: z.string().min(1).catch('See the correct answer above.'),
|
||||||
difficulty: quizDifficultyEnum,
|
difficulty: quizDifficultyEnum,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const quizQuestionsSchema = z.object({
|
export const quizQuestionsSchema = z.object({
|
||||||
questions: z.array(quizQuestionSchema).min(1),
|
questions: z.preprocess(
|
||||||
|
(v) => {
|
||||||
|
// If the model returned an array directly, wrap it
|
||||||
|
if (Array.isArray(v)) return v;
|
||||||
|
// If it returned a single object, wrap in array
|
||||||
|
if (v && typeof v === 'object' && !Array.isArray(v)) return [v];
|
||||||
|
return v;
|
||||||
|
},
|
||||||
|
z.array(quizQuestionSchema).min(1),
|
||||||
|
),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const customTopicSchema = z.object({
|
export const customTopicSchema = z.object({
|
||||||
|
|||||||
@@ -79,10 +79,40 @@ Options must be prefixed "A) ", "B) ", "C) ", "D) ". Make questions specific and
|
|||||||
});
|
});
|
||||||
|
|
||||||
const emitted = result.toolUses[0]?.input;
|
const emitted = result.toolUses[0]?.input;
|
||||||
if (!emitted?.questions?.length) {
|
if (!emitted) {
|
||||||
throw new Error(`Could not generate questions for ${topic.label}`);
|
throw new Error(`LLM did not emit tool output for ${topic.label}`);
|
||||||
}
|
}
|
||||||
return emitted.questions;
|
|
||||||
|
// Handle different shapes the LLM might return:
|
||||||
|
// 1. { questions: [...] } — expected
|
||||||
|
// 2. [...] — model returned array directly
|
||||||
|
// 3. { questions: { ... } } — model wrapped single question in object
|
||||||
|
let questions;
|
||||||
|
if (Array.isArray(emitted.questions)) {
|
||||||
|
questions = emitted.questions;
|
||||||
|
} else if (Array.isArray(emitted)) {
|
||||||
|
questions = emitted;
|
||||||
|
} else if (emitted.questions && typeof emitted.questions === 'object') {
|
||||||
|
// Single question wrapped as object instead of array
|
||||||
|
questions = [emitted.questions];
|
||||||
|
} else {
|
||||||
|
throw new Error(`Unexpected quiz output shape for ${topic.label}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!questions.length) {
|
||||||
|
throw new Error(`No questions generated for ${topic.label}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure each question has required fields (fill in defaults for missing ones)
|
||||||
|
return questions.map((q, i) => ({
|
||||||
|
id: q.id || `gen-${i}-${Math.random().toString(36).slice(2, 8)}`,
|
||||||
|
question: q.question || '',
|
||||||
|
topicLabel: q.topicLabel || topic.label,
|
||||||
|
options: Array.isArray(q.options) ? q.options.slice(0, 4) : [],
|
||||||
|
correctIndex: typeof q.correctIndex === 'number' ? q.correctIndex : 0,
|
||||||
|
explanation: q.explanation || 'No explanation provided.',
|
||||||
|
difficulty: q.difficulty || 'medium',
|
||||||
|
})).filter(q => q.question && q.options.length === 4);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function selectTestTopics(userId, isoWeekNumber) {
|
async function selectTestTopics(userId, isoWeekNumber) {
|
||||||
|
|||||||
Reference in New Issue
Block a user