@ai2070/l0
Version:
L0: The Missing Reliability Substrate for AI
288 lines • 10.4 kB
JavaScript
import { l0 } from "./runtime/l0";
import { chunkDocument, estimateTokenCount, mergeChunks, } from "./utils/chunking";
import { RETRY_DEFAULTS } from "./types/retry";
const DEFAULT_OPTIONS = {
size: 2000,
overlap: 200,
strategy: "token",
estimateTokens: estimateTokenCount,
preserveParagraphs: true,
preserveSentences: false,
metadata: {},
};
export class DocumentWindowImpl {
document;
options;
chunks;
_currentIndex = 0;
constructor(document, options = {}) {
this.document = document;
this.options = { ...DEFAULT_OPTIONS, ...options };
this.chunks = chunkDocument(document, this.options);
if (this.chunks.length === 0) {
throw new Error("Document resulted in zero chunks");
}
}
get totalChunks() {
return this.chunks.length;
}
get currentIndex() {
return this._currentIndex;
}
get(index) {
if (index < 0 || index >= this.chunks.length) {
return null;
}
return this.chunks[index] ?? null;
}
current() {
return this.get(this._currentIndex);
}
next() {
if (this._currentIndex < this.chunks.length - 1) {
this._currentIndex++;
return this.current();
}
return null;
}
prev() {
if (this._currentIndex > 0) {
this._currentIndex--;
return this.current();
}
return null;
}
jump(index) {
if (index < 0 || index >= this.chunks.length) {
return null;
}
this._currentIndex = index;
return this.current();
}
reset() {
this._currentIndex = 0;
return this.current();
}
getAllChunks() {
return [...this.chunks];
}
getRange(start, end) {
const validStart = Math.max(0, start);
const validEnd = Math.min(this.chunks.length, end);
return this.chunks.slice(validStart, validEnd);
}
hasNext() {
return this._currentIndex < this.chunks.length - 1;
}
hasPrev() {
return this._currentIndex > 0;
}
async processAll(processFn) {
return this.processParallel(processFn);
}
async processSequential(processFn) {
const results = [];
for (const chunk of this.chunks) {
const startTime = Date.now();
try {
const options = processFn(chunk);
const result = await l0(options);
for await (const _event of result.stream) {
}
results.push({
chunk,
result,
status: "success",
duration: Date.now() - startTime,
});
}
catch (error) {
results.push({
chunk,
result: undefined,
status: "error",
error: error instanceof Error ? error : new Error(String(error)),
duration: Date.now() - startTime,
});
}
}
return results;
}
async processParallel(processFn, options = {}) {
const { concurrency = 5 } = options;
const results = new Array(this.chunks.length);
const queue = [...this.chunks];
let activeCount = 0;
let index = 0;
return new Promise((resolve, _reject) => {
const processNext = () => {
while (activeCount < concurrency && queue.length > 0) {
const chunk = queue.shift();
const chunkIndex = index++;
activeCount++;
const startTime = Date.now();
(async () => {
try {
const l0Options = processFn(chunk);
const result = await l0(l0Options);
for await (const _event of result.stream) {
}
results[chunkIndex] = {
chunk,
result,
status: "success",
duration: Date.now() - startTime,
};
}
catch (error) {
results[chunkIndex] = {
chunk,
result: undefined,
status: "error",
error: error instanceof Error ? error : new Error(String(error)),
duration: Date.now() - startTime,
};
}
finally {
activeCount--;
if (queue.length > 0) {
processNext();
}
else if (activeCount === 0) {
resolve(results);
}
}
})();
}
};
processNext();
});
}
getStats() {
const totalChars = this.document.length;
const totalTokens = this.options.estimateTokens(this.document);
const avgChunkSize = this.chunks.reduce((sum, c) => sum + c.charCount, 0) / this.chunks.length;
const avgChunkTokens = this.chunks.reduce((sum, c) => sum + c.tokenCount, 0) /
this.chunks.length;
return {
totalChunks: this.chunks.length,
totalChars,
totalTokens,
avgChunkSize: Math.round(avgChunkSize),
avgChunkTokens: Math.round(avgChunkTokens),
overlapSize: this.options.overlap,
strategy: this.options.strategy,
};
}
getContext(index, options = {}) {
const { before = 0, after = 0 } = options;
const start = Math.max(0, index - before);
const end = Math.min(this.chunks.length, index + after + 1);
const contextChunks = this.chunks.slice(start, end);
return mergeChunks(contextChunks, false);
}
findChunks(searchText, caseSensitive = false) {
const search = caseSensitive ? searchText : searchText.toLowerCase();
return this.chunks.filter((chunk) => {
const content = caseSensitive
? chunk.content
: chunk.content.toLowerCase();
return content.includes(search);
});
}
getChunksInRange(startPos, endPos) {
return this.chunks.filter((chunk) => (chunk.startPos >= startPos && chunk.startPos < endPos) ||
(chunk.endPos > startPos && chunk.endPos <= endPos) ||
(chunk.startPos <= startPos && chunk.endPos >= endPos));
}
}
export function createWindow(document, options) {
return new DocumentWindowImpl(document, options);
}
export async function processWithWindow(document, processFn, options) {
const window = createWindow(document, options);
return window.processAll(processFn);
}
export async function l0WithWindow(options) {
const { window, chunkIndex = 0, contextRestoration, ...l0Options } = options;
if (!window) {
throw new Error("Window is required");
}
const chunk = window.get(chunkIndex);
if (!chunk) {
throw new Error(`Invalid chunk index: ${chunkIndex}`);
}
const { enabled = true, strategy = "adjacent", maxAttempts = RETRY_DEFAULTS.attempts, onRestore, } = contextRestoration || {};
let currentChunkIndex = chunkIndex;
let attempts = 0;
while (attempts <= maxAttempts) {
try {
const result = await l0(l0Options);
if (result.state.driftDetected && enabled && attempts < maxAttempts) {
let nextChunkIndex = null;
switch (strategy) {
case "adjacent":
if (window.hasNext()) {
nextChunkIndex = currentChunkIndex + 1;
}
else if (currentChunkIndex > 0) {
nextChunkIndex = currentChunkIndex - 1;
}
break;
case "overlap":
if (window.hasNext()) {
nextChunkIndex = currentChunkIndex + 1;
}
break;
case "full":
if (window.hasNext()) {
nextChunkIndex = currentChunkIndex + 1;
}
else if (currentChunkIndex > 0) {
nextChunkIndex = currentChunkIndex - 1;
}
break;
}
if (nextChunkIndex !== null) {
currentChunkIndex = nextChunkIndex;
attempts++;
if (onRestore) {
onRestore(chunkIndex, nextChunkIndex);
}
continue;
}
}
return result;
}
catch (error) {
if (attempts >= maxAttempts) {
throw error;
}
attempts++;
}
}
throw new Error("Context restoration failed after max attempts");
}
export function mergeResults(results, separator = "\n\n") {
return results
.filter((r) => r.status === "success" && r.result?.state?.content)
.map((r) => r.result.state.content)
.join(separator);
}
export function getProcessingStats(results) {
const total = results.length;
const successful = results.filter((r) => r.status === "success").length;
const failed = results.filter((r) => r.status === "error").length;
const successRate = total > 0 ? (successful / total) * 100 : 0;
const totalDuration = results.reduce((sum, r) => sum + r.duration, 0);
const avgDuration = total > 0 ? totalDuration / total : 0;
return {
total,
successful,
failed,
successRate,
avgDuration: Math.round(avgDuration),
totalDuration,
};
}
//# sourceMappingURL=window.js.map