pi-voicekit 0.3.0 → 0.3.1
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.
|
@@ -1103,14 +1103,8 @@ async function transcribeInProcess(
|
|
|
1103
1103
|
config: VoiceConfig,
|
|
1104
1104
|
onSegment?: (text: string, index: number) => void
|
|
1105
1105
|
): Promise<string> {
|
|
1106
|
-
const {
|
|
1107
|
-
|
|
1108
|
-
isSherpaAvailable,
|
|
1109
|
-
getSherpaError,
|
|
1110
|
-
getOrCreateRecognizer,
|
|
1111
|
-
transcribeBuffer,
|
|
1112
|
-
transcribeBufferSegmented,
|
|
1113
|
-
} = await import("./sherpa-engine");
|
|
1106
|
+
const { initSherpa, isSherpaAvailable, getSherpaError, getOrCreateRecognizer, transcribeBufferSegmented } =
|
|
1107
|
+
await import("./sherpa-engine");
|
|
1114
1108
|
const { ensureModelDownloaded } = await import("./model-download");
|
|
1115
1109
|
|
|
1116
1110
|
// Initialize sherpa if needed
|
|
@@ -1131,21 +1125,10 @@ async function transcribeInProcess(
|
|
|
1131
1125
|
|
|
1132
1126
|
// Create/reuse recognizer and transcribe
|
|
1133
1127
|
const recognizer = getOrCreateRecognizer(model, modelDir, config.language || "en");
|
|
1134
|
-
//
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
const text = await transcribeBuffer(pcmData, recognizer);
|
|
1139
|
-
// Every other in-process model decodes the whole buffer as a single segment. Report it so
|
|
1140
|
-
// a pipelining caller sees one segment for any in-process dictation (a short qwen3
|
|
1141
|
-
// dictation takes the same single-segment fast path). Observational, like the recogniser's
|
|
1142
|
-
// callback: a throwing observer must not cost the transcript.
|
|
1143
|
-
if (onSegment) {
|
|
1144
|
-
try {
|
|
1145
|
-
onSegment(text, 0);
|
|
1146
|
-
} catch {}
|
|
1147
|
-
}
|
|
1148
|
-
return text;
|
|
1128
|
+
// Every in-process model decodes long audio in VAD-sized pieces, so a pipelining caller can
|
|
1129
|
+
// polish one piece while the rest still decodes; audio at or below the segment threshold takes
|
|
1130
|
+
// the same single-decode fast path as before and the concatenation is unchanged.
|
|
1131
|
+
return transcribeBufferSegmented(pcmData, recognizer, undefined, onSegment);
|
|
1149
1132
|
}
|
|
1150
1133
|
|
|
1151
1134
|
/** Check if a local transcription server is reachable. */
|
|
@@ -393,7 +393,7 @@ export function createPolishQueue(options: PolishQueueOptions): PolishQueue {
|
|
|
393
393
|
const call: LiveCall = { settled: false, orphaned: false };
|
|
394
394
|
let issued = false;
|
|
395
395
|
try {
|
|
396
|
-
const sampling = polishSamplingOptions(options.model
|
|
396
|
+
const sampling = polishSamplingOptions(options.model);
|
|
397
397
|
const result = await polishTranscript({
|
|
398
398
|
raw: job.raw,
|
|
399
399
|
entries: entriesForSegment(job.index),
|
|
@@ -331,36 +331,23 @@ export function buildPolishAudit(input: {
|
|
|
331
331
|
}
|
|
332
332
|
|
|
333
333
|
/**
|
|
334
|
-
* Extra request fields for the polish call, or nothing when the model has no thinking to turn
|
|
335
|
-
* off.
|
|
334
|
+
* Extra request fields for the polish call, or nothing when the model has no thinking to turn off.
|
|
336
335
|
*
|
|
337
|
-
*
|
|
338
|
-
*
|
|
339
|
-
*
|
|
340
|
-
*
|
|
341
|
-
* reasoning tokens
|
|
342
|
-
*
|
|
343
|
-
*
|
|
344
|
-
*
|
|
345
|
-
* answer was truncated and the pass fell back to the raw transcript — intermittently, which is
|
|
346
|
-
* what made a long dictation look unpolished. With thinking off the same input finished in ~1.2 s
|
|
347
|
-
* and spent no reasoning tokens at all.
|
|
336
|
+
* Thinking is off for every polish call on a reasoning model. It buys quality on short samples
|
|
337
|
+
* (measured 2026-09-26: a self-correction merged for +1.71 CER with thinking on against 0 with it
|
|
338
|
+
* off, two zh-en term samples +0.08/+0.10) but it spends a budget nobody can predict: the same
|
|
339
|
+
* spend that truncated long transcripts also hit short ones - a 76-character dictation burned
|
|
340
|
+
* ~1,200 reasoning tokens against a 664-1,024 budget and came back truncated after 7.6 s, and
|
|
341
|
+
* every real-corpus failure recorded on 2026-09-26 was `stop-reason:length` on a transcript of
|
|
342
|
+
* under 200 characters. On the real corpus the measured correction gain was ~0 either way, so
|
|
343
|
+
* the pass keeps the wording it can improve and gives up the truncation class entirely.
|
|
348
344
|
*
|
|
349
345
|
* `samplingParams` is applied by OpenAI-compatible adapters only, and the `reasoning` gate keeps
|
|
350
346
|
* the field away from models with no thinking at all.
|
|
351
|
-
*
|
|
352
|
-
* `forceOff` is the segmented queue's retry: the attempt already burned its deadline, so it runs
|
|
353
|
-
* small and cheap regardless of length. It overrides the length gate only - a model with no
|
|
354
|
-
* thinking still gets no sampling fields at all.
|
|
355
347
|
*/
|
|
356
|
-
export
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
model: { reasoning?: boolean } | undefined | null,
|
|
360
|
-
rawLength: number,
|
|
361
|
-
forceOff = false
|
|
362
|
-
): { samplingParams?: { reasoning_effort: string } } {
|
|
348
|
+
export function polishSamplingOptions(model: { reasoning?: boolean } | undefined | null): {
|
|
349
|
+
samplingParams?: { reasoning_effort: string };
|
|
350
|
+
} {
|
|
363
351
|
if (!model || model.reasoning !== true) return {};
|
|
364
|
-
if (!forceOff && Number.isFinite(rawLength) && rawLength <= THINKING_MAX_CHARS) return {};
|
|
365
352
|
return { samplingParams: { reasoning_effort: "none" } };
|
|
366
353
|
}
|
package/extensions/voice.ts
CHANGED
|
@@ -1029,10 +1029,9 @@ export default function (pi: ExtensionAPI) {
|
|
|
1029
1029
|
{
|
|
1030
1030
|
signal,
|
|
1031
1031
|
maxTokens: request.maxTokens,
|
|
1032
|
-
// Measured 2026-09-26: without this a reasoning model
|
|
1033
|
-
//
|
|
1034
|
-
|
|
1035
|
-
...polishSamplingOptions(model as { reasoning?: boolean }, raw.length),
|
|
1032
|
+
// Measured 2026-09-26: without this a reasoning model can spend the whole budget thinking,
|
|
1033
|
+
// truncating the answer on any dictation — see polishSamplingOptions.
|
|
1034
|
+
...polishSamplingOptions(model as { reasoning?: boolean }),
|
|
1036
1035
|
}
|
|
1037
1036
|
),
|
|
1038
1037
|
debug: (reason, data) => voiceDebug(`polish ${reason}`, data),
|
|
@@ -1042,7 +1041,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
1042
1041
|
configured: choice.ref,
|
|
1043
1042
|
// Pure and cheap, so computing it twice (here and in the call options) is fine, and it
|
|
1044
1043
|
// keeps the audit entry honest about what the pass decided.
|
|
1045
|
-
thinkingOff: Boolean(polishSamplingOptions(model as { reasoning?: boolean }
|
|
1044
|
+
thinkingOff: Boolean(polishSamplingOptions(model as { reasoning?: boolean }).samplingParams),
|
|
1046
1045
|
maxTokens: polishMaxTokens(raw.length),
|
|
1047
1046
|
status: result.status,
|
|
1048
1047
|
ms: Date.now() - started,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-voicekit",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "Voice in + voice out for Pi CLI — hold-to-talk STT (Deepgram streaming or 21 offline models) plus TTS (Kitten Nano, Piper, Kokoro, or Deepgram Aura)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"keywords": [
|