livekit-agents 0.8.0.dev2__tar.gz → 0.8.0.dev8__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.
- {livekit_agents-0.8.0.dev2 → livekit_agents-0.8.0.dev8}/PKG-INFO +4 -4
- {livekit_agents-0.8.0.dev2 → livekit_agents-0.8.0.dev8}/livekit/agents/cli/watcher.py +1 -1
- {livekit_agents-0.8.0.dev2 → livekit_agents-0.8.0.dev8}/livekit/agents/ipc/channel.py +1 -1
- {livekit_agents-0.8.0.dev2 → livekit_agents-0.8.0.dev8}/livekit/agents/ipc/proc_main.py +18 -13
- {livekit_agents-0.8.0.dev2 → livekit_agents-0.8.0.dev8}/livekit/agents/ipc/proc_pool.py +3 -11
- {livekit_agents-0.8.0.dev2 → livekit_agents-0.8.0.dev8}/livekit/agents/ipc/proto.py +0 -1
- {livekit_agents-0.8.0.dev2 → livekit_agents-0.8.0.dev8}/livekit/agents/ipc/supervised_proc.py +22 -15
- {livekit_agents-0.8.0.dev2 → livekit_agents-0.8.0.dev8}/livekit/agents/job.py +8 -4
- {livekit_agents-0.8.0.dev2 → livekit_agents-0.8.0.dev8}/livekit/agents/stt/stt.py +1 -1
- {livekit_agents-0.8.0.dev2 → livekit_agents-0.8.0.dev8}/livekit/agents/transcription/_utils.py +2 -2
- {livekit_agents-0.8.0.dev2 → livekit_agents-0.8.0.dev8}/livekit/agents/transcription/stt_forwarder.py +8 -3
- {livekit_agents-0.8.0.dev2 → livekit_agents-0.8.0.dev8}/livekit/agents/transcription/tts_forwarder.py +34 -17
- {livekit_agents-0.8.0.dev2 → livekit_agents-0.8.0.dev8}/livekit/agents/utils/aio/__init__.py +3 -1
- {livekit_agents-0.8.0.dev2 → livekit_agents-0.8.0.dev8}/livekit/agents/vad.py +3 -3
- {livekit_agents-0.8.0.dev2 → livekit_agents-0.8.0.dev8}/livekit/agents/version.py +1 -1
- {livekit_agents-0.8.0.dev2 → livekit_agents-0.8.0.dev8}/livekit/agents/voice_assistant/agent_output.py +50 -52
- {livekit_agents-0.8.0.dev2 → livekit_agents-0.8.0.dev8}/livekit/agents/voice_assistant/cancellable_source.py +13 -11
- {livekit_agents-0.8.0.dev2 → livekit_agents-0.8.0.dev8}/livekit/agents/voice_assistant/human_input.py +28 -17
- {livekit_agents-0.8.0.dev2 → livekit_agents-0.8.0.dev8}/livekit/agents/voice_assistant/voice_assistant.py +89 -49
- {livekit_agents-0.8.0.dev2 → livekit_agents-0.8.0.dev8}/livekit/agents/worker.py +34 -14
- {livekit_agents-0.8.0.dev2 → livekit_agents-0.8.0.dev8}/livekit_agents.egg-info/PKG-INFO +4 -4
- {livekit_agents-0.8.0.dev2 → livekit_agents-0.8.0.dev8}/livekit_agents.egg-info/requires.txt +3 -3
- {livekit_agents-0.8.0.dev2 → livekit_agents-0.8.0.dev8}/setup.py +3 -3
- {livekit_agents-0.8.0.dev2 → livekit_agents-0.8.0.dev8}/README.md +0 -0
- {livekit_agents-0.8.0.dev2 → livekit_agents-0.8.0.dev8}/livekit/agents/__init__.py +0 -0
- {livekit_agents-0.8.0.dev2 → livekit_agents-0.8.0.dev8}/livekit/agents/cli/__init__.py +0 -0
- {livekit_agents-0.8.0.dev2 → livekit_agents-0.8.0.dev8}/livekit/agents/cli/cli.py +0 -0
- {livekit_agents-0.8.0.dev2 → livekit_agents-0.8.0.dev8}/livekit/agents/cli/log.py +0 -0
- {livekit_agents-0.8.0.dev2 → livekit_agents-0.8.0.dev8}/livekit/agents/cli/proto.py +0 -0
- {livekit_agents-0.8.0.dev2 → livekit_agents-0.8.0.dev8}/livekit/agents/exceptions.py +0 -0
- {livekit_agents-0.8.0.dev2 → livekit_agents-0.8.0.dev8}/livekit/agents/http_server.py +0 -0
- {livekit_agents-0.8.0.dev2 → livekit_agents-0.8.0.dev8}/livekit/agents/ipc/__init__.py +0 -0
- {livekit_agents-0.8.0.dev2 → livekit_agents-0.8.0.dev8}/livekit/agents/llm/__init__.py +0 -0
- {livekit_agents-0.8.0.dev2 → livekit_agents-0.8.0.dev8}/livekit/agents/llm/_oai_api.py +0 -0
- {livekit_agents-0.8.0.dev2 → livekit_agents-0.8.0.dev8}/livekit/agents/llm/chat_context.py +0 -0
- {livekit_agents-0.8.0.dev2 → livekit_agents-0.8.0.dev8}/livekit/agents/llm/function_context.py +0 -0
- {livekit_agents-0.8.0.dev2 → livekit_agents-0.8.0.dev8}/livekit/agents/llm/llm.py +0 -0
- {livekit_agents-0.8.0.dev2 → livekit_agents-0.8.0.dev8}/livekit/agents/log.py +0 -0
- {livekit_agents-0.8.0.dev2 → livekit_agents-0.8.0.dev8}/livekit/agents/plugin.py +0 -0
- {livekit_agents-0.8.0.dev2 → livekit_agents-0.8.0.dev8}/livekit/agents/py.typed +0 -0
- {livekit_agents-0.8.0.dev2 → livekit_agents-0.8.0.dev8}/livekit/agents/stt/__init__.py +0 -0
- {livekit_agents-0.8.0.dev2 → livekit_agents-0.8.0.dev8}/livekit/agents/stt/stream_adapter.py +0 -0
- {livekit_agents-0.8.0.dev2 → livekit_agents-0.8.0.dev8}/livekit/agents/tokenize/__init__.py +0 -0
- {livekit_agents-0.8.0.dev2 → livekit_agents-0.8.0.dev8}/livekit/agents/tokenize/_basic_hyphenator.py +0 -0
- {livekit_agents-0.8.0.dev2 → livekit_agents-0.8.0.dev8}/livekit/agents/tokenize/_basic_paragraph.py +0 -0
- {livekit_agents-0.8.0.dev2 → livekit_agents-0.8.0.dev8}/livekit/agents/tokenize/_basic_sent.py +0 -0
- {livekit_agents-0.8.0.dev2 → livekit_agents-0.8.0.dev8}/livekit/agents/tokenize/_basic_word.py +0 -0
- {livekit_agents-0.8.0.dev2 → livekit_agents-0.8.0.dev8}/livekit/agents/tokenize/basic.py +0 -0
- {livekit_agents-0.8.0.dev2 → livekit_agents-0.8.0.dev8}/livekit/agents/tokenize/token_stream.py +0 -0
- {livekit_agents-0.8.0.dev2 → livekit_agents-0.8.0.dev8}/livekit/agents/tokenize/tokenizer.py +0 -0
- {livekit_agents-0.8.0.dev2 → livekit_agents-0.8.0.dev8}/livekit/agents/transcription/__init__.py +0 -0
- {livekit_agents-0.8.0.dev2 → livekit_agents-0.8.0.dev8}/livekit/agents/tts/__init__.py +0 -0
- {livekit_agents-0.8.0.dev2 → livekit_agents-0.8.0.dev8}/livekit/agents/tts/stream_adapter.py +0 -0
- {livekit_agents-0.8.0.dev2 → livekit_agents-0.8.0.dev8}/livekit/agents/tts/tts.py +0 -0
- {livekit_agents-0.8.0.dev2 → livekit_agents-0.8.0.dev8}/livekit/agents/utils/__init__.py +0 -0
- {livekit_agents-0.8.0.dev2 → livekit_agents-0.8.0.dev8}/livekit/agents/utils/aio/channel.py +0 -0
- {livekit_agents-0.8.0.dev2 → livekit_agents-0.8.0.dev8}/livekit/agents/utils/aio/debug.py +0 -0
- {livekit_agents-0.8.0.dev2 → livekit_agents-0.8.0.dev8}/livekit/agents/utils/aio/interval.py +0 -0
- {livekit_agents-0.8.0.dev2 → livekit_agents-0.8.0.dev8}/livekit/agents/utils/aio/sleep.py +0 -0
- {livekit_agents-0.8.0.dev2 → livekit_agents-0.8.0.dev8}/livekit/agents/utils/aio/task_set.py +0 -0
- {livekit_agents-0.8.0.dev2 → livekit_agents-0.8.0.dev8}/livekit/agents/utils/audio.py +0 -0
- {livekit_agents-0.8.0.dev2 → livekit_agents-0.8.0.dev8}/livekit/agents/utils/codecs/__init__.py +0 -0
- {livekit_agents-0.8.0.dev2 → livekit_agents-0.8.0.dev8}/livekit/agents/utils/codecs/mp3.py +0 -0
- {livekit_agents-0.8.0.dev2 → livekit_agents-0.8.0.dev8}/livekit/agents/utils/event_emitter.py +0 -0
- {livekit_agents-0.8.0.dev2 → livekit_agents-0.8.0.dev8}/livekit/agents/utils/exp_filter.py +0 -0
- {livekit_agents-0.8.0.dev2 → livekit_agents-0.8.0.dev8}/livekit/agents/utils/http_context.py +0 -0
- {livekit_agents-0.8.0.dev2 → livekit_agents-0.8.0.dev8}/livekit/agents/utils/images/__init__.py +0 -0
- {livekit_agents-0.8.0.dev2 → livekit_agents-0.8.0.dev8}/livekit/agents/utils/images/image.py +0 -0
- {livekit_agents-0.8.0.dev2 → livekit_agents-0.8.0.dev8}/livekit/agents/utils/log.py +0 -0
- {livekit_agents-0.8.0.dev2 → livekit_agents-0.8.0.dev8}/livekit/agents/utils/misc.py +0 -0
- {livekit_agents-0.8.0.dev2 → livekit_agents-0.8.0.dev8}/livekit/agents/utils/moving_average.py +0 -0
- {livekit_agents-0.8.0.dev2 → livekit_agents-0.8.0.dev8}/livekit/agents/voice_assistant/__init__.py +0 -0
- {livekit_agents-0.8.0.dev2 → livekit_agents-0.8.0.dev8}/livekit/agents/voice_assistant/log.py +0 -0
- {livekit_agents-0.8.0.dev2 → livekit_agents-0.8.0.dev8}/livekit/agents/voice_assistant/plotter.py +0 -0
- {livekit_agents-0.8.0.dev2 → livekit_agents-0.8.0.dev8}/livekit_agents.egg-info/SOURCES.txt +0 -0
- {livekit_agents-0.8.0.dev2 → livekit_agents-0.8.0.dev8}/livekit_agents.egg-info/dependency_links.txt +0 -0
- {livekit_agents-0.8.0.dev2 → livekit_agents-0.8.0.dev8}/livekit_agents.egg-info/top_level.txt +0 -0
- {livekit_agents-0.8.0.dev2 → livekit_agents-0.8.0.dev8}/pyproject.toml +0 -0
- {livekit_agents-0.8.0.dev2 → livekit_agents-0.8.0.dev8}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: livekit-agents
|
|
3
|
-
Version: 0.8.0.
|
|
3
|
+
Version: 0.8.0.dev8
|
|
4
4
|
Summary: LiveKit Python Agents
|
|
5
5
|
Home-page: https://github.com/livekit/agents
|
|
6
6
|
License: Apache-2.0
|
|
@@ -20,9 +20,9 @@ Classifier: Programming Language :: Python :: 3 :: Only
|
|
|
20
20
|
Requires-Python: >=3.9.0
|
|
21
21
|
Description-Content-Type: text/markdown
|
|
22
22
|
Requires-Dist: click~=8.1
|
|
23
|
-
Requires-Dist: livekit~=0.
|
|
24
|
-
Requires-Dist: livekit-api~=0.
|
|
25
|
-
Requires-Dist: livekit-protocol~=0.
|
|
23
|
+
Requires-Dist: livekit~=0.12.0.dev1
|
|
24
|
+
Requires-Dist: livekit-api~=0.6.0
|
|
25
|
+
Requires-Dist: livekit-protocol~=0.6.0
|
|
26
26
|
Requires-Dist: protobuf>=3
|
|
27
27
|
Requires-Dist: pyjwt>=2.0.0
|
|
28
28
|
Requires-Dist: types-protobuf<5,>=4
|
|
@@ -105,7 +105,7 @@ class AsyncProcChannel(ProcChannel):
|
|
|
105
105
|
|
|
106
106
|
self._read_q = asyncio.Queue[Optional[Message]]()
|
|
107
107
|
self._write_q = queue.Queue[Optional[Message]]()
|
|
108
|
-
self._exit_fut = asyncio.Future()
|
|
108
|
+
self._exit_fut = asyncio.Future[None]()
|
|
109
109
|
|
|
110
110
|
self._read_t = threading.Thread(
|
|
111
111
|
target=self._read_thread, daemon=True, name="proc_channel_read"
|
|
@@ -118,12 +118,15 @@ def _start_job(
|
|
|
118
118
|
await room.disconnect()
|
|
119
119
|
|
|
120
120
|
try:
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
121
|
+
shutdown_tasks = []
|
|
122
|
+
for callback in job_ctx._shutdown_callbacks:
|
|
123
|
+
shutdown_tasks.append(
|
|
124
|
+
asyncio.create_task(callback(), name="job_shutdown_callback")
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
await asyncio.gather(*shutdown_tasks)
|
|
125
128
|
except Exception:
|
|
126
|
-
logger.exception("error while
|
|
129
|
+
logger.exception("error while shutting down the job")
|
|
127
130
|
|
|
128
131
|
await utils.http_context._close_http_ctx()
|
|
129
132
|
exit_proc_fut.set()
|
|
@@ -157,9 +160,10 @@ async def _async_main(
|
|
|
157
160
|
|
|
158
161
|
if isinstance(msg, proto.ShutdownRequest):
|
|
159
162
|
if job_task is not None:
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
+
with contextlib.suppress(asyncio.InvalidStateError):
|
|
164
|
+
job_task.shutdown_fut.set_result(
|
|
165
|
+
_ShutdownInfo(reason=msg.reason, user_initiated=False)
|
|
166
|
+
)
|
|
163
167
|
else:
|
|
164
168
|
exit_proc_fut.set() # there is no running job, we can exit immediately
|
|
165
169
|
|
|
@@ -210,8 +214,9 @@ def main(args: proto.ProcStartArgs) -> None:
|
|
|
210
214
|
# (this signal can be sent by watchfiles on dev mode)
|
|
211
215
|
loop.run_until_complete(main_task)
|
|
212
216
|
finally:
|
|
213
|
-
try:
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
finally:
|
|
217
|
-
|
|
217
|
+
# try:
|
|
218
|
+
loop.run_until_complete(loop.shutdown_default_executor())
|
|
219
|
+
loop.run_until_complete(cch.aclose())
|
|
220
|
+
# finally:
|
|
221
|
+
# loop.close()
|
|
222
|
+
# pass
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import asyncio
|
|
4
|
-
|
|
5
|
-
import sys
|
|
4
|
+
from multiprocessing.context import BaseContext
|
|
6
5
|
from typing import Any, Callable, Coroutine, Literal
|
|
7
6
|
|
|
8
7
|
from .. import utils
|
|
@@ -22,22 +21,16 @@ class ProcPool(utils.EventEmitter[EventTypes]):
|
|
|
22
21
|
*,
|
|
23
22
|
initialize_process_fnc: Callable[[JobProcess], Any],
|
|
24
23
|
job_entrypoint_fnc: Callable[[JobContext], Coroutine],
|
|
25
|
-
job_shutdown_fnc: Callable[[JobContext], Coroutine],
|
|
26
24
|
num_idle_processes: int,
|
|
27
25
|
initialize_timeout: float,
|
|
28
26
|
close_timeout: float,
|
|
27
|
+
mp_ctx: BaseContext,
|
|
29
28
|
loop: asyncio.AbstractEventLoop,
|
|
30
29
|
) -> None:
|
|
31
30
|
super().__init__()
|
|
32
|
-
|
|
33
|
-
#if sys.platform.startswith("linux"):
|
|
34
|
-
# self._mp_ctx = mp.get_context("forkserver")
|
|
35
|
-
#else:
|
|
36
|
-
self._mp_ctx = mp.get_context("spawn")
|
|
37
|
-
|
|
31
|
+
self._mp_ctx = mp_ctx
|
|
38
32
|
self._initialize_process_fnc = initialize_process_fnc
|
|
39
33
|
self._job_entrypoint_fnc = job_entrypoint_fnc
|
|
40
|
-
self._job_shutdown_fnc = job_shutdown_fnc
|
|
41
34
|
self._close_timeout = close_timeout
|
|
42
35
|
self._initialize_timeout = initialize_timeout
|
|
43
36
|
self._loop = loop
|
|
@@ -74,7 +67,6 @@ class ProcPool(utils.EventEmitter[EventTypes]):
|
|
|
74
67
|
proc = SupervisedProc(
|
|
75
68
|
initialize_process_fnc=self._initialize_process_fnc,
|
|
76
69
|
job_entrypoint_fnc=self._job_entrypoint_fnc,
|
|
77
|
-
job_shutdown_fnc=self._job_shutdown_fnc,
|
|
78
70
|
initialize_timeout=self._initialize_timeout,
|
|
79
71
|
close_timeout=self._close_timeout,
|
|
80
72
|
mp_ctx=self._mp_ctx,
|
|
@@ -19,7 +19,6 @@ HIGH_PING_THRESHOLD = 0.02 # 20ms
|
|
|
19
19
|
class ProcStartArgs:
|
|
20
20
|
initialize_process_fnc: Callable[[JobProcess], Any]
|
|
21
21
|
job_entrypoint_fnc: Callable[[JobContext], Coroutine]
|
|
22
|
-
job_shutdown_fnc: Callable[[JobContext], Coroutine]
|
|
23
22
|
log_q: mp.Queue
|
|
24
23
|
mp_cch: channel.ProcessConn
|
|
25
24
|
asyncio_debug: bool
|
{livekit_agents-0.8.0.dev2 → livekit_agents-0.8.0.dev8}/livekit/agents/ipc/supervised_proc.py
RENAMED
|
@@ -6,7 +6,7 @@ import logging
|
|
|
6
6
|
import multiprocessing as mp
|
|
7
7
|
import sys
|
|
8
8
|
import threading
|
|
9
|
-
from multiprocessing.context import
|
|
9
|
+
from multiprocessing.context import BaseContext
|
|
10
10
|
from typing import Any, Callable, Coroutine
|
|
11
11
|
|
|
12
12
|
from .. import utils
|
|
@@ -32,6 +32,8 @@ class LogQueueListener:
|
|
|
32
32
|
t.start()
|
|
33
33
|
|
|
34
34
|
def stop(self) -> None:
|
|
35
|
+
if self._thread is None:
|
|
36
|
+
return
|
|
35
37
|
self._q.put_nowait(self._sentinel)
|
|
36
38
|
self._thread.join()
|
|
37
39
|
self._thread = None
|
|
@@ -60,14 +62,13 @@ class SupervisedProc:
|
|
|
60
62
|
*,
|
|
61
63
|
initialize_process_fnc: Callable[[JobProcess], Any],
|
|
62
64
|
job_entrypoint_fnc: Callable[[JobContext], Coroutine],
|
|
63
|
-
job_shutdown_fnc: Callable[[JobContext], Coroutine],
|
|
64
65
|
initialize_timeout: float,
|
|
65
66
|
close_timeout: float,
|
|
66
|
-
mp_ctx:
|
|
67
|
+
mp_ctx: BaseContext,
|
|
67
68
|
loop: asyncio.AbstractEventLoop,
|
|
68
69
|
) -> None:
|
|
69
70
|
self._loop = loop
|
|
70
|
-
log_q =
|
|
71
|
+
log_q = mp_ctx.Queue()
|
|
71
72
|
log_q.cancel_join_thread()
|
|
72
73
|
mp_pch, mp_cch = mp_ctx.Pipe(duplex=True)
|
|
73
74
|
|
|
@@ -80,13 +81,12 @@ class SupervisedProc:
|
|
|
80
81
|
self._proc_args = proto.ProcStartArgs(
|
|
81
82
|
initialize_process_fnc=initialize_process_fnc,
|
|
82
83
|
job_entrypoint_fnc=job_entrypoint_fnc,
|
|
83
|
-
job_shutdown_fnc=job_shutdown_fnc,
|
|
84
84
|
log_q=log_q,
|
|
85
85
|
mp_cch=mp_cch,
|
|
86
86
|
asyncio_debug=loop.get_debug(),
|
|
87
87
|
)
|
|
88
88
|
|
|
89
|
-
self._proc = mp_ctx.Process(
|
|
89
|
+
self._proc = mp_ctx.Process( # type: ignore
|
|
90
90
|
target=proc_main.main, args=(self._proc_args,), name="job_proc"
|
|
91
91
|
)
|
|
92
92
|
self._running_job: RunningJobInfo | None = None
|
|
@@ -97,7 +97,7 @@ class SupervisedProc:
|
|
|
97
97
|
self._main_atask: asyncio.Task[None] | None = None
|
|
98
98
|
self._closing = False
|
|
99
99
|
self._kill_sent = False
|
|
100
|
-
self._initialize_fut = asyncio.Future()
|
|
100
|
+
self._initialize_fut = asyncio.Future[None]()
|
|
101
101
|
|
|
102
102
|
@property
|
|
103
103
|
def exitcode(self) -> int | None:
|
|
@@ -145,7 +145,7 @@ class SupervisedProc:
|
|
|
145
145
|
|
|
146
146
|
self._proc.start()
|
|
147
147
|
self._pid = self._proc.pid
|
|
148
|
-
self._join_fut = asyncio.Future()
|
|
148
|
+
self._join_fut = asyncio.Future[None]()
|
|
149
149
|
|
|
150
150
|
def _sync_run():
|
|
151
151
|
self._proc.join()
|
|
@@ -161,7 +161,8 @@ class SupervisedProc:
|
|
|
161
161
|
if not self.started:
|
|
162
162
|
raise RuntimeError("process not started")
|
|
163
163
|
|
|
164
|
-
|
|
164
|
+
if self._main_atask:
|
|
165
|
+
await asyncio.shield(self._main_atask)
|
|
165
166
|
|
|
166
167
|
async def initialize(self) -> None:
|
|
167
168
|
"""initialize the job process, this is calling the user provided initialize_process_fnc
|
|
@@ -198,16 +199,18 @@ class SupervisedProc:
|
|
|
198
199
|
await self._pch.asend(proto.ShutdownRequest())
|
|
199
200
|
|
|
200
201
|
try:
|
|
201
|
-
|
|
202
|
-
asyncio.
|
|
203
|
-
|
|
202
|
+
if self._main_atask:
|
|
203
|
+
await asyncio.wait_for(
|
|
204
|
+
asyncio.shield(self._main_atask), timeout=self._close_timeout
|
|
205
|
+
)
|
|
204
206
|
except asyncio.TimeoutError:
|
|
205
207
|
logger.error(
|
|
206
208
|
"process did not exit in time, killing job", extra=self.logging_extra()
|
|
207
209
|
)
|
|
208
210
|
self._send_kill_signal()
|
|
209
211
|
|
|
210
|
-
|
|
212
|
+
if self._main_atask:
|
|
213
|
+
await asyncio.shield(self._main_atask)
|
|
211
214
|
|
|
212
215
|
async def kill(self) -> None:
|
|
213
216
|
"""forcefully kill the job process"""
|
|
@@ -216,7 +219,8 @@ class SupervisedProc:
|
|
|
216
219
|
|
|
217
220
|
self._closing = True
|
|
218
221
|
self._send_kill_signal()
|
|
219
|
-
|
|
222
|
+
if self._main_atask:
|
|
223
|
+
await asyncio.shield(self._main_atask)
|
|
220
224
|
|
|
221
225
|
async def launch_job(self, info: RunningJobInfo) -> None:
|
|
222
226
|
"""start/assign a job to the process"""
|
|
@@ -230,7 +234,10 @@ class SupervisedProc:
|
|
|
230
234
|
|
|
231
235
|
def _send_kill_signal(self) -> None:
|
|
232
236
|
"""forcefully kill the job process"""
|
|
233
|
-
|
|
237
|
+
try:
|
|
238
|
+
if not self._proc.is_alive():
|
|
239
|
+
return
|
|
240
|
+
except ValueError:
|
|
234
241
|
return
|
|
235
242
|
|
|
236
243
|
logger.debug("killing job process", extra=self.logging_extra())
|
|
@@ -61,6 +61,7 @@ class JobContext:
|
|
|
61
61
|
self._room = room
|
|
62
62
|
self._on_connect = on_connect
|
|
63
63
|
self._on_shutdown = on_shutdown
|
|
64
|
+
self._shutdown_callbacks: list[Callable[[], Coroutine]] = []
|
|
64
65
|
|
|
65
66
|
@property
|
|
66
67
|
def proc(self) -> JobProcess:
|
|
@@ -78,6 +79,9 @@ class JobContext:
|
|
|
78
79
|
def agent(self) -> rtc.LocalParticipant:
|
|
79
80
|
return self._room.local_participant
|
|
80
81
|
|
|
82
|
+
def add_shutdown_callback(self, callback: Callable[[], Coroutine]) -> None:
|
|
83
|
+
self._shutdown_callbacks.append(callback)
|
|
84
|
+
|
|
81
85
|
async def connect(
|
|
82
86
|
self,
|
|
83
87
|
*,
|
|
@@ -114,8 +118,8 @@ def _apply_auto_subscribe_opts(room: rtc.Room, auto_subscribe: AutoSubscribe) ->
|
|
|
114
118
|
):
|
|
115
119
|
pub.set_subscribed(True)
|
|
116
120
|
|
|
117
|
-
for p in room.
|
|
118
|
-
for pub in p.
|
|
121
|
+
for p in room.remote_participants.values():
|
|
122
|
+
for pub in p.track_publications.values():
|
|
119
123
|
_subscribe_if_needed(pub)
|
|
120
124
|
|
|
121
125
|
@room.on("track_published")
|
|
@@ -128,11 +132,11 @@ def _apply_auto_subscribe_opts(room: rtc.Room, auto_subscribe: AutoSubscribe) ->
|
|
|
128
132
|
class JobProcess:
|
|
129
133
|
def __init__(self, *, start_arguments: Any | None = None) -> None:
|
|
130
134
|
self._mp_proc = mp.current_process()
|
|
131
|
-
self._userdata = {}
|
|
135
|
+
self._userdata: dict[str, Any] = {}
|
|
132
136
|
self._start_arguments = start_arguments
|
|
133
137
|
|
|
134
138
|
@property
|
|
135
|
-
def pid(self) -> int:
|
|
139
|
+
def pid(self) -> int | None:
|
|
136
140
|
return self._mp_proc.pid
|
|
137
141
|
|
|
138
142
|
@property
|
|
@@ -12,7 +12,7 @@ from ..utils import AudioBuffer, aio
|
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
@unique
|
|
15
|
-
class SpeechEventType(Enum):
|
|
15
|
+
class SpeechEventType(str, Enum):
|
|
16
16
|
START_OF_SPEECH = "start_of_speech"
|
|
17
17
|
"""indicate the start of speech
|
|
18
18
|
if the STT doesn't support this event, this will be emitted as the same time as the first INTERIM_TRANSCRIPT"""
|
{livekit_agents-0.8.0.dev2 → livekit_agents-0.8.0.dev8}/livekit/agents/transcription/_utils.py
RENAMED
|
@@ -7,7 +7,7 @@ from livekit import rtc
|
|
|
7
7
|
|
|
8
8
|
def find_micro_track_id(room: rtc.Room, identity: str) -> str:
|
|
9
9
|
p: rtc.RemoteParticipant | rtc.LocalParticipant | None = (
|
|
10
|
-
room.
|
|
10
|
+
room.remote_participants.get(identity)
|
|
11
11
|
)
|
|
12
12
|
if identity == room.local_participant.identity:
|
|
13
13
|
p = room.local_participant
|
|
@@ -17,7 +17,7 @@ def find_micro_track_id(room: rtc.Room, identity: str) -> str:
|
|
|
17
17
|
|
|
18
18
|
# find first micro track
|
|
19
19
|
track_id = None
|
|
20
|
-
for track in p.
|
|
20
|
+
for track in p.track_publications.values():
|
|
21
21
|
if track.source == rtc.TrackSource.SOURCE_MICROPHONE:
|
|
22
22
|
track_id = track.sid
|
|
23
23
|
break
|
|
@@ -45,9 +45,8 @@ class STTSegmentsForwarder:
|
|
|
45
45
|
|
|
46
46
|
transcription = rtc.Transcription(
|
|
47
47
|
participant_identity=self._participant_identity,
|
|
48
|
-
|
|
48
|
+
track_sid=self._track_id,
|
|
49
49
|
segments=[seg], # no history for now
|
|
50
|
-
language="", # TODO(theomonnom)
|
|
51
50
|
)
|
|
52
51
|
await self._room.local_participant.publish_transcription(transcription)
|
|
53
52
|
|
|
@@ -66,13 +65,19 @@ class STTSegmentsForwarder:
|
|
|
66
65
|
start_time=0,
|
|
67
66
|
end_time=0,
|
|
68
67
|
final=False,
|
|
68
|
+
language="", # TODO
|
|
69
69
|
)
|
|
70
70
|
)
|
|
71
71
|
elif ev.type == stt.SpeechEventType.FINAL_TRANSCRIPT:
|
|
72
72
|
text = ev.alternatives[0].text
|
|
73
73
|
self._queue.put_nowait(
|
|
74
74
|
rtc.TranscriptionSegment(
|
|
75
|
-
id=self._current_id,
|
|
75
|
+
id=self._current_id,
|
|
76
|
+
text=text,
|
|
77
|
+
start_time=0,
|
|
78
|
+
end_time=0,
|
|
79
|
+
final=True,
|
|
80
|
+
language="", # TODO
|
|
76
81
|
)
|
|
77
82
|
)
|
|
78
83
|
|
|
@@ -13,6 +13,9 @@ from .. import tokenize, utils
|
|
|
13
13
|
from ..log import logger
|
|
14
14
|
from . import _utils
|
|
15
15
|
|
|
16
|
+
# 3.83 is the "baseline", the number of hyphens per second TTS returns in avg.
|
|
17
|
+
STANDARD_SPEECH_RATE = 3.83
|
|
18
|
+
|
|
16
19
|
|
|
17
20
|
@dataclass
|
|
18
21
|
class _TTSOptions:
|
|
@@ -35,7 +38,7 @@ class _SegmentData:
|
|
|
35
38
|
pushed_duration: float = 0.0
|
|
36
39
|
real_speed: float | None = None
|
|
37
40
|
processed_sentences: int = 0
|
|
38
|
-
|
|
41
|
+
processed_hyphens: int = 0
|
|
39
42
|
validated: bool = False
|
|
40
43
|
forward_start_time: float | None = 0.0
|
|
41
44
|
|
|
@@ -62,7 +65,7 @@ class TTSSegmentsForwarder:
|
|
|
62
65
|
participant: rtc.Participant | str,
|
|
63
66
|
track: rtc.Track | rtc.TrackPublication | str | None = None,
|
|
64
67
|
language: str = "",
|
|
65
|
-
speed: float =
|
|
68
|
+
speed: float = 1.0,
|
|
66
69
|
new_sentence_delay: float = 0.4,
|
|
67
70
|
word_tokenizer: tokenize.WordTokenizer = tokenize.basic.WordTokenizer(),
|
|
68
71
|
sentence_tokenizer: tokenize.SentenceTokenizer = tokenize.basic.SentenceTokenizer(),
|
|
@@ -82,7 +85,7 @@ class TTSSegmentsForwarder:
|
|
|
82
85
|
to start the transcription.
|
|
83
86
|
word_tokenizer: word tokenizer used to split the text into words
|
|
84
87
|
sentence_tokenizer: sentence tokenizer used to split the text into sentences
|
|
85
|
-
hyphenate_word: function that returns a list of
|
|
88
|
+
hyphenate_word: function that returns a list of hyphens for a given word
|
|
86
89
|
|
|
87
90
|
"""
|
|
88
91
|
identity = participant if isinstance(participant, str) else participant.identity
|
|
@@ -92,6 +95,7 @@ class TTSSegmentsForwarder:
|
|
|
92
95
|
elif isinstance(track, (rtc.TrackPublication, rtc.Track)):
|
|
93
96
|
track = track.sid
|
|
94
97
|
|
|
98
|
+
speed = speed * STANDARD_SPEECH_RATE
|
|
95
99
|
self._opts = _TTSOptions(
|
|
96
100
|
room=room,
|
|
97
101
|
participant_identity=identity,
|
|
@@ -185,6 +189,10 @@ class TTSSegmentsForwarder:
|
|
|
185
189
|
self._forming_segments.q.append(new_seg)
|
|
186
190
|
self._seg_queue.put_nowait(new_seg)
|
|
187
191
|
|
|
192
|
+
@property
|
|
193
|
+
def closed(self) -> bool:
|
|
194
|
+
return self._closed
|
|
195
|
+
|
|
188
196
|
async def aclose(self) -> None:
|
|
189
197
|
if self._closed:
|
|
190
198
|
return
|
|
@@ -213,9 +221,8 @@ class TTSSegmentsForwarder:
|
|
|
213
221
|
|
|
214
222
|
tr = rtc.Transcription(
|
|
215
223
|
participant_identity=self._opts.participant_identity,
|
|
216
|
-
|
|
224
|
+
track_sid=self._opts.track_id,
|
|
217
225
|
segments=[seg], # no history for now, only one segment
|
|
218
|
-
language=self._opts.language,
|
|
219
226
|
)
|
|
220
227
|
await self._opts.room.local_participant.publish_transcription(tr)
|
|
221
228
|
|
|
@@ -267,7 +274,7 @@ class TTSSegmentsForwarder:
|
|
|
267
274
|
# transcription closed, early
|
|
268
275
|
return
|
|
269
276
|
|
|
270
|
-
|
|
277
|
+
word_hyphens = len(self._opts.hyphenate_word(word))
|
|
271
278
|
processed_words.append(word)
|
|
272
279
|
|
|
273
280
|
# elapsed time since the start of the seg
|
|
@@ -283,26 +290,36 @@ class TTSSegmentsForwarder:
|
|
|
283
290
|
)
|
|
284
291
|
hyph_pauses = estimated_pauses_s * speed
|
|
285
292
|
|
|
286
|
-
|
|
287
|
-
dt =
|
|
288
|
-
|
|
289
|
-
delay =
|
|
293
|
+
target_hyphens = round(speed * elapsed_time)
|
|
294
|
+
dt = target_hyphens - seg.processed_hyphens - hyph_pauses
|
|
295
|
+
to_wait_hyphens = max(0, word_hyphens - dt)
|
|
296
|
+
delay = to_wait_hyphens / speed
|
|
290
297
|
else:
|
|
291
|
-
delay =
|
|
298
|
+
delay = word_hyphens / speed
|
|
292
299
|
|
|
293
300
|
first_delay = min(delay / 2, 2 / speed)
|
|
294
301
|
await self._sleep_if_not_closed(first_delay)
|
|
295
302
|
rtc_seg_q.put_nowait(
|
|
296
303
|
rtc.TranscriptionSegment(
|
|
297
|
-
id=seg_id,
|
|
304
|
+
id=seg_id,
|
|
305
|
+
text=text,
|
|
306
|
+
start_time=0,
|
|
307
|
+
end_time=0,
|
|
308
|
+
final=False,
|
|
309
|
+
language=self._opts.language,
|
|
298
310
|
)
|
|
299
311
|
)
|
|
300
312
|
await self._sleep_if_not_closed(delay - first_delay)
|
|
301
|
-
seg.
|
|
313
|
+
seg.processed_hyphens += word_hyphens
|
|
302
314
|
|
|
303
315
|
rtc_seg_q.put_nowait(
|
|
304
316
|
rtc.TranscriptionSegment(
|
|
305
|
-
id=seg_id,
|
|
317
|
+
id=seg_id,
|
|
318
|
+
text=tokenized_sentence,
|
|
319
|
+
start_time=0,
|
|
320
|
+
end_time=0,
|
|
321
|
+
final=True,
|
|
322
|
+
language=self._opts.language,
|
|
306
323
|
)
|
|
307
324
|
)
|
|
308
325
|
|
|
@@ -314,13 +331,13 @@ class TTSSegmentsForwarder:
|
|
|
314
331
|
await asyncio.wait([self._close_future], timeout=delay)
|
|
315
332
|
|
|
316
333
|
def _calc_hyphens(self, text: str) -> list[str]:
|
|
317
|
-
|
|
334
|
+
hyphens: list[str] = []
|
|
318
335
|
words = self._opts.word_tokenizer.tokenize(text=text)
|
|
319
336
|
for word in words:
|
|
320
337
|
new = self._opts.hyphenate_word(word)
|
|
321
|
-
|
|
338
|
+
hyphens.extend(new)
|
|
322
339
|
|
|
323
|
-
return
|
|
340
|
+
return hyphens
|
|
324
341
|
|
|
325
342
|
def _create_segment(self) -> _SegmentData:
|
|
326
343
|
data = _SegmentData(
|
{livekit_agents-0.8.0.dev2 → livekit_agents-0.8.0.dev8}/livekit/agents/utils/aio/__init__.py
RENAMED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import asyncio
|
|
2
|
+
import contextlib
|
|
2
3
|
|
|
3
4
|
from . import debug
|
|
4
5
|
from .channel import Chan, ChanClosed, ChanReceiver, ChanSender
|
|
@@ -11,7 +12,8 @@ async def gracefully_cancel(*futures: asyncio.Future):
|
|
|
11
12
|
for f in futures:
|
|
12
13
|
f.cancel()
|
|
13
14
|
|
|
14
|
-
|
|
15
|
+
with contextlib.suppress(asyncio.CancelledError):
|
|
16
|
+
await asyncio.gather(*futures)
|
|
15
17
|
|
|
16
18
|
|
|
17
19
|
__all__ = [
|
|
@@ -10,7 +10,7 @@ from .utils import aio
|
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
@unique
|
|
13
|
-
class VADEventType(Enum):
|
|
13
|
+
class VADEventType(str, Enum):
|
|
14
14
|
START_OF_SPEECH = "start_of_speech"
|
|
15
15
|
INFERENCE_DONE = "inference_done"
|
|
16
16
|
END_OF_SPEECH = "end_of_speech"
|
|
@@ -42,8 +42,8 @@ class VADCapabilities:
|
|
|
42
42
|
|
|
43
43
|
|
|
44
44
|
class VAD(ABC):
|
|
45
|
-
def __init__(self, *,
|
|
46
|
-
self._capabilities =
|
|
45
|
+
def __init__(self, *, capabilities: VADCapabilities) -> None:
|
|
46
|
+
self._capabilities = capabilities
|
|
47
47
|
|
|
48
48
|
@property
|
|
49
49
|
def capabilities(self) -> VADCapabilities:
|