@heripo/document-processor 0.1.32 → 0.1.34

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -147,6 +147,8 @@ function detectProvider(model) {
147
147
  if (providerId.includes("google")) return "google";
148
148
  if (providerId.includes("anthropic")) return "anthropic";
149
149
  if (providerId.includes("together")) return "togetherai";
150
+ if (providerId.includes("ollama")) return "ollama";
151
+ if (providerId.includes("lmstudio")) return "lmstudio";
150
152
  return "unknown";
151
153
  }
152
154
  var LLMCaller = class {
@@ -234,14 +236,18 @@ var LLMCaller = class {
234
236
  *
235
237
  * Strategy per provider:
236
238
  * - OpenAI / Anthropic / Google Gemini: `Output.object()` with schema retry
237
- * - Together AI / unknown: forced tool call pattern
239
+ * - Together AI / Ollama / unknown: forced tool call pattern
240
+ *
241
+ * Ollama uses tool-call because MLX backends ignore the `format` grammar
242
+ * that `Output.object()` relies on, and gguf's grammar is unstable once
243
+ * thinking is disabled. Applies to both text and vision paths.
238
244
  *
239
245
  * Retries up to MAX_STRUCTURED_OUTPUT_RETRIES times on NoObjectGeneratedError
240
246
  * (schema mismatch), re-throwing the last error if all attempts fail.
241
247
  */
242
248
  static async generateStructuredOutput(model, schema, promptParams) {
243
249
  const providerType = detectProvider(model);
244
- if (providerType === "togetherai" || providerType === "unknown") {
250
+ if (providerType === "togetherai" || providerType === "ollama" || providerType === "unknown") {
245
251
  return this.generateViaToolCall(model, schema, promptParams);
246
252
  }
247
253
  let lastError;
@@ -346,6 +352,29 @@ var LLMCaller = class {
346
352
  function formatTokens(usage) {
347
353
  return `${usage.inputTokens} input, ${usage.outputTokens} output, ${usage.totalTokens} total`;
348
354
  }
355
+ function emptyUsage() {
356
+ return { inputTokens: 0, outputTokens: 0, totalTokens: 0 };
357
+ }
358
+ function addInto(target, src) {
359
+ target.inputTokens += src.inputTokens;
360
+ target.outputTokens += src.outputTokens;
361
+ target.totalTokens += src.totalTokens;
362
+ }
363
+ function sumUsage(a, b) {
364
+ return {
365
+ inputTokens: (a?.inputTokens ?? 0) + (b?.inputTokens ?? 0),
366
+ outputTokens: (a?.outputTokens ?? 0) + (b?.outputTokens ?? 0),
367
+ totalTokens: (a?.totalTokens ?? 0) + (b?.totalTokens ?? 0)
368
+ };
369
+ }
370
+ function accumulateModel(bucket, modelName, usage) {
371
+ let entry = bucket.get(modelName);
372
+ if (!entry) {
373
+ entry = emptyUsage();
374
+ bucket.set(modelName, entry);
375
+ }
376
+ addInto(entry, usage);
377
+ }
349
378
  var LLMTokenUsageAggregator = class {
350
379
  usage = {};
351
380
  /**
@@ -358,21 +387,15 @@ var LLMTokenUsageAggregator = class {
358
387
  this.usage[usage.component] = {
359
388
  component: usage.component,
360
389
  phases: {},
361
- total: {
362
- inputTokens: 0,
363
- outputTokens: 0,
364
- totalTokens: 0
365
- }
390
+ total: emptyUsage()
366
391
  };
367
392
  }
368
393
  const component = this.usage[usage.component];
369
394
  if (!component.phases[usage.phase]) {
370
395
  component.phases[usage.phase] = {
371
- total: {
372
- inputTokens: 0,
373
- outputTokens: 0,
374
- totalTokens: 0
375
- },
396
+ primary: /* @__PURE__ */ new Map(),
397
+ fallback: /* @__PURE__ */ new Map(),
398
+ total: emptyUsage(),
376
399
  metadata: []
377
400
  };
378
401
  }
@@ -381,41 +404,17 @@ var LLMTokenUsageAggregator = class {
381
404
  phase.metadata.push(usage.metadata);
382
405
  }
383
406
  if (usage.model === "primary") {
384
- if (!phase.primary) {
385
- phase.primary = {
386
- modelName: usage.modelName,
387
- inputTokens: 0,
388
- outputTokens: 0,
389
- totalTokens: 0
390
- };
391
- }
392
- phase.primary.inputTokens += usage.inputTokens;
393
- phase.primary.outputTokens += usage.outputTokens;
394
- phase.primary.totalTokens += usage.totalTokens;
407
+ accumulateModel(phase.primary, usage.modelName, usage);
395
408
  } else if (usage.model === "fallback") {
396
- if (!phase.fallback) {
397
- phase.fallback = {
398
- modelName: usage.modelName,
399
- inputTokens: 0,
400
- outputTokens: 0,
401
- totalTokens: 0
402
- };
403
- }
404
- phase.fallback.inputTokens += usage.inputTokens;
405
- phase.fallback.outputTokens += usage.outputTokens;
406
- phase.fallback.totalTokens += usage.totalTokens;
409
+ accumulateModel(phase.fallback, usage.modelName, usage);
407
410
  }
408
- phase.total.inputTokens += usage.inputTokens;
409
- phase.total.outputTokens += usage.outputTokens;
410
- phase.total.totalTokens += usage.totalTokens;
411
- component.total.inputTokens += usage.inputTokens;
412
- component.total.outputTokens += usage.outputTokens;
413
- component.total.totalTokens += usage.totalTokens;
411
+ addInto(phase.total, usage);
412
+ addInto(component.total, usage);
414
413
  }
415
414
  /**
416
415
  * Get aggregated usage grouped by component
417
416
  *
418
- * @returns Array of component aggregates with phase breakdown
417
+ * @returns Array of component aggregates with per-model phase breakdown
419
418
  */
420
419
  getByComponent() {
421
420
  return Object.values(this.usage);
@@ -424,8 +423,16 @@ var LLMTokenUsageAggregator = class {
424
423
  * Get token usage report in structured JSON format
425
424
  *
426
425
  * Converts internal usage data to external TokenUsageReport format suitable
427
- * for serialization and reporting. The report includes component breakdown,
428
- * phase-level details, and both primary and fallback model usage.
426
+ * for serialization and reporting.
427
+ *
428
+ * Each (phase, model) pair becomes its own PhaseUsageReport entry. A phase
429
+ * that used a single primary (and/or a single fallback) model yields exactly
430
+ * one entry — identical to the legacy shape — while a phase that mixed models
431
+ * yields one entry per model, all sharing the same `phase` name. Every
432
+ * (phase, tier, modelName) combination appears in exactly one entry, so
433
+ * consumers that key by `${component}|${phase}|${tier}|${modelName}`
434
+ * (heripo-web's token-usage emitter / ledger recorder) attribute each model's
435
+ * tokens — and cost — correctly without double counting.
429
436
  *
430
437
  * @returns Structured token usage report with components and total
431
438
  */
@@ -434,34 +441,41 @@ var LLMTokenUsageAggregator = class {
434
441
  for (const component of Object.values(this.usage)) {
435
442
  const phases = [];
436
443
  for (const [phaseName, phaseData] of Object.entries(component.phases)) {
437
- const phaseReport = {
438
- phase: phaseName,
439
- total: {
440
- inputTokens: phaseData.total.inputTokens,
441
- outputTokens: phaseData.total.outputTokens,
442
- totalTokens: phaseData.total.totalTokens
443
- }
444
- };
445
- if (phaseData.metadata.length > 0) {
446
- phaseReport.metadata = [...phaseData.metadata];
447
- }
448
- if (phaseData.primary) {
449
- phaseReport.primary = {
450
- modelName: phaseData.primary.modelName,
451
- inputTokens: phaseData.primary.inputTokens,
452
- outputTokens: phaseData.primary.outputTokens,
453
- totalTokens: phaseData.primary.totalTokens
444
+ const primaryEntries = [...phaseData.primary.entries()];
445
+ const fallbackEntries = [...phaseData.fallback.entries()];
446
+ const entryCount = Math.max(
447
+ primaryEntries.length,
448
+ fallbackEntries.length
449
+ );
450
+ if (entryCount === 0) {
451
+ const phaseReport = {
452
+ phase: phaseName,
453
+ total: { ...phaseData.total }
454
454
  };
455
+ if (phaseData.metadata.length > 0) {
456
+ phaseReport.metadata = [...phaseData.metadata];
457
+ }
458
+ phases.push(phaseReport);
459
+ continue;
455
460
  }
456
- if (phaseData.fallback) {
457
- phaseReport.fallback = {
458
- modelName: phaseData.fallback.modelName,
459
- inputTokens: phaseData.fallback.inputTokens,
460
- outputTokens: phaseData.fallback.outputTokens,
461
- totalTokens: phaseData.fallback.totalTokens
461
+ for (let i = 0; i < entryCount; i++) {
462
+ const primary = primaryEntries[i];
463
+ const fallback = fallbackEntries[i];
464
+ const phaseReport = {
465
+ phase: phaseName,
466
+ total: sumUsage(primary?.[1], fallback?.[1])
462
467
  };
468
+ if (primary) {
469
+ phaseReport.primary = { modelName: primary[0], ...primary[1] };
470
+ }
471
+ if (fallback) {
472
+ phaseReport.fallback = { modelName: fallback[0], ...fallback[1] };
473
+ }
474
+ if (i === 0 && phaseData.metadata.length > 0) {
475
+ phaseReport.metadata = [...phaseData.metadata];
476
+ }
477
+ phases.push(phaseReport);
463
478
  }
464
- phases.push(phaseReport);
465
479
  }
466
480
  components.push({
467
481
  component: component.component,
@@ -506,8 +520,8 @@ var LLMTokenUsageAggregator = class {
506
520
  /**
507
521
  * Log comprehensive token usage summary
508
522
  *
509
- * Outputs usage grouped by component, with phase and model breakdown.
510
- * Shows primary and fallback token usage separately for each phase.
523
+ * Outputs usage grouped by component, with phase and per-model breakdown.
524
+ * Shows each primary and fallback model on its own line for each phase.
511
525
  * Call this once at the end of document processing.
512
526
  *
513
527
  * @param logger - Logger instance for output
@@ -523,31 +537,23 @@ var LLMTokenUsageAggregator = class {
523
537
  let grandInputTokens = 0;
524
538
  let grandOutputTokens = 0;
525
539
  let grandTotalTokens = 0;
526
- let grandPrimaryInputTokens = 0;
527
- let grandPrimaryOutputTokens = 0;
528
- let grandPrimaryTotalTokens = 0;
529
- let grandFallbackInputTokens = 0;
530
- let grandFallbackOutputTokens = 0;
531
- let grandFallbackTotalTokens = 0;
540
+ const grandPrimary = emptyUsage();
541
+ const grandFallback = emptyUsage();
532
542
  for (const component of components) {
533
543
  logger.info(`${component.component}:`);
534
- for (const [phase, phaseData] of Object.entries(component.phases)) {
535
- logger.info(` - ${phase}:`);
536
- if (phaseData.primary) {
544
+ for (const [phaseName, phaseData] of Object.entries(component.phases)) {
545
+ logger.info(` - ${phaseName}:`);
546
+ for (const [modelName, modelUsage] of phaseData.primary) {
537
547
  logger.info(
538
- ` primary (${phaseData.primary.modelName}): ${formatTokens(phaseData.primary)}`
548
+ ` primary (${modelName}): ${formatTokens(modelUsage)}`
539
549
  );
540
- grandPrimaryInputTokens += phaseData.primary.inputTokens;
541
- grandPrimaryOutputTokens += phaseData.primary.outputTokens;
542
- grandPrimaryTotalTokens += phaseData.primary.totalTokens;
550
+ addInto(grandPrimary, modelUsage);
543
551
  }
544
- if (phaseData.fallback) {
552
+ for (const [modelName, modelUsage] of phaseData.fallback) {
545
553
  logger.info(
546
- ` fallback (${phaseData.fallback.modelName}): ${formatTokens(phaseData.fallback)}`
554
+ ` fallback (${modelName}): ${formatTokens(modelUsage)}`
547
555
  );
548
- grandFallbackInputTokens += phaseData.fallback.inputTokens;
549
- grandFallbackOutputTokens += phaseData.fallback.outputTokens;
550
- grandFallbackTotalTokens += phaseData.fallback.totalTokens;
556
+ addInto(grandFallback, modelUsage);
551
557
  }
552
558
  logger.info(` subtotal: ${formatTokens(phaseData.total)}`);
553
559
  }
@@ -560,23 +566,11 @@ var LLMTokenUsageAggregator = class {
560
566
  grandTotalTokens += component.total.totalTokens;
561
567
  }
562
568
  logger.info("--- Summary ---");
563
- if (grandPrimaryTotalTokens > 0) {
564
- logger.info(
565
- `Primary total: ${formatTokens({
566
- inputTokens: grandPrimaryInputTokens,
567
- outputTokens: grandPrimaryOutputTokens,
568
- totalTokens: grandPrimaryTotalTokens
569
- })}`
570
- );
569
+ if (grandPrimary.totalTokens > 0) {
570
+ logger.info(`Primary total: ${formatTokens(grandPrimary)}`);
571
571
  }
572
- if (grandFallbackTotalTokens > 0) {
573
- logger.info(
574
- `Fallback total: ${formatTokens({
575
- inputTokens: grandFallbackInputTokens,
576
- outputTokens: grandFallbackOutputTokens,
577
- totalTokens: grandFallbackTotalTokens
578
- })}`
579
- );
572
+ if (grandFallback.totalTokens > 0) {
573
+ logger.info(`Fallback total: ${formatTokens(grandFallback)}`);
580
574
  }
581
575
  logger.info(
582
576
  `Grand total: ${formatTokens({
@@ -3213,11 +3207,11 @@ var PageRangeParser = class extends VisionLLMComponent {
3213
3207
  const pages = this.extractPages(doclingDoc);
3214
3208
  if (pages.length === 0) {
3215
3209
  this.log("warn", "No pages found");
3216
- const emptyUsage = this.createEmptyUsage("sampling");
3217
- this.trackUsage(emptyUsage);
3210
+ const emptyUsage2 = this.createEmptyUsage("sampling");
3211
+ this.trackUsage(emptyUsage2);
3218
3212
  return {
3219
3213
  pageRangeMap: {},
3220
- usage: [emptyUsage]
3214
+ usage: [emptyUsage2]
3221
3215
  };
3222
3216
  }
3223
3217
  const sizeGroups = this.analyzeSizes(pages);