UNPKG

@ai2070/l0

Version:

L0: The Missing Reliability Substrate for AI

440 lines (439 loc) 13.4 kB
import { z } from "zod"; import { l0 } from "./runtime/l0"; import { autoCorrectJSON, isValidJSON, extractJSON } from "./utils/autoCorrect"; 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 appliedCorrections = []; let wasAutoCorrected = false; const errors = []; let validationStartTime = 0; let validationEndTime = 0; const abortController = new AbortController(); let parsedData = null; const tryParseAndValidate = (content) => { validationAttempts++; validationStartTime = Date.now(); let processed = content; appliedCorrections = []; if (autoCorrect) { const correctionResult = autoCorrectJSON(processed, { structural: true, stripFormatting: true, schemaBased: false, strict: strictMode }); if (correctionResult.corrections.length > 0) { wasAutoCorrected = true; processed = correctionResult.corrected; appliedCorrections = correctionResult.corrections; autoCorrections++; correctionTypes.push(...correctionResult.corrections); if (onAutoCorrect) { const correctionInfo = { original: content, corrected: processed, corrections: correctionResult.corrections, success: correctionResult.success }; onAutoCorrect(correctionInfo); } } } let parsed; try { parsed = JSON.parse(processed); } catch (parseError) { const err = parseError instanceof Error ? parseError : new Error(String(parseError)); const extracted = extractJSON(processed); if (extracted !== processed) { try { parsed = JSON.parse(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) { try { parsed = JSON.parse(rescueResult.corrected); wasAutoCorrected = true; appliedCorrections.push(...rescueResult.corrections); autoCorrections++; correctionTypes.push(...rescueResult.corrections); } catch (innerErr) { const innerError = innerErr instanceof Error ? innerErr : new Error(String(innerErr)); return { success: false, error: `Invalid JSON after auto-correction: ${innerError.message}` }; } } else { return { success: false, error: `Invalid JSON after auto-correction: ${err.message}` }; } } } else { const rawExtracted = extractJSON(content); if (rawExtracted !== content) { const rescueResult = autoCorrectJSON(rawExtracted, { structural: true, stripFormatting: true }); if (rescueResult.success) { try { parsed = JSON.parse(rescueResult.corrected); wasAutoCorrected = true; appliedCorrections.push( "extract_json", ...rescueResult.corrections ); autoCorrections++; correctionTypes.push("extract_json", ...rescueResult.corrections); } catch (innerErr) { const innerError = innerErr instanceof Error ? innerErr : new Error(String(innerErr)); return { success: false, error: `Invalid JSON: ${innerError.message}` }; } } else { return { success: false, error: `Invalid JSON: ${err.message}` }; } } else { return { success: false, error: `Invalid JSON: ${err.message}` }; } } } const validationResult = schema.safeParse(parsed); if (!validationResult.success) { validationFailures++; validationErrors.push(validationResult.error); if (onValidationError) { onValidationError(validationResult.error, validationAttempts); } return { success: false, error: `Schema validation failed: ${validationResult.error.errors[0]?.message}` }; } validationEndTime = Date.now(); parsedData = validationResult.data; return { success: true, data: validationResult.data }; }; const l0Options = { stream: streamFactory, fallbackStreams, retry: { attempts: retry.attempts ?? 2, backoff: retry.backoff ?? "fixed-jitter", baseDelay: retry.baseDelay ?? 1e3, maxDelay: retry.maxDelay ?? 5e3, retryOn: [...retry.retryOn || [], "guardrail_violation", "incomplete"], errorTypeDelays: retry.errorTypeDelays }, timeout, signal: signal || abortController.signal, // Default to disabled for structured output since short valid JSON // (like "[]" or "{}") should not be rejected detectZeroTokens: detectZeroTokens ?? false, monitoring: { enabled: monitoring?.enabled ?? false, sampleRate: monitoring?.sampleRate ?? 1, metadata: { ...monitoring?.metadata || {}, structured: true, schemaName: schema.description || "unknown" } }, guardrails: [ // JSON + Schema validation guardrail (runs on completion) { name: "json-schema-validation", check: (context) => { if (!context.completed) { return []; } if (!isValidJSON(context.content)) { return [ { rule: "json-schema-validation", message: "Output is not valid JSON", severity: "error", recoverable: true } ]; } const result2 = tryParseAndValidate(context.content); if (!result2.success) { return [ { rule: "json-schema-validation", message: result2.error || "Validation failed", severity: "error", recoverable: true } ]; } return []; } } ], onRetry: (attempt, reason) => { if (onRetry) { onRetry(attempt, reason); } } }; 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"); } if (parsedData === null) { const finalResult = tryParseAndValidate(rawOutput); if (!finalResult.success) { throw new Error(`Structured output failed: ${finalResult.error}`); } } 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: parsedData, raw: rawOutput, corrected: wasAutoCorrected, corrections: appliedCorrections, state: structuredState, telemetry: structuredTelemetry, errors, abort: () => abortController.abort() }; } async function structuredObject(shape, options) { const schema = z.object(shape); return structured({ ...options, schema }); } async function structuredArray(itemSchema, options) { const schema = z.array(itemSchema); return structured({ ...options, schema }); } async function structuredStream(options) { const { schema, stream: streamFactory, fallbackStreams = [], retry = {}, autoCorrect = true, strictMode = false, detectZeroTokens, timeout, signal, monitoring, onValidationError, onAutoCorrect, onRetry } = options; const abortController = new AbortController(); const combinedSignal = signal || abortController.signal; let rawOutput = ""; let validationAttempts = 0; let validationFailures = 0; let autoCorrections = 0; const correctionTypes = []; const validationErrors = []; let appliedCorrections = []; let wasAutoCorrected = false; const errors = []; const l0Result = await l0({ stream: streamFactory, fallbackStreams, retry: { attempts: retry.attempts ?? 2, backoff: retry.backoff ?? "fixed-jitter", baseDelay: retry.baseDelay ?? 1e3, maxDelay: retry.maxDelay ?? 5e3, retryOn: [...retry.retryOn || [], "guardrail_violation", "incomplete"], errorTypeDelays: retry.errorTypeDelays }, detectZeroTokens: detectZeroTokens ?? false, timeout, signal: combinedSignal, monitoring: { enabled: monitoring?.enabled ?? false, sampleRate: monitoring?.sampleRate ?? 1, metadata: { ...monitoring?.metadata || {}, structured: true, schemaName: schema.description || "unknown" } }, onRetry }); let streamResolve; const streamDone = new Promise((resolve) => { streamResolve = resolve; }); const teedStream = async function* () { for await (const event of l0Result.stream) { if (event.type === "token" && event.value) { rawOutput += event.value; } else if (event.type === "error") { errors.push(event.error || new Error("Unknown error")); } yield event; } streamResolve(); }; const sharedStream = teedStream(); const resultPromise = (async () => { await streamDone; if (!rawOutput || rawOutput.trim().length === 0) { throw new Error("No output received from model"); } let processed = rawOutput; appliedCorrections = []; if (autoCorrect) { const correctionResult = autoCorrectJSON(processed, { structural: true, stripFormatting: true, schemaBased: false, strict: strictMode }); if (correctionResult.corrections.length > 0) { wasAutoCorrected = true; processed = correctionResult.corrected; appliedCorrections = correctionResult.corrections; autoCorrections++; correctionTypes.push(...correctionResult.corrections); if (onAutoCorrect) { onAutoCorrect({ original: rawOutput, corrected: processed, corrections: correctionResult.corrections, success: correctionResult.success }); } } } let parsedData; try { parsedData = JSON.parse(processed); } catch (parseError) { const extracted = extractJSON(processed); if (extracted !== processed) { try { parsedData = JSON.parse(extracted); wasAutoCorrected = true; appliedCorrections.push("extract_json"); } catch { throw new Error(`Invalid JSON: ${parseError}`); } } else { throw new Error(`Invalid JSON: ${parseError}`); } } validationAttempts++; const validationResult = schema.safeParse(parsedData); if (!validationResult.success) { validationFailures++; validationErrors.push(validationResult.error); if (onValidationError) { onValidationError(validationResult.error, validationAttempts); } throw new Error( `Schema validation failed: ${validationResult.error.errors[0]?.message}` ); } const structuredState = { ...l0Result.state, validationFailures, autoCorrections, validationErrors }; return { data: validationResult.data, raw: rawOutput, corrected: wasAutoCorrected, corrections: appliedCorrections, state: structuredState, telemetry: l0Result.telemetry ? { ...l0Result.telemetry, structured: { schemaName: schema.description || "unknown", validationAttempts, validationFailures, autoCorrections, correctionTypes: Array.from(new Set(correctionTypes)), validationSuccess: true } } : void 0, errors, abort: () => abortController.abort() }; })(); return { stream: sharedStream, result: resultPromise, abort: () => abortController.abort() }; } export { structured, structuredArray, structuredObject, structuredStream }; //# sourceMappingURL=structured.js.map