@livekit/agents-plugin-livekit 1.0.1 → 1.0.3
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.
|
@@ -53,13 +53,13 @@ class EOURunnerBase extends import_agents.InferenceRunner {
|
|
|
53
53
|
}
|
|
54
54
|
async initialize() {
|
|
55
55
|
const { AutoTokenizer } = await import("@huggingface/transformers");
|
|
56
|
-
const onnxModelPath = await (0, import_hf_utils.downloadFileToCacheDir)({
|
|
57
|
-
repo: import_constants.HG_MODEL_REPO,
|
|
58
|
-
path: import_constants.ONNX_FILEPATH,
|
|
59
|
-
revision: this.modelRevision,
|
|
60
|
-
localFileOnly: true
|
|
61
|
-
});
|
|
62
56
|
try {
|
|
57
|
+
const onnxModelPath = await (0, import_hf_utils.downloadFileToCacheDir)({
|
|
58
|
+
repo: import_constants.HG_MODEL_REPO,
|
|
59
|
+
path: import_constants.ONNX_FILEPATH,
|
|
60
|
+
revision: this.modelRevision,
|
|
61
|
+
localFileOnly: true
|
|
62
|
+
});
|
|
63
63
|
const sessOptions = {
|
|
64
64
|
intraOpNumThreads: Math.max(1, Math.floor(import_node_os.default.cpus().length / 2)),
|
|
65
65
|
interOpNumThreads: 1,
|
|
@@ -71,6 +71,24 @@ class EOURunnerBase extends import_agents.InferenceRunner {
|
|
|
71
71
|
local_files_only: true
|
|
72
72
|
});
|
|
73
73
|
} catch (e) {
|
|
74
|
+
const errorMessage = String(e);
|
|
75
|
+
if (errorMessage.includes("local_files_only=true") || errorMessage.includes("file was not found locally") || errorMessage.includes("File not found in cache")) {
|
|
76
|
+
throw new Error(
|
|
77
|
+
`agents-plugins-livekit failed to initialize ${this.modelType} EOU turn detector: Required model files not found locally.
|
|
78
|
+
|
|
79
|
+
This usually means you need to download the model files first. Please run one of these commands:
|
|
80
|
+
|
|
81
|
+
If using Node.js starter template:
|
|
82
|
+
pnpm download-files
|
|
83
|
+
|
|
84
|
+
If using the agent directly:
|
|
85
|
+
node ./your_agent.ts download-files
|
|
86
|
+
|
|
87
|
+
Then try running your application again.
|
|
88
|
+
|
|
89
|
+
Original error: ${e}`
|
|
90
|
+
);
|
|
91
|
+
}
|
|
74
92
|
throw new Error(
|
|
75
93
|
`agents-plugins-livekit failed to initialize ${this.modelType} EOU turn detector: ${e}`
|
|
76
94
|
);
|
|
@@ -131,7 +149,7 @@ class EOUModel {
|
|
|
131
149
|
constructor(opts) {
|
|
132
150
|
const {
|
|
133
151
|
modelType = "en",
|
|
134
|
-
executor = import_agents.
|
|
152
|
+
executor = (0, import_agents.getJobContext)().inferenceExecutor,
|
|
135
153
|
unlikelyThreshold,
|
|
136
154
|
loadLanguages = true
|
|
137
155
|
} = opts;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/turn_detector/base.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2025 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport { type PreTrainedTokenizer } from '@huggingface/transformers';\nimport type { ipc, llm } from '@livekit/agents';\nimport { CurrentJobContext, Future, InferenceRunner, log } from '@livekit/agents';\nimport { readFileSync } from 'node:fs';\nimport os from 'node:os';\nimport { InferenceSession, Tensor } from 'onnxruntime-node';\nimport { downloadFileToCacheDir } from '../hf_utils.js';\nimport {\n type EOUModelType,\n HG_MODEL_REPO,\n MAX_HISTORY_TURNS,\n MODEL_REVISIONS,\n ONNX_FILEPATH,\n} from './constants.js';\nimport { normalizeText } from './utils.js';\n\ntype RawChatItem = { role: string; content: string };\n\ntype EOUOutput = { eouProbability: number; input: string; duration: number };\n\nexport abstract class EOURunnerBase extends InferenceRunner<RawChatItem[], EOUOutput> {\n private modelType: EOUModelType;\n private modelRevision: string;\n\n private session?: InferenceSession;\n private tokenizer?: PreTrainedTokenizer;\n\n #logger = log();\n\n constructor(modelType: EOUModelType) {\n super();\n this.modelType = modelType;\n this.modelRevision = MODEL_REVISIONS[modelType];\n }\n\n async initialize() {\n const { AutoTokenizer } = await import('@huggingface/transformers');\n\n const onnxModelPath = await downloadFileToCacheDir({\n repo: HG_MODEL_REPO,\n path: ONNX_FILEPATH,\n revision: this.modelRevision,\n localFileOnly: true,\n });\n\n try {\n // TODO(brian): support session config once onnxruntime-node supports it\n const sessOptions: InferenceSession.SessionOptions = {\n intraOpNumThreads: Math.max(1, Math.floor(os.cpus().length / 2)),\n interOpNumThreads: 1,\n executionProviders: [{ name: 'cpu' }],\n };\n\n this.session = await InferenceSession.create(onnxModelPath, sessOptions);\n\n this.tokenizer = await AutoTokenizer.from_pretrained('livekit/turn-detector', {\n revision: this.modelRevision,\n local_files_only: true,\n });\n } catch (e) {\n throw new Error(\n `agents-plugins-livekit failed to initialize ${this.modelType} EOU turn detector: ${e}`,\n );\n }\n }\n\n async run(data: RawChatItem[]) {\n const startTime = Date.now();\n\n const text = this.formatChatCtx(data);\n\n const inputs = this.tokenizer!.encode(text, { add_special_tokens: false });\n this.#logger.debug({ inputs: JSON.stringify(inputs), text }, 'EOU inputs');\n\n const outputs = await this.session!.run(\n { input_ids: new Tensor('int64', inputs, [1, inputs.length]) },\n ['prob'],\n );\n\n const probData = outputs.prob!.data;\n // should be the logits of the last token\n const eouProbability = probData[probData.length - 1] as number;\n const endTime = Date.now();\n\n const result = {\n eouProbability,\n input: text,\n duration: (endTime - startTime) / 1000,\n };\n\n this.#logger.child({ result }).debug('eou prediction');\n return result;\n }\n\n async close() {\n await this.session?.release();\n }\n\n private formatChatCtx(chatCtx: RawChatItem[]): string {\n const newChatCtx: RawChatItem[] = [];\n let lastMsg: RawChatItem | undefined = undefined;\n\n for (const msg of chatCtx) {\n const content = msg.content;\n if (!content) continue;\n\n const norm = normalizeText(content);\n\n // need to combine adjacent turns together to match training data\n if (lastMsg !== undefined && lastMsg.role === msg.role) {\n lastMsg.content += ` ${norm}`;\n } else {\n newChatCtx.push({ role: msg.role, content: norm });\n lastMsg = newChatCtx[newChatCtx.length - 1]!;\n }\n }\n\n // TODO(brian): investigate add_special_tokens options\n const convoText = this.tokenizer!.apply_chat_template(newChatCtx, {\n add_generation_prompt: false,\n tokenize: false,\n }) as string;\n\n // remove the EOU token from current utterance\n return convoText.slice(0, convoText.lastIndexOf('<|im_end|>'));\n }\n}\n\nexport interface EOUModelOptions {\n modelType: EOUModelType;\n executor?: ipc.InferenceExecutor;\n unlikelyThreshold?: number;\n loadLanguages?: boolean;\n}\n\ntype LanguageData = {\n threshold: number;\n};\n\nexport abstract class EOUModel {\n private modelType: EOUModelType;\n private executor: ipc.InferenceExecutor;\n private threshold: number | undefined;\n private loadLanguages: boolean;\n\n protected languagesFuture: Future<Record<string, LanguageData>> = new Future();\n\n #logger = log();\n\n constructor(opts: EOUModelOptions) {\n const {\n modelType = 'en',\n executor = CurrentJobContext.getCurrent().inferenceExecutor,\n unlikelyThreshold,\n loadLanguages = true,\n } = opts;\n\n this.modelType = modelType;\n this.executor = executor;\n this.threshold = unlikelyThreshold;\n this.loadLanguages = loadLanguages;\n\n if (loadLanguages) {\n downloadFileToCacheDir({\n repo: HG_MODEL_REPO,\n path: 'languages.json',\n revision: MODEL_REVISIONS[modelType],\n localFileOnly: true,\n }).then((path) => {\n this.languagesFuture.resolve(JSON.parse(readFileSync(path, 'utf8')));\n });\n }\n }\n\n async unlikelyThreshold(language?: string): Promise<number | undefined> {\n if (language === undefined) {\n return this.threshold;\n }\n\n const lang = language.toLowerCase();\n const languages = await this.languagesFuture.await;\n\n // try the full language code first\n let langData = languages[lang];\n\n if (langData === undefined && lang.includes('-')) {\n const baseLang = lang.split('-')[0]!;\n langData = languages[baseLang];\n }\n\n if (langData === undefined) {\n this.#logger.warn(`Language ${language} not supported by EOU model`);\n return undefined;\n }\n\n // if a custom threshold is provided, use it\n return this.threshold !== undefined ? this.threshold : langData.threshold;\n }\n\n async supportsLanguage(language?: string): Promise<boolean> {\n return (await this.unlikelyThreshold(language)) !== undefined;\n }\n\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n async predictEndOfTurn(chatCtx: llm.ChatContext, timeout: number = 3): Promise<number> {\n let messages: RawChatItem[] = [];\n\n for (const message of chatCtx.items) {\n // skip system and developer messages or tool call messages\n if (message.type !== 'message' || message.role in ['system', 'developer']) {\n continue;\n }\n\n for (const content of message.content) {\n if (typeof content === 'string') {\n messages.push({\n role: message.role === 'assistant' ? 'assistant' : 'user',\n content: content,\n });\n }\n }\n }\n\n messages = messages.slice(-MAX_HISTORY_TURNS);\n\n const result = await this.executor.doInference(this.inferenceMethod(), messages);\n if (result === undefined) {\n throw new Error('EOU inference should always returns a result');\n }\n\n return (result as EOUOutput).eouProbability;\n }\n\n abstract inferenceMethod(): string;\n}\n"],"mappings":"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","names":["os"]}
|
|
1
|
+
{"version":3,"sources":["../../src/turn_detector/base.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2025 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport { type PreTrainedTokenizer } from '@huggingface/transformers';\nimport type { ipc, llm } from '@livekit/agents';\nimport { Future, InferenceRunner, getJobContext, log } from '@livekit/agents';\nimport { readFileSync } from 'node:fs';\nimport os from 'node:os';\nimport { InferenceSession, Tensor } from 'onnxruntime-node';\nimport { downloadFileToCacheDir } from '../hf_utils.js';\nimport {\n type EOUModelType,\n HG_MODEL_REPO,\n MAX_HISTORY_TURNS,\n MODEL_REVISIONS,\n ONNX_FILEPATH,\n} from './constants.js';\nimport { normalizeText } from './utils.js';\n\ntype RawChatItem = { role: string; content: string };\n\ntype EOUOutput = { eouProbability: number; input: string; duration: number };\n\nexport abstract class EOURunnerBase extends InferenceRunner<RawChatItem[], EOUOutput> {\n private modelType: EOUModelType;\n private modelRevision: string;\n\n private session?: InferenceSession;\n private tokenizer?: PreTrainedTokenizer;\n\n #logger = log();\n\n constructor(modelType: EOUModelType) {\n super();\n this.modelType = modelType;\n this.modelRevision = MODEL_REVISIONS[modelType];\n }\n\n async initialize() {\n const { AutoTokenizer } = await import('@huggingface/transformers');\n\n try {\n const onnxModelPath = await downloadFileToCacheDir({\n repo: HG_MODEL_REPO,\n path: ONNX_FILEPATH,\n revision: this.modelRevision,\n localFileOnly: true,\n });\n\n // TODO(brian): support session config once onnxruntime-node supports it\n const sessOptions: InferenceSession.SessionOptions = {\n intraOpNumThreads: Math.max(1, Math.floor(os.cpus().length / 2)),\n interOpNumThreads: 1,\n executionProviders: [{ name: 'cpu' }],\n };\n\n this.session = await InferenceSession.create(onnxModelPath, sessOptions);\n\n this.tokenizer = await AutoTokenizer.from_pretrained('livekit/turn-detector', {\n revision: this.modelRevision,\n local_files_only: true,\n });\n } catch (e) {\n const errorMessage = String(e);\n\n // Check if the error is related to missing local files\n if (\n errorMessage.includes('local_files_only=true') ||\n errorMessage.includes('file was not found locally') ||\n errorMessage.includes('File not found in cache')\n ) {\n throw new Error(\n `agents-plugins-livekit failed to initialize ${this.modelType} EOU turn detector: Required model files not found locally.\\n\\n` +\n `This usually means you need to download the model files first. Please run one of these commands:\\n\\n` +\n ` If using Node.js starter template:\\n` +\n ` pnpm download-files\\n\\n` +\n ` If using the agent directly:\\n` +\n ` node ./your_agent.ts download-files\\n\\n` +\n `Then try running your application again.\\n\\n` +\n `Original error: ${e}`,\n );\n }\n\n throw new Error(\n `agents-plugins-livekit failed to initialize ${this.modelType} EOU turn detector: ${e}`,\n );\n }\n }\n\n async run(data: RawChatItem[]) {\n const startTime = Date.now();\n\n const text = this.formatChatCtx(data);\n\n const inputs = this.tokenizer!.encode(text, { add_special_tokens: false });\n this.#logger.debug({ inputs: JSON.stringify(inputs), text }, 'EOU inputs');\n\n const outputs = await this.session!.run(\n { input_ids: new Tensor('int64', inputs, [1, inputs.length]) },\n ['prob'],\n );\n\n const probData = outputs.prob!.data;\n // should be the logits of the last token\n const eouProbability = probData[probData.length - 1] as number;\n const endTime = Date.now();\n\n const result = {\n eouProbability,\n input: text,\n duration: (endTime - startTime) / 1000,\n };\n\n this.#logger.child({ result }).debug('eou prediction');\n return result;\n }\n\n async close() {\n await this.session?.release();\n }\n\n private formatChatCtx(chatCtx: RawChatItem[]): string {\n const newChatCtx: RawChatItem[] = [];\n let lastMsg: RawChatItem | undefined = undefined;\n\n for (const msg of chatCtx) {\n const content = msg.content;\n if (!content) continue;\n\n const norm = normalizeText(content);\n\n // need to combine adjacent turns together to match training data\n if (lastMsg !== undefined && lastMsg.role === msg.role) {\n lastMsg.content += ` ${norm}`;\n } else {\n newChatCtx.push({ role: msg.role, content: norm });\n lastMsg = newChatCtx[newChatCtx.length - 1]!;\n }\n }\n\n // TODO(brian): investigate add_special_tokens options\n const convoText = this.tokenizer!.apply_chat_template(newChatCtx, {\n add_generation_prompt: false,\n tokenize: false,\n }) as string;\n\n // remove the EOU token from current utterance\n return convoText.slice(0, convoText.lastIndexOf('<|im_end|>'));\n }\n}\n\nexport interface EOUModelOptions {\n modelType: EOUModelType;\n executor?: ipc.InferenceExecutor;\n unlikelyThreshold?: number;\n loadLanguages?: boolean;\n}\n\ntype LanguageData = {\n threshold: number;\n};\n\nexport abstract class EOUModel {\n private modelType: EOUModelType;\n private executor: ipc.InferenceExecutor;\n private threshold: number | undefined;\n private loadLanguages: boolean;\n\n protected languagesFuture: Future<Record<string, LanguageData>> = new Future();\n\n #logger = log();\n\n constructor(opts: EOUModelOptions) {\n const {\n modelType = 'en',\n executor = getJobContext().inferenceExecutor,\n unlikelyThreshold,\n loadLanguages = true,\n } = opts;\n\n this.modelType = modelType;\n this.executor = executor;\n this.threshold = unlikelyThreshold;\n this.loadLanguages = loadLanguages;\n\n if (loadLanguages) {\n downloadFileToCacheDir({\n repo: HG_MODEL_REPO,\n path: 'languages.json',\n revision: MODEL_REVISIONS[modelType],\n localFileOnly: true,\n }).then((path) => {\n this.languagesFuture.resolve(JSON.parse(readFileSync(path, 'utf8')));\n });\n }\n }\n\n async unlikelyThreshold(language?: string): Promise<number | undefined> {\n if (language === undefined) {\n return this.threshold;\n }\n\n const lang = language.toLowerCase();\n const languages = await this.languagesFuture.await;\n\n // try the full language code first\n let langData = languages[lang];\n\n if (langData === undefined && lang.includes('-')) {\n const baseLang = lang.split('-')[0]!;\n langData = languages[baseLang];\n }\n\n if (langData === undefined) {\n this.#logger.warn(`Language ${language} not supported by EOU model`);\n return undefined;\n }\n\n // if a custom threshold is provided, use it\n return this.threshold !== undefined ? this.threshold : langData.threshold;\n }\n\n async supportsLanguage(language?: string): Promise<boolean> {\n return (await this.unlikelyThreshold(language)) !== undefined;\n }\n\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n async predictEndOfTurn(chatCtx: llm.ChatContext, timeout: number = 3): Promise<number> {\n let messages: RawChatItem[] = [];\n\n for (const message of chatCtx.items) {\n // skip system and developer messages or tool call messages\n if (message.type !== 'message' || message.role in ['system', 'developer']) {\n continue;\n }\n\n for (const content of message.content) {\n if (typeof content === 'string') {\n messages.push({\n role: message.role === 'assistant' ? 'assistant' : 'user',\n content: content,\n });\n }\n }\n }\n\n messages = messages.slice(-MAX_HISTORY_TURNS);\n\n const result = await this.executor.doInference(this.inferenceMethod(), messages);\n if (result === undefined) {\n throw new Error('EOU inference should always returns a result');\n }\n\n return (result as EOUOutput).eouProbability;\n }\n\n abstract inferenceMethod(): string;\n}\n"],"mappings":"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","names":["os"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"base.d.ts","sourceRoot":"","sources":["../../src/turn_detector/base.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,
|
|
1
|
+
{"version":3,"file":"base.d.ts","sourceRoot":"","sources":["../../src/turn_detector/base.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,EAAE,MAAM,EAAE,eAAe,EAAsB,MAAM,iBAAiB,CAAC;AAK9E,OAAO,EACL,KAAK,YAAY,EAKlB,MAAM,gBAAgB,CAAC;AAGxB,KAAK,WAAW,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAErD,KAAK,SAAS,GAAG;IAAE,cAAc,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC;AAE7E,8BAAsB,aAAc,SAAQ,eAAe,CAAC,WAAW,EAAE,EAAE,SAAS,CAAC;;IACnF,OAAO,CAAC,SAAS,CAAe;IAChC,OAAO,CAAC,aAAa,CAAS;IAE9B,OAAO,CAAC,OAAO,CAAC,CAAmB;IACnC,OAAO,CAAC,SAAS,CAAC,CAAsB;gBAI5B,SAAS,EAAE,YAAY;IAM7B,UAAU;IAmDV,GAAG,CAAC,IAAI,EAAE,WAAW,EAAE;;;;;IA4BvB,KAAK;IAIX,OAAO,CAAC,aAAa;CA4BtB;AAED,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,YAAY,CAAC;IACxB,QAAQ,CAAC,EAAE,GAAG,CAAC,iBAAiB,CAAC;IACjC,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAED,KAAK,YAAY,GAAG;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,8BAAsB,QAAQ;;IAC5B,OAAO,CAAC,SAAS,CAAe;IAChC,OAAO,CAAC,QAAQ,CAAwB;IACxC,OAAO,CAAC,SAAS,CAAqB;IACtC,OAAO,CAAC,aAAa,CAAU;IAE/B,SAAS,CAAC,eAAe,EAAE,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC,CAAgB;gBAInE,IAAI,EAAE,eAAe;IAyB3B,iBAAiB,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC;IAyBjE,gBAAgB,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAKrD,gBAAgB,CAAC,OAAO,EAAE,GAAG,CAAC,WAAW,EAAE,OAAO,GAAE,MAAU,GAAG,OAAO,CAAC,MAAM,CAAC;IA6BtF,QAAQ,CAAC,eAAe,IAAI,MAAM;CACnC"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import {} from "@huggingface/transformers";
|
|
2
|
-
import {
|
|
2
|
+
import { Future, InferenceRunner, getJobContext, log } from "@livekit/agents";
|
|
3
3
|
import { readFileSync } from "node:fs";
|
|
4
4
|
import os from "node:os";
|
|
5
5
|
import { InferenceSession, Tensor } from "onnxruntime-node";
|
|
@@ -24,13 +24,13 @@ class EOURunnerBase extends InferenceRunner {
|
|
|
24
24
|
}
|
|
25
25
|
async initialize() {
|
|
26
26
|
const { AutoTokenizer } = await import("@huggingface/transformers");
|
|
27
|
-
const onnxModelPath = await downloadFileToCacheDir({
|
|
28
|
-
repo: HG_MODEL_REPO,
|
|
29
|
-
path: ONNX_FILEPATH,
|
|
30
|
-
revision: this.modelRevision,
|
|
31
|
-
localFileOnly: true
|
|
32
|
-
});
|
|
33
27
|
try {
|
|
28
|
+
const onnxModelPath = await downloadFileToCacheDir({
|
|
29
|
+
repo: HG_MODEL_REPO,
|
|
30
|
+
path: ONNX_FILEPATH,
|
|
31
|
+
revision: this.modelRevision,
|
|
32
|
+
localFileOnly: true
|
|
33
|
+
});
|
|
34
34
|
const sessOptions = {
|
|
35
35
|
intraOpNumThreads: Math.max(1, Math.floor(os.cpus().length / 2)),
|
|
36
36
|
interOpNumThreads: 1,
|
|
@@ -42,6 +42,24 @@ class EOURunnerBase extends InferenceRunner {
|
|
|
42
42
|
local_files_only: true
|
|
43
43
|
});
|
|
44
44
|
} catch (e) {
|
|
45
|
+
const errorMessage = String(e);
|
|
46
|
+
if (errorMessage.includes("local_files_only=true") || errorMessage.includes("file was not found locally") || errorMessage.includes("File not found in cache")) {
|
|
47
|
+
throw new Error(
|
|
48
|
+
`agents-plugins-livekit failed to initialize ${this.modelType} EOU turn detector: Required model files not found locally.
|
|
49
|
+
|
|
50
|
+
This usually means you need to download the model files first. Please run one of these commands:
|
|
51
|
+
|
|
52
|
+
If using Node.js starter template:
|
|
53
|
+
pnpm download-files
|
|
54
|
+
|
|
55
|
+
If using the agent directly:
|
|
56
|
+
node ./your_agent.ts download-files
|
|
57
|
+
|
|
58
|
+
Then try running your application again.
|
|
59
|
+
|
|
60
|
+
Original error: ${e}`
|
|
61
|
+
);
|
|
62
|
+
}
|
|
45
63
|
throw new Error(
|
|
46
64
|
`agents-plugins-livekit failed to initialize ${this.modelType} EOU turn detector: ${e}`
|
|
47
65
|
);
|
|
@@ -102,7 +120,7 @@ class EOUModel {
|
|
|
102
120
|
constructor(opts) {
|
|
103
121
|
const {
|
|
104
122
|
modelType = "en",
|
|
105
|
-
executor =
|
|
123
|
+
executor = getJobContext().inferenceExecutor,
|
|
106
124
|
unlikelyThreshold,
|
|
107
125
|
loadLanguages = true
|
|
108
126
|
} = opts;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/turn_detector/base.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2025 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport { type PreTrainedTokenizer } from '@huggingface/transformers';\nimport type { ipc, llm } from '@livekit/agents';\nimport { CurrentJobContext, Future, InferenceRunner, log } from '@livekit/agents';\nimport { readFileSync } from 'node:fs';\nimport os from 'node:os';\nimport { InferenceSession, Tensor } from 'onnxruntime-node';\nimport { downloadFileToCacheDir } from '../hf_utils.js';\nimport {\n type EOUModelType,\n HG_MODEL_REPO,\n MAX_HISTORY_TURNS,\n MODEL_REVISIONS,\n ONNX_FILEPATH,\n} from './constants.js';\nimport { normalizeText } from './utils.js';\n\ntype RawChatItem = { role: string; content: string };\n\ntype EOUOutput = { eouProbability: number; input: string; duration: number };\n\nexport abstract class EOURunnerBase extends InferenceRunner<RawChatItem[], EOUOutput> {\n private modelType: EOUModelType;\n private modelRevision: string;\n\n private session?: InferenceSession;\n private tokenizer?: PreTrainedTokenizer;\n\n #logger = log();\n\n constructor(modelType: EOUModelType) {\n super();\n this.modelType = modelType;\n this.modelRevision = MODEL_REVISIONS[modelType];\n }\n\n async initialize() {\n const { AutoTokenizer } = await import('@huggingface/transformers');\n\n const onnxModelPath = await downloadFileToCacheDir({\n repo: HG_MODEL_REPO,\n path: ONNX_FILEPATH,\n revision: this.modelRevision,\n localFileOnly: true,\n });\n\n try {\n // TODO(brian): support session config once onnxruntime-node supports it\n const sessOptions: InferenceSession.SessionOptions = {\n intraOpNumThreads: Math.max(1, Math.floor(os.cpus().length / 2)),\n interOpNumThreads: 1,\n executionProviders: [{ name: 'cpu' }],\n };\n\n this.session = await InferenceSession.create(onnxModelPath, sessOptions);\n\n this.tokenizer = await AutoTokenizer.from_pretrained('livekit/turn-detector', {\n revision: this.modelRevision,\n local_files_only: true,\n });\n } catch (e) {\n throw new Error(\n `agents-plugins-livekit failed to initialize ${this.modelType} EOU turn detector: ${e}`,\n );\n }\n }\n\n async run(data: RawChatItem[]) {\n const startTime = Date.now();\n\n const text = this.formatChatCtx(data);\n\n const inputs = this.tokenizer!.encode(text, { add_special_tokens: false });\n this.#logger.debug({ inputs: JSON.stringify(inputs), text }, 'EOU inputs');\n\n const outputs = await this.session!.run(\n { input_ids: new Tensor('int64', inputs, [1, inputs.length]) },\n ['prob'],\n );\n\n const probData = outputs.prob!.data;\n // should be the logits of the last token\n const eouProbability = probData[probData.length - 1] as number;\n const endTime = Date.now();\n\n const result = {\n eouProbability,\n input: text,\n duration: (endTime - startTime) / 1000,\n };\n\n this.#logger.child({ result }).debug('eou prediction');\n return result;\n }\n\n async close() {\n await this.session?.release();\n }\n\n private formatChatCtx(chatCtx: RawChatItem[]): string {\n const newChatCtx: RawChatItem[] = [];\n let lastMsg: RawChatItem | undefined = undefined;\n\n for (const msg of chatCtx) {\n const content = msg.content;\n if (!content) continue;\n\n const norm = normalizeText(content);\n\n // need to combine adjacent turns together to match training data\n if (lastMsg !== undefined && lastMsg.role === msg.role) {\n lastMsg.content += ` ${norm}`;\n } else {\n newChatCtx.push({ role: msg.role, content: norm });\n lastMsg = newChatCtx[newChatCtx.length - 1]!;\n }\n }\n\n // TODO(brian): investigate add_special_tokens options\n const convoText = this.tokenizer!.apply_chat_template(newChatCtx, {\n add_generation_prompt: false,\n tokenize: false,\n }) as string;\n\n // remove the EOU token from current utterance\n return convoText.slice(0, convoText.lastIndexOf('<|im_end|>'));\n }\n}\n\nexport interface EOUModelOptions {\n modelType: EOUModelType;\n executor?: ipc.InferenceExecutor;\n unlikelyThreshold?: number;\n loadLanguages?: boolean;\n}\n\ntype LanguageData = {\n threshold: number;\n};\n\nexport abstract class EOUModel {\n private modelType: EOUModelType;\n private executor: ipc.InferenceExecutor;\n private threshold: number | undefined;\n private loadLanguages: boolean;\n\n protected languagesFuture: Future<Record<string, LanguageData>> = new Future();\n\n #logger = log();\n\n constructor(opts: EOUModelOptions) {\n const {\n modelType = 'en',\n executor = CurrentJobContext.getCurrent().inferenceExecutor,\n unlikelyThreshold,\n loadLanguages = true,\n } = opts;\n\n this.modelType = modelType;\n this.executor = executor;\n this.threshold = unlikelyThreshold;\n this.loadLanguages = loadLanguages;\n\n if (loadLanguages) {\n downloadFileToCacheDir({\n repo: HG_MODEL_REPO,\n path: 'languages.json',\n revision: MODEL_REVISIONS[modelType],\n localFileOnly: true,\n }).then((path) => {\n this.languagesFuture.resolve(JSON.parse(readFileSync(path, 'utf8')));\n });\n }\n }\n\n async unlikelyThreshold(language?: string): Promise<number | undefined> {\n if (language === undefined) {\n return this.threshold;\n }\n\n const lang = language.toLowerCase();\n const languages = await this.languagesFuture.await;\n\n // try the full language code first\n let langData = languages[lang];\n\n if (langData === undefined && lang.includes('-')) {\n const baseLang = lang.split('-')[0]!;\n langData = languages[baseLang];\n }\n\n if (langData === undefined) {\n this.#logger.warn(`Language ${language} not supported by EOU model`);\n return undefined;\n }\n\n // if a custom threshold is provided, use it\n return this.threshold !== undefined ? this.threshold : langData.threshold;\n }\n\n async supportsLanguage(language?: string): Promise<boolean> {\n return (await this.unlikelyThreshold(language)) !== undefined;\n }\n\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n async predictEndOfTurn(chatCtx: llm.ChatContext, timeout: number = 3): Promise<number> {\n let messages: RawChatItem[] = [];\n\n for (const message of chatCtx.items) {\n // skip system and developer messages or tool call messages\n if (message.type !== 'message' || message.role in ['system', 'developer']) {\n continue;\n }\n\n for (const content of message.content) {\n if (typeof content === 'string') {\n messages.push({\n role: message.role === 'assistant' ? 'assistant' : 'user',\n content: content,\n });\n }\n }\n }\n\n messages = messages.slice(-MAX_HISTORY_TURNS);\n\n const result = await this.executor.doInference(this.inferenceMethod(), messages);\n if (result === undefined) {\n throw new Error('EOU inference should always returns a result');\n }\n\n return (result as EOUOutput).eouProbability;\n }\n\n abstract inferenceMethod(): string;\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/turn_detector/base.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2025 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport { type PreTrainedTokenizer } from '@huggingface/transformers';\nimport type { ipc, llm } from '@livekit/agents';\nimport { Future, InferenceRunner, getJobContext, log } from '@livekit/agents';\nimport { readFileSync } from 'node:fs';\nimport os from 'node:os';\nimport { InferenceSession, Tensor } from 'onnxruntime-node';\nimport { downloadFileToCacheDir } from '../hf_utils.js';\nimport {\n type EOUModelType,\n HG_MODEL_REPO,\n MAX_HISTORY_TURNS,\n MODEL_REVISIONS,\n ONNX_FILEPATH,\n} from './constants.js';\nimport { normalizeText } from './utils.js';\n\ntype RawChatItem = { role: string; content: string };\n\ntype EOUOutput = { eouProbability: number; input: string; duration: number };\n\nexport abstract class EOURunnerBase extends InferenceRunner<RawChatItem[], EOUOutput> {\n private modelType: EOUModelType;\n private modelRevision: string;\n\n private session?: InferenceSession;\n private tokenizer?: PreTrainedTokenizer;\n\n #logger = log();\n\n constructor(modelType: EOUModelType) {\n super();\n this.modelType = modelType;\n this.modelRevision = MODEL_REVISIONS[modelType];\n }\n\n async initialize() {\n const { AutoTokenizer } = await import('@huggingface/transformers');\n\n try {\n const onnxModelPath = await downloadFileToCacheDir({\n repo: HG_MODEL_REPO,\n path: ONNX_FILEPATH,\n revision: this.modelRevision,\n localFileOnly: true,\n });\n\n // TODO(brian): support session config once onnxruntime-node supports it\n const sessOptions: InferenceSession.SessionOptions = {\n intraOpNumThreads: Math.max(1, Math.floor(os.cpus().length / 2)),\n interOpNumThreads: 1,\n executionProviders: [{ name: 'cpu' }],\n };\n\n this.session = await InferenceSession.create(onnxModelPath, sessOptions);\n\n this.tokenizer = await AutoTokenizer.from_pretrained('livekit/turn-detector', {\n revision: this.modelRevision,\n local_files_only: true,\n });\n } catch (e) {\n const errorMessage = String(e);\n\n // Check if the error is related to missing local files\n if (\n errorMessage.includes('local_files_only=true') ||\n errorMessage.includes('file was not found locally') ||\n errorMessage.includes('File not found in cache')\n ) {\n throw new Error(\n `agents-plugins-livekit failed to initialize ${this.modelType} EOU turn detector: Required model files not found locally.\\n\\n` +\n `This usually means you need to download the model files first. Please run one of these commands:\\n\\n` +\n ` If using Node.js starter template:\\n` +\n ` pnpm download-files\\n\\n` +\n ` If using the agent directly:\\n` +\n ` node ./your_agent.ts download-files\\n\\n` +\n `Then try running your application again.\\n\\n` +\n `Original error: ${e}`,\n );\n }\n\n throw new Error(\n `agents-plugins-livekit failed to initialize ${this.modelType} EOU turn detector: ${e}`,\n );\n }\n }\n\n async run(data: RawChatItem[]) {\n const startTime = Date.now();\n\n const text = this.formatChatCtx(data);\n\n const inputs = this.tokenizer!.encode(text, { add_special_tokens: false });\n this.#logger.debug({ inputs: JSON.stringify(inputs), text }, 'EOU inputs');\n\n const outputs = await this.session!.run(\n { input_ids: new Tensor('int64', inputs, [1, inputs.length]) },\n ['prob'],\n );\n\n const probData = outputs.prob!.data;\n // should be the logits of the last token\n const eouProbability = probData[probData.length - 1] as number;\n const endTime = Date.now();\n\n const result = {\n eouProbability,\n input: text,\n duration: (endTime - startTime) / 1000,\n };\n\n this.#logger.child({ result }).debug('eou prediction');\n return result;\n }\n\n async close() {\n await this.session?.release();\n }\n\n private formatChatCtx(chatCtx: RawChatItem[]): string {\n const newChatCtx: RawChatItem[] = [];\n let lastMsg: RawChatItem | undefined = undefined;\n\n for (const msg of chatCtx) {\n const content = msg.content;\n if (!content) continue;\n\n const norm = normalizeText(content);\n\n // need to combine adjacent turns together to match training data\n if (lastMsg !== undefined && lastMsg.role === msg.role) {\n lastMsg.content += ` ${norm}`;\n } else {\n newChatCtx.push({ role: msg.role, content: norm });\n lastMsg = newChatCtx[newChatCtx.length - 1]!;\n }\n }\n\n // TODO(brian): investigate add_special_tokens options\n const convoText = this.tokenizer!.apply_chat_template(newChatCtx, {\n add_generation_prompt: false,\n tokenize: false,\n }) as string;\n\n // remove the EOU token from current utterance\n return convoText.slice(0, convoText.lastIndexOf('<|im_end|>'));\n }\n}\n\nexport interface EOUModelOptions {\n modelType: EOUModelType;\n executor?: ipc.InferenceExecutor;\n unlikelyThreshold?: number;\n loadLanguages?: boolean;\n}\n\ntype LanguageData = {\n threshold: number;\n};\n\nexport abstract class EOUModel {\n private modelType: EOUModelType;\n private executor: ipc.InferenceExecutor;\n private threshold: number | undefined;\n private loadLanguages: boolean;\n\n protected languagesFuture: Future<Record<string, LanguageData>> = new Future();\n\n #logger = log();\n\n constructor(opts: EOUModelOptions) {\n const {\n modelType = 'en',\n executor = getJobContext().inferenceExecutor,\n unlikelyThreshold,\n loadLanguages = true,\n } = opts;\n\n this.modelType = modelType;\n this.executor = executor;\n this.threshold = unlikelyThreshold;\n this.loadLanguages = loadLanguages;\n\n if (loadLanguages) {\n downloadFileToCacheDir({\n repo: HG_MODEL_REPO,\n path: 'languages.json',\n revision: MODEL_REVISIONS[modelType],\n localFileOnly: true,\n }).then((path) => {\n this.languagesFuture.resolve(JSON.parse(readFileSync(path, 'utf8')));\n });\n }\n }\n\n async unlikelyThreshold(language?: string): Promise<number | undefined> {\n if (language === undefined) {\n return this.threshold;\n }\n\n const lang = language.toLowerCase();\n const languages = await this.languagesFuture.await;\n\n // try the full language code first\n let langData = languages[lang];\n\n if (langData === undefined && lang.includes('-')) {\n const baseLang = lang.split('-')[0]!;\n langData = languages[baseLang];\n }\n\n if (langData === undefined) {\n this.#logger.warn(`Language ${language} not supported by EOU model`);\n return undefined;\n }\n\n // if a custom threshold is provided, use it\n return this.threshold !== undefined ? this.threshold : langData.threshold;\n }\n\n async supportsLanguage(language?: string): Promise<boolean> {\n return (await this.unlikelyThreshold(language)) !== undefined;\n }\n\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n async predictEndOfTurn(chatCtx: llm.ChatContext, timeout: number = 3): Promise<number> {\n let messages: RawChatItem[] = [];\n\n for (const message of chatCtx.items) {\n // skip system and developer messages or tool call messages\n if (message.type !== 'message' || message.role in ['system', 'developer']) {\n continue;\n }\n\n for (const content of message.content) {\n if (typeof content === 'string') {\n messages.push({\n role: message.role === 'assistant' ? 'assistant' : 'user',\n content: content,\n });\n }\n }\n }\n\n messages = messages.slice(-MAX_HISTORY_TURNS);\n\n const result = await this.executor.doInference(this.inferenceMethod(), messages);\n if (result === undefined) {\n throw new Error('EOU inference should always returns a result');\n }\n\n return (result as EOUOutput).eouProbability;\n }\n\n abstract inferenceMethod(): string;\n}\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@livekit/agents-plugin-livekit",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.3",
|
|
4
4
|
"description": "Additional utilities for LiveKit Node Agents",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"require": "dist/index.cjs",
|
|
@@ -29,10 +29,10 @@
|
|
|
29
29
|
"onnxruntime-common": "1.21.0",
|
|
30
30
|
"tsup": "^8.3.5",
|
|
31
31
|
"typescript": "^5.0.0",
|
|
32
|
-
"@livekit/agents": "1.0.
|
|
32
|
+
"@livekit/agents": "1.0.3"
|
|
33
33
|
},
|
|
34
34
|
"peerDependencies": {
|
|
35
|
-
"@livekit/agents": "1.0.
|
|
35
|
+
"@livekit/agents": "1.0.3"
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
38
|
"@huggingface/hub": "2.4.1",
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// SPDX-License-Identifier: Apache-2.0
|
|
4
4
|
import { type PreTrainedTokenizer } from '@huggingface/transformers';
|
|
5
5
|
import type { ipc, llm } from '@livekit/agents';
|
|
6
|
-
import {
|
|
6
|
+
import { Future, InferenceRunner, getJobContext, log } from '@livekit/agents';
|
|
7
7
|
import { readFileSync } from 'node:fs';
|
|
8
8
|
import os from 'node:os';
|
|
9
9
|
import { InferenceSession, Tensor } from 'onnxruntime-node';
|
|
@@ -39,14 +39,14 @@ export abstract class EOURunnerBase extends InferenceRunner<RawChatItem[], EOUOu
|
|
|
39
39
|
async initialize() {
|
|
40
40
|
const { AutoTokenizer } = await import('@huggingface/transformers');
|
|
41
41
|
|
|
42
|
-
const onnxModelPath = await downloadFileToCacheDir({
|
|
43
|
-
repo: HG_MODEL_REPO,
|
|
44
|
-
path: ONNX_FILEPATH,
|
|
45
|
-
revision: this.modelRevision,
|
|
46
|
-
localFileOnly: true,
|
|
47
|
-
});
|
|
48
|
-
|
|
49
42
|
try {
|
|
43
|
+
const onnxModelPath = await downloadFileToCacheDir({
|
|
44
|
+
repo: HG_MODEL_REPO,
|
|
45
|
+
path: ONNX_FILEPATH,
|
|
46
|
+
revision: this.modelRevision,
|
|
47
|
+
localFileOnly: true,
|
|
48
|
+
});
|
|
49
|
+
|
|
50
50
|
// TODO(brian): support session config once onnxruntime-node supports it
|
|
51
51
|
const sessOptions: InferenceSession.SessionOptions = {
|
|
52
52
|
intraOpNumThreads: Math.max(1, Math.floor(os.cpus().length / 2)),
|
|
@@ -61,6 +61,26 @@ export abstract class EOURunnerBase extends InferenceRunner<RawChatItem[], EOUOu
|
|
|
61
61
|
local_files_only: true,
|
|
62
62
|
});
|
|
63
63
|
} catch (e) {
|
|
64
|
+
const errorMessage = String(e);
|
|
65
|
+
|
|
66
|
+
// Check if the error is related to missing local files
|
|
67
|
+
if (
|
|
68
|
+
errorMessage.includes('local_files_only=true') ||
|
|
69
|
+
errorMessage.includes('file was not found locally') ||
|
|
70
|
+
errorMessage.includes('File not found in cache')
|
|
71
|
+
) {
|
|
72
|
+
throw new Error(
|
|
73
|
+
`agents-plugins-livekit failed to initialize ${this.modelType} EOU turn detector: Required model files not found locally.\n\n` +
|
|
74
|
+
`This usually means you need to download the model files first. Please run one of these commands:\n\n` +
|
|
75
|
+
` If using Node.js starter template:\n` +
|
|
76
|
+
` pnpm download-files\n\n` +
|
|
77
|
+
` If using the agent directly:\n` +
|
|
78
|
+
` node ./your_agent.ts download-files\n\n` +
|
|
79
|
+
`Then try running your application again.\n\n` +
|
|
80
|
+
`Original error: ${e}`,
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
64
84
|
throw new Error(
|
|
65
85
|
`agents-plugins-livekit failed to initialize ${this.modelType} EOU turn detector: ${e}`,
|
|
66
86
|
);
|
|
@@ -153,7 +173,7 @@ export abstract class EOUModel {
|
|
|
153
173
|
constructor(opts: EOUModelOptions) {
|
|
154
174
|
const {
|
|
155
175
|
modelType = 'en',
|
|
156
|
-
executor =
|
|
176
|
+
executor = getJobContext().inferenceExecutor,
|
|
157
177
|
unlikelyThreshold,
|
|
158
178
|
loadLanguages = true,
|
|
159
179
|
} = opts;
|