@qvac/sdk 0.20.1 → 0.21.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.
- package/NOTICE +76 -13
- package/README.md +39 -5
- package/dist/examples/advisory-model-fit.d.ts +17 -16
- package/dist/examples/advisory-model-fit.js +18 -18
- package/dist/examples/asr/parakeet-indic-conformer-filesystem.js +3 -3
- package/dist/examples/asr/parakeet-unified-coreml.d.ts +2 -0
- package/dist/examples/asr/parakeet-unified-coreml.js +113 -0
- package/dist/examples/assess-model-fit.js +9 -4
- package/dist/examples/model-fit-ui/server.js +5 -2
- package/dist/examples/rpc-inference.d.ts +2 -0
- package/dist/examples/rpc-inference.js +51 -0
- package/dist/examples/rpc-server.d.ts +2 -0
- package/dist/examples/rpc-server.js +32 -0
- package/dist/examples/tools/llamacpp-deferred-tools.d.ts +2 -0
- package/dist/examples/tools/llamacpp-deferred-tools.js +77 -0
- package/dist/examples/tts/audio8.js +4 -0
- package/dist/examples/tts/moss-dialogue.d.ts +2 -0
- package/dist/examples/tts/moss-dialogue.js +63 -0
- package/dist/examples/tts/moss.d.ts +2 -0
- package/dist/examples/tts/moss.js +71 -0
- package/dist/scripts/contract/build-contract.d.ts +2 -1
- package/dist/scripts/contract/build-contract.js +4 -1
- package/dist/scripts/contract/build-numeric-constants.d.ts +15 -0
- package/dist/scripts/contract/build-numeric-constants.js +13 -0
- package/dist/scripts/contract/method-shapes.d.ts +3 -0
- package/dist/scripts/contract/method-shapes.js +3 -0
- package/dist/scripts/enforce-inference-versions.d.ts +18 -0
- package/dist/scripts/enforce-inference-versions.js +158 -0
- package/dist/scripts/generate-bare-kit-runtimes.d.ts +2 -0
- package/dist/scripts/generate-bare-kit-runtimes.js +106 -0
- package/dist/src/client/api/assess-model-fit.d.ts +5 -4
- package/dist/src/client/api/assess-model-fit.js +24 -7
- package/dist/src/client/api/bci-transcribe.d.ts +4 -1
- package/dist/src/client/api/get-system-resources.d.ts +8 -0
- package/dist/src/client/api/get-system-resources.js +8 -0
- package/dist/src/client/api/index.d.ts +1 -0
- package/dist/src/client/api/index.js +1 -0
- package/dist/src/client/api/rpc-server.d.ts +20 -0
- package/dist/src/client/api/rpc-server.js +45 -0
- package/dist/src/client/api/text-to-speech.d.ts +9 -0
- package/dist/src/client/api/text-to-speech.js +9 -0
- package/dist/src/client/api/transcribe.d.ts +21 -15
- package/dist/src/client/api/transcribe.js +65 -20
- package/dist/src/client/rpc/rpc-error.js +11 -7
- package/dist/src/commands/bundle/entry-gen.d.ts +2 -2
- package/dist/src/commands/bundle/entry-gen.js +8 -4
- package/dist/src/commands/bundle/index.d.ts +20 -0
- package/dist/src/commands/bundle/index.js +88 -12
- package/dist/src/commands/bundle/plugins.js +1 -1
- package/dist/src/commands/command-logger.d.ts +6 -0
- package/dist/src/commands/command-logger.js +12 -0
- package/dist/src/commands/host-prebuilds/index.d.ts +71 -0
- package/dist/src/commands/host-prebuilds/index.js +227 -0
- package/dist/src/commands/host-prebuilds/package-manager.d.ts +54 -0
- package/dist/src/commands/host-prebuilds/package-manager.js +278 -0
- package/dist/src/commands/index.d.ts +10 -1
- package/dist/src/commands/index.js +6 -0
- package/dist/src/commands/verify/abi.d.ts +22 -2
- package/dist/src/commands/verify/abi.js +49 -27
- package/dist/src/commands/verify/addon-source.d.ts +22 -0
- package/dist/src/commands/verify/addon-source.js +69 -3
- package/dist/src/commands/verify/bare-kit-runtime.d.ts +38 -0
- package/dist/src/commands/verify/bare-kit-runtime.js +201 -0
- package/dist/src/commands/verify/bare-kit-runtimes.d.ts +12 -0
- package/dist/src/commands/verify/bare-kit-runtimes.js +108 -0
- package/dist/src/commands/verify/bundle-source.js +16 -15
- package/dist/src/commands/verify/engines-advice.d.ts +73 -0
- package/dist/src/commands/verify/engines-advice.js +271 -0
- package/dist/src/commands/verify/index.d.ts +22 -0
- package/dist/src/commands/verify/index.js +151 -13
- package/dist/src/commands/verify/node-modules-source.js +43 -39
- package/dist/src/commands/verify/prebuilds.d.ts +19 -0
- package/dist/src/commands/verify/prebuilds.js +52 -13
- package/dist/src/expo/plugins/index.d.ts +2 -0
- package/dist/src/expo/plugins/withMobileBundle.d.ts +15 -5
- package/dist/src/expo/plugins/withMobileBundle.js +68 -42
- package/dist/src/expo/plugins/withQvacSDK.d.ts +7 -1
- package/dist/src/expo/plugins/withQvacSDK.js +6 -3
- package/dist/src/index.d.ts +5 -2
- package/dist/src/index.js +4 -2
- package/dist/src/schemas/sdk-errors-client.d.ts +2 -0
- package/dist/src/schemas/sdk-errors-client.js +16 -0
- package/dist/src/schemas/sdk-errors-server.d.ts +1 -0
- package/dist/src/schemas/sdk-errors-server.js +5 -0
- package/dist/src/utils/errors-client.d.ts +15 -0
- package/dist/src/utils/errors-client.js +26 -0
- package/dist/src/utils/errors-server.d.ts +1 -0
- package/dist/src/utils/errors-server.js +1 -0
- package/dist/src/utils/mcp-adapter.js +7 -1
- package/dist/src/utils/tool-helpers.d.ts +4 -0
- package/dist/src/utils/tool-helpers.js +2 -0
- package/dist/src/worker/ggml-rpc-server-provider.d.ts +2 -0
- package/dist/src/worker/ggml-rpc-server-provider.js +1 -0
- package/dist/src/worker/rpc-server-provider.d.ts +3 -0
- package/dist/src/worker/rpc-server-provider.js +1 -0
- package/package.json +30 -25
- package/dist/scripts/enforce-inference-peer-dependencies.d.ts +0 -2
- package/dist/scripts/enforce-inference-peer-dependencies.js +0 -39
- package/dist/src/deprecate.d.ts +0 -2
- package/dist/src/deprecate.js +0 -9
- package/dist/src/worker/legacy-worker-entry.d.ts +0 -2
- package/dist/src/worker/legacy-worker-entry.js +0 -3
- package/dist/src/worker/worker-core.d.ts +0 -7
- package/dist/src/worker/worker-core.js +0 -8
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// npx tsx rpc-inference.ts my-private-rpc-group
|
|
2
|
+
import { discoverRpcServers, getRpcDeviceMap, loadModel, completion, unloadModel, close, LLAMA_3_2_1B_INST_Q4_0 } from '@qvac/sdk';
|
|
3
|
+
try {
|
|
4
|
+
const topic = process.argv[2];
|
|
5
|
+
if (!topic)
|
|
6
|
+
throw new Error('Usage: rpc-inference.ts <topic>');
|
|
7
|
+
// Prefer server RDMA offers before assigning aliases. Transport is negotiated at load time.
|
|
8
|
+
const selected = (await discoverRpcServers({ topic, timeoutMs: 5000 }))
|
|
9
|
+
.sort((a, b) => Number(b.rdmaAvailable) - Number(a.rdmaAvailable) || a.url.localeCompare(b.url))
|
|
10
|
+
.slice(0, 2);
|
|
11
|
+
if (selected.length !== 2)
|
|
12
|
+
throw new Error('Two idle RPC servers are required');
|
|
13
|
+
const devices = getRpcDeviceMap(selected);
|
|
14
|
+
if (!devices.length)
|
|
15
|
+
throw new Error('The selected servers expose no devices');
|
|
16
|
+
console.error(`▸ Using ${devices.length} devices across ${selected.length} servers`);
|
|
17
|
+
const modelId = await loadModel({
|
|
18
|
+
modelSrc: LLAMA_3_2_1B_INST_Q4_0,
|
|
19
|
+
modelConfig: {
|
|
20
|
+
device: 'gpu',
|
|
21
|
+
'rpc-servers': selected.map((server) => server.url).join(','),
|
|
22
|
+
devices: devices.map((device) => device.alias).join(','),
|
|
23
|
+
'split-mode': 'layer',
|
|
24
|
+
'tensor-split': devices.map(() => '1').join(',')
|
|
25
|
+
},
|
|
26
|
+
onProgress: (progress) => {
|
|
27
|
+
process.stderr.write(`\r▸ Downloading ${progress.percentage.toFixed(0)}%`);
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
try {
|
|
31
|
+
process.stderr.write('\n');
|
|
32
|
+
const run = completion({
|
|
33
|
+
modelId,
|
|
34
|
+
history: [{ role: 'user', content: 'Explain distributed inference in one sentence.' }],
|
|
35
|
+
stream: true
|
|
36
|
+
});
|
|
37
|
+
for await (const token of run.tokenStream)
|
|
38
|
+
process.stdout.write(token);
|
|
39
|
+
process.stdout.write('\n');
|
|
40
|
+
}
|
|
41
|
+
finally {
|
|
42
|
+
await unloadModel({ modelId });
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
console.error('✖', error);
|
|
47
|
+
process.exitCode = 1;
|
|
48
|
+
}
|
|
49
|
+
finally {
|
|
50
|
+
await close();
|
|
51
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// Rebuild the worker with
|
|
2
|
+
// rpcServerProvider: '@qvac/sdk/ggml-rpc-server/provider' in qvac.config.json.
|
|
3
|
+
// npx tsx rpc-server.ts 10.0.0.2 my-private-rpc-group
|
|
4
|
+
import { createInterface } from 'node:readline/promises';
|
|
5
|
+
import { startRpcServer, stopRpcServer, close } from '@qvac/sdk';
|
|
6
|
+
try {
|
|
7
|
+
const host = process.argv[2];
|
|
8
|
+
const topic = process.argv[3];
|
|
9
|
+
if (!host || !topic)
|
|
10
|
+
throw new Error('Usage: rpc-server.ts <private-ipv4> <topic>');
|
|
11
|
+
const server = await startRpcServer({
|
|
12
|
+
host,
|
|
13
|
+
allowNonLoopbackHost: true,
|
|
14
|
+
discoveryTopic: topic
|
|
15
|
+
});
|
|
16
|
+
const input = createInterface({ input: process.stdin, output: process.stderr });
|
|
17
|
+
try {
|
|
18
|
+
console.error(`▸ Serving ${server.url}; RDMA capable: ${server.rdmaCapable}`);
|
|
19
|
+
await input.question('▸ Press Enter to stop after clients unload their models.\n');
|
|
20
|
+
}
|
|
21
|
+
finally {
|
|
22
|
+
input.close();
|
|
23
|
+
await stopRpcServer({ serverId: server.serverId });
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
catch (error) {
|
|
27
|
+
console.error('✖', error);
|
|
28
|
+
process.exitCode = 1;
|
|
29
|
+
}
|
|
30
|
+
finally {
|
|
31
|
+
await close();
|
|
32
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
// Deferred tool loading: a large tool set stays out of the prompt until the
|
|
2
|
+
// model asks for it. Only `get_weather` and the built-in `tool_search` (whose
|
|
3
|
+
// description carries a name-and-description catalog of the rest) are in the
|
|
4
|
+
// initial prompt; the deferred schemas arrive in the conversation when the
|
|
5
|
+
// model searches for them.
|
|
6
|
+
import { TOOL_SEARCH_NAME, completion, executeToolSearch, loadModel, unloadModel, QWEN3_1_7B_INST_Q4 } from '@qvac/sdk';
|
|
7
|
+
import { z } from 'zod';
|
|
8
|
+
import { weatherSchema, horoscopeSchema, mockExecute } from './shared.js';
|
|
9
|
+
const tools = [
|
|
10
|
+
{
|
|
11
|
+
name: 'get_weather',
|
|
12
|
+
description: 'Get current weather for a city',
|
|
13
|
+
parameters: weatherSchema
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
name: 'get_horoscope',
|
|
17
|
+
description: "Get today's horoscope for an astrological sign",
|
|
18
|
+
parameters: horoscopeSchema,
|
|
19
|
+
deferLoading: true,
|
|
20
|
+
group: 'astrology'
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
name: 'get_moon_phase',
|
|
24
|
+
description: 'Get the current phase of the moon',
|
|
25
|
+
parameters: z.object({ date: z.string().describe('ISO date').optional() }),
|
|
26
|
+
deferLoading: true,
|
|
27
|
+
group: 'astrology'
|
|
28
|
+
}
|
|
29
|
+
];
|
|
30
|
+
try {
|
|
31
|
+
const modelId = await loadModel({
|
|
32
|
+
modelSrc: QWEN3_1_7B_INST_Q4,
|
|
33
|
+
modelConfig: { ctx_size: 4096, tools: true }
|
|
34
|
+
});
|
|
35
|
+
console.log(`▸ Model loaded. Model ID: ${modelId}`);
|
|
36
|
+
const history = [
|
|
37
|
+
{
|
|
38
|
+
role: 'system',
|
|
39
|
+
content: 'You are a helpful assistant. Search for a tool before calling it.'
|
|
40
|
+
},
|
|
41
|
+
{ role: 'user', content: "What's my horoscope for Aquarius?" }
|
|
42
|
+
];
|
|
43
|
+
console.log('\n▸ Turn 1 — only get_weather and tool_search are in the prompt\n');
|
|
44
|
+
const first = completion({ modelId, history, stream: true, tools });
|
|
45
|
+
for await (const token of first.tokenStream)
|
|
46
|
+
process.stdout.write(token);
|
|
47
|
+
const calls = await first.toolCalls;
|
|
48
|
+
const search = calls.find((call) => call.name === TOOL_SEARCH_NAME);
|
|
49
|
+
if (!search) {
|
|
50
|
+
console.log('\n\n▸ The model answered without searching; nothing more to show.');
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
console.log(`\n\n▸ The model searched: ${JSON.stringify(search.arguments)}`);
|
|
54
|
+
// Running the search is the SDK's job, not a tool handler's. The result is
|
|
55
|
+
// an ordinary tool message: appending it is what makes the definitions
|
|
56
|
+
// callable on the next turn.
|
|
57
|
+
history.push({ role: 'assistant', content: await first.text });
|
|
58
|
+
history.push({
|
|
59
|
+
role: 'tool',
|
|
60
|
+
content: executeToolSearch(tools, search.arguments, history)
|
|
61
|
+
});
|
|
62
|
+
console.log('▸ Definitions appended. Turn 2 — the model can now call them directly.\n');
|
|
63
|
+
const second = completion({ modelId, history, stream: true, tools });
|
|
64
|
+
for await (const token of second.tokenStream)
|
|
65
|
+
process.stdout.write(token);
|
|
66
|
+
for (const call of await second.toolCalls) {
|
|
67
|
+
console.log(`\n\n▸ Tool call: ${call.name}(${JSON.stringify(call.arguments)})`);
|
|
68
|
+
console.log(`▸ Result: ${mockExecute(call.name, call.arguments)}`);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
console.log('\n\n▸ Completed!');
|
|
72
|
+
await unloadModel({ modelId, clearStorage: false });
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
console.error('✖', error);
|
|
76
|
+
process.exit(1);
|
|
77
|
+
}
|
|
@@ -4,6 +4,8 @@ import { createWav, playAudio, int16ArrayToBuffer, createWavHeader } from './uti
|
|
|
4
4
|
// primary modelSrc is the LM GGUF; the codec decoder loads via modelConfig.
|
|
5
5
|
// For zero-shot voice cloning also pass audio8CodecEncoderModelSrc plus
|
|
6
6
|
// referenceAudioSrc/referenceText (a recording and its exact transcript).
|
|
7
|
+
// On macOS and iOS the decoder downloads with its Core ML bundle, which runs
|
|
8
|
+
// the codec's synthesis stack; `stats.codecOnCoreml` reports whether it did.
|
|
7
9
|
// Only a fallback: the engine reports the rate it actually produced, and
|
|
8
10
|
// `outputSampleRate` (plus the LavaSR enhancer) can move it off this default.
|
|
9
11
|
const AUDIO8_SAMPLE_RATE = 44100;
|
|
@@ -35,7 +37,9 @@ try {
|
|
|
35
37
|
});
|
|
36
38
|
const audioBuffer = await result.buffer;
|
|
37
39
|
const sampleRate = (await result.sampleRate) ?? AUDIO8_SAMPLE_RATE;
|
|
40
|
+
const stats = await result.stats;
|
|
38
41
|
console.log(`▸ TTS complete. Total samples: ${audioBuffer.length}`);
|
|
42
|
+
console.log(`▸ Codec ran on ${stats?.codecOnCoreml === 1 ? 'Core ML' : 'ggml'}`);
|
|
39
43
|
console.log('▸ Saving audio to file...');
|
|
40
44
|
createWav(audioBuffer, sampleRate, 'audio8-output.wav');
|
|
41
45
|
console.log('▸ Audio saved to audio8-output.wav');
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { loadModel, textToSpeech, unloadModel, TTS_CODEC_DECODER_MOSS_TTS_F16, TTS_CODEC_ENCODER_MOSS_TTS_F16 } from '@qvac/sdk';
|
|
2
|
+
import { createWav, playAudio, int16ArrayToBuffer, createWavHeader } from './utils.js';
|
|
3
|
+
// MOSS-TTSD (GGML): multi-speaker dialogue in one pass, each voice cloned from
|
|
4
|
+
// a 24 kHz reference recording. The model continues the references, so the
|
|
5
|
+
// text opens with what each recording says under its speaker tag, followed by
|
|
6
|
+
// the lines to generate; only the new lines come out as audio. A dialogue is
|
|
7
|
+
// never split into sentences, so `stream: true` delivers the native chunks of
|
|
8
|
+
// the whole conversation (set `streamChunkTokens` to get them while it is
|
|
9
|
+
// still being generated).
|
|
10
|
+
//
|
|
11
|
+
// The MOSS-TTSD backbone is not in the QVAC Registry yet, so it is a local
|
|
12
|
+
// file; the codec halves it shares with MOSS-TTS download from the registry.
|
|
13
|
+
// Usage: node moss-dialogue.ts <moss-ttsd.gguf> <s1.wav> "<s1 transcript>" <s2.wav> "<s2 transcript>"
|
|
14
|
+
const [backbone, s1Audio, s1Text, s2Audio, s2Text] = process.argv.slice(2);
|
|
15
|
+
if (!backbone || !s1Audio || !s1Text || !s2Audio || !s2Text) {
|
|
16
|
+
console.error('Usage: node moss-dialogue.ts <moss-ttsd.gguf> <s1.wav> "<s1 transcript>" <s2.wav> "<s2 transcript>"');
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}
|
|
19
|
+
// Only a fallback: the engine reports the rate it actually produced.
|
|
20
|
+
const MOSS_SAMPLE_RATE = 24000;
|
|
21
|
+
try {
|
|
22
|
+
const modelId = await loadModel({
|
|
23
|
+
modelSrc: backbone,
|
|
24
|
+
modelType: 'tts-ggml',
|
|
25
|
+
modelConfig: {
|
|
26
|
+
ttsEngine: 'moss',
|
|
27
|
+
language: 'en',
|
|
28
|
+
mossCodecDecoderModelSrc: TTS_CODEC_DECODER_MOSS_TTS_F16,
|
|
29
|
+
mossCodecEncoderModelSrc: TTS_CODEC_ENCODER_MOSS_TTS_F16,
|
|
30
|
+
// One recording per speaker, in the order the text tags them.
|
|
31
|
+
dialogueReferenceSrcs: [s1Audio, s2Audio],
|
|
32
|
+
streamChunkTokens: 25,
|
|
33
|
+
useGPU: true
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
console.log(`▸ Model loaded: ${modelId}`);
|
|
37
|
+
console.log('▸ Synthesizing the dialogue...');
|
|
38
|
+
const result = textToSpeech({
|
|
39
|
+
modelId,
|
|
40
|
+
text: `[S1] ${s1Text} [S2] ${s2Text} ` +
|
|
41
|
+
'[S1] Did the build finish? [S2] Yes, every test passed. [S1] Great, ship it.',
|
|
42
|
+
stream: true
|
|
43
|
+
});
|
|
44
|
+
const samples = [];
|
|
45
|
+
for await (const sample of result.bufferStream)
|
|
46
|
+
samples.push(sample);
|
|
47
|
+
const sampleRate = (await result.sampleRate) ?? MOSS_SAMPLE_RATE;
|
|
48
|
+
console.log(`▸ Dialogue complete: ${samples.length} samples`);
|
|
49
|
+
createWav(samples, sampleRate, 'moss-dialogue-output.wav');
|
|
50
|
+
console.log('▸ Audio saved to moss-dialogue-output.wav');
|
|
51
|
+
console.log('▸ Playing audio...');
|
|
52
|
+
const audioData = int16ArrayToBuffer(samples);
|
|
53
|
+
const wavBuffer = Buffer.concat([createWavHeader(audioData.length, sampleRate), audioData]);
|
|
54
|
+
playAudio(wavBuffer);
|
|
55
|
+
console.log('▸ Audio playback complete');
|
|
56
|
+
await unloadModel({ modelId });
|
|
57
|
+
console.log('▸ Model unloaded');
|
|
58
|
+
process.exit(0);
|
|
59
|
+
}
|
|
60
|
+
catch (error) {
|
|
61
|
+
console.error('✖', error);
|
|
62
|
+
process.exit(1);
|
|
63
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { loadModel, textToSpeech, unloadModel, TTS_DELAY_LLM_MOSS_TTS_F16, TTS_CODEC_DECODER_MOSS_TTS_F16, TTS_CODEC_ENCODER_MOSS_TTS_F16 } from '@qvac/sdk';
|
|
2
|
+
import { createWav, playAudio, int16ArrayToBuffer, createWavHeader } from './utils.js';
|
|
3
|
+
// MOSS (GGML): OpenMOSS MOSS-TTS v1.5 Delay, an 8B backbone (about 17 GB)
|
|
4
|
+
// plus a codec, so desktop only. The primary modelSrc is the backbone; the
|
|
5
|
+
// codec decoder loads via modelConfig, and the codec encoder is only needed to
|
|
6
|
+
// clone a voice from a 24 kHz reference recording (no transcript needed).
|
|
7
|
+
//
|
|
8
|
+
// MOSS streams natively: with `streamChunkTokens` set, `stream: true` emits a
|
|
9
|
+
// chunk every that-many codec frames (12.5 per second) while the backbone is
|
|
10
|
+
// still generating, and the text is synthesized as one utterance, so the
|
|
11
|
+
// `[pause 1.0s]` marker below and `durationTokens` apply to all of it.
|
|
12
|
+
//
|
|
13
|
+
// Uses registry model constants — downloads automatically from QVAC Registry.
|
|
14
|
+
// Usage: node moss.ts [referenceAudio.wav]
|
|
15
|
+
const [referenceAudioSrc] = process.argv.slice(2);
|
|
16
|
+
// Only a fallback: the engine reports the rate it actually produced.
|
|
17
|
+
const MOSS_SAMPLE_RATE = 24000;
|
|
18
|
+
try {
|
|
19
|
+
const modelId = await loadModel({
|
|
20
|
+
modelSrc: TTS_DELAY_LLM_MOSS_TTS_F16,
|
|
21
|
+
modelConfig: {
|
|
22
|
+
ttsEngine: 'moss',
|
|
23
|
+
language: 'en',
|
|
24
|
+
mossCodecDecoderModelSrc: TTS_CODEC_DECODER_MOSS_TTS_F16,
|
|
25
|
+
...(referenceAudioSrc
|
|
26
|
+
? { mossCodecEncoderModelSrc: TTS_CODEC_ENCODER_MOSS_TTS_F16, referenceAudioSrc }
|
|
27
|
+
: {}),
|
|
28
|
+
streamChunkTokens: 25,
|
|
29
|
+
useGPU: true,
|
|
30
|
+
seed: 7
|
|
31
|
+
},
|
|
32
|
+
onProgress: (p) => {
|
|
33
|
+
const mb = (n) => (n / 1e6).toFixed(1);
|
|
34
|
+
const line = `▸ Downloading ${p.percentage.toFixed(0)}% (${mb(p.downloaded)}/${mb(p.total)} MB)`;
|
|
35
|
+
process.stderr.write(process.stderr.isTTY ? `\r${line}` : `${line}\n`);
|
|
36
|
+
if (p.percentage >= 100)
|
|
37
|
+
process.stderr.write('\n');
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
console.log(`▸ Model loaded: ${modelId}`);
|
|
41
|
+
console.log('▸ Streaming MOSS Text-to-Speech...');
|
|
42
|
+
const started = Date.now();
|
|
43
|
+
const result = textToSpeech({
|
|
44
|
+
modelId,
|
|
45
|
+
text: 'Hold on [pause 1.0s] here it comes: speech that starts playing while the rest is still being generated.',
|
|
46
|
+
stream: true
|
|
47
|
+
});
|
|
48
|
+
const samples = [];
|
|
49
|
+
for await (const sample of result.bufferStream) {
|
|
50
|
+
if (samples.length === 0)
|
|
51
|
+
console.log(`▸ First audio after ${Date.now() - started} ms`);
|
|
52
|
+
samples.push(sample);
|
|
53
|
+
}
|
|
54
|
+
const sampleRate = (await result.sampleRate) ?? MOSS_SAMPLE_RATE;
|
|
55
|
+
const stats = await result.stats;
|
|
56
|
+
console.log(`▸ TTS complete: ${samples.length} samples, ${stats?.generatedFrames ?? '?'} codec frames, RTF ${stats?.realTimeFactor?.toFixed(2) ?? '?'}`);
|
|
57
|
+
createWav(samples, sampleRate, 'moss-output.wav');
|
|
58
|
+
console.log('▸ Audio saved to moss-output.wav');
|
|
59
|
+
console.log('▸ Playing audio...');
|
|
60
|
+
const audioData = int16ArrayToBuffer(samples);
|
|
61
|
+
const wavBuffer = Buffer.concat([createWavHeader(audioData.length, sampleRate), audioData]);
|
|
62
|
+
playAudio(wavBuffer);
|
|
63
|
+
console.log('▸ Audio playback complete');
|
|
64
|
+
await unloadModel({ modelId });
|
|
65
|
+
console.log('▸ Model unloaded');
|
|
66
|
+
process.exit(0);
|
|
67
|
+
}
|
|
68
|
+
catch (error) {
|
|
69
|
+
console.error('✖', error);
|
|
70
|
+
process.exit(1);
|
|
71
|
+
}
|
|
@@ -20,7 +20,7 @@ export declare function buildContract(): {
|
|
|
20
20
|
condition: string;
|
|
21
21
|
responseSchema: string;
|
|
22
22
|
};
|
|
23
|
-
name: "audioGenStream" | "audioEditStream" | "audioUnderstand" | "heartbeat" | "loadModel" | "downloadAsset" | "completionStream" | "completionOrchestrate" | "batchCompletionStream" | "unloadModel" | "transcribe" | "transcribeStream" | "bciTranscribe" | "bciTranscribeStream" | "loggingStream" | "embed" | "translate" | "textToSpeech" | "textToSpeechStream" | "cancel" | "finetune" | "rag" | "vectorIndex" | "deleteCache" | "getModelInfo" | "getLoadedModelInfo" | "getSystemResources" | "assessModelFit" | "ocrStream" | "diffusionStream" | "videoStream" | "upscaleStream" | "worldSceneStream" | "worldStepStream" | "resume" | "pluginInvoke" | "pluginInvokeStream" | "modelRegistryList" | "modelRegistrySearch" | "modelRegistryGetModel" | "suspend" | "state" | "classify";
|
|
23
|
+
name: "audioGenStream" | "audioEditStream" | "audioUnderstand" | "startRpcServer" | "stopRpcServer" | "discoverRpcServers" | "heartbeat" | "loadModel" | "downloadAsset" | "completionStream" | "completionOrchestrate" | "batchCompletionStream" | "unloadModel" | "transcribe" | "transcribeStream" | "bciTranscribe" | "bciTranscribeStream" | "loggingStream" | "embed" | "translate" | "textToSpeech" | "textToSpeechStream" | "cancel" | "finetune" | "rag" | "vectorIndex" | "deleteCache" | "getModelInfo" | "getLoadedModelInfo" | "getSystemResources" | "assessModelFit" | "ocrStream" | "diffusionStream" | "videoStream" | "upscaleStream" | "worldSceneStream" | "worldStepStream" | "resume" | "pluginInvoke" | "pluginInvokeStream" | "modelRegistryList" | "modelRegistrySearch" | "modelRegistryGetModel" | "suspend" | "state" | "classify";
|
|
24
24
|
callShape: "duplex" | "request-reply" | "server-stream";
|
|
25
25
|
requestSchema: string;
|
|
26
26
|
responseSchema: string;
|
|
@@ -33,6 +33,7 @@ export declare function renderContractFiles(): Promise<{
|
|
|
33
33
|
'models.json': string;
|
|
34
34
|
'model-type-maps.json': string;
|
|
35
35
|
'error-codes.json': string;
|
|
36
|
+
'numeric-constants.json': string;
|
|
36
37
|
}>;
|
|
37
38
|
export {};
|
|
38
39
|
//# sourceMappingURL=build-contract.d.ts.map
|
|
@@ -7,6 +7,7 @@ import { constantsRegistry } from './constants-registry.js';
|
|
|
7
7
|
import { buildModelsRegistry } from './build-models-registry.js';
|
|
8
8
|
import { buildModelTypeMaps } from './build-model-type-maps.js';
|
|
9
9
|
import { buildErrorCodes } from './build-error-codes.js';
|
|
10
|
+
import { buildNumericConstants } from './build-numeric-constants.js';
|
|
10
11
|
export const contractDir = new URL('../../contract/', import.meta.url);
|
|
11
12
|
export const callShapeByHandlerType = {
|
|
12
13
|
reply: 'request-reply',
|
|
@@ -610,11 +611,13 @@ export async function renderContractFiles() {
|
|
|
610
611
|
const modelsRegistry = buildModelsRegistry();
|
|
611
612
|
const modelTypeMaps = buildModelTypeMaps();
|
|
612
613
|
const errorCodes = buildErrorCodes();
|
|
614
|
+
const numericConstants = buildNumericConstants();
|
|
613
615
|
return {
|
|
614
616
|
'schema.json': await formatJson(schemaDocument, 'schema.json'),
|
|
615
617
|
'manifest.json': await formatJson(manifest, 'manifest.json'),
|
|
616
618
|
'models.json': await formatJson(modelsRegistry, 'models.json'),
|
|
617
619
|
'model-type-maps.json': await formatJson(modelTypeMaps, 'model-type-maps.json'),
|
|
618
|
-
'error-codes.json': await formatJson(errorCodes, 'error-codes.json')
|
|
620
|
+
'error-codes.json': await formatJson(errorCodes, 'error-codes.json'),
|
|
621
|
+
'numeric-constants.json': await formatJson(numericConstants, 'numeric-constants.json')
|
|
619
622
|
};
|
|
620
623
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JSON export of the SDK's numeric public vocabularies (name → number).
|
|
3
|
+
*
|
|
4
|
+
* `constants-registry.ts` emits string vocabularies only, through
|
|
5
|
+
* `enumFromVocabulary`, so numeric ones ride this artifact instead — the same
|
|
6
|
+
* way `error-codes.json` carries the numeric error registries.
|
|
7
|
+
* `sdk-python/scripts/generate.py` renders it into `_generated/numeric_constants.py`.
|
|
8
|
+
*
|
|
9
|
+
* A separate artifact from schema.json, like models.json / error-codes.json.
|
|
10
|
+
*/
|
|
11
|
+
export interface NumericConstants {
|
|
12
|
+
ASR_BACKEND_IDS: Record<string, number>;
|
|
13
|
+
}
|
|
14
|
+
export declare function buildNumericConstants(): NumericConstants;
|
|
15
|
+
//# sourceMappingURL=build-numeric-constants.d.ts.map
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { ASR_BACKEND_IDS } from '@qvac/inference/surface';
|
|
2
|
+
function sortedByValue(record) {
|
|
3
|
+
const entries = Object.entries(record);
|
|
4
|
+
entries.sort(function (a, b) {
|
|
5
|
+
return a[1] - b[1];
|
|
6
|
+
});
|
|
7
|
+
return Object.fromEntries(entries);
|
|
8
|
+
}
|
|
9
|
+
export function buildNumericConstants() {
|
|
10
|
+
return {
|
|
11
|
+
ASR_BACKEND_IDS: sortedByValue(ASR_BACKEND_IDS)
|
|
12
|
+
};
|
|
13
|
+
}
|
|
@@ -7,6 +7,9 @@
|
|
|
7
7
|
* export runs under Node, where core's native `bare-*` modules cannot load.
|
|
8
8
|
*/
|
|
9
9
|
export declare const methodShapes: {
|
|
10
|
+
readonly startRpcServer: "reply";
|
|
11
|
+
readonly stopRpcServer: "reply";
|
|
12
|
+
readonly discoverRpcServers: "reply";
|
|
10
13
|
readonly assessModelFit: "reply";
|
|
11
14
|
readonly audioEditStream: "stream";
|
|
12
15
|
readonly audioGenStream: "stream";
|
|
@@ -7,6 +7,9 @@
|
|
|
7
7
|
* export runs under Node, where core's native `bare-*` modules cannot load.
|
|
8
8
|
*/
|
|
9
9
|
export const methodShapes = {
|
|
10
|
+
startRpcServer: 'reply',
|
|
11
|
+
stopRpcServer: 'reply',
|
|
12
|
+
discoverRpcServers: 'reply',
|
|
10
13
|
assessModelFit: 'reply',
|
|
11
14
|
audioEditStream: 'stream',
|
|
12
15
|
audioGenStream: 'stream',
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
type Ranges = Record<string, string>;
|
|
2
|
+
export interface Manifest {
|
|
3
|
+
version?: string;
|
|
4
|
+
dependencies?: Ranges;
|
|
5
|
+
devDependencies?: Ranges;
|
|
6
|
+
peerDependencies?: Ranges;
|
|
7
|
+
peerDependenciesMeta?: Record<string, {
|
|
8
|
+
optional?: boolean;
|
|
9
|
+
}>;
|
|
10
|
+
engines?: Ranges;
|
|
11
|
+
}
|
|
12
|
+
export declare function checkAddonRanges(inferencePkg: Manifest, sdkPkg: Manifest): string[];
|
|
13
|
+
export declare function checkSharedMajorMinor(sdkPkg: Manifest): string[];
|
|
14
|
+
export declare function checkBareRuntimeRange(inferencePkg: Manifest, sdkPkg: Manifest): string[];
|
|
15
|
+
export declare function collectVersionFailures(inferencePkg: Manifest, sdkPkg: Manifest): string[];
|
|
16
|
+
export declare function runEnforceInferenceVersionsCli(): void;
|
|
17
|
+
export {};
|
|
18
|
+
//# sourceMappingURL=enforce-inference-versions.d.ts.map
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
// The version rules that tie @qvac/sdk's manifest to @qvac/inference. All are
|
|
2
|
+
// checked here and every failure is reported in one run.
|
|
3
|
+
//
|
|
4
|
+
// 1. Addon ranges. @qvac/inference's peerDependencies is the source of truth for
|
|
5
|
+
// the inference addons. For every addon p in it, inference's
|
|
6
|
+
// devDependencies.p and the SDK's dependencies.p must carry the identical
|
|
7
|
+
// range. Opt-in services may instead use an optional SDK peer plus a dev dependency.
|
|
8
|
+
//
|
|
9
|
+
// 2. Shared major.minor. @qvac/sdk and @qvac/inference expose the same API, so
|
|
10
|
+
// the major and minor of the SDK's own version must equal the major and minor
|
|
11
|
+
// of the @qvac/inference range it depends on. Patch numbers are free on both
|
|
12
|
+
// sides, so @qvac/sdk 0.19.4 may depend on @qvac/inference ^0.19.2. The range
|
|
13
|
+
// operator must hold the dependency inside that one major.minor: a caret does
|
|
14
|
+
// below 1.0.0 (^0.19.0 is >=0.19.0 <0.20.0) but not from 1.0.0 up (^1.19.0
|
|
15
|
+
// also allows 1.20.0), so a tilde is required there.
|
|
16
|
+
//
|
|
17
|
+
// 3. Bare runtime. Every version allowed by the SDK's bare-runtime dependency
|
|
18
|
+
// must satisfy inference's engines.bare requirement, including versions
|
|
19
|
+
// retained by a consumer's existing lockfile.
|
|
20
|
+
//
|
|
21
|
+
// The second rule reads @qvac/sdk's manifest alone and never
|
|
22
|
+
// packages/inference's version. The two move independently between releases —
|
|
23
|
+
// the engine is published first and the SDK follows — so comparing them would
|
|
24
|
+
// fail main for as long as that takes.
|
|
25
|
+
//
|
|
26
|
+
// The checks take manifests rather than reading them, so test/enforce-inference-
|
|
27
|
+
// versions.test.ts can drive them over manifest shapes the repo does not hold.
|
|
28
|
+
import { readFileSync } from 'fs';
|
|
29
|
+
import { join, resolve, dirname } from 'path';
|
|
30
|
+
import { fileURLToPath } from 'url';
|
|
31
|
+
import { subset, validRange } from 'semver';
|
|
32
|
+
const dependency = '@qvac/inference';
|
|
33
|
+
const sdkManifest = 'packages/sdk/package.json';
|
|
34
|
+
export function checkAddonRanges(inferencePkg, sdkPkg) {
|
|
35
|
+
const peers = inferencePkg.peerDependencies ?? {};
|
|
36
|
+
const inferenceDev = inferencePkg.devDependencies ?? {};
|
|
37
|
+
const sdkDeps = sdkPkg.dependencies ?? {};
|
|
38
|
+
const drifts = [];
|
|
39
|
+
for (const [name, peer] of Object.entries(peers)) {
|
|
40
|
+
const dev = inferenceDev[name];
|
|
41
|
+
const optionalPeer = sdkPkg.peerDependenciesMeta?.[name]?.optional === true;
|
|
42
|
+
const dep = sdkDeps[name] ?? (optionalPeer ? sdkPkg.peerDependencies?.[name] : undefined);
|
|
43
|
+
const mismatches = [];
|
|
44
|
+
if (dep !== peer)
|
|
45
|
+
mismatches.push(dep === undefined ? 'SDK is missing it' : `SDK has ${dep}`);
|
|
46
|
+
if (sdkDeps[name] === undefined && optionalPeer && sdkPkg.devDependencies?.[name] !== peer) {
|
|
47
|
+
mismatches.push('SDK devDependencies must match its optional peer');
|
|
48
|
+
}
|
|
49
|
+
if (dev !== peer) {
|
|
50
|
+
mismatches.push(dev === undefined
|
|
51
|
+
? 'inference devDependencies is missing it'
|
|
52
|
+
: `inference devDependencies has ${dev}`);
|
|
53
|
+
}
|
|
54
|
+
if (mismatches.length > 0) {
|
|
55
|
+
drifts.push(`${name}: inference has it as ${peer} in peerDependencies, but ${mismatches.join(' and ')}.`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
if (drifts.length === 0)
|
|
59
|
+
return [];
|
|
60
|
+
return [
|
|
61
|
+
`${drifts.join('\n')}\n\nPlease ensure the SDK dependencies and inference devDependencies use the same addon versions as inference's peerDependencies.`
|
|
62
|
+
];
|
|
63
|
+
}
|
|
64
|
+
export function checkSharedMajorMinor(sdkPkg) {
|
|
65
|
+
const rawVersion = sdkPkg.version;
|
|
66
|
+
const rawRange = sdkPkg.dependencies?.[dependency];
|
|
67
|
+
if (rawVersion === undefined)
|
|
68
|
+
return [`${sdkManifest} declares no version.`];
|
|
69
|
+
if (rawRange === undefined)
|
|
70
|
+
return [`${sdkManifest} declares no ${dependency} dependency.`];
|
|
71
|
+
// Specs that point somewhere other than the published package: the workspace
|
|
72
|
+
// link used for development and the pod checks (`sdk-source:workspace` writes
|
|
73
|
+
// `file:../inference`), and the npm alias the GPR dev build pins. They carry no
|
|
74
|
+
// major.minor to compare, so there is nothing to check.
|
|
75
|
+
const localPrefixes = ['file:', 'link:', 'npm:'];
|
|
76
|
+
const localSpec = localPrefixes.find(function (prefix) {
|
|
77
|
+
return rawRange.startsWith(prefix);
|
|
78
|
+
});
|
|
79
|
+
if (localSpec !== undefined) {
|
|
80
|
+
console.log(`${dependency} points at "${rawRange}", not a published version; skipping the major.minor check.`);
|
|
81
|
+
return [];
|
|
82
|
+
}
|
|
83
|
+
const versionMatch = /^(\d+)\.(\d+)\.\d+$/.exec(rawVersion);
|
|
84
|
+
if (versionMatch === null) {
|
|
85
|
+
return [`@qvac/sdk version "${rawVersion}" is not a plain x.y.z version.`];
|
|
86
|
+
}
|
|
87
|
+
const sdkMajorMinor = `${versionMatch[1]}.${versionMatch[2]}`;
|
|
88
|
+
const rangeMatch = /^([\^~])(\d+)\.(\d+)\.\d+$/.exec(rawRange);
|
|
89
|
+
if (rangeMatch === null) {
|
|
90
|
+
return [
|
|
91
|
+
`${dependency} is declared as "${rawRange}", which this check cannot read.\n` +
|
|
92
|
+
`Write it as one caret or tilde range over an exact version, e.g. "^${sdkMajorMinor}.0".`
|
|
93
|
+
];
|
|
94
|
+
}
|
|
95
|
+
const operator = rangeMatch[1];
|
|
96
|
+
const rangeMajorMinor = `${rangeMatch[2]}.${rangeMatch[3]}`;
|
|
97
|
+
if (sdkMajorMinor !== rangeMajorMinor) {
|
|
98
|
+
return [
|
|
99
|
+
`Version mismatch: @qvac/sdk is ${rawVersion} (major.minor ${sdkMajorMinor}) but depends ` +
|
|
100
|
+
`on ${dependency} "${rawRange}" (major.minor ${rangeMajorMinor}).\n\n` +
|
|
101
|
+
`The two ship the same API and must share a major.minor. Either set the range to ` +
|
|
102
|
+
`${sdkMajorMinor}, or move @qvac/sdk to ${rangeMajorMinor}.0 — publishing ` +
|
|
103
|
+
`@qvac/inference ${rangeMajorMinor}.0 first, so the range resolves from npm.`
|
|
104
|
+
];
|
|
105
|
+
}
|
|
106
|
+
if (Number(rangeMatch[2]) >= 1 && operator !== '~') {
|
|
107
|
+
return [
|
|
108
|
+
`${dependency} is declared as "${rawRange}". From 1.0.0 up, a caret range also allows the ` +
|
|
109
|
+
`next minor, which is a different API. Use "~${rawRange.slice(1)}".`
|
|
110
|
+
];
|
|
111
|
+
}
|
|
112
|
+
return [];
|
|
113
|
+
}
|
|
114
|
+
export function checkBareRuntimeRange(inferencePkg, sdkPkg) {
|
|
115
|
+
const required = inferencePkg.engines?.['bare'];
|
|
116
|
+
if (required === undefined)
|
|
117
|
+
return [];
|
|
118
|
+
if (validRange(required) === null) {
|
|
119
|
+
return [`@qvac/inference engines.bare "${required}" is not a valid semver range.`];
|
|
120
|
+
}
|
|
121
|
+
const runtime = sdkPkg.dependencies?.['bare-runtime'];
|
|
122
|
+
if (runtime === undefined || validRange(runtime) === null) {
|
|
123
|
+
return [`${sdkManifest} must declare a valid bare-runtime dependency range.`];
|
|
124
|
+
}
|
|
125
|
+
if (!subset(runtime, required)) {
|
|
126
|
+
return [
|
|
127
|
+
`SDK bare-runtime "${runtime}" permits versions outside inference's engines.bare "${required}". ` +
|
|
128
|
+
'Align the runtime dependency so an existing consumer lockfile cannot retain an unsupported worker.'
|
|
129
|
+
];
|
|
130
|
+
}
|
|
131
|
+
return [];
|
|
132
|
+
}
|
|
133
|
+
export function collectVersionFailures(inferencePkg, sdkPkg) {
|
|
134
|
+
return [
|
|
135
|
+
...checkAddonRanges(inferencePkg, sdkPkg),
|
|
136
|
+
...checkSharedMajorMinor(sdkPkg),
|
|
137
|
+
...checkBareRuntimeRange(inferencePkg, sdkPkg)
|
|
138
|
+
];
|
|
139
|
+
}
|
|
140
|
+
function readManifest(dir) {
|
|
141
|
+
return JSON.parse(readFileSync(join(dir, 'package.json'), 'utf8'));
|
|
142
|
+
}
|
|
143
|
+
export function runEnforceInferenceVersionsCli() {
|
|
144
|
+
const sdkDir = resolve(dirname(fileURLToPath(import.meta.url)), '..');
|
|
145
|
+
const inferenceDir = resolve(sdkDir, '..', 'inference');
|
|
146
|
+
const failures = collectVersionFailures(readManifest(inferenceDir), readManifest(sdkDir));
|
|
147
|
+
if (failures.length > 0) {
|
|
148
|
+
console.error(failures.join('\n\n'));
|
|
149
|
+
process.exit(1);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
function isMainModule() {
|
|
153
|
+
const entryPath = process.argv[1];
|
|
154
|
+
return entryPath !== undefined && resolve(entryPath) === fileURLToPath(import.meta.url);
|
|
155
|
+
}
|
|
156
|
+
if (isMainModule()) {
|
|
157
|
+
runEnforceInferenceVersionsCli();
|
|
158
|
+
}
|