@ai2070/l0
Version:
L0: The Missing Reliability Substrate for AI
294 lines • 12.5 kB
JavaScript
import { z } from "zod";
import { l0 } from "./runtime/l0";
import { autoCorrectJSON, isValidJSON, extractJSON } from "./utils/autoCorrect";
export async function structured(options) {
const { schema, stream: streamFactory, fallbackStreams = [], retry = {}, autoCorrect = true, strictMode = false, timeout, signal, monitoring, detectZeroTokens, onValidationError, onAutoCorrect, onRetry, } = options;
let validationAttempts = 0;
let validationFailures = 0;
let autoCorrections = 0;
const correctionTypes = [];
const validationErrors = [];
let rawOutput = "";
let correctedOutput = "";
let appliedCorrections = [];
let wasAutoCorrected = false;
const errors = [];
let validationStartTime = 0;
let validationEndTime = 0;
const abortController = new AbortController();
const wrappedStreamFactory = async () => {
return streamFactory();
};
const l0Options = {
stream: wrappedStreamFactory,
fallbackStreams,
retry: {
attempts: retry.attempts ?? 2,
backoff: retry.backoff ?? "fixed-jitter",
baseDelay: retry.baseDelay ?? 1000,
maxDelay: retry.maxDelay ?? 5000,
retryOn: [...(retry.retryOn || []), "guardrail_violation", "incomplete"],
errorTypeDelays: retry.errorTypeDelays,
},
timeout,
signal: signal || abortController.signal,
detectZeroTokens: detectZeroTokens ?? false,
monitoring: {
enabled: monitoring?.enabled ?? false,
sampleRate: monitoring?.sampleRate ?? 1.0,
metadata: {
...(monitoring?.metadata || {}),
structured: true,
schemaName: schema.description || "unknown",
},
},
guardrails: [
{
name: "json-structure",
check: (context) => {
if (context.completed) {
if (!isValidJSON(context.content)) {
return [
{
rule: "json-structure",
message: "Output is not valid JSON",
severity: "error",
recoverable: true,
},
];
}
}
return [];
},
},
],
onRetry: (attempt, reason) => {
if (onRetry) {
onRetry(attempt, reason);
}
},
};
const maxValidationRetries = retry.attempts ?? 2;
let currentValidationAttempt = 0;
while (currentValidationAttempt <= maxValidationRetries) {
try {
const result = await l0(l0Options);
rawOutput = "";
for await (const event of result.stream) {
if (event.type === "token" && event.value) {
rawOutput += event.value;
}
else if (event.type === "error") {
errors.push(event.error || new Error("Unknown error"));
}
}
if (!rawOutput || rawOutput.trim().length === 0) {
throw new Error("No output received from model");
}
validationStartTime = Date.now();
validationAttempts++;
correctedOutput = rawOutput;
appliedCorrections = [];
if (autoCorrect) {
const correctionResult = autoCorrectJSON(correctedOutput, {
structural: true,
stripFormatting: true,
schemaBased: false,
strict: strictMode,
});
if (correctionResult.corrections.length > 0) {
wasAutoCorrected = true;
correctedOutput = correctionResult.corrected;
appliedCorrections = correctionResult.corrections;
autoCorrections++;
correctionTypes.push(...correctionResult.corrections);
if (onAutoCorrect) {
const correctionInfo = {
original: rawOutput,
corrected: correctedOutput,
corrections: correctionResult.corrections,
success: correctionResult.success,
};
onAutoCorrect(correctionInfo);
}
}
}
let parsedData;
try {
parsedData = JSON.parse(correctedOutput);
}
catch (parseError) {
const err = parseError instanceof Error
? parseError
: new Error(String(parseError));
errors.push(err);
const extracted = extractJSON(correctedOutput);
if (extracted !== correctedOutput) {
try {
parsedData = JSON.parse(extracted);
correctedOutput = extracted;
wasAutoCorrected = true;
if (!appliedCorrections.includes("extract_json")) {
appliedCorrections.push("extract_json");
correctionTypes.push("extract_json");
}
autoCorrections++;
}
catch {
const rescueResult = autoCorrectJSON(extracted, {
structural: true,
stripFormatting: true,
});
if (rescueResult.success) {
parsedData = JSON.parse(rescueResult.corrected);
correctedOutput = rescueResult.corrected;
wasAutoCorrected = true;
appliedCorrections.push(...rescueResult.corrections);
autoCorrections++;
correctionTypes.push(...rescueResult.corrections);
}
else {
throw new Error(`Invalid JSON after auto-correction: ${err.message}`);
}
}
}
else if (!autoCorrect) {
const rescueResult = autoCorrectJSON(correctedOutput, {
structural: true,
stripFormatting: true,
});
if (rescueResult.success) {
parsedData = JSON.parse(rescueResult.corrected);
correctedOutput = rescueResult.corrected;
wasAutoCorrected = true;
appliedCorrections.push(...rescueResult.corrections);
autoCorrections++;
correctionTypes.push(...rescueResult.corrections);
}
else {
throw new Error(`Invalid JSON: ${err.message}`);
}
}
else {
const rawExtracted = extractJSON(rawOutput);
if (rawExtracted !== rawOutput) {
const rescueResult = autoCorrectJSON(rawExtracted, {
structural: true,
stripFormatting: true,
});
if (rescueResult.success) {
try {
parsedData = JSON.parse(rescueResult.corrected);
correctedOutput = rescueResult.corrected;
wasAutoCorrected = true;
appliedCorrections.push("extract_json", ...rescueResult.corrections);
autoCorrections++;
correctionTypes.push("extract_json", ...rescueResult.corrections);
}
catch {
throw new Error(`Invalid JSON after auto-correction: ${err.message}`);
}
}
else {
throw new Error(`Invalid JSON after auto-correction: ${err.message}`);
}
}
else {
throw new Error(`Invalid JSON after auto-correction: ${err.message}`);
}
}
}
const validationResult = schema.safeParse(parsedData);
if (!validationResult.success) {
validationFailures++;
validationErrors.push(validationResult.error);
if (onValidationError) {
onValidationError(validationResult.error, currentValidationAttempt);
}
if (currentValidationAttempt < maxValidationRetries) {
currentValidationAttempt++;
if (onRetry) {
onRetry(currentValidationAttempt, `Schema validation failed: ${validationResult.error.errors[0]?.message}`);
}
continue;
}
throw new Error(`Schema validation failed after ${validationAttempts} attempts: ${JSON.stringify(validationResult.error.errors)}`);
}
validationEndTime = Date.now();
const structuredState = {
...result.state,
validationFailures,
autoCorrections,
validationErrors,
};
let structuredTelemetry;
if (result.telemetry) {
structuredTelemetry = {
...result.telemetry,
structured: {
schemaName: schema.description || "unknown",
validationAttempts,
validationFailures,
autoCorrections,
correctionTypes: Array.from(new Set(correctionTypes)),
validationSuccess: true,
validationTime: validationEndTime - validationStartTime,
},
};
}
return {
data: validationResult.data,
raw: rawOutput,
corrected: wasAutoCorrected,
corrections: appliedCorrections,
state: structuredState,
telemetry: structuredTelemetry,
errors,
abort: () => abortController.abort(),
};
}
catch (error) {
const err = error instanceof Error ? error : new Error(String(error));
errors.push(err);
if (currentValidationAttempt < maxValidationRetries) {
currentValidationAttempt++;
if (onRetry) {
onRetry(currentValidationAttempt, err.message);
}
continue;
}
throw new Error(`Structured output failed after ${currentValidationAttempt + 1} attempts: ${err.message}`);
}
}
throw new Error("Unexpected: exhausted retry loop without result");
}
export async function structuredObject(shape, options) {
const schema = z.object(shape);
return structured({ ...options, schema });
}
export async function structuredArray(itemSchema, options) {
const schema = z.array(itemSchema);
return structured({ ...options, schema });
}
export async function structuredStream(options) {
const abortController = new AbortController();
const resultPromise = structured({
...options,
signal: abortController.signal,
});
const l0Result = await l0({
stream: options.stream,
fallbackStreams: options.fallbackStreams,
retry: options.retry,
timeout: options.timeout,
signal: abortController.signal,
monitoring: options.monitoring,
onRetry: options.onRetry,
});
return {
stream: l0Result.stream,
result: resultPromise,
abort: () => abortController.abort(),
};
}
//# sourceMappingURL=structured.js.map