fasr-service-fastapi 0.5.2__tar.gz
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.
- fasr_service_fastapi-0.5.2/PKG-INFO +163 -0
- fasr_service_fastapi-0.5.2/README.md +151 -0
- fasr_service_fastapi-0.5.2/pyproject.toml +28 -0
- fasr_service_fastapi-0.5.2/src/fasr_service_fastapi/__init__.py +61 -0
- fasr_service_fastapi-0.5.2/src/fasr_service_fastapi/asr_scheduler.py +147 -0
- fasr_service_fastapi-0.5.2/src/fasr_service_fastapi/routers/__init__.py +10 -0
- fasr_service_fastapi-0.5.2/src/fasr_service_fastapi/routers/realtime/__init__.py +28 -0
- fasr_service_fastapi-0.5.2/src/fasr_service_fastapi/routers/realtime/handler.py +811 -0
- fasr_service_fastapi-0.5.2/src/fasr_service_fastapi/routers/realtime/protocol.py +92 -0
- fasr_service_fastapi-0.5.2/src/fasr_service_fastapi/routers/realtime/router.py +87 -0
- fasr_service_fastapi-0.5.2/src/fasr_service_fastapi/routers/realtime/session.py +66 -0
- fasr_service_fastapi-0.5.2/src/fasr_service_fastapi/routers/transcribe.py +438 -0
- fasr_service_fastapi-0.5.2/src/fasr_service_fastapi/schema.py +80 -0
- fasr_service_fastapi-0.5.2/src/fasr_service_fastapi/service.py +228 -0
- fasr_service_fastapi-0.5.2/src/fasr_service_fastapi/static/demo/app.css +465 -0
- fasr_service_fastapi-0.5.2/src/fasr_service_fastapi/static/demo/app.js +722 -0
- fasr_service_fastapi-0.5.2/src/fasr_service_fastapi/static/demo/index.html +165 -0
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: fasr-service-fastapi
|
|
3
|
+
Version: 0.5.2
|
|
4
|
+
Summary: FastAPI service plugin for fasr
|
|
5
|
+
Author: osc
|
|
6
|
+
Author-email: osc <790990241@qq.com>
|
|
7
|
+
Requires-Dist: fasr>=0.5.1
|
|
8
|
+
Requires-Dist: fastapi>=0.115.4
|
|
9
|
+
Requires-Dist: uvicorn>=0.32.0
|
|
10
|
+
Requires-Python: >=3.10, <3.13
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
|
|
13
|
+
# fasr-service-fastapi
|
|
14
|
+
|
|
15
|
+
FastAPI service plugin for fasr. This package provides the concrete online
|
|
16
|
+
service implementation while the core `fasr` package keeps only service base
|
|
17
|
+
classes and registries.
|
|
18
|
+
|
|
19
|
+
## Features
|
|
20
|
+
|
|
21
|
+
- Demo web page:
|
|
22
|
+
- `GET /` (same as `GET /demo`)
|
|
23
|
+
- Optional batch transcription HTTP endpoints:
|
|
24
|
+
- `POST /transcribe`
|
|
25
|
+
- `POST /inference`
|
|
26
|
+
- Optional realtime websocket endpoints:
|
|
27
|
+
- `GET /v1/realtime`
|
|
28
|
+
- `GET /api-ws/v1/realtime`
|
|
29
|
+
- Health endpoint:
|
|
30
|
+
- `GET /health`
|
|
31
|
+
- Config-driven model and router initialization through fasr registries
|
|
32
|
+
|
|
33
|
+
## Registered entry points
|
|
34
|
+
|
|
35
|
+
| Group | Name | Entry point |
|
|
36
|
+
|---|---|---|
|
|
37
|
+
| `fasr_services` | `fastapi.v1` | `fasr_service_fastapi.service:FastAPIASRService` |
|
|
38
|
+
| `fasr_service_routers` | `transcribe.v1` | `fasr_service_fastapi.routers.transcribe:transcribe_entrypoint` |
|
|
39
|
+
| `fasr_service_routers` | `realtime.v1` | `fasr_service_fastapi.routers.realtime:realtime_entrypoint` |
|
|
40
|
+
|
|
41
|
+
## Configuration
|
|
42
|
+
|
|
43
|
+
The service owns model instances under `[service.models]`. Each router owns its
|
|
44
|
+
own `model_map`, which maps router argument names to model names from
|
|
45
|
+
`[service.models]`. Routers are enabled only when their config block exists, so
|
|
46
|
+
omitting `[service.transcribe_router]` skips batch HTTP routes and omitting
|
|
47
|
+
`[service.realtime_router]` skips realtime websocket routes.
|
|
48
|
+
Configuring neither router is invalid and `fasr serve` will fail fast with a
|
|
49
|
+
clear error.
|
|
50
|
+
|
|
51
|
+
```toml
|
|
52
|
+
[service]
|
|
53
|
+
@services = "fastapi.v1"
|
|
54
|
+
debug_logging = false
|
|
55
|
+
|
|
56
|
+
[service.models.qwen3_asr]
|
|
57
|
+
@asr_models = "qwen3asr"
|
|
58
|
+
size = "small"
|
|
59
|
+
|
|
60
|
+
[service.models.marblenet]
|
|
61
|
+
@vad_models = "marblenet"
|
|
62
|
+
|
|
63
|
+
[service.models.fsmn_online]
|
|
64
|
+
@vad_models = "fsmn_online"
|
|
65
|
+
|
|
66
|
+
[service.transcribe_router]
|
|
67
|
+
@service_routers = "transcribe.v1"
|
|
68
|
+
|
|
69
|
+
[service.transcribe_router.model_map]
|
|
70
|
+
vad_model = "marblenet"
|
|
71
|
+
asr_model = "qwen3_asr"
|
|
72
|
+
|
|
73
|
+
[service.realtime_router]
|
|
74
|
+
@service_routers = "realtime.v1"
|
|
75
|
+
|
|
76
|
+
[service.realtime_router.model_map]
|
|
77
|
+
vad_model = "fsmn_online"
|
|
78
|
+
asr_model = "qwen3_asr"
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Set `debug_logging = true` if you want to keep the service's `DEBUG` logs
|
|
82
|
+
during development.
|
|
83
|
+
|
|
84
|
+
For a transcribe-only service, remove the realtime router block:
|
|
85
|
+
|
|
86
|
+
```toml
|
|
87
|
+
[service]
|
|
88
|
+
@services = "fastapi.v1"
|
|
89
|
+
|
|
90
|
+
[service.models.paraformer]
|
|
91
|
+
@asr_models = "paraformer"
|
|
92
|
+
|
|
93
|
+
[service.models.marblenet]
|
|
94
|
+
@vad_models = "marblenet"
|
|
95
|
+
|
|
96
|
+
[service.models.ct_transformer]
|
|
97
|
+
@punc_models = "ct_transformer"
|
|
98
|
+
|
|
99
|
+
[service.transcribe_router]
|
|
100
|
+
@service_routers = "transcribe.v1"
|
|
101
|
+
|
|
102
|
+
[service.transcribe_router.model_map]
|
|
103
|
+
vad_model = "marblenet"
|
|
104
|
+
asr_model = "paraformer"
|
|
105
|
+
punc_model = "ct_transformer"
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Router entry points are factory functions. For example, `transcribe_entrypoint`
|
|
109
|
+
creates default `AudioLoader`, `VoiceDetector`, and `SpeechRecognizer`
|
|
110
|
+
instances when the config only provides `model_map`. This avoids requiring users
|
|
111
|
+
to spell out internal router components in simple service configs. When
|
|
112
|
+
`punc_model` is present in the transcribe router `model_map`, the router also
|
|
113
|
+
creates a default `SpeechSentencizer` and adds it after the recognizer.
|
|
114
|
+
|
|
115
|
+
## CLI
|
|
116
|
+
|
|
117
|
+
Install the service plugin through the root project extra:
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
uv sync --extra service
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Generate and run a default config:
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
fasr init --cfg run.cfg
|
|
127
|
+
fasr serve --cfg run.cfg
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
Inspect or write the default config without starting the server:
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
fasr serve --print_default_config true
|
|
134
|
+
fasr serve --write_default_config run.cfg
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
The same default config is available from:
|
|
138
|
+
|
|
139
|
+
```python
|
|
140
|
+
from fasr_service_fastapi import FastAPIASRService
|
|
141
|
+
|
|
142
|
+
config = FastAPIASRService.model_construct().get_default_config()
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## Development
|
|
146
|
+
|
|
147
|
+
Install from the repository root:
|
|
148
|
+
|
|
149
|
+
```bash
|
|
150
|
+
uv sync
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
Run service tests:
|
|
154
|
+
|
|
155
|
+
```bash
|
|
156
|
+
uv run pytest tests/test_online_service.py -q
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
Build the plugin:
|
|
160
|
+
|
|
161
|
+
```bash
|
|
162
|
+
uv build --package fasr-service-fastapi
|
|
163
|
+
```
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
# fasr-service-fastapi
|
|
2
|
+
|
|
3
|
+
FastAPI service plugin for fasr. This package provides the concrete online
|
|
4
|
+
service implementation while the core `fasr` package keeps only service base
|
|
5
|
+
classes and registries.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- Demo web page:
|
|
10
|
+
- `GET /` (same as `GET /demo`)
|
|
11
|
+
- Optional batch transcription HTTP endpoints:
|
|
12
|
+
- `POST /transcribe`
|
|
13
|
+
- `POST /inference`
|
|
14
|
+
- Optional realtime websocket endpoints:
|
|
15
|
+
- `GET /v1/realtime`
|
|
16
|
+
- `GET /api-ws/v1/realtime`
|
|
17
|
+
- Health endpoint:
|
|
18
|
+
- `GET /health`
|
|
19
|
+
- Config-driven model and router initialization through fasr registries
|
|
20
|
+
|
|
21
|
+
## Registered entry points
|
|
22
|
+
|
|
23
|
+
| Group | Name | Entry point |
|
|
24
|
+
|---|---|---|
|
|
25
|
+
| `fasr_services` | `fastapi.v1` | `fasr_service_fastapi.service:FastAPIASRService` |
|
|
26
|
+
| `fasr_service_routers` | `transcribe.v1` | `fasr_service_fastapi.routers.transcribe:transcribe_entrypoint` |
|
|
27
|
+
| `fasr_service_routers` | `realtime.v1` | `fasr_service_fastapi.routers.realtime:realtime_entrypoint` |
|
|
28
|
+
|
|
29
|
+
## Configuration
|
|
30
|
+
|
|
31
|
+
The service owns model instances under `[service.models]`. Each router owns its
|
|
32
|
+
own `model_map`, which maps router argument names to model names from
|
|
33
|
+
`[service.models]`. Routers are enabled only when their config block exists, so
|
|
34
|
+
omitting `[service.transcribe_router]` skips batch HTTP routes and omitting
|
|
35
|
+
`[service.realtime_router]` skips realtime websocket routes.
|
|
36
|
+
Configuring neither router is invalid and `fasr serve` will fail fast with a
|
|
37
|
+
clear error.
|
|
38
|
+
|
|
39
|
+
```toml
|
|
40
|
+
[service]
|
|
41
|
+
@services = "fastapi.v1"
|
|
42
|
+
debug_logging = false
|
|
43
|
+
|
|
44
|
+
[service.models.qwen3_asr]
|
|
45
|
+
@asr_models = "qwen3asr"
|
|
46
|
+
size = "small"
|
|
47
|
+
|
|
48
|
+
[service.models.marblenet]
|
|
49
|
+
@vad_models = "marblenet"
|
|
50
|
+
|
|
51
|
+
[service.models.fsmn_online]
|
|
52
|
+
@vad_models = "fsmn_online"
|
|
53
|
+
|
|
54
|
+
[service.transcribe_router]
|
|
55
|
+
@service_routers = "transcribe.v1"
|
|
56
|
+
|
|
57
|
+
[service.transcribe_router.model_map]
|
|
58
|
+
vad_model = "marblenet"
|
|
59
|
+
asr_model = "qwen3_asr"
|
|
60
|
+
|
|
61
|
+
[service.realtime_router]
|
|
62
|
+
@service_routers = "realtime.v1"
|
|
63
|
+
|
|
64
|
+
[service.realtime_router.model_map]
|
|
65
|
+
vad_model = "fsmn_online"
|
|
66
|
+
asr_model = "qwen3_asr"
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Set `debug_logging = true` if you want to keep the service's `DEBUG` logs
|
|
70
|
+
during development.
|
|
71
|
+
|
|
72
|
+
For a transcribe-only service, remove the realtime router block:
|
|
73
|
+
|
|
74
|
+
```toml
|
|
75
|
+
[service]
|
|
76
|
+
@services = "fastapi.v1"
|
|
77
|
+
|
|
78
|
+
[service.models.paraformer]
|
|
79
|
+
@asr_models = "paraformer"
|
|
80
|
+
|
|
81
|
+
[service.models.marblenet]
|
|
82
|
+
@vad_models = "marblenet"
|
|
83
|
+
|
|
84
|
+
[service.models.ct_transformer]
|
|
85
|
+
@punc_models = "ct_transformer"
|
|
86
|
+
|
|
87
|
+
[service.transcribe_router]
|
|
88
|
+
@service_routers = "transcribe.v1"
|
|
89
|
+
|
|
90
|
+
[service.transcribe_router.model_map]
|
|
91
|
+
vad_model = "marblenet"
|
|
92
|
+
asr_model = "paraformer"
|
|
93
|
+
punc_model = "ct_transformer"
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Router entry points are factory functions. For example, `transcribe_entrypoint`
|
|
97
|
+
creates default `AudioLoader`, `VoiceDetector`, and `SpeechRecognizer`
|
|
98
|
+
instances when the config only provides `model_map`. This avoids requiring users
|
|
99
|
+
to spell out internal router components in simple service configs. When
|
|
100
|
+
`punc_model` is present in the transcribe router `model_map`, the router also
|
|
101
|
+
creates a default `SpeechSentencizer` and adds it after the recognizer.
|
|
102
|
+
|
|
103
|
+
## CLI
|
|
104
|
+
|
|
105
|
+
Install the service plugin through the root project extra:
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
uv sync --extra service
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Generate and run a default config:
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
fasr init --cfg run.cfg
|
|
115
|
+
fasr serve --cfg run.cfg
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Inspect or write the default config without starting the server:
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
fasr serve --print_default_config true
|
|
122
|
+
fasr serve --write_default_config run.cfg
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
The same default config is available from:
|
|
126
|
+
|
|
127
|
+
```python
|
|
128
|
+
from fasr_service_fastapi import FastAPIASRService
|
|
129
|
+
|
|
130
|
+
config = FastAPIASRService.model_construct().get_default_config()
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## Development
|
|
134
|
+
|
|
135
|
+
Install from the repository root:
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
uv sync
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Run service tests:
|
|
142
|
+
|
|
143
|
+
```bash
|
|
144
|
+
uv run pytest tests/test_online_service.py -q
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
Build the plugin:
|
|
148
|
+
|
|
149
|
+
```bash
|
|
150
|
+
uv build --package fasr-service-fastapi
|
|
151
|
+
```
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "fasr-service-fastapi"
|
|
3
|
+
version = "0.5.2"
|
|
4
|
+
description = "FastAPI service plugin for fasr"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
authors = [
|
|
7
|
+
{ name = "osc", email = "790990241@qq.com" }
|
|
8
|
+
]
|
|
9
|
+
requires-python = ">=3.10, <3.13"
|
|
10
|
+
dependencies = [
|
|
11
|
+
"fasr>=0.5.1",
|
|
12
|
+
"fastapi>=0.115.4",
|
|
13
|
+
"uvicorn>=0.32.0",
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
[tool.uv.sources]
|
|
17
|
+
fasr = { workspace = true }
|
|
18
|
+
|
|
19
|
+
[project.entry-points."fasr_services"]
|
|
20
|
+
"fastapi.v1" = "fasr_service_fastapi.service:FastAPIASRService"
|
|
21
|
+
|
|
22
|
+
[project.entry-points."fasr_service_routers"]
|
|
23
|
+
"transcribe.v1" = "fasr_service_fastapi.routers.transcribe:transcribe_entrypoint"
|
|
24
|
+
"realtime.v1" = "fasr_service_fastapi.routers.realtime:realtime_entrypoint"
|
|
25
|
+
|
|
26
|
+
[build-system]
|
|
27
|
+
requires = ["uv_build>=0.10.11,<0.11.0"]
|
|
28
|
+
build-backend = "uv_build"
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from fastapi import FastAPI
|
|
4
|
+
|
|
5
|
+
from fasr.model import ASRModel, VADModel
|
|
6
|
+
from fasr_service_fastapi.routers.realtime import (
|
|
7
|
+
RealtimeRouter,
|
|
8
|
+
RealtimeWebSocketHandler,
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
from .service import FastAPIASRService, configure_service_logging
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def create_realtime_app(
|
|
15
|
+
*,
|
|
16
|
+
vad_model: VADModel,
|
|
17
|
+
asr_model: ASRModel,
|
|
18
|
+
sample_rate: int = 16000,
|
|
19
|
+
vad_chunk_size_ms: int = 100,
|
|
20
|
+
vad_model_name: str = "fsmn_online",
|
|
21
|
+
asr_model_name: str = "stream_qwen3_0_6b",
|
|
22
|
+
debug_logging: bool = False,
|
|
23
|
+
) -> FastAPI:
|
|
24
|
+
configure_service_logging(debug_logging)
|
|
25
|
+
handler = RealtimeWebSocketHandler(
|
|
26
|
+
sample_rate=sample_rate,
|
|
27
|
+
chunk_size_ms=vad_chunk_size_ms,
|
|
28
|
+
model_name=asr_model_name,
|
|
29
|
+
)
|
|
30
|
+
router = RealtimeRouter(
|
|
31
|
+
handler=handler,
|
|
32
|
+
model_map={
|
|
33
|
+
"vad_model": "vad_model",
|
|
34
|
+
"asr_model": "asr_model",
|
|
35
|
+
},
|
|
36
|
+
)
|
|
37
|
+
router.setup(
|
|
38
|
+
service_id="realtime_test",
|
|
39
|
+
models={
|
|
40
|
+
"vad_model": vad_model,
|
|
41
|
+
"asr_model": asr_model,
|
|
42
|
+
},
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
app = FastAPI(title="fasr realtime asr service")
|
|
46
|
+
app.include_router(router.create_router())
|
|
47
|
+
|
|
48
|
+
@app.get("/health")
|
|
49
|
+
async def health() -> dict[str, object]:
|
|
50
|
+
return {
|
|
51
|
+
"status": "ok",
|
|
52
|
+
"vad_model": vad_model_name,
|
|
53
|
+
"asr_model": asr_model_name,
|
|
54
|
+
"models_loaded": handler.vad_model is not None
|
|
55
|
+
and handler.asr_model is not None,
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return app
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
__all__ = ["FastAPIASRService", "create_realtime_app"]
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from concurrent.futures import Future
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from itertools import count
|
|
6
|
+
import os
|
|
7
|
+
from queue import PriorityQueue
|
|
8
|
+
import threading
|
|
9
|
+
from typing import Callable, List, TypeVar
|
|
10
|
+
|
|
11
|
+
from loguru import logger
|
|
12
|
+
from pydantic import Field
|
|
13
|
+
|
|
14
|
+
from fasr.config import Config
|
|
15
|
+
from fasr.data.audio import AudioChunk, AudioSpan
|
|
16
|
+
from fasr.model import ASRModel
|
|
17
|
+
|
|
18
|
+
T = TypeVar("T")
|
|
19
|
+
|
|
20
|
+
ASR_PRIORITY_REALTIME = 0
|
|
21
|
+
ASR_PRIORITY_TRANSCRIBE = 10
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@dataclass
|
|
25
|
+
class ASRInferenceTask:
|
|
26
|
+
fn: Callable[[], object]
|
|
27
|
+
future: Future[object]
|
|
28
|
+
label: str
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class ASRInferenceScheduler:
|
|
32
|
+
"""Single-worker scheduler for shared ASR model inference calls."""
|
|
33
|
+
|
|
34
|
+
def __init__(self, service_id: str = "") -> None:
|
|
35
|
+
self.service_id = service_id
|
|
36
|
+
self._tasks: PriorityQueue[tuple[int, int, ASRInferenceTask]] = PriorityQueue()
|
|
37
|
+
self._task_sequence = count()
|
|
38
|
+
self._worker: threading.Thread | None = None
|
|
39
|
+
|
|
40
|
+
def start(self) -> None:
|
|
41
|
+
if self._worker is not None and self._worker.is_alive():
|
|
42
|
+
return
|
|
43
|
+
self._worker = threading.Thread(
|
|
44
|
+
target=self._worker_loop,
|
|
45
|
+
name="asr-inference-worker",
|
|
46
|
+
daemon=True,
|
|
47
|
+
)
|
|
48
|
+
self._worker.start()
|
|
49
|
+
logger.info(
|
|
50
|
+
"ASR inference scheduler started: service_id={}, pid={}, thread={}",
|
|
51
|
+
self.service_id,
|
|
52
|
+
os.getpid(),
|
|
53
|
+
self._worker.name,
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
def submit_sync(
|
|
57
|
+
self,
|
|
58
|
+
label: str,
|
|
59
|
+
fn: Callable[[], T],
|
|
60
|
+
priority: int = ASR_PRIORITY_TRANSCRIBE,
|
|
61
|
+
) -> T:
|
|
62
|
+
self.start()
|
|
63
|
+
future: Future[object] = Future()
|
|
64
|
+
self._tasks.put(
|
|
65
|
+
(
|
|
66
|
+
priority,
|
|
67
|
+
next(self._task_sequence),
|
|
68
|
+
ASRInferenceTask(fn=fn, future=future, label=label),
|
|
69
|
+
)
|
|
70
|
+
)
|
|
71
|
+
logger.debug(
|
|
72
|
+
"ASR inference task queued: service_id={}, pid={}, label={}, priority={}, queue_size={}",
|
|
73
|
+
self.service_id,
|
|
74
|
+
os.getpid(),
|
|
75
|
+
label,
|
|
76
|
+
priority,
|
|
77
|
+
self._tasks.qsize(),
|
|
78
|
+
)
|
|
79
|
+
return future.result() # type: ignore[return-value]
|
|
80
|
+
|
|
81
|
+
def _worker_loop(self) -> None:
|
|
82
|
+
while True:
|
|
83
|
+
priority, _, task = self._tasks.get()
|
|
84
|
+
logger.debug(
|
|
85
|
+
"ASR inference task started: service_id={}, pid={}, thread={}, label={}, priority={}, queue_size={}",
|
|
86
|
+
self.service_id,
|
|
87
|
+
os.getpid(),
|
|
88
|
+
threading.current_thread().name,
|
|
89
|
+
task.label,
|
|
90
|
+
priority,
|
|
91
|
+
self._tasks.qsize(),
|
|
92
|
+
)
|
|
93
|
+
try:
|
|
94
|
+
result = task.fn()
|
|
95
|
+
except Exception as exc:
|
|
96
|
+
task.future.set_exception(exc)
|
|
97
|
+
else:
|
|
98
|
+
task.future.set_result(result)
|
|
99
|
+
finally:
|
|
100
|
+
logger.debug(
|
|
101
|
+
"ASR inference task finished: service_id={}, pid={}, thread={}, label={}",
|
|
102
|
+
self.service_id,
|
|
103
|
+
os.getpid(),
|
|
104
|
+
threading.current_thread().name,
|
|
105
|
+
task.label,
|
|
106
|
+
)
|
|
107
|
+
self._tasks.task_done()
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
class ScheduledASRModel(ASRModel):
|
|
111
|
+
"""ASR model wrapper that serializes calls to a shared model instance."""
|
|
112
|
+
|
|
113
|
+
model: ASRModel = Field(..., exclude=True)
|
|
114
|
+
scheduler: ASRInferenceScheduler = Field(..., exclude=True)
|
|
115
|
+
|
|
116
|
+
def load_checkpoint(self, checkpoint_dir: str | None):
|
|
117
|
+
return None
|
|
118
|
+
|
|
119
|
+
def get_config(self) -> Config:
|
|
120
|
+
return self.model.get_config()
|
|
121
|
+
|
|
122
|
+
def transcribe(
|
|
123
|
+
self,
|
|
124
|
+
batch: List[AudioSpan],
|
|
125
|
+
**kwargs,
|
|
126
|
+
) -> List[AudioSpan]:
|
|
127
|
+
return self.scheduler.submit_sync(
|
|
128
|
+
f"{type(self.model).__name__}.transcribe",
|
|
129
|
+
lambda: self.model.transcribe(batch, **kwargs),
|
|
130
|
+
priority=ASR_PRIORITY_TRANSCRIBE,
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
def push_chunk(self, chunk: AudioChunk) -> AudioSpan | None:
|
|
134
|
+
return self.scheduler.submit_sync(
|
|
135
|
+
f"{type(self.model).__name__}.push_chunk",
|
|
136
|
+
lambda: self.model.push_chunk(chunk),
|
|
137
|
+
priority=ASR_PRIORITY_REALTIME,
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
def reset(self):
|
|
141
|
+
return self.model.reset()
|
|
142
|
+
|
|
143
|
+
def remove_state(self, key: str):
|
|
144
|
+
return self.model.remove_state(key)
|
|
145
|
+
|
|
146
|
+
def get_state(self, key: str):
|
|
147
|
+
return self.model.get_state(key)
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
from .realtime import RealtimeRouter, RealtimeWebSocketHandler, realtime_entrypoint
|
|
2
|
+
from .transcribe import TranscribeRouter, transcribe_entrypoint
|
|
3
|
+
|
|
4
|
+
__all__ = [
|
|
5
|
+
"RealtimeRouter",
|
|
6
|
+
"RealtimeWebSocketHandler",
|
|
7
|
+
"TranscribeRouter",
|
|
8
|
+
"realtime_entrypoint",
|
|
9
|
+
"transcribe_entrypoint",
|
|
10
|
+
]
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
from .handler import RealtimeWebSocketHandler
|
|
2
|
+
from .protocol import (
|
|
3
|
+
create_error_event,
|
|
4
|
+
create_session_created_event,
|
|
5
|
+
create_session_finished_event,
|
|
6
|
+
create_session_updated_event,
|
|
7
|
+
create_speech_started_event,
|
|
8
|
+
create_speech_stopped_event,
|
|
9
|
+
create_transcription_completed_event,
|
|
10
|
+
create_transcription_text_event,
|
|
11
|
+
)
|
|
12
|
+
from .router import RealtimeRouter, realtime_entrypoint
|
|
13
|
+
from .session import ASRSession
|
|
14
|
+
|
|
15
|
+
__all__ = [
|
|
16
|
+
"RealtimeRouter",
|
|
17
|
+
"realtime_entrypoint",
|
|
18
|
+
"RealtimeWebSocketHandler",
|
|
19
|
+
"ASRSession",
|
|
20
|
+
"create_error_event",
|
|
21
|
+
"create_session_created_event",
|
|
22
|
+
"create_session_finished_event",
|
|
23
|
+
"create_session_updated_event",
|
|
24
|
+
"create_speech_started_event",
|
|
25
|
+
"create_speech_stopped_event",
|
|
26
|
+
"create_transcription_completed_event",
|
|
27
|
+
"create_transcription_text_event",
|
|
28
|
+
]
|