omnius 1.0.443 → 1.0.445

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.
@@ -14,8 +14,8 @@ Architecture (EGG voice/whisper lineage):
14
14
  (USB 2886:0018), the XMOS DSP's SPEECHDETECTED register gates utterances,
15
15
  on-chip echo cancellation is enabled, and the DOA angle is attached to
16
16
  transcripts. Falls back to adaptive energy VAD otherwise.
17
- - Dual-model consensus: a second whisper model transcribes finalized
18
- utterances in parallel; transcripts are only emitted when both agree.
17
+ - Optional dual-model consensus: a second whisper model can validate
18
+ finalized utterances when explicitly enabled.
19
19
 
20
20
  Protocol:
21
21
  stdin — raw PCM16 audio (16kHz, mono, 16-bit signed LE)
@@ -58,6 +58,23 @@ SAMPLE_WIDTH = 2 # 16-bit
58
58
  BLOCK_SECONDS = 0.2
59
59
  BLOCK_SAMPLES = int(SAMPLE_RATE * BLOCK_SECONDS)
60
60
 
61
+
62
+ def env_float(name: str, default: float) -> float:
63
+ raw = os.environ.get(name, "").strip()
64
+ if not raw:
65
+ return default
66
+ try:
67
+ return float(raw)
68
+ except ValueError:
69
+ return default
70
+
71
+
72
+ def env_bool(name: str, default: bool = False) -> bool:
73
+ raw = os.environ.get(name, "").strip().lower()
74
+ if not raw:
75
+ return default
76
+ return raw in ("1", "true", "yes", "on", "enabled")
77
+
61
78
  # ---------------------------------------------------------------------------
62
79
  # Output helpers (JSON lines to stdout)
63
80
  # ---------------------------------------------------------------------------
@@ -450,7 +467,7 @@ def main():
450
467
  parser.add_argument("--chunk-seconds", type=float, default=3, help=argparse.SUPPRESS)
451
468
  parser.add_argument("--window-seconds", type=float, default=10, help=argparse.SUPPRESS)
452
469
  parser.add_argument("--language", default=None, help="Language code (e.g. en, es, fr). Auto-detect if omitted.")
453
- parser.add_argument("--consensus-model", default="tiny",
470
+ parser.add_argument("--consensus-model", default="off",
454
471
  help="Second whisper model run in parallel on finalized utterances; transcripts are only emitted when both models agree. 'off' disables.")
455
472
  parser.add_argument("--consensus-threshold", type=float, default=0.55,
456
473
  help="Token similarity (0-1) required between the two models' transcripts.")
@@ -460,6 +477,18 @@ def main():
460
477
  help="Audio kept from before speech onset.")
461
478
  parser.add_argument("--max-utterance-seconds", type=float, default=22,
462
479
  help="Force-finalize utterances longer than this.")
480
+ parser.add_argument("--partials", action="store_true", default=env_bool("OMNIUS_ASR_PARTIALS", False),
481
+ help="Emit interim Whisper transcripts during long utterances. Disabled by default to avoid silence/noise hallucinations.")
482
+ parser.add_argument("--min-speech-ms", type=float, default=env_float("OMNIUS_ASR_MIN_SPEECH_MS", 450.0),
483
+ help="Minimum VAD-positive speech duration required before an utterance is sent to Whisper.")
484
+ parser.add_argument("--start-rms", type=float, default=env_float("OMNIUS_ASR_START_RMS", 0.0025),
485
+ help="Minimum block RMS required for software VAD speech start.")
486
+ parser.add_argument("--energy-ratio", type=float, default=env_float("OMNIUS_ASR_ENERGY_RATIO", 3.5),
487
+ help="Software VAD speech gate as a multiple of the adaptive noise floor.")
488
+ parser.add_argument("--min-utterance-rms", type=float, default=env_float("OMNIUS_ASR_MIN_UTTERANCE_RMS", 0.003),
489
+ help="Minimum loud-block RMS required before final transcription.")
490
+ parser.add_argument("--min-utterance-peak", type=float, default=env_float("OMNIUS_ASR_MIN_UTTERANCE_PEAK", 0.015),
491
+ help="Minimum utterance peak amplitude required when RMS is low.")
463
492
  args = parser.parse_args()
464
493
 
465
494
  import whisper
@@ -543,6 +572,9 @@ def main():
543
572
  utterance = [] # list of float32 blocks while capturing
544
573
  capturing = False
545
574
  silence_run = 0
575
+ speech_blocks = 0
576
+ utterance_rms_values = []
577
+ utterance_peak = 0.0
546
578
  noise_floor = None
547
579
  last_partial_at = 0.0
548
580
  last_final_text = ""
@@ -558,7 +590,7 @@ def main():
558
590
  noise_floor = 0.7 * noise_floor + 0.3 * block_rms # drop fast
559
591
  else:
560
592
  noise_floor = 0.995 * noise_floor + 0.005 * block_rms # creep up slowly
561
- gate = max(1.5e-4, noise_floor * 2.5) # ≥ ~8dB above floor
593
+ gate = max(float(args.start_rms), noise_floor * float(args.energy_ratio))
562
594
  return block_rms >= gate
563
595
 
564
596
  def transcribe_partial(samples):
@@ -574,9 +606,31 @@ def main():
574
606
  except Exception as e:
575
607
  emit_error(f"Transcription error: {e}")
576
608
 
577
- def transcribe_final(samples):
578
- nonlocal last_final_text
609
+ def utterance_quality_ok(samples, speech_block_count, rms_values, peak) -> bool:
579
610
  if len(samples) < int(0.4 * SAMPLE_RATE):
611
+ return False
612
+ speech_ms = speech_block_count * BLOCK_SECONDS * 1000.0
613
+ if speech_ms < float(args.min_speech_ms):
614
+ if env_bool("OMNIUS_ASR_VERBOSE_VAD", False):
615
+ emit_status(f"Dropped short VAD blob before Whisper: speech_ms={speech_ms:.0f}")
616
+ return False
617
+ if rms_values:
618
+ ordered = sorted(float(v) for v in rms_values)
619
+ loud_half = ordered[len(ordered) // 2:] or ordered
620
+ loud_rms = loud_half[len(loud_half) // 2]
621
+ else:
622
+ loud_rms = float(np.sqrt(np.mean(samples ** 2))) if len(samples) else 0.0
623
+ if loud_rms < float(args.min_utterance_rms) and peak < float(args.min_utterance_peak):
624
+ if env_bool("OMNIUS_ASR_VERBOSE_VAD", False):
625
+ emit_status(
626
+ f"Dropped low-energy VAD blob before Whisper: loud_rms={loud_rms:.5f} peak={peak:.5f}"
627
+ )
628
+ return False
629
+ return True
630
+
631
+ def transcribe_final(samples, speech_block_count=0, rms_values=None, peak=0.0):
632
+ nonlocal last_final_text
633
+ if not utterance_quality_ok(samples, speech_block_count, rms_values or [], peak):
580
634
  return
581
635
  samples = amplify(samples)
582
636
  try:
@@ -612,7 +666,9 @@ def main():
612
666
  last_final_text = primary_text
613
667
  emit_transcript(primary_text, is_final=True, doa=primary_doa, consensus=True)
614
668
  except Exception as e:
615
- emit_error(f"Consensus validation error: {e}")
669
+ emit_consensus_rejected(primary_text, "", "validator_error")
670
+ if env_bool("OMNIUS_ASR_VERBOSE_CONSENSUS", False):
671
+ emit_status(f"Consensus validation error: {e}")
616
672
 
617
673
  threading.Thread(
618
674
  target=validate_consensus,
@@ -664,12 +720,18 @@ def main():
664
720
  if speech:
665
721
  capturing = True
666
722
  silence_run = 0
723
+ speech_blocks = 1
724
+ utterance_rms_values = [block_rms]
725
+ utterance_peak = float(np.max(np.abs(block))) if len(block) else 0.0
667
726
  utterance = list(preroll)
668
727
  preroll.clear()
669
728
  continue
670
729
 
671
730
  utterance.append(block)
731
+ utterance_rms_values.append(block_rms)
732
+ utterance_peak = max(utterance_peak, float(np.max(np.abs(block))) if len(block) else 0.0)
672
733
  if speech:
734
+ speech_blocks += 1
673
735
  silence_run = 0
674
736
  else:
675
737
  silence_run += 1
@@ -677,11 +739,17 @@ def main():
677
739
  utter_samples = sum(len(b) for b in utterance)
678
740
  if silence_run >= finalize_blocks or utter_samples >= max_utter_samples:
679
741
  samples = np.concatenate(utterance)
742
+ final_speech_blocks = speech_blocks
743
+ final_rms_values = list(utterance_rms_values)
744
+ final_peak = utterance_peak
680
745
  capturing = False
681
746
  utterance = []
682
747
  silence_run = 0
683
- transcribe_final(samples)
684
- elif utter_samples >= int(3.5 * SAMPLE_RATE) and now - last_partial_at >= 2.5:
748
+ speech_blocks = 0
749
+ utterance_rms_values = []
750
+ utterance_peak = 0.0
751
+ transcribe_final(samples, final_speech_blocks, final_rms_values, final_peak)
752
+ elif args.partials and utter_samples >= int(3.5 * SAMPLE_RATE) and now - last_partial_at >= 2.5:
685
753
  last_partial_at = now
686
754
  transcribe_partial(np.concatenate(utterance))
687
755
  except KeyboardInterrupt:
@@ -690,7 +758,12 @@ def main():
690
758
  # EOF: finalize any in-flight utterance.
691
759
  if utterance:
692
760
  try:
693
- transcribe_final(np.concatenate(utterance))
761
+ transcribe_final(
762
+ np.concatenate(utterance),
763
+ speech_blocks,
764
+ list(utterance_rms_values),
765
+ utterance_peak,
766
+ )
694
767
  except Exception:
695
768
  pass
696
769
  if tuning is not None:
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "omnius",
3
- "version": "1.0.443",
3
+ "version": "1.0.445",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "omnius",
9
- "version": "1.0.443",
9
+ "version": "1.0.445",
10
10
  "bundleDependencies": [
11
11
  "image-to-ascii"
12
12
  ],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "omnius",
3
- "version": "1.0.443",
3
+ "version": "1.0.445",
4
4
  "description": "AI coding agent powered by open-source models (Ollama/vLLM) — interactive TUI with agentic tool-calling loop",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",