Skip to content

Commit

Permalink
added retrieval using lookup table and new oak index as moving toward…
Browse files Browse the repository at this point in the history
…s better retrieval
  • Loading branch information
gclomax committed Dec 11, 2024
1 parent fdf486d commit c931de3
Show file tree
Hide file tree
Showing 8 changed files with 28,883 additions and 11 deletions.
1 change: 1 addition & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ repos:
hooks:
- id: check-yaml
- id: check-added-large-files
args: ["--maxkb=1024"]
- repo: https://github.com/gitleaks/gitleaks
rev: v8.20.1
hooks:
Expand Down
190 changes: 190 additions & 0 deletions packages/aila/src/core/quiz/fixtures/example_lesson_ingest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
{
"title": "Angles in quadrilaterals",
"topic": "Geometry",
"cycle1": {
"title": "Understanding quadrilaterals",
"feedback": "Worked example: When drawing a quadrilateral, ensure it has four sides. Use a protractor to verify that the angles sum to 360°.",
"practice": "Identify three quadrilaterals from a group of shapes and check if their angled interior measurements sum to 360 degrees. Draw and label them.",
"explanation": {
"slideText": "Quadrilaterals are four-sided shapes with four angles. Their interior angles sum to 360°.",
"imagePrompt": "different types of quadrilaterals",
"spokenExplanation": [
"Introduce quadrilaterals as four-sided shapes called polygons.",
"Explain that quadrilaterals have four sides and four vertices.",
"Discuss how different quadrilaterals can have varied angles.",
"State that interior angles of quadrilaterals include two triangles.",
"Mention that this makes the total interior angles of quadrilaterals 360 degrees."
],
"accompanyingSlideDetails": "Illustration showing different quadrilaterals like square, rectangle, parallelogram, and kite with their sides and vertices highlighted."
},
"durationInMinutes": 15,
"checkForUnderstanding": [
{
"answers": ["A four-sided polygon"],
"question": "What is a quadrilateral?",
"distractors": ["A three-sided polygon", "A five-sided shape"]
},
{
"answers": ["360 degrees"],
"question": "How many degrees do the angles in a quadrilateral add up to?",
"distractors": ["180 degrees", "450 degrees"]
}
]
},
"cycle2": {
"title": "Finding missing angles",
"feedback": "Model answer: When solving for the missing angle, add known angles and subtract from 360°. E.g., In a quadrilateral with 90°, 130°, and 110°, missing angle = 360° - 330° = 30°.",
"practice": "Calculate the missing angle in a set of quadrilaterals. Each problem has three known angles. Use subtraction from 360°.",
"explanation": {
"slideText": "Use known angles and 360° as the total to find a missing angle in quadrilaterals.",
"imagePrompt": "bar model for quadrilateral angles",
"spokenExplanation": [
"Introduce the concept of finding a missing angle in quadrilaterals.",
"Use knowledge that angles in a quadrilateral sum to 360 degrees.",
"Present a problem with three known angles and one missing angle.",
"Model finding the missing angle using subtraction from 360 degrees.",
"Use a classroom example to show how to apply reasoning."
],
"accompanyingSlideDetails": "A bar model diagram showing angles of a quadrilateral with one missing angle."
},
"durationInMinutes": 15,
"checkForUnderstanding": [
{
"answers": ["Subtract their sum from 360°"],
"question": "If three angles of a quadrilateral are known, how do you find the fourth?",
"distractors": ["Add them together", "Divide the total by three"]
},
{
"answers": ["50 degrees"],
"question": "What is the missing angle if the other angles are 90°, 120°, and 100°?",
"distractors": ["70 degrees", "40 degrees"]
}
]
},
"cycle3": {
"title": "Practical applications",
"feedback": "Success criteria: Each quadrilateral design must have four sides. Use consistent angle measurements, ensuring angles sum to 360° for structural accuracy.",
"practice": "Design a simple architectural feature using quadrilateral shapes. Label each angle and ensure their sum equals 360 degrees.",
"explanation": {
"slideText": "Applying quadrilateral angle calculations in real-life designs like architecture.",
"imagePrompt": "practical applications of quadrilateral angles",
"spokenExplanation": [
"Consolidate the lesson by applying learned skills to realistic scenarios.",
"Present a range of potential real-life problems requiring angle calculations.",
"Discuss how such calculations assist in architectural design or geometry.",
"Reintroduce Izzy and Sam's method, showcasing practical reasoning.",
"Illustrate with drawing tasks to visualise angles as part of quadrilateral arrangements."
],
"accompanyingSlideDetails": "Images of practical applications such as design blueprints or architectural quadrilaterals showcasing angles."
},
"durationInMinutes": 10,
"checkForUnderstanding": [
{
"answers": ["For accurate measurements and structural integrity"],
"question": "Why is it important to find missing angles in quadrilateral designs?",
"distractors": ["To estimate lengths", "To increase material costs"]
},
{
"answers": ["Very crucial for precise planning"],
"question": "In architectural design, how crucial is quadrilateral angle accuracy?",
"distractors": ["Not crucial", "Only slightly important"]
}
]
},
"subject": "maths",
"exitQuiz": [
{
"answers": ["360 degrees"],
"question": "What is the sum of angles in a quadrilateral?",
"distractors": ["180 degrees", "540 degrees"]
},
{
"answers": ["60 degrees"],
"question": "If the angles in a quadrilateral are 120°, 100°, and 80°, what is the fourth angle?",
"distractors": ["40 degrees", "100 degrees"]
},
{
"answers": ["They can be divided into two triangles."],
"question": "Why do the angles in quadrilaterals add up to 360°?",
"distractors": [
"It’s a fixed rule for all shapes.",
"Because of their symmetry."
]
},
{
"answers": ["Subtract the sum of known angles from 360°."],
"question": "How can you find a missing angle in a quadrilateral?",
"distractors": ["Add all angles together.", "Divide the sum by 2."]
}
],
"keyStage": "key-stage-2",
"keywords": [
{
"keyword": "Quadrilateral",
"definition": "A quadrilateral is a polygon with four straight sides and four vertices."
},
{
"keyword": "Point",
"definition": "A point is an exact location. It has no size, only position."
},
{
"keyword": "Sum",
"definition": "The sum is the result of adding two or more numbers."
}
],
"starterQuiz": [
{
"answers": [" A"],
"question": "Which of the angles shown is an obtuse angle?",
"distractors": [" B", " C"]
},
{
"answers": ["180 degrees"],
"question": "What is the sum of angles in a triangle?",
"distractors": ["360 degrees", "90 degrees"]
},
{
"answers": ["Quadrilateral"],
"question": "Which shape has four sides and four angles?",
"distractors": ["Triangle", "Pentagon"]
},
{
"answers": ["Four"],
"question": "How many sides does a rectangle have?",
"distractors": ["Three", "Five"]
},
{
"answers": ["90 degrees"],
"question": "What is the measure of a right angle?",
"distractors": ["180 degrees", "45 degrees"]
},
{
"answers": ["An exact location"],
"question": "What does a point indicate in geometry?",
"distractors": ["A line", "A shape"]
}
],
"learningCycles": [
"Identify the properties of quadrilaterals.",
"Explain that angles in a quadrilateral sum to 360°.",
"Use known angles to find missing angles in quadrilaterals."
],
"misconceptions": [
{
"description": "Pre-teaching or providing a scaffold containing shape names and properties will allow pupils to see the properties and support them in making connections.",
"misconception": "Pupils may not have fluency in properties of 2D shapes and may therefore not notice properties of angles within quadrilaterals such as trapeziums"
}
],
"priorKnowledge": [
"The sum of angles in a triangle is 180 degrees.",
"The basic properties of triangles and rectangles.",
"Addition and subtraction with numbers up to 360."
],
"learningOutcome": "I can explain why the angles in a quadrilateral sum to 360° and use this to find missing angles.",
"keyLearningPoints": [
"A quadrilateral is a shape with four sides and four vertices",
"Interior angles in a quadrilateral are composed of two triangles",
"Interior angles in a quadrilateral are equal to 360 degrees"
],
"additionalMaterials": "None"
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import { Client } from "@elastic/elasticsearch";
import type { SearchResponseBody } from "@elastic/elasticsearch/lib/api/types";

import { QuizSchema } from "../../../protocol/schema";
import { CircleTheoremLesson } from "../fixtures/CircleTheoremsExampleOutput";
import { AilaRagQuizGenerator } from "./AilaRagQuizGenerator";
Expand Down Expand Up @@ -39,6 +42,40 @@ describe("AilaRagQuizGenerator", () => {
expect(QuizSchema.safeParse(quiz)).toBeTruthy();
}
});
it("Should retrieve questions from a given questionUid", async () => {
const result = await quizGenerator.questionArrayFromCustomIds([
"QUES-EYPJ1-67826",
]);

console.log(JSON.stringify(result, null, 2));
expect(result).toBeDefined();
expect(Array.isArray(result)).toBe(true);
// expect(result.length).toBe(1);
expect(result[0]!.question).toBeDefined();
expect(result[0]!.answers).toBeDefined();
expect(result[0]!.distractors).toBeDefined();
});

it("Should search for questions and provide a hit", async () => {
const client = new Client({
cloud: {
id: process.env.I_DOT_AI_ELASTIC_CLOUD_ID as string,
},

auth: {
apiKey: process.env.I_DOT_AI_ELASTIC_KEY as string,
},
});
const result: SearchResponseBody = await quizGenerator.searchQuestions(
client,
"quiz-questions-text-only",
["QUES-XXXXX-XXXXX"],
);
console.log(JSON.stringify(result, null, 2));
expect(result).toBeDefined();
// in this case we are using the dummy elasticsearch client, so we expect to get a hit.
expect(result.hits.hits.length).toBeGreaterThan(0);
});
});

// Run common quiz generator tests
Expand Down
63 changes: 53 additions & 10 deletions packages/aila/src/core/quiz/generators/BaseQuizGenerator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,11 @@ import {
} from "../OpenAIRanker";
import { processArray, withRandomDelay } from "../apiCallingUtils";
import { testInput } from "../fixtures/cachedQuizOutput";
import type { CustomHit } from "../interfaces";
import type { CustomHit, LessonSlugQuizMapping } from "../interfaces";
import { CohereReranker } from "../rerankers";
import type { QuizzesForConsideration } from "../rerankers/RerankerStructuredOutputSchema";
import { starterQuizQuestionSuitabilityDescriptionSchema } from "../rerankers/RerankerStructuredOutputSchema";
import { lessonSlugQuizMap } from "./lessonSlugLookup";

// Base abstract class
export abstract class BaseQuizGenerator implements AilaQuizGeneratorService {
Expand Down Expand Up @@ -152,6 +153,21 @@ export abstract class BaseQuizGenerator implements AilaQuizGeneratorService {
);
return questionIds;
}

public lessonSlugToQuestionIdsLookupTable(
lessonSlug: string,
quizType: QuizPath,
): string[] {
if (quizType === "/starterQuiz") {
return (
lessonSlugQuizMap?.[lessonSlug]?.starterQuiz ?? ["QUES-HDSJ2-34404"]
);
} else if (quizType === "/exitQuiz") {
return lessonSlugQuizMap?.[lessonSlug]?.exitQuiz ?? ["QUES-HDSJ2-34404"];
}
throw new Error("Invalid quiz type");
}

public async patchFromCustomIDs(
customIds: string[],
quizType: QuizPath = "/starterQuiz",
Expand All @@ -165,9 +181,11 @@ export abstract class BaseQuizGenerator implements AilaQuizGeneratorService {
public async questionArrayFromCustomIds(
customIds: string[],
): Promise<QuizQuestion[]> {
// TODO: GCLOMAX - dependancy injection of index here.

const formattedQuestionSearchResponse = await this.searchQuestions(
this.client,
"oak-vector",
"quiz-questions-text-only",
customIds,
);
const processsedQuestionsAndIds = this.processResponse(
Expand All @@ -176,30 +194,50 @@ export abstract class BaseQuizGenerator implements AilaQuizGeneratorService {
const quizQuestions = this.extractQuizQuestions(processsedQuestionsAndIds);
return quizQuestions;
}
public async questionArrayFromPlanId(

public async questionArrayFromPlanIdLookUpTable(
planId: string,
quizType: QuizPath,
): Promise<QuizQuestion[]> {
const lessonSlug = await this.getLessonSlugFromPlanId(planId);
const lessonSlugList = lessonSlug ? [lessonSlug] : [];
const customIds = await this.lessonSlugToQuestionIdSearch(lessonSlugList);
const quizQuestions = await this.questionArrayFromCustomIds(customIds);
if (!lessonSlug) {
throw new Error("Lesson slug not found for planId: " + planId);
}
const questionIds = this.lessonSlugToQuestionIdsLookupTable(
lessonSlug,
quizType,
);

const quizQuestions = await this.questionArrayFromCustomIds(questionIds);
return quizQuestions;
}

private async searchQuestions(
public async questionArrayFromPlanId(
planId: string,
): Promise<QuizQuestion[]> {
// const lessonSlug = await this.getLessonSlugFromPlanId(planId);
// const lessonSlugList = lessonSlug ? [lessonSlug] : [];
// const customIds = await this.lessonSlugToQuestionIdSearch(lessonSlugList);
// const quizQuestions = await this.questionArrayFromCustomIds(customIds);
// return quizQuestions;
return await this.questionArrayFromPlanIdLookUpTable(
planId,
"/starterQuiz",
);
}

public async searchQuestions(
client: Client,
index: string,
questionUids: string[],
): Promise<SearchResponseBody> {
// currently metadata.is_quiz_question_schema is th flag we use for
// Retrieves questions by questionUids
const response = await client.search({
index: index,
body: {
query: {
bool: {
must: [
{ exists: { field: "metadata.is_quiz_question_schema" } },
{
terms: {
"metadata.questionUid.keyword": questionUids,
Expand Down Expand Up @@ -430,7 +468,8 @@ export abstract class BaseQuizGenerator implements AilaQuizGeneratorService {
): Promise<QuizQuestion[]> {
const formattedQuestionSearchResponse = await this.searchQuestions(
this.client,
"oak-vector",
// "oak-vector",
"quiz-questions-text-only",
customIds,
);
const processedQuestionsAndIds = this.processResponse(
Expand Down Expand Up @@ -489,6 +528,10 @@ export abstract class BaseQuizGenerator implements AilaQuizGeneratorService {
if (error instanceof z.ZodError) {
console.error("Validation error:", error.errors);
} else if (error instanceof SyntaxError) {
console.error(
"OFFENDING JSON STRING: ",
JSON.stringify(jsonString, null, 2),
);
console.error("JSON parsing error:", error.message);
} else {
console.error("An unexpected error occurred:", error);
Expand Down
Loading

0 comments on commit c931de3

Please sign in to comment.