Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions controllers/problemGeneratorController.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,22 @@ const problemGeneratorService = require("../services/problemGeneratorService");
const generateProblem = async (req, res) => {
try {
const { subject, additionalDetails, exampleProblem } = req.body;
const normalizedSubject = typeof subject === "string" ? subject.trim() : "";

if (!subject) {
if (!normalizedSubject) {
return res.status(400).json({
error: "Subject is required",
});
}

if (!exampleProblem) {
if (!exampleProblem || typeof exampleProblem !== "object") {
return res.status(400).json({
error: "Example problem is required",
});
}

const generatedProblem = await problemGeneratorService.generateProblem({
subject,
subject: normalizedSubject,
additionalDetails,
exampleProblem,
});
Expand Down
6 changes: 3 additions & 3 deletions services/evaluationService.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ class EvaluationService {
}

const expectedSolution = leia.spec.problem.spec.solution;
const solutionFormat = leia.spec.problem.solutionFormat;
const evaluationPrompt = leia.spec.problem.evaluationPrompt;
const solutionFormat = leia.spec.problem.spec.solutionFormat;
const evaluationPrompt = leia.spec.problem.spec.evaluationPrompt;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Eso es lo que estaba causando que las evaluaciones no fuesen correctas?


const prompt = this.generatePrompt(result, expectedSolution, solutionFormat, evaluationPrompt)

Expand Down Expand Up @@ -63,7 +63,7 @@ class EvaluationService {
}
}

return `Evaluate the following solution (${solutionFormat} format):
return `Evaluate the following solution (${format} format):

Student's solution:
${studentSolution}
Expand Down
67 changes: 60 additions & 7 deletions services/problemGeneratorService.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
const { OpenAI } = require("openai");
const z = require("zod");
const { zodResponseFormat, zodTextFormat } = require("openai/helpers/zod");
const { zodTextFormat } = require("openai/helpers/zod");


// Schema for generated problem
Expand All @@ -9,8 +9,34 @@ const ProblemSpecSchema = z.object({
personaBackground: z.string().describe("Background context for the persona in this problem scenario"),
details: z.string().describe("Extended details about the problem, including specific requirements"),
solution: z.string().describe("The expected solution in the specified format"),
evaluationPrompt: z.string().optional().describe("Prompt used to evaluate the student solution"),
extendsJson: z.string().optional().describe("JSON object string for extends (e.g., {\"persona\": {\"spec\": {\"personality\": [\"very busy\"]}}})"),
overridesJson: z.string().optional().describe("JSON object string for overrides (e.g., {\"behaviour\": {\"spec\": {\"role\": \"tax inspector\"}}})"),
});

const isObject = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
const hasKeys = (value) => isObject(value) && Object.keys(value).length > 0;
const safeStringifyObject = (value) => {
if (!isObject(value)) return "Not provided";
try {
return JSON.stringify(value, null, 2);
} catch {
return "Not provided";
}
};
const safeParseJsonObject = (value) => {
if (typeof value !== "string" || !value.trim()) {
return null;
}

try {
const parsed = JSON.parse(value);
return isObject(parsed) ? parsed : null;
} catch {
return null;
}
};

class ProblemGeneratorService {
constructor() {
const openai = new OpenAI({
Expand All @@ -22,7 +48,7 @@ class ProblemGeneratorService {
/**
* Generates a new problem based on an example problem with a different subject
* @param {Object} params - Generation parameters
* @param {string} params.subject - The new subject for the problem (e.g., "Prison Management System")
* @param {string} params.subject - The new subject for the problem (e.g., "Library Management System")
* @param {string} params.additionalDetails - Optional additional instructions from the user
* @param {Object} params.exampleProblem - The example problem to use as a template
* @returns {Promise<Object>} - Generated problem matching the Problem schema
Expand All @@ -33,6 +59,12 @@ class ProblemGeneratorService {
}

const exampleSpec = exampleProblem.spec || exampleProblem;
const templateHasEvaluationPrompt = Boolean(
typeof exampleSpec.evaluationPrompt === "string" &&
exampleSpec.evaluationPrompt.trim()
);
const templateHasExtends = hasKeys(exampleSpec.extends);
const templateHasOverrides = hasKeys(exampleSpec.overrides);

const prompt = `Generate a new problem for an educational exercise.

Expand All @@ -58,6 +90,9 @@ You can use these placeholders in the problem fields (description, personaBackgr
- Details: ${exampleSpec.details || "Not provided"}
- Solution: ${exampleSpec.solution || "Not provided"}
- Solution Format: ${exampleSpec.solutionFormat || "text"}
- Evaluation Prompt: ${templateHasEvaluationPrompt ? exampleSpec.evaluationPrompt : "Not provided"}
- Extends (JSON): ${safeStringifyObject(exampleSpec.extends)}
- Overrides (JSON): ${safeStringifyObject(exampleSpec.overrides)}

## NEW PROBLEM REQUIREMENTS:
- Topic/Domain: "${subject}"
Expand All @@ -71,7 +106,17 @@ ${additionalDetails ? `- Additional instructions: ${additionalDetails}` : ""}
5. The SOLUTION must be complete and in ${exampleSpec.solutionFormat || "text"} format (if "mermaid", generate a valid UML class diagram)
6. Maintain the same level of complexity and detail as the example
7. Content should be realistic and educational for students
8. Use template tags ({{persona.*}}, {{behaviour.*}}) to make the content dynamic where it makes sense`;
8. Use template tags ({{persona.*}}, {{behaviour.*}}) to make the content dynamic where it makes sense
9. ${templateHasEvaluationPrompt
? "Generate an adapted EVALUATION PROMPT for the new domain keeping the same intent and strictness."
: "If there is no evaluation prompt in the template, return an empty string or omit it."}
10. ${templateHasExtends
? "Adapt EXTENDS to the new domain. Keep the same structure and keys, but update domain-specific values."
: "If extends is not provided in the template, return an empty object or omit it."}
11. ${templateHasOverrides
? "Adapt OVERRIDES to the new domain. Keep the same structure and keys, but update domain-specific values."
: "If overrides is not provided in the template, return an empty object or omit it."}
12. IMPORTANT: Return extends and overrides as valid JSON STRINGS in fields extendsJson and overridesJson. Use "{}" when empty.`;

const response = await this.client.responses.parse({
model: process.env.OPENAI_MODEL || "gpt-4o-mini",
Expand All @@ -95,21 +140,29 @@ Always respond in the same language as the example problem provided.`,
}
});

const generatedSpec = response.output_parsed;
const generatedSpec = response.output_parsed || {};
const generatedExtends = safeParseJsonObject(generatedSpec.extendsJson);
const generatedOverrides = safeParseJsonObject(generatedSpec.overridesJson);

// Build the complete problem object
return {
apiVersion: "v1",
metadata: {
name: subject.toLowerCase().replace(/\s+/g, "-"),
name: subject.trim().toLowerCase().replace(/\s+/g, "-"),
version: "1.0.0",
},
spec: {
...generatedSpec,
solutionFormat: exampleSpec.solutionFormat || "text",
process: exampleSpec.process || [],
extends: exampleSpec.extends || {},
overrides: exampleSpec.overrides || {},
evaluationPrompt: generatedSpec.evaluationPrompt
|| (templateHasEvaluationPrompt ? exampleSpec.evaluationPrompt : ""),
extends: hasKeys(generatedExtends)
? generatedExtends
: (exampleSpec.extends || {}),
overrides: hasKeys(generatedOverrides)
? generatedOverrides
: (exampleSpec.overrides || {}),
constrainedTo: exampleSpec.constrainedTo || {},
},
};
Expand Down