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.
Files changed (118) hide show
  1. openspeech/__init__.py +75 -0
  2. openspeech/__main__.py +5 -0
  3. openspeech/cli.py +413 -0
  4. openspeech/client/__init__.py +4 -0
  5. openspeech/client/client.py +145 -0
  6. openspeech/config.py +212 -0
  7. openspeech/core/__init__.py +0 -0
  8. openspeech/core/base.py +75 -0
  9. openspeech/core/enums.py +39 -0
  10. openspeech/core/models.py +61 -0
  11. openspeech/core/registry.py +37 -0
  12. openspeech/core/settings.py +8 -0
  13. openspeech/demo.py +675 -0
  14. openspeech/dispatch/__init__.py +0 -0
  15. openspeech/dispatch/context.py +34 -0
  16. openspeech/dispatch/dispatcher.py +661 -0
  17. openspeech/dispatch/executors/__init__.py +0 -0
  18. openspeech/dispatch/executors/base.py +34 -0
  19. openspeech/dispatch/executors/in_process.py +66 -0
  20. openspeech/dispatch/executors/remote.py +64 -0
  21. openspeech/dispatch/executors/subprocess_exec.py +446 -0
  22. openspeech/dispatch/fanout.py +95 -0
  23. openspeech/dispatch/filters.py +73 -0
  24. openspeech/dispatch/lifecycle.py +178 -0
  25. openspeech/dispatch/watcher.py +82 -0
  26. openspeech/engine_catalog.py +236 -0
  27. openspeech/engine_registry.yaml +347 -0
  28. openspeech/exceptions.py +51 -0
  29. openspeech/factory.py +325 -0
  30. openspeech/local_engines/__init__.py +12 -0
  31. openspeech/local_engines/aim_resolver.py +91 -0
  32. openspeech/local_engines/backends/__init__.py +1 -0
  33. openspeech/local_engines/backends/docker_backend.py +490 -0
  34. openspeech/local_engines/backends/native_backend.py +902 -0
  35. openspeech/local_engines/base.py +30 -0
  36. openspeech/local_engines/engines/__init__.py +1 -0
  37. openspeech/local_engines/engines/faster_whisper.py +36 -0
  38. openspeech/local_engines/engines/fish_speech.py +33 -0
  39. openspeech/local_engines/engines/sherpa_onnx.py +56 -0
  40. openspeech/local_engines/engines/whisper.py +41 -0
  41. openspeech/local_engines/engines/whisperlivekit.py +60 -0
  42. openspeech/local_engines/manager.py +208 -0
  43. openspeech/local_engines/models.py +50 -0
  44. openspeech/local_engines/progress.py +69 -0
  45. openspeech/local_engines/registry.py +19 -0
  46. openspeech/local_engines/task_store.py +52 -0
  47. openspeech/local_engines/tasks.py +71 -0
  48. openspeech/logging_config.py +607 -0
  49. openspeech/observe/__init__.py +0 -0
  50. openspeech/observe/base.py +79 -0
  51. openspeech/observe/debug.py +44 -0
  52. openspeech/observe/latency.py +19 -0
  53. openspeech/observe/metrics.py +47 -0
  54. openspeech/observe/tracing.py +44 -0
  55. openspeech/observe/usage.py +27 -0
  56. openspeech/providers/__init__.py +0 -0
  57. openspeech/providers/_template.py +101 -0
  58. openspeech/providers/stt/__init__.py +0 -0
  59. openspeech/providers/stt/alibaba.py +86 -0
  60. openspeech/providers/stt/assemblyai.py +135 -0
  61. openspeech/providers/stt/azure_speech.py +99 -0
  62. openspeech/providers/stt/baidu.py +135 -0
  63. openspeech/providers/stt/deepgram.py +311 -0
  64. openspeech/providers/stt/elevenlabs.py +385 -0
  65. openspeech/providers/stt/faster_whisper.py +211 -0
  66. openspeech/providers/stt/google_cloud.py +106 -0
  67. openspeech/providers/stt/iflytek.py +427 -0
  68. openspeech/providers/stt/macos_speech.py +226 -0
  69. openspeech/providers/stt/openai.py +84 -0
  70. openspeech/providers/stt/sherpa_onnx.py +353 -0
  71. openspeech/providers/stt/tencent.py +212 -0
  72. openspeech/providers/stt/volcengine.py +107 -0
  73. openspeech/providers/stt/whisper.py +153 -0
  74. openspeech/providers/stt/whisperlivekit.py +530 -0
  75. openspeech/providers/stt/windows_speech.py +249 -0
  76. openspeech/providers/tts/__init__.py +0 -0
  77. openspeech/providers/tts/alibaba.py +95 -0
  78. openspeech/providers/tts/azure_speech.py +123 -0
  79. openspeech/providers/tts/baidu.py +143 -0
  80. openspeech/providers/tts/coqui.py +64 -0
  81. openspeech/providers/tts/cosyvoice.py +90 -0
  82. openspeech/providers/tts/deepgram.py +174 -0
  83. openspeech/providers/tts/elevenlabs.py +311 -0
  84. openspeech/providers/tts/fish_speech.py +158 -0
  85. openspeech/providers/tts/google_cloud.py +107 -0
  86. openspeech/providers/tts/iflytek.py +209 -0
  87. openspeech/providers/tts/macos_say.py +251 -0
  88. openspeech/providers/tts/minimax.py +122 -0
  89. openspeech/providers/tts/openai.py +104 -0
  90. openspeech/providers/tts/piper.py +104 -0
  91. openspeech/providers/tts/tencent.py +189 -0
  92. openspeech/providers/tts/volcengine.py +117 -0
  93. openspeech/providers/tts/windows_sapi.py +234 -0
  94. openspeech/server/__init__.py +1 -0
  95. openspeech/server/app.py +72 -0
  96. openspeech/server/auth.py +42 -0
  97. openspeech/server/middleware.py +75 -0
  98. openspeech/server/routes/__init__.py +1 -0
  99. openspeech/server/routes/management.py +848 -0
  100. openspeech/server/routes/stt.py +121 -0
  101. openspeech/server/routes/tts.py +159 -0
  102. openspeech/server/routes/webui.py +29 -0
  103. openspeech/server/webui/app.js +2649 -0
  104. openspeech/server/webui/index.html +216 -0
  105. openspeech/server/webui/styles.css +617 -0
  106. openspeech/server/ws/__init__.py +1 -0
  107. openspeech/server/ws/stt_stream.py +263 -0
  108. openspeech/server/ws/tts_stream.py +207 -0
  109. openspeech/telemetry/__init__.py +21 -0
  110. openspeech/telemetry/perf.py +307 -0
  111. openspeech/utils/__init__.py +5 -0
  112. openspeech/utils/audio_converter.py +406 -0
  113. openspeech/utils/audio_playback.py +156 -0
  114. openspeech/vendor_registry.yaml +74 -0
  115. openspeechapi-0.1.0.dist-info/METADATA +101 -0
  116. openspeechapi-0.1.0.dist-info/RECORD +118 -0
  117. openspeechapi-0.1.0.dist-info/WHEEL +4 -0
  118. openspeechapi-0.1.0.dist-info/entry_points.txt +3 -0
@@ -0,0 +1,307 @@
1
+ """Structured performance / milestone logging primitives.
2
+
3
+ Two integration points are exposed:
4
+
5
+ 1. :class:`PerfTimer` — context manager that measures an operation's wall
6
+ time and emits an ``event`` record on exit. Use for *phases* that have
7
+ a clear start/end (WS accept, provider init, streaming total, ...).
8
+
9
+ 2. :func:`milestone` — one-shot event emitter (TTFB, first frame, ...).
10
+
11
+ Both helpers obey the global ``perf`` level configured in
12
+ :mod:`openspeech.logging_config`:
13
+
14
+ - ``off``: :class:`PerfTimer` / :func:`milestone` become no-ops.
15
+ - ``basic``: milestones tagged ``basic`` (the default) are emitted.
16
+ - ``verbose``: both ``basic`` and ``verbose`` milestones are emitted.
17
+
18
+ An :class:`Event` enum holds the full event taxonomy. New events should
19
+ be added there and documented in ``docs/architecture/logging-spec.md`` so
20
+ consumers (dashboards, LLM log analysis) have a stable vocabulary.
21
+ """
22
+ from __future__ import annotations
23
+
24
+ import functools
25
+ import time
26
+ from contextlib import contextmanager
27
+ from enum import Enum
28
+ from typing import Any, Awaitable, Callable, Iterator, TypeVar
29
+
30
+ from openspeech.logging_config import logger
31
+
32
+ from openspeech.logging_config import get_log_settings
33
+
34
+ # ---------------------------------------------------------------------------
35
+ # Event taxonomy
36
+ # ---------------------------------------------------------------------------
37
+
38
+
39
+ class Event(str, Enum):
40
+ """Canonical event names used in structured logs.
41
+
42
+ Names use dotted namespaces: ``<layer>.<phase>``. Values are stable
43
+ strings — never rename once shipped; add new ones instead.
44
+ """
45
+
46
+ # ---- WebSocket layer -------------------------------------------------
47
+ WS_ACCEPT = "ws.accept"
48
+ WS_META_SENT = "ws.meta_sent"
49
+ WS_FIRST_AUDIO_FRAME = "ws.first_audio_frame"
50
+ WS_FIRST_RESPONSE = "ws.first_response"
51
+ WS_PARTIAL_SENT = "ws.partial_sent"
52
+ WS_FINAL_SENT = "ws.final_sent"
53
+ WS_CLOSED = "ws.closed"
54
+ WS_ERROR = "ws.error"
55
+ WS_TOTAL = "ws.total"
56
+
57
+ # ---- HTTP layer ------------------------------------------------------
58
+ HTTP_REQUEST_START = "http.request_start"
59
+ HTTP_REQUEST_END = "http.request_end"
60
+
61
+ # ---- Dispatcher layer -----------------------------------------------
62
+ DISPATCH_QUEUE_WAIT = "dispatch.queue_wait"
63
+ DISPATCH_EXECUTOR_ACQUIRE = "dispatch.executor_acquire"
64
+ DISPATCH_INVOKE_START = "dispatch.invoke_start"
65
+ DISPATCH_INVOKE_END = "dispatch.invoke_end"
66
+ DISPATCH_INVOKE_ERROR = "dispatch.invoke_error"
67
+ DISPATCH_TOTAL = "dispatch.total"
68
+ DISPATCH_STREAM_START = "dispatch.stream_start"
69
+ DISPATCH_STREAM_END = "dispatch.stream_end"
70
+
71
+ # ---- Lifecycle layer -------------------------------------------------
72
+ LIFECYCLE_PROVIDER_INIT = "lifecycle.provider_init"
73
+ LIFECYCLE_PROVIDER_WARMUP = "lifecycle.provider_warmup"
74
+ LIFECYCLE_PROVIDER_READY = "lifecycle.provider_ready"
75
+ LIFECYCLE_PROVIDER_STOP = "lifecycle.provider_stop"
76
+ LIFECYCLE_IDLE_RECYCLE = "lifecycle.idle_recycle"
77
+
78
+ # ---- Provider layer --------------------------------------------------
79
+ PROVIDER_CONNECT = "provider.connect"
80
+ PROVIDER_FIRST_CHUNK_SENT = "provider.first_chunk_sent"
81
+ PROVIDER_FIRST_PARTIAL = "provider.first_partial"
82
+ PROVIDER_FINAL = "provider.final"
83
+ PROVIDER_TOTAL = "provider.total"
84
+ PROVIDER_ERROR = "provider.error"
85
+
86
+ # ---- Audio utilities -------------------------------------------------
87
+ AUDIO_DECODE = "audio.decode"
88
+ AUDIO_ENCODE = "audio.encode"
89
+ AUDIO_RESAMPLE = "audio.resample"
90
+
91
+ def __str__(self) -> str: # noqa: D401
92
+ return self.value
93
+
94
+
95
+ # ---------------------------------------------------------------------------
96
+ # Perf-level gating
97
+ # ---------------------------------------------------------------------------
98
+
99
+ _LEVELS = {"off": 0, "basic": 1, "verbose": 2}
100
+
101
+
102
+ def perf_enabled(level: str = "basic") -> bool:
103
+ """Return True if a record at the given verbosity should be emitted."""
104
+ current = get_log_settings().perf
105
+ return _LEVELS.get(current, 1) >= _LEVELS.get(level, 1)
106
+
107
+
108
+ # ---------------------------------------------------------------------------
109
+ # Core helpers
110
+ # ---------------------------------------------------------------------------
111
+
112
+
113
+ def _coerce_event(event: Event | str) -> str:
114
+ if isinstance(event, Event):
115
+ return event.value
116
+ return str(event)
117
+
118
+
119
+ def perf_event(
120
+ event: Event | str,
121
+ *,
122
+ level: str = "basic",
123
+ message: str | None = None,
124
+ **fields: Any,
125
+ ) -> None:
126
+ """Emit a structured perf event without a duration.
127
+
128
+ Equivalent to :func:`milestone`. Thin wrapper kept for readability when
129
+ the call site is clearly a "phase marker" rather than a "milestone".
130
+ """
131
+ milestone(event, level=level, message=message, **fields)
132
+
133
+
134
+ def milestone(
135
+ event: Event | str,
136
+ *,
137
+ level: str = "basic",
138
+ message: str | None = None,
139
+ **fields: Any,
140
+ ) -> None:
141
+ """Emit a structured milestone event.
142
+
143
+ The event's ``level`` (``basic`` or ``verbose``) gates whether the
144
+ record is produced, based on the global ``perf`` setting.
145
+ """
146
+ if not perf_enabled(level):
147
+ return
148
+ evt_str = _coerce_event(event)
149
+ # ``event`` is a reserved attribute on loguru records, so we pass it as
150
+ # bound extra. Same for optional elapsed/ttfb fields.
151
+ bindings = {"event": evt_str}
152
+ for k in ("elapsed_ms", "ttfb_ms", "phase"):
153
+ if k in fields and fields[k] is not None:
154
+ bindings[k] = fields.pop(k)
155
+ msg = message or evt_str
156
+ logger.bind(**bindings, **fields).info(msg)
157
+
158
+
159
+ # ---------------------------------------------------------------------------
160
+ # PerfTimer
161
+ # ---------------------------------------------------------------------------
162
+
163
+
164
+ class PerfTimer:
165
+ """Context manager that times a phase and emits a structured event.
166
+
167
+ Usage::
168
+
169
+ with PerfTimer(Event.WS_TOTAL, level="basic", request_id=rid) as t:
170
+ ...
171
+ t.add(bytes_received=1024)
172
+ ...
173
+ # On exit, emits: event=ws.total elapsed_ms=...
174
+
175
+ Inside the block, :meth:`mark` records intermediate milestones (e.g.
176
+ TTFB) relative to the timer's start.
177
+ """
178
+
179
+ __slots__ = ("_event", "_level", "_fields", "_start_ns", "_ttfb_ns", "_active", "_message")
180
+
181
+ def __init__(
182
+ self,
183
+ event: Event | str,
184
+ *,
185
+ level: str = "basic",
186
+ message: str | None = None,
187
+ **fields: Any,
188
+ ) -> None:
189
+ self._event = _coerce_event(event)
190
+ self._level = level
191
+ self._message = message
192
+ self._fields: dict[str, Any] = dict(fields)
193
+ self._start_ns: int = 0
194
+ self._ttfb_ns: int | None = None
195
+ self._active = False
196
+
197
+ # -- context manager ---------------------------------------------------
198
+
199
+ def __enter__(self) -> "PerfTimer":
200
+ self._start_ns = time.perf_counter_ns()
201
+ self._active = perf_enabled(self._level)
202
+ return self
203
+
204
+ def __exit__(self, exc_type, exc_val, exc_tb) -> None: # noqa: D401
205
+ self._emit(error=exc_val)
206
+
207
+ # -- accessors ---------------------------------------------------------
208
+
209
+ @property
210
+ def elapsed_ms(self) -> float:
211
+ return (time.perf_counter_ns() - self._start_ns) / 1_000_000
212
+
213
+ @property
214
+ def ttfb_ms(self) -> float | None:
215
+ if self._ttfb_ns is None:
216
+ return None
217
+ return (self._ttfb_ns - self._start_ns) / 1_000_000
218
+
219
+ def add(self, **fields: Any) -> None:
220
+ """Attach extra fields to be emitted on exit."""
221
+ self._fields.update(fields)
222
+
223
+ def mark_ttfb(self) -> float:
224
+ """Record first-byte/first-response time; returns ms since start."""
225
+ if self._ttfb_ns is None:
226
+ self._ttfb_ns = time.perf_counter_ns()
227
+ return self.ttfb_ms # type: ignore[return-value]
228
+
229
+ def emit_milestone(
230
+ self,
231
+ event: Event | str,
232
+ *,
233
+ level: str = "basic",
234
+ **fields: Any,
235
+ ) -> None:
236
+ """Emit an intermediate milestone with ``elapsed_ms`` relative to this timer."""
237
+ elapsed = self.elapsed_ms
238
+ milestone(event, level=level, elapsed_ms=elapsed, **fields)
239
+
240
+ # -- internal ----------------------------------------------------------
241
+
242
+ def _emit(self, *, error: BaseException | None = None) -> None:
243
+ if not self._active:
244
+ return
245
+ elapsed = self.elapsed_ms
246
+ fields = dict(self._fields)
247
+ if self._ttfb_ns is not None:
248
+ fields["ttfb_ms"] = self.ttfb_ms
249
+ if error is not None:
250
+ fields["error_type"] = type(error).__name__
251
+ fields["error_message"] = str(error)
252
+ milestone(
253
+ self._event,
254
+ level=self._level,
255
+ message=self._message,
256
+ elapsed_ms=elapsed,
257
+ **fields,
258
+ )
259
+
260
+
261
+ # ---------------------------------------------------------------------------
262
+ # Decorators
263
+ # ---------------------------------------------------------------------------
264
+
265
+ F = TypeVar("F", bound=Callable[..., Any])
266
+
267
+
268
+ def timed(event: Event | str, *, level: str = "basic"):
269
+ """Decorator: time a sync function and emit an event on return."""
270
+
271
+ def _wrap(fn: F) -> F:
272
+ @functools.wraps(fn)
273
+ def inner(*args: Any, **kwargs: Any) -> Any:
274
+ with PerfTimer(event, level=level):
275
+ return fn(*args, **kwargs)
276
+ return inner # type: ignore[return-value]
277
+
278
+ return _wrap
279
+
280
+
281
+ def timed_async(event: Event | str, *, level: str = "basic"):
282
+ """Decorator: time an async coroutine and emit an event on return."""
283
+
284
+ def _wrap(fn: Callable[..., Awaitable[Any]]) -> Callable[..., Awaitable[Any]]:
285
+ @functools.wraps(fn)
286
+ async def inner(*args: Any, **kwargs: Any) -> Any:
287
+ with PerfTimer(event, level=level):
288
+ return await fn(*args, **kwargs)
289
+ return inner
290
+
291
+ return _wrap
292
+
293
+
294
+ __all__ = [
295
+ "Event",
296
+ "PerfTimer",
297
+ "milestone",
298
+ "perf_enabled",
299
+ "perf_event",
300
+ "timed",
301
+ "timed_async",
302
+ ]
303
+
304
+
305
+ @contextmanager
306
+ def _noop() -> Iterator[None]:
307
+ yield
@@ -0,0 +1,5 @@
1
+ """Utility helpers for OpenSpeech."""
2
+
3
+ from openspeech.utils.audio_playback import list_output_devices, play_audio
4
+
5
+ __all__ = ["list_output_devices", "play_audio"]