@yuxilabs/gptp-core
Version:
Core validation, formatting and execution logic for the GPTP file format.
89 lines (88 loc) โข 3.02 kB
JavaScript
// src/engine/validate/validatePrompt.ts
import Ajv from "ajv";
import addFormats from "ajv-formats";
import { schemaLoader } from "@/utils/schemaLoader";
const ajv = new Ajv({
strict: true,
strictTypes: false,
allowUnionTypes: true,
allErrors: true,
});
addFormats(ajv);
// ๐ Cache compiled validator
let validateFn = null;
async function getValidator() {
if (validateFn)
return validateFn;
const schema = await schemaLoader();
// ๐งผ Remove $id to avoid duplicate registration errors
if (typeof schema === 'object' && schema !== null && '$id' in schema) {
delete schema['$id'];
}
validateFn = ajv.compile(schema);
return validateFn;
}
/**
* Run domain-level validation checks
*/
function runSemanticChecks(doc) {
const errors = [];
if (!Array.isArray(doc.messages) || doc.messages.length === 0) {
errors.push({
instancePath: "/messages",
schemaPath: "#/properties/messages/minItems",
keyword: "minItems",
params: { limit: 1 },
message: "At least one message is required",
});
}
doc.messages?.forEach((msg, index) => {
if (!["user", "assistant", "system"].includes(msg.role)) {
errors.push({
instancePath: `/messages/${index}/role`,
schemaPath: "#/properties/messages/items/properties/role/enum",
keyword: "enum",
params: {},
message: `Invalid role: '${msg.role}'`,
});
}
if (!msg.content || typeof msg.content !== "string") {
errors.push({
instancePath: `/messages/${index}/content`,
schemaPath: "#/properties/messages/items/properties/content/type",
keyword: "type",
params: {},
message: "Message content must be a non-empty string",
});
}
});
if (doc.variables) {
for (const [name, variable] of Object.entries(doc.variables)) {
if (!variable.type) {
errors.push({
instancePath: `/variables/${name}`,
schemaPath: "#/properties/variables/additionalProperties/required",
keyword: "required",
params: { missingProperty: "type" },
message: `Variable '${name}' is missing a 'type' property`,
});
}
}
}
return errors;
}
/**
* Validates a GPTPDocument against schema + semantic rules.
*/
export async function validatePrompt(prompt) {
const validate = await getValidator();
const isSchemaValid = validate(prompt);
const schemaErrors = validate.errors ?? [];
const semanticErrors = runSemanticChecks(prompt);
const allErrors = [...schemaErrors, ...semanticErrors];
return {
valid: allErrors.length === 0,
errors: allErrors,
data: allErrors.length === 0 ? prompt : undefined,
};
}