UNPKG

@ai2070/l0

Version:

L0: The Missing Reliability Substrate for AI

294 lines 12.5 kB
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