@elizaos/plugin-local-ai 1.0.0-alpha.2 → 1.0.0-alpha.20
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/index.js +1482 -1001
- package/dist/index.js.map +1 -1
- package/package.json +9 -9
- package/dist/index.d.ts +0 -5
package/dist/index.js
CHANGED
|
@@ -5,411 +5,97 @@ import {
|
|
|
5
5
|
} from "./chunk-TIOOHHYI.js";
|
|
6
6
|
|
|
7
7
|
// src/index.ts
|
|
8
|
+
import fs5 from "node:fs";
|
|
9
|
+
import path5 from "node:path";
|
|
10
|
+
import { Readable as Readable2 } from "node:stream";
|
|
11
|
+
import { fileURLToPath } from "node:url";
|
|
8
12
|
import {
|
|
9
|
-
|
|
10
|
-
|
|
13
|
+
ModelTypes as ModelTypes3,
|
|
14
|
+
logger as logger10
|
|
11
15
|
} from "@elizaos/core";
|
|
12
16
|
import { EmbeddingModel, FlagEmbedding } from "fastembed";
|
|
13
17
|
import {
|
|
14
|
-
|
|
15
|
-
|
|
18
|
+
LlamaChatSession,
|
|
19
|
+
getLlama as getLlama2
|
|
16
20
|
} from "node-llama-cpp";
|
|
17
|
-
import path5 from "node:path";
|
|
18
|
-
import { Readable as Readable2 } from "node:stream";
|
|
19
|
-
import { fileURLToPath } from "node:url";
|
|
20
|
-
import fs5 from "node:fs";
|
|
21
21
|
|
|
22
|
-
// src/
|
|
22
|
+
// src/environment.ts
|
|
23
23
|
import { logger } from "@elizaos/core";
|
|
24
|
-
import
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
24
|
+
import { z } from "zod";
|
|
25
|
+
var configSchema = z.object({
|
|
26
|
+
USE_LOCAL_AI: z.boolean().default(true),
|
|
27
|
+
USE_STUDIOLM_TEXT_MODELS: z.boolean().default(false),
|
|
28
|
+
USE_OLLAMA_TEXT_MODELS: z.boolean().default(false),
|
|
29
|
+
// Ollama Configuration
|
|
30
|
+
OLLAMA_SERVER_URL: z.string().default("http://localhost:11434"),
|
|
31
|
+
OLLAMA_MODEL: z.string().default("deepseek-r1-distill-qwen-7b"),
|
|
32
|
+
USE_OLLAMA_EMBEDDING: z.boolean().default(false),
|
|
33
|
+
OLLAMA_EMBEDDING_MODEL: z.string().default(""),
|
|
34
|
+
SMALL_OLLAMA_MODEL: z.string().default("deepseek-r1:1.5b"),
|
|
35
|
+
MEDIUM_OLLAMA_MODEL: z.string().default("deepseek-r1:7b"),
|
|
36
|
+
LARGE_OLLAMA_MODEL: z.string().default("deepseek-r1:7b"),
|
|
37
|
+
// StudioLM Configuration
|
|
38
|
+
STUDIOLM_SERVER_URL: z.string().default("http://localhost:1234"),
|
|
39
|
+
STUDIOLM_SMALL_MODEL: z.string().default("lmstudio-community/deepseek-r1-distill-qwen-1.5b"),
|
|
40
|
+
STUDIOLM_MEDIUM_MODEL: z.string().default("deepseek-r1-distill-qwen-7b"),
|
|
41
|
+
STUDIOLM_EMBEDDING_MODEL: z.union([z.boolean(), z.string()]).default(false)
|
|
42
|
+
});
|
|
43
|
+
function validateModelConfig(config) {
|
|
44
|
+
logger.info("Validating model configuration with values:", {
|
|
45
|
+
USE_LOCAL_AI: config.USE_LOCAL_AI,
|
|
46
|
+
USE_STUDIOLM_TEXT_MODELS: config.USE_STUDIOLM_TEXT_MODELS,
|
|
47
|
+
USE_OLLAMA_TEXT_MODELS: config.USE_OLLAMA_TEXT_MODELS
|
|
48
|
+
});
|
|
49
|
+
if (!config.USE_LOCAL_AI) {
|
|
50
|
+
config.USE_LOCAL_AI = true;
|
|
51
|
+
logger.info("Setting USE_LOCAL_AI to true as it's required");
|
|
38
52
|
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
} catch (error) {
|
|
44
|
-
logger.error("Platform detection failed", { error });
|
|
45
|
-
throw error;
|
|
46
|
-
}
|
|
53
|
+
if (config.USE_STUDIOLM_TEXT_MODELS && config.USE_OLLAMA_TEXT_MODELS) {
|
|
54
|
+
throw new Error(
|
|
55
|
+
"StudioLM and Ollama text models cannot be enabled simultaneously"
|
|
56
|
+
);
|
|
47
57
|
}
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
recommendedModelSize,
|
|
59
|
-
supportedBackends
|
|
58
|
+
logger.info("Configuration is valid");
|
|
59
|
+
}
|
|
60
|
+
async function validateConfig(config) {
|
|
61
|
+
try {
|
|
62
|
+
const booleanConfig = {
|
|
63
|
+
USE_LOCAL_AI: true,
|
|
64
|
+
// Always true
|
|
65
|
+
USE_STUDIOLM_TEXT_MODELS: config.USE_STUDIOLM_TEXT_MODELS === "true",
|
|
66
|
+
USE_OLLAMA_TEXT_MODELS: config.USE_OLLAMA_TEXT_MODELS === "true",
|
|
67
|
+
USE_OLLAMA_EMBEDDING: config.USE_OLLAMA_EMBEDDING === "true"
|
|
60
68
|
};
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
}
|
|
69
|
+
validateModelConfig(booleanConfig);
|
|
70
|
+
const fullConfig = {
|
|
71
|
+
...booleanConfig,
|
|
72
|
+
OLLAMA_SERVER_URL: config.OLLAMA_SERVER_URL || "http://localhost:11434",
|
|
73
|
+
OLLAMA_MODEL: config.OLLAMA_MODEL || "deepseek-r1-distill-qwen-7b",
|
|
74
|
+
OLLAMA_EMBEDDING_MODEL: config.OLLAMA_EMBEDDING_MODEL || "",
|
|
75
|
+
SMALL_OLLAMA_MODEL: config.SMALL_OLLAMA_MODEL || "deepseek-r1:1.5b",
|
|
76
|
+
MEDIUM_OLLAMA_MODEL: config.MEDIUM_OLLAMA_MODEL || "deepseek-r1:7b",
|
|
77
|
+
LARGE_OLLAMA_MODEL: config.LARGE_OLLAMA_MODEL || "deepseek-r1:7b",
|
|
78
|
+
STUDIOLM_SERVER_URL: config.STUDIOLM_SERVER_URL || "http://localhost:1234",
|
|
79
|
+
STUDIOLM_SMALL_MODEL: config.STUDIOLM_SMALL_MODEL || "lmstudio-community/deepseek-r1-distill-qwen-1.5b",
|
|
80
|
+
STUDIOLM_MEDIUM_MODEL: config.STUDIOLM_MEDIUM_MODEL || "deepseek-r1-distill-qwen-7b",
|
|
81
|
+
STUDIOLM_EMBEDDING_MODEL: config.STUDIOLM_EMBEDDING_MODEL || false
|
|
75
82
|
};
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
return await this.detectWindowsGPU();
|
|
85
|
-
case "linux":
|
|
86
|
-
return await this.detectLinuxGPU();
|
|
87
|
-
default:
|
|
88
|
-
return null;
|
|
89
|
-
}
|
|
90
|
-
} catch (error) {
|
|
91
|
-
logger.error("GPU detection failed", { error });
|
|
92
|
-
return null;
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
async detectMacGPU() {
|
|
96
|
-
try {
|
|
97
|
-
const { stdout } = await execAsync("sysctl -n machdep.cpu.brand_string");
|
|
98
|
-
const isAppleSilicon = stdout.toLowerCase().includes("apple");
|
|
99
|
-
if (isAppleSilicon) {
|
|
100
|
-
return {
|
|
101
|
-
name: "Apple Silicon",
|
|
102
|
-
type: "metal",
|
|
103
|
-
isAppleSilicon: true
|
|
104
|
-
};
|
|
105
|
-
}
|
|
106
|
-
const { stdout: gpuInfo } = await execAsync(
|
|
107
|
-
"system_profiler SPDisplaysDataType"
|
|
108
|
-
);
|
|
109
|
-
return {
|
|
110
|
-
name: gpuInfo.split("Chipset Model:")[1]?.split("\n")[0]?.trim() || "Unknown GPU",
|
|
111
|
-
type: "metal",
|
|
112
|
-
isAppleSilicon: false
|
|
113
|
-
};
|
|
114
|
-
} catch (error) {
|
|
115
|
-
logger.error("Mac GPU detection failed", { error });
|
|
116
|
-
return {
|
|
117
|
-
name: "Unknown Mac GPU",
|
|
118
|
-
type: "metal",
|
|
119
|
-
isAppleSilicon: false
|
|
120
|
-
};
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
async detectWindowsGPU() {
|
|
124
|
-
try {
|
|
125
|
-
const { stdout } = await execAsync(
|
|
126
|
-
"wmic path win32_VideoController get name"
|
|
127
|
-
);
|
|
128
|
-
const gpuName = stdout.split("\n")[1].trim();
|
|
129
|
-
if (gpuName.toLowerCase().includes("nvidia")) {
|
|
130
|
-
const { stdout: nvidiaInfo } = await execAsync(
|
|
131
|
-
"nvidia-smi --query-gpu=name,memory.total --format=csv,noheader"
|
|
132
|
-
);
|
|
133
|
-
const [name, memoryStr] = nvidiaInfo.split(",").map((s) => s.trim());
|
|
134
|
-
const memory = Number.parseInt(memoryStr);
|
|
135
|
-
return {
|
|
136
|
-
name,
|
|
137
|
-
memory,
|
|
138
|
-
type: "cuda",
|
|
139
|
-
version: await this.getNvidiaDriverVersion()
|
|
140
|
-
};
|
|
141
|
-
}
|
|
142
|
-
return {
|
|
143
|
-
name: gpuName,
|
|
144
|
-
type: "directml"
|
|
145
|
-
};
|
|
146
|
-
} catch (error) {
|
|
147
|
-
logger.error("Windows GPU detection failed", { error });
|
|
148
|
-
return null;
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
async detectLinuxGPU() {
|
|
152
|
-
try {
|
|
153
|
-
const { stdout } = await execAsync(
|
|
154
|
-
"nvidia-smi --query-gpu=name,memory.total --format=csv,noheader"
|
|
155
|
-
);
|
|
156
|
-
if (stdout) {
|
|
157
|
-
const [name, memoryStr] = stdout.split(",").map((s) => s.trim());
|
|
158
|
-
const memory = Number.parseInt(memoryStr);
|
|
159
|
-
return {
|
|
160
|
-
name,
|
|
161
|
-
memory,
|
|
162
|
-
type: "cuda",
|
|
163
|
-
version: await this.getNvidiaDriverVersion()
|
|
164
|
-
};
|
|
165
|
-
}
|
|
166
|
-
} catch {
|
|
167
|
-
try {
|
|
168
|
-
const { stdout } = await execAsync("lspci | grep -i vga");
|
|
169
|
-
return {
|
|
170
|
-
name: stdout.split(":").pop()?.trim() || "Unknown GPU",
|
|
171
|
-
type: "none"
|
|
172
|
-
};
|
|
173
|
-
} catch (error) {
|
|
174
|
-
logger.error("Linux GPU detection failed", { error });
|
|
175
|
-
return null;
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
return null;
|
|
179
|
-
}
|
|
180
|
-
async getNvidiaDriverVersion() {
|
|
181
|
-
try {
|
|
182
|
-
const { stdout } = await execAsync(
|
|
183
|
-
"nvidia-smi --query-gpu=driver_version --format=csv,noheader"
|
|
184
|
-
);
|
|
185
|
-
return stdout.trim();
|
|
186
|
-
} catch {
|
|
187
|
-
return "unknown";
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
async getSupportedBackends(platform, gpu) {
|
|
191
|
-
const backends = ["cpu"];
|
|
192
|
-
if (gpu) {
|
|
193
|
-
switch (platform) {
|
|
194
|
-
case "darwin":
|
|
195
|
-
backends.push("metal");
|
|
196
|
-
break;
|
|
197
|
-
case "win32":
|
|
198
|
-
if (gpu.type === "cuda") {
|
|
199
|
-
backends.push("cuda");
|
|
200
|
-
}
|
|
201
|
-
backends.push("directml");
|
|
202
|
-
break;
|
|
203
|
-
case "linux":
|
|
204
|
-
if (gpu.type === "cuda") {
|
|
205
|
-
backends.push("cuda");
|
|
206
|
-
}
|
|
207
|
-
break;
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
return backends;
|
|
211
|
-
}
|
|
212
|
-
getRecommendedModelSize(cpu, gpu) {
|
|
213
|
-
if (gpu?.isAppleSilicon) {
|
|
214
|
-
return cpu.memory.total > 16 * 1024 * 1024 * 1024 ? "medium" : "small";
|
|
215
|
-
}
|
|
216
|
-
if (gpu?.type === "cuda") {
|
|
217
|
-
const gpuMemGB = (gpu.memory || 0) / 1024;
|
|
218
|
-
if (gpuMemGB >= 16) return "large";
|
|
219
|
-
if (gpuMemGB >= 8) return "medium";
|
|
220
|
-
}
|
|
221
|
-
if (cpu.memory.total > 32 * 1024 * 1024 * 1024) return "medium";
|
|
222
|
-
return "small";
|
|
223
|
-
}
|
|
224
|
-
getCapabilities() {
|
|
225
|
-
if (!this.capabilities) {
|
|
226
|
-
throw new Error("PlatformManager not initialized");
|
|
227
|
-
}
|
|
228
|
-
return this.capabilities;
|
|
229
|
-
}
|
|
230
|
-
isAppleSilicon() {
|
|
231
|
-
return !!this.capabilities?.gpu?.isAppleSilicon;
|
|
232
|
-
}
|
|
233
|
-
hasGPUSupport() {
|
|
234
|
-
return !!this.capabilities?.gpu;
|
|
235
|
-
}
|
|
236
|
-
supportsCUDA() {
|
|
237
|
-
return this.capabilities?.gpu?.type === "cuda";
|
|
238
|
-
}
|
|
239
|
-
supportsMetal() {
|
|
240
|
-
return this.capabilities?.gpu?.type === "metal";
|
|
241
|
-
}
|
|
242
|
-
supportsDirectML() {
|
|
243
|
-
return this.capabilities?.gpu?.type === "directml";
|
|
244
|
-
}
|
|
245
|
-
getRecommendedBackend() {
|
|
246
|
-
if (!this.capabilities) {
|
|
247
|
-
throw new Error("PlatformManager not initialized");
|
|
248
|
-
}
|
|
249
|
-
const { gpu, supportedBackends } = this.capabilities;
|
|
250
|
-
if (gpu?.type === "cuda") return "cuda";
|
|
251
|
-
if (gpu?.type === "metal") return "metal";
|
|
252
|
-
if (supportedBackends.includes("directml")) return "directml";
|
|
253
|
-
return "cpu";
|
|
254
|
-
}
|
|
255
|
-
};
|
|
256
|
-
var getPlatformManager = () => {
|
|
257
|
-
return PlatformManager.getInstance();
|
|
258
|
-
};
|
|
259
|
-
|
|
260
|
-
// src/utils/tokenizerManager.ts
|
|
261
|
-
import {
|
|
262
|
-
AutoTokenizer
|
|
263
|
-
} from "@huggingface/transformers";
|
|
264
|
-
import { logger as logger2 } from "@elizaos/core";
|
|
265
|
-
var TokenizerManager = class _TokenizerManager {
|
|
266
|
-
static instance = null;
|
|
267
|
-
tokenizers;
|
|
268
|
-
cacheDir;
|
|
269
|
-
modelsDir;
|
|
270
|
-
constructor(cacheDir, modelsDir) {
|
|
271
|
-
this.tokenizers = /* @__PURE__ */ new Map();
|
|
272
|
-
this.cacheDir = cacheDir;
|
|
273
|
-
this.modelsDir = modelsDir;
|
|
274
|
-
}
|
|
275
|
-
static getInstance(cacheDir, modelsDir) {
|
|
276
|
-
if (!_TokenizerManager.instance) {
|
|
277
|
-
_TokenizerManager.instance = new _TokenizerManager(cacheDir, modelsDir);
|
|
278
|
-
}
|
|
279
|
-
return _TokenizerManager.instance;
|
|
280
|
-
}
|
|
281
|
-
async loadTokenizer(modelConfig) {
|
|
282
|
-
try {
|
|
283
|
-
const tokenizerKey = `${modelConfig.tokenizer.type}-${modelConfig.tokenizer.name}`;
|
|
284
|
-
logger2.info("Loading tokenizer:", {
|
|
285
|
-
key: tokenizerKey,
|
|
286
|
-
name: modelConfig.tokenizer.name,
|
|
287
|
-
type: modelConfig.tokenizer.type,
|
|
288
|
-
modelsDir: this.modelsDir,
|
|
289
|
-
cacheDir: this.cacheDir
|
|
290
|
-
});
|
|
291
|
-
if (this.tokenizers.has(tokenizerKey)) {
|
|
292
|
-
logger2.info("Using cached tokenizer:", { key: tokenizerKey });
|
|
293
|
-
const cachedTokenizer = this.tokenizers.get(tokenizerKey);
|
|
294
|
-
if (!cachedTokenizer) {
|
|
295
|
-
throw new Error(
|
|
296
|
-
`Tokenizer ${tokenizerKey} exists in map but returned undefined`
|
|
297
|
-
);
|
|
298
|
-
}
|
|
299
|
-
return cachedTokenizer;
|
|
300
|
-
}
|
|
301
|
-
const fs6 = await import("node:fs");
|
|
302
|
-
if (!fs6.existsSync(this.modelsDir)) {
|
|
303
|
-
logger2.warn(
|
|
304
|
-
"Models directory does not exist, creating it:",
|
|
305
|
-
this.modelsDir
|
|
306
|
-
);
|
|
307
|
-
fs6.mkdirSync(this.modelsDir, { recursive: true });
|
|
308
|
-
}
|
|
309
|
-
logger2.info(
|
|
310
|
-
"Initializing new tokenizer from HuggingFace with models directory:",
|
|
311
|
-
this.modelsDir
|
|
312
|
-
);
|
|
313
|
-
try {
|
|
314
|
-
const tokenizer = await AutoTokenizer.from_pretrained(
|
|
315
|
-
modelConfig.tokenizer.name,
|
|
316
|
-
{
|
|
317
|
-
cache_dir: this.modelsDir,
|
|
318
|
-
local_files_only: false
|
|
319
|
-
}
|
|
320
|
-
);
|
|
321
|
-
this.tokenizers.set(tokenizerKey, tokenizer);
|
|
322
|
-
logger2.success("Tokenizer loaded successfully:", { key: tokenizerKey });
|
|
323
|
-
return tokenizer;
|
|
324
|
-
} catch (tokenizeError) {
|
|
325
|
-
logger2.error("Failed to load tokenizer from HuggingFace:", {
|
|
326
|
-
error: tokenizeError instanceof Error ? tokenizeError.message : String(tokenizeError),
|
|
327
|
-
stack: tokenizeError instanceof Error ? tokenizeError.stack : void 0,
|
|
328
|
-
tokenizer: modelConfig.tokenizer.name,
|
|
329
|
-
modelsDir: this.modelsDir
|
|
330
|
-
});
|
|
331
|
-
logger2.info("Retrying tokenizer loading...");
|
|
332
|
-
const tokenizer = await AutoTokenizer.from_pretrained(
|
|
333
|
-
modelConfig.tokenizer.name,
|
|
334
|
-
{
|
|
335
|
-
cache_dir: this.modelsDir,
|
|
336
|
-
local_files_only: false
|
|
337
|
-
}
|
|
338
|
-
);
|
|
339
|
-
this.tokenizers.set(tokenizerKey, tokenizer);
|
|
340
|
-
logger2.success("Tokenizer loaded successfully on retry:", {
|
|
341
|
-
key: tokenizerKey
|
|
342
|
-
});
|
|
343
|
-
return tokenizer;
|
|
344
|
-
}
|
|
345
|
-
} catch (error) {
|
|
346
|
-
logger2.error("Failed to load tokenizer:", {
|
|
347
|
-
error: error instanceof Error ? error.message : String(error),
|
|
348
|
-
stack: error instanceof Error ? error.stack : void 0,
|
|
349
|
-
model: modelConfig.name,
|
|
350
|
-
tokenizer: modelConfig.tokenizer.name,
|
|
351
|
-
modelsDir: this.modelsDir
|
|
352
|
-
});
|
|
353
|
-
throw error;
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
async encode(text, modelConfig) {
|
|
357
|
-
try {
|
|
358
|
-
logger2.info("Encoding text with tokenizer:", {
|
|
359
|
-
length: text.length,
|
|
360
|
-
tokenizer: modelConfig.tokenizer.name
|
|
361
|
-
});
|
|
362
|
-
const tokenizer = await this.loadTokenizer(modelConfig);
|
|
363
|
-
logger2.info("Tokenizer loaded, encoding text...");
|
|
364
|
-
const encoded = await tokenizer.encode(text, {
|
|
365
|
-
add_special_tokens: true,
|
|
366
|
-
return_token_type_ids: false
|
|
367
|
-
});
|
|
368
|
-
logger2.info("Text encoded successfully:", {
|
|
369
|
-
tokenCount: encoded.length,
|
|
370
|
-
tokenizer: modelConfig.tokenizer.name
|
|
371
|
-
});
|
|
372
|
-
return encoded;
|
|
373
|
-
} catch (error) {
|
|
374
|
-
logger2.error("Text encoding failed:", {
|
|
375
|
-
error: error instanceof Error ? error.message : String(error),
|
|
376
|
-
stack: error instanceof Error ? error.stack : void 0,
|
|
377
|
-
textLength: text.length,
|
|
378
|
-
tokenizer: modelConfig.tokenizer.name,
|
|
379
|
-
modelsDir: this.modelsDir
|
|
380
|
-
});
|
|
381
|
-
throw error;
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
|
-
async decode(tokens, modelConfig) {
|
|
385
|
-
try {
|
|
386
|
-
logger2.info("Decoding tokens with tokenizer:", {
|
|
387
|
-
count: tokens.length,
|
|
388
|
-
tokenizer: modelConfig.tokenizer.name
|
|
389
|
-
});
|
|
390
|
-
const tokenizer = await this.loadTokenizer(modelConfig);
|
|
391
|
-
logger2.info("Tokenizer loaded, decoding tokens...");
|
|
392
|
-
const decoded = await tokenizer.decode(tokens, {
|
|
393
|
-
skip_special_tokens: true,
|
|
394
|
-
clean_up_tokenization_spaces: true
|
|
395
|
-
});
|
|
396
|
-
logger2.info("Tokens decoded successfully:", {
|
|
397
|
-
textLength: decoded.length,
|
|
398
|
-
tokenizer: modelConfig.tokenizer.name
|
|
399
|
-
});
|
|
400
|
-
return decoded;
|
|
401
|
-
} catch (error) {
|
|
402
|
-
logger2.error("Token decoding failed:", {
|
|
403
|
-
error: error instanceof Error ? error.message : String(error),
|
|
404
|
-
stack: error instanceof Error ? error.stack : void 0,
|
|
405
|
-
tokenCount: tokens.length,
|
|
406
|
-
tokenizer: modelConfig.tokenizer.name,
|
|
407
|
-
modelsDir: this.modelsDir
|
|
408
|
-
});
|
|
409
|
-
throw error;
|
|
83
|
+
const validatedConfig = configSchema.parse(fullConfig);
|
|
84
|
+
return validatedConfig;
|
|
85
|
+
} catch (error) {
|
|
86
|
+
if (error instanceof z.ZodError) {
|
|
87
|
+
const errorMessages = error.errors.map((err) => `${err.path.join(".")}: ${err.message}`).join("\n");
|
|
88
|
+
logger.error("Zod validation failed:", errorMessages);
|
|
89
|
+
throw new Error(`Configuration validation failed:
|
|
90
|
+
${errorMessages}`);
|
|
410
91
|
}
|
|
92
|
+
logger.error("Configuration validation failed:", {
|
|
93
|
+
error: error instanceof Error ? error.message : String(error),
|
|
94
|
+
stack: error instanceof Error ? error.stack : void 0
|
|
95
|
+
});
|
|
96
|
+
throw error;
|
|
411
97
|
}
|
|
412
|
-
}
|
|
98
|
+
}
|
|
413
99
|
|
|
414
100
|
// src/types.ts
|
|
415
101
|
var MODEL_SPECS = {
|
|
@@ -574,52 +260,79 @@ var MODEL_SPECS = {
|
|
|
574
260
|
};
|
|
575
261
|
|
|
576
262
|
// src/utils/downloadManager.ts
|
|
577
|
-
import { logger as logger3 } from "@elizaos/core";
|
|
578
263
|
import fs from "node:fs";
|
|
579
264
|
import https from "node:https";
|
|
580
265
|
import path from "node:path";
|
|
266
|
+
import { logger as logger2 } from "@elizaos/core";
|
|
581
267
|
var DownloadManager = class _DownloadManager {
|
|
582
268
|
static instance = null;
|
|
583
269
|
cacheDir;
|
|
584
270
|
modelsDir;
|
|
585
271
|
// Track active downloads to prevent duplicates
|
|
586
272
|
activeDownloads = /* @__PURE__ */ new Map();
|
|
273
|
+
/**
|
|
274
|
+
* Creates a new instance of CacheManager.
|
|
275
|
+
*
|
|
276
|
+
* @param {string} cacheDir - The directory path for caching data.
|
|
277
|
+
* @param {string} modelsDir - The directory path for model files.
|
|
278
|
+
*/
|
|
587
279
|
constructor(cacheDir, modelsDir) {
|
|
588
280
|
this.cacheDir = cacheDir;
|
|
589
281
|
this.modelsDir = modelsDir;
|
|
590
282
|
this.ensureCacheDirectory();
|
|
591
283
|
this.ensureModelsDirectory();
|
|
592
284
|
}
|
|
285
|
+
/**
|
|
286
|
+
* Returns the singleton instance of the DownloadManager class.
|
|
287
|
+
* If an instance does not already exist, it creates a new one using the provided cache directory and models directory.
|
|
288
|
+
*
|
|
289
|
+
* @param {string} cacheDir - The directory where downloaded files are stored.
|
|
290
|
+
* @param {string} modelsDir - The directory where model files are stored.
|
|
291
|
+
* @returns {DownloadManager} The singleton instance of the DownloadManager class.
|
|
292
|
+
*/
|
|
593
293
|
static getInstance(cacheDir, modelsDir) {
|
|
594
294
|
if (!_DownloadManager.instance) {
|
|
595
295
|
_DownloadManager.instance = new _DownloadManager(cacheDir, modelsDir);
|
|
596
296
|
}
|
|
597
297
|
return _DownloadManager.instance;
|
|
598
298
|
}
|
|
299
|
+
/**
|
|
300
|
+
* Ensure that the cache directory exists.
|
|
301
|
+
*/
|
|
599
302
|
ensureCacheDirectory() {
|
|
600
|
-
|
|
303
|
+
logger2.info("Ensuring cache directory exists:", this.cacheDir);
|
|
601
304
|
if (!fs.existsSync(this.cacheDir)) {
|
|
602
305
|
fs.mkdirSync(this.cacheDir, { recursive: true });
|
|
603
|
-
|
|
306
|
+
logger2.info("Created cache directory");
|
|
604
307
|
}
|
|
605
308
|
}
|
|
309
|
+
/**
|
|
310
|
+
* Ensure that the models directory exists. If it does not exist, create it.
|
|
311
|
+
*/
|
|
606
312
|
ensureModelsDirectory() {
|
|
607
|
-
|
|
313
|
+
logger2.info("Ensuring models directory exists:", this.modelsDir);
|
|
608
314
|
if (!fs.existsSync(this.modelsDir)) {
|
|
609
315
|
fs.mkdirSync(this.modelsDir, { recursive: true });
|
|
610
|
-
|
|
316
|
+
logger2.info("Created models directory");
|
|
611
317
|
}
|
|
612
318
|
}
|
|
319
|
+
/**
|
|
320
|
+
* Downloads a file from a given URL to a specified destination path asynchronously.
|
|
321
|
+
*
|
|
322
|
+
* @param {string} url - The URL from which to download the file.
|
|
323
|
+
* @param {string} destPath - The destination path where the downloaded file will be saved.
|
|
324
|
+
* @returns {Promise<void>} A Promise that resolves when the file download is completed successfully or rejects if an error occurs.
|
|
325
|
+
*/
|
|
613
326
|
async downloadFileInternal(url, destPath) {
|
|
614
327
|
return new Promise((resolve, reject) => {
|
|
615
|
-
|
|
328
|
+
logger2.info(`Starting download to: ${destPath}`);
|
|
616
329
|
const tempPath = `${destPath}.tmp`;
|
|
617
330
|
if (fs.existsSync(tempPath)) {
|
|
618
331
|
try {
|
|
619
|
-
|
|
332
|
+
logger2.warn(`Removing existing temporary file: ${tempPath}`);
|
|
620
333
|
fs.unlinkSync(tempPath);
|
|
621
334
|
} catch (err) {
|
|
622
|
-
|
|
335
|
+
logger2.error(
|
|
623
336
|
`Failed to remove existing temporary file: ${err instanceof Error ? err.message : String(err)}`
|
|
624
337
|
);
|
|
625
338
|
}
|
|
@@ -656,7 +369,7 @@ var DownloadManager = class _DownloadManager {
|
|
|
656
369
|
let lastLoggedPercent = 0;
|
|
657
370
|
const barLength = 30;
|
|
658
371
|
const fileName = path.basename(destPath);
|
|
659
|
-
|
|
372
|
+
logger2.info(`Downloading ${fileName}: ${"\u25B1".repeat(barLength)} 0%`);
|
|
660
373
|
const file = fs.createWriteStream(tempPath);
|
|
661
374
|
response.on("data", (chunk) => {
|
|
662
375
|
downloadedSize += chunk.length;
|
|
@@ -666,7 +379,7 @@ var DownloadManager = class _DownloadManager {
|
|
|
666
379
|
downloadedSize / totalSize * barLength
|
|
667
380
|
);
|
|
668
381
|
const progressBar = "\u25B0".repeat(filledLength) + "\u25B1".repeat(barLength - filledLength);
|
|
669
|
-
|
|
382
|
+
logger2.info(
|
|
670
383
|
`Downloading ${fileName}: ${progressBar} ${percent}%`
|
|
671
384
|
);
|
|
672
385
|
lastLoggedPercent = percent;
|
|
@@ -677,7 +390,7 @@ var DownloadManager = class _DownloadManager {
|
|
|
677
390
|
file.close(() => {
|
|
678
391
|
try {
|
|
679
392
|
const completedBar = "\u25B0".repeat(barLength);
|
|
680
|
-
|
|
393
|
+
logger2.info(`Downloading ${fileName}: ${completedBar} 100%`);
|
|
681
394
|
const destDir = path.dirname(destPath);
|
|
682
395
|
if (!fs.existsSync(destDir)) {
|
|
683
396
|
fs.mkdirSync(destDir, { recursive: true });
|
|
@@ -692,29 +405,29 @@ var DownloadManager = class _DownloadManager {
|
|
|
692
405
|
try {
|
|
693
406
|
const backupPath = `${destPath}.bak`;
|
|
694
407
|
fs.renameSync(destPath, backupPath);
|
|
695
|
-
|
|
408
|
+
logger2.info(
|
|
696
409
|
`Created backup of existing file: ${backupPath}`
|
|
697
410
|
);
|
|
698
411
|
fs.renameSync(tempPath, destPath);
|
|
699
412
|
if (fs.existsSync(backupPath)) {
|
|
700
413
|
fs.unlinkSync(backupPath);
|
|
701
|
-
|
|
414
|
+
logger2.info(
|
|
702
415
|
`Removed backup file after successful update: ${backupPath}`
|
|
703
416
|
);
|
|
704
417
|
}
|
|
705
418
|
} catch (moveErr) {
|
|
706
|
-
|
|
419
|
+
logger2.error(
|
|
707
420
|
`Error replacing file: ${moveErr instanceof Error ? moveErr.message : String(moveErr)}`
|
|
708
421
|
);
|
|
709
422
|
const backupPath = `${destPath}.bak`;
|
|
710
423
|
if (fs.existsSync(backupPath)) {
|
|
711
424
|
try {
|
|
712
425
|
fs.renameSync(backupPath, destPath);
|
|
713
|
-
|
|
426
|
+
logger2.info(
|
|
714
427
|
`Restored from backup after failed update: ${backupPath}`
|
|
715
428
|
);
|
|
716
429
|
} catch (restoreErr) {
|
|
717
|
-
|
|
430
|
+
logger2.error(
|
|
718
431
|
`Failed to restore from backup: ${restoreErr instanceof Error ? restoreErr.message : String(restoreErr)}`
|
|
719
432
|
);
|
|
720
433
|
}
|
|
@@ -723,7 +436,7 @@ var DownloadManager = class _DownloadManager {
|
|
|
723
436
|
try {
|
|
724
437
|
fs.unlinkSync(tempPath);
|
|
725
438
|
} catch (unlinkErr) {
|
|
726
|
-
|
|
439
|
+
logger2.error(
|
|
727
440
|
`Failed to clean up temp file: ${unlinkErr instanceof Error ? unlinkErr.message : String(unlinkErr)}`
|
|
728
441
|
);
|
|
729
442
|
}
|
|
@@ -734,20 +447,20 @@ var DownloadManager = class _DownloadManager {
|
|
|
734
447
|
} else {
|
|
735
448
|
fs.renameSync(tempPath, destPath);
|
|
736
449
|
}
|
|
737
|
-
|
|
450
|
+
logger2.success(
|
|
738
451
|
`Download of ${fileName} completed successfully`
|
|
739
452
|
);
|
|
740
453
|
this.activeDownloads.delete(destPath);
|
|
741
454
|
resolve();
|
|
742
455
|
} catch (err) {
|
|
743
|
-
|
|
456
|
+
logger2.error(
|
|
744
457
|
`Error finalizing download: ${err instanceof Error ? err.message : String(err)}`
|
|
745
458
|
);
|
|
746
459
|
if (fs.existsSync(tempPath)) {
|
|
747
460
|
try {
|
|
748
461
|
fs.unlinkSync(tempPath);
|
|
749
462
|
} catch (unlinkErr) {
|
|
750
|
-
|
|
463
|
+
logger2.error(
|
|
751
464
|
`Failed to clean up temp file: ${unlinkErr instanceof Error ? unlinkErr.message : String(unlinkErr)}`
|
|
752
465
|
);
|
|
753
466
|
}
|
|
@@ -758,7 +471,7 @@ var DownloadManager = class _DownloadManager {
|
|
|
758
471
|
});
|
|
759
472
|
});
|
|
760
473
|
file.on("error", (err) => {
|
|
761
|
-
|
|
474
|
+
logger2.error(
|
|
762
475
|
`File write error: ${err instanceof Error ? err.message : String(err)}`
|
|
763
476
|
);
|
|
764
477
|
file.close(() => {
|
|
@@ -766,7 +479,7 @@ var DownloadManager = class _DownloadManager {
|
|
|
766
479
|
try {
|
|
767
480
|
fs.unlinkSync(tempPath);
|
|
768
481
|
} catch (unlinkErr) {
|
|
769
|
-
|
|
482
|
+
logger2.error(
|
|
770
483
|
`Failed to clean up temp file after error: ${unlinkErr instanceof Error ? unlinkErr.message : String(unlinkErr)}`
|
|
771
484
|
);
|
|
772
485
|
}
|
|
@@ -778,14 +491,14 @@ var DownloadManager = class _DownloadManager {
|
|
|
778
491
|
}
|
|
779
492
|
);
|
|
780
493
|
request.on("error", (err) => {
|
|
781
|
-
|
|
494
|
+
logger2.error(
|
|
782
495
|
`Request error: ${err instanceof Error ? err.message : String(err)}`
|
|
783
496
|
);
|
|
784
497
|
if (fs.existsSync(tempPath)) {
|
|
785
498
|
try {
|
|
786
499
|
fs.unlinkSync(tempPath);
|
|
787
500
|
} catch (unlinkErr) {
|
|
788
|
-
|
|
501
|
+
logger2.error(
|
|
789
502
|
`Failed to clean up temp file after request error: ${unlinkErr instanceof Error ? unlinkErr.message : String(unlinkErr)}`
|
|
790
503
|
);
|
|
791
504
|
}
|
|
@@ -794,13 +507,13 @@ var DownloadManager = class _DownloadManager {
|
|
|
794
507
|
reject(err);
|
|
795
508
|
});
|
|
796
509
|
request.on("timeout", () => {
|
|
797
|
-
|
|
510
|
+
logger2.error("Download timeout occurred");
|
|
798
511
|
request.destroy();
|
|
799
512
|
if (fs.existsSync(tempPath)) {
|
|
800
513
|
try {
|
|
801
514
|
fs.unlinkSync(tempPath);
|
|
802
515
|
} catch (unlinkErr) {
|
|
803
|
-
|
|
516
|
+
logger2.error(
|
|
804
517
|
`Failed to clean up temp file after timeout: ${unlinkErr instanceof Error ? unlinkErr.message : String(unlinkErr)}`
|
|
805
518
|
);
|
|
806
519
|
}
|
|
@@ -810,16 +523,23 @@ var DownloadManager = class _DownloadManager {
|
|
|
810
523
|
});
|
|
811
524
|
});
|
|
812
525
|
}
|
|
526
|
+
/**
|
|
527
|
+
* Asynchronously downloads a file from the specified URL to the destination path.
|
|
528
|
+
*
|
|
529
|
+
* @param {string} url - The URL of the file to download.
|
|
530
|
+
* @param {string} destPath - The destination path to save the downloaded file.
|
|
531
|
+
* @returns {Promise<void>} A Promise that resolves once the file has been successfully downloaded.
|
|
532
|
+
*/
|
|
813
533
|
async downloadFile(url, destPath) {
|
|
814
534
|
if (this.activeDownloads.has(destPath)) {
|
|
815
|
-
|
|
535
|
+
logger2.info(
|
|
816
536
|
`Download for ${destPath} already in progress, waiting for it to complete...`
|
|
817
537
|
);
|
|
818
538
|
const existingDownload = this.activeDownloads.get(destPath);
|
|
819
539
|
if (existingDownload) {
|
|
820
540
|
return existingDownload;
|
|
821
541
|
}
|
|
822
|
-
|
|
542
|
+
logger2.warn(
|
|
823
543
|
`Download for ${destPath} was marked as in progress but not found in tracking map`
|
|
824
544
|
);
|
|
825
545
|
}
|
|
@@ -832,12 +552,20 @@ var DownloadManager = class _DownloadManager {
|
|
|
832
552
|
throw error;
|
|
833
553
|
}
|
|
834
554
|
}
|
|
555
|
+
/**
|
|
556
|
+
* Downloads a model specified by the modelSpec and saves it to the provided modelPath.
|
|
557
|
+
* If the model is successfully downloaded, returns true, otherwise returns false.
|
|
558
|
+
*
|
|
559
|
+
* @param {ModelSpec} modelSpec - The model specification containing repo and name.
|
|
560
|
+
* @param {string} modelPath - The path where the model will be saved.
|
|
561
|
+
* @returns {Promise<boolean>} - Indicates if the model was successfully downloaded or not.
|
|
562
|
+
*/
|
|
835
563
|
async downloadModel(modelSpec, modelPath) {
|
|
836
564
|
try {
|
|
837
|
-
|
|
565
|
+
logger2.info("Starting local model download...");
|
|
838
566
|
const modelDir = path.dirname(modelPath);
|
|
839
567
|
if (!fs.existsSync(modelDir)) {
|
|
840
|
-
|
|
568
|
+
logger2.info("Creating model directory:", modelDir);
|
|
841
569
|
fs.mkdirSync(modelDir, { recursive: true });
|
|
842
570
|
}
|
|
843
571
|
if (!fs.existsSync(modelPath)) {
|
|
@@ -863,20 +591,20 @@ var DownloadManager = class _DownloadManager {
|
|
|
863
591
|
let downloadSuccess = false;
|
|
864
592
|
for (const attempt of attempts) {
|
|
865
593
|
try {
|
|
866
|
-
|
|
594
|
+
logger2.info("Attempting model download:", {
|
|
867
595
|
description: attempt.description,
|
|
868
596
|
url: attempt.url,
|
|
869
597
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
870
598
|
});
|
|
871
599
|
await this.downloadFile(attempt.url, modelPath);
|
|
872
|
-
|
|
600
|
+
logger2.success(
|
|
873
601
|
`Model download complete: ${modelSpec.name} using ${attempt.description}`
|
|
874
602
|
);
|
|
875
603
|
downloadSuccess = true;
|
|
876
604
|
break;
|
|
877
605
|
} catch (error) {
|
|
878
606
|
lastError = error;
|
|
879
|
-
|
|
607
|
+
logger2.warn("Model download attempt failed:", {
|
|
880
608
|
description: attempt.description,
|
|
881
609
|
error: error instanceof Error ? error.message : String(error),
|
|
882
610
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -888,10 +616,10 @@ var DownloadManager = class _DownloadManager {
|
|
|
888
616
|
}
|
|
889
617
|
return true;
|
|
890
618
|
}
|
|
891
|
-
|
|
619
|
+
logger2.info("Model already exists at:", modelPath);
|
|
892
620
|
return false;
|
|
893
621
|
} catch (error) {
|
|
894
|
-
|
|
622
|
+
logger2.error("Model download failed:", {
|
|
895
623
|
error: error instanceof Error ? error.message : String(error),
|
|
896
624
|
modelPath,
|
|
897
625
|
model: modelSpec.name
|
|
@@ -899,22 +627,39 @@ var DownloadManager = class _DownloadManager {
|
|
|
899
627
|
throw error;
|
|
900
628
|
}
|
|
901
629
|
}
|
|
630
|
+
/**
|
|
631
|
+
* Returns the cache directory path.
|
|
632
|
+
*
|
|
633
|
+
* @returns {string} The path of the cache directory.
|
|
634
|
+
*/
|
|
902
635
|
getCacheDir() {
|
|
903
636
|
return this.cacheDir;
|
|
904
637
|
}
|
|
638
|
+
/**
|
|
639
|
+
* Downloads a file from a given URL to a specified destination path.
|
|
640
|
+
*
|
|
641
|
+
* @param {string} url - The URL of the file to download.
|
|
642
|
+
* @param {string} destPath - The destination path where the file should be saved.
|
|
643
|
+
* @returns {Promise<void>} A Promise that resolves once the file has been downloaded.
|
|
644
|
+
*/
|
|
905
645
|
async downloadFromUrl(url, destPath) {
|
|
906
646
|
return this.downloadFile(url, destPath);
|
|
907
647
|
}
|
|
648
|
+
/**
|
|
649
|
+
* Ensures that the specified directory exists. If it does not exist, it will be created.
|
|
650
|
+
* @param {string} dirPath - The path of the directory to ensure existence of.
|
|
651
|
+
* @returns {void}
|
|
652
|
+
*/
|
|
908
653
|
ensureDirectoryExists(dirPath) {
|
|
909
654
|
if (!fs.existsSync(dirPath)) {
|
|
910
655
|
fs.mkdirSync(dirPath, { recursive: true });
|
|
911
|
-
|
|
656
|
+
logger2.info(`Created directory: ${dirPath}`);
|
|
912
657
|
}
|
|
913
658
|
}
|
|
914
659
|
};
|
|
915
660
|
|
|
916
661
|
// src/utils/ollamaManager.ts
|
|
917
|
-
import { logger as
|
|
662
|
+
import { ModelTypes, logger as logger3 } from "@elizaos/core";
|
|
918
663
|
|
|
919
664
|
// ../../node_modules/node-fetch/src/index.js
|
|
920
665
|
import http2 from "node:http";
|
|
@@ -964,7 +709,7 @@ var dist_default = dataUriToBuffer;
|
|
|
964
709
|
|
|
965
710
|
// ../../node_modules/node-fetch/src/body.js
|
|
966
711
|
import Stream, { PassThrough } from "node:stream";
|
|
967
|
-
import { types, deprecate, promisify
|
|
712
|
+
import { types, deprecate, promisify } from "node:util";
|
|
968
713
|
import { Buffer as Buffer2 } from "node:buffer";
|
|
969
714
|
|
|
970
715
|
// ../../node_modules/node-fetch/src/errors/base.js
|
|
@@ -1021,7 +766,7 @@ var isSameProtocol = (destination, original) => {
|
|
|
1021
766
|
};
|
|
1022
767
|
|
|
1023
768
|
// ../../node_modules/node-fetch/src/body.js
|
|
1024
|
-
var pipeline =
|
|
769
|
+
var pipeline = promisify(Stream.pipeline);
|
|
1025
770
|
var INTERNALS = Symbol("Body internals");
|
|
1026
771
|
var Body = class {
|
|
1027
772
|
constructor(body, {
|
|
@@ -2203,20 +1948,32 @@ var OllamaManager = class _OllamaManager {
|
|
|
2203
1948
|
small: process.env.SMALL_OLLAMA_MODEL || "deepseek-r1:1.5b",
|
|
2204
1949
|
medium: process.env.MEDIUM_OLLAMA_MODEL || "deepseek-r1:7b"
|
|
2205
1950
|
};
|
|
1951
|
+
/**
|
|
1952
|
+
* Private constructor for initializing OllamaManager.
|
|
1953
|
+
*/
|
|
2206
1954
|
constructor() {
|
|
2207
1955
|
this.serverUrl = process.env.OLLAMA_SERVER_URL || "http://localhost:11434";
|
|
2208
|
-
|
|
1956
|
+
logger3.info("OllamaManager initialized with configuration:", {
|
|
2209
1957
|
serverUrl: this.serverUrl,
|
|
2210
1958
|
configuredModels: this.configuredModels,
|
|
2211
1959
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
2212
1960
|
});
|
|
2213
1961
|
}
|
|
1962
|
+
/**
|
|
1963
|
+
* Returns an instance of the OllamaManager class.
|
|
1964
|
+
* If an instance does not already exist, a new instance is created and returned.
|
|
1965
|
+
* @returns {OllamaManager} The instance of the OllamaManager class.
|
|
1966
|
+
*/
|
|
2214
1967
|
static getInstance() {
|
|
2215
1968
|
if (!_OllamaManager.instance) {
|
|
2216
1969
|
_OllamaManager.instance = new _OllamaManager();
|
|
2217
1970
|
}
|
|
2218
1971
|
return _OllamaManager.instance;
|
|
2219
1972
|
}
|
|
1973
|
+
/**
|
|
1974
|
+
* Asynchronously checks the status of the server by attempting to fetch the "/api/tags" endpoint.
|
|
1975
|
+
* @returns A Promise that resolves to a boolean indicating if the server is reachable and responding with a successful status.
|
|
1976
|
+
*/
|
|
2220
1977
|
async checkServerStatus() {
|
|
2221
1978
|
try {
|
|
2222
1979
|
const response = await fetch2(`${this.serverUrl}/api/tags`);
|
|
@@ -2225,7 +1982,7 @@ var OllamaManager = class _OllamaManager {
|
|
|
2225
1982
|
}
|
|
2226
1983
|
return true;
|
|
2227
1984
|
} catch (error) {
|
|
2228
|
-
|
|
1985
|
+
logger3.error("Ollama server check failed:", {
|
|
2229
1986
|
error: error instanceof Error ? error.message : String(error),
|
|
2230
1987
|
serverUrl: this.serverUrl,
|
|
2231
1988
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -2233,6 +1990,11 @@ var OllamaManager = class _OllamaManager {
|
|
|
2233
1990
|
return false;
|
|
2234
1991
|
}
|
|
2235
1992
|
}
|
|
1993
|
+
/**
|
|
1994
|
+
* Fetches the available Ollama models from the specified server URL.
|
|
1995
|
+
*
|
|
1996
|
+
* @returns {Promise<void>} A Promise that resolves when the available models are successfully fetched.
|
|
1997
|
+
*/
|
|
2236
1998
|
async fetchAvailableModels() {
|
|
2237
1999
|
try {
|
|
2238
2000
|
const response = await fetch2(`${this.serverUrl}/api/tags`);
|
|
@@ -2241,13 +2003,13 @@ var OllamaManager = class _OllamaManager {
|
|
|
2241
2003
|
}
|
|
2242
2004
|
const data = await response.json();
|
|
2243
2005
|
this.availableModels = data.models;
|
|
2244
|
-
|
|
2006
|
+
logger3.info("Ollama available models:", {
|
|
2245
2007
|
count: this.availableModels.length,
|
|
2246
2008
|
models: this.availableModels.map((m) => m.name),
|
|
2247
2009
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
2248
2010
|
});
|
|
2249
2011
|
} catch (error) {
|
|
2250
|
-
|
|
2012
|
+
logger3.error("Failed to fetch Ollama models:", {
|
|
2251
2013
|
error: error instanceof Error ? error.message : String(error),
|
|
2252
2014
|
serverUrl: this.serverUrl,
|
|
2253
2015
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -2255,6 +2017,12 @@ var OllamaManager = class _OllamaManager {
|
|
|
2255
2017
|
throw error;
|
|
2256
2018
|
}
|
|
2257
2019
|
}
|
|
2020
|
+
/**
|
|
2021
|
+
* Asynchronously tests a model specified by the given modelId.
|
|
2022
|
+
*
|
|
2023
|
+
* @param {string} modelId - The ID of the model to be tested.
|
|
2024
|
+
* @returns {Promise<boolean>} - A promise that resolves to true if the model test is successful, false otherwise.
|
|
2025
|
+
*/
|
|
2258
2026
|
async testModel(modelId) {
|
|
2259
2027
|
try {
|
|
2260
2028
|
const testRequest = {
|
|
@@ -2266,7 +2034,7 @@ var OllamaManager = class _OllamaManager {
|
|
|
2266
2034
|
num_predict: 100
|
|
2267
2035
|
}
|
|
2268
2036
|
};
|
|
2269
|
-
|
|
2037
|
+
logger3.info(`Testing model ${modelId}...`);
|
|
2270
2038
|
const response = await fetch2(`${this.serverUrl}/api/generate`, {
|
|
2271
2039
|
method: "POST",
|
|
2272
2040
|
headers: {
|
|
@@ -2281,14 +2049,14 @@ var OllamaManager = class _OllamaManager {
|
|
|
2281
2049
|
if (!result.response) {
|
|
2282
2050
|
throw new Error("No valid response content received");
|
|
2283
2051
|
}
|
|
2284
|
-
|
|
2052
|
+
logger3.info(`Model ${modelId} test response:`, {
|
|
2285
2053
|
content: result.response,
|
|
2286
2054
|
model: result.model,
|
|
2287
2055
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
2288
2056
|
});
|
|
2289
2057
|
return true;
|
|
2290
2058
|
} catch (error) {
|
|
2291
|
-
|
|
2059
|
+
logger3.error(`Model ${modelId} test failed:`, {
|
|
2292
2060
|
error: error instanceof Error ? error.message : String(error),
|
|
2293
2061
|
stack: error instanceof Error ? error.stack : void 0,
|
|
2294
2062
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -2296,8 +2064,13 @@ var OllamaManager = class _OllamaManager {
|
|
|
2296
2064
|
return false;
|
|
2297
2065
|
}
|
|
2298
2066
|
}
|
|
2067
|
+
/**
|
|
2068
|
+
* Asynchronously tests the configured text models to ensure they are working properly.
|
|
2069
|
+
* Logs the test results for each model and outputs a warning if any models fail the test.
|
|
2070
|
+
* @returns {Promise<void>} A Promise that resolves when all configured models have been tested.
|
|
2071
|
+
*/
|
|
2299
2072
|
async testTextModels() {
|
|
2300
|
-
|
|
2073
|
+
logger3.info("Testing configured text models...");
|
|
2301
2074
|
const results = await Promise.all([
|
|
2302
2075
|
this.testModel(this.configuredModels.small),
|
|
2303
2076
|
this.testModel(this.configuredModels.medium)
|
|
@@ -2307,22 +2080,28 @@ var OllamaManager = class _OllamaManager {
|
|
|
2307
2080
|
const failedModels = [];
|
|
2308
2081
|
if (!smallWorking) failedModels.push("small");
|
|
2309
2082
|
if (!mediumWorking) failedModels.push("medium");
|
|
2310
|
-
|
|
2083
|
+
logger3.warn("Some models failed the test:", {
|
|
2311
2084
|
failedModels,
|
|
2312
2085
|
small: this.configuredModels.small,
|
|
2313
2086
|
medium: this.configuredModels.medium
|
|
2314
2087
|
});
|
|
2315
2088
|
} else {
|
|
2316
|
-
|
|
2089
|
+
logger3.success("All configured models passed the test");
|
|
2317
2090
|
}
|
|
2318
2091
|
}
|
|
2092
|
+
/**
|
|
2093
|
+
* Asynchronously initializes the Ollama service by checking server status,
|
|
2094
|
+
* fetching available models, and testing text models.
|
|
2095
|
+
*
|
|
2096
|
+
* @returns A Promise that resolves when initialization is complete
|
|
2097
|
+
*/
|
|
2319
2098
|
async initialize() {
|
|
2320
2099
|
try {
|
|
2321
2100
|
if (this.initialized) {
|
|
2322
|
-
|
|
2101
|
+
logger3.info("Ollama already initialized, skipping initialization");
|
|
2323
2102
|
return;
|
|
2324
2103
|
}
|
|
2325
|
-
|
|
2104
|
+
logger3.info("Starting Ollama initialization...");
|
|
2326
2105
|
const serverAvailable = await this.checkServerStatus();
|
|
2327
2106
|
if (!serverAvailable) {
|
|
2328
2107
|
throw new Error("Ollama server is not available");
|
|
@@ -2330,24 +2109,40 @@ var OllamaManager = class _OllamaManager {
|
|
|
2330
2109
|
await this.fetchAvailableModels();
|
|
2331
2110
|
await this.testTextModels();
|
|
2332
2111
|
this.initialized = true;
|
|
2333
|
-
|
|
2112
|
+
logger3.success("Ollama initialization complete");
|
|
2334
2113
|
} catch (error) {
|
|
2335
|
-
|
|
2114
|
+
logger3.error("Ollama initialization failed:", {
|
|
2336
2115
|
error: error instanceof Error ? error.message : String(error),
|
|
2337
2116
|
stack: error instanceof Error ? error.stack : void 0
|
|
2338
2117
|
});
|
|
2339
2118
|
throw error;
|
|
2340
2119
|
}
|
|
2341
2120
|
}
|
|
2121
|
+
/**
|
|
2122
|
+
* Retrieves the available Ollama models.
|
|
2123
|
+
*
|
|
2124
|
+
* @returns {OllamaModel[]} An array of OllamaModel objects representing the available models.
|
|
2125
|
+
*/
|
|
2342
2126
|
getAvailableModels() {
|
|
2343
2127
|
return this.availableModels;
|
|
2344
2128
|
}
|
|
2129
|
+
/**
|
|
2130
|
+
* Check if the object is initialized.
|
|
2131
|
+
* @returns {boolean} True if the object is initialized, false otherwise.
|
|
2132
|
+
*/
|
|
2345
2133
|
isInitialized() {
|
|
2346
2134
|
return this.initialized;
|
|
2347
2135
|
}
|
|
2136
|
+
/**
|
|
2137
|
+
* Generates text using the Ollama AI model.
|
|
2138
|
+
*
|
|
2139
|
+
* @param {GenerateTextParams} params - The parameters for generating text.
|
|
2140
|
+
* @param {boolean} [isInitialized=false] - Flag indicating if Ollama is already initialized.
|
|
2141
|
+
* @returns {Promise<string>} - A promise that resolves with the generated text.
|
|
2142
|
+
*/
|
|
2348
2143
|
async generateText(params, isInitialized = false) {
|
|
2349
2144
|
try {
|
|
2350
|
-
|
|
2145
|
+
logger3.info("Ollama generateText entry:", {
|
|
2351
2146
|
isInitialized,
|
|
2352
2147
|
currentInitState: this.initialized,
|
|
2353
2148
|
managerInitState: this.isInitialized(),
|
|
@@ -2360,7 +2155,7 @@ var OllamaManager = class _OllamaManager {
|
|
|
2360
2155
|
"Ollama not initialized. Please initialize before generating text."
|
|
2361
2156
|
);
|
|
2362
2157
|
}
|
|
2363
|
-
|
|
2158
|
+
logger3.info("Ollama preparing request:", {
|
|
2364
2159
|
model: params.modelType === ModelTypes.TEXT_LARGE ? this.configuredModels.medium : this.configuredModels.small,
|
|
2365
2160
|
contextLength: params.prompt.length,
|
|
2366
2161
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -2393,24 +2188,24 @@ var OllamaManager = class _OllamaManager {
|
|
|
2393
2188
|
throw new Error("No valid response content received from Ollama");
|
|
2394
2189
|
}
|
|
2395
2190
|
let responseText = result.response;
|
|
2396
|
-
|
|
2191
|
+
logger3.info("Raw response structure:", {
|
|
2397
2192
|
responseLength: responseText.length,
|
|
2398
2193
|
hasAction: responseText.includes("action"),
|
|
2399
2194
|
hasThinkTag: responseText.includes("<think>")
|
|
2400
2195
|
});
|
|
2401
2196
|
if (responseText.includes("<think>")) {
|
|
2402
|
-
|
|
2197
|
+
logger3.info("Cleaning think tags from response");
|
|
2403
2198
|
responseText = responseText.replace(/<think>[\s\S]*?<\/think>\n?/g, "");
|
|
2404
|
-
|
|
2199
|
+
logger3.info("Think tags removed from response");
|
|
2405
2200
|
}
|
|
2406
|
-
|
|
2201
|
+
logger3.info("Ollama request completed successfully:", {
|
|
2407
2202
|
responseLength: responseText.length,
|
|
2408
2203
|
hasThinkTags: responseText.includes("<think>"),
|
|
2409
2204
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
2410
2205
|
});
|
|
2411
2206
|
return responseText;
|
|
2412
2207
|
} catch (error) {
|
|
2413
|
-
|
|
2208
|
+
logger3.error("Ollama text generation error:", {
|
|
2414
2209
|
error: error instanceof Error ? error.message : String(error),
|
|
2415
2210
|
stack: error instanceof Error ? error.stack : void 0,
|
|
2416
2211
|
phase: "text generation",
|
|
@@ -2421,8 +2216,341 @@ var OllamaManager = class _OllamaManager {
|
|
|
2421
2216
|
}
|
|
2422
2217
|
};
|
|
2423
2218
|
|
|
2219
|
+
// src/utils/platform.ts
|
|
2220
|
+
import { exec } from "node:child_process";
|
|
2221
|
+
import os from "node:os";
|
|
2222
|
+
import { promisify as promisify2 } from "node:util";
|
|
2223
|
+
import { logger as logger4 } from "@elizaos/core";
|
|
2224
|
+
var execAsync = promisify2(exec);
|
|
2225
|
+
var PlatformManager = class _PlatformManager {
|
|
2226
|
+
static instance;
|
|
2227
|
+
capabilities = null;
|
|
2228
|
+
/**
|
|
2229
|
+
* Private constructor method.
|
|
2230
|
+
*/
|
|
2231
|
+
constructor() {
|
|
2232
|
+
}
|
|
2233
|
+
/**
|
|
2234
|
+
* Get the singleton instance of the PlatformManager class
|
|
2235
|
+
* @returns {PlatformManager} The instance of PlatformManager
|
|
2236
|
+
*/
|
|
2237
|
+
static getInstance() {
|
|
2238
|
+
if (!_PlatformManager.instance) {
|
|
2239
|
+
_PlatformManager.instance = new _PlatformManager();
|
|
2240
|
+
}
|
|
2241
|
+
return _PlatformManager.instance;
|
|
2242
|
+
}
|
|
2243
|
+
/**
|
|
2244
|
+
* Asynchronous method to initialize platform detection.
|
|
2245
|
+
*
|
|
2246
|
+
* @returns {Promise<void>} Promise that resolves once platform detection is completed.
|
|
2247
|
+
*/
|
|
2248
|
+
async initialize() {
|
|
2249
|
+
try {
|
|
2250
|
+
logger4.info("Initializing platform detection...");
|
|
2251
|
+
this.capabilities = await this.detectSystemCapabilities();
|
|
2252
|
+
} catch (error) {
|
|
2253
|
+
logger4.error("Platform detection failed", { error });
|
|
2254
|
+
throw error;
|
|
2255
|
+
}
|
|
2256
|
+
}
|
|
2257
|
+
/**
|
|
2258
|
+
* Detects the system capabilities including platform, CPU information, GPU information,
|
|
2259
|
+
* supported backends, and recommended model size.
|
|
2260
|
+
*
|
|
2261
|
+
* @returns {Promise<SystemCapabilities>} Details of the system capabilities including platform, CPU info, GPU info,
|
|
2262
|
+
* recommended model size, and supported backends.
|
|
2263
|
+
*/
|
|
2264
|
+
async detectSystemCapabilities() {
|
|
2265
|
+
const platform = process.platform;
|
|
2266
|
+
const cpuInfo = this.getCPUInfo();
|
|
2267
|
+
const gpu = await this.detectGPU();
|
|
2268
|
+
const supportedBackends = await this.getSupportedBackends(platform, gpu);
|
|
2269
|
+
const recommendedModelSize = this.getRecommendedModelSize(cpuInfo, gpu);
|
|
2270
|
+
return {
|
|
2271
|
+
platform,
|
|
2272
|
+
cpu: cpuInfo,
|
|
2273
|
+
gpu,
|
|
2274
|
+
recommendedModelSize,
|
|
2275
|
+
supportedBackends
|
|
2276
|
+
};
|
|
2277
|
+
}
|
|
2278
|
+
/**
|
|
2279
|
+
* Returns information about the CPU and memory of the system.
|
|
2280
|
+
* @returns {SystemCPU} The CPU information including model, number of cores, speed, architecture, and memory details.
|
|
2281
|
+
*/
|
|
2282
|
+
getCPUInfo() {
|
|
2283
|
+
const cpus = os.cpus();
|
|
2284
|
+
const totalMemory = os.totalmem();
|
|
2285
|
+
const freeMemory = os.freemem();
|
|
2286
|
+
return {
|
|
2287
|
+
model: cpus[0].model,
|
|
2288
|
+
cores: cpus.length,
|
|
2289
|
+
speed: cpus[0].speed,
|
|
2290
|
+
architecture: process.arch,
|
|
2291
|
+
memory: {
|
|
2292
|
+
total: totalMemory,
|
|
2293
|
+
free: freeMemory
|
|
2294
|
+
}
|
|
2295
|
+
};
|
|
2296
|
+
}
|
|
2297
|
+
/**
|
|
2298
|
+
* Asynchronously detects the GPU information based on the current platform.
|
|
2299
|
+
* @returns A promise that resolves with the GPU information if detection is successful, otherwise null.
|
|
2300
|
+
*/
|
|
2301
|
+
async detectGPU() {
|
|
2302
|
+
const platform = process.platform;
|
|
2303
|
+
try {
|
|
2304
|
+
switch (platform) {
|
|
2305
|
+
case "darwin":
|
|
2306
|
+
return await this.detectMacGPU();
|
|
2307
|
+
case "win32":
|
|
2308
|
+
return await this.detectWindowsGPU();
|
|
2309
|
+
case "linux":
|
|
2310
|
+
return await this.detectLinuxGPU();
|
|
2311
|
+
default:
|
|
2312
|
+
return null;
|
|
2313
|
+
}
|
|
2314
|
+
} catch (error) {
|
|
2315
|
+
logger4.error("GPU detection failed", { error });
|
|
2316
|
+
return null;
|
|
2317
|
+
}
|
|
2318
|
+
}
|
|
2319
|
+
/**
|
|
2320
|
+
* Asynchronously detects the GPU of a Mac system.
|
|
2321
|
+
* @returns {Promise<SystemGPU>} A promise that resolves to an object representing the detected GPU.
|
|
2322
|
+
*/
|
|
2323
|
+
async detectMacGPU() {
|
|
2324
|
+
try {
|
|
2325
|
+
const { stdout } = await execAsync("sysctl -n machdep.cpu.brand_string");
|
|
2326
|
+
const isAppleSilicon = stdout.toLowerCase().includes("apple");
|
|
2327
|
+
if (isAppleSilicon) {
|
|
2328
|
+
return {
|
|
2329
|
+
name: "Apple Silicon",
|
|
2330
|
+
type: "metal",
|
|
2331
|
+
isAppleSilicon: true
|
|
2332
|
+
};
|
|
2333
|
+
}
|
|
2334
|
+
const { stdout: gpuInfo } = await execAsync(
|
|
2335
|
+
"system_profiler SPDisplaysDataType"
|
|
2336
|
+
);
|
|
2337
|
+
return {
|
|
2338
|
+
name: gpuInfo.split("Chipset Model:")[1]?.split("\n")[0]?.trim() || "Unknown GPU",
|
|
2339
|
+
type: "metal",
|
|
2340
|
+
isAppleSilicon: false
|
|
2341
|
+
};
|
|
2342
|
+
} catch (error) {
|
|
2343
|
+
logger4.error("Mac GPU detection failed", { error });
|
|
2344
|
+
return {
|
|
2345
|
+
name: "Unknown Mac GPU",
|
|
2346
|
+
type: "metal",
|
|
2347
|
+
isAppleSilicon: false
|
|
2348
|
+
};
|
|
2349
|
+
}
|
|
2350
|
+
}
|
|
2351
|
+
/**
|
|
2352
|
+
* Detects the GPU in a Windows system and returns information about it.
|
|
2353
|
+
*
|
|
2354
|
+
* @returns {Promise<SystemGPU | null>} A promise that resolves with the detected GPU information or null if detection fails.
|
|
2355
|
+
*/
|
|
2356
|
+
async detectWindowsGPU() {
|
|
2357
|
+
try {
|
|
2358
|
+
const { stdout } = await execAsync(
|
|
2359
|
+
"wmic path win32_VideoController get name"
|
|
2360
|
+
);
|
|
2361
|
+
const gpuName = stdout.split("\n")[1].trim();
|
|
2362
|
+
if (gpuName.toLowerCase().includes("nvidia")) {
|
|
2363
|
+
const { stdout: nvidiaInfo } = await execAsync(
|
|
2364
|
+
"nvidia-smi --query-gpu=name,memory.total --format=csv,noheader"
|
|
2365
|
+
);
|
|
2366
|
+
const [name, memoryStr] = nvidiaInfo.split(",").map((s) => s.trim());
|
|
2367
|
+
const memory = Number.parseInt(memoryStr);
|
|
2368
|
+
return {
|
|
2369
|
+
name,
|
|
2370
|
+
memory,
|
|
2371
|
+
type: "cuda",
|
|
2372
|
+
version: await this.getNvidiaDriverVersion()
|
|
2373
|
+
};
|
|
2374
|
+
}
|
|
2375
|
+
return {
|
|
2376
|
+
name: gpuName,
|
|
2377
|
+
type: "directml"
|
|
2378
|
+
};
|
|
2379
|
+
} catch (error) {
|
|
2380
|
+
logger4.error("Windows GPU detection failed", { error });
|
|
2381
|
+
return null;
|
|
2382
|
+
}
|
|
2383
|
+
}
|
|
2384
|
+
/**
|
|
2385
|
+
* Asynchronously detects the GPU information for Linux systems.
|
|
2386
|
+
* Tries to detect NVIDIA GPU first using 'nvidia-smi' command and if successful,
|
|
2387
|
+
* returns the GPU name, memory size, type as 'cuda', and NVIDIA driver version.
|
|
2388
|
+
* If NVIDIA detection fails, it falls back to checking for other GPUs using 'lspci | grep -i vga' command.
|
|
2389
|
+
* If no GPU is detected, it returns null.
|
|
2390
|
+
*
|
|
2391
|
+
* @returns {Promise<SystemGPU | null>} The detected GPU information or null if detection fails.
|
|
2392
|
+
*/
|
|
2393
|
+
async detectLinuxGPU() {
|
|
2394
|
+
try {
|
|
2395
|
+
const { stdout } = await execAsync(
|
|
2396
|
+
"nvidia-smi --query-gpu=name,memory.total --format=csv,noheader"
|
|
2397
|
+
);
|
|
2398
|
+
if (stdout) {
|
|
2399
|
+
const [name, memoryStr] = stdout.split(",").map((s) => s.trim());
|
|
2400
|
+
const memory = Number.parseInt(memoryStr);
|
|
2401
|
+
return {
|
|
2402
|
+
name,
|
|
2403
|
+
memory,
|
|
2404
|
+
type: "cuda",
|
|
2405
|
+
version: await this.getNvidiaDriverVersion()
|
|
2406
|
+
};
|
|
2407
|
+
}
|
|
2408
|
+
} catch {
|
|
2409
|
+
try {
|
|
2410
|
+
const { stdout } = await execAsync("lspci | grep -i vga");
|
|
2411
|
+
return {
|
|
2412
|
+
name: stdout.split(":").pop()?.trim() || "Unknown GPU",
|
|
2413
|
+
type: "none"
|
|
2414
|
+
};
|
|
2415
|
+
} catch (error) {
|
|
2416
|
+
logger4.error("Linux GPU detection failed", { error });
|
|
2417
|
+
return null;
|
|
2418
|
+
}
|
|
2419
|
+
}
|
|
2420
|
+
return null;
|
|
2421
|
+
}
|
|
2422
|
+
/**
|
|
2423
|
+
* Asynchronously retrieves the driver version of the Nvidia GPU using the 'nvidia-smi' command.
|
|
2424
|
+
*
|
|
2425
|
+
* @returns A promise that resolves with the driver version as a string, or 'unknown' if an error occurs.
|
|
2426
|
+
*/
|
|
2427
|
+
async getNvidiaDriverVersion() {
|
|
2428
|
+
try {
|
|
2429
|
+
const { stdout } = await execAsync(
|
|
2430
|
+
"nvidia-smi --query-gpu=driver_version --format=csv,noheader"
|
|
2431
|
+
);
|
|
2432
|
+
return stdout.trim();
|
|
2433
|
+
} catch {
|
|
2434
|
+
return "unknown";
|
|
2435
|
+
}
|
|
2436
|
+
}
|
|
2437
|
+
/**
|
|
2438
|
+
* Retrieves the supported backends based on the platform and GPU type.
|
|
2439
|
+
* @param {NodeJS.Platform} platform - The platform on which the code is running.
|
|
2440
|
+
* @param {SystemGPU | null} gpu - The GPU information, if available.
|
|
2441
|
+
* @returns {Promise<Array<"cuda" | "metal" | "directml" | "cpu">>} - An array of supported backends including 'cuda', 'metal', 'directml', and 'cpu'.
|
|
2442
|
+
*/
|
|
2443
|
+
async getSupportedBackends(platform, gpu) {
|
|
2444
|
+
const backends = ["cpu"];
|
|
2445
|
+
if (gpu) {
|
|
2446
|
+
switch (platform) {
|
|
2447
|
+
case "darwin":
|
|
2448
|
+
backends.push("metal");
|
|
2449
|
+
break;
|
|
2450
|
+
case "win32":
|
|
2451
|
+
if (gpu.type === "cuda") {
|
|
2452
|
+
backends.push("cuda");
|
|
2453
|
+
}
|
|
2454
|
+
backends.push("directml");
|
|
2455
|
+
break;
|
|
2456
|
+
case "linux":
|
|
2457
|
+
if (gpu.type === "cuda") {
|
|
2458
|
+
backends.push("cuda");
|
|
2459
|
+
}
|
|
2460
|
+
break;
|
|
2461
|
+
}
|
|
2462
|
+
}
|
|
2463
|
+
return backends;
|
|
2464
|
+
}
|
|
2465
|
+
/**
|
|
2466
|
+
* Determines the recommended model size based on the system's CPU and GPU.
|
|
2467
|
+
* @param {SystemCPU} cpu - The system's CPU.
|
|
2468
|
+
* @param {SystemGPU | null} gpu - The system's GPU, if available.
|
|
2469
|
+
* @returns {"small" | "medium" | "large"} - The recommended model size ("small", "medium", or "large").
|
|
2470
|
+
*/
|
|
2471
|
+
getRecommendedModelSize(cpu, gpu) {
|
|
2472
|
+
if (gpu?.isAppleSilicon) {
|
|
2473
|
+
return cpu.memory.total > 16 * 1024 * 1024 * 1024 ? "medium" : "small";
|
|
2474
|
+
}
|
|
2475
|
+
if (gpu?.type === "cuda") {
|
|
2476
|
+
const gpuMemGB = (gpu.memory || 0) / 1024;
|
|
2477
|
+
if (gpuMemGB >= 16) return "large";
|
|
2478
|
+
if (gpuMemGB >= 8) return "medium";
|
|
2479
|
+
}
|
|
2480
|
+
if (cpu.memory.total > 32 * 1024 * 1024 * 1024) return "medium";
|
|
2481
|
+
return "small";
|
|
2482
|
+
}
|
|
2483
|
+
/**
|
|
2484
|
+
* Returns the SystemCapabilities of the PlatformManager.
|
|
2485
|
+
*
|
|
2486
|
+
* @returns {SystemCapabilities} The SystemCapabilities of the PlatformManager.
|
|
2487
|
+
* @throws {Error} if PlatformManager is not initialized.
|
|
2488
|
+
*/
|
|
2489
|
+
getCapabilities() {
|
|
2490
|
+
if (!this.capabilities) {
|
|
2491
|
+
throw new Error("PlatformManager not initialized");
|
|
2492
|
+
}
|
|
2493
|
+
return this.capabilities;
|
|
2494
|
+
}
|
|
2495
|
+
/**
|
|
2496
|
+
* Checks if the device's GPU is Apple Silicon.
|
|
2497
|
+
* @returns {boolean} True if the GPU is Apple Silicon, false otherwise.
|
|
2498
|
+
*/
|
|
2499
|
+
isAppleSilicon() {
|
|
2500
|
+
return !!this.capabilities?.gpu?.isAppleSilicon;
|
|
2501
|
+
}
|
|
2502
|
+
/**
|
|
2503
|
+
* Checks if the current device has GPU support.
|
|
2504
|
+
* @returns {boolean} - Returns true if the device has GPU support, false otherwise.
|
|
2505
|
+
*/
|
|
2506
|
+
hasGPUSupport() {
|
|
2507
|
+
return !!this.capabilities?.gpu;
|
|
2508
|
+
}
|
|
2509
|
+
/**
|
|
2510
|
+
* Checks if the system supports CUDA GPU for processing.
|
|
2511
|
+
*
|
|
2512
|
+
* @returns {boolean} True if the system supports CUDA, false otherwise.
|
|
2513
|
+
*/
|
|
2514
|
+
supportsCUDA() {
|
|
2515
|
+
return this.capabilities?.gpu?.type === "cuda";
|
|
2516
|
+
}
|
|
2517
|
+
/**
|
|
2518
|
+
* Check if the device supports Metal API for rendering graphics.
|
|
2519
|
+
* @returns {boolean} True if the device supports Metal, false otherwise.
|
|
2520
|
+
*/
|
|
2521
|
+
supportsMetal() {
|
|
2522
|
+
return this.capabilities?.gpu?.type === "metal";
|
|
2523
|
+
}
|
|
2524
|
+
/**
|
|
2525
|
+
* Check if the device supports DirectML for GPU acceleration.
|
|
2526
|
+
*
|
|
2527
|
+
* @returns {boolean} True if the device supports DirectML, false otherwise.
|
|
2528
|
+
*/
|
|
2529
|
+
supportsDirectML() {
|
|
2530
|
+
return this.capabilities?.gpu?.type === "directml";
|
|
2531
|
+
}
|
|
2532
|
+
/**
|
|
2533
|
+
* Get the recommended backend for computation based on the available capabilities.
|
|
2534
|
+
* @returns {"cuda" | "metal" | "directml" | "cpu"} The recommended backend for computation.
|
|
2535
|
+
* @throws {Error} Throws an error if PlatformManager is not initialized.
|
|
2536
|
+
*/
|
|
2537
|
+
getRecommendedBackend() {
|
|
2538
|
+
if (!this.capabilities) {
|
|
2539
|
+
throw new Error("PlatformManager not initialized");
|
|
2540
|
+
}
|
|
2541
|
+
const { gpu, supportedBackends } = this.capabilities;
|
|
2542
|
+
if (gpu?.type === "cuda") return "cuda";
|
|
2543
|
+
if (gpu?.type === "metal") return "metal";
|
|
2544
|
+
if (supportedBackends.includes("directml")) return "directml";
|
|
2545
|
+
return "cpu";
|
|
2546
|
+
}
|
|
2547
|
+
};
|
|
2548
|
+
var getPlatformManager = () => {
|
|
2549
|
+
return PlatformManager.getInstance();
|
|
2550
|
+
};
|
|
2551
|
+
|
|
2424
2552
|
// src/utils/studiolmManager.ts
|
|
2425
|
-
import {
|
|
2553
|
+
import { ModelTypes as ModelTypes2, logger as logger5 } from "@elizaos/core";
|
|
2426
2554
|
var StudioLMManager = class _StudioLMManager {
|
|
2427
2555
|
static instance = null;
|
|
2428
2556
|
serverUrl;
|
|
@@ -2432,6 +2560,11 @@ var StudioLMManager = class _StudioLMManager {
|
|
|
2432
2560
|
small: process.env.STUDIOLM_SMALL_MODEL || "lmstudio-community/deepseek-r1-distill-qwen-1.5b",
|
|
2433
2561
|
medium: process.env.STUDIOLM_MEDIUM_MODEL || "deepseek-r1-distill-qwen-7b"
|
|
2434
2562
|
};
|
|
2563
|
+
/**
|
|
2564
|
+
* Private constructor for StudioLMManager.
|
|
2565
|
+
* Initializes with default serverUrl if not provided in environment variables.
|
|
2566
|
+
* Logs initialization information including serverUrl, configuredModels, and timestamp.
|
|
2567
|
+
*/
|
|
2435
2568
|
constructor() {
|
|
2436
2569
|
this.serverUrl = process.env.STUDIOLM_SERVER_URL || "http://localhost:1234";
|
|
2437
2570
|
logger5.info("StudioLMManager initialized with configuration:", {
|
|
@@ -2440,12 +2573,20 @@ var StudioLMManager = class _StudioLMManager {
|
|
|
2440
2573
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
2441
2574
|
});
|
|
2442
2575
|
}
|
|
2576
|
+
/**
|
|
2577
|
+
* Returns an instance of StudioLMManager. If an instance already exists, it returns the existing instance.
|
|
2578
|
+
* @returns {StudioLMManager} The instance of StudioLMManager
|
|
2579
|
+
*/
|
|
2443
2580
|
static getInstance() {
|
|
2444
2581
|
if (!_StudioLMManager.instance) {
|
|
2445
2582
|
_StudioLMManager.instance = new _StudioLMManager();
|
|
2446
2583
|
}
|
|
2447
2584
|
return _StudioLMManager.instance;
|
|
2448
2585
|
}
|
|
2586
|
+
/**
|
|
2587
|
+
* Check the status of the server by sending a request to the /v1/models endpoint.
|
|
2588
|
+
* @returns {Promise<boolean>} A Promise that resolves to true if the server responds with success status, false otherwise.
|
|
2589
|
+
*/
|
|
2449
2590
|
async checkServerStatus() {
|
|
2450
2591
|
try {
|
|
2451
2592
|
const response = await fetch2(`${this.serverUrl}/v1/models`);
|
|
@@ -2462,6 +2603,11 @@ var StudioLMManager = class _StudioLMManager {
|
|
|
2462
2603
|
return false;
|
|
2463
2604
|
}
|
|
2464
2605
|
}
|
|
2606
|
+
/**
|
|
2607
|
+
* Fetches the available models from the server and stores them in the 'availableModels' property.
|
|
2608
|
+
*
|
|
2609
|
+
* @returns {Promise<void>} A Promise that resolves when the models are fetched successfully or rejects with an error.
|
|
2610
|
+
*/
|
|
2465
2611
|
async fetchAvailableModels() {
|
|
2466
2612
|
try {
|
|
2467
2613
|
const response = await fetch2(`${this.serverUrl}/v1/models`);
|
|
@@ -2484,6 +2630,11 @@ var StudioLMManager = class _StudioLMManager {
|
|
|
2484
2630
|
throw error;
|
|
2485
2631
|
}
|
|
2486
2632
|
}
|
|
2633
|
+
/**
|
|
2634
|
+
* Asynchronously tests the specified model with a chat completion request.
|
|
2635
|
+
* @param {string} modelId - The ID of the model to test.
|
|
2636
|
+
* @returns {Promise<boolean>} - A promise that resolves to true if the model test was successful, false otherwise.
|
|
2637
|
+
*/
|
|
2487
2638
|
async testModel(modelId) {
|
|
2488
2639
|
try {
|
|
2489
2640
|
const testRequest = {
|
|
@@ -2529,6 +2680,11 @@ var StudioLMManager = class _StudioLMManager {
|
|
|
2529
2680
|
return false;
|
|
2530
2681
|
}
|
|
2531
2682
|
}
|
|
2683
|
+
/**
|
|
2684
|
+
* Tests the configured text models to ensure they are working properly.
|
|
2685
|
+
* Logs the results of the test and any failed models.
|
|
2686
|
+
* @returns {Promise<void>} A promise that resolves when the test is complete.
|
|
2687
|
+
*/
|
|
2532
2688
|
async testTextModels() {
|
|
2533
2689
|
logger5.info("Testing configured text models...");
|
|
2534
2690
|
const results = await Promise.all([
|
|
@@ -2549,6 +2705,12 @@ var StudioLMManager = class _StudioLMManager {
|
|
|
2549
2705
|
logger5.success("All configured models passed the test");
|
|
2550
2706
|
}
|
|
2551
2707
|
}
|
|
2708
|
+
/**
|
|
2709
|
+
* Initializes StudioLM by checking server status, fetching available models,
|
|
2710
|
+
* and testing text models.
|
|
2711
|
+
*
|
|
2712
|
+
* @returns {Promise<void>} A Promise that resolves when initialization is complete
|
|
2713
|
+
*/
|
|
2552
2714
|
async initialize() {
|
|
2553
2715
|
try {
|
|
2554
2716
|
if (this.initialized) {
|
|
@@ -2572,12 +2734,29 @@ var StudioLMManager = class _StudioLMManager {
|
|
|
2572
2734
|
throw error;
|
|
2573
2735
|
}
|
|
2574
2736
|
}
|
|
2737
|
+
/**
|
|
2738
|
+
* Retrieves the available models in the studio.
|
|
2739
|
+
*
|
|
2740
|
+
* @returns {StudioLMModel[]} An array of StudioLMModel objects representing the available models.
|
|
2741
|
+
*/
|
|
2575
2742
|
getAvailableModels() {
|
|
2576
2743
|
return this.availableModels;
|
|
2577
2744
|
}
|
|
2745
|
+
/**
|
|
2746
|
+
* Check if the object is initialized.
|
|
2747
|
+
*
|
|
2748
|
+
* @returns {boolean} Returns true if the object is initialized, otherwise false.
|
|
2749
|
+
*/
|
|
2578
2750
|
isInitialized() {
|
|
2579
2751
|
return this.initialized;
|
|
2580
2752
|
}
|
|
2753
|
+
/**
|
|
2754
|
+
* Asynchronously generates text using StudioLM based on provided parameters.
|
|
2755
|
+
*
|
|
2756
|
+
* @param {GenerateTextParams} params - The parameters for generating text.
|
|
2757
|
+
* @param {boolean} [isInitialized=false] - Flag to indicate if the model is already initialized.
|
|
2758
|
+
* @returns {Promise<string>} The generated text as a Promise.
|
|
2759
|
+
*/
|
|
2581
2760
|
async generateText(params, isInitialized = false) {
|
|
2582
2761
|
try {
|
|
2583
2762
|
logger5.info("StudioLM generateText entry:", {
|
|
@@ -2605,56 +2784,245 @@ var StudioLMManager = class _StudioLMManager {
|
|
|
2605
2784
|
messageCount: messages.length,
|
|
2606
2785
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
2607
2786
|
});
|
|
2608
|
-
logger5.info("Incoming context structure:", {
|
|
2609
|
-
contextLength: params.prompt.length,
|
|
2610
|
-
hasAction: params.prompt.includes("action"),
|
|
2611
|
-
runtime: !!params.runtime,
|
|
2612
|
-
stopSequences: params.stopSequences
|
|
2787
|
+
logger5.info("Incoming context structure:", {
|
|
2788
|
+
contextLength: params.prompt.length,
|
|
2789
|
+
hasAction: params.prompt.includes("action"),
|
|
2790
|
+
runtime: !!params.runtime,
|
|
2791
|
+
stopSequences: params.stopSequences
|
|
2792
|
+
});
|
|
2793
|
+
const request = {
|
|
2794
|
+
model: params.modelType === ModelTypes2.TEXT_LARGE ? this.configuredModels.medium : this.configuredModels.small,
|
|
2795
|
+
messages,
|
|
2796
|
+
temperature: 0.7,
|
|
2797
|
+
max_tokens: 8192,
|
|
2798
|
+
stream: false
|
|
2799
|
+
};
|
|
2800
|
+
const response = await fetch2(`${this.serverUrl}/v1/chat/completions`, {
|
|
2801
|
+
method: "POST",
|
|
2802
|
+
headers: {
|
|
2803
|
+
"Content-Type": "application/json"
|
|
2804
|
+
},
|
|
2805
|
+
body: JSON.stringify(request)
|
|
2806
|
+
});
|
|
2807
|
+
if (!response.ok) {
|
|
2808
|
+
throw new Error(`StudioLM request failed: ${response.status}`);
|
|
2809
|
+
}
|
|
2810
|
+
const result = await response.json();
|
|
2811
|
+
if (!result.choices?.[0]?.message?.content) {
|
|
2812
|
+
throw new Error("No valid response content received from StudioLM");
|
|
2813
|
+
}
|
|
2814
|
+
let responseText = result.choices[0].message.content;
|
|
2815
|
+
logger5.info("Raw response structure:", {
|
|
2816
|
+
responseLength: responseText.length,
|
|
2817
|
+
hasAction: responseText.includes("action"),
|
|
2818
|
+
hasThinkTag: responseText.includes("<think>")
|
|
2819
|
+
});
|
|
2820
|
+
if (responseText.includes("<think>")) {
|
|
2821
|
+
logger5.info("Cleaning think tags from response");
|
|
2822
|
+
responseText = responseText.replace(/<think>[\s\S]*?<\/think>\n?/g, "");
|
|
2823
|
+
logger5.info("Think tags removed from response");
|
|
2824
|
+
}
|
|
2825
|
+
logger5.info("StudioLM request completed successfully:", {
|
|
2826
|
+
responseLength: responseText.length,
|
|
2827
|
+
hasThinkTags: responseText.includes("<think>"),
|
|
2828
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
2829
|
+
});
|
|
2830
|
+
return responseText;
|
|
2831
|
+
} catch (error) {
|
|
2832
|
+
logger5.error("StudioLM text generation error:", {
|
|
2833
|
+
error: error instanceof Error ? error.message : String(error),
|
|
2834
|
+
stack: error instanceof Error ? error.stack : void 0,
|
|
2835
|
+
phase: "text generation",
|
|
2836
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
2837
|
+
});
|
|
2838
|
+
throw error;
|
|
2839
|
+
}
|
|
2840
|
+
}
|
|
2841
|
+
};
|
|
2842
|
+
|
|
2843
|
+
// src/utils/tokenizerManager.ts
|
|
2844
|
+
import { logger as logger6 } from "@elizaos/core";
|
|
2845
|
+
import {
|
|
2846
|
+
AutoTokenizer
|
|
2847
|
+
} from "@huggingface/transformers";
|
|
2848
|
+
var TokenizerManager = class _TokenizerManager {
|
|
2849
|
+
static instance = null;
|
|
2850
|
+
tokenizers;
|
|
2851
|
+
cacheDir;
|
|
2852
|
+
modelsDir;
|
|
2853
|
+
/**
|
|
2854
|
+
* Constructor for creating a new instance of the class.
|
|
2855
|
+
*
|
|
2856
|
+
* @param {string} cacheDir - The directory for caching data.
|
|
2857
|
+
* @param {string} modelsDir - The directory for storing models.
|
|
2858
|
+
*/
|
|
2859
|
+
constructor(cacheDir, modelsDir) {
|
|
2860
|
+
this.tokenizers = /* @__PURE__ */ new Map();
|
|
2861
|
+
this.cacheDir = cacheDir;
|
|
2862
|
+
this.modelsDir = modelsDir;
|
|
2863
|
+
}
|
|
2864
|
+
/**
|
|
2865
|
+
* Get the singleton instance of TokenizerManager class. If the instance does not exist, it will create a new one.
|
|
2866
|
+
*
|
|
2867
|
+
* @param {string} cacheDir - The directory to cache the tokenizer models.
|
|
2868
|
+
* @param {string} modelsDir - The directory where tokenizer models are stored.
|
|
2869
|
+
* @returns {TokenizerManager} The singleton instance of TokenizerManager.
|
|
2870
|
+
*/
|
|
2871
|
+
static getInstance(cacheDir, modelsDir) {
|
|
2872
|
+
if (!_TokenizerManager.instance) {
|
|
2873
|
+
_TokenizerManager.instance = new _TokenizerManager(cacheDir, modelsDir);
|
|
2874
|
+
}
|
|
2875
|
+
return _TokenizerManager.instance;
|
|
2876
|
+
}
|
|
2877
|
+
/**
|
|
2878
|
+
* Asynchronously loads a tokenizer based on the provided ModelSpec configuration.
|
|
2879
|
+
*
|
|
2880
|
+
* @param {ModelSpec} modelConfig - The configuration object for the model to load the tokenizer for.
|
|
2881
|
+
* @returns {Promise<PreTrainedTokenizer>} - A promise that resolves to the loaded tokenizer.
|
|
2882
|
+
*/
|
|
2883
|
+
async loadTokenizer(modelConfig) {
|
|
2884
|
+
try {
|
|
2885
|
+
const tokenizerKey = `${modelConfig.tokenizer.type}-${modelConfig.tokenizer.name}`;
|
|
2886
|
+
logger6.info("Loading tokenizer:", {
|
|
2887
|
+
key: tokenizerKey,
|
|
2888
|
+
name: modelConfig.tokenizer.name,
|
|
2889
|
+
type: modelConfig.tokenizer.type,
|
|
2890
|
+
modelsDir: this.modelsDir,
|
|
2891
|
+
cacheDir: this.cacheDir
|
|
2892
|
+
});
|
|
2893
|
+
if (this.tokenizers.has(tokenizerKey)) {
|
|
2894
|
+
logger6.info("Using cached tokenizer:", { key: tokenizerKey });
|
|
2895
|
+
const cachedTokenizer = this.tokenizers.get(tokenizerKey);
|
|
2896
|
+
if (!cachedTokenizer) {
|
|
2897
|
+
throw new Error(
|
|
2898
|
+
`Tokenizer ${tokenizerKey} exists in map but returned undefined`
|
|
2899
|
+
);
|
|
2900
|
+
}
|
|
2901
|
+
return cachedTokenizer;
|
|
2902
|
+
}
|
|
2903
|
+
const fs6 = await import("node:fs");
|
|
2904
|
+
if (!fs6.existsSync(this.modelsDir)) {
|
|
2905
|
+
logger6.warn(
|
|
2906
|
+
"Models directory does not exist, creating it:",
|
|
2907
|
+
this.modelsDir
|
|
2908
|
+
);
|
|
2909
|
+
fs6.mkdirSync(this.modelsDir, { recursive: true });
|
|
2910
|
+
}
|
|
2911
|
+
logger6.info(
|
|
2912
|
+
"Initializing new tokenizer from HuggingFace with models directory:",
|
|
2913
|
+
this.modelsDir
|
|
2914
|
+
);
|
|
2915
|
+
try {
|
|
2916
|
+
const tokenizer = await AutoTokenizer.from_pretrained(
|
|
2917
|
+
modelConfig.tokenizer.name,
|
|
2918
|
+
{
|
|
2919
|
+
cache_dir: this.modelsDir,
|
|
2920
|
+
local_files_only: false
|
|
2921
|
+
}
|
|
2922
|
+
);
|
|
2923
|
+
this.tokenizers.set(tokenizerKey, tokenizer);
|
|
2924
|
+
logger6.success("Tokenizer loaded successfully:", { key: tokenizerKey });
|
|
2925
|
+
return tokenizer;
|
|
2926
|
+
} catch (tokenizeError) {
|
|
2927
|
+
logger6.error("Failed to load tokenizer from HuggingFace:", {
|
|
2928
|
+
error: tokenizeError instanceof Error ? tokenizeError.message : String(tokenizeError),
|
|
2929
|
+
stack: tokenizeError instanceof Error ? tokenizeError.stack : void 0,
|
|
2930
|
+
tokenizer: modelConfig.tokenizer.name,
|
|
2931
|
+
modelsDir: this.modelsDir
|
|
2932
|
+
});
|
|
2933
|
+
logger6.info("Retrying tokenizer loading...");
|
|
2934
|
+
const tokenizer = await AutoTokenizer.from_pretrained(
|
|
2935
|
+
modelConfig.tokenizer.name,
|
|
2936
|
+
{
|
|
2937
|
+
cache_dir: this.modelsDir,
|
|
2938
|
+
local_files_only: false
|
|
2939
|
+
}
|
|
2940
|
+
);
|
|
2941
|
+
this.tokenizers.set(tokenizerKey, tokenizer);
|
|
2942
|
+
logger6.success("Tokenizer loaded successfully on retry:", {
|
|
2943
|
+
key: tokenizerKey
|
|
2944
|
+
});
|
|
2945
|
+
return tokenizer;
|
|
2946
|
+
}
|
|
2947
|
+
} catch (error) {
|
|
2948
|
+
logger6.error("Failed to load tokenizer:", {
|
|
2949
|
+
error: error instanceof Error ? error.message : String(error),
|
|
2950
|
+
stack: error instanceof Error ? error.stack : void 0,
|
|
2951
|
+
model: modelConfig.name,
|
|
2952
|
+
tokenizer: modelConfig.tokenizer.name,
|
|
2953
|
+
modelsDir: this.modelsDir
|
|
2954
|
+
});
|
|
2955
|
+
throw error;
|
|
2956
|
+
}
|
|
2957
|
+
}
|
|
2958
|
+
/**
|
|
2959
|
+
* Encodes the given text using the specified tokenizer model configuration.
|
|
2960
|
+
*
|
|
2961
|
+
* @param {string} text - The text to encode.
|
|
2962
|
+
* @param {ModelSpec} modelConfig - The configuration for the model tokenizer.
|
|
2963
|
+
* @returns {Promise<number[]>} - An array of integers representing the encoded text.
|
|
2964
|
+
* @throws {Error} - If the text encoding fails, an error is thrown.
|
|
2965
|
+
*/
|
|
2966
|
+
async encode(text, modelConfig) {
|
|
2967
|
+
try {
|
|
2968
|
+
logger6.info("Encoding text with tokenizer:", {
|
|
2969
|
+
length: text.length,
|
|
2970
|
+
tokenizer: modelConfig.tokenizer.name
|
|
2971
|
+
});
|
|
2972
|
+
const tokenizer = await this.loadTokenizer(modelConfig);
|
|
2973
|
+
logger6.info("Tokenizer loaded, encoding text...");
|
|
2974
|
+
const encoded = await tokenizer.encode(text, {
|
|
2975
|
+
add_special_tokens: true,
|
|
2976
|
+
return_token_type_ids: false
|
|
2613
2977
|
});
|
|
2614
|
-
|
|
2615
|
-
|
|
2616
|
-
|
|
2617
|
-
temperature: 0.7,
|
|
2618
|
-
max_tokens: 8192,
|
|
2619
|
-
stream: false
|
|
2620
|
-
};
|
|
2621
|
-
const response = await fetch2(`${this.serverUrl}/v1/chat/completions`, {
|
|
2622
|
-
method: "POST",
|
|
2623
|
-
headers: {
|
|
2624
|
-
"Content-Type": "application/json"
|
|
2625
|
-
},
|
|
2626
|
-
body: JSON.stringify(request)
|
|
2978
|
+
logger6.info("Text encoded successfully:", {
|
|
2979
|
+
tokenCount: encoded.length,
|
|
2980
|
+
tokenizer: modelConfig.tokenizer.name
|
|
2627
2981
|
});
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
logger5.info("Raw response structure:", {
|
|
2637
|
-
responseLength: responseText.length,
|
|
2638
|
-
hasAction: responseText.includes("action"),
|
|
2639
|
-
hasThinkTag: responseText.includes("<think>")
|
|
2982
|
+
return encoded;
|
|
2983
|
+
} catch (error) {
|
|
2984
|
+
logger6.error("Text encoding failed:", {
|
|
2985
|
+
error: error instanceof Error ? error.message : String(error),
|
|
2986
|
+
stack: error instanceof Error ? error.stack : void 0,
|
|
2987
|
+
textLength: text.length,
|
|
2988
|
+
tokenizer: modelConfig.tokenizer.name,
|
|
2989
|
+
modelsDir: this.modelsDir
|
|
2640
2990
|
});
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
|
|
2991
|
+
throw error;
|
|
2992
|
+
}
|
|
2993
|
+
}
|
|
2994
|
+
/**
|
|
2995
|
+
* Asynchronously decodes an array of tokens using a tokenizer based on the provided ModelSpec.
|
|
2996
|
+
*
|
|
2997
|
+
* @param {number[]} tokens - The array of tokens to be decoded.
|
|
2998
|
+
* @param {ModelSpec} modelConfig - The ModelSpec object containing information about the model and tokenizer to be used.
|
|
2999
|
+
* @returns {Promise<string>} - A Promise that resolves with the decoded text.
|
|
3000
|
+
* @throws {Error} - If an error occurs during token decoding.
|
|
3001
|
+
*/
|
|
3002
|
+
async decode(tokens, modelConfig) {
|
|
3003
|
+
try {
|
|
3004
|
+
logger6.info("Decoding tokens with tokenizer:", {
|
|
3005
|
+
count: tokens.length,
|
|
3006
|
+
tokenizer: modelConfig.tokenizer.name
|
|
2650
3007
|
});
|
|
2651
|
-
|
|
3008
|
+
const tokenizer = await this.loadTokenizer(modelConfig);
|
|
3009
|
+
logger6.info("Tokenizer loaded, decoding tokens...");
|
|
3010
|
+
const decoded = await tokenizer.decode(tokens, {
|
|
3011
|
+
skip_special_tokens: true,
|
|
3012
|
+
clean_up_tokenization_spaces: true
|
|
3013
|
+
});
|
|
3014
|
+
logger6.info("Tokens decoded successfully:", {
|
|
3015
|
+
textLength: decoded.length,
|
|
3016
|
+
tokenizer: modelConfig.tokenizer.name
|
|
3017
|
+
});
|
|
3018
|
+
return decoded;
|
|
2652
3019
|
} catch (error) {
|
|
2653
|
-
|
|
3020
|
+
logger6.error("Token decoding failed:", {
|
|
2654
3021
|
error: error instanceof Error ? error.message : String(error),
|
|
2655
3022
|
stack: error instanceof Error ? error.stack : void 0,
|
|
2656
|
-
|
|
2657
|
-
|
|
3023
|
+
tokenCount: tokens.length,
|
|
3024
|
+
tokenizer: modelConfig.tokenizer.name,
|
|
3025
|
+
modelsDir: this.modelsDir
|
|
2658
3026
|
});
|
|
2659
3027
|
throw error;
|
|
2660
3028
|
}
|
|
@@ -2662,12 +3030,12 @@ var StudioLMManager = class _StudioLMManager {
|
|
|
2662
3030
|
};
|
|
2663
3031
|
|
|
2664
3032
|
// src/utils/transcribeManager.ts
|
|
2665
|
-
import {
|
|
2666
|
-
import { nodewhisper } from "nodejs-whisper";
|
|
3033
|
+
import { exec as exec2 } from "node:child_process";
|
|
2667
3034
|
import fs2 from "node:fs";
|
|
2668
3035
|
import path2 from "node:path";
|
|
2669
3036
|
import { promisify as promisify3 } from "node:util";
|
|
2670
|
-
import {
|
|
3037
|
+
import { logger as logger7 } from "@elizaos/core";
|
|
3038
|
+
import { nodewhisper } from "nodejs-whisper";
|
|
2671
3039
|
var execAsync2 = promisify3(exec2);
|
|
2672
3040
|
var TranscribeManager = class _TranscribeManager {
|
|
2673
3041
|
static instance = null;
|
|
@@ -2676,21 +3044,30 @@ var TranscribeManager = class _TranscribeManager {
|
|
|
2676
3044
|
ffmpegVersion = null;
|
|
2677
3045
|
ffmpegPath = null;
|
|
2678
3046
|
ffmpegInitialized = false;
|
|
3047
|
+
/**
|
|
3048
|
+
* Constructor for TranscribeManager class.
|
|
3049
|
+
*
|
|
3050
|
+
* @param {string} cacheDir - The directory path for storing cached files.
|
|
3051
|
+
*/
|
|
2679
3052
|
constructor(cacheDir) {
|
|
2680
3053
|
this.cacheDir = path2.join(cacheDir, "whisper");
|
|
2681
|
-
|
|
3054
|
+
logger7.info("Initializing TranscribeManager", {
|
|
2682
3055
|
cacheDir: this.cacheDir,
|
|
2683
3056
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
2684
3057
|
});
|
|
2685
3058
|
this.ensureCacheDirectory();
|
|
2686
3059
|
}
|
|
3060
|
+
/**
|
|
3061
|
+
* Ensures that FFmpeg is initialized and available for use.
|
|
3062
|
+
* @returns {Promise<boolean>} A promise that resolves to a boolean value indicating if FFmpeg is available.
|
|
3063
|
+
*/
|
|
2687
3064
|
async ensureFFmpeg() {
|
|
2688
3065
|
if (!this.ffmpegInitialized) {
|
|
2689
3066
|
try {
|
|
2690
3067
|
await this.initializeFFmpeg();
|
|
2691
3068
|
this.ffmpegInitialized = true;
|
|
2692
3069
|
} catch (error) {
|
|
2693
|
-
|
|
3070
|
+
logger7.error("FFmpeg initialization failed:", {
|
|
2694
3071
|
error: error instanceof Error ? error.message : String(error),
|
|
2695
3072
|
stack: error instanceof Error ? error.stack : void 0,
|
|
2696
3073
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -2700,31 +3077,57 @@ var TranscribeManager = class _TranscribeManager {
|
|
|
2700
3077
|
}
|
|
2701
3078
|
return this.ffmpegAvailable;
|
|
2702
3079
|
}
|
|
3080
|
+
/**
|
|
3081
|
+
* Checks if FFmpeg is available.
|
|
3082
|
+
* @returns {boolean} True if FFmpeg is available, false otherwise.
|
|
3083
|
+
*/
|
|
2703
3084
|
isFFmpegAvailable() {
|
|
2704
3085
|
return this.ffmpegAvailable;
|
|
2705
3086
|
}
|
|
3087
|
+
/**
|
|
3088
|
+
* Asynchronously retrieves the FFmpeg version if it hasn't been fetched yet.
|
|
3089
|
+
* If the FFmpeg version has already been fetched, it will return the stored version.
|
|
3090
|
+
* @returns A Promise that resolves with the FFmpeg version as a string, or null if the version is not available.
|
|
3091
|
+
*/
|
|
2706
3092
|
async getFFmpegVersion() {
|
|
2707
3093
|
if (!this.ffmpegVersion) {
|
|
2708
3094
|
await this.fetchFFmpegVersion();
|
|
2709
3095
|
}
|
|
2710
3096
|
return this.ffmpegVersion;
|
|
2711
3097
|
}
|
|
3098
|
+
/**
|
|
3099
|
+
* Fetches the FFmpeg version by executing the command "ffmpeg -version".
|
|
3100
|
+
* Updates the class property ffmpegVersion with the retrieved version.
|
|
3101
|
+
* Logs the FFmpeg version information or error message.
|
|
3102
|
+
* @returns {Promise<void>} A Promise that resolves once the FFmpeg version is fetched and logged.
|
|
3103
|
+
*/
|
|
2712
3104
|
async fetchFFmpegVersion() {
|
|
2713
3105
|
try {
|
|
2714
3106
|
const { stdout } = await execAsync2("ffmpeg -version");
|
|
2715
3107
|
this.ffmpegVersion = stdout.split("\n")[0];
|
|
2716
|
-
|
|
3108
|
+
logger7.info("FFmpeg version:", {
|
|
2717
3109
|
version: this.ffmpegVersion,
|
|
2718
3110
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
2719
3111
|
});
|
|
2720
3112
|
} catch (error) {
|
|
2721
3113
|
this.ffmpegVersion = null;
|
|
2722
|
-
|
|
3114
|
+
logger7.error("Failed to get FFmpeg version:", {
|
|
2723
3115
|
error: error instanceof Error ? error.message : String(error),
|
|
2724
3116
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
2725
3117
|
});
|
|
2726
3118
|
}
|
|
2727
3119
|
}
|
|
3120
|
+
/**
|
|
3121
|
+
* Initializes FFmpeg by performing the following steps:
|
|
3122
|
+
* 1. Checks for FFmpeg availability in PATH
|
|
3123
|
+
* 2. Retrieves FFmpeg version information
|
|
3124
|
+
* 3. Verifies FFmpeg capabilities
|
|
3125
|
+
*
|
|
3126
|
+
* If FFmpeg is available, logs a success message with version, path, and timestamp.
|
|
3127
|
+
* If FFmpeg is not available, logs installation instructions.
|
|
3128
|
+
*
|
|
3129
|
+
* @returns A Promise that resolves once FFmpeg has been successfully initialized
|
|
3130
|
+
*/
|
|
2728
3131
|
async initializeFFmpeg() {
|
|
2729
3132
|
try {
|
|
2730
3133
|
await this.checkFFmpegAvailability();
|
|
@@ -2736,7 +3139,7 @@ var TranscribeManager = class _TranscribeManager {
|
|
|
2736
3139
|
}
|
|
2737
3140
|
} catch (error) {
|
|
2738
3141
|
this.ffmpegAvailable = false;
|
|
2739
|
-
|
|
3142
|
+
logger7.error("FFmpeg initialization failed:", {
|
|
2740
3143
|
error: error instanceof Error ? error.message : String(error),
|
|
2741
3144
|
stack: error instanceof Error ? error.stack : void 0,
|
|
2742
3145
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -2744,6 +3147,13 @@ var TranscribeManager = class _TranscribeManager {
|
|
|
2744
3147
|
this.logFFmpegInstallInstructions();
|
|
2745
3148
|
}
|
|
2746
3149
|
}
|
|
3150
|
+
/**
|
|
3151
|
+
* Asynchronously checks for the availability of FFmpeg in the system by executing a command to find the FFmpeg location.
|
|
3152
|
+
* Updates the class properties `ffmpegPath` and `ffmpegAvailable` accordingly.
|
|
3153
|
+
* Logs relevant information such as FFmpeg location and potential errors using the logger.
|
|
3154
|
+
*
|
|
3155
|
+
* @returns A Promise that resolves with no value upon completion.
|
|
3156
|
+
*/
|
|
2747
3157
|
async checkFFmpegAvailability() {
|
|
2748
3158
|
try {
|
|
2749
3159
|
const { stdout, stderr } = await execAsync2(
|
|
@@ -2751,7 +3161,7 @@ var TranscribeManager = class _TranscribeManager {
|
|
|
2751
3161
|
);
|
|
2752
3162
|
this.ffmpegPath = stdout.trim();
|
|
2753
3163
|
this.ffmpegAvailable = true;
|
|
2754
|
-
|
|
3164
|
+
logger7.info("FFmpeg found at:", {
|
|
2755
3165
|
path: this.ffmpegPath,
|
|
2756
3166
|
stderr: stderr ? stderr.trim() : void 0,
|
|
2757
3167
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -2759,13 +3169,18 @@ var TranscribeManager = class _TranscribeManager {
|
|
|
2759
3169
|
} catch (error) {
|
|
2760
3170
|
this.ffmpegAvailable = false;
|
|
2761
3171
|
this.ffmpegPath = null;
|
|
2762
|
-
|
|
3172
|
+
logger7.error("FFmpeg not found in PATH:", {
|
|
2763
3173
|
error: error instanceof Error ? error.message : String(error),
|
|
2764
3174
|
stderr: error instanceof Error && "stderr" in error ? error.stderr : void 0,
|
|
2765
3175
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
2766
3176
|
});
|
|
2767
3177
|
}
|
|
2768
3178
|
}
|
|
3179
|
+
/**
|
|
3180
|
+
* Verifies the FFmpeg capabilities by checking if FFmpeg supports the required codecs and formats.
|
|
3181
|
+
*
|
|
3182
|
+
* @returns {Promise<void>} A Promise that resolves if FFmpeg has the required codecs, otherwise rejects with an error message.
|
|
3183
|
+
*/
|
|
2769
3184
|
async verifyFFmpegCapabilities() {
|
|
2770
3185
|
try {
|
|
2771
3186
|
const { stdout } = await execAsync2("ffmpeg -codecs");
|
|
@@ -2776,15 +3191,18 @@ var TranscribeManager = class _TranscribeManager {
|
|
|
2776
3191
|
);
|
|
2777
3192
|
}
|
|
2778
3193
|
} catch (error) {
|
|
2779
|
-
|
|
3194
|
+
logger7.error("FFmpeg capabilities verification failed:", {
|
|
2780
3195
|
error: error instanceof Error ? error.message : String(error),
|
|
2781
3196
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
2782
3197
|
});
|
|
2783
3198
|
throw error;
|
|
2784
3199
|
}
|
|
2785
3200
|
}
|
|
3201
|
+
/**
|
|
3202
|
+
* Logs instructions on how to install FFmpeg if it is not properly installed.
|
|
3203
|
+
*/
|
|
2786
3204
|
logFFmpegInstallInstructions() {
|
|
2787
|
-
|
|
3205
|
+
logger7.warn(
|
|
2788
3206
|
"FFmpeg is required but not properly installed. Please install FFmpeg:",
|
|
2789
3207
|
{
|
|
2790
3208
|
instructions: {
|
|
@@ -2799,17 +3217,36 @@ var TranscribeManager = class _TranscribeManager {
|
|
|
2799
3217
|
}
|
|
2800
3218
|
);
|
|
2801
3219
|
}
|
|
3220
|
+
/**
|
|
3221
|
+
* Gets the singleton instance of TranscribeManager, creates a new instance if it doesn't exist.
|
|
3222
|
+
*
|
|
3223
|
+
* @param {string} cacheDir - The directory path for caching transcriptions.
|
|
3224
|
+
* @returns {TranscribeManager} The singleton instance of TranscribeManager.
|
|
3225
|
+
*/
|
|
2802
3226
|
static getInstance(cacheDir) {
|
|
2803
3227
|
if (!_TranscribeManager.instance) {
|
|
2804
3228
|
_TranscribeManager.instance = new _TranscribeManager(cacheDir);
|
|
2805
3229
|
}
|
|
2806
3230
|
return _TranscribeManager.instance;
|
|
2807
3231
|
}
|
|
3232
|
+
/**
|
|
3233
|
+
* Ensures that the cache directory exists. If it doesn't exist,
|
|
3234
|
+
* creates the directory using fs.mkdirSync with recursive set to true.
|
|
3235
|
+
* @returns {void}
|
|
3236
|
+
*/
|
|
2808
3237
|
ensureCacheDirectory() {
|
|
2809
3238
|
if (!fs2.existsSync(this.cacheDir)) {
|
|
2810
3239
|
fs2.mkdirSync(this.cacheDir, { recursive: true });
|
|
2811
3240
|
}
|
|
2812
3241
|
}
|
|
3242
|
+
/**
|
|
3243
|
+
* Converts an audio file to WAV format using FFmpeg.
|
|
3244
|
+
*
|
|
3245
|
+
* @param {string} inputPath - The input path of the audio file to convert.
|
|
3246
|
+
* @param {string} outputPath - The output path where the converted WAV file will be saved.
|
|
3247
|
+
* @returns {Promise<void>} A Promise that resolves when the conversion is completed.
|
|
3248
|
+
* @throws {Error} If FFmpeg is not installed or not properly configured, or if the audio conversion fails.
|
|
3249
|
+
*/
|
|
2813
3250
|
async convertToWav(inputPath, outputPath) {
|
|
2814
3251
|
if (!this.ffmpegAvailable) {
|
|
2815
3252
|
throw new Error(
|
|
@@ -2821,7 +3258,7 @@ var TranscribeManager = class _TranscribeManager {
|
|
|
2821
3258
|
`ffmpeg -y -loglevel error -i "${inputPath}" -acodec pcm_s16le -ar 16000 -ac 1 "${outputPath}"`
|
|
2822
3259
|
);
|
|
2823
3260
|
if (stderr) {
|
|
2824
|
-
|
|
3261
|
+
logger7.warn("FFmpeg conversion error:", {
|
|
2825
3262
|
stderr,
|
|
2826
3263
|
inputPath,
|
|
2827
3264
|
outputPath,
|
|
@@ -2832,7 +3269,7 @@ var TranscribeManager = class _TranscribeManager {
|
|
|
2832
3269
|
throw new Error("WAV file was not created successfully");
|
|
2833
3270
|
}
|
|
2834
3271
|
} catch (error) {
|
|
2835
|
-
|
|
3272
|
+
logger7.error("Audio conversion failed:", {
|
|
2836
3273
|
error: error instanceof Error ? error.message : String(error),
|
|
2837
3274
|
stack: error instanceof Error ? error.stack : void 0,
|
|
2838
3275
|
command: `ffmpeg -y -loglevel error -i "${inputPath}" -acodec pcm_s16le -ar 16000 -ac 1 "${outputPath}"`,
|
|
@@ -2846,6 +3283,14 @@ var TranscribeManager = class _TranscribeManager {
|
|
|
2846
3283
|
);
|
|
2847
3284
|
}
|
|
2848
3285
|
}
|
|
3286
|
+
/**
|
|
3287
|
+
* Asynchronously preprocesses the audio by converting the provided audio buffer into a WAV file.
|
|
3288
|
+
* If FFmpeg is not installed, an error is thrown.
|
|
3289
|
+
*
|
|
3290
|
+
* @param {Buffer} audioBuffer The audio buffer to preprocess
|
|
3291
|
+
* @returns {Promise<string>} The path to the preprocessed WAV file
|
|
3292
|
+
* @throws {Error} If FFmpeg is not installed or if audio preprocessing fails
|
|
3293
|
+
*/
|
|
2849
3294
|
async preprocessAudio(audioBuffer) {
|
|
2850
3295
|
if (!this.ffmpegAvailable) {
|
|
2851
3296
|
throw new Error(
|
|
@@ -2865,7 +3310,7 @@ var TranscribeManager = class _TranscribeManager {
|
|
|
2865
3310
|
}
|
|
2866
3311
|
return tempWavFile;
|
|
2867
3312
|
} catch (error) {
|
|
2868
|
-
|
|
3313
|
+
logger7.error("Audio preprocessing failed:", {
|
|
2869
3314
|
error: error instanceof Error ? error.message : String(error),
|
|
2870
3315
|
stack: error instanceof Error ? error.stack : void 0,
|
|
2871
3316
|
ffmpegAvailable: this.ffmpegAvailable,
|
|
@@ -2876,6 +3321,13 @@ var TranscribeManager = class _TranscribeManager {
|
|
|
2876
3321
|
);
|
|
2877
3322
|
}
|
|
2878
3323
|
}
|
|
3324
|
+
/**
|
|
3325
|
+
* Transcribes the audio buffer to text using whisper.
|
|
3326
|
+
*
|
|
3327
|
+
* @param {Buffer} audioBuffer The audio buffer to transcribe.
|
|
3328
|
+
* @returns {Promise<TranscriptionResult>} A promise that resolves with the transcription result.
|
|
3329
|
+
* @throws {Error} If FFmpeg is not installed or properly configured.
|
|
3330
|
+
*/
|
|
2879
3331
|
async transcribe(audioBuffer) {
|
|
2880
3332
|
await this.ensureFFmpeg();
|
|
2881
3333
|
if (!this.ffmpegAvailable) {
|
|
@@ -2885,7 +3337,7 @@ var TranscribeManager = class _TranscribeManager {
|
|
|
2885
3337
|
}
|
|
2886
3338
|
try {
|
|
2887
3339
|
const wavFile = await this.preprocessAudio(audioBuffer);
|
|
2888
|
-
|
|
3340
|
+
logger7.info("Starting transcription with whisper...");
|
|
2889
3341
|
const originalStdoutWrite = process.stdout.write;
|
|
2890
3342
|
const originalStderrWrite = process.stderr.write;
|
|
2891
3343
|
const noopWrite = () => true;
|
|
@@ -2908,19 +3360,19 @@ var TranscribeManager = class _TranscribeManager {
|
|
|
2908
3360
|
}
|
|
2909
3361
|
if (fs2.existsSync(wavFile)) {
|
|
2910
3362
|
fs2.unlinkSync(wavFile);
|
|
2911
|
-
|
|
3363
|
+
logger7.info("Temporary WAV file cleaned up");
|
|
2912
3364
|
}
|
|
2913
3365
|
const cleanText = output.split("\n").map((line) => {
|
|
2914
3366
|
const textMatch = line.match(/](.+)$/);
|
|
2915
3367
|
return textMatch ? textMatch[1].trim() : line.trim();
|
|
2916
3368
|
}).filter((line) => line).join(" ");
|
|
2917
|
-
|
|
3369
|
+
logger7.success("Transcription complete:", {
|
|
2918
3370
|
textLength: cleanText.length,
|
|
2919
3371
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
2920
3372
|
});
|
|
2921
3373
|
return { text: cleanText };
|
|
2922
3374
|
} catch (error) {
|
|
2923
|
-
|
|
3375
|
+
logger7.error("Transcription failed:", {
|
|
2924
3376
|
error: error instanceof Error ? error.message : String(error),
|
|
2925
3377
|
stack: error instanceof Error ? error.stack : void 0,
|
|
2926
3378
|
ffmpegAvailable: this.ffmpegAvailable
|
|
@@ -2931,13 +3383,13 @@ var TranscribeManager = class _TranscribeManager {
|
|
|
2931
3383
|
};
|
|
2932
3384
|
|
|
2933
3385
|
// src/utils/ttsManager.ts
|
|
2934
|
-
import { logger as logger7 } from "@elizaos/core";
|
|
2935
3386
|
import fs3 from "node:fs";
|
|
2936
3387
|
import path3 from "node:path";
|
|
3388
|
+
import { Readable } from "node:stream";
|
|
3389
|
+
import { logger as logger8 } from "@elizaos/core";
|
|
2937
3390
|
import {
|
|
2938
3391
|
getLlama
|
|
2939
3392
|
} from "node-llama-cpp";
|
|
2940
|
-
import { Readable } from "node:stream";
|
|
2941
3393
|
|
|
2942
3394
|
// src/utils/audioUtils.ts
|
|
2943
3395
|
import { PassThrough as PassThrough3 } from "node:stream";
|
|
@@ -2990,6 +3442,11 @@ var TTSManager = class _TTSManager {
|
|
|
2990
3442
|
initialized = false;
|
|
2991
3443
|
downloadManager;
|
|
2992
3444
|
modelsDir;
|
|
3445
|
+
/**
|
|
3446
|
+
* Creates a new instance of TTSManager with the provided cache directory.
|
|
3447
|
+
*
|
|
3448
|
+
* @param {string} cacheDir - The directory where cached data will be stored.
|
|
3449
|
+
*/
|
|
2993
3450
|
constructor(cacheDir) {
|
|
2994
3451
|
this.cacheDir = path3.join(cacheDir, "tts");
|
|
2995
3452
|
this.modelsDir = process.env.LLAMALOCAL_PATH?.trim() ? path3.resolve(process.env.LLAMALOCAL_PATH.trim()) : path3.join(process.cwd(), "models");
|
|
@@ -2998,26 +3455,44 @@ var TTSManager = class _TTSManager {
|
|
|
2998
3455
|
this.modelsDir
|
|
2999
3456
|
);
|
|
3000
3457
|
this.ensureCacheDirectory();
|
|
3001
|
-
|
|
3458
|
+
logger8.info("TTSManager initialized");
|
|
3002
3459
|
}
|
|
3460
|
+
/**
|
|
3461
|
+
* Returns an instance of TTSManager, creating a new one if none exist.
|
|
3462
|
+
*
|
|
3463
|
+
* @param {string} cacheDir - The directory path to store cached audio files.
|
|
3464
|
+
* @returns {TTSManager} An instance of TTSManager.
|
|
3465
|
+
*/
|
|
3003
3466
|
static getInstance(cacheDir) {
|
|
3004
3467
|
if (!_TTSManager.instance) {
|
|
3005
3468
|
_TTSManager.instance = new _TTSManager(cacheDir);
|
|
3006
3469
|
}
|
|
3007
3470
|
return _TTSManager.instance;
|
|
3008
3471
|
}
|
|
3472
|
+
/**
|
|
3473
|
+
* Ensures that the cache directory exists. If it does not exist, the directory will be created.
|
|
3474
|
+
*/
|
|
3009
3475
|
ensureCacheDirectory() {
|
|
3010
3476
|
if (!fs3.existsSync(this.cacheDir)) {
|
|
3011
3477
|
fs3.mkdirSync(this.cacheDir, { recursive: true });
|
|
3012
|
-
|
|
3478
|
+
logger8.info("Created TTS cache directory:", this.cacheDir);
|
|
3013
3479
|
}
|
|
3014
3480
|
}
|
|
3481
|
+
/**
|
|
3482
|
+
* Asynchronously initializes the TTS module with GGUF backend.
|
|
3483
|
+
* If already initialized or missing necessary components (model and context), it returns early.
|
|
3484
|
+
* Handles model download using different URL patterns as fallback if model not found locally.
|
|
3485
|
+
* Initializes the TTS model, creates context, and sets the sequence for TTS generation.
|
|
3486
|
+
* Logs detailed steps and final output of initialization.
|
|
3487
|
+
*
|
|
3488
|
+
* @returns {Promise<void>} A promise that resolves once the TTS module is fully initialized.
|
|
3489
|
+
*/
|
|
3015
3490
|
async initialize() {
|
|
3016
3491
|
try {
|
|
3017
3492
|
if (this.initialized && this.model && this.ctx) {
|
|
3018
3493
|
return;
|
|
3019
3494
|
}
|
|
3020
|
-
|
|
3495
|
+
logger8.info("Initializing TTS with GGUF backend...");
|
|
3021
3496
|
const modelSpec = MODEL_SPECS.tts.base;
|
|
3022
3497
|
const modelPath = path3.join(this.modelsDir, modelSpec.name);
|
|
3023
3498
|
if (!fs3.existsSync(modelPath)) {
|
|
@@ -3041,7 +3516,7 @@ var TTSManager = class _TTSManager {
|
|
|
3041
3516
|
let lastError = null;
|
|
3042
3517
|
for (const attempt of attempts) {
|
|
3043
3518
|
try {
|
|
3044
|
-
|
|
3519
|
+
logger8.info("Attempting TTS model download:", {
|
|
3045
3520
|
description: attempt.description,
|
|
3046
3521
|
repo: attempt.spec.repo,
|
|
3047
3522
|
name: attempt.spec.name,
|
|
@@ -3050,18 +3525,18 @@ var TTSManager = class _TTSManager {
|
|
|
3050
3525
|
});
|
|
3051
3526
|
const barLength = 30;
|
|
3052
3527
|
const emptyBar = "\u25B1".repeat(barLength);
|
|
3053
|
-
|
|
3528
|
+
logger8.info(`Downloading TTS model: ${emptyBar} 0%`);
|
|
3054
3529
|
await this.downloadManager.downloadFromUrl(attempt.url, modelPath);
|
|
3055
3530
|
const completedBar = "\u25B0".repeat(barLength);
|
|
3056
|
-
|
|
3057
|
-
|
|
3531
|
+
logger8.info(`Downloading TTS model: ${completedBar} 100%`);
|
|
3532
|
+
logger8.success(
|
|
3058
3533
|
"TTS model download successful with:",
|
|
3059
3534
|
attempt.description
|
|
3060
3535
|
);
|
|
3061
3536
|
break;
|
|
3062
3537
|
} catch (error) {
|
|
3063
3538
|
lastError = error;
|
|
3064
|
-
|
|
3539
|
+
logger8.warn("TTS model download attempt failed:", {
|
|
3065
3540
|
description: attempt.description,
|
|
3066
3541
|
error: error instanceof Error ? error.message : String(error),
|
|
3067
3542
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -3072,7 +3547,7 @@ var TTSManager = class _TTSManager {
|
|
|
3072
3547
|
throw lastError || new Error("All download attempts failed");
|
|
3073
3548
|
}
|
|
3074
3549
|
}
|
|
3075
|
-
|
|
3550
|
+
logger8.info("Loading TTS model...");
|
|
3076
3551
|
const llama = await getLlama();
|
|
3077
3552
|
this.model = await llama.loadModel({
|
|
3078
3553
|
modelPath,
|
|
@@ -3083,14 +3558,14 @@ var TTSManager = class _TTSManager {
|
|
|
3083
3558
|
contextSize: modelSpec.contextSize
|
|
3084
3559
|
});
|
|
3085
3560
|
this.sequence = this.ctx.getSequence();
|
|
3086
|
-
|
|
3561
|
+
logger8.success("TTS initialization complete", {
|
|
3087
3562
|
modelPath,
|
|
3088
3563
|
contextSize: modelSpec.contextSize,
|
|
3089
3564
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
3090
3565
|
});
|
|
3091
3566
|
this.initialized = true;
|
|
3092
3567
|
} catch (error) {
|
|
3093
|
-
|
|
3568
|
+
logger8.error("TTS initialization failed:", {
|
|
3094
3569
|
error: error instanceof Error ? error.message : String(error),
|
|
3095
3570
|
model: MODEL_SPECS.tts.base.name,
|
|
3096
3571
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -3098,20 +3573,26 @@ var TTSManager = class _TTSManager {
|
|
|
3098
3573
|
throw error;
|
|
3099
3574
|
}
|
|
3100
3575
|
}
|
|
3576
|
+
/**
|
|
3577
|
+
* Asynchronously generates speech from a given text using the initialized TTS model.
|
|
3578
|
+
* @param {string} text - The text to generate speech from.
|
|
3579
|
+
* @returns {Promise<Readable>} A promise that resolves to a Readable stream containing the generated audio data.
|
|
3580
|
+
* @throws {Error} If the TTS model is not initialized or if no audio tokens are generated.
|
|
3581
|
+
*/
|
|
3101
3582
|
async generateSpeech(text) {
|
|
3102
3583
|
try {
|
|
3103
3584
|
await this.initialize();
|
|
3104
3585
|
if (!this.model || !this.ctx || !this.sequence) {
|
|
3105
3586
|
throw new Error("TTS model not initialized");
|
|
3106
3587
|
}
|
|
3107
|
-
|
|
3588
|
+
logger8.info("Starting speech generation for text:", { text });
|
|
3108
3589
|
const prompt = `[SPEAKER=female_1][LANGUAGE=en]${text}`;
|
|
3109
|
-
|
|
3110
|
-
|
|
3590
|
+
logger8.info("Formatted prompt:", { prompt });
|
|
3591
|
+
logger8.info("Tokenizing input...");
|
|
3111
3592
|
const inputTokens = this.model.tokenize(prompt);
|
|
3112
|
-
|
|
3593
|
+
logger8.info("Input tokenized:", { tokenCount: inputTokens.length });
|
|
3113
3594
|
const maxTokens = inputTokens.length * 2;
|
|
3114
|
-
|
|
3595
|
+
logger8.info("Starting token generation with optimized limit:", {
|
|
3115
3596
|
maxTokens
|
|
3116
3597
|
});
|
|
3117
3598
|
const responseTokens = [];
|
|
@@ -3127,28 +3608,28 @@ var TTSManager = class _TTSManager {
|
|
|
3127
3608
|
responseTokens.length / maxTokens * barLength
|
|
3128
3609
|
);
|
|
3129
3610
|
const progressBar = "\u25B0".repeat(filledLength) + "\u25B1".repeat(barLength - filledLength);
|
|
3130
|
-
|
|
3611
|
+
logger8.info(
|
|
3131
3612
|
`Token generation: ${progressBar} ${percent}% (${responseTokens.length}/${maxTokens})`
|
|
3132
3613
|
);
|
|
3133
3614
|
if (responseTokens.length >= maxTokens) {
|
|
3134
|
-
|
|
3615
|
+
logger8.info("Token generation complete");
|
|
3135
3616
|
break;
|
|
3136
3617
|
}
|
|
3137
3618
|
}
|
|
3138
3619
|
} catch (error) {
|
|
3139
|
-
|
|
3620
|
+
logger8.error("Token generation error:", error);
|
|
3140
3621
|
throw error;
|
|
3141
3622
|
}
|
|
3142
3623
|
if (responseTokens.length === 0) {
|
|
3143
3624
|
throw new Error("No audio tokens generated");
|
|
3144
3625
|
}
|
|
3145
|
-
|
|
3626
|
+
logger8.info("Converting tokens to audio data...");
|
|
3146
3627
|
const audioData = this.processAudioResponse({
|
|
3147
3628
|
tokens: responseTokens.map(
|
|
3148
3629
|
(t) => Number.parseInt(this.model.detokenize([t]), 10)
|
|
3149
3630
|
)
|
|
3150
3631
|
});
|
|
3151
|
-
|
|
3632
|
+
logger8.info("Audio data generated:", {
|
|
3152
3633
|
byteLength: audioData.length,
|
|
3153
3634
|
sampleRate: MODEL_SPECS.tts.base.sampleRate
|
|
3154
3635
|
});
|
|
@@ -3159,16 +3640,27 @@ var TTSManager = class _TTSManager {
|
|
|
3159
3640
|
1,
|
|
3160
3641
|
16
|
|
3161
3642
|
);
|
|
3162
|
-
|
|
3643
|
+
logger8.success("Speech generation complete");
|
|
3163
3644
|
return audioStream;
|
|
3164
3645
|
} catch (error) {
|
|
3165
|
-
|
|
3646
|
+
logger8.error("Speech generation failed:", {
|
|
3166
3647
|
error: error instanceof Error ? error.message : String(error),
|
|
3167
3648
|
text
|
|
3168
3649
|
});
|
|
3169
3650
|
throw error;
|
|
3170
3651
|
}
|
|
3171
3652
|
}
|
|
3653
|
+
/**
|
|
3654
|
+
* Processes the audio response from the TTS service by converting
|
|
3655
|
+
* the data to 16-bit PCM format.
|
|
3656
|
+
* If the response contains direct audio data, it converts Float32Array
|
|
3657
|
+
* to 16-bit PCM. If the response only contains tokens, it converts
|
|
3658
|
+
* them to PCM data. The actual conversion process may vary depending
|
|
3659
|
+
* on the model used.
|
|
3660
|
+
*
|
|
3661
|
+
* @param {TTSResponse} response - The response object from the TTS service
|
|
3662
|
+
* @returns {Buffer} The processed audio data in 16-bit PCM format
|
|
3663
|
+
*/
|
|
3172
3664
|
processAudioResponse(response) {
|
|
3173
3665
|
if (response.audio) {
|
|
3174
3666
|
const pcmData2 = new Int16Array(response.audio.length);
|
|
@@ -3188,19 +3680,19 @@ var TTSManager = class _TTSManager {
|
|
|
3188
3680
|
};
|
|
3189
3681
|
|
|
3190
3682
|
// src/utils/visionManager.ts
|
|
3191
|
-
import {
|
|
3683
|
+
import { existsSync } from "node:fs";
|
|
3684
|
+
import fs4 from "node:fs";
|
|
3685
|
+
import os2 from "node:os";
|
|
3686
|
+
import path4 from "node:path";
|
|
3687
|
+
import process2 from "node:process";
|
|
3688
|
+
import { logger as logger9 } from "@elizaos/core";
|
|
3192
3689
|
import {
|
|
3193
3690
|
AutoProcessor,
|
|
3194
3691
|
AutoTokenizer as AutoTokenizer2,
|
|
3195
|
-
env,
|
|
3196
3692
|
Florence2ForConditionalGeneration,
|
|
3197
|
-
RawImage
|
|
3693
|
+
RawImage,
|
|
3694
|
+
env
|
|
3198
3695
|
} from "@huggingface/transformers";
|
|
3199
|
-
import { existsSync } from "node:fs";
|
|
3200
|
-
import path4 from "node:path";
|
|
3201
|
-
import process2 from "node:process";
|
|
3202
|
-
import fs4 from "node:fs";
|
|
3203
|
-
import os2 from "node:os";
|
|
3204
3696
|
var VisionManager = class _VisionManager {
|
|
3205
3697
|
static instance = null;
|
|
3206
3698
|
model = null;
|
|
@@ -3220,6 +3712,11 @@ var VisionManager = class _VisionManager {
|
|
|
3220
3712
|
{ name: "decoder_model_merged", type: "decoder" },
|
|
3221
3713
|
{ name: "encoder_model", type: "encoder" }
|
|
3222
3714
|
];
|
|
3715
|
+
/**
|
|
3716
|
+
* Constructor for VisionManager class.
|
|
3717
|
+
*
|
|
3718
|
+
* @param {string} cacheDir - The directory path for caching vision models.
|
|
3719
|
+
*/
|
|
3223
3720
|
constructor(cacheDir) {
|
|
3224
3721
|
this.modelsDir = path4.join(path4.dirname(cacheDir), "models", "vision");
|
|
3225
3722
|
this.cacheDir = cacheDir;
|
|
@@ -3229,8 +3726,12 @@ var VisionManager = class _VisionManager {
|
|
|
3229
3726
|
this.modelsDir
|
|
3230
3727
|
);
|
|
3231
3728
|
this.platformConfig = this.getPlatformConfig();
|
|
3232
|
-
|
|
3729
|
+
logger9.info("VisionManager initialized");
|
|
3233
3730
|
}
|
|
3731
|
+
/**
|
|
3732
|
+
* Retrieves the platform configuration based on the operating system and architecture.
|
|
3733
|
+
* @returns {PlatformConfig} The platform configuration object with device, dtype, and useOnnx properties.
|
|
3734
|
+
*/
|
|
3234
3735
|
getPlatformConfig() {
|
|
3235
3736
|
const platform = os2.platform();
|
|
3236
3737
|
const arch = os2.arch();
|
|
@@ -3255,25 +3756,42 @@ var VisionManager = class _VisionManager {
|
|
|
3255
3756
|
};
|
|
3256
3757
|
}
|
|
3257
3758
|
}
|
|
3258
|
-
|
|
3759
|
+
logger9.info("Platform configuration detected:", {
|
|
3259
3760
|
platform,
|
|
3260
3761
|
arch,
|
|
3261
3762
|
config
|
|
3262
3763
|
});
|
|
3263
3764
|
return config;
|
|
3264
3765
|
}
|
|
3766
|
+
/**
|
|
3767
|
+
* Ensures that the models directory exists. If it does not exist, it creates the directory.
|
|
3768
|
+
*/
|
|
3265
3769
|
ensureModelsDirExists() {
|
|
3266
3770
|
if (!existsSync(this.modelsDir)) {
|
|
3267
|
-
|
|
3771
|
+
logger9.info(`Creating models directory at: ${this.modelsDir}`);
|
|
3268
3772
|
fs4.mkdirSync(this.modelsDir, { recursive: true });
|
|
3269
3773
|
}
|
|
3270
3774
|
}
|
|
3775
|
+
/**
|
|
3776
|
+
* Returns the singleton instance of VisionManager.
|
|
3777
|
+
* If an instance does not already exist, a new instance is created with the specified cache directory.
|
|
3778
|
+
*
|
|
3779
|
+
* @param {string} cacheDir - The directory where cache files will be stored.
|
|
3780
|
+
*
|
|
3781
|
+
* @returns {VisionManager} The singleton instance of VisionManager.
|
|
3782
|
+
*/
|
|
3271
3783
|
static getInstance(cacheDir) {
|
|
3272
3784
|
if (!_VisionManager.instance) {
|
|
3273
3785
|
_VisionManager.instance = new _VisionManager(cacheDir);
|
|
3274
3786
|
}
|
|
3275
3787
|
return _VisionManager.instance;
|
|
3276
3788
|
}
|
|
3789
|
+
/**
|
|
3790
|
+
* Check if the cache exists for the specified model or tokenizer or processor.
|
|
3791
|
+
* @param {string} modelId - The ID of the model.
|
|
3792
|
+
* @param {"model" | "tokenizer" | "processor"} type - The type of the cache ("model", "tokenizer", or "processor").
|
|
3793
|
+
* @returns {boolean} - Returns true if cache exists, otherwise returns false.
|
|
3794
|
+
*/
|
|
3277
3795
|
checkCacheExists(modelId, type) {
|
|
3278
3796
|
const modelPath = path4.join(
|
|
3279
3797
|
this.modelsDir,
|
|
@@ -3281,11 +3799,16 @@ var VisionManager = class _VisionManager {
|
|
|
3281
3799
|
type
|
|
3282
3800
|
);
|
|
3283
3801
|
if (existsSync(modelPath)) {
|
|
3284
|
-
|
|
3802
|
+
logger9.info(`${type} found at: ${modelPath}`);
|
|
3285
3803
|
return true;
|
|
3286
3804
|
}
|
|
3287
3805
|
return false;
|
|
3288
3806
|
}
|
|
3807
|
+
/**
|
|
3808
|
+
* Configures the model components based on the platform and architecture.
|
|
3809
|
+
* Sets the default data type (dtype) for components based on platform capabilities.
|
|
3810
|
+
* Updates all component dtypes to match the default dtype.
|
|
3811
|
+
*/
|
|
3289
3812
|
configureModelComponents() {
|
|
3290
3813
|
const platform = os2.platform();
|
|
3291
3814
|
const arch = os2.arch();
|
|
@@ -3299,13 +3822,18 @@ var VisionManager = class _VisionManager {
|
|
|
3299
3822
|
...component,
|
|
3300
3823
|
dtype: defaultDtype
|
|
3301
3824
|
}));
|
|
3302
|
-
|
|
3825
|
+
logger9.info("Model components configured with dtype:", {
|
|
3303
3826
|
platform,
|
|
3304
3827
|
arch,
|
|
3305
3828
|
defaultDtype,
|
|
3306
3829
|
components: this.modelComponents.map((c) => `${c.name}: ${c.dtype}`)
|
|
3307
3830
|
});
|
|
3308
3831
|
}
|
|
3832
|
+
/**
|
|
3833
|
+
* Get the model configuration based on the input component name.
|
|
3834
|
+
* @param {string} componentName - The name of the component to retrieve the configuration for.
|
|
3835
|
+
* @returns {object} The model configuration object containing device, dtype, and cache_dir.
|
|
3836
|
+
*/
|
|
3309
3837
|
getModelConfig(componentName) {
|
|
3310
3838
|
const component = this.modelComponents.find(
|
|
3311
3839
|
(c) => c.name === componentName
|
|
@@ -3316,24 +3844,30 @@ var VisionManager = class _VisionManager {
|
|
|
3316
3844
|
cache_dir: this.modelsDir
|
|
3317
3845
|
};
|
|
3318
3846
|
}
|
|
3847
|
+
/**
|
|
3848
|
+
* Asynchronous method to initialize the vision model by loading Florence2 model, vision tokenizer, and vision processor.
|
|
3849
|
+
*
|
|
3850
|
+
* @returns {Promise<void>} - Promise that resolves once the initialization process is completed.
|
|
3851
|
+
* @throws {Error} - If there is an error during the initialization process.
|
|
3852
|
+
*/
|
|
3319
3853
|
async initialize() {
|
|
3320
3854
|
try {
|
|
3321
3855
|
if (this.initialized) {
|
|
3322
|
-
|
|
3856
|
+
logger9.info(
|
|
3323
3857
|
"Vision model already initialized, skipping initialization"
|
|
3324
3858
|
);
|
|
3325
3859
|
return;
|
|
3326
3860
|
}
|
|
3327
|
-
|
|
3861
|
+
logger9.info("Starting vision model initialization...");
|
|
3328
3862
|
const modelSpec = MODEL_SPECS.vision;
|
|
3329
|
-
|
|
3863
|
+
logger9.info("Configuring environment for vision model...");
|
|
3330
3864
|
env.allowLocalModels = true;
|
|
3331
3865
|
env.allowRemoteModels = true;
|
|
3332
3866
|
if (this.platformConfig.useOnnx) {
|
|
3333
3867
|
env.backends.onnx.enabled = true;
|
|
3334
3868
|
env.backends.onnx.logLevel = "info";
|
|
3335
3869
|
}
|
|
3336
|
-
|
|
3870
|
+
logger9.info("Loading Florence2 model...");
|
|
3337
3871
|
try {
|
|
3338
3872
|
let lastProgress = -1;
|
|
3339
3873
|
const modelCached = this.checkCacheExists(modelSpec.modelId, "model");
|
|
@@ -3355,7 +3889,7 @@ var VisionManager = class _VisionManager {
|
|
|
3355
3889
|
currentProgress / 100 * barLength
|
|
3356
3890
|
);
|
|
3357
3891
|
const progressBar = "\u25B0".repeat(filledLength) + "\u25B1".repeat(barLength - filledLength);
|
|
3358
|
-
|
|
3892
|
+
logger9.info(
|
|
3359
3893
|
`Downloading vision model: ${progressBar} ${currentProgress}%`
|
|
3360
3894
|
);
|
|
3361
3895
|
if (currentProgress === 100) this.modelDownloaded = true;
|
|
@@ -3364,16 +3898,16 @@ var VisionManager = class _VisionManager {
|
|
|
3364
3898
|
}
|
|
3365
3899
|
);
|
|
3366
3900
|
this.model = model;
|
|
3367
|
-
|
|
3901
|
+
logger9.success("Florence2 model loaded successfully");
|
|
3368
3902
|
} catch (error) {
|
|
3369
|
-
|
|
3903
|
+
logger9.error("Failed to load Florence2 model:", {
|
|
3370
3904
|
error: error instanceof Error ? error.message : String(error),
|
|
3371
3905
|
stack: error instanceof Error ? error.stack : void 0,
|
|
3372
3906
|
modelId: modelSpec.modelId
|
|
3373
3907
|
});
|
|
3374
3908
|
throw error;
|
|
3375
3909
|
}
|
|
3376
|
-
|
|
3910
|
+
logger9.info("Loading vision tokenizer...");
|
|
3377
3911
|
try {
|
|
3378
3912
|
const tokenizerCached = this.checkCacheExists(
|
|
3379
3913
|
modelSpec.modelId,
|
|
@@ -3396,7 +3930,7 @@ var VisionManager = class _VisionManager {
|
|
|
3396
3930
|
currentProgress / 100 * barLength
|
|
3397
3931
|
);
|
|
3398
3932
|
const progressBar = "\u25B0".repeat(filledLength) + "\u25B1".repeat(barLength - filledLength);
|
|
3399
|
-
|
|
3933
|
+
logger9.info(
|
|
3400
3934
|
`Downloading vision tokenizer: ${progressBar} ${currentProgress}%`
|
|
3401
3935
|
);
|
|
3402
3936
|
if (currentProgress === 100) this.tokenizerDownloaded = true;
|
|
@@ -3404,16 +3938,16 @@ var VisionManager = class _VisionManager {
|
|
|
3404
3938
|
}
|
|
3405
3939
|
}
|
|
3406
3940
|
);
|
|
3407
|
-
|
|
3941
|
+
logger9.success("Vision tokenizer loaded successfully");
|
|
3408
3942
|
} catch (error) {
|
|
3409
|
-
|
|
3943
|
+
logger9.error("Failed to load tokenizer:", {
|
|
3410
3944
|
error: error instanceof Error ? error.message : String(error),
|
|
3411
3945
|
stack: error instanceof Error ? error.stack : void 0,
|
|
3412
3946
|
modelId: modelSpec.modelId
|
|
3413
3947
|
});
|
|
3414
3948
|
throw error;
|
|
3415
3949
|
}
|
|
3416
|
-
|
|
3950
|
+
logger9.info("Loading vision processor...");
|
|
3417
3951
|
try {
|
|
3418
3952
|
const processorCached = this.checkCacheExists(
|
|
3419
3953
|
modelSpec.modelId,
|
|
@@ -3437,7 +3971,7 @@ var VisionManager = class _VisionManager {
|
|
|
3437
3971
|
currentProgress / 100 * barLength
|
|
3438
3972
|
);
|
|
3439
3973
|
const progressBar = "\u25B0".repeat(filledLength) + "\u25B1".repeat(barLength - filledLength);
|
|
3440
|
-
|
|
3974
|
+
logger9.info(
|
|
3441
3975
|
`Downloading vision processor: ${progressBar} ${currentProgress}%`
|
|
3442
3976
|
);
|
|
3443
3977
|
if (currentProgress === 100) this.processorDownloaded = true;
|
|
@@ -3445,9 +3979,9 @@ var VisionManager = class _VisionManager {
|
|
|
3445
3979
|
}
|
|
3446
3980
|
}
|
|
3447
3981
|
);
|
|
3448
|
-
|
|
3982
|
+
logger9.success("Vision processor loaded successfully");
|
|
3449
3983
|
} catch (error) {
|
|
3450
|
-
|
|
3984
|
+
logger9.error("Failed to load vision processor:", {
|
|
3451
3985
|
error: error instanceof Error ? error.message : String(error),
|
|
3452
3986
|
stack: error instanceof Error ? error.stack : void 0,
|
|
3453
3987
|
modelId: modelSpec.modelId
|
|
@@ -3455,9 +3989,9 @@ var VisionManager = class _VisionManager {
|
|
|
3455
3989
|
throw error;
|
|
3456
3990
|
}
|
|
3457
3991
|
this.initialized = true;
|
|
3458
|
-
|
|
3992
|
+
logger9.success("Vision model initialization complete");
|
|
3459
3993
|
} catch (error) {
|
|
3460
|
-
|
|
3994
|
+
logger9.error("Vision model initialization failed:", {
|
|
3461
3995
|
error: error instanceof Error ? error.message : String(error),
|
|
3462
3996
|
stack: error instanceof Error ? error.stack : void 0,
|
|
3463
3997
|
modelsDir: this.modelsDir
|
|
@@ -3465,15 +3999,21 @@ var VisionManager = class _VisionManager {
|
|
|
3465
3999
|
throw error;
|
|
3466
4000
|
}
|
|
3467
4001
|
}
|
|
4002
|
+
/**
|
|
4003
|
+
* Fetches an image from a given URL and returns the image data as a Buffer along with its MIME type.
|
|
4004
|
+
*
|
|
4005
|
+
* @param {string} url - The URL of the image to fetch.
|
|
4006
|
+
* @returns {Promise<{ buffer: Buffer; mimeType: string }>} Object containing the image data as a Buffer and its MIME type.
|
|
4007
|
+
*/
|
|
3468
4008
|
async fetchImage(url) {
|
|
3469
4009
|
try {
|
|
3470
|
-
|
|
4010
|
+
logger9.info(`Fetching image from URL: ${url.slice(0, 100)}...`);
|
|
3471
4011
|
if (url.startsWith("data:")) {
|
|
3472
|
-
|
|
4012
|
+
logger9.info("Processing data URL...");
|
|
3473
4013
|
const [header, base64Data] = url.split(",");
|
|
3474
4014
|
const mimeType2 = header.split(";")[0].split(":")[1];
|
|
3475
4015
|
const buffer2 = Buffer.from(base64Data, "base64");
|
|
3476
|
-
|
|
4016
|
+
logger9.info("Data URL processed successfully");
|
|
3477
4017
|
return { buffer: buffer2, mimeType: mimeType2 };
|
|
3478
4018
|
}
|
|
3479
4019
|
const response = await fetch(url);
|
|
@@ -3482,14 +4022,14 @@ var VisionManager = class _VisionManager {
|
|
|
3482
4022
|
}
|
|
3483
4023
|
const buffer = Buffer.from(await response.arrayBuffer());
|
|
3484
4024
|
const mimeType = response.headers.get("content-type") || "image/jpeg";
|
|
3485
|
-
|
|
4025
|
+
logger9.info("Image fetched successfully:", {
|
|
3486
4026
|
mimeType,
|
|
3487
4027
|
bufferSize: buffer.length,
|
|
3488
4028
|
status: response.status
|
|
3489
4029
|
});
|
|
3490
4030
|
return { buffer, mimeType };
|
|
3491
4031
|
} catch (error) {
|
|
3492
|
-
|
|
4032
|
+
logger9.error("Failed to fetch image:", {
|
|
3493
4033
|
error: error instanceof Error ? error.message : String(error),
|
|
3494
4034
|
stack: error instanceof Error ? error.stack : void 0,
|
|
3495
4035
|
url
|
|
@@ -3497,39 +4037,44 @@ var VisionManager = class _VisionManager {
|
|
|
3497
4037
|
throw error;
|
|
3498
4038
|
}
|
|
3499
4039
|
}
|
|
4040
|
+
/**
|
|
4041
|
+
* Processes the image from the provided URL using the initialized vision model components.
|
|
4042
|
+
* @param {string} imageUrl - The URL of the image to process.
|
|
4043
|
+
* @returns {Promise<{ title: string; description: string }>} An object containing the title and description of the processed image.
|
|
4044
|
+
*/
|
|
3500
4045
|
async processImage(imageUrl) {
|
|
3501
4046
|
try {
|
|
3502
|
-
|
|
4047
|
+
logger9.info("Starting image processing...");
|
|
3503
4048
|
if (!this.initialized) {
|
|
3504
|
-
|
|
4049
|
+
logger9.info("Vision model not initialized, initializing now...");
|
|
3505
4050
|
await this.initialize();
|
|
3506
4051
|
}
|
|
3507
4052
|
if (!this.model || !this.processor || !this.tokenizer) {
|
|
3508
4053
|
throw new Error("Vision model components not properly initialized");
|
|
3509
4054
|
}
|
|
3510
|
-
|
|
4055
|
+
logger9.info("Fetching image...");
|
|
3511
4056
|
const { buffer, mimeType } = await this.fetchImage(imageUrl);
|
|
3512
|
-
|
|
4057
|
+
logger9.info("Creating image blob...");
|
|
3513
4058
|
const blob = new Blob([buffer], { type: mimeType });
|
|
3514
|
-
|
|
4059
|
+
logger9.info("Converting blob to RawImage...");
|
|
3515
4060
|
const image = await RawImage.fromBlob(blob);
|
|
3516
|
-
|
|
4061
|
+
logger9.info("Processing image with vision processor...");
|
|
3517
4062
|
const visionInputs = await this.processor(image);
|
|
3518
|
-
|
|
4063
|
+
logger9.info("Constructing prompts...");
|
|
3519
4064
|
const prompts = this.processor.construct_prompts("<DETAILED_CAPTION>");
|
|
3520
|
-
|
|
4065
|
+
logger9.info("Tokenizing prompts...");
|
|
3521
4066
|
const textInputs = this.tokenizer(prompts);
|
|
3522
|
-
|
|
4067
|
+
logger9.info("Generating image description...");
|
|
3523
4068
|
const generatedIds = await this.model.generate({
|
|
3524
4069
|
...textInputs,
|
|
3525
4070
|
...visionInputs,
|
|
3526
4071
|
max_new_tokens: MODEL_SPECS.vision.maxTokens
|
|
3527
4072
|
});
|
|
3528
|
-
|
|
4073
|
+
logger9.info("Decoding generated text...");
|
|
3529
4074
|
const generatedText = this.tokenizer.batch_decode(generatedIds, {
|
|
3530
4075
|
skip_special_tokens: false
|
|
3531
4076
|
})[0];
|
|
3532
|
-
|
|
4077
|
+
logger9.info("Post-processing generation...");
|
|
3533
4078
|
const result = this.processor.post_process_generation(
|
|
3534
4079
|
generatedText,
|
|
3535
4080
|
"<DETAILED_CAPTION>",
|
|
@@ -3540,103 +4085,25 @@ var VisionManager = class _VisionManager {
|
|
|
3540
4085
|
title: `${detailedCaption.split(".")[0]}.`,
|
|
3541
4086
|
description: detailedCaption
|
|
3542
4087
|
};
|
|
3543
|
-
|
|
4088
|
+
logger9.success("Image processing complete:", {
|
|
3544
4089
|
titleLength: response.title.length,
|
|
3545
4090
|
descriptionLength: response.description.length
|
|
3546
4091
|
});
|
|
3547
4092
|
return response;
|
|
3548
4093
|
} catch (error) {
|
|
3549
|
-
|
|
4094
|
+
logger9.error("Image processing failed:", {
|
|
3550
4095
|
error: error instanceof Error ? error.message : String(error),
|
|
3551
4096
|
stack: error instanceof Error ? error.stack : void 0,
|
|
3552
4097
|
imageUrl,
|
|
3553
4098
|
modelInitialized: this.initialized,
|
|
3554
4099
|
hasModel: !!this.model,
|
|
3555
|
-
hasProcessor: !!this.processor,
|
|
3556
|
-
hasTokenizer: !!this.tokenizer
|
|
3557
|
-
});
|
|
3558
|
-
throw error;
|
|
3559
|
-
}
|
|
3560
|
-
}
|
|
3561
|
-
};
|
|
3562
|
-
|
|
3563
|
-
// src/environment.ts
|
|
3564
|
-
import { z } from "zod";
|
|
3565
|
-
import { logger as logger9 } from "@elizaos/core";
|
|
3566
|
-
var configSchema = z.object({
|
|
3567
|
-
USE_LOCAL_AI: z.boolean().default(true),
|
|
3568
|
-
USE_STUDIOLM_TEXT_MODELS: z.boolean().default(false),
|
|
3569
|
-
USE_OLLAMA_TEXT_MODELS: z.boolean().default(false),
|
|
3570
|
-
// Ollama Configuration
|
|
3571
|
-
OLLAMA_SERVER_URL: z.string().default("http://localhost:11434"),
|
|
3572
|
-
OLLAMA_MODEL: z.string().default("deepseek-r1-distill-qwen-7b"),
|
|
3573
|
-
USE_OLLAMA_EMBEDDING: z.boolean().default(false),
|
|
3574
|
-
OLLAMA_EMBEDDING_MODEL: z.string().default(""),
|
|
3575
|
-
SMALL_OLLAMA_MODEL: z.string().default("deepseek-r1:1.5b"),
|
|
3576
|
-
MEDIUM_OLLAMA_MODEL: z.string().default("deepseek-r1:7b"),
|
|
3577
|
-
LARGE_OLLAMA_MODEL: z.string().default("deepseek-r1:7b"),
|
|
3578
|
-
// StudioLM Configuration
|
|
3579
|
-
STUDIOLM_SERVER_URL: z.string().default("http://localhost:1234"),
|
|
3580
|
-
STUDIOLM_SMALL_MODEL: z.string().default("lmstudio-community/deepseek-r1-distill-qwen-1.5b"),
|
|
3581
|
-
STUDIOLM_MEDIUM_MODEL: z.string().default("deepseek-r1-distill-qwen-7b"),
|
|
3582
|
-
STUDIOLM_EMBEDDING_MODEL: z.union([z.boolean(), z.string()]).default(false)
|
|
3583
|
-
});
|
|
3584
|
-
function validateModelConfig(config) {
|
|
3585
|
-
logger9.info("Validating model configuration with values:", {
|
|
3586
|
-
USE_LOCAL_AI: config.USE_LOCAL_AI,
|
|
3587
|
-
USE_STUDIOLM_TEXT_MODELS: config.USE_STUDIOLM_TEXT_MODELS,
|
|
3588
|
-
USE_OLLAMA_TEXT_MODELS: config.USE_OLLAMA_TEXT_MODELS
|
|
3589
|
-
});
|
|
3590
|
-
if (!config.USE_LOCAL_AI) {
|
|
3591
|
-
config.USE_LOCAL_AI = true;
|
|
3592
|
-
logger9.info("Setting USE_LOCAL_AI to true as it's required");
|
|
3593
|
-
}
|
|
3594
|
-
if (config.USE_STUDIOLM_TEXT_MODELS && config.USE_OLLAMA_TEXT_MODELS) {
|
|
3595
|
-
throw new Error(
|
|
3596
|
-
"StudioLM and Ollama text models cannot be enabled simultaneously"
|
|
3597
|
-
);
|
|
3598
|
-
}
|
|
3599
|
-
logger9.info("Configuration is valid");
|
|
3600
|
-
}
|
|
3601
|
-
async function validateConfig(config) {
|
|
3602
|
-
try {
|
|
3603
|
-
const booleanConfig = {
|
|
3604
|
-
USE_LOCAL_AI: true,
|
|
3605
|
-
// Always true
|
|
3606
|
-
USE_STUDIOLM_TEXT_MODELS: config.USE_STUDIOLM_TEXT_MODELS === "true",
|
|
3607
|
-
USE_OLLAMA_TEXT_MODELS: config.USE_OLLAMA_TEXT_MODELS === "true",
|
|
3608
|
-
USE_OLLAMA_EMBEDDING: config.USE_OLLAMA_EMBEDDING === "true"
|
|
3609
|
-
};
|
|
3610
|
-
validateModelConfig(booleanConfig);
|
|
3611
|
-
const fullConfig = {
|
|
3612
|
-
...booleanConfig,
|
|
3613
|
-
OLLAMA_SERVER_URL: config.OLLAMA_SERVER_URL || "http://localhost:11434",
|
|
3614
|
-
OLLAMA_MODEL: config.OLLAMA_MODEL || "deepseek-r1-distill-qwen-7b",
|
|
3615
|
-
OLLAMA_EMBEDDING_MODEL: config.OLLAMA_EMBEDDING_MODEL || "",
|
|
3616
|
-
SMALL_OLLAMA_MODEL: config.SMALL_OLLAMA_MODEL || "deepseek-r1:1.5b",
|
|
3617
|
-
MEDIUM_OLLAMA_MODEL: config.MEDIUM_OLLAMA_MODEL || "deepseek-r1:7b",
|
|
3618
|
-
LARGE_OLLAMA_MODEL: config.LARGE_OLLAMA_MODEL || "deepseek-r1:7b",
|
|
3619
|
-
STUDIOLM_SERVER_URL: config.STUDIOLM_SERVER_URL || "http://localhost:1234",
|
|
3620
|
-
STUDIOLM_SMALL_MODEL: config.STUDIOLM_SMALL_MODEL || "lmstudio-community/deepseek-r1-distill-qwen-1.5b",
|
|
3621
|
-
STUDIOLM_MEDIUM_MODEL: config.STUDIOLM_MEDIUM_MODEL || "deepseek-r1-distill-qwen-7b",
|
|
3622
|
-
STUDIOLM_EMBEDDING_MODEL: config.STUDIOLM_EMBEDDING_MODEL || false
|
|
3623
|
-
};
|
|
3624
|
-
const validatedConfig = configSchema.parse(fullConfig);
|
|
3625
|
-
return validatedConfig;
|
|
3626
|
-
} catch (error) {
|
|
3627
|
-
if (error instanceof z.ZodError) {
|
|
3628
|
-
const errorMessages = error.errors.map((err) => `${err.path.join(".")}: ${err.message}`).join("\n");
|
|
3629
|
-
logger9.error("Zod validation failed:", errorMessages);
|
|
3630
|
-
throw new Error(`Configuration validation failed:
|
|
3631
|
-
${errorMessages}`);
|
|
4100
|
+
hasProcessor: !!this.processor,
|
|
4101
|
+
hasTokenizer: !!this.tokenizer
|
|
4102
|
+
});
|
|
4103
|
+
throw error;
|
|
3632
4104
|
}
|
|
3633
|
-
logger9.error("Configuration validation failed:", {
|
|
3634
|
-
error: error instanceof Error ? error.message : String(error),
|
|
3635
|
-
stack: error instanceof Error ? error.stack : void 0
|
|
3636
|
-
});
|
|
3637
|
-
throw error;
|
|
3638
4105
|
}
|
|
3639
|
-
}
|
|
4106
|
+
};
|
|
3640
4107
|
|
|
3641
4108
|
// src/index.ts
|
|
3642
4109
|
var __filename = fileURLToPath(import.meta.url);
|
|
@@ -3711,141 +4178,87 @@ var LocalAIManager = class _LocalAIManager {
|
|
|
3711
4178
|
ttsManager;
|
|
3712
4179
|
studioLMManager;
|
|
3713
4180
|
ollamaManager;
|
|
4181
|
+
// Initialization state flags
|
|
3714
4182
|
ollamaInitialized = false;
|
|
3715
4183
|
studioLMInitialized = false;
|
|
4184
|
+
smallModelInitialized = false;
|
|
4185
|
+
mediumModelInitialized = false;
|
|
4186
|
+
embeddingInitialized = false;
|
|
4187
|
+
visionInitialized = false;
|
|
4188
|
+
transcriptionInitialized = false;
|
|
4189
|
+
ttsInitialized = false;
|
|
4190
|
+
// Initialization promises to prevent duplicate initialization
|
|
4191
|
+
smallModelInitializingPromise = null;
|
|
4192
|
+
mediumModelInitializingPromise = null;
|
|
4193
|
+
embeddingInitializingPromise = null;
|
|
4194
|
+
visionInitializingPromise = null;
|
|
4195
|
+
transcriptionInitializingPromise = null;
|
|
4196
|
+
ttsInitializingPromise = null;
|
|
4197
|
+
ollamaInitializingPromise = null;
|
|
4198
|
+
studioLMInitializingPromise = null;
|
|
3716
4199
|
modelsDir;
|
|
4200
|
+
/**
|
|
4201
|
+
* Private constructor function to initialize base managers and paths.
|
|
4202
|
+
* This now only sets up the basic infrastructure without loading any models.
|
|
4203
|
+
*/
|
|
3717
4204
|
constructor() {
|
|
3718
4205
|
const modelsDir = path5.join(process.cwd(), "models");
|
|
3719
|
-
|
|
3720
|
-
|
|
3721
|
-
|
|
3722
|
-
|
|
3723
|
-
|
|
3724
|
-
|
|
3725
|
-
|
|
3726
|
-
this.modelsDir
|
|
4206
|
+
if (process.env.LLAMALOCAL_PATH?.trim()) {
|
|
4207
|
+
this.modelsDir = path5.resolve(process.env.LLAMALOCAL_PATH.trim());
|
|
4208
|
+
} else {
|
|
4209
|
+
if (!fs5.existsSync(modelsDir)) {
|
|
4210
|
+
fs5.mkdirSync(modelsDir, { recursive: true });
|
|
4211
|
+
logger10.info("Created models directory");
|
|
4212
|
+
}
|
|
4213
|
+
this.modelsDir = modelsDir;
|
|
4214
|
+
}
|
|
4215
|
+
this.modelPath = path5.join(
|
|
4216
|
+
this.modelsDir,
|
|
4217
|
+
"DeepSeek-R1-Distill-Qwen-1.5B-Q8_0.gguf"
|
|
3727
4218
|
);
|
|
3728
|
-
this.
|
|
3729
|
-
this.
|
|
3730
|
-
|
|
4219
|
+
this.mediumModelPath = path5.join(
|
|
4220
|
+
this.modelsDir,
|
|
4221
|
+
"DeepSeek-R1-Distill-Qwen-7B-Q5_K_M.gguf"
|
|
3731
4222
|
);
|
|
3732
|
-
|
|
3733
|
-
|
|
3734
|
-
|
|
4223
|
+
const cacheDirEnv = process.env.CACHE_DIR?.trim();
|
|
4224
|
+
if (cacheDirEnv) {
|
|
4225
|
+
this.cacheDir = path5.resolve(cacheDirEnv);
|
|
4226
|
+
} else {
|
|
4227
|
+
const cacheDir = path5.join(process.cwd(), "cache");
|
|
4228
|
+
if (!fs5.existsSync(cacheDir)) {
|
|
4229
|
+
fs5.mkdirSync(cacheDir, { recursive: true });
|
|
4230
|
+
logger10.info("Ensuring cache directory exists:", cacheDir);
|
|
4231
|
+
}
|
|
4232
|
+
this.cacheDir = cacheDir;
|
|
4233
|
+
}
|
|
4234
|
+
this.downloadManager = DownloadManager.getInstance();
|
|
4235
|
+
this.tokenizerManager = TokenizerManager.getInstance();
|
|
4236
|
+
this.visionManager = VisionManager.getInstance();
|
|
4237
|
+
this.transcribeManager = TranscribeManager.getInstance();
|
|
4238
|
+
this.ttsManager = TTSManager.getInstance();
|
|
3735
4239
|
if (process.env.USE_STUDIOLM_TEXT_MODELS === "true") {
|
|
3736
|
-
logger10.info(
|
|
3737
|
-
"Creating StudioLM manager instance (enabled in environment)"
|
|
3738
|
-
);
|
|
3739
4240
|
this.studioLMManager = StudioLMManager.getInstance();
|
|
3740
|
-
} else {
|
|
3741
|
-
logger10.info(
|
|
3742
|
-
"StudioLM manager instance not created (disabled in environment)"
|
|
3743
|
-
);
|
|
3744
4241
|
}
|
|
3745
4242
|
if (process.env.USE_OLLAMA_TEXT_MODELS === "true") {
|
|
3746
|
-
logger10.info("Creating Ollama manager instance (enabled in environment)");
|
|
3747
4243
|
this.ollamaManager = OllamaManager.getInstance();
|
|
3748
|
-
} else {
|
|
3749
|
-
logger10.info(
|
|
3750
|
-
"Ollama manager instance not created (disabled in environment)"
|
|
3751
|
-
);
|
|
3752
|
-
}
|
|
3753
|
-
this.initializeEnvironment().catch((error) => {
|
|
3754
|
-
logger10.error("Environment initialization failed:", error);
|
|
3755
|
-
throw error;
|
|
3756
|
-
});
|
|
3757
|
-
this.checkPlatformCapabilities().catch((error) => {
|
|
3758
|
-
logger10.warn("Platform capabilities check failed:", error);
|
|
3759
|
-
});
|
|
3760
|
-
this.initializeEmbedding().catch((error) => {
|
|
3761
|
-
logger10.warn("Embedding initialization failed:", error);
|
|
3762
|
-
});
|
|
3763
|
-
logger10.info("Starting model initialization sequence");
|
|
3764
|
-
this.initialize(ModelTypes3.TEXT_SMALL).then(() => {
|
|
3765
|
-
logger10.info(
|
|
3766
|
-
"Small model initialization complete, starting large model initialization"
|
|
3767
|
-
);
|
|
3768
|
-
return this.initialize(ModelTypes3.TEXT_LARGE);
|
|
3769
|
-
}).catch((error) => {
|
|
3770
|
-
logger10.warn("Models initialization failed:", {
|
|
3771
|
-
stack: error instanceof Error ? error.stack : void 0,
|
|
3772
|
-
error: error instanceof Error ? error.message : String(error)
|
|
3773
|
-
});
|
|
3774
|
-
});
|
|
3775
|
-
const servicePromises = [
|
|
3776
|
-
// Add vision initialization using a public method
|
|
3777
|
-
this.initializeVision().catch((error) => {
|
|
3778
|
-
logger10.warn("Vision initialization failed:", error);
|
|
3779
|
-
return null;
|
|
3780
|
-
}),
|
|
3781
|
-
// Add transcription initialization with better error handling
|
|
3782
|
-
this.initializeTranscription().catch((error) => {
|
|
3783
|
-
logger10.warn("Transcription initialization failed:", {
|
|
3784
|
-
error: error instanceof Error ? error.message : String(error),
|
|
3785
|
-
stack: error instanceof Error ? error.stack : void 0
|
|
3786
|
-
});
|
|
3787
|
-
return null;
|
|
3788
|
-
}),
|
|
3789
|
-
// Add TTS initialization
|
|
3790
|
-
this.initializeTTS().catch((error) => {
|
|
3791
|
-
logger10.warn("TTS initialization failed:", {
|
|
3792
|
-
error: error instanceof Error ? error.message : String(error),
|
|
3793
|
-
stack: error instanceof Error ? error.stack : void 0
|
|
3794
|
-
});
|
|
3795
|
-
return null;
|
|
3796
|
-
})
|
|
3797
|
-
];
|
|
3798
|
-
if (process.env.USE_STUDIOLM_TEXT_MODELS === "true" && this.studioLMManager) {
|
|
3799
|
-
logger10.info(
|
|
3800
|
-
"StudioLM initialization enabled by environment configuration"
|
|
3801
|
-
);
|
|
3802
|
-
servicePromises.push(
|
|
3803
|
-
this.initializeStudioLM().then(() => {
|
|
3804
|
-
this.studioLMInitialized = true;
|
|
3805
|
-
}).catch((error) => {
|
|
3806
|
-
logger10.warn("StudioLM initialization failed:", {
|
|
3807
|
-
error: error instanceof Error ? error.message : String(error),
|
|
3808
|
-
stack: error instanceof Error ? error.stack : void 0
|
|
3809
|
-
});
|
|
3810
|
-
return null;
|
|
3811
|
-
})
|
|
3812
|
-
);
|
|
3813
|
-
} else {
|
|
3814
|
-
logger10.info(
|
|
3815
|
-
"StudioLM initialization skipped (disabled in environment configuration or manager not created)"
|
|
3816
|
-
);
|
|
3817
|
-
}
|
|
3818
|
-
if (process.env.USE_OLLAMA_TEXT_MODELS === "true" && this.ollamaManager) {
|
|
3819
|
-
logger10.info("Ollama initialization enabled by environment configuration");
|
|
3820
|
-
servicePromises.push(
|
|
3821
|
-
this.initializeOllama().then(() => {
|
|
3822
|
-
this.ollamaInitialized = true;
|
|
3823
|
-
}).catch((error) => {
|
|
3824
|
-
logger10.warn("Ollama initialization failed:", {
|
|
3825
|
-
error: error instanceof Error ? error.message : String(error),
|
|
3826
|
-
stack: error instanceof Error ? error.stack : void 0
|
|
3827
|
-
});
|
|
3828
|
-
return null;
|
|
3829
|
-
})
|
|
3830
|
-
);
|
|
3831
|
-
} else {
|
|
3832
|
-
logger10.info(
|
|
3833
|
-
"Ollama initialization skipped (disabled in environment configuration or manager not created)"
|
|
3834
|
-
);
|
|
3835
4244
|
}
|
|
3836
|
-
|
|
3837
|
-
logger10.warn("Models initialization failed:", {
|
|
3838
|
-
error: error instanceof Error ? error.message : String(error),
|
|
3839
|
-
stack: error instanceof Error ? error.stack : void 0
|
|
3840
|
-
});
|
|
3841
|
-
});
|
|
4245
|
+
this.activeModelConfig = MODEL_SPECS.small;
|
|
3842
4246
|
}
|
|
4247
|
+
/**
|
|
4248
|
+
* Retrieves the singleton instance of LocalAIManager. If an instance does not already exist, a new one is created and returned.
|
|
4249
|
+
* @returns {LocalAIManager} The singleton instance of LocalAIManager
|
|
4250
|
+
*/
|
|
3843
4251
|
static getInstance() {
|
|
3844
4252
|
if (!_LocalAIManager.instance) {
|
|
3845
4253
|
_LocalAIManager.instance = new _LocalAIManager();
|
|
3846
4254
|
}
|
|
3847
4255
|
return _LocalAIManager.instance;
|
|
3848
4256
|
}
|
|
4257
|
+
/**
|
|
4258
|
+
* Initializes the environment by validating the configuration and setting the environment variables with the validated values.
|
|
4259
|
+
*
|
|
4260
|
+
* @returns {Promise<void>} A Promise that resolves once the environment has been successfully initialized.
|
|
4261
|
+
*/
|
|
3849
4262
|
async initializeEnvironment() {
|
|
3850
4263
|
try {
|
|
3851
4264
|
logger10.info("Validating environment configuration...");
|
|
@@ -3872,6 +4285,12 @@ var LocalAIManager = class _LocalAIManager {
|
|
|
3872
4285
|
throw error;
|
|
3873
4286
|
}
|
|
3874
4287
|
}
|
|
4288
|
+
/**
|
|
4289
|
+
* Asynchronously initializes the Ollama model.
|
|
4290
|
+
*
|
|
4291
|
+
* @returns {Promise<void>} A Promise that resolves when the initialization is complete.
|
|
4292
|
+
* @throws {Error} If the Ollama manager is not created, or if initialization of Ollama models fails.
|
|
4293
|
+
*/
|
|
3875
4294
|
async initializeOllama() {
|
|
3876
4295
|
try {
|
|
3877
4296
|
logger10.info("Initializing Ollama models...");
|
|
@@ -3894,6 +4313,11 @@ var LocalAIManager = class _LocalAIManager {
|
|
|
3894
4313
|
throw error;
|
|
3895
4314
|
}
|
|
3896
4315
|
}
|
|
4316
|
+
/**
|
|
4317
|
+
* Initializes StudioLM model with error handling.
|
|
4318
|
+
* @returns A Promise that resolves when the initialization is complete.
|
|
4319
|
+
* @throws {Error} If StudioLM manager is not created, initialization fails, or models are not properly loaded.
|
|
4320
|
+
*/
|
|
3897
4321
|
async initializeStudioLM() {
|
|
3898
4322
|
try {
|
|
3899
4323
|
logger10.info("Initializing StudioLM models...");
|
|
@@ -3917,130 +4341,12 @@ var LocalAIManager = class _LocalAIManager {
|
|
|
3917
4341
|
throw error;
|
|
3918
4342
|
}
|
|
3919
4343
|
}
|
|
3920
|
-
|
|
3921
|
-
|
|
3922
|
-
|
|
3923
|
-
|
|
3924
|
-
|
|
3925
|
-
|
|
3926
|
-
"Cannot initialize transcription without FFmpeg. Please install FFmpeg and try again."
|
|
3927
|
-
);
|
|
3928
|
-
}
|
|
3929
|
-
const samplePath = path5.join(this.cacheDir, "sample1.wav");
|
|
3930
|
-
const awsSampleUrl = "https://d2908q01vomqb2.cloudfront.net/artifacts/DBSBlogs/ML-15311/sample1.wav?_=1";
|
|
3931
|
-
if (!fs5.existsSync(samplePath)) {
|
|
3932
|
-
logger10.info(
|
|
3933
|
-
"Sample WAV file not found in cache, downloading from AWS..."
|
|
3934
|
-
);
|
|
3935
|
-
try {
|
|
3936
|
-
await this.downloadManager.downloadFromUrl(awsSampleUrl, samplePath);
|
|
3937
|
-
logger10.success("Sample WAV file downloaded successfully");
|
|
3938
|
-
} catch (downloadError) {
|
|
3939
|
-
logger10.error("Failed to download sample WAV file:", {
|
|
3940
|
-
error: downloadError instanceof Error ? downloadError.message : String(downloadError),
|
|
3941
|
-
url: awsSampleUrl,
|
|
3942
|
-
destination: samplePath,
|
|
3943
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
3944
|
-
});
|
|
3945
|
-
throw downloadError;
|
|
3946
|
-
}
|
|
3947
|
-
} else {
|
|
3948
|
-
logger10.info("Sample WAV file already exists in cache");
|
|
3949
|
-
}
|
|
3950
|
-
if (!fs5.existsSync(samplePath)) {
|
|
3951
|
-
throw new Error(
|
|
3952
|
-
`Sample audio file not found at: ${samplePath} after download attempt`
|
|
3953
|
-
);
|
|
3954
|
-
}
|
|
3955
|
-
const testAudioBuffer = fs5.readFileSync(samplePath);
|
|
3956
|
-
const result = await this.transcribeAudio(testAudioBuffer);
|
|
3957
|
-
logger10.info("Test transcription result:", {
|
|
3958
|
-
text: result,
|
|
3959
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
3960
|
-
});
|
|
3961
|
-
logger10.success("Transcription model initialization complete");
|
|
3962
|
-
} catch (error) {
|
|
3963
|
-
logger10.error("Transcription initialization failed:", {
|
|
3964
|
-
error: error instanceof Error ? error.message : String(error),
|
|
3965
|
-
stack: error instanceof Error ? error.stack : void 0,
|
|
3966
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
3967
|
-
});
|
|
3968
|
-
throw error;
|
|
3969
|
-
}
|
|
3970
|
-
}
|
|
3971
|
-
async initializeVision() {
|
|
3972
|
-
try {
|
|
3973
|
-
logger10.info("Initializing vision model...");
|
|
3974
|
-
const awsImageUrl = "https://d1.awsstatic.com/product-marketing/Rekognition/Image%20for%20facial%20analysis.3fcc22e8451b4a238540128cb5510b8cbe22da51.jpg";
|
|
3975
|
-
const imagePath = path5.join(this.cacheDir, "test_image.jpg");
|
|
3976
|
-
if (!fs5.existsSync(imagePath)) {
|
|
3977
|
-
logger10.info("Downloading test image from AWS...");
|
|
3978
|
-
try {
|
|
3979
|
-
await this.downloadManager.downloadFromUrl(awsImageUrl, imagePath);
|
|
3980
|
-
logger10.success("Test image downloaded successfully");
|
|
3981
|
-
} catch (downloadError) {
|
|
3982
|
-
logger10.error("Failed to download test image:", {
|
|
3983
|
-
error: downloadError instanceof Error ? downloadError.message : String(downloadError),
|
|
3984
|
-
url: awsImageUrl,
|
|
3985
|
-
destination: imagePath
|
|
3986
|
-
});
|
|
3987
|
-
throw downloadError;
|
|
3988
|
-
}
|
|
3989
|
-
} else {
|
|
3990
|
-
logger10.info("Test image already exists in cache");
|
|
3991
|
-
}
|
|
3992
|
-
if (!fs5.existsSync(imagePath)) {
|
|
3993
|
-
throw new Error(
|
|
3994
|
-
`Test image not found at: ${imagePath} after download attempt`
|
|
3995
|
-
);
|
|
3996
|
-
}
|
|
3997
|
-
const imageBuffer = fs5.readFileSync(imagePath);
|
|
3998
|
-
const result = await this.describeImage(imageBuffer, "image/jpeg");
|
|
3999
|
-
logger10.info("Test image description:", result);
|
|
4000
|
-
logger10.success("Vision model initialization complete");
|
|
4001
|
-
} catch (error) {
|
|
4002
|
-
logger10.error("Vision initialization failed:", error);
|
|
4003
|
-
throw error;
|
|
4004
|
-
}
|
|
4005
|
-
}
|
|
4006
|
-
async initializeTTS() {
|
|
4007
|
-
try {
|
|
4008
|
-
logger10.info("Initializing TTS model...");
|
|
4009
|
-
const testText = "ElizaOS is yours";
|
|
4010
|
-
logger10.info("Testing TTS with sample text:", { text: testText });
|
|
4011
|
-
const audioStream = await this.ttsManager.generateSpeech(testText);
|
|
4012
|
-
if (!(audioStream instanceof Readable2)) {
|
|
4013
|
-
throw new Error("TTS did not return a valid audio stream");
|
|
4014
|
-
}
|
|
4015
|
-
let dataReceived = false;
|
|
4016
|
-
await new Promise((resolve, reject) => {
|
|
4017
|
-
audioStream.on("data", () => {
|
|
4018
|
-
if (!dataReceived) {
|
|
4019
|
-
dataReceived = true;
|
|
4020
|
-
logger10.info("TTS audio stream is producing data");
|
|
4021
|
-
}
|
|
4022
|
-
});
|
|
4023
|
-
audioStream.on("end", () => {
|
|
4024
|
-
if (!dataReceived) {
|
|
4025
|
-
reject(new Error("No audio data received from TTS stream"));
|
|
4026
|
-
} else {
|
|
4027
|
-
resolve();
|
|
4028
|
-
}
|
|
4029
|
-
});
|
|
4030
|
-
audioStream.on("error", (err) => {
|
|
4031
|
-
reject(err);
|
|
4032
|
-
});
|
|
4033
|
-
});
|
|
4034
|
-
logger10.success("TTS model initialization complete");
|
|
4035
|
-
} catch (error) {
|
|
4036
|
-
logger10.error("TTS initialization failed:", {
|
|
4037
|
-
error: error instanceof Error ? error.message : String(error),
|
|
4038
|
-
stack: error instanceof Error ? error.stack : void 0,
|
|
4039
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
4040
|
-
});
|
|
4041
|
-
throw error;
|
|
4042
|
-
}
|
|
4043
|
-
}
|
|
4344
|
+
/**
|
|
4345
|
+
* Downloads the model based on the modelPath provided.
|
|
4346
|
+
* Determines whether to download a large or small model based on the current modelPath.
|
|
4347
|
+
*
|
|
4348
|
+
* @returns A Promise that resolves to a boolean indicating whether the model download was successful.
|
|
4349
|
+
*/
|
|
4044
4350
|
async downloadModel() {
|
|
4045
4351
|
try {
|
|
4046
4352
|
const isLargeModel = this.modelPath === this.mediumModelPath;
|
|
@@ -4057,6 +4363,11 @@ var LocalAIManager = class _LocalAIManager {
|
|
|
4057
4363
|
throw error;
|
|
4058
4364
|
}
|
|
4059
4365
|
}
|
|
4366
|
+
/**
|
|
4367
|
+
* Asynchronously checks the platform capabilities.
|
|
4368
|
+
*
|
|
4369
|
+
* @returns {Promise<void>} A promise that resolves once the platform capabilities have been checked.
|
|
4370
|
+
*/
|
|
4060
4371
|
async checkPlatformCapabilities() {
|
|
4061
4372
|
try {
|
|
4062
4373
|
const platformManager = getPlatformManager();
|
|
@@ -4072,65 +4383,24 @@ var LocalAIManager = class _LocalAIManager {
|
|
|
4072
4383
|
logger10.warn("Platform detection failed:", error);
|
|
4073
4384
|
}
|
|
4074
4385
|
}
|
|
4386
|
+
/**
|
|
4387
|
+
* Initializes the LocalAI Manager for a given model type.
|
|
4388
|
+
*
|
|
4389
|
+
* @param {ModelType} modelType - The type of model to initialize (default: ModelTypes.TEXT_SMALL)
|
|
4390
|
+
* @returns {Promise<void>} A promise that resolves when initialization is complete or rejects if an error occurs
|
|
4391
|
+
*/
|
|
4075
4392
|
async initialize(modelType = ModelTypes3.TEXT_SMALL) {
|
|
4076
|
-
|
|
4077
|
-
|
|
4078
|
-
|
|
4079
|
-
|
|
4080
|
-
logger10.info("Using medium model path:", this.modelPath);
|
|
4081
|
-
} else {
|
|
4082
|
-
this.modelPath = path5.join(this.modelsDir, MODEL_SPECS.small.name);
|
|
4083
|
-
logger10.info("Using small model path:", this.modelPath);
|
|
4084
|
-
}
|
|
4085
|
-
const wasNewlyDownloaded = await this.downloadModel();
|
|
4086
|
-
if (wasNewlyDownloaded) {
|
|
4087
|
-
if (modelType === ModelTypes3.TEXT_LARGE) {
|
|
4088
|
-
logger10.info(
|
|
4089
|
-
"Adding delay before loading large model to ensure download is complete..."
|
|
4090
|
-
);
|
|
4091
|
-
await new Promise((resolve) => setTimeout(resolve, 1e4));
|
|
4092
|
-
} else {
|
|
4093
|
-
logger10.info(
|
|
4094
|
-
"Adding delay before loading small model to ensure download is complete..."
|
|
4095
|
-
);
|
|
4096
|
-
await new Promise((resolve) => setTimeout(resolve, 1e4));
|
|
4097
|
-
}
|
|
4098
|
-
}
|
|
4099
|
-
if (!fs5.existsSync(this.modelPath)) {
|
|
4100
|
-
throw new Error(`Model file not found at path: ${this.modelPath}`);
|
|
4101
|
-
}
|
|
4102
|
-
this.llama = await getLlama2();
|
|
4103
|
-
if (modelType === ModelTypes3.TEXT_LARGE) {
|
|
4104
|
-
this.activeModelConfig = MODEL_SPECS.medium;
|
|
4105
|
-
logger10.info("Loading large model from:", this.modelPath);
|
|
4106
|
-
this.mediumModel = await this.llama.loadModel({
|
|
4107
|
-
modelPath: this.modelPath
|
|
4108
|
-
});
|
|
4109
|
-
this.ctx = await this.mediumModel.createContext({
|
|
4110
|
-
contextSize: MODEL_SPECS.medium.contextSize
|
|
4111
|
-
});
|
|
4112
|
-
} else {
|
|
4113
|
-
this.activeModelConfig = MODEL_SPECS.small;
|
|
4114
|
-
logger10.info("Loading small model from:", this.modelPath);
|
|
4115
|
-
this.smallModel = await this.llama.loadModel({
|
|
4116
|
-
modelPath: this.modelPath
|
|
4117
|
-
});
|
|
4118
|
-
this.ctx = await this.smallModel.createContext({
|
|
4119
|
-
contextSize: MODEL_SPECS.small.contextSize
|
|
4120
|
-
});
|
|
4121
|
-
}
|
|
4122
|
-
if (!this.ctx) {
|
|
4123
|
-
throw new Error("Failed to create prompt");
|
|
4124
|
-
}
|
|
4125
|
-
this.sequence = this.ctx.getSequence();
|
|
4126
|
-
logger10.success(
|
|
4127
|
-
`Model initialization complete for ${modelType === ModelTypes3.TEXT_LARGE ? "large" : "small"} model`
|
|
4128
|
-
);
|
|
4129
|
-
} catch (error) {
|
|
4130
|
-
logger10.error("Initialization failed:", error);
|
|
4131
|
-
throw error;
|
|
4393
|
+
if (modelType === ModelTypes3.TEXT_LARGE) {
|
|
4394
|
+
await this.lazyInitMediumModel();
|
|
4395
|
+
} else {
|
|
4396
|
+
await this.lazyInitSmallModel();
|
|
4132
4397
|
}
|
|
4133
4398
|
}
|
|
4399
|
+
/**
|
|
4400
|
+
* Asynchronously initializes the embedding model.
|
|
4401
|
+
*
|
|
4402
|
+
* @returns {Promise<void>} A promise that resolves once the initialization is complete.
|
|
4403
|
+
*/
|
|
4134
4404
|
async initializeEmbedding() {
|
|
4135
4405
|
try {
|
|
4136
4406
|
logger10.info("Initializing embedding model...");
|
|
@@ -4159,13 +4429,6 @@ var LocalAIManager = class _LocalAIManager {
|
|
|
4159
4429
|
logger10.info(`Downloading embedding model: ${completedBar} 100%`);
|
|
4160
4430
|
logger10.success("FlagEmbedding instance created successfully");
|
|
4161
4431
|
}
|
|
4162
|
-
logger10.info("Testing embedding model with sample text...");
|
|
4163
|
-
const testEmbed = await this.embeddingModel.queryEmbed("test");
|
|
4164
|
-
logger10.info(
|
|
4165
|
-
"Test embedding generated successfully, dimensions:",
|
|
4166
|
-
testEmbed.length
|
|
4167
|
-
);
|
|
4168
|
-
logger10.success("Embedding model initialization complete");
|
|
4169
4432
|
} catch (error) {
|
|
4170
4433
|
logger10.error("Embedding initialization failed with details:", {
|
|
4171
4434
|
error: error instanceof Error ? error.message : String(error),
|
|
@@ -4176,6 +4439,12 @@ var LocalAIManager = class _LocalAIManager {
|
|
|
4176
4439
|
throw error;
|
|
4177
4440
|
}
|
|
4178
4441
|
}
|
|
4442
|
+
/**
|
|
4443
|
+
* Asynchronously generates text using either StudioLM or Ollama models based on the specified parameters.
|
|
4444
|
+
*
|
|
4445
|
+
* @param {GenerateTextParams} params - The parameters for generating the text.
|
|
4446
|
+
* @returns {Promise<string>} - A promise that resolves to the generated text.
|
|
4447
|
+
*/
|
|
4179
4448
|
async generateTextOllamaStudio(params) {
|
|
4180
4449
|
try {
|
|
4181
4450
|
const modelConfig = this.getTextModelSource();
|
|
@@ -4242,28 +4511,30 @@ var LocalAIManager = class _LocalAIManager {
|
|
|
4242
4511
|
return this.generateText(params);
|
|
4243
4512
|
}
|
|
4244
4513
|
}
|
|
4514
|
+
/**
|
|
4515
|
+
* Asynchronously generates text based on the provided parameters.
|
|
4516
|
+
* Now uses lazy initialization for models
|
|
4517
|
+
*/
|
|
4245
4518
|
async generateText(params) {
|
|
4246
4519
|
try {
|
|
4247
|
-
if (!this.sequence || !this.smallModel || params.modelType === ModelTypes3.TEXT_LARGE && !this.mediumModel) {
|
|
4248
|
-
await this.initialize(params.modelType);
|
|
4249
|
-
}
|
|
4250
|
-
let activeModel;
|
|
4251
4520
|
if (params.modelType === ModelTypes3.TEXT_LARGE) {
|
|
4521
|
+
await this.lazyInitMediumModel();
|
|
4252
4522
|
if (!this.mediumModel) {
|
|
4253
|
-
throw new Error("Medium model
|
|
4523
|
+
throw new Error("Medium model initialization failed");
|
|
4254
4524
|
}
|
|
4255
4525
|
this.activeModelConfig = MODEL_SPECS.medium;
|
|
4256
|
-
|
|
4257
|
-
this.ctx = await
|
|
4526
|
+
const mediumModel = this.mediumModel;
|
|
4527
|
+
this.ctx = await mediumModel.createContext({
|
|
4258
4528
|
contextSize: MODEL_SPECS.medium.contextSize
|
|
4259
4529
|
});
|
|
4260
4530
|
} else {
|
|
4531
|
+
await this.lazyInitSmallModel();
|
|
4261
4532
|
if (!this.smallModel) {
|
|
4262
|
-
throw new Error("Small model
|
|
4533
|
+
throw new Error("Small model initialization failed");
|
|
4263
4534
|
}
|
|
4264
4535
|
this.activeModelConfig = MODEL_SPECS.small;
|
|
4265
|
-
|
|
4266
|
-
this.ctx = await
|
|
4536
|
+
const smallModel = this.smallModel;
|
|
4537
|
+
this.ctx = await smallModel.createContext({
|
|
4267
4538
|
contextSize: MODEL_SPECS.small.contextSize
|
|
4268
4539
|
});
|
|
4269
4540
|
}
|
|
@@ -4300,7 +4571,7 @@ var LocalAIManager = class _LocalAIManager {
|
|
|
4300
4571
|
temperature: 0.7,
|
|
4301
4572
|
topP: 0.9,
|
|
4302
4573
|
repeatPenalty: {
|
|
4303
|
-
punishTokensFilter: () =>
|
|
4574
|
+
punishTokensFilter: () => this.smallModel.tokenize(wordsToPunish.join(" ")),
|
|
4304
4575
|
penalty: 1.2,
|
|
4305
4576
|
frequencyPenalty: 0.7,
|
|
4306
4577
|
presencePenalty: 0.7
|
|
@@ -4322,19 +4593,12 @@ var LocalAIManager = class _LocalAIManager {
|
|
|
4322
4593
|
throw error;
|
|
4323
4594
|
}
|
|
4324
4595
|
}
|
|
4596
|
+
/**
|
|
4597
|
+
* Generate embeddings - now with lazy initialization
|
|
4598
|
+
*/
|
|
4325
4599
|
async generateEmbedding(text) {
|
|
4326
4600
|
try {
|
|
4327
|
-
|
|
4328
|
-
if (!text) {
|
|
4329
|
-
throw new Error("Input text cannot be null or undefined");
|
|
4330
|
-
}
|
|
4331
|
-
logger10.debug("Input text length:", text.length);
|
|
4332
|
-
if (!this.embeddingModel) {
|
|
4333
|
-
logger10.error(
|
|
4334
|
-
"Embedding model not initialized, attempting to initialize..."
|
|
4335
|
-
);
|
|
4336
|
-
await this.initializeEmbedding();
|
|
4337
|
-
}
|
|
4601
|
+
await this.lazyInitEmbedding();
|
|
4338
4602
|
if (!this.embeddingModel) {
|
|
4339
4603
|
throw new Error("Failed to initialize embedding model");
|
|
4340
4604
|
}
|
|
@@ -4353,8 +4617,12 @@ var LocalAIManager = class _LocalAIManager {
|
|
|
4353
4617
|
throw error;
|
|
4354
4618
|
}
|
|
4355
4619
|
}
|
|
4620
|
+
/**
|
|
4621
|
+
* Describe image with lazy vision model initialization
|
|
4622
|
+
*/
|
|
4356
4623
|
async describeImage(imageData, mimeType) {
|
|
4357
4624
|
try {
|
|
4625
|
+
await this.lazyInitVision();
|
|
4358
4626
|
const base64 = imageData.toString("base64");
|
|
4359
4627
|
const dataUrl = `data:${mimeType};base64,${base64}`;
|
|
4360
4628
|
return await this.visionManager.processImage(dataUrl);
|
|
@@ -4363,8 +4631,12 @@ var LocalAIManager = class _LocalAIManager {
|
|
|
4363
4631
|
throw error;
|
|
4364
4632
|
}
|
|
4365
4633
|
}
|
|
4634
|
+
/**
|
|
4635
|
+
* Transcribe audio with lazy transcription model initialization
|
|
4636
|
+
*/
|
|
4366
4637
|
async transcribeAudio(audioBuffer) {
|
|
4367
4638
|
try {
|
|
4639
|
+
await this.lazyInitTranscription();
|
|
4368
4640
|
const result = await this.transcribeManager.transcribe(audioBuffer);
|
|
4369
4641
|
return result.text;
|
|
4370
4642
|
} catch (error) {
|
|
@@ -4375,8 +4647,12 @@ var LocalAIManager = class _LocalAIManager {
|
|
|
4375
4647
|
throw error;
|
|
4376
4648
|
}
|
|
4377
4649
|
}
|
|
4650
|
+
/**
|
|
4651
|
+
* Generate speech with lazy TTS model initialization
|
|
4652
|
+
*/
|
|
4378
4653
|
async generateSpeech(text) {
|
|
4379
4654
|
try {
|
|
4655
|
+
await this.lazyInitTTS();
|
|
4380
4656
|
return await this.ttsManager.generateSpeech(text);
|
|
4381
4657
|
} catch (error) {
|
|
4382
4658
|
logger10.error("Speech generation failed:", {
|
|
@@ -4387,12 +4663,25 @@ var LocalAIManager = class _LocalAIManager {
|
|
|
4387
4663
|
}
|
|
4388
4664
|
}
|
|
4389
4665
|
// Add public accessor methods
|
|
4666
|
+
/**
|
|
4667
|
+
* Returns the TokenizerManager associated with this object.
|
|
4668
|
+
*
|
|
4669
|
+
* @returns {TokenizerManager} The TokenizerManager object.
|
|
4670
|
+
*/
|
|
4390
4671
|
getTokenizerManager() {
|
|
4391
4672
|
return this.tokenizerManager;
|
|
4392
4673
|
}
|
|
4674
|
+
/**
|
|
4675
|
+
* Returns the active model configuration.
|
|
4676
|
+
* @returns {ModelSpec} The active model configuration.
|
|
4677
|
+
*/
|
|
4393
4678
|
getActiveModelConfig() {
|
|
4394
4679
|
return this.activeModelConfig;
|
|
4395
4680
|
}
|
|
4681
|
+
/**
|
|
4682
|
+
* Retrieves the source configuration for the text model based on environment variables and manager existence.
|
|
4683
|
+
* @returns {TextModelConfig} The configuration object containing the text model source and type.
|
|
4684
|
+
*/
|
|
4396
4685
|
getTextModelSource() {
|
|
4397
4686
|
try {
|
|
4398
4687
|
const config = {
|
|
@@ -4411,19 +4700,211 @@ var LocalAIManager = class _LocalAIManager {
|
|
|
4411
4700
|
return { source: "local", modelType: ModelTypes3.TEXT_SMALL };
|
|
4412
4701
|
}
|
|
4413
4702
|
}
|
|
4703
|
+
/**
|
|
4704
|
+
* Generic lazy initialization handler for any model type
|
|
4705
|
+
*/
|
|
4706
|
+
async lazyInitialize(modelType, isInitialized, initPromise, initFunction) {
|
|
4707
|
+
if (isInitialized) {
|
|
4708
|
+
return Promise.resolve(null);
|
|
4709
|
+
}
|
|
4710
|
+
if (initPromise) {
|
|
4711
|
+
logger10.info(`Waiting for ${modelType} initialization to complete...`);
|
|
4712
|
+
await initPromise;
|
|
4713
|
+
return Promise.resolve(null);
|
|
4714
|
+
}
|
|
4715
|
+
logger10.info(`Lazy initializing ${modelType}...`);
|
|
4716
|
+
return initFunction();
|
|
4717
|
+
}
|
|
4718
|
+
/**
|
|
4719
|
+
* Lazy initialize the small text model
|
|
4720
|
+
*/
|
|
4721
|
+
async lazyInitSmallModel() {
|
|
4722
|
+
if (this.smallModelInitialized) return;
|
|
4723
|
+
if (!this.smallModelInitializingPromise) {
|
|
4724
|
+
this.smallModelInitializingPromise = (async () => {
|
|
4725
|
+
await this.initializeEnvironment();
|
|
4726
|
+
await this.checkPlatformCapabilities();
|
|
4727
|
+
await this.downloadModel();
|
|
4728
|
+
try {
|
|
4729
|
+
this.llama = await getLlama2();
|
|
4730
|
+
const smallModel = await this.llama.loadModel(this.modelPath, {
|
|
4731
|
+
gpuLayers: 43,
|
|
4732
|
+
embeddings: true,
|
|
4733
|
+
vocabOnly: false
|
|
4734
|
+
});
|
|
4735
|
+
this.smallModel = smallModel;
|
|
4736
|
+
const ctx = await smallModel.createContext({
|
|
4737
|
+
contextSize: MODEL_SPECS.small.contextSize
|
|
4738
|
+
});
|
|
4739
|
+
this.ctx = ctx;
|
|
4740
|
+
this.sequence = void 0;
|
|
4741
|
+
this.smallModelInitialized = true;
|
|
4742
|
+
logger10.info("Small model initialized successfully");
|
|
4743
|
+
} catch (error) {
|
|
4744
|
+
logger10.error("Failed to initialize small model:", error);
|
|
4745
|
+
this.smallModelInitializingPromise = null;
|
|
4746
|
+
throw error;
|
|
4747
|
+
}
|
|
4748
|
+
})();
|
|
4749
|
+
}
|
|
4750
|
+
await this.smallModelInitializingPromise;
|
|
4751
|
+
}
|
|
4752
|
+
/**
|
|
4753
|
+
* Lazy initialize the medium text model
|
|
4754
|
+
*/
|
|
4755
|
+
async lazyInitMediumModel() {
|
|
4756
|
+
if (this.mediumModelInitialized) return;
|
|
4757
|
+
if (!this.mediumModelInitializingPromise) {
|
|
4758
|
+
this.mediumModelInitializingPromise = (async () => {
|
|
4759
|
+
if (!this.llama) {
|
|
4760
|
+
await this.lazyInitSmallModel();
|
|
4761
|
+
}
|
|
4762
|
+
try {
|
|
4763
|
+
const mediumModel = await this.llama.loadModel(
|
|
4764
|
+
this.mediumModelPath,
|
|
4765
|
+
{
|
|
4766
|
+
gpuLayers: 43,
|
|
4767
|
+
embeddings: true,
|
|
4768
|
+
vocabOnly: false
|
|
4769
|
+
}
|
|
4770
|
+
);
|
|
4771
|
+
this.mediumModel = mediumModel;
|
|
4772
|
+
this.mediumModelInitialized = true;
|
|
4773
|
+
logger10.info("Medium model initialized successfully");
|
|
4774
|
+
} catch (error) {
|
|
4775
|
+
logger10.error("Failed to initialize medium model:", error);
|
|
4776
|
+
this.mediumModelInitializingPromise = null;
|
|
4777
|
+
throw error;
|
|
4778
|
+
}
|
|
4779
|
+
})();
|
|
4780
|
+
}
|
|
4781
|
+
await this.mediumModelInitializingPromise;
|
|
4782
|
+
}
|
|
4783
|
+
/**
|
|
4784
|
+
* Lazy initialize the embedding model
|
|
4785
|
+
*/
|
|
4786
|
+
async lazyInitEmbedding() {
|
|
4787
|
+
if (this.embeddingInitialized) return;
|
|
4788
|
+
if (!this.embeddingInitializingPromise) {
|
|
4789
|
+
this.embeddingInitializingPromise = (async () => {
|
|
4790
|
+
try {
|
|
4791
|
+
await this.initializeEmbedding();
|
|
4792
|
+
this.embeddingInitialized = true;
|
|
4793
|
+
logger10.info("Embedding model initialized successfully");
|
|
4794
|
+
} catch (error) {
|
|
4795
|
+
logger10.error("Failed to initialize embedding model:", error);
|
|
4796
|
+
this.embeddingInitializingPromise = null;
|
|
4797
|
+
throw error;
|
|
4798
|
+
}
|
|
4799
|
+
})();
|
|
4800
|
+
}
|
|
4801
|
+
await this.embeddingInitializingPromise;
|
|
4802
|
+
}
|
|
4803
|
+
/**
|
|
4804
|
+
* Lazy initialize the vision model
|
|
4805
|
+
*/
|
|
4806
|
+
async lazyInitVision() {
|
|
4807
|
+
if (this.visionInitialized) return;
|
|
4808
|
+
if (!this.visionInitializingPromise) {
|
|
4809
|
+
this.visionInitializingPromise = (async () => {
|
|
4810
|
+
try {
|
|
4811
|
+
this.visionInitialized = true;
|
|
4812
|
+
logger10.info("Vision model initialized successfully");
|
|
4813
|
+
} catch (error) {
|
|
4814
|
+
logger10.error("Failed to initialize vision model:", error);
|
|
4815
|
+
this.visionInitializingPromise = null;
|
|
4816
|
+
throw error;
|
|
4817
|
+
}
|
|
4818
|
+
})();
|
|
4819
|
+
}
|
|
4820
|
+
await this.visionInitializingPromise;
|
|
4821
|
+
}
|
|
4822
|
+
/**
|
|
4823
|
+
* Lazy initialize the transcription model
|
|
4824
|
+
*/
|
|
4825
|
+
async lazyInitTranscription() {
|
|
4826
|
+
if (this.transcriptionInitialized) return;
|
|
4827
|
+
if (!this.transcriptionInitializingPromise) {
|
|
4828
|
+
this.transcriptionInitializingPromise = (async () => {
|
|
4829
|
+
try {
|
|
4830
|
+
this.transcriptionInitialized = true;
|
|
4831
|
+
logger10.info("Transcription model initialized successfully");
|
|
4832
|
+
} catch (error) {
|
|
4833
|
+
logger10.error("Failed to initialize transcription model:", error);
|
|
4834
|
+
this.transcriptionInitializingPromise = null;
|
|
4835
|
+
throw error;
|
|
4836
|
+
}
|
|
4837
|
+
})();
|
|
4838
|
+
}
|
|
4839
|
+
await this.transcriptionInitializingPromise;
|
|
4840
|
+
}
|
|
4841
|
+
/**
|
|
4842
|
+
* Lazy initialize the TTS model
|
|
4843
|
+
*/
|
|
4844
|
+
async lazyInitTTS() {
|
|
4845
|
+
if (this.ttsInitialized) return;
|
|
4846
|
+
if (!this.ttsInitializingPromise) {
|
|
4847
|
+
this.ttsInitializingPromise = (async () => {
|
|
4848
|
+
try {
|
|
4849
|
+
this.ttsInitialized = true;
|
|
4850
|
+
logger10.info("TTS model initialized successfully");
|
|
4851
|
+
} catch (error) {
|
|
4852
|
+
logger10.error("Failed to initialize TTS model:", error);
|
|
4853
|
+
this.ttsInitializingPromise = null;
|
|
4854
|
+
throw error;
|
|
4855
|
+
}
|
|
4856
|
+
})();
|
|
4857
|
+
}
|
|
4858
|
+
await this.ttsInitializingPromise;
|
|
4859
|
+
}
|
|
4860
|
+
/**
|
|
4861
|
+
* Lazy initialize the Ollama integration
|
|
4862
|
+
*/
|
|
4863
|
+
async lazyInitOllama() {
|
|
4864
|
+
if (this.ollamaInitialized) return;
|
|
4865
|
+
if (!this.ollamaInitializingPromise) {
|
|
4866
|
+
this.ollamaInitializingPromise = (async () => {
|
|
4867
|
+
try {
|
|
4868
|
+
await this.initializeOllama();
|
|
4869
|
+
this.ollamaInitialized = true;
|
|
4870
|
+
logger10.info("Ollama initialized successfully");
|
|
4871
|
+
} catch (error) {
|
|
4872
|
+
logger10.error("Failed to initialize Ollama:", error);
|
|
4873
|
+
this.ollamaInitializingPromise = null;
|
|
4874
|
+
throw error;
|
|
4875
|
+
}
|
|
4876
|
+
})();
|
|
4877
|
+
}
|
|
4878
|
+
await this.ollamaInitializingPromise;
|
|
4879
|
+
}
|
|
4880
|
+
/**
|
|
4881
|
+
* Lazy initialize the StudioLM integration
|
|
4882
|
+
*/
|
|
4883
|
+
async lazyInitStudioLM() {
|
|
4884
|
+
if (this.studioLMInitialized) return;
|
|
4885
|
+
if (!this.studioLMInitializingPromise) {
|
|
4886
|
+
this.studioLMInitializingPromise = (async () => {
|
|
4887
|
+
try {
|
|
4888
|
+
await this.initializeStudioLM();
|
|
4889
|
+
this.studioLMInitialized = true;
|
|
4890
|
+
logger10.info("StudioLM initialized successfully");
|
|
4891
|
+
} catch (error) {
|
|
4892
|
+
logger10.error("Failed to initialize StudioLM:", error);
|
|
4893
|
+
this.studioLMInitializingPromise = null;
|
|
4894
|
+
throw error;
|
|
4895
|
+
}
|
|
4896
|
+
})();
|
|
4897
|
+
}
|
|
4898
|
+
await this.studioLMInitializingPromise;
|
|
4899
|
+
}
|
|
4414
4900
|
};
|
|
4415
4901
|
var localAIManager = LocalAIManager.getInstance();
|
|
4416
4902
|
var localAIPlugin = {
|
|
4417
4903
|
name: "local-ai",
|
|
4418
4904
|
description: "Local AI plugin using LLaMA models",
|
|
4419
|
-
async init(
|
|
4905
|
+
async init() {
|
|
4420
4906
|
try {
|
|
4421
4907
|
logger10.info("Initializing local-ai plugin...");
|
|
4422
|
-
const validatedConfig = await validateConfig(config);
|
|
4423
|
-
for (const [key, value] of Object.entries(validatedConfig)) {
|
|
4424
|
-
process.env[key] = String(value);
|
|
4425
|
-
logger10.debug(`Set ${key}=${value}`);
|
|
4426
|
-
}
|
|
4427
4908
|
logger10.success("Local AI plugin configuration validated and initialized");
|
|
4428
4909
|
} catch (error) {
|
|
4429
4910
|
logger10.error("Plugin initialization failed:", {
|