@datalayer/agent-runtimes 1.5.28 → 1.5.31
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/dist-embed/chunks/{ChatView-C0PWJ_wj.js → ChatView-C_NOLqwI.js} +3 -3
- package/dist-embed/datalayer-app-main.js +83 -83
- package/lib/apps/apps/appspec.d.ts +0 -5
- package/lib/apps/apps/appspec.js +1 -6
- package/lib/apps/apps/checks.js +13 -9
- package/lib/apps/embed/element.js +14 -3
- package/lib/apps/embed/embedConfig.d.ts +1 -1
- package/lib/apps/embed/embedConfig.js +1 -1
- package/lib/apps/embed/visitorApp.d.ts +9 -2
- package/lib/apps/embed/visitorApp.js +9 -2
- package/lib/apps/plugins/chat/ChatView.js +4 -1
- package/lib/chat/base/ChatBase.js +12 -3
- package/lib/runtimes/variants.d.ts +42 -37
- package/lib/runtimes/variants.js +109 -9
- package/lib/specs/appspecSchema.js +2 -2
- package/lib/specs/envvars.d.ts +4 -0
- package/lib/specs/envvars.js +44 -0
- package/lib/specs/harnesses.d.ts +22 -0
- package/lib/specs/harnesses.js +372 -0
- package/lib/specs/index.d.ts +1 -0
- package/lib/specs/index.js +1 -0
- package/lib/specs/voices.js +42 -0
- package/lib/types/agentspecs.d.ts +70 -1
- package/lib/voice/ComposerVoice.d.ts +10 -2
- package/lib/voice/ComposerVoice.js +10 -2
- package/lib/voice/HandsFree.d.ts +55 -0
- package/lib/voice/HandsFree.js +211 -0
- package/lib/voice/chatVoice.d.ts +2 -1
- package/lib/voice/chatVoice.js +5 -3
- package/lib/voice/handsFree.d.ts +58 -0
- package/lib/voice/handsFree.js +48 -0
- package/lib/voice/index.d.ts +10 -2
- package/lib/voice/index.js +2 -0
- package/package.json +1 -1
- package/scripts/codegen/generate_agents.py +56 -0
- package/scripts/codegen/generate_envvars.py +3 -2
- package/scripts/codegen/generate_harnesses.py +329 -0
- package/scripts/voice/measure.py +74 -0
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# Copyright (c) 2025-2026 Datalayer, Inc.
|
|
3
|
+
# Distributed under the terms of the Modified BSD License.
|
|
4
|
+
|
|
5
|
+
"""
|
|
6
|
+
Generate Python and TypeScript code from the YAML harness specifications.
|
|
7
|
+
|
|
8
|
+
One YAML per harness under ``agentspecs/harnesses`` (SHIELD.md SH-01, §3.1):
|
|
9
|
+
an agent program driven over the Agent Client Protocol, how it is launched,
|
|
10
|
+
what it needs, what it honours of an agent spec, and the terms it is served
|
|
11
|
+
under. ``register.yaml`` beside them is the register (SH-04, §3.3): a harness
|
|
12
|
+
without an entry is refused here as it is in agentspecs.
|
|
13
|
+
|
|
14
|
+
Usage:
|
|
15
|
+
python generate_harnesses.py \\
|
|
16
|
+
--specs-dir agentspecs/agentspecs/harnesses \\
|
|
17
|
+
--python-output agent_runtimes/specs/harnesses.py \\
|
|
18
|
+
--typescript-output src/specs/harnesses.ts
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
import argparse
|
|
22
|
+
import json
|
|
23
|
+
from pathlib import Path
|
|
24
|
+
from typing import Any
|
|
25
|
+
|
|
26
|
+
import yaml
|
|
27
|
+
from versioning import ensure_spec_version, version_suffix
|
|
28
|
+
|
|
29
|
+
MCP = ("lend", "own", "none")
|
|
30
|
+
THIRD_PARTIES = ("api_key_only", "permitted", "refused", "organizations_own")
|
|
31
|
+
SKILLS_FORMATS = ("skill-md", "none")
|
|
32
|
+
REGISTER = "register.yaml"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def load_register(specs_dir: Path) -> dict[str, dict[str, Any]]:
|
|
36
|
+
"""The register, by harness id."""
|
|
37
|
+
register = yaml.safe_load((specs_dir / REGISTER).read_text()) or {}
|
|
38
|
+
return {str(entry["harness"]): entry for entry in register.get("entries") or []}
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def load_harness_specs(specs_dir: Path) -> list[dict[str, Any]]:
|
|
42
|
+
"""Every harness spec of a directory, each named for its id, registered, its enums known."""
|
|
43
|
+
register = load_register(specs_dir)
|
|
44
|
+
specs = []
|
|
45
|
+
for yaml_file in sorted(specs_dir.glob("*.yaml")):
|
|
46
|
+
if yaml_file.name == REGISTER:
|
|
47
|
+
continue
|
|
48
|
+
spec = yaml.safe_load(yaml_file.read_text()) or {}
|
|
49
|
+
ensure_spec_version(spec)
|
|
50
|
+
if spec["id"] != yaml_file.stem:
|
|
51
|
+
raise ValueError(
|
|
52
|
+
f"{yaml_file.name}: the file is named for {yaml_file.stem!r}, the spec says {spec['id']!r}"
|
|
53
|
+
)
|
|
54
|
+
entry = register.get(spec["id"])
|
|
55
|
+
if entry is None:
|
|
56
|
+
raise ValueError(
|
|
57
|
+
f"{yaml_file.name}: the harness {spec['id']!r} has no entry in the register"
|
|
58
|
+
)
|
|
59
|
+
acp = spec.get("acp")
|
|
60
|
+
if spec["id"] == "custom":
|
|
61
|
+
if acp is not None:
|
|
62
|
+
raise ValueError(f"{yaml_file.name}: custom names no command")
|
|
63
|
+
elif not acp or not acp.get("command"):
|
|
64
|
+
raise ValueError(f"{yaml_file.name}: an acp block with its command")
|
|
65
|
+
if acp is not None and acp.get("mcp") not in MCP:
|
|
66
|
+
raise ValueError(f"{yaml_file.name}: acp.mcp one of {MCP}")
|
|
67
|
+
terms = spec.get("terms") or {}
|
|
68
|
+
if terms.get("served_to_third_parties") not in THIRD_PARTIES:
|
|
69
|
+
raise ValueError(
|
|
70
|
+
f"{yaml_file.name}: terms.served_to_third_parties one of {THIRD_PARTIES}"
|
|
71
|
+
)
|
|
72
|
+
if terms.get("served_to_third_parties") != entry.get("served_to_third_parties"):
|
|
73
|
+
raise ValueError(
|
|
74
|
+
f"{yaml_file.name}: the register and the spec disagree on third parties"
|
|
75
|
+
)
|
|
76
|
+
skills = spec.get("skills") or {}
|
|
77
|
+
if skills.get("format") not in SKILLS_FORMATS:
|
|
78
|
+
raise ValueError(f"{yaml_file.name}: skills.format one of {SKILLS_FORMATS}")
|
|
79
|
+
spec["_register"] = entry
|
|
80
|
+
specs.append(spec)
|
|
81
|
+
return specs
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def _const(spec: dict[str, Any]) -> str:
|
|
85
|
+
return (
|
|
86
|
+
spec["id"].replace("-", "_").upper()
|
|
87
|
+
+ "_HARNESS"
|
|
88
|
+
+ version_suffix(spec["version"])
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _s(value: Any) -> str:
|
|
93
|
+
return json.dumps(" ".join(str(value or "").split()), ensure_ascii=False)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def _opt(value: Any) -> str:
|
|
97
|
+
return _s(value) if value else "None"
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def generate_python_code(specs: list[dict[str, Any]]) -> str:
|
|
101
|
+
lines = [
|
|
102
|
+
"# Copyright (c) 2025-2026 Datalayer, Inc.",
|
|
103
|
+
"# Distributed under the terms of the Modified BSD License.",
|
|
104
|
+
'"""',
|
|
105
|
+
"Harness Catalog.",
|
|
106
|
+
"",
|
|
107
|
+
"An agent program driven over the Agent Client Protocol (SHIELD.md SH-01).",
|
|
108
|
+
"",
|
|
109
|
+
"This file is AUTO-GENERATED from YAML specifications.",
|
|
110
|
+
"DO NOT EDIT MANUALLY - run 'make specs' to regenerate.",
|
|
111
|
+
'"""',
|
|
112
|
+
"",
|
|
113
|
+
"from typing import Dict, List, Optional",
|
|
114
|
+
"",
|
|
115
|
+
"from agent_runtimes.types import (",
|
|
116
|
+
" HarnessAcpSpec,",
|
|
117
|
+
" HarnessHonoursSpec,",
|
|
118
|
+
" HarnessRegisterEntry,",
|
|
119
|
+
" HarnessRequiresSpec,",
|
|
120
|
+
" HarnessSkillsSpec,",
|
|
121
|
+
" HarnessSpec,",
|
|
122
|
+
" HarnessTermsSpec,",
|
|
123
|
+
")",
|
|
124
|
+
]
|
|
125
|
+
for spec in specs:
|
|
126
|
+
acp = spec.get("acp")
|
|
127
|
+
requires = spec.get("requires") or {}
|
|
128
|
+
terms = spec.get("terms") or {}
|
|
129
|
+
skills = spec.get("skills") or {}
|
|
130
|
+
honours = spec.get("honours") or {}
|
|
131
|
+
entry = spec["_register"]
|
|
132
|
+
lines += [
|
|
133
|
+
"",
|
|
134
|
+
f"{_const(spec)} = HarnessSpec(",
|
|
135
|
+
f" id={_s(spec['id'])},",
|
|
136
|
+
f" version={_s(spec['version'])},",
|
|
137
|
+
f" name={_s(spec['name'])},",
|
|
138
|
+
f" description={_s(spec.get('description'))},",
|
|
139
|
+
f" enabled={bool(spec.get('enabled', True))},",
|
|
140
|
+
f" tags={json.dumps(list(spec.get('tags') or []))},",
|
|
141
|
+
f" docs={_s(spec.get('docs'))},",
|
|
142
|
+
]
|
|
143
|
+
if acp is None:
|
|
144
|
+
lines.append(" acp=None,")
|
|
145
|
+
else:
|
|
146
|
+
lines += [
|
|
147
|
+
" acp=HarnessAcpSpec(",
|
|
148
|
+
f" command={_s(acp['command'])},",
|
|
149
|
+
f" args={json.dumps(list(acp.get('args') or []))},",
|
|
150
|
+
f" protocol_version={int(acp.get('protocol_version', 1))},",
|
|
151
|
+
f" load_session={bool(acp.get('load_session', False))},",
|
|
152
|
+
f" mcp={_s(acp['mcp'])},",
|
|
153
|
+
f" modes={json.dumps(list(acp.get('modes') or []))},",
|
|
154
|
+
f" env={json.dumps(dict(acp.get('env') or {}))},",
|
|
155
|
+
" ),",
|
|
156
|
+
]
|
|
157
|
+
lines += [
|
|
158
|
+
" requires=HarnessRequiresSpec(",
|
|
159
|
+
f" secrets={json.dumps(list(requires.get('secrets') or []))},",
|
|
160
|
+
f" binaries={json.dumps(list(requires.get('binaries') or []))},",
|
|
161
|
+
" ),",
|
|
162
|
+
" terms=HarnessTermsSpec(",
|
|
163
|
+
f" served_to_third_parties={_s(terms['served_to_third_parties'])},",
|
|
164
|
+
f" licence={_s(terms.get('licence'))},",
|
|
165
|
+
f" tier_restrictions={_opt(terms.get('tier_restrictions'))},",
|
|
166
|
+
" ),",
|
|
167
|
+
" skills=HarnessSkillsSpec(",
|
|
168
|
+
f" format={_s(skills['format'])},",
|
|
169
|
+
f" dir={_s(skills.get('dir'))},",
|
|
170
|
+
" ),",
|
|
171
|
+
" honours=HarnessHonoursSpec(",
|
|
172
|
+
f" model={bool(honours.get('model', False))},",
|
|
173
|
+
f" subagents={bool(honours.get('subagents', False))},",
|
|
174
|
+
" ),",
|
|
175
|
+
f" instructions_file={_s(spec.get('instructions_file'))},",
|
|
176
|
+
" registered=HarnessRegisterEntry(",
|
|
177
|
+
f" adapter={_s(entry.get('adapter'))},",
|
|
178
|
+
f" version={_s(entry.get('version'))},",
|
|
179
|
+
f" licence_source={_s(entry.get('licence_source'))},",
|
|
180
|
+
f" terms_source={_s(entry.get('terms_source'))},",
|
|
181
|
+
f" credential={_s(entry.get('credential'))},",
|
|
182
|
+
f" binaries={_s(entry.get('binaries'))},",
|
|
183
|
+
f" note={_s(entry.get('note'))},",
|
|
184
|
+
" ),",
|
|
185
|
+
")",
|
|
186
|
+
]
|
|
187
|
+
lines += [
|
|
188
|
+
"",
|
|
189
|
+
"",
|
|
190
|
+
"HARNESS_CATALOGUE: Dict[str, HarnessSpec] = {",
|
|
191
|
+
*[f" {_s(spec['id'])}: {_const(spec)}," for spec in specs],
|
|
192
|
+
"}",
|
|
193
|
+
"",
|
|
194
|
+
"",
|
|
195
|
+
"def get_harness(harness_id: str) -> Optional[HarnessSpec]:",
|
|
196
|
+
' """A harness by id, or None."""',
|
|
197
|
+
" return HARNESS_CATALOGUE.get(harness_id)",
|
|
198
|
+
"",
|
|
199
|
+
"",
|
|
200
|
+
"def list_harnesses() -> List[HarnessSpec]:",
|
|
201
|
+
' """Every harness of the catalogue."""',
|
|
202
|
+
" return list(HARNESS_CATALOGUE.values())",
|
|
203
|
+
"",
|
|
204
|
+
]
|
|
205
|
+
return "\n".join(lines)
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def _t(value: Any) -> str:
|
|
209
|
+
return json.dumps(" ".join(str(value or "").split()), ensure_ascii=False)
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def generate_typescript_code(specs: list[dict[str, Any]]) -> str:
|
|
213
|
+
lines = [
|
|
214
|
+
"/*",
|
|
215
|
+
" * Copyright (c) 2025-2026 Datalayer, Inc.",
|
|
216
|
+
" * Distributed under the terms of the Modified BSD License.",
|
|
217
|
+
" */",
|
|
218
|
+
"",
|
|
219
|
+
"/**",
|
|
220
|
+
" * Harness Catalog.",
|
|
221
|
+
" *",
|
|
222
|
+
" * An agent program driven over the Agent Client Protocol (SHIELD.md SH-01).",
|
|
223
|
+
" *",
|
|
224
|
+
" * This file is AUTO-GENERATED from YAML specifications.",
|
|
225
|
+
" * DO NOT EDIT MANUALLY - run 'make specs' to regenerate.",
|
|
226
|
+
" */",
|
|
227
|
+
"",
|
|
228
|
+
"import type { HarnessSpec } from '../types/agentspecs';",
|
|
229
|
+
]
|
|
230
|
+
for spec in specs:
|
|
231
|
+
acp = spec.get("acp")
|
|
232
|
+
requires = spec.get("requires") or {}
|
|
233
|
+
terms = spec.get("terms") or {}
|
|
234
|
+
skills = spec.get("skills") or {}
|
|
235
|
+
honours = spec.get("honours") or {}
|
|
236
|
+
entry = spec["_register"]
|
|
237
|
+
lines += [
|
|
238
|
+
"",
|
|
239
|
+
f"export const {_const(spec)}: HarnessSpec = {{",
|
|
240
|
+
f" id: {_t(spec['id'])},",
|
|
241
|
+
f" version: {_t(spec['version'])},",
|
|
242
|
+
f" name: {_t(spec['name'])},",
|
|
243
|
+
f" description: {_t(spec.get('description'))},",
|
|
244
|
+
f" enabled: {json.dumps(bool(spec.get('enabled', True)))},",
|
|
245
|
+
f" tags: {json.dumps(list(spec.get('tags') or []))},",
|
|
246
|
+
f" docs: {_t(spec.get('docs'))},",
|
|
247
|
+
]
|
|
248
|
+
if acp is not None:
|
|
249
|
+
lines += [
|
|
250
|
+
" acp: {",
|
|
251
|
+
f" command: {_t(acp['command'])},",
|
|
252
|
+
f" args: {json.dumps(list(acp.get('args') or []))},",
|
|
253
|
+
f" protocolVersion: {int(acp.get('protocol_version', 1))},",
|
|
254
|
+
f" loadSession: {json.dumps(bool(acp.get('load_session', False)))},",
|
|
255
|
+
f" mcp: {_t(acp['mcp'])},",
|
|
256
|
+
f" modes: {json.dumps(list(acp.get('modes') or []))},",
|
|
257
|
+
f" env: {json.dumps(dict(acp.get('env') or {}))},",
|
|
258
|
+
" },",
|
|
259
|
+
]
|
|
260
|
+
lines += [
|
|
261
|
+
" requires: {",
|
|
262
|
+
f" secrets: {json.dumps(list(requires.get('secrets') or []))},",
|
|
263
|
+
f" binaries: {json.dumps(list(requires.get('binaries') or []))},",
|
|
264
|
+
" },",
|
|
265
|
+
" terms: {",
|
|
266
|
+
f" servedToThirdParties: {_t(terms['served_to_third_parties'])},",
|
|
267
|
+
f" licence: {_t(terms.get('licence'))},",
|
|
268
|
+
*(
|
|
269
|
+
[f" tierRestrictions: {_t(terms.get('tier_restrictions'))},"]
|
|
270
|
+
if terms.get("tier_restrictions")
|
|
271
|
+
else []
|
|
272
|
+
),
|
|
273
|
+
" },",
|
|
274
|
+
" skills: {",
|
|
275
|
+
f" format: {_t(skills['format'])},",
|
|
276
|
+
f" dir: {_t(skills.get('dir'))},",
|
|
277
|
+
" },",
|
|
278
|
+
" honours: {",
|
|
279
|
+
f" model: {json.dumps(bool(honours.get('model', False)))},",
|
|
280
|
+
f" subagents: {json.dumps(bool(honours.get('subagents', False)))},",
|
|
281
|
+
" },",
|
|
282
|
+
f" instructionsFile: {_t(spec.get('instructions_file'))},",
|
|
283
|
+
" registered: {",
|
|
284
|
+
f" adapter: {_t(entry.get('adapter'))},",
|
|
285
|
+
f" version: {_t(entry.get('version'))},",
|
|
286
|
+
f" licenceSource: {_t(entry.get('licence_source'))},",
|
|
287
|
+
f" termsSource: {_t(entry.get('terms_source'))},",
|
|
288
|
+
f" credential: {_t(entry.get('credential'))},",
|
|
289
|
+
f" binaries: {_t(entry.get('binaries'))},",
|
|
290
|
+
f" note: {_t(entry.get('note'))},",
|
|
291
|
+
" },",
|
|
292
|
+
"};",
|
|
293
|
+
]
|
|
294
|
+
lines += [
|
|
295
|
+
"",
|
|
296
|
+
"export const HARNESS_CATALOGUE: Record<string, HarnessSpec> = {",
|
|
297
|
+
*[f" {_t(spec['id'])}: {_const(spec)}," for spec in specs],
|
|
298
|
+
"};",
|
|
299
|
+
"",
|
|
300
|
+
"/** A harness by id. */",
|
|
301
|
+
"export function getHarness(harnessId: string): HarnessSpec | undefined {",
|
|
302
|
+
" return HARNESS_CATALOGUE[harnessId];",
|
|
303
|
+
"}",
|
|
304
|
+
"",
|
|
305
|
+
"/** Every harness of the catalogue. */",
|
|
306
|
+
"export function listHarnesses(): HarnessSpec[] {",
|
|
307
|
+
" return Object.values(HARNESS_CATALOGUE);",
|
|
308
|
+
"}",
|
|
309
|
+
"",
|
|
310
|
+
]
|
|
311
|
+
return "\n".join(lines)
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
def main() -> None:
|
|
315
|
+
parser = argparse.ArgumentParser(description=__doc__)
|
|
316
|
+
parser.add_argument("--specs-dir", type=Path, required=True)
|
|
317
|
+
parser.add_argument("--python-output", type=Path, required=True)
|
|
318
|
+
parser.add_argument("--typescript-output", type=Path, required=True)
|
|
319
|
+
args = parser.parse_args()
|
|
320
|
+
specs = load_harness_specs(args.specs_dir)
|
|
321
|
+
args.python_output.write_text(generate_python_code(specs))
|
|
322
|
+
args.typescript_output.write_text(generate_typescript_code(specs))
|
|
323
|
+
print(
|
|
324
|
+
f"Generated {len(specs)} harnesses: {args.python_output}, {args.typescript_output}"
|
|
325
|
+
)
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
if __name__ == "__main__":
|
|
329
|
+
main()
|
package/scripts/voice/measure.py
CHANGED
|
@@ -92,6 +92,67 @@ def transcribe(
|
|
|
92
92
|
return json.loads(done.stdout.strip().splitlines()[-1])
|
|
93
93
|
|
|
94
94
|
|
|
95
|
+
def measure_server(
|
|
96
|
+
store: Path,
|
|
97
|
+
voices: Any,
|
|
98
|
+
fixtures: List[Dict[str, Any]],
|
|
99
|
+
wavs: Dict[str, Path],
|
|
100
|
+
measured: Path,
|
|
101
|
+
threads: int,
|
|
102
|
+
) -> None:
|
|
103
|
+
"""Each server speech-to-text model on the fixtures, as the speech service runs it (VO-47).
|
|
104
|
+
|
|
105
|
+
faster-whisper, 8-bit on the CPU with ``threads`` threads, told the
|
|
106
|
+
language, one clip at a time: what it heard and how long it took.
|
|
107
|
+
"""
|
|
108
|
+
from faster_whisper import WhisperModel # the speech image's own
|
|
109
|
+
|
|
110
|
+
for model in voices.SPEECH_MODEL_CATALOGUE.values():
|
|
111
|
+
if model["task"] != "stt" or model["where"] != ["server"]:
|
|
112
|
+
continue
|
|
113
|
+
started = time.monotonic()
|
|
114
|
+
whisper = WhisperModel(
|
|
115
|
+
str(store / model["provider"] / model["id"]),
|
|
116
|
+
device="cpu",
|
|
117
|
+
compute_type="int8",
|
|
118
|
+
cpu_threads=threads,
|
|
119
|
+
)
|
|
120
|
+
load_ms = round((time.monotonic() - started) * 1000)
|
|
121
|
+
by_language = {}
|
|
122
|
+
for language in ("en", "fr"):
|
|
123
|
+
clips = [clip for clip in fixtures if clip["language"] == language]
|
|
124
|
+
print(f"{model['id']} on {len(clips)} {language} clips", flush=True)
|
|
125
|
+
results = []
|
|
126
|
+
for clip in clips:
|
|
127
|
+
samples, _ = sf.read(wavs[clip["id"]], dtype="float32")
|
|
128
|
+
started = time.monotonic()
|
|
129
|
+
parts, _ = whisper.transcribe(
|
|
130
|
+
samples, language=language, vad_filter=True
|
|
131
|
+
)
|
|
132
|
+
text = " ".join(part.text.strip() for part in parts).strip()
|
|
133
|
+
results.append(
|
|
134
|
+
{
|
|
135
|
+
"id": clip["id"],
|
|
136
|
+
"heard": text,
|
|
137
|
+
"ms": round((time.monotonic() - started) * 1000),
|
|
138
|
+
"audio_s": round(len(samples) / 16000, 2),
|
|
139
|
+
}
|
|
140
|
+
)
|
|
141
|
+
by_language[language] = {"load_ms": load_ms, "clips": results}
|
|
142
|
+
(measured / f"stt-{model['id']}.json").write_text(
|
|
143
|
+
json.dumps(
|
|
144
|
+
{
|
|
145
|
+
"model": model["id"],
|
|
146
|
+
"runtime": f"faster-whisper, int8 on the CPU, {threads} threads",
|
|
147
|
+
"languages": by_language,
|
|
148
|
+
},
|
|
149
|
+
ensure_ascii=False,
|
|
150
|
+
indent=2,
|
|
151
|
+
)
|
|
152
|
+
+ "\n"
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
|
|
95
156
|
def main() -> int:
|
|
96
157
|
parser = argparse.ArgumentParser(description=__doc__.split("\n\n")[0])
|
|
97
158
|
parser.add_argument("--store", type=Path, required=True)
|
|
@@ -101,6 +162,14 @@ def main() -> int:
|
|
|
101
162
|
default=None,
|
|
102
163
|
help="Where @huggingface/transformers is installed, when not in this checkout",
|
|
103
164
|
)
|
|
165
|
+
parser.add_argument(
|
|
166
|
+
"--server",
|
|
167
|
+
action="store_true",
|
|
168
|
+
help="Measure the server's speech-to-text models instead, with faster-whisper (in the speech image)",
|
|
169
|
+
)
|
|
170
|
+
parser.add_argument(
|
|
171
|
+
"--threads", type=int, default=4, help="CPU threads for --server"
|
|
172
|
+
)
|
|
104
173
|
args = parser.parse_args()
|
|
105
174
|
voices = catalogue()
|
|
106
175
|
env = dict(os.environ)
|
|
@@ -124,6 +193,11 @@ def main() -> int:
|
|
|
124
193
|
wav = Path(scratch) / f"{clip['id']}.wav"
|
|
125
194
|
sf.write(wav, resample(samples, rate), 16000, subtype="PCM_16")
|
|
126
195
|
wavs[clip["id"]] = wav
|
|
196
|
+
if args.server:
|
|
197
|
+
measure_server(
|
|
198
|
+
args.store, voices, fixtures, wavs, measured, args.threads
|
|
199
|
+
)
|
|
200
|
+
return 0
|
|
127
201
|
# --- speech to text, on the device's models ---------------------------------
|
|
128
202
|
for model in voices.SPEECH_MODEL_CATALOGUE.values():
|
|
129
203
|
if model["task"] != "stt" or "device" not in model["where"]:
|