openspeechapi 0.1.0__py3-none-any.whl
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.
- openspeech/__init__.py +75 -0
- openspeech/__main__.py +5 -0
- openspeech/cli.py +413 -0
- openspeech/client/__init__.py +4 -0
- openspeech/client/client.py +145 -0
- openspeech/config.py +212 -0
- openspeech/core/__init__.py +0 -0
- openspeech/core/base.py +75 -0
- openspeech/core/enums.py +39 -0
- openspeech/core/models.py +61 -0
- openspeech/core/registry.py +37 -0
- openspeech/core/settings.py +8 -0
- openspeech/demo.py +675 -0
- openspeech/dispatch/__init__.py +0 -0
- openspeech/dispatch/context.py +34 -0
- openspeech/dispatch/dispatcher.py +661 -0
- openspeech/dispatch/executors/__init__.py +0 -0
- openspeech/dispatch/executors/base.py +34 -0
- openspeech/dispatch/executors/in_process.py +66 -0
- openspeech/dispatch/executors/remote.py +64 -0
- openspeech/dispatch/executors/subprocess_exec.py +446 -0
- openspeech/dispatch/fanout.py +95 -0
- openspeech/dispatch/filters.py +73 -0
- openspeech/dispatch/lifecycle.py +178 -0
- openspeech/dispatch/watcher.py +82 -0
- openspeech/engine_catalog.py +236 -0
- openspeech/engine_registry.yaml +347 -0
- openspeech/exceptions.py +51 -0
- openspeech/factory.py +325 -0
- openspeech/local_engines/__init__.py +12 -0
- openspeech/local_engines/aim_resolver.py +91 -0
- openspeech/local_engines/backends/__init__.py +1 -0
- openspeech/local_engines/backends/docker_backend.py +490 -0
- openspeech/local_engines/backends/native_backend.py +902 -0
- openspeech/local_engines/base.py +30 -0
- openspeech/local_engines/engines/__init__.py +1 -0
- openspeech/local_engines/engines/faster_whisper.py +36 -0
- openspeech/local_engines/engines/fish_speech.py +33 -0
- openspeech/local_engines/engines/sherpa_onnx.py +56 -0
- openspeech/local_engines/engines/whisper.py +41 -0
- openspeech/local_engines/engines/whisperlivekit.py +60 -0
- openspeech/local_engines/manager.py +208 -0
- openspeech/local_engines/models.py +50 -0
- openspeech/local_engines/progress.py +69 -0
- openspeech/local_engines/registry.py +19 -0
- openspeech/local_engines/task_store.py +52 -0
- openspeech/local_engines/tasks.py +71 -0
- openspeech/logging_config.py +607 -0
- openspeech/observe/__init__.py +0 -0
- openspeech/observe/base.py +79 -0
- openspeech/observe/debug.py +44 -0
- openspeech/observe/latency.py +19 -0
- openspeech/observe/metrics.py +47 -0
- openspeech/observe/tracing.py +44 -0
- openspeech/observe/usage.py +27 -0
- openspeech/providers/__init__.py +0 -0
- openspeech/providers/_template.py +101 -0
- openspeech/providers/stt/__init__.py +0 -0
- openspeech/providers/stt/alibaba.py +86 -0
- openspeech/providers/stt/assemblyai.py +135 -0
- openspeech/providers/stt/azure_speech.py +99 -0
- openspeech/providers/stt/baidu.py +135 -0
- openspeech/providers/stt/deepgram.py +311 -0
- openspeech/providers/stt/elevenlabs.py +385 -0
- openspeech/providers/stt/faster_whisper.py +211 -0
- openspeech/providers/stt/google_cloud.py +106 -0
- openspeech/providers/stt/iflytek.py +427 -0
- openspeech/providers/stt/macos_speech.py +226 -0
- openspeech/providers/stt/openai.py +84 -0
- openspeech/providers/stt/sherpa_onnx.py +353 -0
- openspeech/providers/stt/tencent.py +212 -0
- openspeech/providers/stt/volcengine.py +107 -0
- openspeech/providers/stt/whisper.py +153 -0
- openspeech/providers/stt/whisperlivekit.py +530 -0
- openspeech/providers/stt/windows_speech.py +249 -0
- openspeech/providers/tts/__init__.py +0 -0
- openspeech/providers/tts/alibaba.py +95 -0
- openspeech/providers/tts/azure_speech.py +123 -0
- openspeech/providers/tts/baidu.py +143 -0
- openspeech/providers/tts/coqui.py +64 -0
- openspeech/providers/tts/cosyvoice.py +90 -0
- openspeech/providers/tts/deepgram.py +174 -0
- openspeech/providers/tts/elevenlabs.py +311 -0
- openspeech/providers/tts/fish_speech.py +158 -0
- openspeech/providers/tts/google_cloud.py +107 -0
- openspeech/providers/tts/iflytek.py +209 -0
- openspeech/providers/tts/macos_say.py +251 -0
- openspeech/providers/tts/minimax.py +122 -0
- openspeech/providers/tts/openai.py +104 -0
- openspeech/providers/tts/piper.py +104 -0
- openspeech/providers/tts/tencent.py +189 -0
- openspeech/providers/tts/volcengine.py +117 -0
- openspeech/providers/tts/windows_sapi.py +234 -0
- openspeech/server/__init__.py +1 -0
- openspeech/server/app.py +72 -0
- openspeech/server/auth.py +42 -0
- openspeech/server/middleware.py +75 -0
- openspeech/server/routes/__init__.py +1 -0
- openspeech/server/routes/management.py +848 -0
- openspeech/server/routes/stt.py +121 -0
- openspeech/server/routes/tts.py +159 -0
- openspeech/server/routes/webui.py +29 -0
- openspeech/server/webui/app.js +2649 -0
- openspeech/server/webui/index.html +216 -0
- openspeech/server/webui/styles.css +617 -0
- openspeech/server/ws/__init__.py +1 -0
- openspeech/server/ws/stt_stream.py +263 -0
- openspeech/server/ws/tts_stream.py +207 -0
- openspeech/telemetry/__init__.py +21 -0
- openspeech/telemetry/perf.py +307 -0
- openspeech/utils/__init__.py +5 -0
- openspeech/utils/audio_converter.py +406 -0
- openspeech/utils/audio_playback.py +156 -0
- openspeech/vendor_registry.yaml +74 -0
- openspeechapi-0.1.0.dist-info/METADATA +101 -0
- openspeechapi-0.1.0.dist-info/RECORD +118 -0
- openspeechapi-0.1.0.dist-info/WHEEL +4 -0
- openspeechapi-0.1.0.dist-info/entry_points.txt +3 -0
openspeech/factory.py
ADDED
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
"""High-level factory — create providers by string name, no class imports needed."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import sys
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from openspeech.core.base import SpeechProvider
|
|
9
|
+
from openspeech.exceptions import ProviderNotFoundError
|
|
10
|
+
|
|
11
|
+
# Lazy registry: name → (module_path, class_name, settings_class_name)
|
|
12
|
+
_PROVIDER_MAP: dict[str, tuple[str, str, str]] = {
|
|
13
|
+
# STT
|
|
14
|
+
"openai-stt": (
|
|
15
|
+
"openspeech.providers.stt.openai",
|
|
16
|
+
"OpenAISTT",
|
|
17
|
+
"OpenAISTTSettings",
|
|
18
|
+
),
|
|
19
|
+
"faster-whisper": (
|
|
20
|
+
"openspeech.providers.stt.faster_whisper",
|
|
21
|
+
"FasterWhisperSTT",
|
|
22
|
+
"FasterWhisperSTTSettings",
|
|
23
|
+
),
|
|
24
|
+
"whisper": (
|
|
25
|
+
"openspeech.providers.stt.whisper",
|
|
26
|
+
"WhisperSTT",
|
|
27
|
+
"WhisperSTTSettings",
|
|
28
|
+
),
|
|
29
|
+
"deepgram": (
|
|
30
|
+
"openspeech.providers.stt.deepgram",
|
|
31
|
+
"DeepgramSTT",
|
|
32
|
+
"DeepgramSTTSettings",
|
|
33
|
+
),
|
|
34
|
+
"elevenlabs-stt": (
|
|
35
|
+
"openspeech.providers.stt.elevenlabs",
|
|
36
|
+
"ElevenLabsSTT",
|
|
37
|
+
"ElevenLabsSTTSettings",
|
|
38
|
+
),
|
|
39
|
+
"deepgram-tts": (
|
|
40
|
+
"openspeech.providers.tts.deepgram",
|
|
41
|
+
"DeepgramTTS",
|
|
42
|
+
"DeepgramTTSSettings",
|
|
43
|
+
),
|
|
44
|
+
"whisperlivekit-stt": (
|
|
45
|
+
"openspeech.providers.stt.whisperlivekit",
|
|
46
|
+
"WhisperLiveKitSTT",
|
|
47
|
+
"WhisperLiveKitSTTSettings",
|
|
48
|
+
),
|
|
49
|
+
"sherpa-onnx-stt": (
|
|
50
|
+
"openspeech.providers.stt.sherpa_onnx",
|
|
51
|
+
"SherpaOnnxSTT",
|
|
52
|
+
"SherpaOnnxSTTSettings",
|
|
53
|
+
),
|
|
54
|
+
# TTS
|
|
55
|
+
"openai-tts": (
|
|
56
|
+
"openspeech.providers.tts.openai",
|
|
57
|
+
"OpenAITTS",
|
|
58
|
+
"OpenAITTSSettings",
|
|
59
|
+
),
|
|
60
|
+
"elevenlabs": (
|
|
61
|
+
"openspeech.providers.tts.elevenlabs",
|
|
62
|
+
"ElevenLabsTTS",
|
|
63
|
+
"ElevenLabsTTSSettings",
|
|
64
|
+
),
|
|
65
|
+
"piper": (
|
|
66
|
+
"openspeech.providers.tts.piper",
|
|
67
|
+
"PiperTTS",
|
|
68
|
+
"PiperTTSSettings",
|
|
69
|
+
),
|
|
70
|
+
"coqui": (
|
|
71
|
+
"openspeech.providers.tts.coqui",
|
|
72
|
+
"CoquiTTS",
|
|
73
|
+
"CoquiTTSSettings",
|
|
74
|
+
),
|
|
75
|
+
"cosyvoice": (
|
|
76
|
+
"openspeech.providers.tts.cosyvoice",
|
|
77
|
+
"CosyVoiceTTS",
|
|
78
|
+
"CosyVoiceTTSSettings",
|
|
79
|
+
),
|
|
80
|
+
"fish-speech": (
|
|
81
|
+
"openspeech.providers.tts.fish_speech",
|
|
82
|
+
"FishSpeechTTS",
|
|
83
|
+
"FishSpeechTTSSettings",
|
|
84
|
+
),
|
|
85
|
+
"minimax": (
|
|
86
|
+
"openspeech.providers.tts.minimax",
|
|
87
|
+
"MinimaxTTS",
|
|
88
|
+
"MinimaxTTSSettings",
|
|
89
|
+
),
|
|
90
|
+
"macos-say": (
|
|
91
|
+
"openspeech.providers.tts.macos_say",
|
|
92
|
+
"MacOSSayTTS",
|
|
93
|
+
"MacOSSaySettings",
|
|
94
|
+
),
|
|
95
|
+
"macos-stt": (
|
|
96
|
+
"openspeech.providers.stt.macos_speech",
|
|
97
|
+
"MacOSSpeechSTT",
|
|
98
|
+
"MacOSSpeechSettings",
|
|
99
|
+
),
|
|
100
|
+
# Windows Native
|
|
101
|
+
"windows-tts": (
|
|
102
|
+
"openspeech.providers.tts.windows_sapi",
|
|
103
|
+
"WindowsSapiTTS",
|
|
104
|
+
"WindowsSapiSettings",
|
|
105
|
+
),
|
|
106
|
+
"windows-stt": (
|
|
107
|
+
"openspeech.providers.stt.windows_speech",
|
|
108
|
+
"WindowsSpeechSTT",
|
|
109
|
+
"WindowsSpeechSettings",
|
|
110
|
+
),
|
|
111
|
+
# Cloud STT
|
|
112
|
+
"google-stt": (
|
|
113
|
+
"openspeech.providers.stt.google_cloud",
|
|
114
|
+
"GoogleCloudSTT",
|
|
115
|
+
"GoogleCloudSTTSettings",
|
|
116
|
+
),
|
|
117
|
+
"azure-stt": (
|
|
118
|
+
"openspeech.providers.stt.azure_speech",
|
|
119
|
+
"AzureSpeechSTT",
|
|
120
|
+
"AzureSpeechSTTSettings",
|
|
121
|
+
),
|
|
122
|
+
"assemblyai-stt": (
|
|
123
|
+
"openspeech.providers.stt.assemblyai",
|
|
124
|
+
"AssemblyAISTT",
|
|
125
|
+
"AssemblyAISTTSettings",
|
|
126
|
+
),
|
|
127
|
+
"volcengine-stt": (
|
|
128
|
+
"openspeech.providers.stt.volcengine",
|
|
129
|
+
"VolcengineSTT",
|
|
130
|
+
"VolcengineSTTSettings",
|
|
131
|
+
),
|
|
132
|
+
"alibaba-stt": (
|
|
133
|
+
"openspeech.providers.stt.alibaba",
|
|
134
|
+
"AlibabaSTT",
|
|
135
|
+
"AlibabaSTTSettings",
|
|
136
|
+
),
|
|
137
|
+
"tencent-stt": (
|
|
138
|
+
"openspeech.providers.stt.tencent",
|
|
139
|
+
"TencentSTT",
|
|
140
|
+
"TencentSTTSettings",
|
|
141
|
+
),
|
|
142
|
+
"baidu-stt": (
|
|
143
|
+
"openspeech.providers.stt.baidu",
|
|
144
|
+
"BaiduSTT",
|
|
145
|
+
"BaiduSTTSettings",
|
|
146
|
+
),
|
|
147
|
+
"iflytek-stt": (
|
|
148
|
+
"openspeech.providers.stt.iflytek",
|
|
149
|
+
"IflytekSTT",
|
|
150
|
+
"IflytekSTTSettings",
|
|
151
|
+
),
|
|
152
|
+
# Cloud TTS
|
|
153
|
+
"google-tts": (
|
|
154
|
+
"openspeech.providers.tts.google_cloud",
|
|
155
|
+
"GoogleCloudTTS",
|
|
156
|
+
"GoogleCloudTTSSettings",
|
|
157
|
+
),
|
|
158
|
+
"azure-tts": (
|
|
159
|
+
"openspeech.providers.tts.azure_speech",
|
|
160
|
+
"AzureSpeechTTS",
|
|
161
|
+
"AzureSpeechTTSSettings",
|
|
162
|
+
),
|
|
163
|
+
"volcengine-tts": (
|
|
164
|
+
"openspeech.providers.tts.volcengine",
|
|
165
|
+
"VolcengineTTS",
|
|
166
|
+
"VolcengineTTSSettings",
|
|
167
|
+
),
|
|
168
|
+
"alibaba-tts": (
|
|
169
|
+
"openspeech.providers.tts.alibaba",
|
|
170
|
+
"AlibabaTTS",
|
|
171
|
+
"AlibabaTTSSettings",
|
|
172
|
+
),
|
|
173
|
+
"tencent-tts": (
|
|
174
|
+
"openspeech.providers.tts.tencent",
|
|
175
|
+
"TencentTTS",
|
|
176
|
+
"TencentTTSSettings",
|
|
177
|
+
),
|
|
178
|
+
"baidu-tts": (
|
|
179
|
+
"openspeech.providers.tts.baidu",
|
|
180
|
+
"BaiduTTS",
|
|
181
|
+
"BaiduTTSSettings",
|
|
182
|
+
),
|
|
183
|
+
"iflytek-tts": (
|
|
184
|
+
"openspeech.providers.tts.iflytek",
|
|
185
|
+
"IflytekTTS",
|
|
186
|
+
"IflytekTTSSettings",
|
|
187
|
+
),
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
# Platform-generic aliases that resolve to the correct native provider.
|
|
192
|
+
_NATIVE_ALIASES: dict[str, dict[str, str]] = {
|
|
193
|
+
"native-tts": {"darwin": "macos-say", "win32": "windows-tts"},
|
|
194
|
+
"native-stt": {"darwin": "macos-stt", "win32": "windows-stt"},
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def _resolve_native(alias: str) -> str:
|
|
199
|
+
"""Map a ``native-*`` alias to the concrete provider name for this OS."""
|
|
200
|
+
platform_map = _NATIVE_ALIASES.get(alias)
|
|
201
|
+
if platform_map is None:
|
|
202
|
+
raise ProviderNotFoundError(f"Unknown native alias '{alias}'")
|
|
203
|
+
concrete = platform_map.get(sys.platform)
|
|
204
|
+
if concrete is None:
|
|
205
|
+
supported = ", ".join(sorted(platform_map.keys()))
|
|
206
|
+
raise ProviderNotFoundError(
|
|
207
|
+
f"No native provider for platform '{sys.platform}'. "
|
|
208
|
+
f"Supported: {supported}"
|
|
209
|
+
)
|
|
210
|
+
return concrete
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def _resolve(name: str) -> tuple[type, type]:
|
|
214
|
+
"""Lazily import and return (ProviderClass, SettingsClass).
|
|
215
|
+
|
|
216
|
+
Raises ``ProviderNotFoundError`` with a ``pip install`` hint when the
|
|
217
|
+
provider's optional extras are not installed.
|
|
218
|
+
"""
|
|
219
|
+
import importlib
|
|
220
|
+
|
|
221
|
+
# Handle native aliases
|
|
222
|
+
if name in _NATIVE_ALIASES:
|
|
223
|
+
name = _resolve_native(name)
|
|
224
|
+
|
|
225
|
+
entry = _PROVIDER_MAP.get(name)
|
|
226
|
+
if entry is None:
|
|
227
|
+
available = ", ".join(sorted(_PROVIDER_MAP))
|
|
228
|
+
raise ProviderNotFoundError(
|
|
229
|
+
f"Unknown provider '{name}'. Available: {available}"
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
module_path, cls_name, settings_name = entry
|
|
233
|
+
try:
|
|
234
|
+
mod = importlib.import_module(module_path)
|
|
235
|
+
except ImportError as e:
|
|
236
|
+
raise ProviderNotFoundError(
|
|
237
|
+
f"Provider '{name}' requires optional dependencies that are not installed. "
|
|
238
|
+
f"Install with: pip install 'openspeech[{name}]' "
|
|
239
|
+
f"(missing: {e.name or e})"
|
|
240
|
+
) from e
|
|
241
|
+
return getattr(mod, cls_name), getattr(mod, settings_name)
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def _resolve_from_config(prov_cfg) -> tuple[type, type]:
|
|
245
|
+
"""Resolve provider class from a ProviderConfig.
|
|
246
|
+
|
|
247
|
+
If the config specifies ``module`` and ``provider_class``, use importlib
|
|
248
|
+
to load the class dynamically. Otherwise fall back to the built-in
|
|
249
|
+
``_PROVIDER_MAP`` via ``_resolve()``.
|
|
250
|
+
"""
|
|
251
|
+
import importlib
|
|
252
|
+
|
|
253
|
+
if prov_cfg.module and prov_cfg.provider_class:
|
|
254
|
+
mod = importlib.import_module(prov_cfg.module)
|
|
255
|
+
provider_cls = getattr(mod, prov_cfg.provider_class)
|
|
256
|
+
settings_cls = getattr(provider_cls, "settings_cls", None)
|
|
257
|
+
return provider_cls, settings_cls
|
|
258
|
+
|
|
259
|
+
return _resolve(prov_cfg.provider)
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
def _ensure_plugins_dir(project_root: Path | None = None) -> None:
|
|
263
|
+
"""Add ``<project_root>/plugins`` to *sys.path* if the directory exists."""
|
|
264
|
+
if project_root is None:
|
|
265
|
+
project_root = Path.cwd()
|
|
266
|
+
plugins_dir = project_root / "plugins"
|
|
267
|
+
if plugins_dir.is_dir():
|
|
268
|
+
plugins_str = str(plugins_dir)
|
|
269
|
+
if plugins_str not in sys.path:
|
|
270
|
+
sys.path.insert(0, plugins_str)
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
def create_provider(name: str, **settings: Any) -> SpeechProvider:
|
|
274
|
+
"""Create a provider instance by name.
|
|
275
|
+
|
|
276
|
+
Usage::
|
|
277
|
+
|
|
278
|
+
tts = create_provider("openai-tts", api_key="sk-...")
|
|
279
|
+
stt = create_provider("faster-whisper", model_size="tiny")
|
|
280
|
+
|
|
281
|
+
"""
|
|
282
|
+
provider_cls, settings_cls = _resolve(name)
|
|
283
|
+
s = settings_cls(**settings)
|
|
284
|
+
return provider_cls(settings=s)
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
def list_providers() -> list[str]:
|
|
288
|
+
"""Return all registered provider names."""
|
|
289
|
+
return sorted(_PROVIDER_MAP)
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
def create_default_registry() -> "ProviderRegistry":
|
|
293
|
+
"""Build a ProviderRegistry pre-populated with all known providers.
|
|
294
|
+
|
|
295
|
+
Resolves all entries in ``_PROVIDER_MAP`` and ``_NATIVE_ALIASES``,
|
|
296
|
+
silently skipping any whose dependencies are missing.
|
|
297
|
+
"""
|
|
298
|
+
from openspeech.core.registry import ProviderRegistry
|
|
299
|
+
|
|
300
|
+
from openspeech.logging_config import logger
|
|
301
|
+
|
|
302
|
+
_ensure_plugins_dir()
|
|
303
|
+
registry = ProviderRegistry()
|
|
304
|
+
skipped: list[str] = []
|
|
305
|
+
for name in _PROVIDER_MAP:
|
|
306
|
+
try:
|
|
307
|
+
provider_cls, _ = _resolve(name)
|
|
308
|
+
registry.register(name, provider_cls)
|
|
309
|
+
except ProviderNotFoundError as e:
|
|
310
|
+
skipped.append(name)
|
|
311
|
+
logger.debug(f"skip provider '{name}': {e}")
|
|
312
|
+
except Exception as e:
|
|
313
|
+
logger.debug(f"skip provider '{name}' (unexpected): {e}")
|
|
314
|
+
for alias in _NATIVE_ALIASES:
|
|
315
|
+
try:
|
|
316
|
+
provider_cls, _ = _resolve(alias)
|
|
317
|
+
registry.register(alias, provider_cls)
|
|
318
|
+
except Exception as e:
|
|
319
|
+
logger.debug(f"skip native alias '{alias}': {e}")
|
|
320
|
+
if skipped:
|
|
321
|
+
logger.info(
|
|
322
|
+
f"{len(skipped)} provider(s) unavailable due to missing optional deps: "
|
|
323
|
+
f"{', '.join(skipped)}. Install e.g. `pip install 'openspeech[<name>]'`."
|
|
324
|
+
)
|
|
325
|
+
return registry
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""Local engine manager exports."""
|
|
2
|
+
|
|
3
|
+
from openspeech.local_engines.manager import EngineManager
|
|
4
|
+
from openspeech.local_engines.models import EngineAction, EngineStatus, RuntimeConfig, TaskStatus
|
|
5
|
+
|
|
6
|
+
__all__ = [
|
|
7
|
+
"EngineAction",
|
|
8
|
+
"EngineManager",
|
|
9
|
+
"EngineStatus",
|
|
10
|
+
"RuntimeConfig",
|
|
11
|
+
"TaskStatus",
|
|
12
|
+
]
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"""Resolve model paths from AIM CLI output."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import json
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
import subprocess
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def _run_aim(args: list[str]) -> str:
|
|
11
|
+
proc = subprocess.run(
|
|
12
|
+
["aim", *args],
|
|
13
|
+
capture_output=True,
|
|
14
|
+
text=True,
|
|
15
|
+
check=False,
|
|
16
|
+
)
|
|
17
|
+
if proc.returncode != 0:
|
|
18
|
+
raise RuntimeError((proc.stderr or proc.stdout or "").strip() or f"aim {' '.join(args)} failed")
|
|
19
|
+
return (proc.stdout or "").strip()
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _parse_aim_resolve_json(raw: str) -> dict[str, Any]:
|
|
23
|
+
data = json.loads(raw)
|
|
24
|
+
if not isinstance(data, dict):
|
|
25
|
+
return {}
|
|
26
|
+
return data
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def resolve_aim_model_paths(
|
|
30
|
+
*,
|
|
31
|
+
model_ids: list[str],
|
|
32
|
+
provision_engine: str = "whisper",
|
|
33
|
+
kind: str = "any",
|
|
34
|
+
) -> list[str]:
|
|
35
|
+
"""Return absolute model paths for requested model IDs from AIM CLI.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
model_ids: model IDs in priority order.
|
|
39
|
+
provision_engine: target engine filter.
|
|
40
|
+
kind: "file", "dir", or "any".
|
|
41
|
+
"""
|
|
42
|
+
ids = [x.strip() for x in model_ids if str(x).strip()]
|
|
43
|
+
if not ids:
|
|
44
|
+
return []
|
|
45
|
+
|
|
46
|
+
out: list[str] = []
|
|
47
|
+
seen: set[str] = set()
|
|
48
|
+
for mid in ids:
|
|
49
|
+
try:
|
|
50
|
+
resolved = _parse_aim_resolve_json(
|
|
51
|
+
_run_aim(["resolve", mid, "--engine", provision_engine, "--json"])
|
|
52
|
+
)
|
|
53
|
+
except Exception:
|
|
54
|
+
continue
|
|
55
|
+
engines = resolved.get("engines", [])
|
|
56
|
+
if isinstance(engines, list) and engines:
|
|
57
|
+
if provision_engine and provision_engine not in {str(x).strip() for x in engines}:
|
|
58
|
+
continue
|
|
59
|
+
|
|
60
|
+
resolved_path = str(resolved.get("path", "")).strip()
|
|
61
|
+
if not resolved_path:
|
|
62
|
+
continue
|
|
63
|
+
abs_path = Path(resolved_path).expanduser()
|
|
64
|
+
abs_str = str(abs_path)
|
|
65
|
+
if abs_str not in seen:
|
|
66
|
+
seen.add(abs_str)
|
|
67
|
+
out.append(abs_str)
|
|
68
|
+
|
|
69
|
+
resolved_file = str(resolved.get("resolved_file", "") or "").strip()
|
|
70
|
+
if resolved_file:
|
|
71
|
+
rf = str(Path(resolved_file).expanduser())
|
|
72
|
+
if rf not in seen:
|
|
73
|
+
seen.add(rf)
|
|
74
|
+
out.append(rf)
|
|
75
|
+
|
|
76
|
+
# whisper-style canonical directory often contains .pt/.bin payload files.
|
|
77
|
+
if abs_path.is_dir():
|
|
78
|
+
for p in sorted(abs_path.iterdir()):
|
|
79
|
+
if p.is_file() and p.suffix.lower() in {".pt", ".bin"}:
|
|
80
|
+
f = str(p)
|
|
81
|
+
if f not in seen:
|
|
82
|
+
seen.add(f)
|
|
83
|
+
out.append(f)
|
|
84
|
+
|
|
85
|
+
if kind == "any":
|
|
86
|
+
return out
|
|
87
|
+
if kind == "file":
|
|
88
|
+
return [x for x in out if Path(x).is_file() or Path(x).suffix.lower() in {".pt", ".bin"}]
|
|
89
|
+
if kind == "dir":
|
|
90
|
+
return [x for x in out if Path(x).is_dir() or (not Path(x).suffix and not x.endswith(".pt"))]
|
|
91
|
+
return out
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Runtime backend implementations."""
|