@ai2070/l0
Version:
L0: The Missing Reliability Substrate for AI
248 lines • 7.94 kB
JavaScript
const BEGIN_PATTERN = /\\begin\{(\w+)\}/g;
const END_PATTERN = /\\end\{(\w+)\}/g;
const DISPLAY_MATH_OPEN = /\\\[/g;
const DISPLAY_MATH_CLOSE = /\\\]/g;
const DOUBLE_DOLLAR = /\$\$/g;
const LATEX_PATTERNS = [
/\\begin\{/,
/\\end\{/,
/\\\[/,
/\\\]/,
/\$\$/,
/\\[a-zA-Z]+\{/,
/\\section/,
/\\subsection/,
/\\textbf/,
/\\textit/,
/\\frac/,
/\\sum/,
/\\int/,
];
export function analyzeLatexStructure(content) {
const issues = [];
const openEnvironments = [];
const envStack = [];
BEGIN_PATTERN.lastIndex = 0;
END_PATTERN.lastIndex = 0;
const begins = [];
const ends = [];
let match;
while ((match = BEGIN_PATTERN.exec(content)) !== null) {
begins.push({ env: match[1], pos: match.index });
}
while ((match = END_PATTERN.exec(content)) !== null) {
ends.push({ env: match[1], pos: match.index });
}
const events = [
...begins.map((b) => ({ ...b, type: "begin" })),
...ends.map((e) => ({ ...e, type: "end" })),
].sort((a, b) => a.pos - b.pos);
for (const event of events) {
if (event.type === "begin") {
envStack.push(event.env);
}
else {
if (envStack.length === 0) {
issues.push(`\\end{${event.env}} without matching \\begin{${event.env}}`);
}
else {
const last = envStack[envStack.length - 1];
if (last === event.env) {
envStack.pop();
}
else {
issues.push(`Environment mismatch: \\begin{${last}} closed with \\end{${event.env}}`);
envStack.pop();
}
}
}
}
for (const env of envStack) {
openEnvironments.push(env);
issues.push(`Unclosed environment: \\begin{${env}}`);
}
const isBalanced = envStack.length === 0 && issues.length === 0;
return {
openEnvironments,
isBalanced,
issues,
};
}
export function looksLikeLatex(content) {
if (!content)
return false;
return LATEX_PATTERNS.some((pattern) => pattern.test(content));
}
export function validateLatexEnvironments(context) {
const { content, completed } = context;
const violations = [];
if (!looksLikeLatex(content)) {
return violations;
}
const structure = analyzeLatexStructure(content);
if (!completed) {
const mismatchIssues = structure.issues.filter((issue) => issue.includes("mismatch"));
for (const issue of mismatchIssues) {
violations.push({
rule: "latex-environments",
message: issue,
severity: "error",
recoverable: true,
});
}
if (structure.openEnvironments.length > 5) {
violations.push({
rule: "latex-environments",
message: `Excessive unclosed environments: ${structure.openEnvironments.length}`,
severity: "warning",
recoverable: true,
});
}
}
else {
if (!structure.isBalanced) {
for (const issue of structure.issues) {
violations.push({
rule: "latex-environments",
message: issue,
severity: "error",
recoverable: true,
suggestion: "Retry generation to properly balance LaTeX environments",
});
}
}
}
return violations;
}
export function validateLatexMath(context) {
const { content, completed } = context;
const violations = [];
if (!looksLikeLatex(content)) {
return violations;
}
DISPLAY_MATH_OPEN.lastIndex = 0;
DISPLAY_MATH_CLOSE.lastIndex = 0;
DOUBLE_DOLLAR.lastIndex = 0;
const displayMathOpen = (content.match(DISPLAY_MATH_OPEN) || []).length;
const displayMathClose = (content.match(DISPLAY_MATH_CLOSE) || []).length;
const doubleDollar = (content.match(DOUBLE_DOLLAR) || []).length;
let singleDollarCount = 0;
let escapeNext = false;
for (let i = 0; i < content.length; i++) {
if (escapeNext) {
escapeNext = false;
continue;
}
if (content[i] === "\\") {
escapeNext = true;
continue;
}
if (content[i] === "$" &&
(i + 1 >= content.length || content[i + 1] !== "$")) {
singleDollarCount++;
}
}
if (completed) {
if (displayMathOpen !== displayMathClose) {
violations.push({
rule: "latex-math",
message: `Unbalanced display math: ${displayMathOpen} \\[ and ${displayMathClose} \\]`,
severity: "error",
recoverable: true,
suggestion: "Ensure all \\[ have matching \\]",
});
}
if (doubleDollar % 2 !== 0) {
violations.push({
rule: "latex-math",
message: `Unbalanced $$ delimiters: ${doubleDollar} found (should be even)`,
severity: "error",
recoverable: true,
suggestion: "Ensure all $$ are paired",
});
}
if (singleDollarCount % 2 !== 0) {
violations.push({
rule: "latex-math",
message: `Unbalanced inline math: ${singleDollarCount} $ found (should be even)`,
severity: "warning",
recoverable: true,
suggestion: "Check inline math delimiters",
});
}
}
return violations;
}
export function validateLatexCommon(context) {
const { content, completed } = context;
const violations = [];
if (!looksLikeLatex(content)) {
return violations;
}
if (!completed) {
return violations;
}
const commandPattern = /\\[a-zA-Z]+/g;
let match;
while ((match = commandPattern.exec(content)) !== null) {
const afterCommand = content.slice(match.index + match[0].length);
if (afterCommand.startsWith("{")) {
let depth = 0;
let found = false;
for (let i = 0; i < afterCommand.length; i++) {
if (afterCommand[i] === "{")
depth++;
if (afterCommand[i] === "}") {
depth--;
if (depth === 0) {
found = true;
break;
}
}
}
if (!found && depth > 0) {
violations.push({
rule: "latex-common",
message: `Unclosed braces after command ${match[0]}`,
severity: "warning",
recoverable: true,
});
}
}
}
return violations;
}
export function latexRule() {
return {
name: "latex-environments",
description: "Validates LaTeX environment balance and structure",
streaming: true,
severity: "error",
recoverable: true,
check: (context) => {
const violations = [];
if (!looksLikeLatex(context.content) && context.content.length > 50) {
return violations;
}
violations.push(...validateLatexEnvironments(context));
violations.push(...validateLatexMath(context));
if (context.completed) {
violations.push(...validateLatexCommon(context));
}
return violations;
},
};
}
export class LatexGuardrail {
rule;
constructor() {
this.rule = latexRule();
}
check(context) {
return this.rule.check(context);
}
get name() {
return this.rule.name;
}
}
//# sourceMappingURL=latex.js.map