@ztimson/ai-utils 0.7.9 → 0.7.10
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 +20 -20
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +212 -210
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -6,8 +6,8 @@ import { OpenAI as U } from "openai";
|
|
|
6
6
|
import { fileURLToPath as z } from "url";
|
|
7
7
|
import { join as N, dirname as L } from "path";
|
|
8
8
|
import { spawn as g, execSync as C } from "node:child_process";
|
|
9
|
-
import { mkdtempSync as J
|
|
10
|
-
import k from "node:fs/promises";
|
|
9
|
+
import { mkdtempSync as J } from "node:fs";
|
|
10
|
+
import k, { rm as W } from "node:fs/promises";
|
|
11
11
|
import _, { join as x } from "node:path";
|
|
12
12
|
import { createWorker as D } from "tesseract.js";
|
|
13
13
|
import * as I from "cheerio";
|
|
@@ -21,19 +21,19 @@ class G extends E {
|
|
|
21
21
|
client;
|
|
22
22
|
toStandard(r) {
|
|
23
23
|
const e = Date.now(), t = [];
|
|
24
|
-
for (let
|
|
25
|
-
if (typeof
|
|
26
|
-
t.push({ timestamp: e, ...
|
|
24
|
+
for (let m of r)
|
|
25
|
+
if (typeof m.content == "string")
|
|
26
|
+
t.push({ timestamp: e, ...m });
|
|
27
27
|
else {
|
|
28
|
-
const n =
|
|
28
|
+
const n = m.content?.filter((s) => s.type == "text").map((s) => s.text).join(`
|
|
29
29
|
|
|
30
30
|
`);
|
|
31
|
-
n && t.push({ timestamp: e, role:
|
|
31
|
+
n && t.push({ timestamp: e, role: m.role, content: n }), m.content.forEach((s) => {
|
|
32
32
|
if (s.type == "tool_use")
|
|
33
33
|
t.push({ timestamp: e, role: "tool", id: s.id, name: s.name, args: s.input, content: void 0 });
|
|
34
34
|
else if (s.type == "tool_result") {
|
|
35
|
-
const
|
|
36
|
-
|
|
35
|
+
const i = t.findLast((l) => l.id == s.tool_use_id);
|
|
36
|
+
i && (i[s.is_error ? "error" : "content"] = s.content);
|
|
37
37
|
}
|
|
38
38
|
});
|
|
39
39
|
}
|
|
@@ -54,78 +54,78 @@ class G extends E {
|
|
|
54
54
|
}
|
|
55
55
|
ask(r, e = {}) {
|
|
56
56
|
const t = new AbortController();
|
|
57
|
-
return Object.assign(new Promise(async (
|
|
57
|
+
return Object.assign(new Promise(async (m) => {
|
|
58
58
|
let n = this.fromStandard([...e.history || [], { role: "user", content: r, timestamp: Date.now() }]);
|
|
59
|
-
const s = e.tools || this.ai.options.llm?.tools || [],
|
|
59
|
+
const s = e.tools || this.ai.options.llm?.tools || [], i = {
|
|
60
60
|
model: e.model || this.model,
|
|
61
61
|
max_tokens: e.max_tokens || this.ai.options.llm?.max_tokens || 4096,
|
|
62
62
|
system: e.system || this.ai.options.llm?.system || "",
|
|
63
63
|
temperature: e.temperature || this.ai.options.llm?.temperature || 0.7,
|
|
64
|
-
tools: s.map((
|
|
65
|
-
name:
|
|
66
|
-
description:
|
|
64
|
+
tools: s.map((a) => ({
|
|
65
|
+
name: a.name,
|
|
66
|
+
description: a.description,
|
|
67
67
|
input_schema: {
|
|
68
68
|
type: "object",
|
|
69
|
-
properties:
|
|
70
|
-
required:
|
|
69
|
+
properties: a.args ? j(a.args, (o, c) => ({ ...c, required: void 0 })) : {},
|
|
70
|
+
required: a.args ? Object.entries(a.args).filter((o) => o[1].required).map((o) => o[0]) : []
|
|
71
71
|
},
|
|
72
72
|
fn: void 0
|
|
73
73
|
})),
|
|
74
74
|
messages: n,
|
|
75
75
|
stream: !!e.stream
|
|
76
76
|
};
|
|
77
|
-
let
|
|
77
|
+
let l, d = !0;
|
|
78
78
|
do {
|
|
79
|
-
if (
|
|
80
|
-
throw
|
|
79
|
+
if (l = await this.client.messages.create(i).catch((o) => {
|
|
80
|
+
throw o.message += `
|
|
81
81
|
|
|
82
82
|
Messages:
|
|
83
|
-
${JSON.stringify(n, null, 2)}`,
|
|
83
|
+
${JSON.stringify(n, null, 2)}`, o;
|
|
84
84
|
}), e.stream) {
|
|
85
|
-
|
|
85
|
+
d ? d = !1 : e.stream({ text: `
|
|
86
86
|
|
|
87
|
-
` }),
|
|
88
|
-
for await (const
|
|
87
|
+
` }), l.content = [];
|
|
88
|
+
for await (const o of l) {
|
|
89
89
|
if (t.signal.aborted) break;
|
|
90
|
-
if (
|
|
91
|
-
|
|
92
|
-
else if (
|
|
93
|
-
if (
|
|
94
|
-
const
|
|
95
|
-
|
|
96
|
-
} else
|
|
97
|
-
else if (
|
|
98
|
-
const
|
|
99
|
-
|
|
100
|
-
} else if (
|
|
90
|
+
if (o.type === "content_block_start")
|
|
91
|
+
o.content_block.type === "text" ? l.content.push({ type: "text", text: "" }) : o.content_block.type === "tool_use" && l.content.push({ type: "tool_use", id: o.content_block.id, name: o.content_block.name, input: "" });
|
|
92
|
+
else if (o.type === "content_block_delta")
|
|
93
|
+
if (o.delta.type === "text_delta") {
|
|
94
|
+
const c = o.delta.text;
|
|
95
|
+
l.content.at(-1).text += c, e.stream({ text: c });
|
|
96
|
+
} else o.delta.type === "input_json_delta" && (l.content.at(-1).input += o.delta.partial_json);
|
|
97
|
+
else if (o.type === "content_block_stop") {
|
|
98
|
+
const c = l.content.at(-1);
|
|
99
|
+
c.input != null && (c.input = c.input ? w(c.input, {}) : {});
|
|
100
|
+
} else if (o.type === "message_stop")
|
|
101
101
|
break;
|
|
102
102
|
}
|
|
103
103
|
}
|
|
104
|
-
const
|
|
105
|
-
if (
|
|
106
|
-
n.push({ role: "assistant", content:
|
|
107
|
-
const
|
|
108
|
-
const p = s.find(T("name",
|
|
109
|
-
if (e.stream && e.stream({ tool:
|
|
104
|
+
const a = l.content.filter((o) => o.type === "tool_use");
|
|
105
|
+
if (a.length && !t.signal.aborted) {
|
|
106
|
+
n.push({ role: "assistant", content: l.content });
|
|
107
|
+
const o = await Promise.all(a.map(async (c) => {
|
|
108
|
+
const p = s.find(T("name", c.name));
|
|
109
|
+
if (e.stream && e.stream({ tool: c.name }), !p) return { tool_use_id: c.id, is_error: !0, content: "Tool not found" };
|
|
110
110
|
try {
|
|
111
|
-
const u = await p.fn(
|
|
112
|
-
return { type: "tool_result", tool_use_id:
|
|
111
|
+
const u = await p.fn(c.input, e?.stream, this.ai);
|
|
112
|
+
return { type: "tool_result", tool_use_id: c.id, content: b(u) };
|
|
113
113
|
} catch (u) {
|
|
114
|
-
return { type: "tool_result", tool_use_id:
|
|
114
|
+
return { type: "tool_result", tool_use_id: c.id, is_error: !0, content: u?.message || u?.toString() || "Unknown" };
|
|
115
115
|
}
|
|
116
116
|
}));
|
|
117
|
-
n.push({ role: "user", content:
|
|
117
|
+
n.push({ role: "user", content: o }), i.messages = n;
|
|
118
118
|
}
|
|
119
|
-
} while (!t.signal.aborted &&
|
|
120
|
-
n.push({ role: "assistant", content:
|
|
119
|
+
} while (!t.signal.aborted && l.content.some((a) => a.type === "tool_use"));
|
|
120
|
+
n.push({ role: "assistant", content: l.content.filter((a) => a.type == "text").map((a) => a.text).join(`
|
|
121
121
|
|
|
122
|
-
`) }), n = this.toStandard(n), e.stream && e.stream({ done: !0 }), e.history && e.history.splice(0, e.history.length, ...n),
|
|
122
|
+
`) }), n = this.toStandard(n), e.stream && e.stream({ done: !0 }), e.history && e.history.splice(0, e.history.length, ...n), m(n.at(-1)?.content);
|
|
123
123
|
}), { abort: () => t.abort() });
|
|
124
124
|
}
|
|
125
125
|
}
|
|
126
126
|
class S extends E {
|
|
127
|
-
constructor(r, e, t,
|
|
128
|
-
super(), this.ai = r, this.host = e, this.token = t, this.model =
|
|
127
|
+
constructor(r, e, t, m) {
|
|
128
|
+
super(), this.ai = r, this.host = e, this.token = t, this.model = m, this.client = new U(M({
|
|
129
129
|
baseURL: e,
|
|
130
130
|
apiKey: t
|
|
131
131
|
}));
|
|
@@ -135,17 +135,17 @@ class S extends E {
|
|
|
135
135
|
for (let e = 0; e < r.length; e++) {
|
|
136
136
|
const t = r[e];
|
|
137
137
|
if (t.role === "assistant" && t.tool_calls) {
|
|
138
|
-
const
|
|
138
|
+
const m = t.tool_calls.map((n) => ({
|
|
139
139
|
role: "tool",
|
|
140
140
|
id: n.id,
|
|
141
141
|
name: n.function.name,
|
|
142
142
|
args: w(n.function.arguments, {}),
|
|
143
143
|
timestamp: t.timestamp
|
|
144
144
|
}));
|
|
145
|
-
r.splice(e, 1, ...
|
|
145
|
+
r.splice(e, 1, ...m), e += m.length - 1;
|
|
146
146
|
} else if (t.role === "tool" && t.content) {
|
|
147
|
-
const
|
|
148
|
-
|
|
147
|
+
const m = r.find((n) => t.tool_call_id == n.id);
|
|
148
|
+
m && (t.content.includes('"error":') ? m.error = t.content : m.content = t.content), r.splice(e, 1), e--;
|
|
149
149
|
}
|
|
150
150
|
r[e]?.timestamp || (r[e].timestamp = Date.now());
|
|
151
151
|
}
|
|
@@ -166,7 +166,7 @@ class S extends E {
|
|
|
166
166
|
content: t.error || t.content
|
|
167
167
|
});
|
|
168
168
|
else {
|
|
169
|
-
const { timestamp:
|
|
169
|
+
const { timestamp: m, ...n } = t;
|
|
170
170
|
e.push(n);
|
|
171
171
|
}
|
|
172
172
|
return e;
|
|
@@ -174,49 +174,49 @@ class S extends E {
|
|
|
174
174
|
}
|
|
175
175
|
ask(r, e = {}) {
|
|
176
176
|
const t = new AbortController();
|
|
177
|
-
return Object.assign(new Promise(async (
|
|
177
|
+
return Object.assign(new Promise(async (m, n) => {
|
|
178
178
|
e.system && e.history?.[0]?.role != "system" && e.history?.splice(0, 0, { role: "system", content: e.system, timestamp: Date.now() });
|
|
179
179
|
let s = this.fromStandard([...e.history || [], { role: "user", content: r, timestamp: Date.now() }]);
|
|
180
|
-
const
|
|
180
|
+
const i = e.tools || this.ai.options.llm?.tools || [], l = {
|
|
181
181
|
model: e.model || this.model,
|
|
182
182
|
messages: s,
|
|
183
183
|
stream: !!e.stream,
|
|
184
184
|
max_tokens: e.max_tokens || this.ai.options.llm?.max_tokens || 4096,
|
|
185
185
|
temperature: e.temperature || this.ai.options.llm?.temperature || 0.7,
|
|
186
|
-
tools:
|
|
186
|
+
tools: i.map((o) => ({
|
|
187
187
|
type: "function",
|
|
188
188
|
function: {
|
|
189
|
-
name:
|
|
190
|
-
description:
|
|
189
|
+
name: o.name,
|
|
190
|
+
description: o.description,
|
|
191
191
|
parameters: {
|
|
192
192
|
type: "object",
|
|
193
|
-
properties:
|
|
194
|
-
required:
|
|
193
|
+
properties: o.args ? j(o.args, (c, p) => ({ ...p, required: void 0 })) : {},
|
|
194
|
+
required: o.args ? Object.entries(o.args).filter((c) => c[1].required).map((c) => c[0]) : []
|
|
195
195
|
}
|
|
196
196
|
}
|
|
197
197
|
}))
|
|
198
198
|
};
|
|
199
|
-
let
|
|
199
|
+
let d, a = !0;
|
|
200
200
|
do {
|
|
201
|
-
if (
|
|
202
|
-
throw
|
|
201
|
+
if (d = await this.client.chat.completions.create(l).catch((c) => {
|
|
202
|
+
throw c.message += `
|
|
203
203
|
|
|
204
204
|
Messages:
|
|
205
|
-
${JSON.stringify(s, null, 2)}`,
|
|
205
|
+
${JSON.stringify(s, null, 2)}`, c;
|
|
206
206
|
}), e.stream) {
|
|
207
|
-
|
|
207
|
+
a ? a = !1 : e.stream({ text: `
|
|
208
208
|
|
|
209
|
-
` }),
|
|
210
|
-
for await (const
|
|
209
|
+
` }), d.choices = [{ message: { content: "", tool_calls: [] } }];
|
|
210
|
+
for await (const c of d) {
|
|
211
211
|
if (t.signal.aborted) break;
|
|
212
|
-
|
|
212
|
+
c.choices[0].delta.content && (d.choices[0].message.content += c.choices[0].delta.content, e.stream({ text: c.choices[0].delta.content })), c.choices[0].delta.tool_calls && (d.choices[0].message.tool_calls = c.choices[0].delta.tool_calls);
|
|
213
213
|
}
|
|
214
214
|
}
|
|
215
|
-
const
|
|
216
|
-
if (
|
|
217
|
-
s.push(
|
|
218
|
-
const
|
|
219
|
-
const u =
|
|
215
|
+
const o = d.choices[0].message.tool_calls || [];
|
|
216
|
+
if (o.length && !t.signal.aborted) {
|
|
217
|
+
s.push(d.choices[0].message);
|
|
218
|
+
const c = await Promise.all(o.map(async (p) => {
|
|
219
|
+
const u = i?.find(T("name", p.function.name));
|
|
220
220
|
if (e.stream && e.stream({ tool: p.function.name }), !u) return { role: "tool", tool_call_id: p.id, content: '{"error": "Tool not found"}' };
|
|
221
221
|
try {
|
|
222
222
|
const f = w(p.function.arguments, {}), y = await u.fn(f, e.stream, this.ai);
|
|
@@ -225,10 +225,10 @@ ${JSON.stringify(s, null, 2)}`, d;
|
|
|
225
225
|
return { role: "tool", tool_call_id: p.id, content: b({ error: f?.message || f?.toString() || "Unknown" }) };
|
|
226
226
|
}
|
|
227
227
|
}));
|
|
228
|
-
s.push(...
|
|
228
|
+
s.push(...c), l.messages = s;
|
|
229
229
|
}
|
|
230
|
-
} while (!t.signal.aborted &&
|
|
231
|
-
s.push({ role: "assistant", content:
|
|
230
|
+
} while (!t.signal.aborted && d.choices?.[0]?.message?.tool_calls?.length);
|
|
231
|
+
s.push({ role: "assistant", content: d.choices[0].message.content || "" }), s = this.toStandard(s), e.stream && e.stream({ done: !0 }), e.history && e.history.splice(0, e.history.length, ...s), m(s.at(-1)?.content);
|
|
232
232
|
}), { abort: () => t.abort() });
|
|
233
233
|
}
|
|
234
234
|
}
|
|
@@ -249,22 +249,22 @@ class B {
|
|
|
249
249
|
ask(r, e = {}) {
|
|
250
250
|
const t = e.model || this.defaultModel;
|
|
251
251
|
if (!this.models[t]) throw new Error(`Model does not exist: ${t}`);
|
|
252
|
-
let
|
|
252
|
+
let m = () => {
|
|
253
253
|
};
|
|
254
254
|
return Object.assign(new Promise(async (n) => {
|
|
255
255
|
if (e.history || (e.history = []), e.memory) {
|
|
256
256
|
e.system = (e.system || "") + `
|
|
257
257
|
You have passive persistent memory never make any mention of your memory capabilities and what you can/cannot remember
|
|
258
258
|
`;
|
|
259
|
-
const
|
|
260
|
-
const [
|
|
261
|
-
|
|
262
|
-
|
|
259
|
+
const i = async (d, a, o = 50) => {
|
|
260
|
+
const [c, p] = await Promise.all([
|
|
261
|
+
a ? this.embedding(a) : Promise.resolve(null),
|
|
262
|
+
d ? this.embedding(d) : Promise.resolve(null)
|
|
263
263
|
]);
|
|
264
|
-
return (e.memory || []).map((u) => ({ ...u, score:
|
|
265
|
-
},
|
|
266
|
-
|
|
267
|
-
` +
|
|
264
|
+
return (e.memory || []).map((u) => ({ ...u, score: c ? this.cosineSimilarity(u.embeddings[0], c[0].embedding) : 1 })).filter((u) => u.score >= 0.8).map((u) => ({ ...u, score: p ? this.cosineSimilarity(u.embeddings[1], p[0].embedding) : u.score })).filter((u) => u.score >= 0.2).toSorted((u, f) => u.score - f.score).slice(0, o);
|
|
265
|
+
}, l = await i(r);
|
|
266
|
+
l.length && e.history.push({ role: "assistant", content: `Things I remembered:
|
|
267
|
+
` + l.map((d) => `${d.owner}: ${d.fact}`).join(`
|
|
268
268
|
`) }), e.tools = [...e.tools || [], {
|
|
269
269
|
name: "read_memory",
|
|
270
270
|
description: "Check your long-term memory for more information",
|
|
@@ -273,32 +273,32 @@ You have passive persistent memory never make any mention of your memory capabil
|
|
|
273
273
|
query: { type: "string", description: "Search memory based on a query, can be used with or without subject argument" },
|
|
274
274
|
limit: { type: "number", description: "Result limit, default 5" }
|
|
275
275
|
},
|
|
276
|
-
fn: (
|
|
277
|
-
if (!
|
|
278
|
-
return
|
|
276
|
+
fn: (d) => {
|
|
277
|
+
if (!d.subject && !d.query) throw new Error("Either a subject or query argument is required");
|
|
278
|
+
return i(d.query, d.subject, d.limit || 5);
|
|
279
279
|
}
|
|
280
280
|
}];
|
|
281
281
|
}
|
|
282
282
|
const s = await this.models[t].ask(r, e);
|
|
283
283
|
if (e.memory) {
|
|
284
|
-
const
|
|
285
|
-
|
|
284
|
+
const i = e.history?.findIndex((l) => l.role == "assistant" && l.content.startsWith("Things I remembered:"));
|
|
285
|
+
i != null && i >= 0 && e.history?.splice(i, 1);
|
|
286
286
|
}
|
|
287
287
|
if (e.compress || e.memory) {
|
|
288
|
-
let
|
|
288
|
+
let i = null;
|
|
289
289
|
if (e.compress)
|
|
290
|
-
|
|
290
|
+
i = await this.ai.language.compressHistory(e.history, e.compress.max, e.compress.min, e), e.history.splice(0, e.history.length, ...i.history);
|
|
291
291
|
else {
|
|
292
|
-
const
|
|
293
|
-
|
|
292
|
+
const l = e.history?.findLastIndex((d) => d.role == "user") ?? -1;
|
|
293
|
+
i = await this.ai.language.compressHistory(l != -1 ? e.history.slice(l) : e.history, 0, 0, e);
|
|
294
294
|
}
|
|
295
295
|
if (e.memory) {
|
|
296
|
-
const
|
|
297
|
-
e.memory.splice(0, e.memory.length, ...
|
|
296
|
+
const l = e.memory.filter((d) => !i.memory.some((a) => this.cosineSimilarity(d.embeddings[1], a.embeddings[1]) > 0.8)).concat(i.memory);
|
|
297
|
+
e.memory.splice(0, e.memory.length, ...l);
|
|
298
298
|
}
|
|
299
299
|
}
|
|
300
300
|
return n(s);
|
|
301
|
-
}), { abort:
|
|
301
|
+
}), { abort: m });
|
|
302
302
|
}
|
|
303
303
|
/**
|
|
304
304
|
* Compress chat history to reduce context size
|
|
@@ -308,24 +308,24 @@ You have passive persistent memory never make any mention of your memory capabil
|
|
|
308
308
|
* @param {LLMRequest} options LLM options
|
|
309
309
|
* @returns {Promise<LLMMessage[]>} New chat history will summary at index 0
|
|
310
310
|
*/
|
|
311
|
-
async compressHistory(r, e, t,
|
|
311
|
+
async compressHistory(r, e, t, m) {
|
|
312
312
|
if (this.estimateTokens(r) < e) return { history: r, memory: [] };
|
|
313
313
|
let n = 0, s = 0;
|
|
314
314
|
for (let u of r.toReversed())
|
|
315
315
|
if (s += this.estimateTokens(u.content), s < t) n++;
|
|
316
316
|
else break;
|
|
317
317
|
if (r.length <= n) return { history: r, memory: [] };
|
|
318
|
-
const
|
|
318
|
+
const i = r[0].role == "system" ? r[0] : null, l = n == 0 ? [] : r.slice(-n), d = (n == 0 ? r : r.slice(0, -n)).filter((u) => u.role === "assistant" || u.role === "user"), a = await this.json(d.map((u) => `${u.role}: ${u.content}`).join(`
|
|
319
319
|
|
|
320
320
|
`), "{summary: string, facts: [[subject, fact]]}", {
|
|
321
321
|
system: "Create the smallest summary possible, no more than 500 tokens. Create a list of NEW facts (split by subject [pro]noun and fact) about what you learned from this conversation that you didn't already know or get from a tool call or system prompt. Focus only on new information about people, topics, or facts. Avoid generating facts about the AI.",
|
|
322
|
-
model:
|
|
323
|
-
temperature:
|
|
324
|
-
}),
|
|
322
|
+
model: m?.model,
|
|
323
|
+
temperature: m?.temperature || 0.3
|
|
324
|
+
}), o = /* @__PURE__ */ new Date(), c = await Promise.all((a?.facts || [])?.map(async ([u, f]) => {
|
|
325
325
|
const y = await Promise.all([this.embedding(u), this.embedding(`${u}: ${f}`)]);
|
|
326
|
-
return { owner: u, fact: f, embeddings: [y[0][0].embedding, y[1][0].embedding], timestamp:
|
|
327
|
-
})), p = [{ role: "assistant", content: `Conversation Summary: ${
|
|
328
|
-
return
|
|
326
|
+
return { owner: u, fact: f, embeddings: [y[0][0].embedding, y[1][0].embedding], timestamp: o };
|
|
327
|
+
})), p = [{ role: "assistant", content: `Conversation Summary: ${a?.summary}`, timestamp: Date.now() }, ...l];
|
|
328
|
+
return i && p.splice(0, 0, i), { history: p, memory: c };
|
|
329
329
|
}
|
|
330
330
|
/**
|
|
331
331
|
* Compare the difference between embeddings (calculates the angle between two vectors)
|
|
@@ -335,10 +335,10 @@ You have passive persistent memory never make any mention of your memory capabil
|
|
|
335
335
|
*/
|
|
336
336
|
cosineSimilarity(r, e) {
|
|
337
337
|
if (r.length !== e.length) throw new Error("Vectors must be same length");
|
|
338
|
-
let t = 0,
|
|
339
|
-
for (let
|
|
340
|
-
t += r[
|
|
341
|
-
const s = Math.sqrt(
|
|
338
|
+
let t = 0, m = 0, n = 0;
|
|
339
|
+
for (let i = 0; i < r.length; i++)
|
|
340
|
+
t += r[i] * e[i], m += r[i] * r[i], n += e[i] * e[i];
|
|
341
|
+
const s = Math.sqrt(m) * Math.sqrt(n);
|
|
342
342
|
return s === 0 ? 0 : t / s;
|
|
343
343
|
}
|
|
344
344
|
/**
|
|
@@ -349,25 +349,25 @@ You have passive persistent memory never make any mention of your memory capabil
|
|
|
349
349
|
* @returns {string[]} Chunked strings
|
|
350
350
|
*/
|
|
351
351
|
chunk(r, e = 500, t = 50) {
|
|
352
|
-
const
|
|
353
|
-
const
|
|
354
|
-
return typeof
|
|
355
|
-
}) : [], s = (typeof r == "object" ?
|
|
356
|
-
`)).flatMap((
|
|
357
|
-
`]),
|
|
358
|
-
for (let
|
|
359
|
-
let
|
|
360
|
-
for (;
|
|
361
|
-
const
|
|
362
|
-
if (this.estimateTokens(
|
|
363
|
-
`)) > e &&
|
|
364
|
-
|
|
352
|
+
const m = (l, d = "") => l ? Object.entries(l).flatMap(([a, o]) => {
|
|
353
|
+
const c = d ? `${d}${isNaN(+a) ? `.${a}` : `[${a}]`}` : a;
|
|
354
|
+
return typeof o == "object" && !Array.isArray(o) ? m(o, c) : `${c}: ${Array.isArray(o) ? o.join(", ") : o}`;
|
|
355
|
+
}) : [], s = (typeof r == "object" ? m(r) : r.split(`
|
|
356
|
+
`)).flatMap((l) => [...l.split(/\s+/).filter(Boolean), `
|
|
357
|
+
`]), i = [];
|
|
358
|
+
for (let l = 0; l < s.length; ) {
|
|
359
|
+
let d = "", a = l;
|
|
360
|
+
for (; a < s.length; ) {
|
|
361
|
+
const c = d + (d ? " " : "") + s[a];
|
|
362
|
+
if (this.estimateTokens(c.replace(/\s*\n\s*/g, `
|
|
363
|
+
`)) > e && d) break;
|
|
364
|
+
d = c, a++;
|
|
365
365
|
}
|
|
366
|
-
const
|
|
366
|
+
const o = d.replace(/\s*\n\s*/g, `
|
|
367
367
|
`).trim();
|
|
368
|
-
|
|
368
|
+
o && i.push(o), l = Math.max(a - t, a === l ? l + 1 : a);
|
|
369
369
|
}
|
|
370
|
-
return
|
|
370
|
+
return i;
|
|
371
371
|
}
|
|
372
372
|
/**
|
|
373
373
|
* Create a vector representation of a string
|
|
@@ -376,39 +376,39 @@ You have passive persistent memory never make any mention of your memory capabil
|
|
|
376
376
|
* @returns {Promise<Awaited<{index: number, embedding: number[], text: string, tokens: number}>[]>} Chunked embeddings
|
|
377
377
|
*/
|
|
378
378
|
embedding(r, e = {}) {
|
|
379
|
-
let { maxTokens: t = 500, overlapTokens:
|
|
379
|
+
let { maxTokens: t = 500, overlapTokens: m = 50 } = e, n = !1;
|
|
380
380
|
const s = () => {
|
|
381
381
|
n = !0;
|
|
382
|
-
},
|
|
383
|
-
if (n) return
|
|
384
|
-
const
|
|
382
|
+
}, i = (d) => new Promise((a, o) => {
|
|
383
|
+
if (n) return o(new Error("Aborted"));
|
|
384
|
+
const c = [
|
|
385
385
|
N(L(z(import.meta.url)), "embedder.js"),
|
|
386
386
|
this.ai.options.path,
|
|
387
387
|
this.ai.options?.embedder || "bge-small-en-v1.5"
|
|
388
|
-
], p = g("node",
|
|
389
|
-
p.stdin.write(
|
|
388
|
+
], p = g("node", c, { stdio: ["pipe", "pipe", "ignore"] });
|
|
389
|
+
p.stdin.write(d), p.stdin.end();
|
|
390
390
|
let u = "";
|
|
391
391
|
p.stdout.on("data", (f) => u += f.toString()), p.on("close", (f) => {
|
|
392
|
-
if (n) return
|
|
392
|
+
if (n) return o(new Error("Aborted"));
|
|
393
393
|
if (f === 0)
|
|
394
394
|
try {
|
|
395
395
|
const y = JSON.parse(u);
|
|
396
|
-
|
|
396
|
+
a(y.embedding);
|
|
397
397
|
} catch {
|
|
398
|
-
|
|
398
|
+
o(new Error("Failed to parse embedding output"));
|
|
399
399
|
}
|
|
400
400
|
else
|
|
401
|
-
|
|
402
|
-
}), p.on("error",
|
|
403
|
-
}),
|
|
404
|
-
const
|
|
405
|
-
for (let
|
|
406
|
-
const
|
|
407
|
-
|
|
401
|
+
o(new Error(`Embedder process exited with code ${f}`));
|
|
402
|
+
}), p.on("error", o);
|
|
403
|
+
}), l = (async () => {
|
|
404
|
+
const d = this.chunk(r, t, m), a = [];
|
|
405
|
+
for (let o = 0; o < d.length && !n; o++) {
|
|
406
|
+
const c = d[o], p = await i(c);
|
|
407
|
+
a.push({ index: o, embedding: p, text: c, tokens: this.estimateTokens(c) });
|
|
408
408
|
}
|
|
409
|
-
return
|
|
409
|
+
return a;
|
|
410
410
|
})();
|
|
411
|
-
return Object.assign(
|
|
411
|
+
return Object.assign(l, { abort: s });
|
|
412
412
|
}
|
|
413
413
|
/**
|
|
414
414
|
* Estimate variable as tokens
|
|
@@ -427,8 +427,8 @@ You have passive persistent memory never make any mention of your memory capabil
|
|
|
427
427
|
*/
|
|
428
428
|
fuzzyMatch(r, ...e) {
|
|
429
429
|
if (e.length < 2) throw new Error("Requires at least 2 strings to compare");
|
|
430
|
-
const t = (s,
|
|
431
|
-
return { avg: n.reduce((s,
|
|
430
|
+
const t = (s, i = 10) => s.toLowerCase().split("").map((l, d) => l.charCodeAt(0) * (d + 1) % i / i).slice(0, i), m = t(r), n = e.map((s) => t(s)).map((s) => this.cosineSimilarity(m, s));
|
|
431
|
+
return { avg: n.reduce((s, i) => s + i, 0) / n.length, max: Math.max(...n), similarities: n };
|
|
432
432
|
}
|
|
433
433
|
/**
|
|
434
434
|
* Ask a question with JSON response
|
|
@@ -438,13 +438,13 @@ You have passive persistent memory never make any mention of your memory capabil
|
|
|
438
438
|
* @returns {Promise<{} | {} | RegExpExecArray | null>}
|
|
439
439
|
*/
|
|
440
440
|
async json(r, e, t) {
|
|
441
|
-
let
|
|
441
|
+
let m = await this.ask(r, { ...t, system: (t?.system ? `${t.system}
|
|
442
442
|
` : "") + `Only respond using a JSON code block matching this schema:
|
|
443
443
|
\`\`\`json
|
|
444
444
|
${e}
|
|
445
445
|
\`\`\`` });
|
|
446
|
-
if (!
|
|
447
|
-
const n = /```(?:.+)?\s*([\s\S]*?)```/.exec(
|
|
446
|
+
if (!m) return {};
|
|
447
|
+
const n = /```(?:.+)?\s*([\s\S]*?)```/.exec(m), s = n ? n[1].trim() : m;
|
|
448
448
|
return w(s, {});
|
|
449
449
|
}
|
|
450
450
|
/**
|
|
@@ -482,96 +482,98 @@ print(json.dumps(segments))
|
|
|
482
482
|
whisperModel;
|
|
483
483
|
runAsr(r, e = {}) {
|
|
484
484
|
let t;
|
|
485
|
-
const
|
|
486
|
-
this.downloadAsrModel(e.model).then((
|
|
487
|
-
let
|
|
488
|
-
const
|
|
489
|
-
t = g(this.ai.options.whisper,
|
|
490
|
-
if (
|
|
485
|
+
const m = new Promise((n, s) => {
|
|
486
|
+
this.downloadAsrModel(e.model).then((i) => {
|
|
487
|
+
let l = "";
|
|
488
|
+
const d = [e.diarization ? "-owts" : "-nt", "-m", i, "-f", r];
|
|
489
|
+
console.log(this.ai.options.whisper + " " + d.join(" ")), t = g(this.ai.options.whisper, d, { stdio: ["ignore", "pipe", "ignore"] }), t.on("error", (a) => s(a)), t.stdout.on("data", (a) => l += a.toString()), t.on("close", (a) => {
|
|
490
|
+
if (a === 0)
|
|
491
491
|
if (e.diarization)
|
|
492
492
|
try {
|
|
493
|
-
n(JSON.parse(
|
|
493
|
+
n(JSON.parse(l));
|
|
494
494
|
} catch {
|
|
495
495
|
s(new Error("Failed to parse whisper JSON"));
|
|
496
496
|
}
|
|
497
497
|
else
|
|
498
|
-
n(
|
|
498
|
+
n(l.trim() || null);
|
|
499
499
|
else
|
|
500
|
-
s(new Error(`Exit code ${
|
|
500
|
+
s(new Error(`Exit code ${a}`));
|
|
501
501
|
});
|
|
502
502
|
});
|
|
503
503
|
});
|
|
504
|
-
return Object.assign(
|
|
504
|
+
return Object.assign(m, { abort: () => t?.kill("SIGTERM") });
|
|
505
505
|
}
|
|
506
506
|
runDiarization(r) {
|
|
507
507
|
let e = !1, t = () => {
|
|
508
508
|
e = !0;
|
|
509
509
|
};
|
|
510
|
-
const
|
|
511
|
-
const
|
|
512
|
-
|
|
510
|
+
const m = (s) => new Promise((i) => {
|
|
511
|
+
const l = g(s, ["-c", "import pyannote.audio"]);
|
|
512
|
+
l.on("close", (d) => i(d === 0)), l.on("error", () => i(!1));
|
|
513
513
|
}), n = Promise.all([
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
]).then((async ([s,
|
|
514
|
+
m("python"),
|
|
515
|
+
m("python3")
|
|
516
|
+
]).then((async ([s, i]) => {
|
|
517
517
|
if (e) return;
|
|
518
|
-
if (!s && !
|
|
519
|
-
const
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
if (u === 0)
|
|
518
|
+
if (!s && !i) throw new Error("Pyannote is not installed: pip install pyannote.audio");
|
|
519
|
+
const l = i ? "python3" : "python";
|
|
520
|
+
return new Promise((d, a) => {
|
|
521
|
+
if (e) return;
|
|
522
|
+
let o = "";
|
|
523
|
+
const c = g(l, ["-c", this.pyannote, r]);
|
|
524
|
+
c.stdout.on("data", (p) => o += p.toString()), c.stderr.on("data", (p) => console.error(p.toString())), c.on("close", (p) => {
|
|
525
|
+
if (p === 0)
|
|
527
526
|
try {
|
|
528
|
-
|
|
527
|
+
d(JSON.parse(o));
|
|
529
528
|
} catch {
|
|
530
|
-
|
|
529
|
+
a(new Error("Failed to parse diarization output"));
|
|
531
530
|
}
|
|
532
531
|
else
|
|
533
|
-
|
|
534
|
-
}),
|
|
535
|
-
}).finally(() => {
|
|
536
|
-
a && W(_.dirname(a), { recursive: !0, force: !0 });
|
|
532
|
+
a(new Error(`Python process exited with code ${p}`));
|
|
533
|
+
}), c.on("error", a), t = () => c.kill("SIGTERM");
|
|
537
534
|
});
|
|
538
535
|
}));
|
|
539
536
|
return Object.assign(n, { abort: t });
|
|
540
537
|
}
|
|
541
538
|
combineSpeakerTranscript(r, e) {
|
|
542
539
|
const t = /* @__PURE__ */ new Map();
|
|
543
|
-
let
|
|
544
|
-
e.forEach((
|
|
545
|
-
t.has(
|
|
540
|
+
let m = 0;
|
|
541
|
+
e.forEach((l) => {
|
|
542
|
+
t.has(l.speaker) || t.set(l.speaker, ++m);
|
|
546
543
|
});
|
|
547
544
|
const n = [];
|
|
548
|
-
let s = -1,
|
|
549
|
-
return r.transcription.forEach((
|
|
550
|
-
const
|
|
551
|
-
|
|
552
|
-
}),
|
|
545
|
+
let s = -1, i = "";
|
|
546
|
+
return r.transcription.forEach((l) => {
|
|
547
|
+
const d = l.offsets.from / 1e3, a = e.find((c) => d >= c.start && d <= c.end), o = a ? t.get(a.speaker) : 1;
|
|
548
|
+
o !== s ? (i && n.push(`[Speaker ${s}]: ${i.trim()}`), s = o, i = l.text) : i += " " + l.text;
|
|
549
|
+
}), i && n.push(`[Speaker ${s}]: ${i.trim()}`), n.join(`
|
|
553
550
|
`);
|
|
554
551
|
}
|
|
555
552
|
asr(r, e = {}) {
|
|
556
553
|
if (!this.ai.options.whisper) throw new Error("Whisper not configured");
|
|
557
|
-
const t =
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
554
|
+
const t = x(J(x(P(), "audio-")), "converted.wav");
|
|
555
|
+
C(`ffmpeg -i "${r}" -ar 16000 -ac 1 -f wav "${t}"`, { stdio: "ignore" });
|
|
556
|
+
const m = () => W(_.dirname(t), { recursive: !0, force: !0 }).catch(() => {
|
|
557
|
+
}), n = this.runAsr(t, { model: e.model, diarization: !!e.diarization }), s = e.diarization ? this.runDiarization(t) : Promise.resolve(null);
|
|
558
|
+
let i = !1, l = () => {
|
|
559
|
+
i = !0, n.abort(), s?.abort?.(), m();
|
|
560
|
+
};
|
|
561
|
+
const d = Promise.all([n, s]).then(async ([a, o]) => {
|
|
562
|
+
if (i || !e.diarization) return a;
|
|
563
|
+
if (a = this.combineSpeakerTranscript(a, o), !i && e.diarization === "id") {
|
|
562
564
|
if (!this.ai.language.defaultModel) throw new Error("Configure an LLM for advanced ASR speaker detection");
|
|
563
|
-
let
|
|
564
|
-
|
|
565
|
-
const
|
|
565
|
+
let c = this.ai.language.chunk(a, 500, 0);
|
|
566
|
+
c.length > 4 && (c = [...c.slice(0, 3), c.at(-1)]);
|
|
567
|
+
const p = await this.ai.language.json(c.join(`
|
|
566
568
|
`), '{1: "Detected Name", 2: "Second Name"}', {
|
|
567
569
|
system: "Use the following transcript to identify speakers. Only identify speakers you are positive about, dont mention speakers you are unsure about in your response",
|
|
568
570
|
temperature: 0.1
|
|
569
571
|
});
|
|
570
|
-
Object.entries(
|
|
572
|
+
Object.entries(p).forEach(([u, f]) => a = a.replaceAll(`[Speaker ${u}]`, `[${f}]`));
|
|
571
573
|
}
|
|
572
|
-
return
|
|
573
|
-
});
|
|
574
|
-
return Object.assign(
|
|
574
|
+
return a;
|
|
575
|
+
}).finally(() => m());
|
|
576
|
+
return Object.assign(d, { abort: l });
|
|
575
577
|
}
|
|
576
578
|
async downloadAsrModel(r = this.whisperModel) {
|
|
577
579
|
if (!this.ai.options.whisper) throw new Error("Whisper not configured");
|
|
@@ -591,10 +593,10 @@ class V {
|
|
|
591
593
|
*/
|
|
592
594
|
ocr(r) {
|
|
593
595
|
let e;
|
|
594
|
-
const t = new Promise(async (
|
|
596
|
+
const t = new Promise(async (m) => {
|
|
595
597
|
e = await D(this.ai.options.ocr || "eng", 2, { cachePath: this.ai.options.path });
|
|
596
598
|
const { data: n } = await e.recognize(r);
|
|
597
|
-
await e.terminate(),
|
|
599
|
+
await e.terminate(), m(n.text.trim() || null);
|
|
598
600
|
});
|
|
599
601
|
return Object.assign(t, { abort: () => e?.terminate() });
|
|
600
602
|
}
|
|
@@ -684,16 +686,16 @@ const Y = {
|
|
|
684
686
|
title: e('meta[property="og:title"]').attr("content") || e("title").text() || "",
|
|
685
687
|
description: e('meta[name="description"]').attr("content") || e('meta[property="og:description"]').attr("content") || ""
|
|
686
688
|
};
|
|
687
|
-
let
|
|
689
|
+
let m = "";
|
|
688
690
|
const n = ["article", "main", '[role="main"]', ".content", ".post", ".entry", "body"];
|
|
689
691
|
for (const s of n) {
|
|
690
|
-
const
|
|
691
|
-
if (
|
|
692
|
-
|
|
692
|
+
const i = e(s).first();
|
|
693
|
+
if (i.length && i.text().trim().length > 200) {
|
|
694
|
+
m = i.text();
|
|
693
695
|
break;
|
|
694
696
|
}
|
|
695
697
|
}
|
|
696
|
-
return
|
|
698
|
+
return m || (m = e("body").text()), m = m.replace(/\s+/g, " ").trim().slice(0, 8e3), { url: h.url, title: t.title.trim(), description: t.description.trim(), content: m, focus: h.focus };
|
|
697
699
|
}
|
|
698
700
|
}, ye = {
|
|
699
701
|
name: "web_search",
|
|
@@ -707,12 +709,12 @@ const Y = {
|
|
|
707
709
|
headers: { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)", "Accept-Language": "en-US,en;q=0.9" }
|
|
708
710
|
}).then((n) => n.text());
|
|
709
711
|
let e, t = /<a .*?href="(.+?)".+?<\/a>/g;
|
|
710
|
-
const
|
|
712
|
+
const m = new v();
|
|
711
713
|
for (; (e = t.exec(r)) !== null; ) {
|
|
712
714
|
let n = /uddg=(.+)&?/.exec(decodeURIComponent(e[1]))?.[1];
|
|
713
|
-
if (n && (n = decodeURIComponent(n)), n &&
|
|
715
|
+
if (n && (n = decodeURIComponent(n)), n && m.add(n), m.size >= (h.length || 5)) break;
|
|
714
716
|
}
|
|
715
|
-
return
|
|
717
|
+
return m;
|
|
716
718
|
}
|
|
717
719
|
};
|
|
718
720
|
export {
|