@livekit/agents 0.6.4 → 0.7.1
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/cli.cjs +8 -0
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +8 -0
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +6 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/inference_runner.cjs +38 -0
- package/dist/inference_runner.cjs.map +1 -0
- package/dist/inference_runner.d.ts +11 -0
- package/dist/inference_runner.d.ts.map +1 -0
- package/dist/inference_runner.js +14 -0
- package/dist/inference_runner.js.map +1 -0
- package/dist/ipc/index.cjs +23 -0
- package/dist/ipc/index.cjs.map +1 -0
- package/dist/ipc/index.d.ts +2 -0
- package/dist/ipc/index.d.ts.map +1 -0
- package/dist/ipc/index.js +2 -0
- package/dist/ipc/index.js.map +1 -0
- package/dist/ipc/inference_executor.cjs +17 -0
- package/dist/ipc/inference_executor.cjs.map +1 -0
- package/dist/ipc/inference_executor.d.ts +4 -0
- package/dist/ipc/inference_executor.d.ts.map +1 -0
- package/dist/ipc/inference_executor.js +1 -0
- package/dist/ipc/inference_executor.js.map +1 -0
- package/dist/ipc/inference_proc_executor.cjs +97 -0
- package/dist/ipc/inference_proc_executor.cjs.map +1 -0
- package/dist/ipc/inference_proc_executor.d.ts +23 -0
- package/dist/ipc/inference_proc_executor.d.ts.map +1 -0
- package/dist/ipc/inference_proc_executor.js +72 -0
- package/dist/ipc/inference_proc_executor.js.map +1 -0
- package/dist/ipc/inference_proc_lazy_main.cjs +92 -0
- package/dist/ipc/inference_proc_lazy_main.cjs.map +1 -0
- package/dist/ipc/inference_proc_lazy_main.d.ts +2 -0
- package/dist/ipc/inference_proc_lazy_main.d.ts.map +1 -0
- package/dist/ipc/inference_proc_lazy_main.js +69 -0
- package/dist/ipc/inference_proc_lazy_main.js.map +1 -0
- package/dist/ipc/job_executor.cjs +8 -7
- package/dist/ipc/job_executor.cjs.map +1 -1
- package/dist/ipc/job_executor.d.ts +14 -15
- package/dist/ipc/job_executor.d.ts.map +1 -1
- package/dist/ipc/job_executor.js +7 -6
- package/dist/ipc/job_executor.js.map +1 -1
- package/dist/ipc/job_proc_executor.cjs +108 -0
- package/dist/ipc/job_proc_executor.cjs.map +1 -0
- package/dist/ipc/job_proc_executor.d.ts +19 -0
- package/dist/ipc/job_proc_executor.d.ts.map +1 -0
- package/dist/ipc/job_proc_executor.js +83 -0
- package/dist/ipc/job_proc_executor.js.map +1 -0
- package/dist/ipc/{job_main.cjs → job_proc_lazy_main.cjs} +46 -36
- package/dist/ipc/job_proc_lazy_main.cjs.map +1 -0
- package/dist/ipc/job_proc_lazy_main.d.ts +2 -0
- package/dist/ipc/job_proc_lazy_main.d.ts.map +1 -0
- package/dist/ipc/{job_main.js → job_proc_lazy_main.js} +46 -11
- package/dist/ipc/job_proc_lazy_main.js.map +1 -0
- package/dist/ipc/message.cjs.map +1 -1
- package/dist/ipc/message.d.ts +17 -0
- package/dist/ipc/message.d.ts.map +1 -1
- package/dist/ipc/proc_pool.cjs +30 -4
- package/dist/ipc/proc_pool.cjs.map +1 -1
- package/dist/ipc/proc_pool.d.ts +5 -1
- package/dist/ipc/proc_pool.d.ts.map +1 -1
- package/dist/ipc/proc_pool.js +30 -4
- package/dist/ipc/proc_pool.js.map +1 -1
- package/dist/ipc/{proc_job_executor.cjs → supervised_proc.cjs} +57 -45
- package/dist/ipc/supervised_proc.cjs.map +1 -0
- package/dist/ipc/supervised_proc.d.ts +30 -0
- package/dist/ipc/supervised_proc.d.ts.map +1 -0
- package/dist/ipc/{proc_job_executor.js → supervised_proc.js} +53 -31
- package/dist/ipc/supervised_proc.js.map +1 -0
- package/dist/job.cjs +18 -1
- package/dist/job.cjs.map +1 -1
- package/dist/job.d.ts +9 -1
- package/dist/job.d.ts.map +1 -1
- package/dist/job.js +17 -1
- package/dist/job.js.map +1 -1
- package/dist/multimodal/agent_playout.cjs +18 -16
- package/dist/multimodal/agent_playout.cjs.map +1 -1
- package/dist/multimodal/agent_playout.d.ts +4 -4
- package/dist/multimodal/agent_playout.d.ts.map +1 -1
- package/dist/multimodal/agent_playout.js +18 -16
- package/dist/multimodal/agent_playout.js.map +1 -1
- package/dist/multimodal/multimodal_agent.cjs +12 -8
- package/dist/multimodal/multimodal_agent.cjs.map +1 -1
- package/dist/multimodal/multimodal_agent.d.ts.map +1 -1
- package/dist/multimodal/multimodal_agent.js +13 -9
- package/dist/multimodal/multimodal_agent.js.map +1 -1
- package/dist/pipeline/agent_output.cjs +22 -4
- package/dist/pipeline/agent_output.cjs.map +1 -1
- package/dist/pipeline/agent_output.d.ts +4 -2
- package/dist/pipeline/agent_output.d.ts.map +1 -1
- package/dist/pipeline/agent_output.js +22 -4
- package/dist/pipeline/agent_output.js.map +1 -1
- package/dist/pipeline/agent_playout.cjs +9 -3
- package/dist/pipeline/agent_playout.cjs.map +1 -1
- package/dist/pipeline/agent_playout.d.ts +4 -2
- package/dist/pipeline/agent_playout.d.ts.map +1 -1
- package/dist/pipeline/agent_playout.js +9 -3
- package/dist/pipeline/agent_playout.js.map +1 -1
- package/dist/pipeline/human_input.cjs +6 -0
- package/dist/pipeline/human_input.cjs.map +1 -1
- package/dist/pipeline/human_input.d.ts +3 -1
- package/dist/pipeline/human_input.d.ts.map +1 -1
- package/dist/pipeline/human_input.js +6 -0
- package/dist/pipeline/human_input.js.map +1 -1
- package/dist/pipeline/pipeline_agent.cjs +79 -12
- package/dist/pipeline/pipeline_agent.cjs.map +1 -1
- package/dist/pipeline/pipeline_agent.d.ts +8 -0
- package/dist/pipeline/pipeline_agent.d.ts.map +1 -1
- package/dist/pipeline/pipeline_agent.js +79 -12
- package/dist/pipeline/pipeline_agent.js.map +1 -1
- package/dist/stt/stream_adapter.cjs +16 -4
- package/dist/stt/stream_adapter.cjs.map +1 -1
- package/dist/stt/stream_adapter.d.ts.map +1 -1
- package/dist/stt/stream_adapter.js +16 -4
- package/dist/stt/stream_adapter.js.map +1 -1
- package/dist/tokenize/basic/basic.cjs +2 -0
- package/dist/tokenize/basic/basic.cjs.map +1 -1
- package/dist/tokenize/basic/basic.d.ts +2 -0
- package/dist/tokenize/basic/basic.d.ts.map +1 -1
- package/dist/tokenize/basic/basic.js +1 -0
- package/dist/tokenize/basic/basic.js.map +1 -1
- package/dist/tokenize/basic/index.cjs +2 -0
- package/dist/tokenize/basic/index.cjs.map +1 -1
- package/dist/tokenize/basic/index.d.ts +1 -1
- package/dist/tokenize/basic/index.d.ts.map +1 -1
- package/dist/tokenize/basic/index.js +8 -1
- package/dist/tokenize/basic/index.js.map +1 -1
- package/dist/tokenize/token_stream.cjs +5 -3
- package/dist/tokenize/token_stream.cjs.map +1 -1
- package/dist/tokenize/token_stream.d.ts.map +1 -1
- package/dist/tokenize/token_stream.js +5 -3
- package/dist/tokenize/token_stream.js.map +1 -1
- package/dist/transcription.cjs +203 -86
- package/dist/transcription.cjs.map +1 -1
- package/dist/transcription.d.ts +24 -17
- package/dist/transcription.d.ts.map +1 -1
- package/dist/transcription.js +201 -85
- package/dist/transcription.js.map +1 -1
- package/dist/worker.cjs +42 -9
- package/dist/worker.cjs.map +1 -1
- package/dist/worker.d.ts +5 -1
- package/dist/worker.d.ts.map +1 -1
- package/dist/worker.js +42 -9
- package/dist/worker.js.map +1 -1
- package/package.json +3 -3
- package/src/cli.ts +9 -0
- package/src/index.ts +3 -1
- package/src/inference_runner.ts +19 -0
- package/src/ipc/index.ts +5 -0
- package/src/ipc/inference_executor.ts +7 -0
- package/src/ipc/inference_proc_executor.ts +93 -0
- package/src/ipc/inference_proc_lazy_main.ts +90 -0
- package/src/ipc/job_executor.ts +15 -17
- package/src/ipc/job_proc_executor.ts +112 -0
- package/src/ipc/{job_main.ts → job_proc_lazy_main.ts} +52 -14
- package/src/ipc/message.ts +14 -1
- package/src/ipc/proc_pool.ts +33 -3
- package/src/ipc/{proc_job_executor.ts → supervised_proc.ts} +77 -29
- package/src/job.ts +21 -0
- package/src/multimodal/agent_playout.ts +19 -18
- package/src/multimodal/multimodal_agent.ts +13 -9
- package/src/pipeline/agent_output.ts +36 -5
- package/src/pipeline/agent_playout.ts +10 -1
- package/src/pipeline/human_input.ts +8 -0
- package/src/pipeline/pipeline_agent.ts +96 -11
- package/src/stt/stream_adapter.ts +17 -5
- package/src/tokenize/basic/basic.ts +2 -0
- package/src/tokenize/basic/index.ts +7 -1
- package/src/tokenize/token_stream.ts +6 -3
- package/src/transcription.ts +270 -96
- package/src/worker.ts +42 -5
- package/dist/ipc/job_main.cjs.map +0 -1
- package/dist/ipc/job_main.d.ts +0 -8
- package/dist/ipc/job_main.d.ts.map +0 -1
- package/dist/ipc/job_main.js.map +0 -1
- package/dist/ipc/proc_job_executor.cjs.map +0 -1
- package/dist/ipc/proc_job_executor.d.ts +0 -15
- package/dist/ipc/proc_job_executor.d.ts.map +0 -1
- package/dist/ipc/proc_job_executor.js.map +0 -1
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: 2025 LiveKit, Inc.
|
|
2
|
+
//
|
|
3
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
import type { ChildProcess } from 'node:child_process';
|
|
5
|
+
import { fork } from 'node:child_process';
|
|
6
|
+
import type { RunningJobInfo } from '../job.js';
|
|
7
|
+
import { log } from '../log.js';
|
|
8
|
+
import type { InferenceExecutor } from './inference_executor.js';
|
|
9
|
+
import type { JobExecutor } from './job_executor.js';
|
|
10
|
+
import { JobStatus } from './job_executor.js';
|
|
11
|
+
import type { IPCMessage } from './message.js';
|
|
12
|
+
import { SupervisedProc } from './supervised_proc.js';
|
|
13
|
+
|
|
14
|
+
export class JobProcExecutor extends SupervisedProc implements JobExecutor {
|
|
15
|
+
#userArgs?: any;
|
|
16
|
+
#jobStatus?: JobStatus;
|
|
17
|
+
#runningJob?: RunningJobInfo;
|
|
18
|
+
#agent: string;
|
|
19
|
+
#inferenceExecutor?: InferenceExecutor;
|
|
20
|
+
#inferenceTasks: Promise<void>[] = [];
|
|
21
|
+
#logger = log();
|
|
22
|
+
|
|
23
|
+
constructor(
|
|
24
|
+
agent: string,
|
|
25
|
+
inferenceExecutor: InferenceExecutor | undefined,
|
|
26
|
+
initializeTimeout: number,
|
|
27
|
+
closeTimeout: number,
|
|
28
|
+
memoryWarnMB: number,
|
|
29
|
+
memoryLimitMB: number,
|
|
30
|
+
pingInterval: number,
|
|
31
|
+
pingTimeout: number,
|
|
32
|
+
highPingThreshold: number,
|
|
33
|
+
) {
|
|
34
|
+
super(
|
|
35
|
+
initializeTimeout,
|
|
36
|
+
closeTimeout,
|
|
37
|
+
memoryWarnMB,
|
|
38
|
+
memoryLimitMB,
|
|
39
|
+
pingInterval,
|
|
40
|
+
pingTimeout,
|
|
41
|
+
highPingThreshold,
|
|
42
|
+
);
|
|
43
|
+
this.#agent = agent;
|
|
44
|
+
this.#inferenceExecutor = inferenceExecutor;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
get status(): JobStatus {
|
|
48
|
+
if (this.#jobStatus) {
|
|
49
|
+
return this.#jobStatus;
|
|
50
|
+
}
|
|
51
|
+
throw new Error('job status not available');
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
get userArguments(): any {
|
|
55
|
+
return this.#userArgs;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
set userArguments(args: any) {
|
|
59
|
+
this.#userArgs = args;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
get runningJob(): RunningJobInfo | undefined {
|
|
63
|
+
return this.#runningJob;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
createProcess(): ChildProcess {
|
|
67
|
+
return fork(new URL(import.meta.resolve('./job_proc_lazy_main.js')), [this.#agent]);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async mainTask(proc: ChildProcess) {
|
|
71
|
+
proc.on('message', (msg: IPCMessage) => {
|
|
72
|
+
switch (msg.case) {
|
|
73
|
+
case 'inferenceRequest':
|
|
74
|
+
this.#inferenceTasks.push(this.#doInferenceTask(proc, msg.value));
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async #doInferenceTask(
|
|
80
|
+
proc: ChildProcess,
|
|
81
|
+
req: { method: string; requestId: string; data: unknown },
|
|
82
|
+
) {
|
|
83
|
+
if (!this.#inferenceExecutor) {
|
|
84
|
+
this.#logger.warn('inference request received but no inference executor');
|
|
85
|
+
proc.send({
|
|
86
|
+
case: 'inferenceResponse',
|
|
87
|
+
value: { requestId: req.requestId, error: new Error('no inference executor') },
|
|
88
|
+
});
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
try {
|
|
93
|
+
const data = await this.#inferenceExecutor.doInference(req.method, req.data);
|
|
94
|
+
proc.send({ case: 'inferenceResponse', value: { requestId: req.requestId, data } });
|
|
95
|
+
} catch (error) {
|
|
96
|
+
proc.send({ case: 'inferenceResponse', value: { requestId: req.requestId, error } });
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
async launchJob(info: RunningJobInfo) {
|
|
101
|
+
if (this.#runningJob) {
|
|
102
|
+
throw Error('process already has a running job');
|
|
103
|
+
}
|
|
104
|
+
if (!this.init.done) {
|
|
105
|
+
throw Error('process not initialized');
|
|
106
|
+
}
|
|
107
|
+
this.#jobStatus = JobStatus.RUNNING;
|
|
108
|
+
this.#runningJob = info;
|
|
109
|
+
|
|
110
|
+
this.proc!.send({ case: 'startJobRequest', value: { runningJob: info } });
|
|
111
|
+
}
|
|
112
|
+
}
|
|
@@ -2,34 +2,63 @@
|
|
|
2
2
|
//
|
|
3
3
|
// SPDX-License-Identifier: Apache-2.0
|
|
4
4
|
import { Room, RoomEvent } from '@livekit/rtc-node';
|
|
5
|
-
import
|
|
6
|
-
import { fork } from 'node:child_process';
|
|
5
|
+
import { randomUUID } from 'node:crypto';
|
|
7
6
|
import { EventEmitter, once } from 'node:events';
|
|
8
7
|
import { pathToFileURL } from 'node:url';
|
|
9
8
|
import type { Logger } from 'pino';
|
|
10
9
|
import { type Agent, isAgent } from '../generator.js';
|
|
11
|
-
import type
|
|
12
|
-
import { JobContext } from '../job.js';
|
|
13
|
-
import { JobProcess } from '../job.js';
|
|
10
|
+
import { CurrentJobContext, JobContext, JobProcess, type RunningJobInfo } from '../job.js';
|
|
14
11
|
import { initializeLogger, log } from '../log.js';
|
|
15
12
|
import { defaultInitializeProcessFunc } from '../worker.js';
|
|
13
|
+
import type { InferenceExecutor } from './inference_executor.js';
|
|
16
14
|
import type { IPCMessage } from './message.js';
|
|
17
15
|
|
|
18
16
|
const ORPHANED_TIMEOUT = 15 * 1000;
|
|
19
17
|
|
|
20
|
-
type StartArgs = {
|
|
21
|
-
agentFile: string;
|
|
22
|
-
// userArguments: unknown;
|
|
23
|
-
};
|
|
24
|
-
|
|
25
18
|
type JobTask = {
|
|
26
19
|
ctx: JobContext;
|
|
27
20
|
task: Promise<void>;
|
|
28
21
|
};
|
|
29
22
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
23
|
+
class PendingInference {
|
|
24
|
+
promise = new Promise<{ requestId: string; data: unknown; error?: Error }>((resolve) => {
|
|
25
|
+
this.resolve = resolve; // this is how JavaScript lets you resolve promises externally
|
|
26
|
+
});
|
|
27
|
+
resolve(arg: { requestId: string; data: unknown; error?: Error }) {
|
|
28
|
+
arg; // useless call to counteract TypeScript E6133
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
class InfClient implements InferenceExecutor {
|
|
33
|
+
#requests: { [id: string]: PendingInference } = {};
|
|
34
|
+
|
|
35
|
+
constructor() {
|
|
36
|
+
process.on('message', (msg: IPCMessage) => {
|
|
37
|
+
switch (msg.case) {
|
|
38
|
+
case 'inferenceResponse':
|
|
39
|
+
const fut = this.#requests[msg.value.requestId];
|
|
40
|
+
delete this.#requests[msg.value.requestId];
|
|
41
|
+
if (!fut) {
|
|
42
|
+
log().child({ resp: msg.value }).warn('received unexpected inference response');
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
fut.resolve(msg.value);
|
|
46
|
+
break;
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async doInference(method: string, data: unknown): Promise<unknown> {
|
|
52
|
+
const requestId = 'inference_job_' + randomUUID;
|
|
53
|
+
process.send!({ case: 'inferenceRequest', value: { requestId, method, data } });
|
|
54
|
+
this.#requests[requestId] = new PendingInference();
|
|
55
|
+
const resp = await this.#requests[requestId]!.promise;
|
|
56
|
+
if (resp.error) {
|
|
57
|
+
throw new Error(`inference of ${method} failed: ${resp.error.message}`);
|
|
58
|
+
}
|
|
59
|
+
return resp.data;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
33
62
|
|
|
34
63
|
const startJob = (
|
|
35
64
|
proc: JobProcess,
|
|
@@ -54,7 +83,8 @@ const startJob = (
|
|
|
54
83
|
closeEvent.emit('close', true, reason);
|
|
55
84
|
};
|
|
56
85
|
|
|
57
|
-
const ctx = new JobContext(proc, info, room, onConnect, onShutdown);
|
|
86
|
+
const ctx = new JobContext(proc, info, room, onConnect, onShutdown, new InfClient());
|
|
87
|
+
new CurrentJobContext(ctx);
|
|
58
88
|
|
|
59
89
|
const task = new Promise<void>(async () => {
|
|
60
90
|
const unconnectedTimeout = setTimeout(() => {
|
|
@@ -110,6 +140,10 @@ const startJob = (
|
|
|
110
140
|
// this is handled in cli, triggering a termination of all child processes at once.
|
|
111
141
|
process.on('SIGINT', () => {});
|
|
112
142
|
|
|
143
|
+
// don't do anything on SIGTERM
|
|
144
|
+
// Render uses SIGTERM in autoscale, this ensures the processes are properly drained if needed
|
|
145
|
+
process.on('SIGTERM', () => {});
|
|
146
|
+
|
|
113
147
|
await once(process, 'message').then(([msg]: IPCMessage[]) => {
|
|
114
148
|
msg = msg!;
|
|
115
149
|
if (msg.case !== 'initializeRequest') {
|
|
@@ -120,6 +154,10 @@ const startJob = (
|
|
|
120
154
|
const proc = new JobProcess();
|
|
121
155
|
let logger = log().child({ pid: proc.pid });
|
|
122
156
|
|
|
157
|
+
process.on('unhandledRejection', (reason) => {
|
|
158
|
+
logger.error(reason);
|
|
159
|
+
});
|
|
160
|
+
|
|
123
161
|
logger.debug('initializing job runner');
|
|
124
162
|
agent.prewarm(proc);
|
|
125
163
|
logger.debug('job runner initialized');
|
package/src/ipc/message.ts
CHANGED
|
@@ -7,7 +7,12 @@ import type { LoggerOptions } from '../log.js';
|
|
|
7
7
|
export type IPCMessage =
|
|
8
8
|
| {
|
|
9
9
|
case: 'initializeRequest';
|
|
10
|
-
value: {
|
|
10
|
+
value: {
|
|
11
|
+
loggerOptions: LoggerOptions;
|
|
12
|
+
pingInterval?: number;
|
|
13
|
+
pingTimeout?: number;
|
|
14
|
+
highPingThreshold?: number;
|
|
15
|
+
};
|
|
11
16
|
}
|
|
12
17
|
| {
|
|
13
18
|
case: 'initializeResponse';
|
|
@@ -29,6 +34,14 @@ export type IPCMessage =
|
|
|
29
34
|
case: 'shutdownRequest';
|
|
30
35
|
value: { reason?: string };
|
|
31
36
|
}
|
|
37
|
+
| {
|
|
38
|
+
case: 'inferenceRequest';
|
|
39
|
+
value: { method: string; requestId: string; data: unknown };
|
|
40
|
+
}
|
|
41
|
+
| {
|
|
42
|
+
case: 'inferenceResponse';
|
|
43
|
+
value: { requestId: string; data: unknown; error?: Error };
|
|
44
|
+
}
|
|
32
45
|
| {
|
|
33
46
|
case: 'exiting';
|
|
34
47
|
value: { reason?: string };
|
package/src/ipc/proc_pool.ts
CHANGED
|
@@ -4,8 +4,9 @@
|
|
|
4
4
|
import { MultiMutex, Mutex } from '@livekit/mutex';
|
|
5
5
|
import type { RunningJobInfo } from '../job.js';
|
|
6
6
|
import { Queue } from '../utils.js';
|
|
7
|
+
import type { InferenceExecutor } from './inference_executor.js';
|
|
7
8
|
import type { JobExecutor } from './job_executor.js';
|
|
8
|
-
import {
|
|
9
|
+
import { JobProcExecutor } from './job_proc_executor.js';
|
|
9
10
|
|
|
10
11
|
export class ProcPool {
|
|
11
12
|
agent: string;
|
|
@@ -20,12 +21,18 @@ export class ProcPool {
|
|
|
20
21
|
procMutex?: MultiMutex;
|
|
21
22
|
procUnlock?: () => void;
|
|
22
23
|
warmedProcQueue = new Queue<JobExecutor>();
|
|
24
|
+
inferenceExecutor?: InferenceExecutor;
|
|
25
|
+
memoryWarnMB: number;
|
|
26
|
+
memoryLimitMB: number;
|
|
23
27
|
|
|
24
28
|
constructor(
|
|
25
29
|
agent: string,
|
|
26
30
|
numIdleProcesses: number,
|
|
27
31
|
initializeTimeout: number,
|
|
28
32
|
closeTimeout: number,
|
|
33
|
+
inferenceExecutor: InferenceExecutor | undefined,
|
|
34
|
+
memoryWarnMB: number,
|
|
35
|
+
memoryLimitMB: number,
|
|
29
36
|
) {
|
|
30
37
|
this.agent = agent;
|
|
31
38
|
if (numIdleProcesses > 0) {
|
|
@@ -33,6 +40,9 @@ export class ProcPool {
|
|
|
33
40
|
}
|
|
34
41
|
this.initializeTimeout = initializeTimeout;
|
|
35
42
|
this.closeTimeout = closeTimeout;
|
|
43
|
+
this.inferenceExecutor = inferenceExecutor;
|
|
44
|
+
this.memoryWarnMB = memoryWarnMB;
|
|
45
|
+
this.memoryLimitMB = memoryLimitMB;
|
|
36
46
|
}
|
|
37
47
|
|
|
38
48
|
get processes(): JobExecutor[] {
|
|
@@ -52,7 +62,17 @@ export class ProcPool {
|
|
|
52
62
|
this.procUnlock = undefined;
|
|
53
63
|
}
|
|
54
64
|
} else {
|
|
55
|
-
proc = new
|
|
65
|
+
proc = new JobProcExecutor(
|
|
66
|
+
this.agent,
|
|
67
|
+
this.inferenceExecutor,
|
|
68
|
+
this.initializeTimeout,
|
|
69
|
+
this.closeTimeout,
|
|
70
|
+
this.memoryWarnMB,
|
|
71
|
+
this.memoryLimitMB,
|
|
72
|
+
2500,
|
|
73
|
+
60000,
|
|
74
|
+
500,
|
|
75
|
+
);
|
|
56
76
|
this.executors.push(proc);
|
|
57
77
|
await proc.start();
|
|
58
78
|
await proc.initialize();
|
|
@@ -61,7 +81,17 @@ export class ProcPool {
|
|
|
61
81
|
}
|
|
62
82
|
|
|
63
83
|
async procWatchTask() {
|
|
64
|
-
const proc = new
|
|
84
|
+
const proc = new JobProcExecutor(
|
|
85
|
+
this.agent,
|
|
86
|
+
this.inferenceExecutor,
|
|
87
|
+
this.initializeTimeout,
|
|
88
|
+
this.closeTimeout,
|
|
89
|
+
this.memoryWarnMB,
|
|
90
|
+
this.memoryLimitMB,
|
|
91
|
+
2500,
|
|
92
|
+
60000,
|
|
93
|
+
500,
|
|
94
|
+
);
|
|
65
95
|
|
|
66
96
|
try {
|
|
67
97
|
this.executors.push(proc);
|
|
@@ -6,31 +6,54 @@ import { once } from 'node:events';
|
|
|
6
6
|
import type { RunningJobInfo } from '../job.js';
|
|
7
7
|
import { log, loggerOptions } from '../log.js';
|
|
8
8
|
import { Future } from '../utils.js';
|
|
9
|
-
import type { ProcOpts } from './job_executor.js';
|
|
10
|
-
import { JobExecutor } from './job_executor.js';
|
|
11
9
|
import type { IPCMessage } from './message.js';
|
|
12
10
|
|
|
13
|
-
export
|
|
11
|
+
export interface ProcOpts {
|
|
12
|
+
initializeTimeout: number;
|
|
13
|
+
closeTimeout: number;
|
|
14
|
+
memoryWarnMB: number;
|
|
15
|
+
memoryLimitMB: number;
|
|
16
|
+
pingInterval: number;
|
|
17
|
+
pingTimeout: number;
|
|
18
|
+
highPingThreshold: number;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export abstract class SupervisedProc {
|
|
14
22
|
#opts: ProcOpts;
|
|
15
23
|
#started = false;
|
|
16
24
|
#closing = false;
|
|
17
25
|
#runningJob?: RunningJobInfo = undefined;
|
|
18
|
-
|
|
26
|
+
proc?: ChildProcess;
|
|
19
27
|
#pingInterval?: ReturnType<typeof setInterval>;
|
|
28
|
+
#memoryWatch?: ReturnType<typeof setInterval>;
|
|
20
29
|
#pongTimeout?: ReturnType<typeof setTimeout>;
|
|
21
|
-
|
|
30
|
+
protected init = new Future();
|
|
22
31
|
#join = new Future();
|
|
23
32
|
#logger = log().child({ runningJob: this.#runningJob });
|
|
24
33
|
|
|
25
|
-
constructor(
|
|
26
|
-
|
|
34
|
+
constructor(
|
|
35
|
+
initializeTimeout: number,
|
|
36
|
+
closeTimeout: number,
|
|
37
|
+
memoryWarnMB: number,
|
|
38
|
+
memoryLimitMB: number,
|
|
39
|
+
pingInterval: number,
|
|
40
|
+
pingTimeout: number,
|
|
41
|
+
highPingThreshold: number,
|
|
42
|
+
) {
|
|
27
43
|
this.#opts = {
|
|
28
|
-
agent,
|
|
29
44
|
initializeTimeout,
|
|
30
45
|
closeTimeout,
|
|
46
|
+
memoryWarnMB,
|
|
47
|
+
memoryLimitMB,
|
|
48
|
+
pingInterval,
|
|
49
|
+
pingTimeout,
|
|
50
|
+
highPingThreshold,
|
|
31
51
|
};
|
|
32
52
|
}
|
|
33
53
|
|
|
54
|
+
abstract createProcess(): ChildProcess;
|
|
55
|
+
abstract mainTask(child: ChildProcess): Promise<void>;
|
|
56
|
+
|
|
34
57
|
get started(): boolean {
|
|
35
58
|
return this.#started;
|
|
36
59
|
}
|
|
@@ -46,36 +69,50 @@ export class ProcJobExecutor extends JobExecutor {
|
|
|
46
69
|
throw new Error('runner is closed');
|
|
47
70
|
}
|
|
48
71
|
|
|
49
|
-
this
|
|
50
|
-
m.runProcess({
|
|
51
|
-
agentFile: this.#opts.agent,
|
|
52
|
-
}),
|
|
53
|
-
);
|
|
72
|
+
this.proc = this.createProcess();
|
|
54
73
|
|
|
55
74
|
this.#started = true;
|
|
56
75
|
this.run();
|
|
57
76
|
}
|
|
58
77
|
|
|
59
78
|
async run() {
|
|
60
|
-
await this
|
|
79
|
+
await this.init.await;
|
|
61
80
|
|
|
62
81
|
this.#pingInterval = setInterval(() => {
|
|
63
|
-
this
|
|
64
|
-
}, this.
|
|
82
|
+
this.proc!.send({ case: 'pingRequest', value: { timestamp: Date.now() } });
|
|
83
|
+
}, this.#opts.pingInterval);
|
|
65
84
|
|
|
66
85
|
this.#pongTimeout = setTimeout(() => {
|
|
67
86
|
this.#logger.warn('job is unresponsive');
|
|
68
87
|
clearTimeout(this.#pongTimeout);
|
|
69
88
|
clearInterval(this.#pingInterval);
|
|
70
|
-
this
|
|
89
|
+
this.proc!.kill();
|
|
71
90
|
this.#join.resolve();
|
|
72
|
-
}, this.
|
|
91
|
+
}, this.#opts.pingTimeout);
|
|
92
|
+
|
|
93
|
+
this.#memoryWatch = setInterval(() => {
|
|
94
|
+
const memoryMB = process.memoryUsage().heapUsed / (1024 * 1024);
|
|
95
|
+
if (this.#opts.memoryLimitMB > 0 && memoryMB > this.#opts.memoryLimitMB) {
|
|
96
|
+
this.#logger
|
|
97
|
+
.child({ memoryUsageMB: memoryMB, memoryLimitMB: this.#opts.memoryLimitMB })
|
|
98
|
+
.error('process exceeded memory limit, killing process');
|
|
99
|
+
this.close();
|
|
100
|
+
} else if (this.#opts.memoryWarnMB > 0 && memoryMB > this.#opts.memoryWarnMB) {
|
|
101
|
+
this.#logger
|
|
102
|
+
.child({
|
|
103
|
+
memoryUsageMB: memoryMB,
|
|
104
|
+
memoryWarnMB: this.#opts.memoryWarnMB,
|
|
105
|
+
memoryLimitMB: this.#opts.memoryLimitMB,
|
|
106
|
+
})
|
|
107
|
+
.error('process memory usage is high');
|
|
108
|
+
}
|
|
109
|
+
});
|
|
73
110
|
|
|
74
111
|
const listener = (msg: IPCMessage) => {
|
|
75
112
|
switch (msg.case) {
|
|
76
113
|
case 'pongResponse': {
|
|
77
114
|
const delay = Date.now() - msg.value.timestamp;
|
|
78
|
-
if (delay > this.
|
|
115
|
+
if (delay > this.#opts.highPingThreshold) {
|
|
79
116
|
this.#logger.child({ delay }).warn('job executor is unresponsive');
|
|
80
117
|
}
|
|
81
118
|
this.#pongTimeout?.refresh();
|
|
@@ -87,23 +124,26 @@ export class ProcJobExecutor extends JobExecutor {
|
|
|
87
124
|
}
|
|
88
125
|
case 'done': {
|
|
89
126
|
this.#closing = true;
|
|
90
|
-
this
|
|
127
|
+
this.proc!.off('message', listener);
|
|
91
128
|
this.#join.resolve();
|
|
92
129
|
break;
|
|
93
130
|
}
|
|
94
131
|
}
|
|
95
132
|
};
|
|
96
|
-
this
|
|
97
|
-
this
|
|
133
|
+
this.proc!.on('message', listener);
|
|
134
|
+
this.proc!.on('error', (err) => {
|
|
98
135
|
if (this.#closing) return;
|
|
99
136
|
this.#logger
|
|
100
137
|
.child({ err })
|
|
101
138
|
.warn('job process exited unexpectedly; this likely means the error above caused a crash');
|
|
102
139
|
clearTimeout(this.#pongTimeout);
|
|
103
140
|
clearInterval(this.#pingInterval);
|
|
141
|
+
clearInterval(this.#memoryWatch);
|
|
104
142
|
this.#join.resolve();
|
|
105
143
|
});
|
|
106
144
|
|
|
145
|
+
this.mainTask(this.proc!);
|
|
146
|
+
|
|
107
147
|
await this.#join.await;
|
|
108
148
|
}
|
|
109
149
|
|
|
@@ -118,17 +158,25 @@ export class ProcJobExecutor extends JobExecutor {
|
|
|
118
158
|
async initialize() {
|
|
119
159
|
const timer = setTimeout(() => {
|
|
120
160
|
const err = new Error('runner initialization timed out');
|
|
121
|
-
this
|
|
161
|
+
this.init.reject(err);
|
|
122
162
|
throw err;
|
|
123
163
|
}, this.#opts.initializeTimeout);
|
|
124
|
-
this
|
|
125
|
-
|
|
164
|
+
this.proc!.send({
|
|
165
|
+
case: 'initializeRequest',
|
|
166
|
+
value: {
|
|
167
|
+
loggerOptions,
|
|
168
|
+
pingInterval: this.#opts.pingInterval,
|
|
169
|
+
pingTimeout: this.#opts.pingTimeout,
|
|
170
|
+
highPingThreshold: this.#opts.highPingThreshold,
|
|
171
|
+
},
|
|
172
|
+
});
|
|
173
|
+
await once(this.proc!, 'message').then(([msg]: IPCMessage[]) => {
|
|
126
174
|
clearTimeout(timer);
|
|
127
175
|
if (msg!.case !== 'initializeResponse') {
|
|
128
176
|
throw new Error('first message must be InitializeResponse');
|
|
129
177
|
}
|
|
130
178
|
});
|
|
131
|
-
this
|
|
179
|
+
this.init.resolve();
|
|
132
180
|
}
|
|
133
181
|
|
|
134
182
|
async close() {
|
|
@@ -138,11 +186,11 @@ export class ProcJobExecutor extends JobExecutor {
|
|
|
138
186
|
this.#closing = true;
|
|
139
187
|
|
|
140
188
|
if (!this.#runningJob) {
|
|
141
|
-
this
|
|
189
|
+
this.proc!.kill();
|
|
142
190
|
this.#join.resolve();
|
|
143
191
|
}
|
|
144
192
|
|
|
145
|
-
this
|
|
193
|
+
this.proc!.send({ case: 'shutdownRequest' });
|
|
146
194
|
|
|
147
195
|
const timer = setTimeout(() => {
|
|
148
196
|
this.#logger.error('job shutdown is taking too much time');
|
|
@@ -159,6 +207,6 @@ export class ProcJobExecutor extends JobExecutor {
|
|
|
159
207
|
throw new Error('executor already has a running job');
|
|
160
208
|
}
|
|
161
209
|
this.#runningJob = info;
|
|
162
|
-
this
|
|
210
|
+
this.proc!.send({ case: 'startJobRequest', value: { runningJob: info } });
|
|
163
211
|
}
|
|
164
212
|
}
|
package/src/job.ts
CHANGED
|
@@ -11,8 +11,21 @@ import type {
|
|
|
11
11
|
} from '@livekit/rtc-node';
|
|
12
12
|
import { ParticipantKind, RoomEvent, TrackKind } from '@livekit/rtc-node';
|
|
13
13
|
import type { Logger } from 'pino';
|
|
14
|
+
import type { InferenceExecutor } from './ipc/inference_executor.js';
|
|
14
15
|
import { log } from './log.js';
|
|
15
16
|
|
|
17
|
+
export class CurrentJobContext {
|
|
18
|
+
static #current: JobContext;
|
|
19
|
+
|
|
20
|
+
constructor(proc: JobContext) {
|
|
21
|
+
CurrentJobContext.#current = proc;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
static getCurrent(): JobContext {
|
|
25
|
+
return CurrentJobContext.#current;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
16
29
|
/** Which tracks, if any, should the agent automatically subscribe to? */
|
|
17
30
|
export enum AutoSubscribe {
|
|
18
31
|
SUBSCRIBE_ALL,
|
|
@@ -60,6 +73,7 @@ export class JobContext {
|
|
|
60
73
|
};
|
|
61
74
|
} = {};
|
|
62
75
|
#logger: Logger;
|
|
76
|
+
#inferenceExecutor: InferenceExecutor;
|
|
63
77
|
|
|
64
78
|
constructor(
|
|
65
79
|
proc: JobProcess,
|
|
@@ -67,6 +81,7 @@ export class JobContext {
|
|
|
67
81
|
room: Room,
|
|
68
82
|
onConnect: () => void,
|
|
69
83
|
onShutdown: (s: string) => void,
|
|
84
|
+
inferenceExecutor: InferenceExecutor,
|
|
70
85
|
) {
|
|
71
86
|
this.#proc = proc;
|
|
72
87
|
this.#info = info;
|
|
@@ -76,6 +91,7 @@ export class JobContext {
|
|
|
76
91
|
this.onParticipantConnected = this.onParticipantConnected.bind(this);
|
|
77
92
|
this.#room.on(RoomEvent.ParticipantConnected, this.onParticipantConnected);
|
|
78
93
|
this.#logger = log().child({ info: this.#info });
|
|
94
|
+
this.#inferenceExecutor = inferenceExecutor;
|
|
79
95
|
}
|
|
80
96
|
|
|
81
97
|
get proc(): JobProcess {
|
|
@@ -96,6 +112,11 @@ export class JobContext {
|
|
|
96
112
|
return this.#room.localParticipant;
|
|
97
113
|
}
|
|
98
114
|
|
|
115
|
+
/** @returns The global inference executor */
|
|
116
|
+
get inferenceExecutor(): InferenceExecutor {
|
|
117
|
+
return this.#inferenceExecutor;
|
|
118
|
+
}
|
|
119
|
+
|
|
99
120
|
/** Adds a promise to be awaited when {@link JobContext.shutdown | shutdown} is called. */
|
|
100
121
|
addShutdownCallback(callback: () => Promise<void>) {
|
|
101
122
|
this.shutdownCallbacks.push(callback);
|