@absolutejs/voice 0.0.21 → 0.0.22-beta.0

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.
Files changed (39) hide show
  1. package/README.md +499 -2
  2. package/dist/angular/index.js +90 -0
  3. package/dist/angular/voice-controller.service.d.ts +6 -0
  4. package/dist/angular/voice-stream.service.d.ts +6 -0
  5. package/dist/client/actions.d.ts +41 -0
  6. package/dist/client/audioPlayer.d.ts +40 -0
  7. package/dist/client/duplex.d.ts +3 -0
  8. package/dist/client/htmxBootstrap.js +84 -0
  9. package/dist/client/index.d.ts +2 -0
  10. package/dist/client/index.js +507 -5
  11. package/dist/correction.d.ts +18 -1
  12. package/dist/fileStore.d.ts +27 -0
  13. package/dist/index.d.ts +12 -1
  14. package/dist/index.js +2425 -33
  15. package/dist/ops.d.ts +100 -0
  16. package/dist/react/index.js +86 -0
  17. package/dist/react/useVoiceController.d.ts +6 -0
  18. package/dist/react/useVoiceStream.d.ts +6 -0
  19. package/dist/routing.d.ts +3 -0
  20. package/dist/runtimeOps.d.ts +23 -0
  21. package/dist/svelte/index.js +84 -0
  22. package/dist/telephony/response.d.ts +7 -0
  23. package/dist/telephony/twilio.d.ts +116 -0
  24. package/dist/testing/benchmark.d.ts +59 -4
  25. package/dist/testing/corrected.d.ts +41 -0
  26. package/dist/testing/duplex.d.ts +59 -0
  27. package/dist/testing/fixtures.d.ts +18 -2
  28. package/dist/testing/index.d.ts +5 -0
  29. package/dist/testing/index.js +4940 -307
  30. package/dist/testing/review.d.ts +143 -0
  31. package/dist/testing/sessionBenchmark.d.ts +25 -0
  32. package/dist/testing/stt.d.ts +2 -1
  33. package/dist/testing/telephony.d.ts +70 -0
  34. package/dist/testing/tts.d.ts +73 -0
  35. package/dist/types.d.ts +290 -3
  36. package/dist/vue/index.js +90 -0
  37. package/dist/vue/useVoiceController.d.ts +11 -0
  38. package/dist/vue/useVoiceStream.d.ts +11 -0
  39. package/package.json +115 -1
package/README.md CHANGED
@@ -37,6 +37,13 @@ const app = new Elysia()
37
37
  voice({
38
38
  path: '/voice',
39
39
  preset: 'guided-intake',
40
+ lexicon: [
41
+ {
42
+ text: 'AbsoluteJS',
43
+ aliases: ['absoloot js'],
44
+ pronunciation: 'ab-so-lute jay ess'
45
+ }
46
+ ],
40
47
  phraseHints: [
41
48
  { text: 'AbsoluteJS', aliases: ['absolute js'] },
42
49
  { text: 'Joe Johnston', aliases: ['joe johnson'] }
@@ -66,11 +73,283 @@ const app = new Elysia()
66
73
 
67
74
  `createVoiceMemoryStore()` is dev-only. Real deployments should provide a shared store backed by Redis, Postgres, or equivalent.
68
75
 
76
+ ## TTS
77
+
78
+ `@absolutejs/voice` now supports optional assistant audio streaming on the same session path. If you provide a `tts` adapter, `assistantText` responses are still sent as text, and the synthesized PCM chunks are streamed as `audio` messages alongside them.
79
+
80
+ ```ts
81
+ import { voice, createVoiceMemoryStore } from '@absolutejs/voice';
82
+ import { deepgram } from '@absolutejs/voice-deepgram';
83
+ import { elevenlabs } from '@absolutejs/voice-elevenlabs';
84
+
85
+ app.use(
86
+ voice({
87
+ path: '/voice',
88
+ session: createVoiceMemoryStore(),
89
+ stt: deepgram({
90
+ apiKey: process.env.DEEPGRAM_API_KEY!,
91
+ model: 'flux-general-en'
92
+ }),
93
+ tts: elevenlabs({
94
+ apiKey: process.env.ELEVENLABS_API_KEY!,
95
+ voiceId: process.env.ELEVENLABS_VOICE_ID!
96
+ }),
97
+ onTurn: async ({ turn }) => ({
98
+ assistantText: `You said: ${turn.text}`
99
+ }),
100
+ onComplete: async () => {}
101
+ })
102
+ );
103
+ ```
104
+
105
+ Client state now exposes `assistantAudio` on the stream/controller helpers, so apps can buffer or play synthesized chunks without inventing a second transport.
106
+
107
+ If you want a minimal browser playback path, use the client audio player:
108
+
109
+ ```ts
110
+ import {
111
+ createVoiceAudioPlayer,
112
+ createVoiceController
113
+ } from '@absolutejs/voice/client';
114
+
115
+ const voice = createVoiceController('/voice', {
116
+ preset: 'chat'
117
+ });
118
+ const player = createVoiceAudioPlayer(voice);
119
+
120
+ await player.start(); // call from a user gesture
121
+ await player.interrupt(); // flush queued assistant playback for barge-in
122
+ ```
123
+
124
+ `createVoiceAudioPlayer()` subscribes to `assistantAudio`, decodes raw `pcm_s16le` chunks, and queues them in WebAudio. It also exposes `interrupt()`, `lastInterruptLatencyMs`, and `lastPlaybackStopLatencyMs` so apps can flush assistant playback during barge-in and inspect how long it took for queued playback to fully stop.
125
+
126
+ For a higher-level client path, use the duplex helper:
127
+
128
+ ```ts
129
+ import { createVoiceDuplexController } from '@absolutejs/voice/client';
130
+
131
+ const voice = createVoiceDuplexController('/voice', {
132
+ bargeIn: {
133
+ interruptThreshold: 0.08
134
+ },
135
+ preset: 'chat'
136
+ });
137
+
138
+ await voice.audioPlayer.start();
139
+ await voice.startRecording();
140
+ ```
141
+
142
+ `createVoiceDuplexController()` composes the controller and audio player and automatically interrupts assistant playback when:
143
+
144
+ - microphone input crosses the configured barge-in threshold
145
+ - partial user speech starts arriving
146
+ - manual `sendAudio(...)` is called while assistant audio is playing
147
+
148
+ ## Duplex Benchmarks
149
+
150
+ The first duplex benchmark lane measures package-level barge-in interruption on the client path. It records scenario pass/fail plus local interruption latency for:
151
+
152
+ - manual `sendAudio(...)`
153
+ - partial transcript start
154
+ - input-level threshold crossing
155
+
156
+ Run it with:
157
+
158
+ ```bash
159
+ bun run bench:duplex
160
+ ```
161
+
162
+ That writes:
163
+
164
+ - `benchmark-results/duplex-barge-in.json`
165
+
166
+ ## Telephony
167
+
168
+ `@absolutejs/voice` now includes a first PSTN bridge layer for Twilio Media Streams. It converts inbound `audio/x-mulaw` 8 kHz frames into the PCM format the voice session expects, and converts assistant PCM audio back into outbound Twilio media events.
169
+
170
+ Minimal usage:
171
+
172
+ ```ts
173
+ import { createTwilioMediaStreamBridge, createTwilioVoiceResponse } from '@absolutejs/voice';
174
+ import { deepgram } from '@absolutejs/voice-deepgram';
175
+ import { elevenlabs } from '@absolutejs/voice-elevenlabs';
176
+
177
+ const twiml = createTwilioVoiceResponse({
178
+ streamUrl: 'wss://example.com/voice/twilio',
179
+ parameters: {
180
+ sessionId: 'call-123',
181
+ scenarioId: 'phone-intake'
182
+ },
183
+ track: 'both_tracks'
184
+ });
185
+
186
+ const bridge = createTwilioMediaStreamBridge(twilioSocket, {
187
+ context: {},
188
+ onComplete: async () => {},
189
+ onTurn: async ({ turn }) => ({
190
+ assistantText: `You said: ${turn.text}`
191
+ }),
192
+ session: createVoiceMemoryStore(),
193
+ stt: deepgram({
194
+ apiKey: process.env.DEEPGRAM_API_KEY!,
195
+ model: 'flux-general-en'
196
+ }),
197
+ tts: elevenlabs({
198
+ apiKey: process.env.ELEVENLABS_API_KEY!,
199
+ voiceId: process.env.ELEVENLABS_VOICE_ID!
200
+ })
201
+ });
202
+
203
+ await bridge.handleMessage(startMessageFromTwilio);
204
+ await bridge.handleMessage(mediaMessageFromTwilio);
205
+ ```
206
+
207
+ The bridge also sends Twilio `clear` events on new inbound media after assistant audio has started streaming, so telephony barge-in can stop queued outbound playback.
208
+
209
+ You can benchmark the package-level Twilio bridge path with:
210
+
211
+ ```bash
212
+ bun run bench:telephony:run
213
+ ```
214
+
215
+ That writes:
216
+ - `benchmark-results/telephony-twilio-bridge.json`
217
+ - `benchmark-results/telephony-run-manifest.json`
218
+
219
+ For a live vendor-backed duplex smoke benchmark on the real TTS adapters, run:
220
+
221
+ ```bash
222
+ bun run bench:duplex:live:run
223
+ ```
224
+
225
+ That writes fresh results to:
226
+
227
+ For a live vendor-backed telephony smoke benchmark through the Twilio bridge path, run:
228
+
229
+ ```bash
230
+ bun run bench:telephony:live:run
231
+ ```
232
+
233
+ That writes:
234
+ - `benchmark-results/telephony-live-deepgram-elevenlabs.json`
235
+ - `benchmark-results/telephony-live-run-manifest.json`
236
+
237
+ For a repeated live telephony stability read, run:
238
+
239
+ ```bash
240
+ bun run bench:telephony:live:series
241
+ ```
242
+
243
+ That writes:
244
+ - `benchmark-results/telephony-live-series-summary-runs-3.json`
245
+
246
+ For a live Deepgram telephony model shootout on the same PSTN path, run:
247
+
248
+ ```bash
249
+ bun run bench:telephony:live:shootout
250
+ ```
251
+
252
+ That writes:
253
+ - `benchmark-results/telephony-live-flux-general-en.json`
254
+ - `benchmark-results/telephony-live-nova-3-phone.json`
255
+ - `benchmark-results/telephony-live-shootout-manifest.json`
256
+
257
+ - `benchmark-results/duplex-live-elevenlabs.json`
258
+ - `benchmark-results/duplex-live-openai.json`
259
+ - `benchmark-results/duplex-live-all.json`
260
+ - `benchmark-results/duplex-live-run-manifest.json`
261
+
262
+ For a browser-run duplex benchmark that uses a real headless Chrome `AudioContext` instead of the fake Node-side playback context, run:
263
+
264
+ ```bash
265
+ bun run bench:duplex:browser:run
266
+ ```
267
+
268
+ That writes fresh results to:
269
+
270
+ - `benchmark-results/duplex-browser-elevenlabs.json`
271
+ - `benchmark-results/duplex-browser-openai.json`
272
+ - `benchmark-results/duplex-browser-all.json`
273
+ - `benchmark-results/duplex-browser-run-manifest.json`
274
+
275
+ To measure browser duplex stability across repeated runs, use:
276
+
277
+ ```bash
278
+ bun run bench:duplex:browser:series
279
+ ```
280
+
281
+ That writes:
282
+
283
+ - `benchmark-results/duplex-browser-series-summary-runs-3.json`
284
+ - per-run provider artifacts like `benchmark-results/duplex-browser-elevenlabs-series-run-1.json`
285
+
286
+ For repeated interrupt-and-resume across several consecutive assistant turns, run:
287
+
288
+ ```bash
289
+ bun run bench:duplex:browser:overlap:run
290
+ ```
291
+
292
+ That writes:
293
+
294
+ - `benchmark-results/duplex-browser-overlap-elevenlabs.json`
295
+ - `benchmark-results/duplex-browser-overlap-openai.json`
296
+ - `benchmark-results/duplex-browser-overlap-all.json`
297
+ - `benchmark-results/duplex-browser-overlap-run-manifest.json`
298
+
299
+ To measure overlap stability across repeated live browser runs, use:
300
+
301
+ ```bash
302
+ bun run bench:duplex:browser:overlap:series
303
+ ```
304
+
305
+ That writes:
306
+
307
+ - `benchmark-results/duplex-browser-overlap-series-summary-runs-3.json`
308
+ - per-run provider artifacts like `benchmark-results/duplex-browser-overlap-elevenlabs-series-run-1.json`
309
+
310
+ ## TTS Benchmarks
311
+
312
+ `@absolutejs/voice` now includes a first TTS benchmark harness for streaming output adapters. The initial metrics are:
313
+
314
+ - `firstAudioLatencyMs`
315
+ - `elapsedMs`
316
+ - `audioChunkCount`
317
+ - `totalAudioBytes`
318
+ - estimated PCM `audioDurationMs`
319
+ - interruption responsiveness via `interruptionLatencyMs`
320
+
321
+ Run the full TTS suite with one command:
322
+
323
+ ```bash
324
+ bun run bench:tts:run
325
+ ```
326
+
327
+ That writes fresh results to:
328
+
329
+ - `benchmark-results/tts-all.json`
330
+ - `benchmark-results/tts-elevenlabs.json`
331
+ - `benchmark-results/tts-openai.json`
332
+ - `benchmark-results/tts-run-manifest.json`
333
+
334
+ To measure interruption/cancel responsiveness separately:
335
+
336
+ ```bash
337
+ bun run bench:tts:interrupt:run
338
+ ```
339
+
340
+ That writes fresh interruption results to:
341
+
342
+ - `benchmark-results/tts-all-interrupt.json`
343
+ - `benchmark-results/tts-elevenlabs-interrupt.json`
344
+ - `benchmark-results/tts-openai-interrupt.json`
345
+ - `benchmark-results/tts-interrupt-run-manifest.json`
346
+
69
347
  ## Recommended Production Path
70
348
 
71
349
  The current best-performing path in the bundled benchmarks is:
72
350
 
73
351
  - `deepgram-flux` as primary STT
352
+ - route-level `lexicon` for pronunciation/domain entries
74
353
  - route-level `phraseHints`
75
354
  - route-level `correctTurn` using `createPhraseHintCorrectionHandler()`
76
355
 
@@ -80,7 +359,9 @@ Minimal production-oriented example:
80
359
 
81
360
  ```ts
82
361
  import {
362
+ createVoiceSTTRoutingCorrectionHandler,
83
363
  createPhraseHintCorrectionHandler,
364
+ resolveVoiceSTTRoutingStrategy,
84
365
  voice
85
366
  } from '@absolutejs/voice';
86
367
  import { deepgram } from '@absolutejs/voice-deepgram';
@@ -89,6 +370,13 @@ app.use(
89
370
  voice({
90
371
  path: '/voice/intake',
91
372
  preset: 'reliability',
373
+ lexicon: [
374
+ {
375
+ text: 'AbsoluteJS',
376
+ aliases: ['absoloot js'],
377
+ pronunciation: 'ab-so-lute jay ess'
378
+ }
379
+ ],
92
380
  phraseHints: [
93
381
  { text: 'AbsoluteJS', aliases: ['absolute js'] },
94
382
  { text: 'Joe Johnston', aliases: ['joe johnson'] },
@@ -113,6 +401,45 @@ app.use(
113
401
 
114
402
  `phraseHints` are user-controlled route config, not hidden framework magic. They are there so the app can teach the voice route its domain vocabulary.
115
403
 
404
+ ## Best Vs Cheap STT
405
+
406
+ `@absolutejs/voice` now exposes an explicit package-level routing split so apps can choose between the strongest benchmarked path and a cheaper/raw path without inventing their own policy layer.
407
+
408
+ ```ts
409
+ import {
410
+ createVoiceMemoryStore,
411
+ createVoiceSTTRoutingCorrectionHandler,
412
+ resolveVoiceSTTRoutingStrategy,
413
+ voice
414
+ } from '@absolutejs/voice';
415
+ import { deepgram } from '@absolutejs/voice-deepgram';
416
+
417
+ const strategy = resolveVoiceSTTRoutingStrategy('best');
418
+
419
+ app.use(
420
+ voice({
421
+ path: '/voice/stt',
422
+ preset: strategy.preset,
423
+ phraseHints: [{ text: 'Joe Johnston', aliases: ['joe johnson'] }],
424
+ correctTurn: createVoiceSTTRoutingCorrectionHandler(strategy.correctionMode),
425
+ session: createVoiceMemoryStore(),
426
+ sttLifecycle: strategy.sttLifecycle,
427
+ stt: deepgram({
428
+ apiKey: process.env.DEEPGRAM_API_KEY!,
429
+ model: 'flux-general-en'
430
+ })
431
+ })
432
+ );
433
+ ```
434
+
435
+ - `best` maps to the current strongest in-package path: Deepgram Flux plus generic deterministic correction.
436
+ - `low-cost` maps to a cheaper/raw package path: one primary STT pass with no correction hook.
437
+ - session benchmarks now include per-turn cost telemetry fields like `averageRelativeCostUnits`, `averagePrimaryAudioMs`, and `averageFallbackReplayAudioMs`.
438
+ - use `bun run bench:stt:routing:run` to benchmark both in parallel and write fresh:
439
+ - `benchmark-results/sessions-best-stt-runs-3.json`
440
+ - `benchmark-results/sessions-cheap-stt-runs-3.json`
441
+ - `benchmark-results/stt-routing-run-manifest.json`
442
+
116
443
  ## Presets
117
444
 
118
445
  Voice now ships named runtime presets so apps can start from a useful baseline instead of hand-tuning silence and capture settings every time.
@@ -161,11 +488,13 @@ Presets are still overridable. If you need to tune for a specific route, layer `
161
488
  Presets are not the same thing as phrase hints:
162
489
 
163
490
  - presets tune framework-owned behavior like silence windows, reconnect defaults, and audio conditioning
491
+ - `lexicon` tunes pronunciation-aware domain entries that should reach STT/TTS adapters directly
164
492
  - `phraseHints` tune app/domain vocabulary like company names, product names, legal phrases, or subscriber-specific jargon
165
493
 
166
494
  In practice:
167
495
 
168
496
  - use a preset to choose the runtime shape (`guided-intake`, `reliability`, `noisy-room`)
497
+ - use `lexicon` when pronunciation matters and you want adapter-consumable entries
169
498
  - use `phraseHints` to teach the route what words matter for your business
170
499
  - use `correctTurn` when you want deterministic post-STT repair before the turn is committed
171
500
 
@@ -199,9 +528,51 @@ The controller helpers abstract the common browser boilerplate:
199
528
 
200
529
  They do not hide the underlying transport. You still choose the route path and preset explicitly.
201
530
 
202
- ## Phrase Hints And Correction
531
+ ## Lexicon, Phrase Hints, And Correction
532
+
533
+ `lexicon` is a route-level input for pronunciation-aware domain entries.
534
+
535
+ It can be:
536
+
537
+ - a static array for known names, products, and jargon
538
+ - a resolver function when entries depend on the tenant, subscriber, or scenario
539
+
540
+ ```ts
541
+ voice({
542
+ path: '/voice/intake',
543
+ lexicon: async ({ context }) => {
544
+ return [
545
+ {
546
+ text: 'AbsoluteJS',
547
+ aliases: ['absoloot js'],
548
+ pronunciation: 'ab-so-lute jay ess'
549
+ },
550
+ {
551
+ text: 'Eden Treaty',
552
+ aliases: ['eden tree tea'],
553
+ pronunciation: 'ee-den tree-tee'
554
+ }
555
+ ];
556
+ },
557
+ session: createVoiceMemoryStore(),
558
+ stt: deepgram({
559
+ apiKey: process.env.DEEPGRAM_API_KEY!,
560
+ model: 'flux-general-en'
561
+ }),
562
+ onTurn: async ({ turn }) => ({
563
+ assistantText: turn.text
564
+ }),
565
+ onComplete: async () => {}
566
+ });
567
+ ```
568
+
569
+ How the package uses it:
570
+
571
+ - adapters receive `lexicon` at open time and translate it into vendor-native hinting surfaces when possible
572
+ - STT adapters can use the canonical text plus aliases to bias recognition
573
+ - future TTS adapters can use the same entries for pronunciation-aware speech output
203
574
 
204
- `phraseHints` are a route-level input that the application owns.
575
+ `phraseHints` are a separate route-level input that the application owns.
205
576
 
206
577
  They can be:
207
578
 
@@ -234,6 +605,7 @@ voice({
234
605
 
235
606
  How the package uses them:
236
607
 
608
+ - adapters receive `lexicon` and `phraseHints` at open time
237
609
  - adapters receive `phraseHints` at open time and can translate them into vendor-native hinting surfaces
238
610
  - the correction layer can use the same hints after STT to repair domain terms before commit
239
611
 
@@ -361,6 +733,11 @@ Use profiles to focus where you want to win:
361
733
 
362
734
  - `bun run bench:vs all` (default)
363
735
  - `bun run bench:vs all accents`
736
+ - `bun run bench:vs all code-switch`
737
+ - `bun run bench:vs all jargon`
738
+ - `bun run bench:vs all multilingual`
739
+ - `bun run bench:vs all multi-speaker`
740
+ - `bun run bench:vs all telephony`
364
741
  - `bun run bench:vs all clean`
365
742
  - `bun run bench:vs all noisy`
366
743
  - `bun run bench:vs deepgram accents`
@@ -387,6 +764,21 @@ DEEPGRAM_MODEL=flux-general-en bun run bench:deepgram:accents
387
764
  DEEPGRAM_MODEL=nova-3 bun run bench:deepgram:accents
388
765
  ```
389
766
 
767
+ To stress the STT path with synthesized narrowband phone audio:
768
+
769
+ ```bash
770
+ bun run bench:telephony
771
+ bun run bench:telephony:run
772
+ bun run bench:deepgram:telephony
773
+ bun run bench:deepgram:corrected:telephony
774
+ bun run bench:jargon
775
+ bun run bench:deepgram:jargon
776
+ bun run bench:deepgram:corrected:audit:jargon
777
+ bun run bench:multi-speaker:run
778
+ bun run bench:multi-speaker:analyze
779
+ bun run bench:deepgram:multi-speaker
780
+ ```
781
+
390
782
  To compare against Vapi or other providers, provide a baseline JSON file:
391
783
 
392
784
  ```bash
@@ -427,20 +819,31 @@ The harness prints:
427
819
  - pass rate and recall deltas per adapter
428
820
  - weighted scorecard (`passRate`, term recall, word accuracy)
429
821
  - optional competitor deltas (Vapi)
822
+ - a markdown report beside the JSON output, for example:
823
+ - `benchmark-results/vs-all-telephony.json`
824
+ - `benchmark-results/vs-all-telephony.md`
430
825
 
431
826
  For package-level multi-turn behavior, use the session benchmark harness instead of raw STT-only benchmarking:
432
827
 
433
828
  ```bash
434
829
  bun run bench:sessions
435
830
  bun run bench:deepgram:sessions
831
+ bun run bench:deepgram:soak:sessions
436
832
  bun run bench:deepgram:hybrid:sessions
437
833
  bun run bench:deepgram:corrected:sessions
834
+ bun run bench:deepgram:corrected:soak:sessions
835
+ bun run bench:stt:routing:run
438
836
  bun run bench:assemblyai:sessions
439
837
  bun run bench:openai:sessions
838
+ bun run bench:soak:run
440
839
  ```
441
840
 
442
841
  That harness runs the adapter through `VoiceSession` itself, so the output reflects reconnect handling, turn commit stability, and duplicate-turn protection rather than only raw transcript quality.
443
842
 
843
+ `bench:soak:run` is the STT-5 runner. It executes the long-session soak lane for raw Deepgram Flux, corrected Deepgram, and the reconnect resilience suite in parallel, then writes fresh JSON into `benchmark-results/` without the runs deleting each other.
844
+
845
+ `bench:stt:routing:run` is the STT-7 runner. It benchmarks the package’s current `best` vs `low-cost` session strategies in parallel, clears stale outputs first, and writes a manifest so the cost-aware summaries are guaranteed fresh.
846
+
444
847
  `bench:deepgram:corrected:sessions` exercises the current recommended package-level production path:
445
848
 
446
849
  - Deepgram Flux as primary STT
@@ -568,6 +971,100 @@ Fallback triggers are evaluated at commit time:
568
971
 
569
972
  The fallback adapter receives the same window of turn audio as the primary (default `8s`, configurable with `replayWindowMs`) and can only run `maxAttemptsPerTurn` times per turn.
570
973
 
974
+ ## Benchmark Fixture Sources
975
+
976
+ Bundled fixtures cover the current in-repo English benchmark suite. For multilingual and code-switch evaluation, add external fixture directories and let the benchmark scripts merge them automatically.
977
+
978
+ The public corpus builder currently assembles:
979
+
980
+ - FLEURS multilingual dev clips
981
+ - BSC Catalan-Spanish code-switch evaluation clips
982
+ - CoSHE Hindi-English code-switch evaluation clips
983
+
984
+ Set either:
985
+
986
+ - `VOICE_FIXTURE_DIR=/abs/path/to/fixtures`
987
+ - `VOICE_FIXTURE_DIRS=/abs/path/one,/abs/path/two`
988
+
989
+ Each fixture directory must include:
990
+
991
+ - `manifest.json`
992
+ - `pcm/*.pcm`
993
+
994
+ Each manifest entry can include:
995
+
996
+ - `language`
997
+ - `tags`
998
+ Use `multilingual`, `bilingual`, or `code-switch` to route fixtures into the multilingual benchmark lane.
999
+
1000
+ Benchmark commands:
1001
+
1002
+ ```bash
1003
+ bun run bench:multilingual
1004
+ bun run bench:code-switch
1005
+ bun run bench:code-switch:series
1006
+ bun run bench:code-switch:ca-es
1007
+ bun run bench:code-switch:ca-es:series
1008
+ bun run bench:code-switch:ca-es:corts:series
1009
+ bun run bench:code-switch:ca-es:parlament:series
1010
+ bun run bench:code-switch:hi-en
1011
+ bun run bench:code-switch:hi-en:series
1012
+ bun run bench:deepgram:multilingual
1013
+ bun run bench:deepgram:code-switch
1014
+ bun run bench:deepgram:code-switch:series
1015
+ bun run bench:deepgram:code-switch:ca-es
1016
+ bun run bench:deepgram:code-switch:ca-es:series
1017
+ bun run bench:deepgram:code-switch:ca-es:corts:series
1018
+ bun run bench:deepgram:code-switch:ca-es:parlament:series
1019
+ bun run bench:deepgram:code-switch:ca-es:nova3-multi:series
1020
+ bun run bench:deepgram:code-switch:ca-es:nova3-ca:series
1021
+ bun run bench:deepgram:code-switch:ca-es:nova3-es:series
1022
+ bun run bench:deepgram:code-switch:ca-es:nova2-ca:series
1023
+ bun run bench:deepgram:code-switch:ca-es:nova2-es:series
1024
+ bun run bench:deepgram:code-switch:ca-es:best:corrected:series
1025
+ bun run bench:deepgram:code-switch:ca-es:parlament:debug
1026
+ bun run bench:deepgram:code-switch:corrected:ca-es
1027
+ bun run bench:deepgram:code-switch:corrected:ca-es:series
1028
+ bun run bench:deepgram:code-switch:corrected:ca-es:corts:series
1029
+ bun run bench:deepgram:code-switch:corrected:ca-es:parlament:series
1030
+ bun run bench:deepgram:code-switch:hi-en
1031
+ bun run bench:deepgram:code-switch:hi-en:series
1032
+ bun run bench:deepgram:code-switch:corrected:hi-en
1033
+ bun run bench:deepgram:code-switch:corrected:hi-en:series
1034
+ bun run bench:deepgram:code-switch:corrected
1035
+ bun run bench:deepgram:code-switch:corrected:series
1036
+ bun run bench:assemblyai:multilingual
1037
+ bun run bench:assemblyai:code-switch
1038
+ bun run bench:openai:multilingual
1039
+ bun run bench:openai:code-switch
1040
+ bun run bench:openai:code-switch:series
1041
+ bun run bench:openai:code-switch:ca-es
1042
+ bun run bench:openai:code-switch:ca-es:series
1043
+ bun run bench:openai:code-switch:corrected:ca-es
1044
+ bun run bench:openai:code-switch:corrected:ca-es:series
1045
+ bun run bench:openai:code-switch:hi-en
1046
+ bun run bench:openai:code-switch:hi-en:series
1047
+ bun run bench:openai:code-switch:corrected:hi-en
1048
+ bun run bench:openai:code-switch:corrected:hi-en:series
1049
+ bun run bench:openai:code-switch:corrected
1050
+ bun run bench:openai:code-switch:corrected:series
1051
+ ```
1052
+
1053
+ Current benchmark direction:
1054
+
1055
+ - `openai` is the strongest adapter on the current public multilingual corpus
1056
+ - `deepgram` remains the strongest browser-English path
1057
+ - raw code-switch remains a weaker surface for every adapter and should be benchmarked separately with `bench:code-switch`
1058
+ - jargon-heavy/domain-heavy English terms now have their own profile; use `bench:jargon` for the cross-adapter read and `bench:deepgram:corrected:audit:jargon` to compare `raw` vs `generic` vs `experimental` vs `benchmarkSeeded`
1059
+ - code-switch should be treated as language-pair-specific, not one universal lane; `ca-es` and `hi-en` now have dedicated series commands
1060
+ - `ca-es` also has a dedicated Deepgram model/language shootout lane so you can compare `nova-3`/`nova-2` with `multi`, `ca`, and `es` routing without overwriting results
1061
+ - current best `ca-es` base path is `deepgram` `nova-3` with `language=ca`; the short runner script uses that path for corrected series
1062
+ - `ca-es` is also split by source now: `corts_valencianes` and `parlament_parla` can be benchmarked independently, and `parlament_parla` has a dedicated transcript dump script
1063
+ - corrected code-switch runs now have dedicated lexicon-driven series commands so raw and corrected stability can be compared directly
1064
+ - multi-speaker diarization is now its own benchmark surface; use `bench:multi-speaker:run` for the parallel cross-adapter plus Deepgram-specific read
1065
+ - when tuning diarization specifically, use `bench:multi-speaker:analyze` to split Deepgram into clean vs noisy handoff lanes, include a corrected noisy read, and emit a speaker-pattern debug dump
1066
+ - use the `:series` commands when you need stability rather than a single-pass snapshot
1067
+
571
1068
  ## Client Primitives
572
1069
 
573
1070
  Browser and framework helpers sit on top of the same connection core: