@blueharford/scrypted-spatial-awareness 0.6.24 → 0.6.25

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/out/plugin.zip CHANGED
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blueharford/scrypted-spatial-awareness",
3
- "version": "0.6.24",
3
+ "version": "0.6.25",
4
4
  "description": "Cross-camera object tracking for Scrypted NVR with spatial awareness",
5
5
  "author": "Joshua Seidel <blueharford>",
6
6
  "license": "Apache-2.0",
@@ -324,6 +324,16 @@ export class TrackingEngine {
324
324
  this.lastLlmCallTime = Date.now();
325
325
  }
326
326
 
327
+ /** Check and record LLM call - returns false if rate limited */
328
+ private tryLlmCall(): boolean {
329
+ if (!this.isLlmCallAllowed()) {
330
+ this.console.log('[LLM] Rate limited, skipping LLM call');
331
+ return false;
332
+ }
333
+ this.recordLlmCall();
334
+ return true;
335
+ }
336
+
327
337
  /** Get spatial reasoning result for movement (uses RAG + LLM) with debouncing and fallback */
328
338
  private async getSpatialDescription(
329
339
  tracked: TrackedObject,
@@ -337,8 +347,8 @@ export class TrackingEngine {
337
347
 
338
348
  try {
339
349
  // Check rate limiting - if not allowed, return null to use basic description
340
- if (!this.isLlmCallAllowed()) {
341
- this.console.log('LLM rate-limited, using basic notification');
350
+ if (!this.tryLlmCall()) {
351
+ this.console.log('[Movement] LLM rate-limited, using basic notification');
342
352
  return null;
343
353
  }
344
354
 
@@ -351,9 +361,6 @@ export class TrackingEngine {
351
361
  }
352
362
  }
353
363
 
354
- // Record that we're making an LLM call
355
- this.recordLlmCall();
356
-
357
364
  // Use spatial reasoning engine for rich context-aware description
358
365
  // Apply timeout if fallback is enabled
359
366
  let result: SpatialReasoningResult;
@@ -567,16 +574,23 @@ export class TrackingEngine {
567
574
  spatialResult = await pendingDescription;
568
575
  this.console.log(`[Entry Alert] Prefetch result: "${spatialResult.description.substring(0, 60)}...", usedLlm=${spatialResult.usedLlm}`);
569
576
  } catch (e) {
570
- this.console.warn(`[Entry Alert] Prefetch failed, generating fallback: ${e}`);
577
+ this.console.warn(`[Entry Alert] Prefetch failed, using basic description: ${e}`);
578
+ // Don't make another LLM call - use basic description (no mediaObject = no LLM)
571
579
  spatialResult = await this.spatialReasoning.generateEntryDescription(tracked, sighting.cameraId);
572
580
  }
573
581
  this.pendingDescriptions.delete(globalId);
574
582
  } else {
575
- // Fallback: generate description now (slower path)
576
- this.console.log(`[Entry Alert] No prefetch available, generating now`);
577
- const mediaObject = this.snapshotCache.get(globalId);
578
- spatialResult = await this.spatialReasoning.generateEntryDescription(tracked, sighting.cameraId, mediaObject);
579
- this.console.log(`[Entry Alert] Got description: "${spatialResult.description.substring(0, 60)}...", usedLlm=${spatialResult.usedLlm}`);
583
+ // No prefetch available - only call LLM if rate limit allows
584
+ if (this.tryLlmCall()) {
585
+ this.console.log(`[Entry Alert] No prefetch, generating with LLM`);
586
+ const mediaObject = this.snapshotCache.get(globalId);
587
+ spatialResult = await this.spatialReasoning.generateEntryDescription(tracked, sighting.cameraId, mediaObject);
588
+ this.console.log(`[Entry Alert] Got description: "${spatialResult.description.substring(0, 60)}...", usedLlm=${spatialResult.usedLlm}`);
589
+ } else {
590
+ // Rate limited - use basic description (no LLM)
591
+ this.console.log(`[Entry Alert] Rate limited, using basic description`);
592
+ spatialResult = await this.spatialReasoning.generateEntryDescription(tracked, sighting.cameraId);
593
+ }
580
594
  }
581
595
 
582
596
  // Always use movement alert type for smart notifications with LLM descriptions
@@ -611,9 +625,9 @@ export class TrackingEngine {
611
625
  this.snapshotCache.set(globalId, mediaObject);
612
626
  this.console.log(`[Snapshot] Cached snapshot for ${globalId.slice(0, 8)} from ${cameraId}`);
613
627
 
614
- // Start LLM analysis immediately in parallel (don't await)
628
+ // Start LLM analysis immediately in parallel (don't await) - but respect rate limits
615
629
  const tracked = this.state.getObject(globalId);
616
- if (tracked && this.config.useLlmDescriptions) {
630
+ if (tracked && this.config.useLlmDescriptions && this.tryLlmCall()) {
617
631
  this.console.log(`[LLM Prefetch] Starting ${eventType} analysis for ${globalId.slice(0, 8)}`);
618
632
  const descriptionPromise = eventType === 'exit'
619
633
  ? this.spatialReasoning.generateExitDescription(tracked, cameraId, mediaObject)
@@ -627,6 +641,8 @@ export class TrackingEngine {
627
641
  }).catch(e => {
628
642
  this.console.warn(`[LLM Prefetch] Failed for ${globalId.slice(0, 8)}: ${e}`);
629
643
  });
644
+ } else if (tracked && this.config.useLlmDescriptions) {
645
+ this.console.log(`[LLM Prefetch] Skipped for ${globalId.slice(0, 8)} - rate limited`);
630
646
  }
631
647
  }
632
648
  }
@@ -706,16 +722,23 @@ export class TrackingEngine {
706
722
  spatialResult = await pendingDescription;
707
723
  this.console.log(`[Exit Alert] Prefetch result: "${spatialResult.description.substring(0, 60)}...", usedLlm=${spatialResult.usedLlm}`);
708
724
  } catch (e) {
709
- this.console.warn(`[Exit Alert] Prefetch failed, generating fallback: ${e}`);
725
+ this.console.warn(`[Exit Alert] Prefetch failed, using basic description: ${e}`);
726
+ // Don't make another LLM call - use basic description
710
727
  spatialResult = await this.spatialReasoning.generateExitDescription(current, sighting.cameraId);
711
728
  }
712
729
  this.pendingDescriptions.delete(tracked.globalId);
713
730
  } else {
714
- // Fallback: generate description now (slower path)
715
- this.console.log(`[Exit Alert] No prefetch available, generating now`);
716
- const mediaObject = this.snapshotCache.get(tracked.globalId);
717
- spatialResult = await this.spatialReasoning.generateExitDescription(current, sighting.cameraId, mediaObject);
718
- this.console.log(`[Exit Alert] Got description: "${spatialResult.description.substring(0, 60)}...", usedLlm=${spatialResult.usedLlm}`);
731
+ // No prefetch available - only call LLM if rate limit allows
732
+ if (this.tryLlmCall()) {
733
+ this.console.log(`[Exit Alert] No prefetch, generating with LLM`);
734
+ const mediaObject = this.snapshotCache.get(tracked.globalId);
735
+ spatialResult = await this.spatialReasoning.generateExitDescription(current, sighting.cameraId, mediaObject);
736
+ this.console.log(`[Exit Alert] Got description: "${spatialResult.description.substring(0, 60)}...", usedLlm=${spatialResult.usedLlm}`);
737
+ } else {
738
+ // Rate limited - use basic description (no LLM)
739
+ this.console.log(`[Exit Alert] Rate limited, using basic description`);
740
+ spatialResult = await this.spatialReasoning.generateExitDescription(current, sighting.cameraId);
741
+ }
719
742
  }
720
743
 
721
744
  // Use movement alert for exit too - smart notifications with LLM descriptions
package/src/main.ts CHANGED
@@ -130,8 +130,8 @@ export class SpatialAwarenessPlugin extends ScryptedDeviceBase
130
130
  llmDebounceInterval: {
131
131
  title: 'LLM Rate Limit (seconds)',
132
132
  type: 'number',
133
- defaultValue: 10,
134
- description: 'Minimum time between LLM calls to prevent API overload (0 = no limit)',
133
+ defaultValue: 30,
134
+ description: 'Minimum time between LLM calls to prevent API rate limiting. Increase if you get rate limit errors. (0 = no limit)',
135
135
  group: 'AI & Spatial Reasoning',
136
136
  },
137
137
  llmFallbackEnabled: {